Complete deposit flow example using viem (vanilla TypeScript) and the Earn SDK.
Overview
This example demonstrates how to:
- Check deposit approval status before depositing
- Approve deposit tokens when needed
- Execute deposit transactions with slippage protection
- Handle errors gracefully
- Use automatic vault discovery based on yield type and deposit token
Prerequisites
Before running this example, ensure you have:
- Node.js >= 20 installed
- viem >= 2.0.0 installed
- @paxoslabs/earn-sdk installed
- Ethereum RPC URL (Alchemy, Infura, or similar)
- Private key for testing (use testnet or small amounts on mainnet)
- ETH for gas fees
- Deposit tokens (e.g., USDC)
Security: Never commit private keys to version control. Use environment
variables or secure key management solutions in production.
Complete Code Example
/**
* Viem Deposit Example
*
* Demonstrates complete deposit flow using viem (vanilla TypeScript):
* 1. Check if approval is needed
* 2. Approve deposit token if needed
* 3. Execute deposit transaction
*/
import {
isDepositSpendApproved,
prepareApproveDepositToken,
prepareDepositTransactionData,
} from "@paxoslabs/earn-sdk";
import type { Address } from "viem";
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
/**
* Configuration for the deposit
*/
const config = {
yieldType: "PRIME" as const, // Yield type: PRIME, TBILL, or LENDING
depositToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" as Address, // USDC on Ethereum
depositAmount: "1000.0", // $1000 USDC (decimal string format)
chainId: 1, // Ethereum mainnet
slippage: 100, // 1% slippage tolerance (100 basis points). Defaults to 10 basis points
// Replace with your private key for testing
privateKey: process.env.PRIVATE_KEY || "0x...",
// Replace with your RPC URL
rpcUrl:
process.env.RPC_URL || "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
};
/**
* Execute complete deposit flow
*/
async function deposit() {
console.log("=".repeat(60));
console.log("Earn SDK Deposit Example (Viem)");
console.log("=".repeat(60));
console.log();
// Step 1: Setup wallet client
// Create account from private key
const account = privateKeyToAccount(config.privateKey as `0x${string}`);
// Create public client for reading blockchain data
const publicClient = createPublicClient({
chain: mainnet,
transport: http(config.rpcUrl),
});
// Create wallet client for signing transactions
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(config.rpcUrl),
});
console.log(`Wallet address: ${account.address}`);
console.log(`Yield type: ${config.yieldType}`);
console.log(`Deposit amount: ${config.depositAmount} USDC`);
console.log(`Chain: ${mainnet.name} (${config.chainId})`);
console.log();
try {
// Step 2: Check approval status
console.log("Step 1: Checking approval status...");
const isApproved = await isDepositSpendApproved({
yieldType: config.yieldType,
depositToken: config.depositToken,
depositAmount: config.depositAmount,
recipientAddress: account.address,
chainId: config.chainId,
});
if (isApproved) {
console.log("✓ Tokens already approved");
} else {
console.log("✗ Approval required");
}
console.log();
// Step 3: Approve tokens if needed
if (!isApproved) {
console.log("Step 2: Preparing approval transaction...");
// Prepare approval transaction data
const approvalTx = await prepareApproveDepositToken({
yieldType: config.yieldType,
depositToken: config.depositToken,
depositAmount: config.depositAmount,
chainId: config.chainId,
});
console.log(`Approval target: ${approvalTx.address}`);
console.log(`Approval amount: ${approvalTx.args[1]}`);
console.log();
// Execute approval transaction
console.log("Executing approval transaction...");
const approvalHash = await walletClient.writeContract(approvalTx);
console.log(`Approval tx hash: ${approvalHash}`);
console.log();
// Wait for approval confirmation
console.log("Waiting for approval confirmation...");
const approvalReceipt = await publicClient.waitForTransactionReceipt({
hash: approvalHash,
});
if (approvalReceipt.status === "success") {
console.log(
`✓ Approval confirmed in block ${approvalReceipt.blockNumber}`
);
} else {
throw new Error("Approval transaction failed");
}
console.log();
} else {
console.log("Step 2: Skipping approval (already approved)");
console.log();
}
// Step 4: Prepare deposit transaction
console.log("Step 3: Preparing deposit transaction...");
const depositTx = await prepareDepositTransactionData({
yieldType: config.yieldType,
recipientAddress: account.address,
depositToken: config.depositToken,
depositAmount: config.depositAmount,
chainId: config.chainId,
slippage: config.slippage, // Slippage protection
});
console.log(`Deposit contract: ${depositTx.address}`);
console.log(`Deposit token: ${depositTx.args[0]}`);
console.log(`Deposit amount: ${depositTx.args[1]}`);
console.log(`Minimum shares: ${depositTx.args[2]}`); // Calculated based on slippage
console.log();
// Execute deposit transaction
console.log("Executing deposit transaction...");
const depositHash = await walletClient.writeContract(depositTx);
console.log(`Deposit tx hash: ${depositHash}`);
console.log();
// Wait for deposit confirmation
console.log("Waiting for deposit confirmation...");
const depositReceipt = await publicClient.waitForTransactionReceipt({
hash: depositHash,
});
if (depositReceipt.status === "success") {
console.log(`✓ Deposit confirmed in block ${depositReceipt.blockNumber}`);
} else {
throw new Error("Deposit transaction failed");
}
console.log();
// Success!
console.log("=".repeat(60));
console.log("✓ Deposit completed successfully!");
console.log("=".repeat(60));
console.log();
console.log(`Transaction hash: ${depositReceipt.transactionHash}`);
console.log(
`View on Etherscan: https://etherscan.io/tx/${depositReceipt.transactionHash}`
);
return depositReceipt;
} catch (error) {
// Error handling
console.error();
console.error("=".repeat(60));
console.error("✗ Deposit failed");
console.error("=".repeat(60));
if (error instanceof Error) {
console.error(`Error: ${error.message}`);
} else {
console.error("Unknown error:", error);
}
throw error;
}
}
// Execute deposit if run directly
deposit()
.then(() => {
process.exit(0);
})
.catch((error) => {
console.error(error);
process.exit(1);
});
What This Code Does
1. Setup Phase
- Creates viem wallet client from private key
- Creates public client for reading blockchain data
- Configures connection to Ethereum mainnet via RPC
2. Approval Check Phase
- Calls
isDepositSpendApproved() to verify if tokens are already approved
- Checks allowance for the Teller contract to spend your deposit tokens
3. Approval Phase (if needed)
- Prepares approval transaction using
prepareApproveDepositToken()
- Executes approval transaction with
walletClient.writeContract()
- Waits for approval confirmation before proceeding
4. Deposit Phase
- Prepares deposit transaction using
prepareDepositTxData()
- Automatically calculates minimum shares based on slippage tolerance
- Executes deposit transaction
- Waits for confirmation and reports success
Key Features
Automatic Vault Resolution
The SDK automatically finds the correct vault based on:
- Yield type:
PRIME, TBILL, or LENDING
- Deposit token address: e.g., USDC (
0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)
- Chain ID: e.g., Ethereum mainnet (
1)
No need to manually specify vault addresses or search for vault configurations!
Slippage Protection
This example uses 1% slippage tolerance (100 basis points):
const depositTx = await prepareDepositTransactionData({
// ... other params
slippage: 100, // 1% = 100 basis points
});
Approval-First Workflow
Configuration Options
Change Deposit Amount
const config = {
// ...
depositAmount: "5000.0", // Change to $5000 USDC
};
Change Yield Type
const config = {
yieldType: "TBILL", // Switch to T-Bill vault
// ...
};
Change Slippage Tolerance
const config = {
// ...
slippage: 50, // 0.5% slippage (50 basis points)
};
Use Different Token
const config = {
depositToken: "0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI
// ...
};
Expected Output
============================================================
Earn SDK Deposit Example (Viem)
============================================================
Wallet address: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb
Yield type: PRIME
Deposit amount: 1000.0 USDC
Chain: Ethereum (1)
Step 1: Checking approval status...
✗ Approval required
Step 2: Preparing approval transaction...
Approval target: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
Approval amount: 1000000000
Executing approval transaction...
Approval tx hash: 0x...
Waiting for approval confirmation...
✓ Approval confirmed in block 12345678
Step 3: Preparing deposit transaction...
Deposit contract: 0x2234567890123456789012345678901234567890
Deposit token: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
Deposit amount: 1000000000
Minimum shares: 990000000
Executing deposit transaction...
Deposit tx hash: 0x...
Waiting for deposit confirmation...
✓ Deposit confirmed in block 12345679
============================================================
✓ Deposit completed successfully!
============================================================
Transaction hash: 0x...
View on Etherscan: https://etherscan.io/tx/0x...
Troubleshooting
”Approval transaction failed”
Possible causes:
- Insufficient ETH for gas fees
- Incorrect deposit token address
- RPC URL not working or rate limited
Solutions:
- Check your ETH balance:
await publicClient.getBalance({ address: account.address })
- Verify deposit token address is correct
- Test RPC connection with a simple call
”Deposit transaction failed”
Possible causes:
- Approval not successful or pending
- Insufficient deposit token balance
- Slippage tolerance too low (exchange rate changed)
Solutions:
- Verify approval was confirmed before depositing
- Check token balance: Use
publicClient.readContract() with ERC20 balanceOf
- Increase slippage tolerance if exchange rate moved
”Vault not found”
Possible causes:
- Invalid yield type
- Deposit token not supported on this chain
- Incorrect chain ID
Solutions:
- Verify yield type is
PRIME, TBILL, or LENDING
- Check that deposit token is supported using vault discovery API
- Ensure chain ID matches the network you’re connected to
”API request timed out”
Possible causes:
- Network connectivity issues
- Earn API server not reachable
- API server overloaded
Solutions:
- Check network connection
- Verify API is running (default:
http://localhost:8500)
- Try again after a few seconds with exponential backoff
Next Steps