@zebec-network/exchange-card-sdk
Version:
An sdk for purchasing silver card in zebec
196 lines (195 loc) • 7.66 kB
JavaScript
;
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;