@btc-vision/transaction
Version:
OPNet transaction library allows you to create and sign transactions for the OPNet network.
167 lines (166 loc) • 5.99 kB
JavaScript
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 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;
}
}