Documentation Index
Fetch the complete documentation index at: https://developers.paxoslabs.com/llms.txt
Use this file to discover all available pages before exploring further.
Complete deposit flow example using wagmi (React hooks) and the Earn SDK.
Overview
This example demonstrates how to:
- Connect wallet with wagmi in React
- Check deposit approval status
- Approve deposit tokens when needed
- Execute deposit transactions with slippage protection
- Create custom React hooks for reusable deposit logic
- Handle loading states and transaction confirmation tracking
- Display error messages and transaction results
Prerequisites
Before running this example, ensure you have:
- Node.js >= 20 installed
- wagmi >= 2.0.0 and viem >= 2.0.0 installed
- @paxoslabs/earn-sdk installed
- @tanstack/react-query installed
- Modern web browser with Web3 wallet extension (MetaMask, Rainbow, etc.)
- ETH for gas fees
- Deposit tokens (e.g., USDC)
Complete Code Example
Custom Hook: useVaultDeposit.ts
First, create a custom hook that encapsulates all deposit logic:
/**
* useVaultDeposit Hook
*
* Custom React hook that encapsulates the complete vault deposit flow:
* - Check approval status
* - Approve deposit tokens
* - Execute deposit transaction
*/
import {
isDepositSpendApproved,
prepareApproveDepositToken,
prepareDepositTransactionData,
} from "@paxoslabs/earn-sdk";
import { useCallback, useState } from "react";
import type { Address } from "viem";
import { useWaitForTransactionReceipt, useWriteContract } from "wagmi";
interface CheckApprovalParams {
yieldType: string;
depositToken: Address;
depositAmount: string;
recipientAddress: Address;
chainId: number;
}
interface ApproveParams {
yieldType: string;
depositToken: Address;
depositAmount: string;
chainId: number;
}
interface DepositParams {
yieldType: string;
recipientAddress: Address;
depositToken: Address;
depositAmount: string;
chainId: number;
slippage: number;
}
interface UseVaultDepositReturn {
// Actions
checkApproval: (params: CheckApprovalParams) => Promise<void>;
approve: (params: ApproveParams) => Promise<void>;
deposit: (params: DepositParams) => Promise<void>;
reset: () => void;
// State
isApproved: boolean | null;
isCheckingApproval: boolean;
isApprovingToken: boolean;
isDepositing: boolean;
error: Error | null;
approvalHash: Address | null;
depositHash: Address | null;
}
export function useDeposit(): UseVaultDepositReturn {
// State management
const [isApproved, setIsApproved] = useState<boolean | null>(null);
const [isCheckingApproval, setIsCheckingApproval] = useState(false);
const [error, setError] = useState<Error | null>(null);
// Wagmi hooks for writing contracts
const {
writeContractAsync: writeApproval,
data: approvalHash,
isPending: isApprovingToken,
} = useWriteContract();
const {
writeContractAsync: writeDeposit,
data: depositHash,
isPending: isDepositing,
} = useWriteContract();
// Wait for transaction confirmations
const { isLoading: isWaitingForApproval } = useWaitForTransactionReceipt({
hash: approvalHash,
});
const { isLoading: isWaitingForDeposit } = useWaitForTransactionReceipt({
hash: depositHash,
});
/**
* Check if deposit tokens are approved
*/
const checkApproval = useCallback(async (params: CheckApprovalParams) => {
setIsCheckingApproval(true);
setError(null);
try {
const approved = await isDepositSpendApproved({
yieldType: params.yieldType,
depositToken: params.depositToken,
depositAmount: params.depositAmount,
recipientAddress: params.recipientAddress,
chainId: params.chainId,
});
setIsApproved(approved);
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
setError(error);
setIsApproved(null);
} finally {
setIsCheckingApproval(false);
}
}, []);
/**
* Approve deposit tokens
*/
const approve = useCallback(
async (params: ApproveParams) => {
setError(null);
try {
// Prepare approval transaction
const approvalTx = await prepareApproveDepositTxData({
yieldType: params.yieldType,
depositToken: params.depositToken,
depositAmount: params.depositAmount,
chainId: params.chainId,
});
// Execute approval using wagmi
await writeApproval(approvalTx);
// Update approval state after successful transaction
setIsApproved(true);
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
setError(error);
throw error;
}
},
[writeApproval]
);
/**
* Execute deposit transaction
*/
const deposit = useCallback(
async (params: DepositParams) => {
setError(null);
try {
// Prepare deposit transaction
const depositTx = await prepareDepositTxData({
yieldType: params.yieldType,
recipientAddress: params.recipientAddress,
depositToken: params.depositToken,
depositAmount: params.depositAmount,
chainId: params.chainId,
slippage: params.slippage,
});
// Execute deposit using wagmi
await writeDeposit(depositTx);
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
setError(error);
throw error;
}
},
[writeDeposit]
);
/**
* Reset hook state
*/
const reset = useCallback(() => {
setIsApproved(null);
setError(null);
}, []);
return {
// Actions
checkApproval,
approve,
deposit,
reset,
// State
isApproved,
isCheckingApproval,
isApprovingToken:
isApprovingToken || isWaitingForApproval || Boolean(approvalHash),
isDepositing: isDepositing || isWaitingForDeposit || Boolean(depositHash),
error,
approvalHash: (approvalHash as Address) || null,
depositHash: (depositHash as Address) || null,
};
}
Component: DepositComponent.tsx
Now create the React component that uses the custom hook:
/**
* Deposit Component
*
* React component demonstrating vault deposit using wagmi hooks
* and the custom useVaultDeposit hook.
*/
import { useState } from "react";
import type { Address } from "viem";
import { useAccount, useConnect, useDisconnect } from "wagmi";
import { injected } from "wagmi/connectors";
import { useVaultDeposit } from "./hooks/useVaultDeposit";
interface DepositConfig {
yieldType: "PRIME" | "TBILL" | "LENDING";
depositToken: Address;
depositAmount: string;
chainId: number;
slippage: number;
}
const DEFAULT_CONFIG: DepositConfig = {
yieldType: "PRIME",
depositToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
depositAmount: "1000.0", // $1000 USDC
chainId: 1, // Ethereum mainnet
slippage: 100, // 1% (100 basis points)
};
export function DepositComponent() {
// Wagmi wallet connection hooks
const { address, isConnected } = useAccount();
const { connect } = useConnect();
const { disconnect } = useDisconnect();
// Local state for deposit configuration
const [config, setConfig] = useState<DepositConfig>(DEFAULT_CONFIG);
// Custom deposit hook
const {
deposit,
approve,
checkApproval,
isApproved,
isCheckingApproval,
isApprovingToken,
isDepositing,
error,
approvalHash,
depositHash,
reset,
} = useVaultDeposit();
/**
* Handle wallet connection
*/
const handleConnect = () => {
connect({ connector: injected() });
};
/**
* Handle check approval
*/
const handleCheckApproval = async () => {
if (!address) return;
await checkApproval({
yieldType: config.yieldType,
depositToken: config.depositToken,
depositAmount: config.depositAmount,
recipientAddress: address,
chainId: config.chainId,
});
};
/**
* Handle approve tokens
*/
const handleApprove = async () => {
await approve({
yieldType: config.yieldType,
depositToken: config.depositToken,
depositAmount: config.depositAmount,
chainId: config.chainId,
});
};
/**
* Handle deposit
*/
const handleDeposit = async () => {
if (!address) return;
await deposit({
yieldType: config.yieldType,
recipientAddress: address,
depositToken: config.depositToken,
depositAmount: config.depositAmount,
chainId: config.chainId,
slippage: config.slippage,
});
};
// Render wallet connection UI if not connected
if (!isConnected) {
return (
<div className="deposit-container">
<div className="connect-section">
<h2>Connect Wallet</h2>
<p>Connect your wallet to deposit into Earn Vaults</p>
<button onClick={handleConnect} className="btn btn-primary">
Connect Wallet
</button>
</div>
</div>
);
}
// Render deposit form
return (
<div className="deposit-container">
<div className="wallet-section">
<p className="wallet-address">
Connected: {address?.slice(0, 6)}...{address?.slice(-4)}
</p>
<button onClick={() => disconnect()} className="btn btn-secondary">
Disconnect
</button>
</div>
<div className="deposit-form">
<h2>Deposit to Vault</h2>
<div className="form-group">
<label htmlFor="yieldType">Yield Type</label>
<select
id="yieldType"
value={config.yieldType}
onChange={(e) =>
setConfig((prev) => ({
...prev,
yieldType: e.target.value as "PRIME" | "TBILL" | "LENDING",
}))
}
disabled={isApprovingToken || isDepositing}
>
<option value="PRIME">PRIME</option>
<option value="TBILL">TBILL</option>
<option value="LENDING">LENDING</option>
</select>
</div>
<div className="form-group">
<label htmlFor="amount">Deposit Amount (USDC)</label>
<input
id="amount"
type="text"
value={config.depositAmount}
onChange={(e) =>
setConfig((prev) => ({
...prev,
depositAmount: e.target.value,
}))
}
disabled={isApprovingToken || isDepositing}
placeholder="1000.0"
/>
</div>
<div className="form-group">
<label htmlFor="slippage">Slippage (basis points)</label>
<input
id="slippage"
type="number"
value={config.slippage}
onChange={(e) =>
setConfig((prev) => ({
...prev,
slippage: Number(e.target.value),
}))
}
disabled={isApprovingToken || isDepositing}
placeholder="100"
/>
<small>100 basis points = 1%</small>
</div>
<div className="button-group">
<button
onClick={handleCheckApproval}
disabled={isCheckingApproval || isApprovingToken || isDepositing}
className="btn btn-secondary"
>
{isCheckingApproval ? "Checking..." : "Check Approval"}
</button>
{isApproved !== null && (
<div className="approval-status">
{isApproved ? (
<span className="status-approved">✓ Approved</span>
) : (
<span className="status-not-approved">✗ Not Approved</span>
)}
</div>
)}
{isApproved === false && (
<button
onClick={handleApprove}
disabled={isApprovingToken || isDepositing}
className="btn btn-warning"
>
{isApprovingToken ? "Approving..." : "Approve Tokens"}
</button>
)}
{isApproved === true && (
<button
onClick={handleDeposit}
disabled={isDepositing}
className="btn btn-primary"
>
{isDepositing ? "Depositing..." : "Deposit"}
</button>
)}
{(approvalHash || depositHash) && (
<button onClick={reset} className="btn btn-secondary">
Reset
</button>
)}
</div>
{error && (
<div className="error-message">
<strong>Error:</strong> {error.message}
</div>
)}
{approvalHash && (
<div className="success-message">
<strong>Approval Transaction:</strong>
<br />
<a
href={`https://etherscan.io/tx/${approvalHash}`}
target="_blank"
rel="noopener noreferrer"
>
{approvalHash.slice(0, 10)}...{approvalHash.slice(-8)}
</a>
</div>
)}
{depositHash && (
<div className="success-message">
<strong>Deposit Transaction:</strong>
<br />
<a
href={`https://etherscan.io/tx/${depositHash}`}
target="_blank"
rel="noopener noreferrer"
>
{depositHash.slice(0, 10)}...{depositHash.slice(-8)}
</a>
<br />
<br />
<strong>✓ Deposit completed successfully!</strong>
</div>
)}
</div>
</div>
);
}
What This Code Does
Custom Hook (useVaultDeposit)
The custom hook encapsulates all deposit logic and provides a clean API:
- State Management: Tracks approval status, loading states, errors, and transaction hashes
- Approval Checking:
checkApproval() verifies if tokens are already approved
- Token Approval:
approve() prepares and executes approval transaction
- Deposit Execution:
deposit() prepares and executes deposit transaction
- Transaction Tracking: Uses
useWaitForTransactionReceipt to track confirmations
- Reset Functionality:
reset() clears state for new deposits
Component (DepositComponent)
The React component provides the UI and orchestrates the deposit flow:
- Wallet Connection: Uses wagmi’s
useConnect and useAccount hooks
- Form State: Manages deposit configuration (yield type, amount, slippage)
- Approval Workflow: Check approval → Approve if needed → Deposit
- Loading States: Disables buttons and shows loading indicators during transactions
- Error Handling: Displays error messages from hook
- Transaction Results: Shows Etherscan links for approval and deposit transactions
Key Features
Custom Hook Pattern
The useVaultDeposit hook provides a reusable, testable pattern for vault deposits:
const {
deposit,
approve,
checkApproval,
isApproved,
isCheckingApproval,
isApprovingToken,
isDepositing,
error,
approvalHash,
depositHash,
reset,
} = useVaultDeposit();
Benefits:
- Separation of Concerns: Business logic separated from UI
- Reusability: Use the same hook in multiple components
- Testability: Test logic independently from UI
- Type Safety: Full TypeScript support with interfaces
Approval-First Workflow
Loading States
The component shows appropriate loading states during:
- Approval checking:
isCheckingApproval
- Token approval:
isApprovingToken (includes waiting for confirmation)
- Deposit execution:
isDepositing (includes waiting for confirmation)
Buttons are disabled during these operations to prevent duplicate transactions.
Error Handling
Errors are caught at the hook level and exposed to components:
if (error) {
// Display user-friendly error message
console.error("Deposit failed:", error.message);
}
Slippage Protection
Configuration Options
Change Deposit Amount
const DEFAULT_CONFIG: DepositConfig = {
// ...
depositAmount: "5000.0", // Change to $5000 USDC
};
Change Yield Type
const DEFAULT_CONFIG: DepositConfig = {
yieldType: "TBILL", // Switch to T-Bill vault
// ...
};
Change Slippage Tolerance
const DEFAULT_CONFIG: DepositConfig = {
// ...
slippage: 50, // 0.5% slippage (50 basis points)
};
Use Different Token
const DEFAULT_CONFIG: DepositConfig = {
depositToken: "0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI
// ...
};
Add More Chains
Update your wagmi config to support additional chains:
import { createConfig, http } from "wagmi";
import { mainnet, boba, sei } from "wagmi/chains";
const config = createConfig({
chains: [mainnet, boba, sei],
transports: {
[mainnet.id]: http(),
[boba.id]: http(),
[sei.id]: http(),
},
});
Troubleshooting
Possible causes:
- No Web3 wallet extension installed
- Wallet extension disabled or locked
Solutions:
- Install MetaMask, Rainbow, or another Web3 wallet
- Unlock your wallet extension
- Refresh the page and try again
”Approval transaction failed”
Possible causes:
- Insufficient ETH for gas fees
- Incorrect deposit token address
- Wrong network selected in wallet
Solutions:
- Check ETH balance for gas fees
- Verify deposit token address is correct
- Ensure wallet is connected to Ethereum mainnet
”Deposit transaction failed”
Possible causes:
- Approval not successful
- Insufficient deposit tokens
- Slippage exceeded
Solutions:
- Wait for approval confirmation before depositing
- Check token balance in wallet
- Increase slippage tolerance if needed
Wagmi hook errors
Possible causes:
- Wagmi or React Query providers not configured
- Incompatible wagmi version
- Missing chains in wagmi config
Solutions:
- Ensure
WagmiProvider and QueryClientProvider wrap your app
- Use wagmi >= 2.0.0
- Add chains to wagmi config that you’re using
Next Steps