@ledgerhq/coin-tron
Version:
Ledger Tron Coin integration
380 lines (363 loc) • 10.9 kB
text/typescript
import BigNumber from "bignumber.js";
import {
decode58Check,
encode58Check,
formatTrongridTrc20TxResponse,
formatTrongridTxResponse,
} from "./format";
import { Trc20API, TransactionTronAPI } from "./types";
const ownerHex = "41fd49eda0f23ff7ec1d03b52c3a45991c24cd440e";
const toHex = "4198927ffb9f554dc4a453c64b2e553a02d6df514b";
const trc20ContractHex = "4142a1e39aefa49290f2b3f9ed688d7cecf86cd6e0";
function baseTransactionTronApi(
contract: TransactionTronAPI["raw_data"]["contract"][0],
overrides: Partial<TransactionTronAPI> = {},
): TransactionTronAPI {
return {
ret: [{ contractRet: "SUCCESS", fee: 1000 }],
signature: [],
txID: "txId",
net_usage: 0,
raw_data_hex: "",
net_fee: 0,
energy_usage: 0,
block_timestamp: 1,
blockNumber: 42,
energy_fee: 0,
energy_usage_total: 0,
raw_data: {
contract: [contract],
ref_block_bytes: "",
ref_block_hash: "",
expiration: 0,
},
internal_transactions: [],
...overrides,
};
}
describe("formatTrongridTrc20TxResponse", () => {
it("should return correct TrongridTxInfo for Approval tx type", () => {
const tx = {
from: "from",
to: "to",
block_timestamp: 1,
detail: {
ret: [{ fee: 1 }],
},
value: 1,
transaction_id: "txId",
token_info: { address: "addr" },
type: "Approval",
};
const result = formatTrongridTrc20TxResponse(tx as unknown as Trc20API);
expect(result).toEqual({
txID: "txId",
date: new Date(1),
type: "ContractApproval",
tokenId: undefined,
from: "from",
to: "to",
blockHeight: undefined,
value: new BigNumber(1),
fee: new BigNumber(1),
hasFailed: false,
tokenType: "trc20",
tokenAddress: "addr",
feesPayer: "from",
});
});
it("should return correct TrongridTxInfo for Transfer tx type", () => {
const tx = {
from: "from",
to: "to",
block_timestamp: 1,
detail: {
ret: [{ fee: 1 }],
},
value: 1,
transaction_id: "txId",
token_info: { address: "tokenId" },
type: "Transfer",
};
const result = formatTrongridTrc20TxResponse(tx as unknown as Trc20API);
expect(result).toEqual({
txID: "txId",
date: new Date(1),
type: "TriggerSmartContract",
tokenId: "tokenId",
from: "from",
to: "to",
blockHeight: undefined,
value: new BigNumber(1),
fee: new BigNumber(1),
hasFailed: false,
tokenType: "trc20",
tokenAddress: "tokenId",
feesPayer: "from",
});
});
it("should fall back to contract_address when token_info.address is missing (unindexed LP contract)", () => {
const tx = {
from: "from",
to: "to",
block_timestamp: 1,
detail: {
ret: [{ fee: 0 }],
raw_data: {
contract: [
{
parameter: {
value: {
owner_address: "41f1fe9d73ffb3b6ab532858b266c02f63410fbd70",
// hex for TU1wcXoAq5EXhZp7ga6E1Vb1Sqw1ciQP4s (unindexed LP contract)
contract_address: "41c5f6aa996edd0696a0295b07fd7b20b0dd84c557",
},
},
},
],
},
},
value: "20000000000",
transaction_id: "txId",
token_info: {},
type: "Transfer",
};
const result = formatTrongridTrc20TxResponse(tx as unknown as Trc20API);
expect(result).toMatchObject({
type: "TriggerSmartContract",
tokenId: "TU1wcXoAq5EXhZp7ga6E1Vb1Sqw1ciQP4s",
tokenAddress: "TU1wcXoAq5EXhZp7ga6E1Vb1Sqw1ciQP4s",
});
});
it("should set feesPayer from owner_address when it differs from from (transferFrom case)", () => {
// owner_address is the Tron tx initiator (hex); `from` is the TRC20 token source (base58)
const tx = {
from: "TTokenSourceAddress",
to: "to",
block_timestamp: 1,
detail: {
ret: [{ fee: 1 }],
raw_data: {
contract: [
{
parameter: {
value: {
// hex for TY2ksFgpvb82TgGPwUSa7iseqPW5weYQyh
owner_address: "41f1fe9d73ffb3b6ab532858b266c02f63410fbd70",
},
},
},
],
},
},
value: 100,
transaction_id: "txId",
token_info: { address: "tokenAddr" },
type: "Transfer",
};
const result = formatTrongridTrc20TxResponse(tx as unknown as Trc20API);
expect(result).toEqual({
txID: "txId",
date: new Date(1),
type: "TriggerSmartContract",
tokenId: "tokenAddr",
from: "TTokenSourceAddress",
to: "to",
blockHeight: undefined,
value: new BigNumber(100),
fee: new BigNumber(1),
hasFailed: false,
tokenType: "trc20",
tokenAddress: "tokenAddr",
feesPayer: "TY2ksFgpvb82TgGPwUSa7iseqPW5weYQyh",
});
});
});
describe("formatTrongridTxResponse", () => {
it("should return correct TrongridTxInfo for TransferContract", async () => {
const tx = baseTransactionTronApi({
type: "TransferContract",
parameter: {
type_url: "",
value: {
owner_address: ownerHex,
to_address: toHex,
amount: 1_000_000,
},
},
});
const result = await formatTrongridTxResponse(tx, () => Promise.resolve(null));
expect(result).toEqual({
txID: "txId",
date: new Date(1),
type: "TransferContract",
tokenId: undefined,
tokenType: undefined,
tokenAddress: undefined,
from: encode58Check(ownerHex),
to: encode58Check(toHex),
value: new BigNumber(1_000_000),
fee: new BigNumber(1000),
blockHeight: 42,
hasFailed: false,
feesPayer: encode58Check(ownerHex),
});
});
it("should set hasFailed when contract execution did not succeed", async () => {
const tx = baseTransactionTronApi(
{
type: "TransferContract",
parameter: {
type_url: "",
value: {
owner_address: ownerHex,
to_address: toHex,
amount: 100,
},
},
},
{ ret: [{ contractRet: "REVERT", fee: 0 }] },
);
const result = await formatTrongridTxResponse(tx, () => Promise.resolve(null));
expect(result?.hasFailed).toBe(true);
});
it("should return correct TrongridTxInfo for TransferAssetContract", async () => {
const assetName = "10013004";
const tx = baseTransactionTronApi({
type: "TransferAssetContract",
parameter: {
type_url: "",
value: {
owner_address: ownerHex,
to_address: toHex,
amount: 100,
asset_name: assetName,
},
},
});
const result = await formatTrongridTxResponse(tx, () => Promise.resolve(null));
expect(result).toEqual({
txID: "txId",
date: new Date(1),
type: "TransferAssetContract",
tokenId: assetName,
tokenType: "trc10",
tokenAddress: undefined,
from: encode58Check(ownerHex),
to: encode58Check(toHex),
value: new BigNumber(100),
fee: new BigNumber(1000),
blockHeight: 42,
hasFailed: false,
feesPayer: encode58Check(ownerHex),
});
});
it("should return correct TrongridTxInfo for TriggerSmartContract", async () => {
const tx = baseTransactionTronApi({
type: "TriggerSmartContract",
parameter: {
type_url: "",
value: {
owner_address: ownerHex,
to_address: toHex,
contract_address: trc20ContractHex,
amount: 0,
},
},
});
const result = await formatTrongridTxResponse(tx, () => Promise.resolve(null));
expect(result).toEqual({
txID: "txId",
date: new Date(1),
type: "TriggerSmartContract",
tokenId: encode58Check(trc20ContractHex),
tokenType: "trc20",
tokenAddress: encode58Check(trc20ContractHex),
from: encode58Check(ownerHex),
to: encode58Check(toHex),
value: new BigNumber(0),
fee: new BigNumber(1000),
blockHeight: 42,
hasFailed: false,
feesPayer: encode58Check(ownerHex),
});
});
it("should map withdraw amount for WithdrawBalanceContract", async () => {
const tx = baseTransactionTronApi(
{
type: "WithdrawBalanceContract",
parameter: {
type_url: "",
value: {
owner_address: ownerHex,
},
},
},
{ withdraw_amount: 5_000_000 },
);
const result = await formatTrongridTxResponse(tx, () => Promise.resolve(null));
expect(result?.value).toEqual(new BigNumber(5_000_000));
});
it("should map quant for ExchangeTransactionContract", async () => {
const tx = baseTransactionTronApi({
type: "ExchangeTransactionContract",
parameter: {
type_url: "",
value: {
owner_address: ownerHex,
quant: 777,
},
},
});
const result = await formatTrongridTxResponse(tx, () => Promise.resolve(null));
expect(result?.value).toEqual(new BigNumber(777));
});
it("should attach vote extra info using getValidatorName", async () => {
const voteAddressHex = "41b183301d6301fae224c6ab9b28b19b6d1625bf23";
const getValidatorName = jest
.fn()
.mockImplementation(async (address: string) =>
address === encode58Check(voteAddressHex) ? "SR-Name" : null,
);
const tx = baseTransactionTronApi({
type: "VoteWitnessContract",
parameter: {
type_url: "",
value: {
owner_address: ownerHex,
votes: [{ vote_address: voteAddressHex, vote_count: 3 }],
},
},
});
const result = await formatTrongridTxResponse(tx, getValidatorName);
expect(getValidatorName).toHaveBeenCalledWith(encode58Check(voteAddressHex));
expect(result?.extra).toEqual({
votes: [
{
name: "SR-Name",
address: encode58Check(voteAddressHex),
voteCount: 3,
},
],
});
});
it("should return undefined when the transaction payload cannot be parsed", async () => {
const tx = { txID: "broken", block_timestamp: 1 } as TransactionTronAPI;
const result = await formatTrongridTxResponse(tx, () => Promise.resolve(null));
expect(result).toBeUndefined();
});
});
describe("decode58Check", () => {
it("decodes correctly Tron address", () => {
expect(decode58Check("TY2ksFgpvb82TgGPwUSa7iseqPW5weYQyh")).toEqual(
"41f1fe9d73ffb3b6ab532858b266c02f63410fbd70",
);
});
});
describe("encode58Check", () => {
it("encodes correctly Tron address", () => {
expect(encode58Check("41f1fe9d73ffb3b6ab532858b266c02f63410fbd70")).toEqual(
"TY2ksFgpvb82TgGPwUSa7iseqPW5weYQyh",
);
});
});