@ledgerhq/coin-hedera
Version:
Ledger Hedera Coin integration
193 lines • 7.91 kB
JavaScript
;
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