Skip to content
Merged
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
29 changes: 8 additions & 21 deletions src/transaction/components/TransactionProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import {
useEffect,
useState,
} from 'react';
import type {
Address,
TransactionExecutionError,
TransactionReceipt,
} from 'viem';
import type { Address, TransactionReceipt } from 'viem';
import {
useAccount,
useConfig,
Expand All @@ -30,6 +26,7 @@ import type {
TransactionContextType,
TransactionProviderReact,
} from '../types';
import { isUserRejectedRequestError } from '../utils/isUserRejectedRequestError';

const emptyContext = {} as TransactionContextType;
export const TransactionContext =
Expand Down Expand Up @@ -74,7 +71,6 @@ export function TransactionProvider({
// Hooks that depend from Core Hooks
const { status: statusWriteContracts, writeContractsAsync } =
useWriteContracts({
setErrorMessage,
setLifeCycleStatus,
setTransactionId,
});
Expand All @@ -83,7 +79,6 @@ export function TransactionProvider({
writeContractAsync,
data: writeContractTransactionHash,
} = useWriteContract({
setErrorMessage,
setLifeCycleStatus,
setTransactionHashArray,
transactionHashArray,
Expand All @@ -100,6 +95,7 @@ export function TransactionProvider({
useEffect(() => {
// Emit Error
if (lifeCycleStatus.statusName === 'error') {
setErrorMessage(lifeCycleStatus.statusData.message);
onError?.(lifeCycleStatus.statusData);
}
// Emit State
Expand Down Expand Up @@ -145,15 +141,10 @@ export function TransactionProvider({
try {
await writeContractAsync?.(contract);
} catch (err) {
// if user rejected request
if (
(err as TransactionExecutionError)?.cause?.name ===
'UserRejectedRequestError'
) {
setErrorMessage('Request denied.');
} else {
setErrorMessage(GENERIC_ERROR_MESSAGE);
}
const errorMessage = isUserRejectedRequestError(err)
? 'Request denied.'
: GENERIC_ERROR_MESSAGE;
setErrorMessage(errorMessage);
}
}
}, [contracts, writeContractAsync]);
Expand Down Expand Up @@ -188,10 +179,7 @@ export function TransactionProvider({
setErrorMessage(GENERIC_ERROR_MESSAGE);
}
// handles user rejected request error
} else if (
(err as TransactionExecutionError)?.cause?.name ===
'UserRejectedRequestError'
) {
} else if (isUserRejectedRequestError(err)) {
setErrorMessage('Request denied.');
// handles generic error
} else {
Expand Down Expand Up @@ -230,7 +218,6 @@ export function TransactionProvider({
isToastVisible,
onSubmit: handleSubmit,
receipt,
setErrorMessage,
setIsToastVisible,
setLifeCycleStatus,
setTransactionId,
Expand Down
6 changes: 0 additions & 6 deletions src/transaction/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,3 @@ export const GENERIC_ERROR_MESSAGE = 'Something went wrong. Please try again.';
export const METHOD_NOT_SUPPORTED_ERROR_SUBSTRING =
'this request method is not supported';
export const SEND_CALLS_NOT_SUPPORTED_ERROR = 'SEND_CALLS_NOT_SUPPORTED_ERROR';
export const UNCAUGHT_WRITE_CONTRACT_ERROR_CODE =
'UNCAUGHT_WRITE_CONTRACT_ERROR';
export const UNCAUGHT_WRITE_CONTRACTS_ERROR_CODE =
'UNCAUGHT_WRITE_WRITE_CONTRACTS_ERROR';
export const WRITE_CONTRACT_ERROR_CODE = 'WRITE_CONTRACT_ERROR';
export const WRITE_CONTRACTS_ERROR_CODE = 'WRITE_CONTRACTS_ERROR';
3 changes: 2 additions & 1 deletion src/transaction/hooks/useCallsStatus.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ describe('useCallsStatus', () => {
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'UNCAUGHT_CALL_STATUS_ERROR',
code: 'TmUCSh01',
error: JSON.stringify(mockError),
message: '',
},
});
});
Expand Down
8 changes: 5 additions & 3 deletions src/transaction/hooks/useCallsStatus.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { useCallsStatus as useCallsStatusWagmi } from 'wagmi/experimental';
import type { UseCallsStatusParams } from '../types';

const uncaughtErrorCode = 'UNCAUGHT_CALL_STATUS_ERROR';

export function useCallsStatus({
setLifeCycleStatus,
transactionId,
Expand All @@ -22,7 +20,11 @@ export function useCallsStatus({
} catch (err) {
setLifeCycleStatus({
statusName: 'error',
statusData: { code: uncaughtErrorCode, error: JSON.stringify(err) },
statusData: {
code: 'TmUCSh01',
error: JSON.stringify(err),
message: '',
},
});
return { status: 'error', transactionHash: undefined };
}
Expand Down
19 changes: 4 additions & 15 deletions src/transaction/hooks/useWriteContract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ type MockUseWriteContractReturn = {
};

describe('useWriteContract', () => {
const mockSetErrorMessage = vi.fn();
const mockSetLifeCycleStatus = vi.fn();
const mockSetTransactionHashArray = vi.fn();

Expand All @@ -39,7 +38,6 @@ describe('useWriteContract', () => {
} as MockUseWriteContractReturn);
const { result } = renderHook(() =>
useWriteContract({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionHashArray: mockSetTransactionHashArray,
}),
Expand All @@ -64,23 +62,18 @@ describe('useWriteContract', () => {
);
renderHook(() =>
useWriteContract({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionHashArray: mockSetTransactionHashArray,
}),
);

expect(onErrorCallback).toBeDefined();
onErrorCallback?.(genericError);

expect(mockSetErrorMessage).toHaveBeenCalledWith(
'Something went wrong. Please try again.',
);
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'WRITE_CONTRACT_ERROR',
code: 'TmUWCh01',
error: 'Something went wrong. Please try again.',
message: 'Something went wrong. Please try again.',
},
});
});
Expand All @@ -100,7 +93,6 @@ describe('useWriteContract', () => {
);
renderHook(() =>
useWriteContract({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionHashArray: mockSetTransactionHashArray,
}),
Expand All @@ -119,21 +111,18 @@ describe('useWriteContract', () => {
);
const { result } = renderHook(() =>
useWriteContract({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionHashArray: mockSetTransactionHashArray,
}),
);
expect(result.current.status).toBe('error');
expect(result.current.writeContractAsync).toBeInstanceOf(Function);
expect(mockSetErrorMessage).toHaveBeenCalledWith(
'Something went wrong. Please try again.',
);
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'UNCAUGHT_WRITE_CONTRACT_ERROR',
code: 'TmUWCh02',
error: JSON.stringify(uncaughtError),
message: 'Something went wrong. Please try again.',
},
});
});
Expand Down
31 changes: 13 additions & 18 deletions src/transaction/hooks/useWriteContract.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import type { Address, TransactionExecutionError } from 'viem';
import type { Address } from 'viem';
import { useWriteContract as useWriteContractWagmi } from 'wagmi';
import {
GENERIC_ERROR_MESSAGE,
UNCAUGHT_WRITE_CONTRACT_ERROR_CODE,
WRITE_CONTRACT_ERROR_CODE,
} from '../constants';
import { GENERIC_ERROR_MESSAGE } from '../constants';
import type { UseWriteContractParams } from '../types';
import { isUserRejectedRequestError } from '../utils/isUserRejectedRequestError';

/**
* Wagmi hook for single contract transactions.
* Supports both EOAs and Smart Wallets.
* Does not support transaction batching or paymasters.
*/
export function useWriteContract({
setErrorMessage,
setLifeCycleStatus,
setTransactionHashArray,
transactionHashArray,
Expand All @@ -22,17 +18,16 @@ export function useWriteContract({
const { status, writeContractAsync, data } = useWriteContractWagmi({
mutation: {
onError: (e) => {
if (
(e as TransactionExecutionError)?.cause?.name ===
'UserRejectedRequestError'
) {
setErrorMessage('Request denied.');
} else {
setErrorMessage(GENERIC_ERROR_MESSAGE);
}
const errorMessage = isUserRejectedRequestError(e)
? 'Request denied.'
: GENERIC_ERROR_MESSAGE;
setLifeCycleStatus({
statusName: 'error',
statusData: { code: WRITE_CONTRACT_ERROR_CODE, error: e.message },
statusData: {
code: 'TmUWCh01', // Transaction module UseWriteContract hook 01 error
error: e.message,
message: errorMessage,
},
});
},
onSuccess: (hash: Address) => {
Expand All @@ -47,11 +42,11 @@ export function useWriteContract({
setLifeCycleStatus({
statusName: 'error',
statusData: {
code: UNCAUGHT_WRITE_CONTRACT_ERROR_CODE,
code: 'TmUWCh02',
error: JSON.stringify(err),
message: GENERIC_ERROR_MESSAGE,
},
});
setErrorMessage(GENERIC_ERROR_MESSAGE);
return { status: 'error', writeContractAsync: () => {} };
}
}
53 changes: 41 additions & 12 deletions src/transaction/hooks/useWriteContracts.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { renderHook } from '@testing-library/react';
import type { TransactionExecutionError } from 'viem';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { useWriteContracts as useWriteContractsWagmi } from 'wagmi/experimental';
import { useWriteContracts } from './useWriteContracts';
Expand All @@ -8,7 +9,6 @@ vi.mock('wagmi/experimental', () => ({
}));

describe('useWriteContracts', () => {
const mockSetErrorMessage = vi.fn();
const mockSetLifeCycleStatus = vi.fn();
const mockSetTransactionId = vi.fn();

Expand All @@ -30,21 +30,54 @@ describe('useWriteContracts', () => {
);
renderHook(() =>
useWriteContracts({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionId: mockSetTransactionId,
}),
);
expect(onErrorCallback).toBeDefined();
onErrorCallback?.(genericError);
expect(mockSetErrorMessage).toHaveBeenCalledWith(
'Something went wrong. Please try again.',
);
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'WRITE_CONTRACTS_ERROR',
code: 'TmUWCSh01',
error: 'Something went wrong. Please try again.',
message: 'Something went wrong. Please try again.',
},
});
});

it('should handle userRejectedRequestError', () => {
let onErrorCallback:
| ((error: TransactionExecutionError) => void)
| undefined;
(useWriteContractsWagmi as ReturnType<typeof vi.fn>).mockImplementation(
({ mutation }: UseWriteContractsConfig) => {
onErrorCallback = mutation.onError;
return {
writeContracts: vi.fn(),
status: 'error',
};
},
);
renderHook(() =>
useWriteContracts({
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionId: mockSetTransactionId,
}),
);
expect(onErrorCallback).toBeDefined();
onErrorCallback?.({
cause: {
name: 'UserRejectedRequestError',
},
message: 'Request denied.',
});
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'TmUWCSh01',
error: 'Request denied.',
message: 'Request denied.',
},
});
});
Expand All @@ -63,7 +96,6 @@ describe('useWriteContracts', () => {
);
renderHook(() =>
useWriteContracts({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionId: mockSetTransactionId,
}),
Expand All @@ -82,21 +114,18 @@ describe('useWriteContracts', () => {
);
const { result } = renderHook(() =>
useWriteContracts({
setErrorMessage: mockSetErrorMessage,
setLifeCycleStatus: mockSetLifeCycleStatus,
setTransactionId: mockSetTransactionId,
}),
);
expect(result.current.status).toBe('error');
expect(result.current.writeContracts).toBeInstanceOf(Function);
expect(mockSetErrorMessage).toHaveBeenCalledWith(
'Something went wrong. Please try again.',
);
expect(mockSetLifeCycleStatus).toHaveBeenCalledWith({
statusName: 'error',
statusData: {
code: 'UNCAUGHT_WRITE_WRITE_CONTRACTS_ERROR',
code: 'TmUWCSh02',
error: JSON.stringify(uncaughtError),
message: 'Something went wrong. Please try again.',
},
});
});
Expand Down
Loading