UNPKG

@ledgerhq/coin-hedera

Version:
193 lines 7.91 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getBlockV2 = getBlockV2; const constants_1 = require("../constants"); const api_1 = require("../network/api"); const hgraph_1 = require("../network/hgraph"); const utils_1 = require("../network/utils"); const getBlockInfo_1 = require("./getBlockInfo"); const utils_2 = require("./utils"); function isStakingTransactionType(item) { return item.type === "mirror" && item.data.name === constants_1.HEDERA_TRANSACTION_NAMES.UpdateAccount; } function getMirrorTransaction(item) { return item.type === "mirror" ? item.data : item.data.mirrorTransaction; } function createBlockOperationFromCoinTransfer({ payerAccount, chargedFee, transfer, rewardTransfers, }) { const address = transfer.account; const reward = rewardTransfers.find(r => r.account === address); const asset = { type: "native", }; // adjust the transfer amount: // - exclude fee from payer's operation amount (fees are accounted for separately, so operations must not represent fees) // - subtract staking rewards from the amount as they are represented as separate operations const feeAdjustment = payerAccount === address ? BigInt(chargedFee) : BigInt(0); const rewardAdjustment = BigInt(reward?.amount ?? 0); const amount = BigInt(transfer.amount) + feeAdjustment - rewardAdjustment; return { type: "transfer", address, asset, amount, }; } function createBlockOperationFromHTSTokenTransfer({ transfer, }) { const amount = BigInt(transfer.amount); const address = transfer.account; const asset = { type: "hts", assetReference: transfer.token_id, }; return { type: "transfer", address, asset, amount, }; } function createBlockOperationFromERC20TokenTransfer({ transfer, }) { const amount = BigInt(transfer.amount); const recipient = transfer.receiver_account_id ? (0, utils_2.toEntityId)({ num: transfer.receiver_account_id }) : transfer.receiver_evm_address; const sender = transfer.sender_account_id ? (0, utils_2.toEntityId)({ num: transfer.sender_account_id }) : transfer.sender_evm_address; const asset = { type: "erc20", assetReference: transfer.token_evm_address, }; // if we don't have either sender or recipient info, we cannot create a meaningful operation, so we skip it if (!sender || !recipient) { return []; } return [ { type: "transfer", address: recipient, asset, amount, }, { type: "transfer", address: sender, asset, amount: -amount, }, ]; } function createStakingRewardOperations(tx) { return tx.staking_reward_transfers.map(rewardTransfer => ({ type: "transfer", address: rewardTransfer.account, asset: { type: "native" }, amount: BigInt(rewardTransfer.amount), })); } async function getBlockV2(height) { const { start, end } = (0, utils_2.getDateRangeFromBlockHeight)(height); // block data should be immutable: do not allow querying blocks on non-finalized time range if (end.getTime() > Date.now() - constants_1.FINALITY_MS) { throw new Error(`Block ${height} is not available yet`); } const latestHgraphIndexedTimestampNs = await hgraph_1.hgraphClient.getLatestIndexedConsensusTimestamp(); const startSeconds = (0, utils_2.millisToSeconds)(start.getTime()); const endSeconds = (0, utils_2.millisToSeconds)(end.getTime()); const endNanos = (0, utils_2.secondsToNanos)(endSeconds); const limit = 100; const order = "desc"; // do not allow querying blocks if hgraph is not fully synced up to the end of the block time range if (latestHgraphIndexedTimestampNs.lt(endNanos)) { throw new Error(`Block ${height} has no ERC20 synced yet (${latestHgraphIndexedTimestampNs})`); } const [blockInfo, mirrorTransactions, enrichedERC20Transfers] = await Promise.all([ (0, getBlockInfo_1.getBlockInfo)(height), api_1.apiClient.getTransactionsByTimestampRange({ startTimestamp: `gte:${startSeconds}`, endTimestamp: `lt:${endSeconds}`, limit, order, }), hgraph_1.hgraphClient .getERC20TransfersByTimestampRange({ startTimestamp: startSeconds.toFixed(9), endTimestamp: endSeconds.toFixed(9), limit, order, }) .then(erc20Transfers => (0, utils_1.enrichERC20Transfers)(erc20Transfers)), ]); const mergeResult = (0, utils_2.mergeTransactionsFromDifferentSources)({ mirrorTransactions, enrichedERC20Transfers, order, limit, latestHgraphIndexedTimestampNs, fetchAllPages: true, }); // analyze CRYPTOUPDATEACCOUNT transactions to distinguish staking operations from regular account updates. // this creates a map of transaction_hash -> StakingAnalysis to avoid repeated lookups. const stakingAnalyses = await Promise.all(mergeResult.merged.filter(isStakingTransactionType).map(async (item) => { const payerAccount = (0, utils_2.extractFeesPayer)(item.data); const analysis = await (0, utils_2.analyzeStakingOperation)(payerAccount, item.data); return [item.data.transaction_hash, analysis]; })); const stakingAnalysisMap = new Map(stakingAnalyses); const blockTransactions = mergeResult.merged.map(item => { const mirrorTx = getMirrorTransaction(item); const payerAccount = (0, utils_2.extractFeesPayer)(mirrorTx); const stakingAnalysis = stakingAnalysisMap.get(mirrorTx.transaction_hash); let operations; if (stakingAnalysis) { operations = [ { type: "other", operationType: stakingAnalysis.operationType, stakedNodeId: stakingAnalysis.targetStakingNodeId, previousStakedNodeId: stakingAnalysis.previousStakingNodeId, stakedAmount: stakingAnalysis.stakedAmount, }, ]; } else { const allTransfers = [ ...mirrorTx.transfers, ...mirrorTx.token_transfers, ...(item.type === "erc20" ? item.data.transfers : []), ]; operations = allTransfers.flatMap(transfer => { if ("token_evm_address" in transfer) { return createBlockOperationFromERC20TokenTransfer({ transfer }); } else if ("token_id" in transfer) { return createBlockOperationFromHTSTokenTransfer({ transfer }); } else { return createBlockOperationFromCoinTransfer({ payerAccount, transfer, chargedFee: mirrorTx.charged_tx_fee, rewardTransfers: mirrorTx.staking_reward_transfers, }); } }); } // add staking reward operations if present (can occur on any transaction type) const rewardOperations = createStakingRewardOperations(mirrorTx); operations.push(...rewardOperations); return { hash: mirrorTx.transaction_hash, failed: mirrorTx.result !== "SUCCESS", operations, fees: BigInt(mirrorTx.charged_tx_fee), feesPayer: payerAccount, details: { memo: (0, utils_2.getMemoFromBase64)(mirrorTx.memo_base64) }, }; }); return { info: blockInfo, transactions: blockTransactions, }; } //# sourceMappingURL=getBlock.v2.js.map