UNPKG

multichain-controller

Version:

A Multichain crypto wallet library that supports Ethereum, Bitcoin, Solana, Waves and other EVM compatible blockchains E.g. Binance Smart Chain, Polygon, Avalanche etc.

292 lines (263 loc) 8.87 kB
import axios from 'axios'; import { sortUTXOs, UTXO } from '../utils/utxo'; import { DEFAULT_TIMEOUT } from './timeout'; const fetchUTXO = (network: string) => async ( txHash: string, vOut: number ): Promise<UTXO> => { const url = `https://api.blockchair.com/${network}/dashboards/transaction/${txHash}`; const response = ( await axios.get<TransactionResponse>(`${url}`, { timeout: DEFAULT_TIMEOUT, }) ).data; if (!response.data[txHash]) { throw new Error(`Transaction not found.`); } const tx = response.data[txHash]; let latestBlock = response.context.state; if (latestBlock === 0) { const statsUrl = `https://api.blockchair.com/${network}/stats`; const statsResponse = (await axios.get(statsUrl)).data; latestBlock = statsResponse.data.blocks - 1; } const confirmations = tx.transaction.block_id === -1 ? 0 : Math.max(latestBlock - tx.transaction.block_id + 1, 0); return { txHash, block: tx.transaction.block_id === -1 ? 0 : tx.transaction.block_id, amount: tx.outputs[vOut].value, confirmations, }; }; const fetchUTXOs = (network: string) => async ( address: string, confirmations: number ): Promise<readonly UTXO[]> => { const url = `https://api.blockchair.com/${network}/dashboards/address/${address}?limit=0,100`; const response = ( await axios.get<AddressResponse>(url, { timeout: DEFAULT_TIMEOUT }) ).data; let latestBlock = response.context.state; if (latestBlock === 0) { const statsUrl = `https://api.blockchair.com/${network}/stats`; const statsResponse = (await axios.get(statsUrl)).data; latestBlock = statsResponse.data.blocks - 1; } return response.data[address].utxo .map(utxo => ({ txHash: utxo.transaction_hash, amount: utxo.value, vOut: utxo.index, confirmations: utxo.block_id === -1 ? 0 : latestBlock - utxo.block_id + 1, })) .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; const fetchTXs = (network: string) => async ( address: string, confirmations: number = 0, limit: number = 25 ): Promise<readonly UTXO[]> => { const url = `https://api.blockchair.com/${network}/dashboards/address/${address}?limit=${limit},0`; const response = ( await axios.get<AddressResponse>(url, { timeout: DEFAULT_TIMEOUT }) ).data; let latestBlock = response.context.state; if (latestBlock === 0) { const statsUrl = `https://api.blockchair.com/${network}/stats`; const statsResponse = (await axios.get(statsUrl)).data; latestBlock = statsResponse.data.blocks - 1; } const txHashes = response.data[address].transactions; let txDetails: { [txHash: string]: TransactionResponse['data'][''] } = {}; // Fetch in sets of 10 for (let i = 0; i < Math.ceil(txHashes.length / 10); i++) { const txUrl = `https://api.blockchair.com/${network}/dashboards/transactions/${txHashes .slice(i * 10, (i + 1) * 10) .join(',')}`; const txResponse = ( await axios.get<TransactionResponse>(txUrl, { timeout: DEFAULT_TIMEOUT, }) ).data; txDetails = { ...txDetails, ...txResponse.data, }; } const received: UTXO[] = []; for (const txHash of txHashes) { const tx = txDetails[txHash]; const txConfirmations = tx.transaction.block_id === -1 ? 0 : Math.max(latestBlock - tx.transaction.block_id + 1, 0); for (let i = 0; i < tx.outputs.length; i++) { const vout = tx.outputs[i]; if (vout.recipient === address) { received.push({ txHash: tx.transaction.hash, amount: vout.value, vOut: i, confirmations: txConfirmations, }); } } } return received .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; export const broadcastTransaction = (network: string) => async ( txHex: string ): Promise<string> => { const url = `https://api.blockchair.com/${network}/push/transaction`; const response = await axios.post<{ data: { transaction_hash: string } }>( url, { data: txHex }, { timeout: DEFAULT_TIMEOUT } ); if ((response.data as any).error) { throw new Error((response.data as any).error); } return response.data.data.transaction_hash; }; enum Networks { BITCOIN = 'bitcoin', BITCOIN_CASH = 'bitcoin-cash', LITECOIN = 'litecoin', BITCOIN_SV = 'bitcoin-sv', DOGECOIN = 'dogecoin', DASH = 'dash', GROESTLCOIN = 'groestlcoin', BITCOIN_TESTNET = 'bitcoin/testnet', } export const Blockchair = { networks: Networks, fetchUTXO, fetchUTXOs, broadcastTransaction, fetchTXs, }; interface BlockchairContext { code: number; // 200 source: string; // "D" time: number; // 0.2793741226196289 limit: string; // "0,100" offset: string; // "0,0" results: number; // 0 state: number; // 611807 cache: { live: boolean; duration: number; since: string; until: string; time: null; }; api: { version: string; last_major_update: string; next_major_update: null | string; documentation: 'https://blockchair.com/api/docs'; notice?: string; }; } /** TYPES */ interface AddressResponse { data: { [addr: string]: { address: { type: 'pubkey'; script_hex: string; // "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac" balance: number; // 6820995737 balance_usd: number; // 527582.1930705917 received: number; // 6820995737 received_usd: number; // 15963.6287 spent: number; // 0 spent_usd: number; // 0 output_count: number; // 1924 unspent_output_count: number; // 1924 first_seen_receiving: string; // "2009-01-03 18:15:05" last_seen_receiving: string; // "2020-01-07 22:38:01" first_seen_spending: null; last_seen_spending: null; transaction_count: null; }; transactions: string[]; utxo: Array<{ block_id: number; // 611802, transaction_hash: string; // "f3c8e9b5964703f5634261a6769d6c9d836e3175fbfbebd204837aa15ef382f7" index: number; // 29 value: number; // 7043123 }>; }; }; context: BlockchairContext; } interface InputOrOutput { block_id: number; // 9 transaction_id: number; // 9 index: number; // 0 transaction_hash: string; // "0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9" date: string; // "2009-01-09" time: string; // "2009-01-09 03:54:39" value: number; // 5000000000 value_usd: number; // 0.5 recipient: string; // "12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S" type: string; // "pubkey" script_hex: string; // "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac" is_from_coinbase: boolean; // true is_spendable: boolean; // true is_spent: boolean; // true spending_block_id: number; // 170, spending_transaction_id: number; // 171, spending_index: number; // 0, spending_transaction_hash: string; // "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16" spending_date: string; // "2009-01-12" spending_time: string; // "2009-01-12 03:30:25" spending_value_usd: number; // 0.5 spending_sequence: number; // 4294967295 spending_signature_hex: string; // "47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901" spending_witness: string; // "" lifespan: number; // 257746 cdd: number; // 149.15856481481 } interface TransactionResponse { data: { [utxo: string]: { transaction: { block_id: number; // 170 id: number; // 171 hash: string; // "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16" date: string; // "2009-01-12" time: string; // "2009-01-12 03:30:25" size: number; // 275 weight: number; // 1100 version: number; // 1 lock_time: number; // 0 is_coinbase: boolean; // false has_witness: boolean; // false input_count: number; // 1 output_count: number; // 2 input_total: number; // 5000000000 input_total_usd: number; // 0.5 output_total: number; // 5000000000 output_total_usd: number; // 0.5 fee: number; // 0 fee_usd: number; // 0 fee_per_kb: number; // 0 fee_per_kb_usd: number; // 0 fee_per_kwu: number; // 0 fee_per_kwu_usd: number; // 0 cdd_total: number; // 149.15856481481 is_rbf: boolean; }; inputs: InputOrOutput[]; outputs: InputOrOutput[]; }; }; context: BlockchairContext; }