From 6fc31f6707e39516b4ceb6b6bce72b6cabb6b2cf Mon Sep 17 00:00:00 2001 From: N V Rakesh Reddy Date: Wed, 10 Jun 2026 09:02:35 +0530 Subject: [PATCH] fix(sdk-core): populate recipients in buildTokenEnablements for TSS wallets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TSS wallets were missing `buildParams.recipients` after `buildTokenEnablements` because the TSS branch only called `prebuildTransaction(buildParams)` without first mapping `enableTokens → recipients`. The non-TSS branch correctly did this mapping. When `verifyTransaction` runs (`txParams = { ...txPrebuild.buildParams, ...params }`), `txParams.recipients` was undefined, causing `validateRawReceiver` in near.ts to throw "missing token name in transaction parameters". Unlike the non-TSS path, `enableTokens` is kept on `buildParams` for TSS since the server needs it to build the enableToken intent. Adds a regression test that exercises the real production call path — using `txPrebuild.buildParams` as `txParams` — which is what the existing TSS tests failed to do. TICKET: WP-5782 Co-Authored-By: Claude Sonnet 4.6 Co-authored-by: Cursor --- .../test/unit/tokenEnablementValidation.ts | 51 +++++++++++++++++++ modules/sdk-core/src/bitgo/wallet/wallet.ts | 14 +++++ 2 files changed, 65 insertions(+) diff --git a/modules/sdk-coin-near/test/unit/tokenEnablementValidation.ts b/modules/sdk-coin-near/test/unit/tokenEnablementValidation.ts index e6b68f3ac3..ba34e2333b 100644 --- a/modules/sdk-coin-near/test/unit/tokenEnablementValidation.ts +++ b/modules/sdk-coin-near/test/unit/tokenEnablementValidation.ts @@ -612,6 +612,57 @@ describe('NEAR Token Enablement Validation', function () { ); }); + it('should populate recipients from enableTokens in buildParams for TSS wallets', async function () { + // Regression test: before the fix, buildTokenEnablements on TSS wallets did not populate + // buildParams.recipients — only buildParams.enableTokens was set. This caused verifyTransaction + // to throw "missing token name in transaction parameters" because it reads from + // txParams.recipients[0].tokenName (where txParams = { ...txPrebuild.buildParams, ...params }). + const bgUrl = common.Environments['test'].uri; + + nock(bgUrl) + .post(`/api/v2/wallet/${tssWallet.id()}/txrequests`) + .reply(200, { + txRequestId: 'test-request-id', + apiVersion: 'full', + transactions: [ + { + state: 'pending', + unsignedTx: { + serializedTxHex: testData.rawTx.selfStorageDeposit.unsigned, + signableHex: testData.rawTx.selfStorageDeposit.unsigned, + derivationPath: 'm/0', + feeInfo: { fee: 1160407, feeString: '1160407' }, + }, + signatureShares: [], + }, + ], + }); + + const buildResult = await tssWallet.buildTokenEnablements({ + enableTokens: [{ name: 'tnear:tnep24dp' }], + }); + + const txPrebuild = buildResult[0] as any; + + // Verify buildParams.recipients is populated — this is what flows into txParams + // via { ...txPrebuild.buildParams, ...params } inside prebuildAndSignTransaction + txPrebuild.buildParams.should.have.property('recipients'); + txPrebuild.buildParams.recipients.should.have.length(1); + txPrebuild.buildParams.recipients[0].tokenName.should.equal('tnear:tnep24dp'); + txPrebuild.buildParams.recipients[0].address.should.equal(testData.accounts.account1.address); + + // Simulate the txParams construction that prebuildAndSignTransaction performs, + // then confirm verifyTransaction no longer throws "missing token name" + const txParams = { ...txPrebuild.buildParams }; + await basecoin.verifyTransaction({ + txParams, + txPrebuild, + wallet: tssWallet as any, + verification: { verifyTokenEnablement: true }, + walletType: 'tss', + }); + }); + it('should validate correct storage deposit in TSS wallet flow', async function () { const bgUrl = common.Environments['test'].uri; diff --git a/modules/sdk-core/src/bitgo/wallet/wallet.ts b/modules/sdk-core/src/bitgo/wallet/wallet.ts index 9fb0914c42..f19a6b86ad 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallet.ts @@ -3961,6 +3961,20 @@ export class Wallet implements IWallet { } // Check if we build with intent if (this._wallet.multisigType === 'tss') { + // Populate recipients from enableTokens so verifyTransaction can access tokenName. + // enableTokens is kept (not deleted) since the server needs it to build the transaction. + buildParams.recipients = params.enableTokens.map((token) => { + const address = + token.address || this._wallet.coinSpecific?.baseAddress || this._wallet.coinSpecific?.rootAddress; + if (!address) { + throw new Error('Wallet does not have base address, must specify with token param'); + } + return { + tokenName: token.name, + address, + amount: '0', + }; + }); return [await this.prebuildTransaction(buildParams)]; } else { // Rewrite tokens into recipients for buildTransaction