Skip to main content
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:
  1. Check your ETH balance: await publicClient.getBalance({ address: account.address })
  2. Verify deposit token address is correct
  3. 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:
  1. Verify approval was confirmed before depositing
  2. Check token balance: Use publicClient.readContract() with ERC20 balanceOf
  3. 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:
  1. Verify yield type is PRIME, TBILL, or LENDING
  2. Check that deposit token is supported using vault discovery API
  3. 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:
  1. Check network connection
  2. Verify API is running (default: http://localhost:8500)
  3. Try again after a few seconds with exponential backoff

Next Steps