From e25cd94e53e8f0992959e6b8c1a6658814ee9f7a Mon Sep 17 00:00:00 2001 From: Marzooqa Naeema Kather Date: Fri, 23 Oct 2026 15:53:46 +0530 Subject: [PATCH] fix(sdk-coin-sol): default recipient tokenName on token wallets Token transfers sent with explicit txParams recipients that carry no tokenName failed local verification with "Tx outputs does not match with expected txParams recipients". On a token wallet getChain() is the token itself, so a recipient without tokenName is still a token transfer; default it so the ATA derivation check can match. Ticket: WCI-2009 Co-Authored-By: Claude --- modules/sdk-coin-sol/src/sol.ts | 8 +++++--- modules/sdk-coin-sol/test/unit/sol.ts | 28 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/modules/sdk-coin-sol/src/sol.ts b/modules/sdk-coin-sol/src/sol.ts index 2e7c4e61492..a1cbfc2b02f 100644 --- a/modules/sdk-coin-sol/src/sol.ts +++ b/modules/sdk-coin-sol/src/sol.ts @@ -623,9 +623,11 @@ export class Sol extends BaseCoin { // users do not input recipients for consolidation requests as they are generated by the server // Close-ATA txs do not populate explainedTx.outputs; recipients carry ATA addresses for intent only. if (txParams.recipients !== undefined && !isTokenEnablementTx && !isCloseAssociatedTokenAccountTx) { - const filteredRecipients = txParams.recipients?.map((recipient) => - _.pick(recipient, ['address', 'amount', 'tokenName', 'tokenAddress', 'programId']) - ); + // On a token wallet getChain() is the token itself, so a recipient without tokenName is still a token transfer. + const filteredRecipients = txParams.recipients?.map((recipient) => { + const picked = _.pick(recipient, ['address', 'amount', 'tokenName', 'tokenAddress', 'programId']); + return coinConfig.isToken && !picked.tokenName ? { ...picked, tokenName: this.getChain() } : picked; + }); const filteredOutputs = explainedTx.outputs.map((output) => _.pick(output, ['address', 'amount', 'tokenName'])); if (filteredRecipients.length !== filteredOutputs.length) { diff --git a/modules/sdk-coin-sol/test/unit/sol.ts b/modules/sdk-coin-sol/test/unit/sol.ts index 4e85ebc4a2e..edcb11721ab 100644 --- a/modules/sdk-coin-sol/test/unit/sol.ts +++ b/modules/sdk-coin-sol/test/unit/sol.ts @@ -9,6 +9,7 @@ import { Keypair, PublicKey, SystemProgram, TransactionInstruction } from '@sola import * as base58 from 'bs58'; import * as nacl from 'tweetnacl'; +import { SolToken } from '../../src/solToken'; import { compileTransactionMessage } from '../../src/lib/serialization/compileTransactionMessage'; import { serializeWireTransaction } from '../../src/lib/serialization/wire-transaction'; @@ -968,6 +969,33 @@ describe('SOL:', function () { validTransaction.should.equal(true); }); + it('should default a missing recipient tokenName to the token on a token wallet', async function () { + const { name, coinConstructor } = SolToken.createTokenConstructors().find((t) => t.name === 'tsol:usdc')!; + bitgo.register(name, coinConstructor); + const tokenCoin = bitgo.coin('tsol:usdc'); + const address = 'AF5H6vBkFnJuVqChRPgPQ4JRcQ5Gk25HBFhQQkyojmvg'; // Native SOL address + const txParams = newTxParamsTokenTransfer(); + txParams.recipients = [{ address, amount: '1' }]; + const txPrebuild = newTxPrebuildTokenTransfer(); + const wallet = new Wallet(bitgo, tokenCoin, { + id: '5b34252f1bf349930e34020a00000000', + coin: 'tsol', + keys: [ + '5b3424f91bf349930e34017500000000', + '5b3424f91bf349930e34017600000000', + '5b3424f91bf349930e34017700000000', + ], + coinSpecific: { rootAddress: '4DujymUFbQ8GBKtAwAZrQ6QqpvtBEivL48h4ta2oJGd2' }, + multisigType: 'tss', + }); + (await tokenCoin.verifyTransaction({ txParams, txPrebuild, wallet } as any)).should.equal(true); + + txParams.recipients = [{ address, amount: '2' }]; + await tokenCoin + .verifyTransaction({ txParams, txPrebuild, wallet } as any) + .should.be.rejectedWith('Tx outputs does not match with expected txParams recipients'); + }); + it('should fail to verify token transaction with different recipient tokenName', async function () { const txParams = newTxParamsTokenTransfer(); const address = 'AF5H6vBkFnJuVqChRPgPQ4JRcQ5Gk25HBFhQQkyojmvg'; // Native SOL address