@ledgerhq/coin-tron
Version:
Ledger Tron Coin integration
176 lines • 6.58 kB
JavaScript
import { log } from "@ledgerhq/logs";
import BigNumber from "bignumber.js";
import { getBlock as networkGetBlock, getBlockWithTransactions, getTransactionInfoByBlockNum, } from "../network";
import { encode58Check } from "../network/format";
import { inferAssetInfo } from "../network/trongrid/trongrid-adapters";
import { abiDecodeTrc20Transfer } from "../network/utils";
export async function getBlockInfo(height) {
if (!Number.isSafeInteger(height) || height <= 0) {
throw new Error(`Invalid block height: ${height}`);
}
const block = await networkGetBlock(height);
return {
height: block.height,
hash: block.hash,
time: block.time ?? new Date(0),
};
}
export async function getBlock(height) {
if (!Number.isSafeInteger(height) || height <= 0) {
throw new Error(`Invalid block height: ${height}`);
}
const [data, txInfos] = await Promise.all([
getBlockWithTransactions(height),
getTransactionInfoByBlockNum(height).catch(error => {
log("tron/getBlock", "Failed to fetch transaction info, falling back to ret fees", {
height,
error,
});
return [];
}),
]);
const header = data.block_header.raw_data;
const blockTimestamp = header.timestamp ?? 0;
const info = {
height: header.number ?? height,
hash: data.blockID,
time: blockTimestamp ? new Date(blockTimestamp) : new Date(0),
};
if (header.parentHash && info.height > 1) {
info.parent = { height: info.height - 1, hash: header.parentHash };
}
const rawTxs = data.transactions ?? [];
const txInfoById = buildTxInfoMap(txInfos);
const transactions = rawTxs
.map(tx => toBlockTransaction(tx, blockTimestamp, info.height, txInfoById))
.filter((tx) => tx !== null);
return { info, transactions };
}
function buildTxInfoMap(txInfos) {
return new Map(txInfos.map(tx => [tx.id, tx]));
}
function toBlockTransaction(tx, blockTimestamp, blockHeight, txInfoById) {
const txInfo = formatBlockTransaction(tx, blockTimestamp, blockHeight);
if (!txInfo)
return null;
const txDetail = txInfoById.get(tx.txID);
const fee = txDetail?.fee ?? tx.ret?.[0]?.fee ?? 0;
return {
hash: txInfo.txID,
failed: txInfo.hasFailed,
fees: BigInt(fee),
feesPayer: txInfo.from,
operations: txInfo.hasFailed ? [] : toBlockOperations(txInfo),
};
}
function formatBlockTransaction(tx, blockTimestamp, blockHeight) {
try {
const contract = tx.raw_data.contract[0];
if (!contract)
return null;
const type = contract.type;
const params = contract.parameter.value;
const ownerAddress = params.owner_address;
if (!ownerAddress)
return null;
const from = encode58Check(ownerAddress);
const contractRet = tx.ret?.[0]?.contractRet ?? "SUCCESS";
const hasFailed = contractRet !== "SUCCESS";
const isTrc20 = type === "TriggerSmartContract" && params.contract_address;
const isTrc10 = type === "TransferAssetContract";
const tokenType = isTrc10 ? "trc10" : isTrc20 ? "trc20" : undefined;
let to;
let value;
if (isTrc20 && params.data) {
const decoded = abiDecodeTrc20Transfer(params.data);
if (decoded) {
to = encode58Check(decoded.to);
value = decoded.amount;
}
else {
value = new BigNumber(0);
}
}
else {
to = params.to_address ? encode58Check(params.to_address) : undefined;
value = params.amount ? new BigNumber(params.amount) : new BigNumber(0);
}
const tokenId = isTrc10
? decodeHexAssetName(params.asset_name)
: isTrc20 && params.contract_address
? encode58Check(params.contract_address)
: undefined;
return {
txID: tx.txID,
date: new Date(blockTimestamp),
type,
tokenId,
tokenType,
tokenAddress: isTrc20 && params.contract_address ? encode58Check(params.contract_address) : undefined,
from,
to,
value,
blockHeight,
hasFailed,
};
}
catch (error) {
log("tron/getBlock", "formatBlockTransaction error", {
txId: tx.txID,
error,
});
return null;
}
}
function toBlockOperations(txInfo) {
if (isTransfer(txInfo) && txInfo.to && txInfo.value && !txInfo.value.isZero()) {
const asset = inferAssetInfo(txInfo);
const value = txInfo.value;
if (value.isNaN() || !value.isFinite()) {
return [{ type: "other", operationType: "NONE", contractType: txInfo.type }];
}
const amount = BigInt(value.integerValue().toFixed(0));
return [
{ type: "transfer", address: txInfo.from, peer: txInfo.to, asset, amount: -amount },
{ type: "transfer", address: txInfo.to, peer: txInfo.from, asset, amount },
];
}
const operationType = getOperationType(txInfo.type);
return [{ type: "other", operationType: operationType, contractType: txInfo.type }];
}
function isTransfer(txInfo) {
return (txInfo.type === "TransferContract" ||
txInfo.type === "TransferAssetContract" ||
(txInfo.type === "TriggerSmartContract" && txInfo.tokenType === "trc20"));
}
function getOperationType(contractType) {
switch (contractType) {
case "ContractApproval":
return "APPROVE";
case "ExchangeTransactionContract":
return "OUT";
case "VoteWitnessContract":
return "VOTE";
case "WithdrawBalanceContract":
return "REWARD";
case "FreezeBalanceContract":
case "FreezeBalanceV2Contract":
return "FREEZE";
case "UnfreezeBalanceV2Contract":
return "UNFREEZE";
case "WithdrawExpireUnfreezeContract":
return "WITHDRAW_EXPIRE_UNFREEZE";
case "UnDelegateResourceContract":
return "UNDELEGATE_RESOURCE";
case "UnfreezeBalanceContract":
return "LEGACY_UNFREEZE";
default:
return "NONE";
}
}
function decodeHexAssetName(hexAssetName) {
if (!hexAssetName)
return undefined;
return Buffer.from(hexAssetName, "hex").toString("utf8");
}
//# sourceMappingURL=getBlock.js.map