UNPKG

@bronlabs/intents-sdk

Version:
131 lines 5.28 kB
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