Skip to content

Commit

Permalink
Transaction - use walletCapabilities for atomic batching (#1214)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xAlec authored Sep 5, 2024
1 parent 5afc2be commit 2542d60
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 24 deletions.
5 changes: 5 additions & 0 deletions .changeset/spotty-ants-burn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@coinbase/onchainkit': patch
---

**feat** `Transaction` - use `walletCapabilities` for atomic batching (`useWriteContracts` vs `useWriteContract`). by @0xAlec #1214
36 changes: 28 additions & 8 deletions src/transaction/components/TransactionProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -119,6 +124,13 @@ describe('TransactionProvider', () => {
(useWaitForTransactionReceipt as ReturnType<typeof vi.fn>).mockReturnValue({
receipt: undefined,
});
(useOnchainKit as ReturnType<typeof vi.fn>).mockReturnValue({
walletCapabilities: {
hasAtomicBatch: false,
hasPaymasterService: false,
hasAuxiliaryFunds: false,
},
});
});

it('should emit onError when setLifeCycleStatus is called with error', async () => {
Expand Down Expand Up @@ -291,6 +303,11 @@ describe('TransactionProvider', () => {
status: 'idle',
writeContractsAsync: writeContractsAsyncMock,
});
(useOnchainKit as ReturnType<typeof vi.fn>).mockReturnValue({
walletCapabilities: {
hasAtomicBatch: true,
},
});
render(
<TransactionProvider contracts={[]}>
<TestComponent />
Expand All @@ -311,6 +328,11 @@ describe('TransactionProvider', () => {
status: 'idle',
writeContractsAsync: writeContractsAsyncMock,
});
(useOnchainKit as ReturnType<typeof vi.fn>).mockReturnValue({
walletCapabilities: {
hasAtomicBatch: true,
},
});
render(
<TransactionProvider contracts={[]}>
<TestComponent />
Expand Down Expand Up @@ -371,6 +393,11 @@ describe('TransactionProvider', () => {
status: 'idle',
writeContractsAsync: writeContractsAsyncMock,
});
(useOnchainKit as ReturnType<typeof vi.fn>).mockReturnValue({
walletCapabilities: {
hasAtomicBatch: true,
},
});
render(
<TransactionProvider contracts={[]}>
<TestComponent />
Expand Down Expand Up @@ -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<typeof vi.fn>).mockReturnValue({
status: 'idle',
writeContractsAsync: writeContractsAsyncMock,
});
(useWriteContract as ReturnType<typeof vi.fn>).mockReturnValue({
status: 'idle',
writeContractAsync: writeContractAsyncMock,
Expand All @@ -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.',
Expand Down
32 changes: 16 additions & 16 deletions src/transaction/components/TransactionProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -65,6 +63,9 @@ export function TransactionProvider({
const [transactionId, setTransactionId] = useState('');
const [transactionHashList, setTransactionHashList] = useState<Address[]>([]);

// Retrieve wallet capabilities
const { walletCapabilities } = useOnchainKit();

const { switchChainAsync } = useSwitchChain();

// Hooks that depend from Core Hooks
Expand Down Expand Up @@ -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;
Expand All @@ -253,6 +252,7 @@ export function TransactionProvider({
fallbackToWriteContract,
switchChain,
writeContractsAsync,
walletCapabilities.hasAtomicBatch,
]);

const value = useValue({
Expand Down

0 comments on commit 2542d60

Please sign in to comment.