UNPKG

@zebec-network/exchange-card-sdk

Version:
196 lines (195 loc) 7.66 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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BitcoinService = void 0; const axios_1 = __importDefault(require("axios")); const bitcoin = __importStar(require("bitcoinjs-lib")); const constants_1 = require("../constants"); const apiHelpers_1 = require("../helpers/apiHelpers"); class BitcoinService { wallet; apiService; network; apiEndpoint; constructor(wallet, apiConfig, sdkOptions) { this.wallet = wallet; const sandbox = sdkOptions?.sandbox ?? false; this.apiService = new apiHelpers_1.ZebecCardAPIService(apiConfig, sandbox); this.network = sandbox ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; this.apiEndpoint = sandbox ? constants_1.BITCOIN_ENDPOINTS.Sandbox : constants_1.BITCOIN_ENDPOINTS.Production; } /** * Fetches a quote for Bitcoin transfer. * * @returns {Promise<Quote>} A promise that resolves to a Quote object. */ async fetchQuote() { const res = await this.apiService.fetchQuote("BTC"); return res; } /** * Fetches the Bitcoin vault address. * * @returns {Promise<{ address: string }>} A promise that resolves to the vault address. */ async fetchVault() { const data = await this.apiService.fetchVault("BTC"); return data; } async getUTXOs() { const response = await axios_1.default.get(`${this.apiEndpoint}/address/${this.wallet.address}/utxo`); console.log("utxos:", response.data); return Promise.all(response.data.map(async (utxo) => { const rawTx = await axios_1.default.get(`${this.apiEndpoint}/tx/${utxo.txid}/hex`); console.log("txHex:", rawTx.data); if (!rawTx.data) { throw new Error("Transaction not found"); } // const scriptPubKey = txResponse.data.vout[utxo.vout].scriptpubkey; return { txid: utxo.txid, vout: utxo.vout, value: utxo.value, rawTx: Buffer.from(rawTx.data, "hex"), }; })); } async getBalance() { const response = await axios_1.default.get(`${this.apiEndpoint}/address/${this.wallet.address}/utxo`); const utxos = response.data; return utxos.reduce((sum, utxo) => sum + utxo.value, 0); } /** * Transfers Bitcoin to the vault address. * * @param {string} amount - The amount of BTC to transfer in BTC units * @param {number} feeRate - Fee rate in satoshis per byte * @returns {Promise<string>} - A promise that resolves to the transaction hash * @throws {Error} If there is not enough balance or if the transaction fails. */ async transferBTC(amount, feeRate = 10) { // Convert BTC to satoshis const satoshisToSend = Math.floor(Number(amount) * 100_000_000); // Fetch deposit address const vault = await this.fetchVault(); console.log({ vault }); let retries = 0; const maxRetries = 5; let delay = 1000; const psbt = new bitcoin.Psbt({ network: this.network }); const utxos = await this.getUTXOs(); let inputAmount = 0; for (const utxo of utxos) { const transaction = bitcoin.Transaction.fromBuffer(utxo.rawTx); const script = transaction.outs[utxo.vout].script; const value = transaction.outs[utxo.vout].value; inputAmount += value; psbt.addInput({ hash: utxo.txid, index: utxo.vout, // nonWitnessUtxo: utxo.rawTx, witnessUtxo: { script: script, value: value, }, }); if (inputAmount >= satoshisToSend) break; } if (inputAmount < satoshisToSend) { throw new Error("Insufficient UTXO amount"); } // Add output for payment psbt.addOutput({ // address: vault.address, address: "tb1q6h8w53xzj74n28kg8qq3d78xxgrch8zd2km97d", value: satoshisToSend, }); // Add change output if necessary const estimatedFee = Math.ceil(psbt.toBuffer().length * feeRate); // Check wallet balance const balance = utxos.reduce((sum, utxo) => sum + utxo.value, 0); if (balance < satoshisToSend + estimatedFee) { throw new Error("Insufficient balance"); } const changeAmount = inputAmount - satoshisToSend - estimatedFee; if (changeAmount > 0) { psbt.addOutput({ address: this.wallet.address, value: changeAmount, }); } console.log("psbt:", JSON.stringify(psbt)); // Sign transaction const signedPsbt = await this.wallet.signTransaction(psbt); const tx = signedPsbt.finalizeAllInputs().extractTransaction(); while (retries < maxRetries) { try { // Broadcast transaction return this.wallet.broadcastTransaction(tx.toHex()); } catch (error) { console.debug("error: ", error); if (retries >= maxRetries) { throw error; } retries += 1; console.debug(`Retrying in ${delay / 1000} seconds...`); await new Promise((resolve) => setTimeout(resolve, delay)); delay *= 2; // Exponential backoff } } throw new Error("Max retries reached"); } /** * Gets the balance of the Bitcoin wallet. * * @returns {Promise<string>} - A promise that resolves to the wallet balance in BTC */ async getWalletBalance() { try { const balanceSats = await this.getBalance(); return (balanceSats / 100_000_000).toString(); } catch (error) { console.debug("Error fetching BTC balance:", error); return "0"; } } } exports.BitcoinService = BitcoinService;