From 389f90c9f867a17e9aa36dac6564ae80e4a8a3d0 Mon Sep 17 00:00:00 2001 From: Support Bot Date: Fri, 26 Jun 2026 14:16:30 +0000 Subject: [PATCH 1/2] Initial state Session-Id: afc68d99-c916-424d-bffc-d814dea1d611 Task-Id: 60985a53-c0de-4df0-9fb9-187bad6e4464 From d65632f6385b0e7d4dadff061b887eb8eebc0187 Mon Sep 17 00:00:00 2001 From: Support Bot Date: Fri, 26 Jun 2026 14:26:39 +0000 Subject: [PATCH 2/2] 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 2ba3b225a2..d68b21c8ad 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 3e36160e4c..fc00197596 100644 --- a/modules/sdk-coin-eth/test/unit/ethWallet.ts +++ b/modules/sdk-coin-eth/test/unit/ethWallet.ts @@ -485,4 +485,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'); + }); });