From 6c5e13f0cba9aeb15dc0304a73b9cb1d1318e9fa Mon Sep 17 00:00:00 2001 From: Alok Baltiyal Date: Fri, 5 Dec 2025 13:07:00 +0530 Subject: [PATCH] fix(express): support legacy EIP1559 transaction in type validation TICKET: WIN-8042 --- .../src/typedRoutes/api/v2/coinSignTx.ts | 14 +++- .../test/unit/typedRoutes/coinSignTx.ts | 78 +++++++++++++++++++ 2 files changed, 88 insertions(+), 4 deletions(-) diff --git a/modules/express/src/typedRoutes/api/v2/coinSignTx.ts b/modules/express/src/typedRoutes/api/v2/coinSignTx.ts index 4561c81177..ce567d0c64 100644 --- a/modules/express/src/typedRoutes/api/v2/coinSignTx.ts +++ b/modules/express/src/typedRoutes/api/v2/coinSignTx.ts @@ -14,13 +14,19 @@ export const CoinSignTxParams = { /** * EIP1559 transaction parameters for Ethereum * Reference: modules/abstract-eth/src/abstractEthLikeNewCoins.ts:116-119 - * Note: Both fields are REQUIRED when EIP1559 object is provided + * + * Note: Changed to t.partial() to support multiple use cases: + * 1. Full EIP1559 transactions: { maxFeePerGas, maxPriorityFeePerGas } + * 2. Legacy token transactions: { isEip1559: false } (SDK marker for non-EIP1559) + * 3. Legacy base coin transactions: undefined (field omitted entirely) */ -export const EIP1559 = t.type({ - /** Maximum priority fee per gas (REQUIRED) */ +export const EIP1559 = t.partial({ + /** Maximum priority fee per gas */ maxPriorityFeePerGas: t.union([t.string, t.number]), - /** Maximum fee per gas (REQUIRED) */ + /** Maximum fee per gas */ maxFeePerGas: t.union([t.string, t.number]), + /** Flag indicating whether transaction uses EIP1559 fee model */ + isEip1559: t.boolean, }); /** diff --git a/modules/express/test/unit/typedRoutes/coinSignTx.ts b/modules/express/test/unit/typedRoutes/coinSignTx.ts index f917172587..e491a2c8c3 100644 --- a/modules/express/test/unit/typedRoutes/coinSignTx.ts +++ b/modules/express/test/unit/typedRoutes/coinSignTx.ts @@ -782,6 +782,84 @@ describe('CoinSignTx codec tests', function () { assert.strictEqual(decoded.recipients[0].tokenName, 'USDC'); assert.strictEqual(decoded.recipients[0].data, '0xabcdef'); }); + + it('should validate prebuild with eip1559 as legacy transaction marker (token transactions)', function () { + // This tests the fix for WP-6630 - SDK sends { isEip1559: false } for legacy token transactions + const validPrebuild = { + txHex: '0xf9010d81f88408ccd68c830f424094932bb3ec0a0cb9e8aafba8e2f2c7ecf83deacc5c80b8e40dcd7a6c', + gasPrice: '147641996', + gasLimit: 1000000, + eip1559: { + isEip1559: false, // Legacy transaction marker + }, + coin: 'avaxc', + token: 'avaxc:usdc', + walletId: '6618f448f6c53303d38130bc60e9efe6', + }; + + const decoded = assertDecode(TransactionPrebuild, validPrebuild); + assert.deepStrictEqual(decoded.eip1559, validPrebuild.eip1559); + assert.strictEqual(decoded.eip1559?.isEip1559, false); + assert.strictEqual(decoded.gasPrice, validPrebuild.gasPrice); + assert.strictEqual(decoded.gasLimit, validPrebuild.gasLimit); + assert.strictEqual(decoded.coin, validPrebuild.coin); + assert.strictEqual(decoded.token, validPrebuild.token); + }); + + it('should validate prebuild without eip1559 field (base coin legacy transactions)', function () { + // This tests base coin legacy transactions where eip1559 is undefined + const validPrebuild = { + txHex: '0xf9012e81f88408b66e168307a12094932bb3ec0a0cb9e8aafba8e2f2c7ecf83deacc5c80b90104', + gasPrice: 146173462, + gasLimit: 500000, + coin: 'avaxc', + walletId: '6618f448f6c53303d38130bc60e9efe6', + // Note: No eip1559 field - it's undefined + }; + + const decoded = assertDecode(TransactionPrebuild, validPrebuild); + assert.strictEqual(decoded.eip1559, undefined); + assert.strictEqual(decoded.gasPrice, validPrebuild.gasPrice); + assert.strictEqual(decoded.gasLimit, validPrebuild.gasLimit); + assert.strictEqual(decoded.coin, validPrebuild.coin); + }); + + it('should validate prebuild with full EIP1559 parameters', function () { + // This tests actual EIP1559 transactions with maxFeePerGas and maxPriorityFeePerGas + const validPrebuild = { + txHex: '0x02f87301808459682f008459682f0e8252089439c0f2000e39186af4b78b554eb96a2ea8dc5c3680', + eip1559: { + maxPriorityFeePerGas: '1500000000', + maxFeePerGas: '2000000000', + isEip1559: true, + }, + coin: 'eth', + walletId: '6618f448f6c53303d38130bc60e9efe6', + }; + + const decoded = assertDecode(TransactionPrebuild, validPrebuild); + assert.deepStrictEqual(decoded.eip1559, validPrebuild.eip1559); + assert.strictEqual(decoded.eip1559?.maxPriorityFeePerGas, '1500000000'); + assert.strictEqual(decoded.eip1559?.maxFeePerGas, '2000000000'); + assert.strictEqual(decoded.eip1559?.isEip1559, true); + }); + + it('should validate prebuild with partial EIP1559 fields', function () { + // This tests that EIP1559 fields are optional (t.partial) + const validPrebuild = { + txHex: '0x02f87301808459682f008459682f0e8252089439c0f2000e39186af4b78b554eb96a2ea8dc5c3680', + eip1559: { + maxFeePerGas: '2000000000', + // Only one field provided - tests that t.partial allows omitting fields + }, + coin: 'eth', + walletId: '6618f448f6c53303d38130bc60e9efe6', + }; + + const decoded = assertDecode(TransactionPrebuild, validPrebuild); + assert.deepStrictEqual(decoded.eip1559, validPrebuild.eip1559); + assert.strictEqual(decoded.eip1559?.maxFeePerGas, '2000000000'); + }); }); describe('CoinSignTxBody', function () {