diff --git a/src/transaction/components/TransactionProvider.test.tsx b/src/transaction/components/TransactionProvider.test.tsx
index 037c083ec2..7832b57180 100644
--- a/src/transaction/components/TransactionProvider.test.tsx
+++ b/src/transaction/components/TransactionProvider.test.tsx
@@ -37,12 +37,20 @@ vi.mock('../hooks/useWriteContracts', () => ({
const TestComponent = () => {
const context = useTransactionContext();
- const handleSetLifeCycleStatus = async () => {
+ const handleStatusError = async () => {
context.setLifeCycleStatus({
statusName: 'error',
statusData: { code: 'code', error: 'error_long_messages' },
});
};
+ const handleStatusTransactionLegacyExecuted = async () => {
+ context.setLifeCycleStatus({
+ statusName: 'transactionLegacyExecuted',
+ statusData: {
+ transactionHashList: ['hash12345678'],
+ },
+ });
+ };
return (
);
};
@@ -99,7 +110,7 @@ describe('TransactionProvider', () => {
expect(onErrorMock).toHaveBeenCalled();
});
- it('should emit onStatus when setLifeCycleStatus is called', async () => {
+ it('should emit onStatus when setLifeCycleStatus is called with transactionLegacyExecuted', async () => {
const onStatusMock = vi.fn();
render(
{
,
);
- const button = screen.getByText('setLifeCycleStatus.error');
+ const button = screen.getByText(
+ 'setLifeCycleStatus.transactionLegacyExecuted',
+ );
fireEvent.click(button);
- expect(onStatusMock).toHaveBeenCalled();
+ expect(onStatusMock).toHaveBeenCalledWith({
+ statusName: 'transactionLegacyExecuted',
+ statusData: {
+ transactionHashList: ['hash12345678'],
+ },
+ });
});
- it('should update context on handleSubmit', async () => {
- const writeContractsAsyncMock = vi.fn();
- (useWriteContracts as ReturnType).mockReturnValue({
- statusWriteContracts: 'IDLE',
- writeContractsAsync: writeContractsAsyncMock,
- });
+ it('should emit onStatus when setLifeCycleStatus is called', async () => {
+ const onStatusMock = vi.fn();
render(
-
+
,
);
- const button = screen.getByText('Submit');
+ const button = screen.getByText('setLifeCycleStatus.error');
fireEvent.click(button);
- await waitFor(() => {
- expect(writeContractsAsyncMock).toHaveBeenCalled();
- });
+ expect(onStatusMock).toHaveBeenCalled();
});
- it('should call onsuccess when receipt exists', async () => {
+ it('should emit onSuccess when one receipt exist', async () => {
const onSuccessMock = vi.fn();
(useWaitForTransactionReceipt as ReturnType).mockReturnValue({
- data: '123',
+ data: { status: 'success' },
});
(useCallsStatus as ReturnType).mockReturnValue({
transactionHash: 'hash',
@@ -144,7 +160,7 @@ describe('TransactionProvider', () => {
render(
@@ -154,6 +170,27 @@ describe('TransactionProvider', () => {
fireEvent.click(button);
await waitFor(() => {
expect(onSuccessMock).toHaveBeenCalled();
+ expect(onSuccessMock).toHaveBeenCalledWith({
+ transactionReceipts: [{ status: 'success' }],
+ });
+ });
+ });
+
+ it('should update context on handleSubmit', async () => {
+ const writeContractsAsyncMock = vi.fn();
+ (useWriteContracts as ReturnType).mockReturnValue({
+ statusWriteContracts: 'IDLE',
+ writeContractsAsync: writeContractsAsyncMock,
+ });
+ render(
+
+
+ ,
+ );
+ const button = screen.getByText('Submit');
+ fireEvent.click(button);
+ await waitFor(() => {
+ expect(writeContractsAsyncMock).toHaveBeenCalled();
});
});
@@ -252,30 +289,6 @@ describe('TransactionProvider', () => {
});
});
- it('should call onSuccess when receipts are available', async () => {
- const onSuccessMock = vi.fn();
- (useWaitForTransactionReceipt as ReturnType).mockReturnValue({
- data: { status: 'success' },
- });
- (useCallsStatus as ReturnType).mockReturnValue({
- transactionHash: 'hash',
- });
- render(
-
-
- ,
- );
- await waitFor(() => {
- expect(onSuccessMock).toHaveBeenCalledWith({
- transactionReceipts: [{ status: 'success' }],
- });
- });
- });
-
it('should handle chain switching', async () => {
const switchChainAsyncMock = vi.fn();
(useSwitchChain as ReturnType).mockReturnValue({
@@ -382,34 +395,6 @@ describe('TransactionProvider', () => {
);
});
});
-
- it('should call onSuccess when receiptArray has receipts', async () => {
- const onSuccessMock = vi.fn();
- const mockReceipt = { status: 'success' };
-
- (useWaitForTransactionReceipt as ReturnType).mockReturnValue({
- data: mockReceipt,
- });
-
- render(
-
-
- ,
- );
-
- const button = screen.getByText('Submit');
- fireEvent.click(button);
-
- await waitFor(() => {
- expect(onSuccessMock).toHaveBeenCalledWith({
- transactionReceipts: [mockReceipt],
- });
- });
- });
});
describe('useTransactionContext', () => {
diff --git a/src/transaction/components/TransactionProvider.tsx b/src/transaction/components/TransactionProvider.tsx
index 99c2326abd..31ce6ae0a0 100644
--- a/src/transaction/components/TransactionProvider.tsx
+++ b/src/transaction/components/TransactionProvider.tsx
@@ -5,7 +5,7 @@ import {
useEffect,
useState,
} from 'react';
-import type { Address, TransactionReceipt } from 'viem';
+import type { Address } from 'viem';
import {
useAccount,
useConfig,
@@ -62,11 +62,9 @@ export function TransactionProvider({
statusName: 'init',
statusData: null,
}); // Component lifecycle
- const [receiptArray, setReceiptArray] = useState([]);
const [transactionId, setTransactionId] = useState('');
- const [transactionHashArray, setTransactionHashArray] = useState(
- [],
- );
+ const [transactionHashList, setTransactionHashList] = useState([]);
+
const { switchChainAsync } = useSwitchChain();
// Hooks that depend from Core Hooks
@@ -81,8 +79,7 @@ export function TransactionProvider({
data: writeContractTransactionHash,
} = useWriteContract({
setLifeCycleStatus,
- setTransactionHashArray,
- transactionHashArray,
+ transactionHashList,
});
const { transactionHash, status: callStatus } = useCallsStatus({
setLifeCycleStatus,
@@ -94,25 +91,61 @@ export function TransactionProvider({
// Component lifecycle emitters
useEffect(() => {
- // Emit Error
+ setErrorMessage('');
+ // Error
if (lifeCycleStatus.statusName === 'error') {
setErrorMessage(lifeCycleStatus.statusData.message);
setErrorCode(lifeCycleStatus.statusData.code);
onError?.(lifeCycleStatus.statusData);
}
+ // Transaction Legacy Executed
+ if (lifeCycleStatus.statusName === 'transactionLegacyExecuted') {
+ setTransactionHashList(lifeCycleStatus.statusData.transactionHashList);
+ }
+ // Success
+ if (lifeCycleStatus.statusName === 'success') {
+ onSuccess?.({
+ transactionReceipts: lifeCycleStatus.statusData.transactionReceipts,
+ });
+ }
// Emit State
onStatus?.(lifeCycleStatus);
}, [
onError,
onStatus,
+ onSuccess,
lifeCycleStatus,
lifeCycleStatus.statusData, // Keep statusData, so that the effect runs when it changes
lifeCycleStatus.statusName, // Keep statusName, so that the effect runs when it changes
]);
- const getTransactionReceipts = useCallback(async () => {
+ // Trigger success status when receipt is generated by useWaitForTransactionReceipt
+ useEffect(() => {
+ if (!receipt) {
+ return;
+ }
+ setLifeCycleStatus({
+ statusName: 'success',
+ statusData: {
+ transactionReceipts: [receipt],
+ },
+ });
+ }, [receipt]);
+
+ // When all transactions are succesful, get the receipts
+ useEffect(() => {
+ if (
+ transactionHashList.length !== contracts.length ||
+ contracts.length < 2
+ ) {
+ return;
+ }
+ getTransactionLegacyReceipts();
+ }, [contracts, transactionHashList]);
+
+ const getTransactionLegacyReceipts = useCallback(async () => {
const receipts = [];
- for (const hash of transactionHashArray) {
+ for (const hash of transactionHashList) {
try {
const txnReceipt = await waitForTransactionReceipt(config, {
hash,
@@ -130,17 +163,13 @@ export function TransactionProvider({
});
}
}
- setReceiptArray(receipts);
- }, [chainId, config, transactionHashArray]);
-
- useEffect(() => {
- if (
- transactionHashArray.length === contracts.length &&
- contracts?.length > 1
- ) {
- getTransactionReceipts();
- }
- }, [contracts, getTransactionReceipts, transactionHashArray]);
+ setLifeCycleStatus({
+ statusName: 'success',
+ statusData: {
+ transactionReceipts: receipts,
+ },
+ });
+ }, [chainId, config, transactionHashList]);
const fallbackToWriteContract = useCallback(async () => {
// EOAs don't support batching, so we process contracts individually.
@@ -209,14 +238,6 @@ export function TransactionProvider({
}
}, [chainId, executeContracts, fallbackToWriteContract, switchChain]);
- useEffect(() => {
- if (receiptArray?.length) {
- onSuccess?.({ transactionReceipts: receiptArray });
- } else if (receipt) {
- onSuccess?.({ transactionReceipts: [receipt] });
- }
- }, [onSuccess, receipt, receiptArray]);
-
const value = useValue({
address,
chainId,
diff --git a/src/transaction/hooks/useTransactionReceipts.ts b/src/transaction/hooks/useTransactionReceipts.ts
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/src/transaction/hooks/useWriteContract.test.ts b/src/transaction/hooks/useWriteContract.test.ts
index 804e0f36c1..965fd8f5e0 100644
--- a/src/transaction/hooks/useWriteContract.test.ts
+++ b/src/transaction/hooks/useWriteContract.test.ts
@@ -27,7 +27,6 @@ type MockUseWriteContractReturn = {
describe('useWriteContract', () => {
const mockSetLifeCycleStatus = vi.fn();
- const mockSetTransactionHashArray = vi.fn();
beforeEach(() => {
vi.resetAllMocks();
@@ -44,7 +43,7 @@ describe('useWriteContract', () => {
const { result } = renderHook(() =>
useWriteContract({
setLifeCycleStatus: mockSetLifeCycleStatus,
- setTransactionHashArray: mockSetTransactionHashArray,
+ transactionHashList: [],
}),
);
expect(result.current.status).toBe('idle');
@@ -68,7 +67,7 @@ describe('useWriteContract', () => {
renderHook(() =>
useWriteContract({
setLifeCycleStatus: mockSetLifeCycleStatus,
- setTransactionHashArray: mockSetTransactionHashArray,
+ transactionHashList: [],
}),
);
expect(onErrorCallback).toBeDefined();
@@ -100,7 +99,7 @@ describe('useWriteContract', () => {
renderHook(() =>
useWriteContract({
setLifeCycleStatus: mockSetLifeCycleStatus,
- setTransactionHashArray: mockSetTransactionHashArray,
+ transactionHashList: [],
}),
);
expect(onErrorCallback).toBeDefined();
@@ -116,7 +115,7 @@ describe('useWriteContract', () => {
});
it('should handle successful transaction', () => {
- const transactionId = '0x123';
+ const transactionId = '0x123456';
let onSuccessCallback: ((id: string) => void) | undefined;
(useWriteContractWagmi as ReturnType).mockImplementation(
({ mutation }: UseWriteContractConfig) => {
@@ -131,16 +130,21 @@ describe('useWriteContract', () => {
renderHook(() =>
useWriteContract({
setLifeCycleStatus: mockSetLifeCycleStatus,
- setTransactionHashArray: mockSetTransactionHashArray,
+ transactionHashList: [],
}),
);
expect(onSuccessCallback).toBeDefined();
onSuccessCallback?.(transactionId);
- expect(mockSetTransactionHashArray).toHaveBeenCalledWith([transactionId]);
+ expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
+ statusName: 'transactionLegacyExecuted',
+ statusData: {
+ transactionHashList: [transactionId],
+ },
+ });
});
it('should handle multiple successful transactions', () => {
- const transactionId = '0x123';
+ const transactionId = '0x12345678';
let onSuccessCallback: ((id: string) => void) | undefined;
(useWriteContractWagmi as ReturnType).mockImplementation(
({ mutation }: UseWriteContractConfig) => {
@@ -155,16 +159,30 @@ describe('useWriteContract', () => {
renderHook(() =>
useWriteContract({
setLifeCycleStatus: mockSetLifeCycleStatus,
- setTransactionHashArray: mockSetTransactionHashArray,
- transactionHashArray: ['0x1234'],
+ transactionHashList: [],
}),
);
expect(onSuccessCallback).toBeDefined();
onSuccessCallback?.(transactionId);
- expect(mockSetTransactionHashArray).toHaveBeenCalledWith([
- '0x1234',
- transactionId,
- ]);
+ expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
+ statusName: 'transactionLegacyExecuted',
+ statusData: {
+ transactionHashList: [transactionId],
+ },
+ });
+ renderHook(() =>
+ useWriteContract({
+ setLifeCycleStatus: mockSetLifeCycleStatus,
+ transactionHashList: [transactionId],
+ }),
+ );
+ onSuccessCallback?.(transactionId);
+ expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
+ statusName: 'transactionLegacyExecuted',
+ statusData: {
+ transactionHashList: [transactionId, transactionId],
+ },
+ });
});
it('should handle uncaught errors', () => {
@@ -177,7 +195,7 @@ describe('useWriteContract', () => {
const { result } = renderHook(() =>
useWriteContract({
setLifeCycleStatus: mockSetLifeCycleStatus,
- setTransactionHashArray: mockSetTransactionHashArray,
+ transactionHashList: [],
}),
);
expect(result.current.status).toBe('error');
diff --git a/src/transaction/hooks/useWriteContract.ts b/src/transaction/hooks/useWriteContract.ts
index 385aee697e..6a001f4613 100644
--- a/src/transaction/hooks/useWriteContract.ts
+++ b/src/transaction/hooks/useWriteContract.ts
@@ -11,8 +11,7 @@ import { isUserRejectedRequestError } from '../utils/isUserRejectedRequestError'
*/
export function useWriteContract({
setLifeCycleStatus,
- setTransactionHashArray,
- transactionHashArray,
+ transactionHashList,
}: UseWriteContractParams) {
try {
const { status, writeContractAsync, data } = useWriteContractWagmi({
@@ -31,9 +30,12 @@ export function useWriteContract({
});
},
onSuccess: (hash: Address) => {
- setTransactionHashArray(
- transactionHashArray ? transactionHashArray?.concat(hash) : [hash],
- );
+ setLifeCycleStatus({
+ statusName: 'transactionLegacyExecuted',
+ statusData: {
+ transactionHashList: [...transactionHashList, hash],
+ },
+ });
},
},
});
diff --git a/src/transaction/types.ts b/src/transaction/types.ts
index b7c3ec95ce..4d829f3a2a 100644
--- a/src/transaction/types.ts
+++ b/src/transaction/types.ts
@@ -17,6 +17,18 @@ export type LifeCycleStatus =
| {
statusName: 'error';
statusData: TransactionError;
+ }
+ | {
+ statusName: 'success';
+ statusData: {
+ transactionReceipts: TransactionReceipt[];
+ };
+ }
+ | {
+ statusName: 'transactionLegacyExecuted';
+ statusData: {
+ transactionHashList: Address[];
+ };
};
export type IsSpinnerDisplayedProps = {
@@ -174,8 +186,7 @@ export type UseCallsStatusParams = {
export type UseWriteContractParams = {
setLifeCycleStatus: (state: LifeCycleStatus) => void;
- setTransactionHashArray: (ids: Address[]) => void;
- transactionHashArray?: Address[];
+ transactionHashList: Address[];
};
export type UseWriteContractsParams = {
diff --git a/vitest.config.ts b/vitest.config.ts
index d44a093dae..d83a345ace 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -22,10 +22,10 @@ export default defineConfig({
],
reportOnFailure: true,
thresholds: {
- statements: 99.59,
- branches: 99.18,
- functions: 97.84,
- lines: 99.59,
+ statements: 99.57,
+ branches: 99.23,
+ functions: 97.85,
+ lines: 99.57,
},
},
environment: 'jsdom',