@bronlabs/intents-sdk
Version:
SDK for Intents DeFi smart contracts
131 lines • 5.28 kB
JavaScript
import * as bitcoin from 'bitcoinjs-lib';
import { ECPairFactory } from 'ecpair';
import * as ecc from 'tiny-secp256k1';
import { log } from '../utils.js';
import { randomUUID } from 'node:crypto';
const ECPair = ECPairFactory(ecc);
export class BtcNetwork {
constructor(rpcUrl, confirmations = 6) {
this.nativeAssetDecimals = 8;
this.retryDelay = 15000;
this.rpcUrl = rpcUrl;
this.confirmations = confirmations;
}
async getDecimals(tokenAddress) {
if (tokenAddress !== "0x0") {
throw new Error("Don't support tokens for BTC network");
}
return this.nativeAssetDecimals;
}
async getTxData(txHash, tokenAddress, recipientAddress) {
if (tokenAddress !== "0x0") {
throw new Error("Don't support tokens for BTC network");
}
try {
const tx = await this.rpcCall('getrawtransaction', [txHash, true]);
if (!tx)
return;
const output = tx.vout.find(vout => vout.scriptPubKey.address === recipientAddress || vout.scriptPubKey.addresses?.includes(recipientAddress));
if (!output) {
log.warn(`Transaction ${txHash} has no output to ${recipientAddress}: ${JSON.stringify(tx.vout, null, 2)}`);
return {
to: recipientAddress,
token: tokenAddress,
amount: 0n,
confirmed: tx.confirmations >= this.confirmations
};
}
log.info(`Confirmations ${txHash}: ${tx.confirmations}`);
return {
to: recipientAddress,
token: tokenAddress,
amount: BigInt(Math.round(Number(output.value) * 1e8)),
confirmed: tx.confirmations >= this.confirmations
};
}
catch (error) {
log.warn(`Failed to get transaction data for ${txHash}: ${error}`);
return;
}
}
async transfer(privateKey, to, value, tokenAddress) {
if (tokenAddress !== "0x0") {
throw new Error("Don't support tokens for BTC network");
}
const keyPair = ECPair.fromWIF(privateKey);
const fromAddress = this.getAddressFromPrivateKey(privateKey);
const utxos = await this.getUtxos(fromAddress);
if (utxos.length === 0) {
throw new Error("No UTXOs available");
}
const targetAmount = value;
let inputAmount = 0n;
const feeRate = await this.getFeeRate();
const selectedUtxos = [];
for (const utxo of utxos) {
selectedUtxos.push(utxo);
inputAmount += utxo.value;
const estimatedFee = BigInt(selectedUtxos.length * 148 + 2 * 34 + 10) * feeRate;
if (inputAmount >= targetAmount + estimatedFee)
break;
}
const estimatedFee = BigInt(selectedUtxos.length * 148 + 2 * 34 + 10) * feeRate;
const changeAmount = inputAmount - targetAmount - estimatedFee;
if (inputAmount < targetAmount + estimatedFee) {
throw new Error(`Insufficient funds. Need ${targetAmount + estimatedFee}, have ${inputAmount}`);
}
const psbt = new bitcoin.Psbt();
for (const utxo of selectedUtxos) {
psbt.addInput({
hash: utxo.txid,
index: utxo.vout,
nonWitnessUtxo: Buffer.from(await this.rpcCall('getrawtransaction', [utxo.txid]), 'hex')
});
}
psbt.addOutput({ address: to, value: Number(targetAmount) });
if (changeAmount > 546) {
psbt.addOutput({ address: fromAddress, value: Number(changeAmount) });
}
selectedUtxos.forEach((_, index) => psbt.signInput(index, keyPair));
psbt.finalizeAllInputs();
return await this.rpcCall('sendrawtransaction', [psbt.extractTransaction().toHex()]);
}
async getFeeRate() {
try {
const feeEstimate = await this.rpcCall('estimatesmartfee', [6]);
return feeEstimate?.feerate ? BigInt(Math.ceil(feeEstimate.feerate * 100000000 / 1000)) : 10n;
}
catch {
return 10n;
}
}
async getUtxos(address) {
const utxos = await this.rpcCall('listunspent', [1, 9999999, [address]]);
return utxos.map((utxo) => ({
txid: utxo.txid,
vout: utxo.vout,
value: BigInt(Math.round(Number(utxo.amount) * 1e8)),
scriptPubKey: utxo.scriptPubKey
}));
}
getAddressFromPrivateKey(privateKey) {
return bitcoin.payments.p2pkh({ pubkey: ECPair.fromWIF(privateKey).publicKey }).address;
}
async rpcCall(method, params = []) {
const response = await fetch(this.rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '1.0',
id: randomUUID(),
method,
params
})
});
const { result, error } = await response.json();
if (error)
throw new Error(`RPC Error: ${error.message}`);
return result;
}
}
//# sourceMappingURL=btc.js.map