UNPKG

@ledgerhq/coin-tron

Version:
386 lines (352 loc) 12.5 kB
import { createHash } from "crypto"; import BigNumber from "bignumber.js"; import get from "lodash/get"; import { TronWeb, providers, utils } from "tronweb"; import coinConfig from "../config"; import { TronResources, UnFrozenInfo } from "../types"; export function createTronWeb(trongridUrl?: string): TronWeb { if (!trongridUrl) { trongridUrl = coinConfig.getCoinConfig().explorer.url; } const HttpProvider = providers.HttpProvider; const fullNode = new HttpProvider(trongridUrl); const solidityNode = new HttpProvider(trongridUrl); const eventServer = new HttpProvider(trongridUrl); return new TronWeb(fullNode, solidityNode, eventServer); } /** * Convert `raw_data_hex` value from {@link https://developers.tron.network/reference/createtransaction|createTransaction API} to `raw_data` value. * The function try to find the correct Protobuf deserialization to use for inner (Contract)[] object. * @param rawTx * @returns */ export async function decodeTransaction(rawTx: string): Promise<{ txID: string; raw_data: Record<string, any>; raw_data_hex: string; }> { const { Transaction } = (globalThis as unknown as any).TronWebProto; const transaction = Transaction.raw.deserializeBinary(Buffer.from(rawTx, "hex")); return { txID: createHash("sha256") .update(new Uint8Array(Buffer.from(rawTx, "hex"))) .digest("hex"), raw_data: convertTxFromRaw(transaction), raw_data_hex: rawTx, }; } /** * @see https://github.com/tronprotocol/protocol/blob/master/core/Tron.proto#L431 * @param tx */ function convertTxFromRaw(tx: any) { let transactionRawData: any = { ref_block_bytes: convertBufferToHex(tx.getRefBlockBytes()), ref_block_hash: convertBufferToHex(tx.getRefBlockHash()), expiration: tx.getExpiration(), contract: tx.getContractList().map(convertContractFromRaw), timestamp: tx.getTimestamp(), }; if (tx.getRefBlockNum()) { transactionRawData = { ...transactionRawData, ref_block_num: tx.getRefBlockNum(), }; } if (tx.getFeeLimit()) { transactionRawData = { ...transactionRawData, fee_limit: tx.getFeeLimit(), }; } if (tx.getData()) { transactionRawData = { ...transactionRawData, data: tx.getData(), }; } if (tx.getScripts()) { transactionRawData = { ...transactionRawData, scripts: tx.getScripts(), }; } return transactionRawData; } /** * @see https://github.com/tronprotocol/protocol/blob/master/core/contract/balance_contract.proto#L32 * @param contract */ function convertContractFromRaw(contract: any) { const contractType = convertNumberToContractType(contract.getType()); const value = contractType.convertFunction?.(contract); if (!value) { throw new Error( `Missing deserializer for this contract: "${contract.getParameter().getTypeUrl()}"`, ); } return { type: contractType.name, parameter: { value, type_url: contract.getParameter().getTypeUrl(), }, }; } type TransferValue = { owner_address: string }; type TrxTransferValue = TransferValue & { amount: number; to_address: string; }; function convertTransferContractFromRaw(contract: any): TrxTransferValue { const { TransferContract } = (globalThis as unknown as any).TronWebProto; const transferContract = TransferContract.deserializeBinary(contract.getParameter().getValue()); // Expected address format in Contract are in Hex and not in Base58, // despite what (tron API portal may say)[https://developers.tron.network/reference/createtransaction] return { amount: transferContract.getAmount(), owner_address: convertBufferToHex(transferContract.getOwnerAddress()), to_address: convertBufferToHex(transferContract.getToAddress()), }; } type Trc10TransferValue = TransferValue & { amount: number; asset_name: string; to_address: string; }; function convertTransferAssetContractFromRaw(contract: any): Trc10TransferValue { const { TransferAssetContract } = (globalThis as unknown as any).TronWebProto; const transferContract = TransferAssetContract.deserializeBinary( contract.getParameter().getValue(), ); // Expected address format in Contract are in Hex and not in Base58, // despite what (tron API portal may say)[https://developers.tron.network/reference/transferasset] return { amount: transferContract.getAmount(), asset_name: convertBufferToString(transferContract.getAssetName()), owner_address: convertBufferToHex(transferContract.getOwnerAddress()), to_address: convertBufferToHex(transferContract.getToAddress()), }; } type Trc20TransferValue = TransferValue & { data: string; contract_address: string; }; function convertTriggerSmartContractFromRaw(contract: any): Trc20TransferValue { const { TriggerSmartContract } = (globalThis as unknown as any).TronWebProto; const transferContract = TriggerSmartContract.deserializeBinary( contract.getParameter().getValue(), ); // Expected address format in Contract are in Hex and not in Base58, // despite what (tron API portal may say)[https://developers.tron.network/reference/triggersmartcontract] return { data: convertBufferToHex(transferContract.getData()), owner_address: convertBufferToHex(transferContract.getOwnerAddress()), contract_address: convertBufferToHex(transferContract.getContractAddress()), }; } type ContractInfo = { name: string; convertFunction?: (contract: any) => TrxTransferValue | Trc10TransferValue | Trc20TransferValue; }; /** * @see https://github.com/tronprotocol/protocol/blob/master/core/Tron.proto#L338 */ const CONTRACT_TYPE: Record<number, ContractInfo> = { 0: { name: "AccountCreateContract" }, 1: { name: "TransferContract", convertFunction: convertTransferContractFromRaw }, // Transfer TRX 2: { name: "TransferAssetContract", convertFunction: convertTransferAssetContractFromRaw }, // Transfer TRC-10 3: { name: "VoteAssetContract" }, 4: { name: "VoteWitnessContract" }, 5: { name: "WitnessCreateContract" }, 6: { name: "AssetIssueContract" }, 8: { name: "WitnessUpdateContract" }, 9: { name: "ParticipateAssetIssueContract" }, 10: { name: "AccountUpdateContract" }, 11: { name: "FreezeBalanceContract" }, 12: { name: "UnfreezeBalanceContract" }, 13: { name: "WithdrawBalanceContract" }, 14: { name: "UnfreezeAssetContract" }, 15: { name: "UpdateAssetContract" }, 16: { name: "ProposalCreateContract" }, 17: { name: "ProposalApproveContract" }, 18: { name: "ProposalDeleteContract" }, 19: { name: "SetAccountIdContract" }, 20: { name: "CustomContract" }, 30: { name: "CreateSmartContract" }, 31: { name: "TriggerSmartContract", convertFunction: convertTriggerSmartContractFromRaw }, // Transfer TRC-20 32: { name: "GetContract" }, 33: { name: "UpdateSettingContract" }, 41: { name: "ExchangeCreateContract" }, 42: { name: "ExchangeInjectContract" }, 43: { name: "ExchangeWithdrawContract" }, 44: { name: "ExchangeTransactionContract" }, 45: { name: "UpdateEnergyLimitContract" }, 46: { name: "AccountPermissionUpdateContract" }, 48: { name: "ClearABIContract" }, 49: { name: "UpdateBrokerageContract" }, 51: { name: "ShieldedTransferContract" }, 52: { name: "MarketSellAssetContract" }, 53: { name: "MarketCancelOrderContract" }, 54: { name: "FreezeBalanceV2Contract" }, 55: { name: "UnfreezeBalanceV2Contract" }, 56: { name: "WithdrawExpireUnfreezeContract" }, 57: { name: "DelegateResourceContract" }, 58: { name: "UnDelegateResourceContract" }, 59: { name: "CancelAllUnfreezeV2Contract" }, }; const convertNumberToContractType = (value: number): ContractInfo => CONTRACT_TYPE[value]; function convertBufferToHex(address: Buffer): string { return utils.bytes.byteArray2hexStr(new Uint8Array(address)).toLowerCase(); } function convertBufferToString(address: Buffer): string { return utils.bytes.bytesToString(new Uint8Array(address)); } export type AccountInfo = { account_resource?: { delegated_frozenV2_balance_for_energy?: number; frozen_balance_for_energy?: { frozen_balance: number; expire_time: number; }; }; delegated_frozenV2_balance_for_bandwidth?: number; frozen?: { frozen_balance: number; expire_time: number; }[]; frozenV2?: { type?: "ENERGY" | "TRON_POWER" | string; amount?: number; }[]; latest_withdraw_time?: number; unfrozenV2?: { type?: "ENERGY" | string; unfreeze_amount: number; unfreeze_expire_time: number; }[]; votes?: { vote_address: string; vote_count: number; }[]; }; export function getTronResources( acc: AccountInfo, ): Omit<TronResources, "energy" | "bandwidth" | "unwithdrawnReward" | "lastVotedDate" | "votes"> { const delegatedFrozenBandwidth = get(acc, "delegated_frozenV2_balance_for_bandwidth", undefined); const delegatedFrozenEnergy = get( acc, "account_resource.delegated_frozenV2_balance_for_energy", undefined, ); const frozenBalances: { type?: string; amount?: number }[] = get(acc, "frozenV2", []); const legacyFrozenBandwidth = get(acc, "frozen[0]", undefined); const legacyFrozenEnergy = get(acc, "account_resource.frozen_balance_for_energy", undefined); const legacyFrozen = { bandwidth: legacyFrozenBandwidth ? { amount: new BigNumber(legacyFrozenBandwidth.frozen_balance), expiredAt: new Date(legacyFrozenBandwidth.expire_time), } : undefined, energy: legacyFrozenEnergy ? { amount: new BigNumber(legacyFrozenEnergy.frozen_balance), expiredAt: new Date(legacyFrozenEnergy.expire_time), } : undefined, }; const { frozenEnergy, frozenBandwidth } = frozenBalances.reduce( (accum, cur) => { const amount = new BigNumber(cur?.amount ?? 0); if (cur.type === "ENERGY") { accum.frozenEnergy = accum.frozenEnergy.plus(amount); } else if (cur.type === undefined) { accum.frozenBandwidth = accum.frozenBandwidth.plus(amount); } return accum; }, { frozenEnergy: new BigNumber(0), frozenBandwidth: new BigNumber(0), }, ); const unFrozenBalances: { type?: string; unfreeze_amount: number; unfreeze_expire_time: number; }[] = get(acc, "unfrozenV2", []); const unFrozen: { bandwidth: UnFrozenInfo[]; energy: UnFrozenInfo[] } = unFrozenBalances ? unFrozenBalances.reduce( (accum, cur) => { if (cur && cur.type === "ENERGY") { accum.energy.push({ amount: new BigNumber(cur.unfreeze_amount), expireTime: new Date(cur.unfreeze_expire_time), }); } else if (cur) { accum.bandwidth.push({ amount: new BigNumber(cur.unfreeze_amount), expireTime: new Date(cur.unfreeze_expire_time), }); } return accum; }, { bandwidth: [] as UnFrozenInfo[], energy: [] as UnFrozenInfo[] }, ) : { bandwidth: [], energy: [] }; const frozen = { bandwidth: frozenBandwidth.isGreaterThan(0) ? { amount: frozenBandwidth, } : undefined, energy: frozenEnergy.isGreaterThan(0) ? { amount: frozenEnergy, } : undefined, }; const delegatedFrozen = { bandwidth: delegatedFrozenBandwidth ? { amount: new BigNumber(delegatedFrozenBandwidth), } : undefined, energy: delegatedFrozenEnergy ? { amount: new BigNumber(delegatedFrozenEnergy), } : undefined, }; const tronPower = new BigNumber(get(frozen, "bandwidth.amount", 0)) .plus(get(frozen, "energy.amount", 0)) .plus(get(delegatedFrozen, "bandwidth.amount", 0)) .plus(get(delegatedFrozen, "energy.amount", 0)) .plus(get(legacyFrozen, "energy.amount", 0)) .plus(get(legacyFrozen, "bandwidth.amount", 0)) .dividedBy(1_000_000) .integerValue(BigNumber.ROUND_FLOOR) .toNumber(); const lastWithdrawnRewardDate = acc.latest_withdraw_time ? new Date(acc.latest_withdraw_time) : undefined; return { frozen, unFrozen, delegatedFrozen, legacyFrozen, tronPower, lastWithdrawnRewardDate, }; } export function feesToNumber(customFees?: bigint): number | undefined { if ( customFees !== undefined && customFees >= Number.MIN_SAFE_INTEGER && customFees <= Number.MAX_SAFE_INTEGER ) { return Number(customFees); } return undefined; }