Skip to content
Merged
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
19 changes: 15 additions & 4 deletions modules/sdk-coin-trx/src/trx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,21 @@ export class Trx extends BaseCoin {
}

if (walletType === 'tss') {
// For TSS wallets, txHex is the signableHex (raw_data_hex protobuf bytes),
// not a full transaction JSON. Decode it directly via protobuf.
// Note: decodeTransaction already validates exactly 1 contract exists.
const decodedTx = Utils.decodeTransaction(txPrebuild.txHex);
// For TSS wallets, verifyTransaction is called from two places:
// 1. prebuildAndSignTransaction (wallet.ts) — txHex is serializedTxHex, a full JSON string
// containing { txID, raw_data, raw_data_hex }.
// 2. ECDSA signing flow (ecdsa.ts) — txHex is signableHex, the raw protobuf bytes (raw_data_hex).
// We need to extract the raw_data_hex in case 1 before decoding.
let rawDataHex: string;
try {
// serializedTxHex: full JSON string — extract the raw_data_hex field
rawDataHex = JSON.parse(txPrebuild.txHex).raw_data_hex;
} catch {
// signableHex: already raw protobuf hex (raw_data_hex)
console.debug(`Could not parse txHex as JSON for coin ${this.getChain()}, using txHex directly`);
rawDataHex = txPrebuild.txHex;
}
const decodedTx = Utils.decodeTransaction(rawDataHex);

// decodedTx uses a numeric enum for contract type (from protobuf decoding),
// unlike the multisig path which checks the string 'TransferContract' from node JSON.
Expand Down
73 changes: 73 additions & 0 deletions modules/sdk-coin-trx/test/unit/verifyTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,5 +574,78 @@ describe('TRON Verify Transaction:', function () {
message: 'missing txHex in txPrebuild',
});
});

describe('serializedTxHex (JSON string) path', () => {
// prebuildAndSignTransaction passes txHex as serializedTxHex — a full JSON string
// containing { txID, raw_data, raw_data_hex }. The TSS branch must handle this format
// by extracting raw_data_hex before protobuf decoding.

it('should validate TSS TransferContract when txHex is a JSON string (serializedTxHex)', async function () {
const ownerHex = '4173a5993cd182ae152adad8203163f780c65a8aa5';
const toHex = '41d6cd6a2c0ff35a319e6abb5b9503ba0278679882';
const amount = 1000000;

const rawDataHex = buildTssTransferTxHex({ ownerAddress: ownerHex, toAddress: toHex, amount });
const txID = createHash('sha256').update(Buffer.from(rawDataHex, 'hex')).digest('hex');

// Simulate the serializedTxHex format from BitGo API (JSON string with txID + raw_data_hex)
const serializedTxHex = JSON.stringify({
txID,
raw_data_hex: rawDataHex,
raw_data: {},
});

const params = {
txParams: {
recipients: [
{
address: Utils.getBase58AddressFromHex(toHex),
amount: amount.toString(),
},
],
},
txPrebuild: {
txHex: serializedTxHex,
},
wallet: {},
walletType: 'tss',
};

const result = await basecoin.verifyTransaction(params);
assert.strictEqual(result, true);
});

it('should fail when amount mismatches with JSON txHex', async function () {
const ownerHex = '4173a5993cd182ae152adad8203163f780c65a8aa5';
const toHex = '41d6cd6a2c0ff35a319e6abb5b9503ba0278679882';

const rawDataHex = buildTssTransferTxHex({ ownerAddress: ownerHex, toAddress: toHex, amount: 2000000 });
const serializedTxHex = JSON.stringify({
txID: createHash('sha256').update(Buffer.from(rawDataHex, 'hex')).digest('hex'),
raw_data_hex: rawDataHex,
raw_data: {},
});

const params = {
txParams: {
recipients: [
{
address: Utils.getBase58AddressFromHex(toHex),
amount: '1000000', // mismatch
},
],
},
txPrebuild: {
txHex: serializedTxHex,
},
wallet: {},
walletType: 'tss',
};

await assert.rejects(basecoin.verifyTransaction(params), {
message: 'transaction amount in txPrebuild does not match the value given by client',
});
});
});
});
});
Loading