|
| 1 | +import assert from 'node:assert'; |
| 2 | +import { describe, it, before } from 'node:test'; |
| 3 | +import { BitGoAPI } from '@bitgo/sdk-api'; |
| 4 | +import { TestBitGoAPI, TestBitGo } from '@bitgo/sdk-test'; |
| 5 | +import { tokens } from '@bitgo/statics'; |
| 6 | +import { Trx } from '../../src/trx'; |
| 7 | +import { TrxToken } from '../../src/trxToken'; |
| 8 | +import { Ttrx } from '../../src/ttrx'; |
| 9 | +import { Utils } from '../../src/lib'; |
| 10 | + |
| 11 | +// Real TriggerSmartContract protobuf raw_data_hex encoding a TRC20 transfer: |
| 12 | +// owner: 41c51fbeea78910b15b1d3e8a9b62914ca94d1a4ac |
| 13 | +// contract: 4142a1e39aefa49290f2b3f9ed688d7cecf86cd6e0 |
| 14 | +// data: a9059cbb + abi(address=8483618ca85c35a9b923d98bebca718f5a1db279, uint256=100000000) |
| 15 | +const TRC20_RAW_DATA_HEX = |
| 16 | + '0a02578b22086113bb9ac351432b4088eae7a6de305aae01081f12a9010a31747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e54726967676572536d617274436f6e747261637412740a1541c51fbeea78910b15b1d3e8a9b62914ca94d1a4ac12154142a1e39aefa49290f2b3f9ed688d7cecf86cd6e02244a9059cbb0000000000000000000000008483618ca85c35a9b923d98bebca718f5a1db2790000000000000000000000000000000000000000000000000000000005f5e10070888d8ca5de309001c0c39307'; |
| 17 | + |
| 18 | +// Recipient address decoded from the ABI data above (41 prefix → base58) |
| 19 | +const TRC20_RECIPIENT_HEX = '418483618ca85c35a9b923d98bebca718f5a1db279'; |
| 20 | +const TRC20_AMOUNT = '100000000'; |
| 21 | + |
| 22 | +describe('TrxToken verifyTransaction:', function () { |
| 23 | + const bitgo: TestBitGoAPI = TestBitGo.decorate(BitGoAPI, { env: 'test' }); |
| 24 | + bitgo.initializeTestVars(); |
| 25 | + bitgo.safeRegister('trx', Trx.createInstance); |
| 26 | + bitgo.safeRegister('ttrx', Ttrx.createInstance); |
| 27 | + |
| 28 | + let tokenCoin: TrxToken; |
| 29 | + |
| 30 | + before(function () { |
| 31 | + const usdtConfig = tokens.testnet.trx.tokens.find((t) => t.type === 'ttrx:usdt'); |
| 32 | + assert.ok(usdtConfig, 'ttrx:usdt token config not found'); |
| 33 | + tokenCoin = new TrxToken(bitgo, usdtConfig); |
| 34 | + }); |
| 35 | + |
| 36 | + describe('TSS wallet — TriggerSmartContract validation', () => { |
| 37 | + it('should validate a correct TRC20 transfer', async function () { |
| 38 | + const recipientBase58 = Utils.getBase58AddressFromHex(TRC20_RECIPIENT_HEX); |
| 39 | + |
| 40 | + const result = await tokenCoin.verifyTransaction({ |
| 41 | + txPrebuild: { txHex: TRC20_RAW_DATA_HEX }, |
| 42 | + txParams: { recipients: [{ address: recipientBase58, amount: TRC20_AMOUNT }] }, |
| 43 | + walletType: 'tss', |
| 44 | + } as any); |
| 45 | + |
| 46 | + assert.strictEqual(result, true); |
| 47 | + }); |
| 48 | + |
| 49 | + it('should validate when txHex is a serialized JSON (prebuildAndSignTransaction path)', async function () { |
| 50 | + const recipientBase58 = Utils.getBase58AddressFromHex(TRC20_RECIPIENT_HEX); |
| 51 | + const serializedTxHex = JSON.stringify({ txID: 'abc', raw_data_hex: TRC20_RAW_DATA_HEX, raw_data: {} }); |
| 52 | + |
| 53 | + const result = await tokenCoin.verifyTransaction({ |
| 54 | + txPrebuild: { txHex: serializedTxHex }, |
| 55 | + txParams: { recipients: [{ address: recipientBase58, amount: TRC20_AMOUNT }] }, |
| 56 | + walletType: 'tss', |
| 57 | + } as any); |
| 58 | + |
| 59 | + assert.strictEqual(result, true); |
| 60 | + }); |
| 61 | + |
| 62 | + it('should throw when amount does not match', async function () { |
| 63 | + const recipientBase58 = Utils.getBase58AddressFromHex(TRC20_RECIPIENT_HEX); |
| 64 | + |
| 65 | + await assert.rejects( |
| 66 | + tokenCoin.verifyTransaction({ |
| 67 | + txPrebuild: { txHex: TRC20_RAW_DATA_HEX }, |
| 68 | + txParams: { recipients: [{ address: recipientBase58, amount: '999' }] }, |
| 69 | + walletType: 'tss', |
| 70 | + } as any), |
| 71 | + { message: 'transaction amount in txPrebuild does not match the value given by client' } |
| 72 | + ); |
| 73 | + }); |
| 74 | + |
| 75 | + it('should throw when recipient address does not match', async function () { |
| 76 | + await assert.rejects( |
| 77 | + tokenCoin.verifyTransaction({ |
| 78 | + txPrebuild: { txHex: TRC20_RAW_DATA_HEX }, |
| 79 | + txParams: { recipients: [{ address: 'TLWh67P93KgtnZNCtGnEHM1H33Nhq2uvvN', amount: TRC20_AMOUNT }] }, |
| 80 | + walletType: 'tss', |
| 81 | + } as any), |
| 82 | + { message: 'destination address does not match with the recipient address' } |
| 83 | + ); |
| 84 | + }); |
| 85 | + |
| 86 | + it('should throw when recipients is empty', async function () { |
| 87 | + await assert.rejects( |
| 88 | + tokenCoin.verifyTransaction({ |
| 89 | + txPrebuild: { txHex: TRC20_RAW_DATA_HEX }, |
| 90 | + txParams: { recipients: [] }, |
| 91 | + walletType: 'tss', |
| 92 | + } as any), |
| 93 | + { message: 'missing or invalid required property recipients' } |
| 94 | + ); |
| 95 | + }); |
| 96 | + |
| 97 | + it('should throw when contract type is not TriggerSmartContract', async function () { |
| 98 | + // Use a native TRX Transfer protobuf as txHex — TrxToken only handles TriggerSmartContract |
| 99 | + const recipientBase58 = Utils.getBase58AddressFromHex(TRC20_RECIPIENT_HEX); |
| 100 | + const nativeTrxRawDataHex = Utils.generateRawDataHex({ |
| 101 | + contract: [ |
| 102 | + { |
| 103 | + parameter: { |
| 104 | + value: { |
| 105 | + amount: 100000000, |
| 106 | + owner_address: '4173a5993cd182ae152adad8203163f780c65a8aa5', |
| 107 | + to_address: TRC20_RECIPIENT_HEX, |
| 108 | + } as any, |
| 109 | + type_url: 'type.googleapis.com/protocol.TransferContract', |
| 110 | + }, |
| 111 | + type: 'TransferContract', |
| 112 | + } as any, |
| 113 | + ], |
| 114 | + refBlockBytes: 'c8cf', |
| 115 | + refBlockHash: '89177fd84c5d9196', |
| 116 | + expiration: Date.now() + 3600000, |
| 117 | + timestamp: Date.now(), |
| 118 | + }); |
| 119 | + |
| 120 | + await assert.rejects( |
| 121 | + tokenCoin.verifyTransaction({ |
| 122 | + txPrebuild: { txHex: nativeTrxRawDataHex }, |
| 123 | + txParams: { recipients: [{ address: recipientBase58, amount: '100000000' }] }, |
| 124 | + walletType: 'tss', |
| 125 | + } as any), |
| 126 | + { message: /Expected TriggerSmartContract for TRC20 token transfer/ } |
| 127 | + ); |
| 128 | + }); |
| 129 | + }); |
| 130 | + |
| 131 | + describe('non-TSS wallet — builder-based validation (existing path)', () => { |
| 132 | + it('should validate a correct non-TSS TRC20 transfer using txBuilder', async function () { |
| 133 | + // The non-TSS path uses getBuilder().from(rawTx).build() and checks tx.outputs[0] |
| 134 | + // This test uses the full JSON tx format that the builder understands. |
| 135 | + const txHex = |
| 136 | + '{"raw_data":{"contractType":2,"contract":[{"parameter":{"value":{"data":"a9059cbb0000000000000000000000008483618ca85c35a9b923d98bebca718f5a1db2790000000000000000000000000000000000000000000000000000000005f5e100","owner_address":"41c51fbeea78910b15b1d3e8a9b62914ca94d1a4ac","contract_address":"4142a1e39aefa49290f2b3f9ed688d7cecf86cd6e0"},"type_url":"type.googleapis.com/protocol.TriggerSmartContract"},"type":"TriggerSmartContract"}],"expiration":1674581767432,"timestamp":1674578167432,"ref_block_bytes":"578b","ref_block_hash":"6113bb9ac351432b","fee_limit":15000000},"raw_data_hex":"0a02578b22086113bb9ac351432b4088eae7a6de305aae01081f12a9010a31747970652e676f6f676c65617069732e636f6d2f70726f746f636f6c2e54726967676572536d617274436f6e747261637412740a1541c51fbeea78910b15b1d3e8a9b62914ca94d1a4ac12154142a1e39aefa49290f2b3f9ed688d7cecf86cd6e02244a9059cbb0000000000000000000000008483618ca85c35a9b923d98bebca718f5a1db2790000000000000000000000000000000000000000000000000000000005f5e10070888d8ca5de309001c0c39307","txID":"fe21c49f4febd9089125e3a006943c145721d8fcb7ab84136f8c6663ff92f8ed","signature":["0775cde302689eb8293883c66a89b31e80d608bfc3ad3c283b64a490ea4cc712c55a2fd2e62c75843dd7e77d8c4cb52e0f371fbb29b332c259f8cb63c2e6195301"]}'; |
| 137 | + const recipientBase58 = Utils.getBase58AddressFromHex(TRC20_RECIPIENT_HEX); |
| 138 | + |
| 139 | + const result = await tokenCoin.verifyTransaction({ |
| 140 | + txPrebuild: { txHex }, |
| 141 | + txParams: { recipients: [{ address: recipientBase58, amount: TRC20_AMOUNT }] }, |
| 142 | + } as any); |
| 143 | + |
| 144 | + assert.strictEqual(result, true); |
| 145 | + }); |
| 146 | + }); |
| 147 | +}); |
0 commit comments