UNPKG

chaingate

Version:

A complete TypeScript library for connecting to and making transactions on different blockchains

220 lines 9.65 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.UtxoTransaction = void 0; const UtxoFee_1 = require("./UtxoFee"); const Utils_1 = require("../../../../InternalUtils/Utils"); const btc = __importStar(require("@scure/btc-signer")); const Transaction_1 = require("../../Transaction"); const UtxoBroadcastedTransaction_1 = require("./UtxoBroadcastedTransaction"); const utils_1 = require("./utils"); const NumberLike_1 = require("../../../../InternalUtils/NumberLike"); class UtxoTransaction extends Transaction_1.Transaction { utils; state; privateKeyProvider; networkParams; addressToNative; constructor(utils, fromAddress, toAddress, amount, networkParams, privateKeyProvider, addressToNative) { super(fromAddress, toAddress, amount); this.utils = utils; this.state = { utxos: [], page: 0, crawled: false }; this.networkParams = networkParams; this.privateKeyProvider = privateKeyProvider; this.addressToNative = addressToNative; } async broadcast(fee) { if (typeof fee === 'string') fee = (this._suggestedFees ?? (await this.buildSuggestedFees()))[fee]; // Param passed is fee level await this.findUtxos(fee.feePerKb); const transaction = this.createTransaction(fee.feePerKb); const txSigned = await this.sign(transaction.vins, transaction.vouts); // Broadcast transaction const broadcastTx = await this.utils.broadcastTransaction(txSigned); // Add vins to spent cache const spentTxoCache = this.utils.getSpentTxoCache(); for (const vin of transaction.vins) { spentTxoCache.push({ txid: vin.txid, n: vin.n, amount: vin.amount, address: this.fromAddress, script: vin.script, }); } // Add vouts to utxo cache const utxoCache = this.utils.getUtxoCache(); for (const [n, vout] of transaction.vouts.entries()) { const script = this.utils.addressToScript(vout.address); utxoCache.push({ txid: broadcastTx.transactionId, n: n, amount: vout.amount, address: vout.address, script: script, }); } return new UtxoBroadcastedTransaction_1.UtxoBroadcastedTransaction(this.utils, broadcastTx.transactionId); } async fee(fee, unit) { const currencySymbol = this.utils.currencyInfo.symbol; if (typeof fee == 'string') fee = this.utils.amount((0, NumberLike_1.toDecimal)(fee), currencySymbol); const feeBase = fee.baseAmount; let feePerKb; if (unit == 'satoshi/kB') feePerKb = this.utils.amount(feeBase, 'btc'); else if (unit == 'satoshi/byte') feePerKb = this.utils.amount((0, NumberLike_1.toDecimal)(feeBase).div(1e8).mul(1000), currencySymbol); else if (unit == `${this.utils.currencyInfo.symbol}/kB`) feePerKb = this.utils.amount((0, NumberLike_1.toDecimal)(feeBase), currencySymbol); else if (unit == `${this.utils.currencyInfo.symbol}/byte`) feePerKb = this.utils.amount((0, NumberLike_1.toDecimal)(feeBase).mul(1000), currencySymbol); else throw new Error('Unsupported unit'); return this.feePerKb(feePerKb, null); } async buildSuggestedFees() { const feeRates = await this.utils.getFeeRate(); return { low: await this.feePerKb(feeRates.low.feePerKb, feeRates.low.confirmationTimeSecs), normal: await this.feePerKb(feeRates.normal.feePerKb, feeRates.normal.confirmationTimeSecs), high: await this.feePerKb(feeRates.high.feePerKb, feeRates.high.confirmationTimeSecs), maximum: await this.feePerKb(feeRates.maximum.feePerKb, feeRates.maximum.confirmationTimeSecs), }; } async feePerKb(feePerKb, confirmationTimeSecs) { const currencySymbol = this.utils.currencyInfo.symbol; await this.findUtxos(feePerKb); const selected = this.createTransaction(feePerKb); return new UtxoFee_1.UtxoFee(feePerKb, true, !!selected, confirmationTimeSecs, selected ? this.utils.amount(selected?.feeBase, currencySymbol) : null); } async findUtxos(feePerKb) { // 1) Check if the provided UTXOs already suffice if (this.createTransaction(feePerKb)) { return; } // 2) Otherwise, keep fetching until we either have enough or get no more UTXOs while (!this.state.crawled) { const utxosByAddress = await this.utils.addressUtxos(this.fromAddress, this.state.page); // If no new UTXOs are returned, break out of loop if (utxosByAddress.utxos.length === 0) { this.state.crawled = true; break; } // Push all new UTXOs into the state's UTXOs array for (const utxo of utxosByAddress.utxos) { // If the output is already spent by this wallet, take it out if (this.utils.getSpentTxoCache().find((t) => t.txid === utxo.txid && t.n == utxo.n)) continue; // Otherwise, add to utxos this.state.utxos.push({ txid: utxo.txid, amount: utxo.amount.baseAmount, script: utxo.script, n: utxo.n, }); } // Check again if we now have enough if (this.createTransaction(feePerKb)) return; // Increment the page so next time we fetch the next "page" this.state.page++; } // Add cached UTXOs (used only as a last resort to avoid spending unconfirmed outputs) for (const utxo of this.utils .getUtxoCache() .filter((t) => t.address === this.fromAddress)) { if (this.state.utxos.some((t) => t.txid === utxo.txid && t.n === utxo.n)) continue; this.state.utxos.push({ txid: utxo.txid, amount: utxo.amount, script: utxo.script, n: utxo.n, }); } } createTransaction(feePerKb) { // IMPROVE: Do not allow dust outputs const vins = this.state.utxos.map((utxo) => ({ txid: (0, Utils_1.hexToBytes)(utxo.txid), index: utxo.n, witnessUtxo: { script: utxo.script, amount: BigInt((0, utils_1.toSatoshi)((0, NumberLike_1.toDecimal)(utxo.amount.toString())).toString()), }, })); const vouts = [ { address: this.addressToNative ? this.addressToNative(this.toAddress) : this.toAddress, amount: BigInt(this.amount.minimalUnitAmount.toString()), }, ]; const selected = btc.selectUTXO(vins, vouts, 'default', { changeAddress: this.addressToNative ? this.addressToNative(this.fromAddress) : this.fromAddress, feePerByte: BigInt(feePerKb.minimalUnitAmount.div(1000).round().toString()), bip69: true, createTx: true, allowLegacyWitnessUtxo: true, network: this.networkParams, }); if (!selected) return null; return { vins: selected.inputs.map((input) => ({ txid: (0, Utils_1.bytesToHex)(input.txid, false), amount: (0, utils_1.toBase)(input.witnessUtxo.amount), n: input.index, script: input.witnessUtxo.script, })), vouts: selected.outputs.map((output) => { if (!('address' in output)) throw new Error(); return { amount: (0, utils_1.toBase)(output.amount), address: output.address, }; }), feeBase: (0, utils_1.toBase)(selected.fee), }; } } exports.UtxoTransaction = UtxoTransaction; //# sourceMappingURL=UtxoTransaction.js.map