blockchain-api
Version:
API utilities for interacting with the Exatechl2 blockchain
315 lines (311 loc) • 13.2 kB
JavaScript
;
/**
* 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}`);
}
}