diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts index 5a49de6875..1728e802f3 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts @@ -892,8 +892,7 @@ export class EddsaUtils extends baseTSSUtils { ...resolveTssVerifyTransactionOptions( txRequestResolved, unsignedTx.serializedTxHex ?? unsignedTx.signableHex, - txParams, - this.baseCoin.getChain() + txParams ), wallet: this.wallet, walletType: this.wallet.multisigType(), diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts index 7011dfcef2..eef78b01e6 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts @@ -601,8 +601,7 @@ export class EddsaMPCv2Utils extends BaseEddsaUtils { ...resolveTssVerifyTransactionOptions( txRequest, unsignedTx.serializedTxHex ?? txOrMessageToSign, - params.txParams, - this.baseCoin.getChain() + params.txParams ), wallet: this.wallet, walletType: this.wallet.multisigType(), diff --git a/modules/sdk-core/src/bitgo/utils/tss/recipientUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/recipientUtils.ts index c167386b08..1936b80dd9 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/recipientUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/recipientUtils.ts @@ -1,4 +1,7 @@ import * as t from 'io-ts'; + +import { coins } from '@bitgo/statics'; + import { TransactionParams, VerifyTransactionOptions } from '../../baseCoin'; import { InvalidTransactionError } from '../../errors'; import { PopulatedIntent, TxRequest } from './baseTypes'; @@ -113,7 +116,7 @@ export const NO_RECIPIENT_TX_TYPES = new Set([ * mapped to ITransactionRecipient shape when txParams.recipients is absent. * * tokenName is derived from tokenData.tokenName when present, otherwise from - * amount.symbol when chainName is provided and symbol differs from it. + * amount.symbol when statics marks the symbol as a token. * * Staking intents (BSC delegate/undelegate, CELO stake/unstake, etc.) are * identified generically by the presence of `stakingRequestId` on the intent — @@ -125,21 +128,18 @@ export const NO_RECIPIENT_TX_TYPES = new Set([ * * @param txRequest - the transaction request containing the persisted intent * @param txParams - the caller-supplied transaction parameters (may be undefined) - * @param chainName - the base chain name (e.g. 'sol', 'tsol') used to exclude - * native-coin transfers from tokenName; pass baseCoin.getChain() */ export function resolveEffectiveTxParams( txRequest: TxRequest, - txParams: TransactionParams | undefined, - chainName?: string + txParams: TransactionParams | undefined ): TransactionParams { const intentRecipients = (txRequest.intent as PopulatedIntent)?.recipients?.map((intentRecipient) => { - // Prefer tokenData.tokenName; fall back to amount.symbol when chainName is - // provided and differs from it. When absent, skip the symbol fallback. + // Prefer tokenData.tokenName; fall back to amount.symbol when statics marks it as a token. + // Decided from the symbol alone, so it does not depend on which coin the wallet was loaded through. const { symbol } = intentRecipient.amount; const tokenName = intentRecipient.tokenData?.tokenName || - (chainName !== undefined && symbol && symbol !== chainName ? symbol : undefined); + (symbol && coins.has(symbol) && coins.get(symbol).isToken ? symbol : undefined); return { address: intentRecipient.address.address, amount: intentRecipient.amount.value, @@ -207,13 +207,11 @@ const ConsolidateIntent = t.intersection([ * @param txRequest - the transaction request containing the persisted intent * @param txHex - the unsigned transaction to verify * @param txParams - the caller-supplied transaction parameters (may be undefined) - * @param chainName - the base chain name; pass baseCoin.getChain() */ export function resolveTssVerifyTransactionOptions( txRequest: TxRequest, txHex: string, - txParams: TransactionParams | undefined, - chainName: string + txParams: TransactionParams | undefined ): Pick { if (ConsolidateIntent.is(txRequest.intent)) { return { @@ -222,5 +220,5 @@ export function resolveTssVerifyTransactionOptions( verification: { consolidationToBaseAddress: true }, }; } - return { txPrebuild: { txHex }, txParams: resolveEffectiveTxParams(txRequest, txParams, chainName) }; + return { txPrebuild: { txHex }, txParams: resolveEffectiveTxParams(txRequest, txParams) }; } diff --git a/modules/sdk-core/src/bitgo/wallet/wallet.ts b/modules/sdk-core/src/bitgo/wallet/wallet.ts index 68b7d8e0da..5c7a4dafda 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallet.ts @@ -2410,7 +2410,7 @@ export class Wallet implements IWallet { assert(unsignedTx.signableHex, 'Missing signableHex in unsignedTx'); await this.baseCoin.verifyTransaction({ txPrebuild: { txHex: unsignedTx.serializedTxHex ?? unsignedTx.signableHex }, - txParams: resolveEffectiveTxParams(txRequest, params.verifyTxParams.txParams, this.baseCoin.getChain()), + txParams: resolveEffectiveTxParams(txRequest, params.verifyTxParams.txParams), wallet: this, verification: params.verifyTxParams.verification, reqId: params.reqId, diff --git a/modules/sdk-core/test/unit/bitgo/utils/tss/recipientUtils.ts b/modules/sdk-core/test/unit/bitgo/utils/tss/recipientUtils.ts index 1c93d7ca99..df29e938e1 100644 --- a/modules/sdk-core/test/unit/bitgo/utils/tss/recipientUtils.ts +++ b/modules/sdk-core/test/unit/bitgo/utils/tss/recipientUtils.ts @@ -329,7 +329,7 @@ describe('recipientUtils', function () { }); describe('tokenName preservation regression tests', function () { - it('preserves tokenName from amount.symbol when it differs from chainName', function () { + it('preserves tokenName from amount.symbol when it is a token', function () { const txRequest = makeTxRequest({ intent: { intentType: 'payment', @@ -341,12 +341,12 @@ describe('recipientUtils', function () { ], } as any, }); - const result = resolveEffectiveTxParams(txRequest, {}, 'tsol'); + const result = resolveEffectiveTxParams(txRequest, {}); assert.strictEqual(result.recipients?.length, 1); assert.strictEqual(result.recipients?.[0].tokenName, 'tsol:usdc'); }); - it('does NOT set tokenName when symbol equals chainName (native SOL transfer)', function () { + it('does NOT set tokenName for a native symbol (native SOL transfer)', function () { const txRequest = makeTxRequest({ intent: { intentType: 'payment', @@ -358,7 +358,7 @@ describe('recipientUtils', function () { ], } as any, }); - const result = resolveEffectiveTxParams(txRequest, {}, 'tsol'); + const result = resolveEffectiveTxParams(txRequest, {}); assert.strictEqual(result.recipients?.[0].tokenName, undefined); }); @@ -375,7 +375,7 @@ describe('recipientUtils', function () { ], } as any, }); - const result = resolveEffectiveTxParams(txRequest, {}, 'tsol'); + const result = resolveEffectiveTxParams(txRequest, {}); assert.strictEqual(result.recipients?.[0].tokenName, 'canonical-token-name'); }); @@ -392,7 +392,7 @@ describe('recipientUtils', function () { ], } as any, }); - const result = resolveEffectiveTxParams(txRequest, {}, 'sol'); + const result = resolveEffectiveTxParams(txRequest, {}); assert.strictEqual(result.recipients?.[0].tokenName, 'sol:usdc'); }); @@ -409,11 +409,11 @@ describe('recipientUtils', function () { ], } as any, }); - const result = resolveEffectiveTxParams(txRequest, {}, 'tsol'); + const result = resolveEffectiveTxParams(txRequest, {}); assert.strictEqual(result.recipients?.[0].tokenName, 'tsol:usdc'); }); - it('does NOT set tokenName when chainName is absent (legacy ECDSA callers, no tokenData)', function () { + it('does NOT set tokenName for a native symbol without tokenData (ECDSA)', function () { const txRequest = makeTxRequest({ intent: { intentType: 'payment', @@ -425,7 +425,7 @@ describe('recipientUtils', function () { assert.strictEqual(result.recipients?.[0].address, '0xabc'); }); - it('preserves tokenData.tokenName when chainName is absent (legacy ECDSA with tokenData)', function () { + it('preserves tokenData.tokenName (ECDSA with tokenData)', function () { const txRequest = makeTxRequest({ intent: { intentType: 'transferToken', @@ -458,7 +458,7 @@ describe('recipientUtils', function () { ], } as any, }); - const result = resolveEffectiveTxParams(txRequest, {}, 'tsol'); + const result = resolveEffectiveTxParams(txRequest, {}); assert.strictEqual(result.recipients?.length, 2); result.recipients!.forEach((r) => assert.strictEqual(r.tokenName, 'tsol:usdc')); assert.strictEqual(result.recipients![0].amount, '2000000'); @@ -481,7 +481,7 @@ describe('recipientUtils', function () { ], } as any, }); - const result = resolveEffectiveTxParams(txRequest, {}, 'tsol'); + const result = resolveEffectiveTxParams(txRequest, {}); assert.strictEqual(result.recipients![0].tokenName, undefined); assert.strictEqual(result.recipients![1].tokenName, 'tsol:usdc'); }); @@ -517,7 +517,7 @@ describe('recipientUtils', function () { const txRequest = makeTxRequest({ intent: { intentType: 'consolidate', consolidateId: 'consolidate-id', recipients: intentRecipients }, }); - assert.deepStrictEqual(resolveTssVerifyTransactionOptions(txRequest, 'abcd', undefined, 'tsol'), { + assert.deepStrictEqual(resolveTssVerifyTransactionOptions(txRequest, 'abcd', undefined), { txPrebuild: { txHex: 'abcd', consolidateId: 'consolidate-id' }, txParams: {}, verification: { consolidationToBaseAddress: true }, @@ -526,10 +526,40 @@ describe('recipientUtils', function () { it('resolves effective txParams for non-consolidation intents', function () { const txRequest = makeTxRequest({ intent: { intentType: 'payment', recipients: intentRecipients } }); - assert.deepStrictEqual(resolveTssVerifyTransactionOptions(txRequest, 'abcd', undefined, 'tsol'), { + assert.deepStrictEqual(resolveTssVerifyTransactionOptions(txRequest, 'abcd', undefined), { txPrebuild: { txHex: 'abcd' }, txParams: { recipients: [{ address: 'addr1', amount: '100', data: undefined }], type: 'payment' }, }); }); }); + + describe('token detection from the intent symbol', function () { + function intentWithSymbols(...symbols: string[]): TxRequest { + return makeTxRequest({ + intent: { + intentType: 'payment', + recipients: symbols.map((symbol) => ({ + address: { address: '54CAre4wvLrQFBERN1zg1sJfVoeaSGGiUjRkrvbowctp' }, + amount: { value: '1000000', symbol }, + })), + } as any, + }); + } + + it('sets tokenName for a token symbol', function () { + const result = resolveEffectiveTxParams(intentWithSymbols('tsol:usdcv2'), undefined); + assert.strictEqual(result.recipients?.[0].tokenName, 'tsol:usdcv2'); + }); + + it('does not set tokenName for a native symbol', function () { + const result = resolveEffectiveTxParams(intentWithSymbols('tsol', 'tdot'), undefined); + assert.strictEqual(result.recipients?.[0].tokenName, undefined); + assert.strictEqual(result.recipients?.[1].tokenName, undefined); + }); + + it('does not set tokenName for symbols missing from statics', function () { + const result = resolveEffectiveTxParams(intentWithSymbols('tsol:not-in-statics'), undefined); + assert.strictEqual(result.recipients?.[0].tokenName, undefined); + }); + }); });