From 3e554810663131130210f90287d230286f9c651c Mon Sep 17 00:00:00 2001 From: Paras Garg Date: Mon, 16 Feb 2026 12:41:07 +0530 Subject: [PATCH] feat(sdk-core): add bridgeFunds intent support for HyperEVM TSS wallets Ticket: CECHO-103 --- modules/bitgo/test/v2/unit/wallet.ts | 47 +++++++++++++++++++ modules/sdk-core/src/bitgo/utils/mpcUtils.ts | 7 +++ .../sdk-core/src/bitgo/utils/tss/baseTypes.ts | 8 ++++ modules/sdk-core/src/bitgo/wallet/iWallet.ts | 5 ++ modules/sdk-core/src/bitgo/wallet/wallet.ts | 15 ++++++ 5 files changed, 82 insertions(+) diff --git a/modules/bitgo/test/v2/unit/wallet.ts b/modules/bitgo/test/v2/unit/wallet.ts index d6f198138e..047c82063d 100644 --- a/modules/bitgo/test/v2/unit/wallet.ts +++ b/modules/bitgo/test/v2/unit/wallet.ts @@ -3462,6 +3462,32 @@ describe('V2 Wallet:', function () { args[1]!.should.equal('full'); }); + it('should call prebuildTxWithIntent with the correct params for bridgeFunds', async function () { + const intentAmount = { value: '1000000', symbol: 'thypeevm' }; + const feeOptions = { + maxFeePerGas: 3000000000, + maxPriorityFeePerGas: 2000000000, + }; + + const prebuildTxWithIntent = sandbox.stub(ECDSAUtils.EcdsaUtils.prototype, 'prebuildTxWithIntent'); + prebuildTxWithIntent.resolves(txRequestFull); + + await tssEthWallet.prebuildTransaction({ + reqId, + type: 'bridgeFunds', + intentAmount, + feeOptions, + }); + + sinon.assert.calledOnce(prebuildTxWithIntent); + const args = prebuildTxWithIntent.args[0]; + args[0]!.intentType.should.equal('bridgeFunds'); + args[0]!.amount!.should.deepEqual(intentAmount); + args[0]!.feeOptions!.should.deepEqual(feeOptions); + args[0]!.should.not.have.property('recipients'); + args[1]!.should.equal('full'); + }); + it('should call prebuildTxWithIntent with the correct feeOptions when passing using the legacy format', async function () { const recipients = [ { @@ -3722,6 +3748,27 @@ describe('V2 Wallet:', function () { intent.intentType.should.equal('fillNonce'); }); + it('populate intent should return valid bridgeFunds intent', async function () { + const mpcUtils = new ECDSAUtils.EcdsaUtils(bitgo, bitgo.coin('hteth')); + const amount = { value: '1000000', symbol: 'thypeevm' }; + const feeOptions = { + maxFeePerGas: 3000000000, + maxPriorityFeePerGas: 2000000000, + }; + + const intent = mpcUtils.populateIntent(bitgo.coin('hteth'), { + reqId, + intentType: 'bridgeFunds', + amount, + feeOptions, + }); + + intent.intentType.should.equal('bridgeFunds'); + intent.amount!.should.deepEqual(amount); + intent.feeOptions!.should.deepEqual(feeOptions); + intent.should.have.property('recipients', undefined); + }); + it('should build a single recipient transfer transaction providing apiVersion parameter as "full" ', async function () { const recipients = [ { diff --git a/modules/sdk-core/src/bitgo/utils/mpcUtils.ts b/modules/sdk-core/src/bitgo/utils/mpcUtils.ts index b332f02a48..b0c21776c2 100644 --- a/modules/sdk-core/src/bitgo/utils/mpcUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/mpcUtils.ts @@ -155,6 +155,7 @@ export abstract class MpcUtils { 'transferAccept', 'transferReject', 'transferOfferWithdrawn', + 'bridgeFunds', ].includes(params.intentType) ) { assert(params.recipients, `'recipients' is a required parameter for ${params.intentType} intent`); @@ -224,6 +225,12 @@ export abstract class MpcUtils { tokenName: params.tokenName, feeOptions: params.feeOptions, }; + case 'bridgeFunds': + return { + ...baseIntent, + amount: params.amount, + feeOptions: params.feeOptions, + }; default: throw new Error(`Unsupported intent type ${params.intentType}`); } diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts index 297924897c..b0fb098557 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts @@ -271,6 +271,10 @@ export interface PrebuildTransactionWithIntentOptions extends IntentOptionsBase txRequestId?: string; isTestTransaction?: boolean; contractId?: string; + /** + * Amount for intents that use a top-level amount instead of recipients (e.g. bridgeFunds). + */ + amount?: { value: string; symbol: string }; } export interface IntentRecipient { address: { @@ -342,6 +346,10 @@ export interface PopulatedIntent extends PopulatedIntentBase { txRequestId?: string; isTestTransaction?: boolean; contractId?: string; + /** + * Amount for intents that use a top-level amount instead of recipients (e.g. bridgeFunds). + */ + amount?: { value: string; symbol: string }; } export type TxRequestState = diff --git a/modules/sdk-core/src/bitgo/wallet/iWallet.ts b/modules/sdk-core/src/bitgo/wallet/iWallet.ts index 53e292db55..750bb80224 100644 --- a/modules/sdk-core/src/bitgo/wallet/iWallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/iWallet.ts @@ -219,6 +219,11 @@ export interface PrebuildTransactionOptions { txRequestId?: string; isTestTransaction?: boolean; contractId?: string; + /** + * Amount for intents that use a top-level amount instead of recipients (e.g. bridgeFunds). + * Named intentAmount to avoid collision with SendOptions.amount which is string | number. + */ + intentAmount?: { value: string; symbol: string }; } export interface PrebuildAndSignTransactionOptions extends PrebuildTransactionOptions, WalletSignTransactionOptions { diff --git a/modules/sdk-core/src/bitgo/wallet/wallet.ts b/modules/sdk-core/src/bitgo/wallet/wallet.ts index ae9d9179fe..de1a4386c2 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallet.ts @@ -3746,6 +3746,21 @@ export class Wallet implements IWallet { params.preview ); break; + case 'bridgeFunds': + txRequest = await this.tssUtils!.prebuildTxWithIntent( + { + reqId, + intentType: 'bridgeFunds', + sequenceId: params.sequenceId, + comment: params.comment, + amount: params.intentAmount, + nonce: params.nonce, + feeOptions, + }, + apiVersion, + params.preview + ); + break; default: throw new Error(`transaction type not supported: ${params.type}`); }