diff --git a/src/SmartTransactionsController.test.ts b/src/SmartTransactionsController.test.ts index fab1a26c..bcb2b7c9 100644 --- a/src/SmartTransactionsController.test.ts +++ b/src/SmartTransactionsController.test.ts @@ -722,6 +722,105 @@ describe('SmartTransactionsController', () => { expect(submittedSmartTransaction.deviceModel).toBe('ledger'); }); }); + + it('submits a batch of signed transactions', async () => { + await withController(async ({ controller }) => { + const signedTransaction1 = createSignedTransaction(); + const signedTransaction2 = createSignedTransaction(); + const submitTransactionsApiResponse = + createSubmitTransactionsApiResponse(); // It has uuid. + nock(API_BASE_URL) + .post( + `/networks/${ethereumChainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`, + ) + .reply(200, submitTransactionsApiResponse); + + const txParams = createTxParams(); + const result = await controller.submitSignedTransactions({ + signedTransactions: [signedTransaction1, signedTransaction2], + txParams, + }); + + // Check response has both txHash and txHashes + expect(result.uuid).toBe('dP23W7c2kt4FK9TmXOkz1UM2F20'); + expect(result.txHash).toBeDefined(); + expect(result.txHashes).toBeDefined(); + expect(result.txHashes?.length).toBe(2); + + // Check smart transaction has correct properties + const submittedSmartTransaction = + controller.state.smartTransactionsState.smartTransactions[ + ChainId.mainnet + ][0]; + expect(submittedSmartTransaction.uuid).toBe( + 'dP23W7c2kt4FK9TmXOkz1UM2F20', + ); + expect(submittedSmartTransaction.txHashes).toBeDefined(); + expect(submittedSmartTransaction.txHashes?.length).toBe(2); + expect(submittedSmartTransaction.txHash).toBe(result.txHashes[0]); + }); + }); + + it('works with optional signedCanceledTransactions', async () => { + await withController(async ({ controller }) => { + const signedTransaction = createSignedTransaction(); + const submitTransactionsApiResponse = + createSubmitTransactionsApiResponse(); + + // Verify that the request body has empty rawCancelTxs array when signedCanceledTransactions is omitted + let requestBody: any; + nock(API_BASE_URL) + .post( + `/networks/${ethereumChainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`, + (body) => { + requestBody = body; + return true; + }, + ) + .reply(200, submitTransactionsApiResponse); + + await controller.submitSignedTransactions({ + signedTransactions: [signedTransaction], + txParams: createTxParams(), + // No signedCanceledTransactions provided + }); + + // Verify the request was made with an empty rawCancelTxs array + expect(requestBody).toBeDefined(); + expect(requestBody.rawCancelTxs).toStrictEqual([]); + }); + }); + + it('works without txParams', async () => { + await withController(async ({ controller }) => { + const signedTransaction = createSignedTransaction(); + const submitTransactionsApiResponse = + createSubmitTransactionsApiResponse(); + + nock(API_BASE_URL) + .post( + `/networks/${ethereumChainIdDec}/submitTransactions?stxControllerVersion=${packageJson.version}`, + ) + .reply(200, submitTransactionsApiResponse); + + // This should not throw an error when txParams is missing + const result = await controller.submitSignedTransactions({ + signedTransactions: [signedTransaction], + // No txParams provided + }); + + expect(result.uuid).toBe('dP23W7c2kt4FK9TmXOkz1UM2F20'); + + // The transaction should still be created in state + const submittedSmartTransaction = + controller.state.smartTransactionsState.smartTransactions[ + ChainId.mainnet + ][0]; + expect(submittedSmartTransaction.uuid).toBe( + 'dP23W7c2kt4FK9TmXOkz1UM2F20', + ); + }); + }); }); describe('fetchSmartTransactionsStatus', () => { diff --git a/src/SmartTransactionsController.ts b/src/SmartTransactionsController.ts index e24d45f6..ac3aa45f 100644 --- a/src/SmartTransactionsController.ts +++ b/src/SmartTransactionsController.ts @@ -916,11 +916,11 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo transactionMeta, txParams, signedTransactions, - signedCanceledTransactions, + signedCanceledTransactions = [], networkClientId, }: { signedTransactions: SignedTransaction[]; - signedCanceledTransactions: SignedCanceledTransaction[]; + signedCanceledTransactions?: SignedCanceledTransaction[]; transactionMeta?: TransactionMeta; txParams?: TransactionParams; networkClientId?: NetworkClientId; @@ -948,10 +948,12 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo const time = Date.now(); let preTxBalance; try { - const preTxBalanceBN = await query(ethQuery, 'getBalance', [ - txParams?.from, - ]); - preTxBalance = new BigNumber(preTxBalanceBN).toString(16); + if (txParams?.from) { + const preTxBalanceBN = await query(ethQuery, 'getBalance', [ + txParams.from, + ]); + preTxBalance = new BigNumber(preTxBalanceBN).toString(16); + } } catch (error) { console.error('provider error', error); } @@ -970,9 +972,12 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo nonceDetails = nonceLock.nonceDetails; txParams.nonce ??= nonce; } + + const txHashes = signedTransactions.map((tx) => getTxHash(tx)); const submitTransactionResponse = { ...data, - txHash: getTxHash(signedTransactions[0]), + txHash: txHashes[0], // For backward compatibility + txHashes, }; try { @@ -990,6 +995,7 @@ export default class SmartTransactionsController extends StaticIntervalPollingCo type: transactionMeta?.type ?? 'swap', transactionId: transactionMeta?.id, networkClientId: selectedNetworkClientId, + txHashes, // Add support for multiple transaction hashes }, { chainId, ethQuery }, ); diff --git a/src/types.ts b/src/types.ts index 1c48ba61..dceecf55 100644 --- a/src/types.ts +++ b/src/types.ts @@ -73,6 +73,7 @@ export type SmartTransactionsStatus = { export type SmartTransaction = { uuid: string; txHash?: string; + txHashes?: string[]; chainId?: string; destinationTokenAddress?: string; destinationTokenDecimals?: string;