Skip to main content
Redeem vault shares in a single transaction using prepareWithdrawTxData and viem’s wallet client. This script fetches the target vault, prepares the calldata with a slippage safeguard, submits the transaction, and waits for confirmation.

Overview

  • Discover a vault that supports withdrawals.
  • Prepare withdrawal calldata with slippage tolerance.
  • Submit and confirm the transaction with viem.
  • Handle failures by surfacing the APIError payload.

Prerequisites

  • Node.js >= 20
  • viem >= 2.0.0
  • @paxoslabs/amplify-sdk installed and initialized (initAmplifySDK elsewhere in your app)
  • RPC URL (Alchemy, Infura, etc.)
  • Private key with gas and vault shares to redeem
Never commit private keys. Use environment variables or a secrets manager in production.

Complete Code Example

/**
 * Viem Withdrawal Example
 *
 * Demonstrates complete deposit flow using viem (vanilla TypeScript):
 * 1. Check if approval is needed
 * 2. Approve withdrawal token if needed
 * 3. Execute withdraw transaction
 */
import {
  fetchVaults,
  isWithdrawalSpendApproved,
  prepareApproveWithdrawTxData,
  prepareWithdrawTxData,
  YieldType,
} from "@paxoslabs/amplify-sdk";
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";

const CONFIG = {
  chainId: 1,
  yieldType: YieldType.CORE,
  shareAmount: "5.0", // decimal string of the earn token to exchange
  slippage: 75, // Optional: 0.75% slippage tolerance in basis points. Defaults to 10 basis points
  rpcUrl:
    process.env.RPC_URL ?? "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
  privateKey: process.env.PRIVATE_KEY ?? "0x...",
};

async function withdraw() {
  // 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),
  });

  const [vault] = await fetchVaults({
    chainId: CONFIG.chainId,
    yieldType: CONFIG.yieldType,
  });
  if (!vault) {
    throw new Error(
      "No vault configuration found for the selected yield type."
    );
  }

  try {
    // Step 2: Check approval status
    console.log("Step 1: Checking approval status...");
    const isApproved = await isWithdrawalSpendApproved({
      yieldType: CONFIG.yieldType,
      shareAmount: CONFIG.shareAmount,
      userAddress: 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 prepareApproveWithdrawTxData({
        yieldType: CONFIG.yieldType,
        shareAmount: CONFIG.shareAmount,
        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 withdrawal transaction
    console.log("Step 3: Preparing withdrawal transaction...");
    const tx = await prepareWithdrawTxData({
      yieldType: vault.yieldType,
      shareAmount: CONFIG.shareAmount,
      wantAssetAddress: vault.depositTokenAddressId,
      chainId: vault.chainId,
      recipientAddress: account.address,
      slippage: CONFIG.slippage,
    });

    console.log("Submitting transaction...");
    const withdrawalHash = await walletClient.writeContract({ ...tx, account });
    console.log(`withdrawal sent: ${hash}`);

    console.log("Waiting for confirmation...");
    const withdrawalReceipt = await publicClient.waitForTransactionReceipt({
      withdrawalHash,
    });
    if (receipt.status !== "success") {
      throw new Error(`withdrawal reverted in tx ${withdrawalHash}`);
    }

    console.log(
      `Withdrawal confirmed in block ${withdrawalReceipt.blockNumber}`
    );
    return withdrawalReceipt;
  } 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
withdraw()
  .then(() => {
    process.exit(0);
  })
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });
prepareWithdrawTxData throws APIError when a vault, token, or chain is unsupported. Log the endpoint and cause fields to help users fall back to alternative flows when liquidity is unavailable.