Skip to content

Commit

Permalink
fix: await sequential transaction calls in <Transaction /> (#1918)
Browse files Browse the repository at this point in the history
  • Loading branch information
dschlabach authored Feb 3, 2025
1 parent cdf6a78 commit e31686a
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 4 deletions.
6 changes: 6 additions & 0 deletions src/transaction/hooks/useSendWalletTransactions.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import { useSendWalletTransactions } from './useSendWalletTransactions';
vi.mock('../utils/sendBatchedTransactions');
vi.mock('../utils/sendSingleTransactions');

// Mock wagmi provider
vi.mock('wagmi', () => ({
useConfig: vi.fn(),
useProviderDependencies: vi.fn(),
}));

describe('useSendWalletTransactions', () => {
beforeEach(() => {
vi.clearAllMocks();
Expand Down
5 changes: 4 additions & 1 deletion src/transaction/hooks/useSendWalletTransactions.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback } from 'react';
import type { ContractFunctionParameters } from 'viem';
import { useConfig } from 'wagmi';
import { Capabilities } from '../../core/constants';
import type { Call, UseSendWalletTransactionsParams } from '../types';
import { sendBatchedTransactions } from '../utils/sendBatchedTransactions';
Expand All @@ -14,6 +15,7 @@ export const useSendWalletTransactions = ({
sendCallsAsync,
walletCapabilities,
}: UseSendWalletTransactionsParams) => {
const config = useConfig();
return useCallback(
async (
transactions?:
Expand All @@ -39,11 +41,12 @@ export const useSendWalletTransactions = ({
} else {
// Non-batched transactions
await sendSingleTransactions({
config,
sendCallAsync,
transactions: resolvedTransactions,
});
}
},
[sendCallsAsync, sendCallAsync, capabilities, walletCapabilities],
[sendCallsAsync, sendCallAsync, capabilities, walletCapabilities, config],
);
};
1 change: 1 addition & 0 deletions src/transaction/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export type SendBatchedTransactionsParams = {
};

export type SendSingleTransactionParams = {
config: Config;
sendCallAsync: SendTransactionMutateAsync<Config, unknown> | (() => void);
transactions: (Call | ContractFunctionParameters)[];
};
Expand Down
58 changes: 57 additions & 1 deletion src/transaction/utils/sendSingleTransactions.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { encodeFunctionData, erc20Abi } from 'viem';
import { http, encodeFunctionData, erc20Abi } from 'viem';
import { baseSepolia } from 'viem/chains';
import { type Mock, beforeEach, describe, expect, it, vi } from 'vitest';
import { createConfig } from 'wagmi';
import { waitForTransactionReceipt } from 'wagmi/actions';
import { mock } from 'wagmi/connectors';
import type { Call } from '../types';
import { sendSingleTransactions } from './sendSingleTransactions';

Expand All @@ -10,6 +14,23 @@ vi.mock('viem', async (importOriginal) => {
encodeFunctionData: vi.fn(),
};
});
vi.mock('wagmi/actions', () => ({
waitForTransactionReceipt: vi.fn().mockResolvedValue({
transactionHash: 'receiptHash',
}),
}));

const mockConfig = createConfig({
chains: [baseSepolia],
connectors: [
mock({
accounts: ['0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'],
}),
],
transports: {
[baseSepolia.id]: http(),
},
});

describe('sendSingleTransactions', () => {
const mockSendCallAsync = vi.fn();
Expand All @@ -25,6 +46,7 @@ describe('sendSingleTransactions', () => {

it('should call sendCallAsync for each transaction when type is TRANSACTION_TYPE_CALLS', async () => {
await sendSingleTransactions({
config: mockConfig,
sendCallAsync: mockSendCallAsync,
transactions,
});
Expand All @@ -35,6 +57,7 @@ describe('sendSingleTransactions', () => {

it('should call sendCallAsync for each transaction', async () => {
await sendSingleTransactions({
config: mockConfig,
sendCallAsync: mockSendCallAsync,
transactions,
});
Expand All @@ -43,6 +66,7 @@ describe('sendSingleTransactions', () => {

it('should not call any function if transactions array is empty', async () => {
await sendSingleTransactions({
config: mockConfig,
sendCallAsync: mockSendCallAsync,
transactions: [],
});
Expand All @@ -51,10 +75,12 @@ describe('sendSingleTransactions', () => {

it('should handle mixed transaction types correctly', async () => {
await sendSingleTransactions({
config: mockConfig,
sendCallAsync: mockSendCallAsync,
transactions,
});
await sendSingleTransactions({
config: mockConfig,
sendCallAsync: mockSendCallAsync,
transactions,
});
Expand All @@ -63,6 +89,7 @@ describe('sendSingleTransactions', () => {

it('should transform contracts to calls', async () => {
await sendSingleTransactions({
config: mockConfig,
sendCallAsync: mockSendCallAsync,
transactions: [
{ abi: erc20Abi, address: '0x123', functionName: 'transfer' },
Expand All @@ -73,4 +100,33 @@ describe('sendSingleTransactions', () => {
to: '0x123',
});
});

it('should wait for transaction receipt when txHash is returned', async () => {
const transactions: Call[] = [{ to: '0x123', data: '0x456' }];
mockSendCallAsync.mockResolvedValueOnce('0xhash');

await sendSingleTransactions({
config: mockConfig,
sendCallAsync: mockSendCallAsync,
transactions,
});

expect(waitForTransactionReceipt).toHaveBeenCalledWith(mockConfig, {
hash: '0xhash',
confirmations: 1,
});
});

it('should skip waiting for receipt when txHash is null', async () => {
const transactions: Call[] = [{ to: '0x123', data: '0x456' }];
mockSendCallAsync.mockResolvedValueOnce(null);

await sendSingleTransactions({
config: mockConfig,
sendCallAsync: mockSendCallAsync,
transactions,
});

expect(waitForTransactionReceipt).not.toHaveBeenCalled();
});
});
12 changes: 10 additions & 2 deletions src/transaction/utils/sendSingleTransactions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { encodeFunctionData } from 'viem';
import type { Call, SendSingleTransactionParams } from '../types';
import { waitForTransactionReceipt } from 'wagmi/actions';
import type { SendSingleTransactionParams } from '../types';
import { isContract } from './isContract';

export const sendSingleTransactions = async ({
config,
sendCallAsync,
transactions,
}: SendSingleTransactionParams) => {
Expand All @@ -21,6 +23,12 @@ export const sendSingleTransactions = async ({
});

for (const call of calls) {
await sendCallAsync(call as Call);
const txHash = await sendCallAsync(call);
if (txHash) {
await waitForTransactionReceipt(config, {
hash: txHash,
confirmations: 1,
});
}
}
};

0 comments on commit e31686a

Please sign in to comment.