From 184a67e83669785fb0116aee36fb7845ab0f6290 Mon Sep 17 00:00:00 2001 From: Support Bot Date: Fri, 26 Jun 2026 14:26:39 +0000 Subject: [PATCH] fix(sdk-coin-eth): guard BN construction in signFinal for EIP-1559 txns In Eth.signFinal (the second-signature path for WRW/offline-vault transactions), the ethTxParams object was unconditionally calling `new BN(txPrebuild.gasPrice)`. For EIP-1559 transactions, gasPrice is absent from txPrebuild, so bn.js received undefined and threw "Cannot read properties of undefined (reading 'toString')" internally. The buildTransaction helper already handles EIP-1559 correctly by consuming maxFeePerGas/maxPriorityFeePerGas from the eip1559 field and ignoring gasPrice when eip1559 is present, so passing undefined is safe. The fix skips BN construction when txPrebuild.eip1559 is set, preventing the crash when users raise fees on EIP-1559 Ethereum transactions. Added a WRWUnsignedSweepEIP1559ETHTx fixture and a corresponding test that exercises the isLastSignature=true path with an EIP-1559 txPrebuild (no gasPrice field). Ticket: COINS-572 Session-Id: afc68d99-c916-424d-bffc-d814dea1d611 Task-Id: 60985a53-c0de-4df0-9fb9-187bad6e4464 --- modules/sdk-coin-eth/src/eth.ts | 2 +- modules/sdk-coin-eth/test/fixtures/eth.ts | 27 +++++++++++++++++++++ modules/sdk-coin-eth/test/unit/ethWallet.ts | 26 ++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/modules/sdk-coin-eth/src/eth.ts b/modules/sdk-coin-eth/src/eth.ts index b8356a8e4a..34731475eb 100644 --- a/modules/sdk-coin-eth/src/eth.ts +++ b/modules/sdk-coin-eth/src/eth.ts @@ -429,7 +429,7 @@ export class Eth extends AbstractEthLikeNewCoins { nonce: params.signingKeyNonce !== undefined ? params.signingKeyNonce : params.txPrebuild.halfSigned?.backupKeyNonce, value: 0, - gasPrice: new optionalDeps.ethUtil.BN(txPrebuild.gasPrice), + gasPrice: txPrebuild.eip1559 ? undefined : new optionalDeps.ethUtil.BN(txPrebuild.gasPrice), gasLimit: new optionalDeps.ethUtil.BN(txPrebuild.gasLimit), data: sendData, }; diff --git a/modules/sdk-coin-eth/test/fixtures/eth.ts b/modules/sdk-coin-eth/test/fixtures/eth.ts index 311b176e61..69a773373c 100644 --- a/modules/sdk-coin-eth/test/fixtures/eth.ts +++ b/modules/sdk-coin-eth/test/fixtures/eth.ts @@ -151,6 +151,33 @@ module.exports.WRWUnsignedSweepETHTx = { nextContractSequenceId: 1, }; +module.exports.WRWUnsignedSweepEIP1559ETHTx = { + tx: 'f9012b808504a817c8008307a12094fd12f1d563650fbdd9314dce06992159778f634380b9010439125215000000000000000000000000a98fdfc2c711260cd665a3884b509b4a5ad6f4e80000000000000000000000000000000000000000000000003782dace9d90000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000005f6a471c000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c8080', + userKey: + 'xpub661MyMwAqRbcFcNDKt46HgPAJTfNyQUS6M7i8jUwZKHz9wZGaK1XdQuT8XU5PkFfbrfoGXc1C4QD9PDJ7zhpu52rLzzynovwgcXh7NtDbH9', + backupKey: + 'xpub661MyMwAqRbcGUJYcAgycBqG5HrQoUJAvBv7PEbvjGGffdtMP8hx3DX9AwzaY4vA7ynqHfxzRTRLwS2E9DH1HRPG8u7kWXd4JMCNgonGGnk', + coin: 'teth', + eip1559: { maxFeePerGas: '40000000000', maxPriorityFeePerGas: '2000000000' }, + gasLimit: '500000', + recipients: [ + { + address: '0xa98fdfc2c711260cd665a3884b509b4a5ad6f4e8', + amount: '4000000000000000000', + }, + ], + walletContractAddress: '0xfd12f1d563650fbdd9314dce06992159778f6343', + amount: '4000000000000000000', + backupKeyNonce: 0, + recipient: { + address: '0xa98fdfc2c711260cd665a3884b509b4a5ad6f4e8', + amount: '4000000000000000000', + }, + expireTime: 1600800540, + contractSequenceId: 1, + nextContractSequenceId: 1, +}; + module.exports.WRWUnsignedSweepERC20Tx = { tx: 'f9010a808504a817c8008307a12094df07117705a9f8dc4c2a78de66b7f1797dba9d4e80b8e40dcd7a6c00000000000000000000000052c8b29ab8b0a49a01c2b75f8e7f11b23e0e37820000000000000000000000000000000000000000000000000de0b6b3a76400000000000000000000000000004f96fe3b7a6cf9725f59d353f723c1bdb64ca6aa00000000000000000000000000000000000000000000000000000000611fb4330000000000000000000000000000000000000000000000000000000000002a7f00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000001c8080', userKey: diff --git a/modules/sdk-coin-eth/test/unit/ethWallet.ts b/modules/sdk-coin-eth/test/unit/ethWallet.ts index c0ac6d4760..11f3e9654a 100644 --- a/modules/sdk-coin-eth/test/unit/ethWallet.ts +++ b/modules/sdk-coin-eth/test/unit/ethWallet.ts @@ -583,4 +583,30 @@ describe('final-sign transaction from WRW', function () { outputs[0].address.should.equal(fixtures.WRWUnsignedSweepERC20Tx.recipient.address); outputs[0].amount.should.equal(fixtures.WRWUnsignedSweepERC20Tx.recipient.amount); }); + + it('should add a second EIP-1559 signature to unsigned sweep for teth without throwing when gasPrice is absent', async function () { + const bitgo = TestBitGo.decorate(BitGoAPI, { env: 'test' }); + bitgo.safeRegister('teth', Teth.createInstance); + const basecoin: any = bitgo.coin('teth'); + const gasLimit = 500000; + const prv = + 'xprv9s21ZrQH143K3D8TXfvAJgHVfTEeQNW5Ys9wZtnUZkqPzFzSjbEJrWC1vZ4GnXCvR7rQL2UFX3RSuYeU9MrERm1XBvACow7c36vnz5iYyj2'; + const tx = { + txPrebuild: fixtures.WRWUnsignedSweepEIP1559ETHTx, + prv, + }; + const halfSigned = await basecoin.signTransaction(tx); + + const wrapper = {} as SignTransactionOptions; + wrapper.txPrebuild = halfSigned; + wrapper.txPrebuild.recipients = halfSigned.halfSigned.recipients; + wrapper.txPrebuild.eip1559 = fixtures.WRWUnsignedSweepEIP1559ETHTx.eip1559; + wrapper.txPrebuild.gasLimit = gasLimit.toString(); + wrapper.isLastSignature = true; + wrapper.walletContractAddress = fixtures.WRWUnsignedSweepEIP1559ETHTx.walletContractAddress; + wrapper.prv = prv; + + const finalSignedTx = await basecoin.signTransaction(wrapper); + finalSignedTx.should.have.property('txHex'); + }); });