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.