From a9f8bae8308b657a2744082f4f72f544196d9b40 Mon Sep 17 00:00:00 2001 From: Bhavi Dhingra Date: Fri, 22 May 2026 21:06:38 +0530 Subject: [PATCH] fix(sdk-coin-trx): emit canonical AccountCreate raw_data_hex Ticket: CHALO-457 --- .../src/lib/accountCreateTxBuilder.ts | 9 ++++-- .../accountCreateTxBuilder.ts | 32 +++++++++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/modules/sdk-coin-trx/src/lib/accountCreateTxBuilder.ts b/modules/sdk-coin-trx/src/lib/accountCreateTxBuilder.ts index 7becd591c9..9d8d3e02ad 100644 --- a/modules/sdk-coin-trx/src/lib/accountCreateTxBuilder.ts +++ b/modules/sdk-coin-trx/src/lib/accountCreateTxBuilder.ts @@ -15,8 +15,6 @@ import { } from './utils'; import { ACCOUNT_CREATE_TYPE_URL } from './constants'; -import ContractType = protocol.Transaction.Contract.ContractType; - export class AccountCreateTxBuilder extends TransactionBuilder { protected _signingKeys: BaseKey[]; // Stored as hex address, consistent with _ownerAddress @@ -148,8 +146,13 @@ export class AccountCreateTxBuilder extends TransactionBuilder { }; const accountCreateContract = protocol.AccountCreateContract.fromObject(rawContract); const accountCreateContractBytes = protocol.AccountCreateContract.encode(accountCreateContract).finish(); + // AccountCreateContract is enum value 0 — the proto3 default. TRON's node + // re-serializes raw_data from broadcast JSON and omits default-valued + // fields, producing a different raw_data_hex (and txID) than the SDK if + // we encode the type field explicitly. Skip it so signing and broadcast + // hashes match. See freezeBalanceTxBuilder.ts:175-181 for the same class + // of issue on the inner `resource` field. const txContract = { - type: ContractType.AccountCreateContract, parameter: { value: accountCreateContractBytes, type_url: ACCOUNT_CREATE_TYPE_URL, diff --git a/modules/sdk-coin-trx/test/unit/transactionBuilder/accountCreateTxBuilder.ts b/modules/sdk-coin-trx/test/unit/transactionBuilder/accountCreateTxBuilder.ts index 3370cdc000..8c2c0d9e47 100644 --- a/modules/sdk-coin-trx/test/unit/transactionBuilder/accountCreateTxBuilder.ts +++ b/modules/sdk-coin-trx/test/unit/transactionBuilder/accountCreateTxBuilder.ts @@ -115,6 +115,38 @@ describe('Tron AccountCreate builder', function () { assert.equal(tx2.toJson().txID, originalTxId); }); + + // Regression test: AccountCreateContract's enum value is 0, which is the + // proto3 default for the outer Transaction.Contract.type field. When the + // SDK explicitly encoded `type: 0`, the wire format included the 2-byte + // tag `0800` inside the contract envelope. TRON's node re-serializes + // raw_data from broadcast JSON and omits default-valued fields, producing + // a 2-byte-shorter canonical raw_data_hex and a different txID. The TSS + // signature was valid for the SDK's hash but recovered to a garbage + // pubkey under TRON's canonical hash, so AccountCreate broadcasts failed + // with SIGERROR "signed by but not contained of permission". + it('produces raw_data_hex without the proto3 default-valued type field', async () => { + const timestamp = 1779455020653; + const expiration = 1779465820653; + const txBuilder = initTxBuilder(); + txBuilder.timestamp(timestamp); + txBuilder.expiration(expiration); + const tx = await txBuilder.build(); + const rawDataHex = tx.toJson().raw_data_hex; + + // The buggy encoding had `5a68 0800 1264` (contract tag, length 0x68, + // type=0, parameter tag, length 0x64). The canonical encoding TRON + // re-serializes to drops the `0800` and decrements the contract length + // by 2 -> `5a66 1264`. + assert.ok( + !rawDataHex.includes('5a68080012'), + `raw_data_hex must not include the proto3-default \`type: 0\` tag (0800). Got: ${rawDataHex}` + ); + assert.ok( + rawDataHex.includes('5a661264'), + `raw_data_hex must use the canonical contract framing (5a66...1264). Got: ${rawDataHex}` + ); + }); }); describe('should validate', () => {