diff --git a/packages/webapp/src/__tests__/DepositOrWithdrawModal.test.ts b/packages/webapp/src/__tests__/DepositOrWithdrawModal.test.ts index 171f82535..9de809754 100644 --- a/packages/webapp/src/__tests__/DepositOrWithdrawModal.test.ts +++ b/packages/webapp/src/__tests__/DepositOrWithdrawModal.test.ts @@ -6,6 +6,8 @@ import { signerAddress } from '$lib/stores/wagmi'; import { readContract, switchChain } from '@wagmi/core'; import type { ComponentProps } from 'svelte'; +import { getVaultApprovalCalldata } from '@rainlanguage/orderbook/js_api'; +import { getVaultDepositCalldata } from '@rainlanguage/orderbook/js_api'; export type ModalProps = ComponentProps; @@ -147,7 +149,7 @@ describe('DepositOrWithdrawModal', () => { render(DepositOrWithdrawModal, defaultProps); await waitFor(() => { - expect(screen.getByTestId('chain-error')).toHaveTextContent( + expect(screen.getByTestId('error-message')).toHaveTextContent( 'Switch to Ethereum to check your balance.' ); }); @@ -173,4 +175,55 @@ describe('DepositOrWithdrawModal', () => { const continueButton = screen.getByText('Deposit'); expect(continueButton).toBeDisabled(); }); + + it('shows loading state while checking calldata', async () => { + render(DepositOrWithdrawModal, defaultProps); + + const input = screen.getByRole('textbox'); + await fireEvent.input(input, { target: { value: '1' } }); + + const depositButton = screen.getByText('Deposit'); + await fireEvent.click(depositButton); + + expect(screen.getByText('Checking...')).toBeInTheDocument(); + }); + + it('handles failed calldata fetch', async () => { + vi.mocked(getVaultDepositCalldata).mockRejectedValueOnce(new Error('Failed to fetch')); + + render(DepositOrWithdrawModal, defaultProps); + + const input = screen.getByRole('textbox'); + await fireEvent.input(input, { target: { value: '1' } }); + + const depositButton = screen.getByText('Deposit'); + await fireEvent.click(depositButton); + + await waitFor(() => { + expect(screen.getByTestId('error-message')).toHaveTextContent('Failed to get calldata.'); + }); + }); + + it('handles deposit without approval when approval fails', async () => { + const handleTransactionSpy = vi.spyOn(transactionStore, 'handleDepositOrWithdrawTransaction'); + vi.mocked(getVaultApprovalCalldata).mockRejectedValueOnce(new Error('Approval not needed')); + + render(DepositOrWithdrawModal, defaultProps); + + const input = screen.getByRole('textbox'); + await fireEvent.input(input, { target: { value: '1' } }); + + const depositButton = screen.getByText('Deposit'); + await fireEvent.click(depositButton); + + expect(handleTransactionSpy).toHaveBeenCalledWith({ + action: 'deposit', + chainId: 1, + vault: mockVault, + config: undefined, + subgraphUrl: undefined, + approvalCalldata: undefined, + transactionCalldata: { to: '0x123', data: '0x456' } + }); + }); }); diff --git a/packages/webapp/src/lib/components/DepositOrWithdrawModal.svelte b/packages/webapp/src/lib/components/DepositOrWithdrawModal.svelte index 3066c256e..e2eb19afb 100644 --- a/packages/webapp/src/lib/components/DepositOrWithdrawModal.svelte +++ b/packages/webapp/src/lib/components/DepositOrWithdrawModal.svelte @@ -43,7 +43,11 @@ let currentStep = 1; let amount: bigint = 0n; let userBalance: bigint = 0n; - let switchChainError = ''; + let errorMessage = ''; + let depositCalldata: DepositCalldataResult | undefined = undefined; + let approvalCalldata: ApprovalCalldata | undefined = undefined; + let withdrawCalldata: WithdrawCalldataResult | undefined = undefined; + let isCheckingCalldata = false; const messages = { success: 'Transaction successful.', @@ -60,7 +64,7 @@ try { await switchChain($wagmiConfig, { chainId }); } catch { - return (switchChainError = `Switch to ${targetChain.name} to check your balance.`); + return (errorMessage = `Switch to ${targetChain.name} to check your balance.`); } userBalance = await readContract($wagmiConfig, { abi: erc20Abi, @@ -70,42 +74,45 @@ }); }; + async function handleTransaction( + transactionCalldata: DepositCalldataResult | WithdrawCalldataResult, + approvalCalldata?: ApprovalCalldata | undefined + ) { + transactionStore.handleDepositOrWithdrawTransaction({ + config: $wagmiConfig, + transactionCalldata, + approvalCalldata, + action, + chainId, + vault, + subgraphUrl + }); + } + async function handleContinue() { - if (action === 'deposit') { - let approvalCalldata: ApprovalCalldata | undefined = undefined; - try { - approvalCalldata = await getVaultApprovalCalldata(rpcUrl, vault, amount.toString()); - } catch { - approvalCalldata = undefined; + isCheckingCalldata = true; + try { + if (action === 'deposit') { + try { + approvalCalldata = await getVaultApprovalCalldata(rpcUrl, vault, amount.toString()); + } catch { + approvalCalldata = undefined; + } + depositCalldata = await getVaultDepositCalldata(vault, amount.toString()); + if (depositCalldata) { + handleTransaction(depositCalldata, approvalCalldata); + } + } else if (action === 'withdraw') { + withdrawCalldata = await getVaultWithdrawCalldata(vault, amount.toString()); + if (withdrawCalldata) { + handleTransaction(withdrawCalldata); + } } - const depositCalldata: DepositCalldataResult = await getVaultDepositCalldata( - vault, - amount.toString() - ); - currentStep = 2; - transactionStore.handleDepositOrWithdrawTransaction({ - config: $wagmiConfig, - transactionCalldata: depositCalldata, - approvalCalldata, - action, - chainId, - vault, - subgraphUrl - }); - } else if (action === 'withdraw') { - const withdrawCalldata: WithdrawCalldataResult = await getVaultWithdrawCalldata( - vault, - amount.toString() - ); currentStep = 2; - transactionStore.handleDepositOrWithdrawTransaction({ - config: $wagmiConfig, - transactionCalldata: withdrawCalldata, - action, - chainId, - vault, - subgraphUrl - }); + } catch { + errorMessage = 'Failed to get calldata.'; + } finally { + isCheckingCalldata = false; } } @@ -142,17 +149,23 @@ {:else} {/if} - {#if switchChainError} -

{switchChainError}

+ {#if errorMessage} +

{errorMessage}

{/if} {#if amountGreaterThanBalance[actionType]}

Amount cannot exceed available balance.