Skip to content

Commit

Permalink
Merge branch 'main' into 2025-02-20-error-handling
Browse files Browse the repository at this point in the history
  • Loading branch information
hardyjosh authored Feb 20, 2025
2 parents 7d0a239 + b82de46 commit 137d3ee
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 40 deletions.
55 changes: 54 additions & 1 deletion packages/webapp/src/__tests__/DepositOrWithdrawModal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DepositOrWithdrawModal>;

Expand Down Expand Up @@ -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.'
);
});
Expand All @@ -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' }
});
});
});
91 changes: 52 additions & 39 deletions packages/webapp/src/lib/components/DepositOrWithdrawModal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand All @@ -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,
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -142,17 +149,23 @@
<Button
color="blue"
on:click={handleContinue}
disabled={amount <= 0n || amountGreaterThanBalance[actionType]}
disabled={amount <= 0n ||
amountGreaterThanBalance[actionType] ||
isCheckingCalldata}
>
{action === 'deposit' ? 'Deposit' : 'Withdraw'}
{#if isCheckingCalldata}
Checking...
{:else}
{action === 'deposit' ? 'Deposit' : 'Withdraw'}
{/if}
</Button>
</div>
{:else}
<WalletConnect {appKitModal} {connected} />
{/if}
</div>
{#if switchChainError}
<p data-testid="chain-error">{switchChainError}</p>
{#if errorMessage}
<p data-testid="error-message">{errorMessage}</p>
{/if}
{#if amountGreaterThanBalance[actionType]}
<p class="text-red-500" data-testid="error">Amount cannot exceed available balance.</p>
Expand Down

0 comments on commit 137d3ee

Please sign in to comment.