UNPKG

@thorwallet/xchain-litecoin

Version:

Custom Litecoin client and utilities used by XChainJS clients

276 lines 9.19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPrefix = exports.getDefaultFees = exports.getDefaultFeesWithRates = exports.calcFee = exports.broadcastTx = exports.buildTx = exports.scanUTXOs = exports.validateAddress = exports.getBalance = exports.ltcNetwork = exports.isTestnet = exports.arrayAverage = exports.getFee = exports.compileMemo = exports.LTC_DECIMAL = void 0; const tslib_1 = require("tslib"); const Litecoin = tslib_1.__importStar(require("bitcoinjs-lib")); // https://github.com/bitcoinjs/bitcoinjs-lib const sochain = tslib_1.__importStar(require("./sochain-api")); const nodeApi = tslib_1.__importStar(require("./node-api")); const xchain_util_1 = require("@thorwallet/xchain-util"); const const_1 = require("./const"); const coininfo_1 = tslib_1.__importDefault(require("coininfo")); exports.LTC_DECIMAL = 8; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const accumulative_1 = tslib_1.__importDefault(require("coinselect/accumulative")); const TX_EMPTY_SIZE = 4 + 1 + 1 + 4; //10 const TX_INPUT_BASE = 32 + 4 + 1 + 4; // 41 const TX_INPUT_PUBKEYHASH = 107; const TX_OUTPUT_BASE = 8 + 1; //9 const TX_OUTPUT_PUBKEYHASH = 25; function inputBytes(input) { return TX_INPUT_BASE + (input.witnessUtxo.script ? input.witnessUtxo.script.length : TX_INPUT_PUBKEYHASH); } /** * Compile memo. * * @param {string} memo The memo to be compiled. * @returns {Buffer} The compiled memo. */ const compileMemo = (memo) => { const data = Buffer.from(memo, 'utf8'); // converts MEMO to buffer return Litecoin.script.compile([Litecoin.opcodes.OP_RETURN, data]); // Compile OP_RETURN script }; exports.compileMemo = compileMemo; /** * Get the transaction fee. * * @param {UTXOs} inputs The UTXOs. * @param {FeeRate} feeRate The fee rate. * @param {Buffer} data The compiled memo (Optional). * @returns {number} The fee amount. */ function getFee(inputs, feeRate, data = null) { let sum = TX_EMPTY_SIZE + inputs.reduce(function (a, x) { return a + inputBytes(x); }, 0) + inputs.length + // +1 byte for each input signature TX_OUTPUT_BASE + TX_OUTPUT_PUBKEYHASH + TX_OUTPUT_BASE + TX_OUTPUT_PUBKEYHASH; if (data) { sum += TX_OUTPUT_BASE + data.length; } const fee = sum * feeRate; return fee > const_1.MIN_TX_FEE ? fee : const_1.MIN_TX_FEE; } exports.getFee = getFee; /** * Get the average value of an array. * * @param {Array<number>} array * @returns {number} The average value. */ function arrayAverage(array) { let sum = 0; array.forEach((value) => (sum += value)); return sum / array.length; } exports.arrayAverage = arrayAverage; /** * Check if give network is a testnet. * * @param {Network} network * @returns {boolean} `true` or `false` */ const isTestnet = (network) => { return network === 'testnet'; }; exports.isTestnet = isTestnet; /** * Get Litecoin network to be used with bitcoinjs. * * @param {Network} network * @returns {Litecoin.Network} The LTC network. */ const ltcNetwork = (network) => { return exports.isTestnet(network) ? coininfo_1.default.litecoin.test.toBitcoinJS() : coininfo_1.default.litecoin.main.toBitcoinJS(); }; exports.ltcNetwork = ltcNetwork; /** * Get the balances of an address. * * @param {AddressParams} params * @returns {Array<Balance>} The balances of the given address. */ const getBalance = (params) => tslib_1.__awaiter(void 0, void 0, void 0, function* () { try { const balance = yield sochain.getBalance(params); return [ { asset: xchain_util_1.AssetLTC, amount: balance, }, ]; } catch (error) { return Promise.reject(new Error('Invalid address')); } }); exports.getBalance = getBalance; /** * Validate the LTC address. * * @param {string} address * @param {Network} network * @returns {boolean} `true` or `false`. */ const validateAddress = (address, network) => { try { Litecoin.address.toOutputScript(address, exports.ltcNetwork(network)); return true; } catch (error) { return false; } }; exports.validateAddress = validateAddress; /** * Scan UTXOs from sochain. * * @param {AddressParams} params * @returns {Array<UTXO>} The UTXOs of the given address. */ const scanUTXOs = (params) => tslib_1.__awaiter(void 0, void 0, void 0, function* () { const utxos = yield sochain.getUnspentTxs(params); return utxos.map((utxo) => ({ hash: utxo.txid, index: utxo.output_no, value: xchain_util_1.assetToBase(xchain_util_1.assetAmount(utxo.value, exports.LTC_DECIMAL)).amount().toNumber(), witnessUtxo: { value: xchain_util_1.assetToBase(xchain_util_1.assetAmount(utxo.value, exports.LTC_DECIMAL)).amount().toNumber(), script: Buffer.from(utxo.script_hex, 'hex'), }, })); }); exports.scanUTXOs = scanUTXOs; /** * Build transcation. * * @param {BuildParams} params The transaction build options. * @returns {Transaction} */ const buildTx = ({ amount, recipient, memo, feeRate, sender, network, sochainUrl, }) => tslib_1.__awaiter(void 0, void 0, void 0, function* () { try { if (!exports.validateAddress(recipient, network)) { return Promise.reject(new Error('Invalid address')); } const utxos = yield exports.scanUTXOs({ sochainUrl, network, address: sender }); if (utxos.length === 0) { return Promise.reject(Error('No utxos to send')); } const feeRateWhole = Number(feeRate.toFixed(0)); const compiledMemo = memo ? exports.compileMemo(memo) : null; const targetOutputs = []; //1. output to recipient targetOutputs.push({ address: recipient, value: amount.amount().toNumber(), }); //2. add output memo to targets (optional) if (compiledMemo) { targetOutputs.push({ script: compiledMemo, value: 0 }); } const { inputs, outputs } = accumulative_1.default(utxos, targetOutputs, feeRateWhole); // .inputs and .outputs will be undefined if no solution was found if (!inputs || !outputs) { return Promise.reject(Error('Balance insufficient for transaction')); } const psbt = new Litecoin.Psbt({ network: exports.ltcNetwork(network) }); // Network-specific //Inputs inputs.forEach((utxo) => psbt.addInput({ hash: utxo.hash, index: utxo.index, witnessUtxo: utxo.witnessUtxo, })); // Outputs outputs.forEach((output) => { if (!output.address) { //an empty address means this is the change ddress output.address = sender; } if (!output.script) { psbt.addOutput(output); } else { //we need to add the compiled memo this way to //avoid dust error tx when accumulating memo output with 0 value if (compiledMemo) { psbt.addOutput({ script: compiledMemo, value: 0 }); } } }); return { psbt, utxos }; } catch (e) { return Promise.reject(e); } }); exports.buildTx = buildTx; /** * Broadcast the transaction. * * @param {BroadcastTxParams} params The transaction broadcast options. * @returns {TxHash} The transaction hash. */ const broadcastTx = (params) => tslib_1.__awaiter(void 0, void 0, void 0, function* () { return yield nodeApi.broadcastTx(params); }); exports.broadcastTx = broadcastTx; /** * Calculate fees based on fee rate and memo. * * @param {FeeRate} feeRate * @param {string} memo * @returns {BaseAmount} The calculated fees based on fee rate and the memo. */ const calcFee = (feeRate, memo) => { const compiledMemo = memo ? exports.compileMemo(memo) : null; const fee = getFee([], feeRate, compiledMemo); return xchain_util_1.baseAmount(fee); }; exports.calcFee = calcFee; /** * Get the default fees with rates. * * @returns {FeesWithRates} The default fees and rates. */ const getDefaultFeesWithRates = () => { const rates = { fastest: 50, fast: 20, average: 10, }; const fees = { type: 'byte', fast: exports.calcFee(rates.fast), average: exports.calcFee(rates.average), fastest: exports.calcFee(rates.fastest), }; return { fees, rates, }; }; exports.getDefaultFeesWithRates = getDefaultFeesWithRates; /** * Get the default fees. * * @returns {Fees} The default fees. */ const getDefaultFees = () => { const { fees } = exports.getDefaultFeesWithRates(); return fees; }; exports.getDefaultFees = getDefaultFees; /** * Get address prefix based on the network. * * @param {string} network * @returns {string} The address prefix based on the network. * **/ const getPrefix = (network) => (network === 'testnet' ? 'tltc1' : 'ltc1'); exports.getPrefix = getPrefix; //# sourceMappingURL=utils.js.map