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
Standard Approval Check Pattern
Before depositing assets, always check if the Teller contract has sufficient allowance to spend your tokens. This prevents unnecessary approval transactions and saves gas fees.
import {
isDepositSpendApproved,
prepareApproveDepositToken,
prepareDepositTransactionData,
} from "@paxoslabs/earn-sdk";
// Step 1: Check approval status
const isApproved = await isDepositSpendApproved({
yieldType: "PRIME",
depositToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
depositAmount: "1000.0",
recipientAddress: userAddress,
chainId: 1,
});
// Step 2: Approve if needed
if (!isApproved) {
const approvalTx = await prepareApproveDepositToken({
yieldType: "PRIME",
depositToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
depositAmount: "1000.0",
chainId: 1,
});
const approvalHash = await client.writeContract(approvalTx);
await client.waitForTransactionReceipt({ hash: approvalHash });
}
// Step 3: Proceed with deposit
const depositTx = await prepareDepositTransactionData({
yieldType: "PRIME",
recipientAddress: userAddress,
depositToken: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
depositAmount: "1000.0",
chainId: 1,
slippage: 100,
});
Gas Optimization: If you frequently deposit to the same vault, you can approve a larger amount once (e.g., type(uint256).max) to avoid repeated approval transactions. However, this increases security risk if the contract is compromised.
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