UNPKG

@btc-vision/transaction

Version:

OPNet transaction library allows you to create and sign transactions for the OPNet network.

174 lines (173 loc) 6.17 kB
import { TransactionFactory } from '../opnet.js'; export class OPNetLimitedProvider { constructor(opnetAPIUrl) { this.opnetAPIUrl = opnetAPIUrl; this.utxoPath = 'address/utxos'; this.rpc = 'json-rpc'; } async fetchUTXO(settings) { if (settings.usePendingUTXO === undefined) { settings.usePendingUTXO = true; } if (settings.optimized === undefined) { settings.optimized = true; } const params = { method: 'GET', headers: { 'Content-Type': 'application/json', }, }; const url = `${this.opnetAPIUrl}/api/v1/${this.utxoPath}?address=${settings.address}&optimize=${settings.optimized ?? false}`; const resp = await fetch(url, params); if (!resp.ok) { throw new Error(`Failed to fetch UTXO data: ${resp.statusText}`); } const fetchedData = (await resp.json()); const allUtxos = settings.usePendingUTXO ? [...fetchedData.confirmed, ...fetchedData.pending] : fetchedData.confirmed; const unspentUTXOs = []; for (const utxo of allUtxos) { if (fetchedData.spentTransactions.some((spent) => spent.transactionId === utxo.transactionId && spent.outputIndex === utxo.outputIndex)) { continue; } unspentUTXOs.push(utxo); } if (unspentUTXOs.length === 0) { throw new Error('No UTXO found'); } const meetCriteria = unspentUTXOs.filter((utxo) => { return BigInt(utxo.value) >= settings.minAmount; }); if (meetCriteria.length === 0) { throw new Error('No UTXO found (minAmount)'); } const finalUTXOs = []; let currentAmount = 0n; const amountRequested = settings.requestedAmount; for (const utxo of meetCriteria) { const utxoValue = BigInt(utxo.value); if (utxoValue <= 0n) { continue; } currentAmount += utxoValue; finalUTXOs.push({ transactionId: utxo.transactionId, outputIndex: utxo.outputIndex, value: utxoValue, scriptPubKey: utxo.scriptPubKey, nonWitnessUtxo: Buffer.from(utxo.raw, 'base64'), }); if (currentAmount > amountRequested) { break; } } return finalUTXOs; } async fetchUTXOMultiAddr(settings) { const promises = []; for (const address of settings.addresses) { const params = { address: address, minAmount: settings.minAmount, requestedAmount: settings.requestedAmount, optimized: settings.optimized, usePendingUTXO: settings.usePendingUTXO, }; const promise = this.fetchUTXO(params).catch(() => { return []; }); promises.push(promise); } const utxos = await Promise.all(promises); const all = utxos.flat(); const finalUTXOs = []; let currentAmount = 0n; for (let i = 0; i < all.length; i++) { const utxo = all[i]; if (currentAmount >= settings.requestedAmount) { break; } currentAmount += utxo.value; finalUTXOs.push(utxo); } return finalUTXOs; } async broadcastTransaction(transaction, psbt) { const params = [transaction, psbt]; const result = await this.rpcMethod('btc_sendRawTransaction', params); if (!result) { return; } return result; } async getPreimage() { const result = await this.rpcMethod('btc_preimage', []); if (!result) { return; } return result; } async splitUTXOs(wallet, network, splitInputsInto, amountPerUTXO) { const utxoSetting = { addresses: [wallet.p2wpkh, wallet.p2tr], minAmount: 330n, requestedAmount: 1000000000000000n, }; const utxos = await this.fetchUTXOMultiAddr(utxoSetting); if (!utxos || !utxos.length) return { error: 'No UTXOs found' }; const amount = BigInt(splitInputsInto) * amountPerUTXO; const fundingTransactionParameters = { amount: amount, feeRate: 500, from: wallet.p2tr, utxos: utxos, signer: wallet.keypair, network, to: wallet.p2tr, splitInputsInto, priorityFee: 0n, gasSatFee: 330n, }; const transactionFactory = new TransactionFactory(); const fundingTx = await transactionFactory.createBTCTransfer(fundingTransactionParameters); const broadcastResponse = await this.broadcastTransaction(fundingTx.tx, false); if (!broadcastResponse) return { error: 'Could not broadcast transaction' }; return broadcastResponse; } async rpcMethod(method, paramsMethod) { const params = { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ jsonrpc: '2.0', method: method, params: paramsMethod, id: 1, }), }; const url = `${this.opnetAPIUrl}/api/v1/${this.rpc}`; const resp = await fetch(url, params); if (!resp.ok) { throw new Error(`Failed to fetch to rpc: ${resp.statusText}`); } const fetchedData = (await resp.json()); if (!fetchedData) { throw new Error('No data fetched'); } const result = fetchedData.result; if (!result) { throw new Error('No rpc parameters found'); } if ('error' in result) { throw new Error(`Error in fetching to rpc ${result.error}`); } return result; } }