diff --git a/.changeset/spotty-ants-burn.md b/.changeset/spotty-ants-burn.md new file mode 100644 index 0000000000..a788cc7f86 --- /dev/null +++ b/.changeset/spotty-ants-burn.md @@ -0,0 +1,5 @@ +--- +'@coinbase/onchainkit': patch +--- + +**feat** `Transaction` - use `walletCapabilities` for atomic batching (`useWriteContracts` vs `useWriteContract`). by @0xAlec #1214 diff --git a/src/transaction/components/TransactionProvider.test.tsx b/src/transaction/components/TransactionProvider.test.tsx index e14fa59993..9f3d41781f 100644 --- a/src/transaction/components/TransactionProvider.test.tsx +++ b/src/transaction/components/TransactionProvider.test.tsx @@ -6,6 +6,7 @@ import { useWaitForTransactionReceipt, } from 'wagmi'; import { waitForTransactionReceipt } from 'wagmi/actions'; +import { useOnchainKit } from '../../useOnchainKit'; import { METHOD_NOT_SUPPORTED_ERROR_SUBSTRING } from '../constants'; import { useCallsStatus } from '../hooks/useCallsStatus'; import { useWriteContract } from '../hooks/useWriteContract'; @@ -40,6 +41,10 @@ vi.mock('../hooks/useWriteContracts', () => ({ genericErrorMessage: 'Something went wrong. Please try again.', })); +vi.mock('../../useOnchainKit', () => ({ + useOnchainKit: vi.fn(), +})); + const TestComponent = () => { const context = useTransactionContext(); const handleStatusError = async () => { @@ -119,6 +124,13 @@ describe('TransactionProvider', () => { (useWaitForTransactionReceipt as ReturnType).mockReturnValue({ receipt: undefined, }); + (useOnchainKit as ReturnType).mockReturnValue({ + walletCapabilities: { + hasAtomicBatch: false, + hasPaymasterService: false, + hasAuxiliaryFunds: false, + }, + }); }); it('should emit onError when setLifeCycleStatus is called with error', async () => { @@ -291,6 +303,11 @@ describe('TransactionProvider', () => { status: 'idle', writeContractsAsync: writeContractsAsyncMock, }); + (useOnchainKit as ReturnType).mockReturnValue({ + walletCapabilities: { + hasAtomicBatch: true, + }, + }); render( @@ -311,6 +328,11 @@ describe('TransactionProvider', () => { status: 'idle', writeContractsAsync: writeContractsAsyncMock, }); + (useOnchainKit as ReturnType).mockReturnValue({ + walletCapabilities: { + hasAtomicBatch: true, + }, + }); render( @@ -371,6 +393,11 @@ describe('TransactionProvider', () => { status: 'idle', writeContractsAsync: writeContractsAsyncMock, }); + (useOnchainKit as ReturnType).mockReturnValue({ + walletCapabilities: { + hasAtomicBatch: true, + }, + }); render( @@ -428,16 +455,9 @@ describe('TransactionProvider', () => { }); it('should handle generic error during fallback', async () => { - const writeContractsAsyncMock = vi - .fn() - .mockRejectedValue(new Error('Method not supported')); const writeContractAsyncMock = vi .fn() .mockRejectedValue(new Error('Generic error')); - (useWriteContracts as ReturnType).mockReturnValue({ - status: 'idle', - writeContractsAsync: writeContractsAsyncMock, - }); (useWriteContract as ReturnType).mockReturnValue({ status: 'idle', writeContractAsync: writeContractAsyncMock, @@ -451,7 +471,7 @@ describe('TransactionProvider', () => { fireEvent.click(button); await waitFor(() => { expect(screen.getByTestId('context-value-errorCode').textContent).toBe( - 'TmTPc03', + 'TmTPc02', ); expect(screen.getByTestId('context-value-errorMessage').textContent).toBe( 'Something went wrong. Please try again.', diff --git a/src/transaction/components/TransactionProvider.tsx b/src/transaction/components/TransactionProvider.tsx index b35d6bfbe0..f0e72c8ca2 100644 --- a/src/transaction/components/TransactionProvider.tsx +++ b/src/transaction/components/TransactionProvider.tsx @@ -14,10 +14,8 @@ import { } from 'wagmi'; import { waitForTransactionReceipt } from 'wagmi/actions'; import { useValue } from '../../internal/hooks/useValue'; -import { - GENERIC_ERROR_MESSAGE, - METHOD_NOT_SUPPORTED_ERROR_SUBSTRING, -} from '../constants'; +import { useOnchainKit } from '../../useOnchainKit'; +import { GENERIC_ERROR_MESSAGE } from '../constants'; import { useCallsStatus } from '../hooks/useCallsStatus'; import { useWriteContract } from '../hooks/useWriteContract'; import { useWriteContracts } from '../hooks/useWriteContracts'; @@ -65,6 +63,9 @@ export function TransactionProvider({ const [transactionId, setTransactionId] = useState(''); const [transactionHashList, setTransactionHashList] = useState([]); + // Retrieve wallet capabilities + const { walletCapabilities } = useOnchainKit(); + const { switchChainAsync } = useSwitchChain(); // Hooks that depend from Core Hooks @@ -219,21 +220,19 @@ export function TransactionProvider({ setErrorMessage(''); setIsToastVisible(true); try { + // Switch chain before attempting transactions await switchChain(chainId); - await writeContractsAsync({ - contracts, - capabilities, - }); - } catch (err) { - // When writeContracts does not work, fallback to writeContract - // writeContracts so far worksl only with Smart Wallets - if ( - err instanceof Error && - err.message.includes(METHOD_NOT_SUPPORTED_ERROR_SUBSTRING) - ) { + if (walletCapabilities.hasAtomicBatch) { + // Use experiemental hook if the wallet supports atomic batch + await writeContractsAsync({ + contracts, + capabilities, + }); + } else { + // Use fallback if the wallet does not support atomic batch await fallbackToWriteContract(); - return; } + } catch (err) { const errorMessage = isUserRejectedRequestError(err) ? 'Request denied.' : GENERIC_ERROR_MESSAGE; @@ -253,6 +252,7 @@ export function TransactionProvider({ fallbackToWriteContract, switchChain, writeContractsAsync, + walletCapabilities.hasAtomicBatch, ]); const value = useValue({