Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -892,8 +892,7 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
...resolveTssVerifyTransactionOptions(
txRequestResolved,
unsignedTx.serializedTxHex ?? unsignedTx.signableHex,
txParams,
this.baseCoin.getChain()
txParams
),
wallet: this.wallet,
walletType: this.wallet.multisigType(),
Expand Down
3 changes: 1 addition & 2 deletions modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsaMPCv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
22 changes: 10 additions & 12 deletions modules/sdk-core/src/bitgo/utils/tss/recipientUtils.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 —
Expand All @@ -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,
Expand Down Expand Up @@ -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<VerifyTransactionOptions, 'txPrebuild' | 'txParams' | 'verification'> {
if (ConsolidateIntent.is(txRequest.intent)) {
return {
Expand All @@ -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) };
}
2 changes: 1 addition & 1 deletion modules/sdk-core/src/bitgo/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
56 changes: 43 additions & 13 deletions modules/sdk-core/test/unit/bitgo/utils/tss/recipientUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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);
});

Expand All @@ -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');
});

Expand All @@ -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');
});

Expand All @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -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');
Expand All @@ -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');
});
Expand Down Expand Up @@ -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 },
Expand All @@ -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);
});
});
});
Loading