UNPKG

blockchain-api

Version:

API utilities for interacting with the Exatechl2 blockchain

315 lines (311 loc) 13.2 kB
"use strict"; /** * Transaction Operations */ Object.defineProperty(exports, "__esModule", { value: true }); exports.getTransactionByHash = getTransactionByHash; exports.getTransactionReceipt = getTransactionReceipt; exports.getLatestTransactions = getLatestTransactions; exports.getGasPrice = getGasPrice; exports.formatTransactionsForDisplay = formatTransactionsForDisplay; exports.getLatestTransactionsFromExplorer = getLatestTransactionsFromExplorer; const rpc_1 = require("../../core/rpc"); const explorerApi_1 = require("../../core/explorerApi"); const blocks_1 = require("../blocks/blocks"); const formatters_1 = require("../../utils/formatters"); const logger_1 = require("../../utils/logger"); /** * @param {string} txHash * @returns {Promise<Transaction>} */ async function getTransactionByHash(txHash) { return (0, rpc_1.callRpc)('eth_getTransactionByHash', [txHash]); } /** * @param {string} txHash * @returns {Promise<TransactionReceipt>} */ async function getTransactionReceipt(txHash) { return (0, rpc_1.callRpc)('eth_getTransactionReceipt', [txHash]); } /** * @param {number} count * @returns {Promise<Transaction[]>} */ async function getLatestTransactions(count = 10) { try { const latestBlockHex = await (0, blocks_1.getLatestBlockNumber)(); const latestBlock = (0, formatters_1.parseHexToDecimal)(latestBlockHex); const transactions = []; const blocksToFetch = Math.min(count, 10); for (let i = 0; i < blocksToFetch; i++) { const blockNumber = latestBlock - i; const blockData = await (0, blocks_1.getBlockByNumber)(blockNumber, true); if (blockData && blockData.transactions) { const blockTimestamp = blockData.timestamp; const txsWithTimestamp = blockData.transactions.map(tx => ({ ...tx, timestamp: blockTimestamp, blockHash: blockData.hash, })); transactions.push(...txsWithTimestamp); if (transactions.length >= count) { break; } } } return transactions.slice(0, count); } catch (error) { logger_1.logger.error('Error getting latest transactions:', error); throw error; } } /** * @returns {Promise<string>} */ async function getGasPrice() { return (0, rpc_1.callRpc)('eth_gasPrice'); } /** * @param {Transaction[]} transactions * @returns {Transaction[]} */ function formatTransactionsForDisplay(transactions) { return transactions.map(tx => { let txFee = '0'; const gasUsed = tx.gasUsed || '0x0'; const gasPrice = tx.gasPrice || tx.effectiveGasPrice || '0x0'; const gas = tx.gas || '0x0'; if (tx.gasUsed && tx.effectiveGasPrice) { const gasUsedNum = (0, formatters_1.parseHexToDecimal)(tx.gasUsed); const gasPriceNum = (0, formatters_1.parseHexToDecimal)(tx.effectiveGasPrice); const feeValue = gasUsedNum * gasPriceNum; txFee = (0, formatters_1.formatWeiToEth)(feeValue, 8).replace(' tEXT', ''); } else if (tx.gas && tx.gasPrice) { const gasNum = (0, formatters_1.parseHexToDecimal)(tx.gas); const gasPriceNum = (0, formatters_1.parseHexToDecimal)(tx.gasPrice); const feeValue = gasNum * gasPriceNum; txFee = (0, formatters_1.formatWeiToEth)(feeValue, 8).replace(' tEXT', ''); } let status = 'pending'; if (tx.blockNumber) { const txStatus = tx.status; status = txStatus === '0x1' ? 'success' : txStatus === '0x0' ? 'failed' : 'success'; } const capitalizedStatus = status.charAt(0).toUpperCase() + status.slice(1); let valueFormatted; try { if (tx.value) { valueFormatted = (0, formatters_1.formatWeiToEth)(tx.value, 6); } else { valueFormatted = '0 tEXT'; } } catch (e) { logger_1.logger.error('Error parsing transaction value:', e); valueFormatted = '0 tEXT'; } const timestamp = (0, formatters_1.parseTimestampToMillis)(tx.timestamp || Date.now()); const age = (0, formatters_1.formatRelativeTime)(timestamp); let blockNumberFormatted; let blockNumberValue; try { if (tx.blockNumber) { blockNumberFormatted = tx.blockNumber.toString(); blockNumberValue = typeof tx.blockNumber === 'string' ? (tx.blockNumber.startsWith('0x') ? parseInt(tx.blockNumber, 16) : parseInt(tx.blockNumber)) : tx.blockNumber; } else { blockNumberFormatted = 'Pending'; blockNumberValue = null; } } catch (e) { logger_1.logger.error('Error parsing block number:', e); blockNumberFormatted = 'Unknown'; blockNumberValue = null; } let methodName = 'Transfer'; let methodSignature = ''; let methodDisplayName = ''; if (tx.method_name) { methodName = tx.method_name; if (methodName.includes('(')) { methodDisplayName = methodName.split('(')[0]; } else { methodDisplayName = methodName; } } else if (tx.method_signature) { methodName = tx.method_signature; methodSignature = tx.method_signature; if (methodName.includes('(')) { methodDisplayName = methodName.split('(')[0]; } else { methodDisplayName = methodName; } } else { if (tx.input && tx.input !== '0x') { methodName = 'Contract Call'; methodDisplayName = 'Contract Call'; if (tx.input.length >= 10) { methodSignature = tx.input.substring(0, 10); } if (!tx.to) { methodName = 'Contract Creation'; methodDisplayName = 'Contract Creation'; } } else { methodDisplayName = 'Transfer'; } } if (methodDisplayName.toLowerCase() === 'transfer') { methodDisplayName = 'Transfer'; } else if (methodDisplayName.toLowerCase() === 'approve') { methodDisplayName = 'Approve'; } else if (methodDisplayName.toLowerCase() === 'mint') { methodDisplayName = 'Mint'; } else if (methodDisplayName.toLowerCase() === 'withdraw') { methodDisplayName = 'Withdraw'; } else if (methodDisplayName.toLowerCase() === 'deposit') { methodDisplayName = 'Deposit'; } else if (methodDisplayName.toLowerCase() === 'swap') { methodDisplayName = 'Swap'; } const nonceDecimal = (0, formatters_1.parseHexToDecimal)(tx.nonce); return { hash: tx.hash, from: tx.from, to: tx.to || 'Contract Creation', value: valueFormatted, status, statusCapitalized: capitalizedStatus, timestamp, age, method: methodName, methodDisplayName, methodSignature: methodSignature, txFee: txFee === '0' ? '0 tEXT' : `${txFee} tEXT`, txnFee: txFee === '0' ? '0 tEXT' : `${txFee} tEXT`, blockNumber: blockNumberFormatted, block: blockNumberValue, gas, gasPrice, gasUsed, nonce: tx.nonce || '0x0', input: tx.input || '0x', gasUsedDecimal: (0, formatters_1.parseHexToDecimal)(gasUsed), gasPriceGwei: (0, formatters_1.parseHexToDecimal)(gasPrice) / 1e9 > 0 ? ((0, formatters_1.parseHexToDecimal)(gasPrice) / 1e9).toFixed(2) : '0', gasDecimal: (0, formatters_1.parseHexToDecimal)(gas), nonceDecimal }; }); } /** * @param {number} page * @param {number} perPage * @param {boolean} debug * @returns {Promise<PaginatedTransactions>} */ async function getLatestTransactionsFromExplorer(page = 1, perPage = 10, debug = false) { try { if (debug) logger_1.logger.debug(`Fetching transactions: page=${page}, perPage=${perPage}`); const response = await (0, explorerApi_1.callExplorerApi)('transactions', { limit: perPage, offset: (page - 1) * perPage }); if (debug) { logger_1.logger.debug('API Response Structure:', JSON.stringify(response).substring(0, 200) + '...'); logger_1.logger.debug('Response received, processing transactions...'); } let txData = []; if (Array.isArray(response)) { txData = response; } else if (response.data && Array.isArray(response.data)) { txData = response.data; } else if (response.transactions && Array.isArray(response.transactions)) { txData = response.transactions; } else { logger_1.logger.error('Unexpected API response format:', response); throw new Error('Unexpected API response format. Check logs for details.'); } if (debug) logger_1.logger.debug(`Processing ${txData.length} transactions...`); const transactions = txData.map((txData) => { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p; const transaction = { hash: txData.hash, blockNumber: txData.block_number ? (((_b = (_a = txData.block_number).startsWith) === null || _b === void 0 ? void 0 : _b.call(_a, '0x')) ? txData.block_number : `0x${parseInt(txData.block_number).toString(16)}`) : undefined, blockHash: txData.block_hash, from: txData.from_address || txData.from, to: txData.to_address || txData.to, value: txData.value ? (((_d = (_c = txData.value).startsWith) === null || _d === void 0 ? void 0 : _d.call(_c, '0x')) ? txData.value : `0x${BigInt(txData.value).toString(16)}`) : '0x0', gas: txData.gas ? (((_f = (_e = txData.gas).startsWith) === null || _f === void 0 ? void 0 : _f.call(_e, '0x')) ? txData.gas : `0x${parseInt(txData.gas).toString(16)}`) : undefined, gasPrice: txData.gas_price ? (((_h = (_g = txData.gas_price).startsWith) === null || _h === void 0 ? void 0 : _h.call(_g, '0x')) ? txData.gas_price : `0x${BigInt(txData.gas_price).toString(16)}`) : undefined, gasUsed: txData.gas_used ? (((_k = (_j = txData.gas_used).startsWith) === null || _k === void 0 ? void 0 : _k.call(_j, '0x')) ? txData.gas_used : `0x${parseInt(txData.gas_used).toString(16)}`) : undefined, nonce: txData.nonce ? (((_m = (_l = txData.nonce).startsWith) === null || _m === void 0 ? void 0 : _m.call(_l, '0x')) ? txData.nonce : `0x${parseInt(txData.nonce).toString(16)}`) : undefined, transactionIndex: txData.transaction_index ? (((_p = (_o = txData.transaction_index).startsWith) === null || _p === void 0 ? void 0 : _p.call(_o, '0x')) ? txData.transaction_index : `0x${parseInt(txData.transaction_index).toString(16)}`) : undefined, input: txData.input || txData.input_data || txData.data || '0x', status: txData.status, timestamp: (0, formatters_1.standardizeTimestamp)(txData.timestamp, true) || Date.now(), method_signature: txData.method_signature, method_name: txData.method_name }; return transaction; }); const formattedTransactions = formatTransactionsForDisplay(transactions); const totalCount = (response.meta && response.meta.total) || response.total || response.totalCount || response.count || (response.meta && response.meta.count) || transactions.length; const totalPages = (response.meta && response.meta.total_pages) || Math.ceil(totalCount / perPage); if (debug) logger_1.logger.debug(`Returning ${transactions.length} transactions, totalCount: ${totalCount}, totalPages: ${totalPages}`); return { transactions: formattedTransactions, totalCount, totalPages, page, perPage }; } catch (error) { logger_1.logger.error('Error fetching transactions from Explorer DB:', error); throw new Error(`Failed to get transactions from Explorer DB: ${error.message}`); } }