Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion modules/sdk-coin-trx/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export interface AccountInfo {
keys: [PermissionKey];
};
active_permission: [{ keys: [PermissionKey] }];
trc20: [Record<string, string>];
trc20?: Record<string, string>[];
}

/**
Expand Down
9 changes: 5 additions & 4 deletions modules/sdk-coin-trx/src/trx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ export class Trx extends BaseCoin {
// check for possible token recovery, recover the token provide by user
if (tokenContractAddr) {
let rawTokenTxn: any | undefined;
for (const token of account.data[0].trc20) {
for (const token of account.data[0].trc20 ?? []) {
if (token[tokenContractAddr]) {
const amount = token[tokenContractAddr];
const tokenContractAddrHex = Utils.getHexAddressFromBase58Address(tokenContractAddr);
Expand Down Expand Up @@ -1053,7 +1053,7 @@ export class Trx extends BaseCoin {
let rawTokenTxn: any | undefined;
let recoveryAmount = 0;

for (const token of account.data[0].trc20) {
for (const token of account.data[0].trc20 ?? []) {
if (token[tokenContractAddr]) {
const amount = token[tokenContractAddr];
const tokenContractAddrHex = Utils.getHexAddressFromBase58Address(tokenContractAddr);
Expand Down Expand Up @@ -1165,8 +1165,9 @@ export class Trx extends BaseCoin {

// check for possible token recovery, recover the token provide by user
if (params.tokenContractAddress) {
if (accountInfo.data[0].balance > SAFE_TRON_TOKEN_TRANSACTION_FEE && accountInfo.data[0].trc20[0]) {
const tokenDataArray = accountInfo.data[0].trc20;
const trc20Data = accountInfo.data[0].trc20 ?? [];
if (accountInfo.data[0].balance > SAFE_TRON_TOKEN_TRANSACTION_FEE && trc20Data.length > 0) {
const tokenDataArray = trc20Data;
for (const tokenData of tokenDataArray) {
const contractAddress = Object.keys(tokenData) as Array<string>;
if (params.tokenContractAddress === contractAddress[0]) {
Expand Down
90 changes: 90 additions & 0 deletions modules/sdk-coin-trx/test/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,96 @@ export function receiveAddressBalance(balance: number, address: string, trc20Bal
};
}

export function receiveAddressBalanceWithoutTrc20Field(balance: number, address: string) {
return {
data: [
{
owner_permission: {
keys: [
{
address: address,
weight: 1,
},
],
threshold: 1,
permission_name: 'owner',
},
active_permission: [
{
operations: '7fff1fc0033ec30f000000000000000000000000000000000000000000000000',
keys: [
{
address: address,
weight: 1,
},
],
threshold: 1,
id: 2,
type: 'Active',
permission_name: 'active',
},
],
balance: balance,
// trc20 field intentionally absent — real TRON node omits it for
// accounts that have never interacted with TRC20 tokens
},
],
};
}

export function baseAddressBalanceWithoutTrc20Field(trxBalance: number) {
return {
data: [
{
owner_permission: {
keys: [
{
address: 'TTgisRP7EJWMgpLXvbNHoHh5UotkjkBPoo',
weight: 1,
},
{
address: 'TBDy8HAy8vvhoqKc5V1hHQatfjZHM1MhPb',
weight: 1,
},
{
address: 'TPHPDfQ8Vs3Yp5UKDLHr5MjoVUrr5Y69m9',
weight: 1,
},
],
threshold: 2,
permission_name: 'owner',
},
balance: trxBalance,
// trc20 field intentionally absent — real TRON node omits it for
// accounts that have never interacted with TRC20 tokens
active_permission: [
{
operations: '7fff1fc0037e0000000000000000000000000000000000000000000000000000',
keys: [
{
address: 'TTgisRP7EJWMgpLXvbNHoHh5UotkjkBPoo',
weight: 1,
},
{
address: 'TBDy8HAy8vvhoqKc5V1hHQatfjZHM1MhPb',
weight: 1,
},
{
address: 'TPHPDfQ8Vs3Yp5UKDLHr5MjoVUrr5Y69m9',
weight: 1,
},
],
threshold: 2,
id: 2,
type: 'Active',
permission_name: 'active0',
},
],
},
],
};
}

export function creationTransaction(fromAddress: string, toAddress: string, amount: number) {
return {
visible: false,
Expand Down
72 changes: 72 additions & 0 deletions modules/sdk-coin-trx/test/unit/trx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { Trx, Ttrx, Utils } from '../../src';
import { signTxOptions, mockTx } from '../fixtures';
import {
baseAddressBalance,
baseAddressBalanceWithoutTrc20Field,
SampleRawTokenSendTxn,
receiveAddressBalance,
receiveAddressBalanceWithoutTrc20Field,
TestRecoverData,
TssTestRecoverData,
creationTransaction,
Expand Down Expand Up @@ -442,6 +444,28 @@ describe('TRON:', function () {
}
);
});

it('should throw if trc20 field absent and token recovery requested (base address)', async function () {
mock.method(Trx.prototype as any, 'getAccountBalancesFromNode', (...args) => {
if (args.length > 0 && args[0] === TestRecoverData.baseAddress) {
return Promise.resolve(baseAddressBalanceWithoutTrc20Field(100000000));
}
return undefined;
});

await assert.rejects(
basecoin.recover({
userKey: TestRecoverData.userKey,
backupKey: TestRecoverData.backupKey,
bitgoKey: TestRecoverData.bitgoKey,
tokenContractAddress: 'TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs',
recoveryDestination: TestRecoverData.recoveryDestination,
}),
{
message: 'Not found token to recover, please check token balance',
}
);
});
});

describe('Build Unsigned Consolidation Recoveries', () => {
Expand Down Expand Up @@ -633,6 +657,34 @@ describe('TRON:', function () {
assert.equal(Utils.getBase58AddressFromHex(value1.owner_address), TestRecoverData.firstReceiveAddress);
assert.equal(Utils.getBase58AddressFromHex(value1.to_address), TestRecoverData.baseAddress);
});

it('should skip token consolidation when trc20 field absent from account response', async () => {
mock.method(Trx.prototype as any, 'getAccountBalancesFromNode', (...args) => {
if (args.length > 0 && args[0] === TestRecoverData.firstReceiveAddress) {
return Promise.resolve(
receiveAddressBalanceWithoutTrc20Field(202100000, TestRecoverData.firstReceiveAddress)
);
}

if (args.length > 0 && args[0] === TestRecoverData.secondReceiveAddress) {
return Promise.resolve(receiveAddressBalanceWithoutTrc20Field(500, TestRecoverData.secondReceiveAddress));
}

return undefined;
});

const res = await basecoin.recoverConsolidations({
userKey: TestRecoverData.userKey,
backupKey: TestRecoverData.backupKey,
bitgoKey: TestRecoverData.bitgoKey,
tokenContractAddress: 'TSdZwNqpHofzP6BsBKGQUWdBeJphLmF6id',
startingScanIndex: 1,
endingScanIndex: 3,
});

assert.ok(Object.prototype.hasOwnProperty.call(res, 'transactions'));
assert.equal(res.transactions.length, 0);
});
});

describe('TSS Recovery', () => {
Expand Down Expand Up @@ -824,6 +876,26 @@ describe('TRON:', function () {
}
);
});

it('should throw if trc20 field absent for TSS wallet token recovery', async function () {
mock.method(Trx.prototype as any, 'getAccountBalancesFromNode', () => {
return Promise.resolve(baseAddressBalanceWithoutTrc20Field(100000000));
});

await assert.rejects(
basecoin.recover({
userKey: TssTestRecoverData.userKey,
backupKey: TssTestRecoverData.backupKey,
bitgoKey: TssTestRecoverData.bitgoKey,
tokenContractAddress: 'TG3XXyExBkPp9nzdajDZsozEu4BkaSJozs',
recoveryDestination: TssTestRecoverData.recoveryDestination,
isTss: true,
}),
{
message: 'Not found token to recover, please check token balance',
}
);
});
});

describe('isWalletAddress', () => {
Expand Down
Loading