Withdrawals follow a two-step process:
- Approve - Authorize the vault to spend your shares
- Withdraw - Redeem shares for the underlying token
Prerequisites
- Node.js 22+
- Amplify API key from Paxos Labs
- SDK initialized with
initAmplifySDK() - Vault shares to redeem
Installation
- Privy
- Wagmi
- Viem
Copy
Ask AI
pnpm add @paxoslabs/amplify-sdk @privy-io/react-auth viem @tanstack/react-query
Copy
Ask AI
pnpm add @paxoslabs/amplify-sdk wagmi viem @tanstack/react-query
Copy
Ask AI
pnpm add @paxoslabs/amplify-sdk viem
Complete Examples
- Privy
- Wagmi
- Viem
useWithdraw Hook
Copy
Ask AI
// src/hooks/useWithdraw.ts
import { useCallback, useMemo, useState } from "react";
import { encodeFunctionData } from "viem";
import {
prepareApproveWithdrawTxData,
prepareWithdrawTxData,
type YieldType,
} from "@paxoslabs/amplify-sdk";
declare const usePrivy: () => { sendTransaction: (tx: any) => Promise<string> };
declare const useWallets: () => { wallets: Array<{ address: string }> };
interface WithdrawParams {
yieldType: YieldType;
offerAmount: string;
wantAssetAddress: `0x${string}`;
chainId: number;
slippage?: number;
}
export function usePrivyWithdraw() {
const { sendTransaction } = usePrivy();
const { wallets } = useWallets();
const wallet = useMemo(() => wallets.find(Boolean), [wallets]);
const [txHash, setTxHash] = useState<`0x${string}` | null>(null);
const [status, setStatus] = useState<
"idle" | "approving" | "withdrawing" | "success"
>("idle");
const [error, setError] = useState<Error | null>(null);
const withdraw = useCallback(
async ({
yieldType,
offerAmount,
wantAssetAddress,
chainId,
slippage = 75,
}: WithdrawParams) => {
if (!wallet) {
throw new Error("Connect a Privy wallet before withdrawing.");
}
setError(null);
setTxHash(null);
try {
// Step 1: Approve withdrawal
setStatus("approving");
const approval = await prepareApproveWithdrawTxData({
chainId,
wantAssetAddress,
yieldType,
});
await sendTransaction({
chainId,
to: approval.address,
data: encodeFunctionData({
abi: approval.abi,
functionName: approval.functionName,
args: approval.args,
}),
});
// Step 2: Execute withdrawal
setStatus("withdrawing");
const tx = await prepareWithdrawTxData({
yieldType,
wantAssetAddress,
offerAmount,
chainId,
slippage,
});
const { abi, functionName, args } = tx;
const hash = await sendTransaction({
chainId: tx.chainId,
to: tx.address,
data: encodeFunctionData({ abi, functionName, args }),
});
setTxHash(hash as `0x${string}`);
setStatus("success");
} catch (err) {
const message = err instanceof Error ? err : new Error(String(err));
setError(message);
setStatus("idle");
throw err;
}
},
[wallet, sendTransaction]
);
return { withdraw, status, error, txHash };
}
Withdraw Component
Copy
Ask AI
// src/components/WithdrawButton.tsx
import { useMemo } from "react";
import { useQuery } from "@tanstack/react-query";
import { fetchSupportedAssets, YieldType } from "@paxoslabs/amplify-sdk";
import { mainnet } from "viem/chains";
import { usePrivyWithdraw } from "../hooks/useWithdraw";
// @noErrors
declare const useWallets: () => { wallets: Array<{ address: string }> };
export function PrivyWithdrawButton() {
const { wallets } = useWallets();
const wallet = useMemo(() => wallets.find(Boolean), [wallets]);
const { withdraw, status, error, txHash } = usePrivyWithdraw();
const { data: supportedAssets } = useQuery({
queryKey: ["amplify-assets", mainnet.id],
queryFn: () =>
fetchSupportedAssets({
yieldType: YieldType.CORE,
}),
enabled: Boolean(wallet),
});
const asset = supportedAssets?.[0];
if (!wallet) return <p>Connect with Privy to withdraw.</p>;
if (!asset) return <p>Loading supported assets...</p>;
async function handleClick() {
await withdraw({
yieldType: YieldType.CORE,
offerAmount: "5.0",
wantAssetAddress: asset.address as `0x${string}`,
chainId: mainnet.id,
slippage: 75,
});
}
return (
<section>
<button onClick={handleClick} disabled={status !== "idle" && status !== "success"}>
{status === "approving" && "Approving..."}
{status === "withdrawing" && "Withdrawing..."}
{status === "success" && "Withdraw Again"}
{status === "idle" && "Withdraw"}
</button>
{txHash && (
<p>
Withdrawal sent:{" "}
<a
href={`https://etherscan.io/tx/${txHash}`}
target="_blank"
rel="noreferrer"
>
{txHash}
</a>
</p>
)}
{error && <p role="alert">Withdraw failed: {error.message}</p>}
</section>
);
}
useWithdraw Hook
Copy
Ask AI
// src/hooks/useWithdraw.ts
import { useCallback, useEffect, useState } from "react";
import { useWaitForTransactionReceipt, useWriteContract } from "wagmi";
import {
prepareApproveWithdrawTxData,
prepareWithdrawTxData,
type YieldType,
} from "@paxoslabs/amplify-sdk";
import type { Address } from "viem";
interface WithdrawParams {
yieldType: YieldType;
offerAmount: string;
wantAssetAddress: Address;
chainId: number;
userAddress: Address;
slippage?: number;
}
export function useWithdraw() {
const [error, setError] = useState<Error | null>(null);
const [status, setStatus] = useState<
"idle" | "approving" | "submitting" | "confirming" | "success"
>("idle");
// Wagmi hooks for writing contracts
const {
writeContractAsync,
data: hash,
isPending: isSubmitting,
} = useWriteContract();
// Wait for transaction confirmation
const { isLoading: isConfirming } = useWaitForTransactionReceipt({
hash,
confirmations: 1,
query: { enabled: Boolean(hash) },
});
const withdraw = useCallback(
async ({
yieldType,
offerAmount,
wantAssetAddress,
chainId,
userAddress,
slippage = 75,
}: WithdrawParams) => {
setError(null);
try {
// Step 1: Approve withdrawal
setStatus("approving");
const approval = await prepareApproveWithdrawTxData({
chainId,
wantAssetAddress,
yieldType,
});
await writeContractAsync({
...approval,
account: userAddress,
});
// Step 2: Execute withdrawal
setStatus("submitting");
const tx = await prepareWithdrawTxData({
yieldType,
offerAmount,
wantAssetAddress,
chainId,
slippage,
});
const transactionHash = await writeContractAsync({
...tx,
account: userAddress,
});
setStatus("confirming");
return transactionHash;
} catch (err) {
const error = err instanceof Error ? err : new Error(String(err));
setError(error);
setStatus("idle");
throw error;
}
},
[writeContractAsync]
);
useEffect(() => {
if (hash && !isSubmitting && !isConfirming) {
setStatus("success");
}
}, [hash, isSubmitting, isConfirming]);
return {
withdraw,
status,
hash,
error,
isIdle: status === "idle",
isSubmitting,
isConfirming,
isSuccess: status === "success",
};
}
Withdraw Component
Copy
Ask AI
// src/components/WithdrawForm.tsx
import { useAccount } from "wagmi";
import { useQuery } from "@tanstack/react-query";
import { fetchSupportedAssets, YieldType } from "@paxoslabs/amplify-sdk";
import { mainnet } from "viem/chains";
import { useWithdraw } from "../hooks/useWithdraw";
export function WithdrawForm() {
const { address, chainId } = useAccount();
const { withdraw, status, error, hash } = useWithdraw();
const { data: supportedAssets } = useQuery({
queryKey: ["amplify-assets", chainId],
queryFn: () =>
fetchSupportedAssets({ yieldType: YieldType.CORE }),
enabled: Boolean(address),
});
const asset = supportedAssets?.[0];
if (!address) return <p>Connect a wallet to redeem shares.</p>;
if (!asset) return <p>Loading supported assets...</p>;
async function handleWithdraw() {
await withdraw({
yieldType: YieldType.CORE,
offerAmount: "5.0",
wantAssetAddress: asset.address as `0x${string}`,
chainId: mainnet.id,
userAddress: address,
slippage: 75,
});
}
return (
<section>
<button
disabled={status === "approving" || status === "submitting" || status === "confirming"}
onClick={handleWithdraw}
>
{status === "approving"
? "Approving..."
: status === "submitting"
? "Submitting..."
: status === "confirming"
? "Confirming..."
: "Withdraw"}
</button>
{hash && (
<p>
Transaction hash:{" "}
<a
href={`https://etherscan.io/tx/${hash}`}
target="_blank"
rel="noreferrer"
>
{hash}
</a>
</p>
)}
{error && <p role="alert">Withdraw failed: {error.message}</p>}
</section>
);
}
Complete Script
This example uses pure viem without React, suitable for Node.js scripts or vanilla TypeScript applications.Never commit private keys. Use environment variables or a secrets manager in production.
Copy
Ask AI
// withdraw.ts
import {
initAmplifySDK,
fetchSupportedAssets,
prepareApproveWithdrawTxData,
prepareWithdrawTxData,
YieldType,
} from "@paxoslabs/amplify-sdk";
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
declare const process: { env: Record<string, string>, exit: (code: number) => never };
const CONFIG = {
apiKey: process.env.AMPLIFY_API_KEY!,
chainId: 1,
yieldType: YieldType.CORE,
offerAmount: "5.0", // decimal string of the earn token to exchange
slippage: 75, // 0.75% slippage tolerance in basis points
rpcUrl: process.env.RPC_URL ?? "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY",
privateKey: process.env.PRIVATE_KEY!,
};
async function main() {
// Step 1: Initialize SDK
console.log("Initializing SDK...");
await initAmplifySDK(CONFIG.apiKey);
// Step 2: Setup wallet client
const account = privateKeyToAccount(CONFIG.privateKey as `0x${string}`);
const publicClient = createPublicClient({
chain: mainnet,
transport: http(CONFIG.rpcUrl),
});
const walletClient = createWalletClient({
account,
chain: mainnet,
transport: http(CONFIG.rpcUrl),
});
// Step 3: Fetch supported assets to get the want asset address
console.log("Fetching supported assets...");
const supportedAssets = await fetchSupportedAssets({
yieldType: CONFIG.yieldType,
});
const asset = supportedAssets[0];
if (!asset) {
throw new Error("No supported assets found for the selected yield type.");
}
const wantAssetAddress = asset.address as `0x${string}`;
console.log(`Using asset: ${asset.symbol} (${wantAssetAddress})`);
try {
// Step 4: Approve withdrawal
console.log("\nStep 1: Preparing approval transaction...");
const approvalTx = await prepareApproveWithdrawTxData({
chainId: CONFIG.chainId,
wantAssetAddress,
yieldType: CONFIG.yieldType,
});
console.log(`Approval target: ${approvalTx.address}`);
console.log("Executing approval transaction...");
const approvalHash = await walletClient.writeContract(approvalTx);
console.log(`Approval tx hash: ${approvalHash}`);
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");
}
// Step 5: Execute withdrawal
console.log("\nStep 2: Preparing withdrawal transaction...");
const tx = await prepareWithdrawTxData({
yieldType: CONFIG.yieldType,
wantAssetAddress,
offerAmount: CONFIG.offerAmount,
chainId: CONFIG.chainId,
slippage: CONFIG.slippage,
});
console.log("Submitting withdrawal...");
const withdrawalHash = await walletClient.writeContract({ ...tx, account });
console.log(`Withdrawal sent: ${withdrawalHash}`);
console.log("Waiting for confirmation...");
const withdrawalReceipt = await publicClient.waitForTransactionReceipt({
hash: withdrawalHash,
});
if (withdrawalReceipt.status !== "success") {
throw new Error(`Withdrawal reverted in tx ${withdrawalHash}`);
}
console.log(`Withdrawal confirmed in block ${withdrawalReceipt.blockNumber}`);
return withdrawalReceipt;
} catch (error) {
console.error("\nWithdrawal failed");
if (error instanceof Error) {
console.error(`Error: ${error.message}`);
}
throw error;
}
}
main()
.then(() => process.exit(0))
.catch(() => process.exit(1));
Running the Script
Copy
Ask AI
# Set environment variables
export AMPLIFY_API_KEY=pxl_your_api_key
export PRIVATE_KEY=0x...
export RPC_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY
# Run with tsx or ts-node
npx tsx withdraw.ts
Expected Output
Copy
Ask AI
Initializing SDK...
Fetching supported assets...
Using asset: USDC (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)
Step 1: Preparing approval transaction...
Approval target: 0x...
Executing approval transaction...
Approval tx hash: 0x1234...
Waiting for approval confirmation...
Approval confirmed in block 12345678
Step 2: Preparing withdrawal transaction...
Submitting withdrawal...
Withdrawal sent: 0x5678...
Waiting for confirmation...
Withdrawal confirmed in block 12345679
What the Code Does
- Approves the vault - Authorizes the vault contract to spend your shares
- Prepares withdrawal - Builds transaction data with slippage protection
- Executes withdrawal - Redeems shares for underlying tokens
- Tracks status - Updates UI with current operation state
Customization
Slippage Tolerance
Slippage is specified in basis points (1 bp = 0.01%). Default is 75 (0.75%).Copy
Ask AI
const tx = await prepareWithdrawTxData({
...params,
slippage: 100, // 1% slippage tolerance
});
Partial Withdrawals
Specify the exact amount of vault shares to redeem:Copy
Ask AI
await withdraw({
...params,
offerAmount: "10.5", // Redeem 10.5 vault shares
});
Troubleshooting
| Issue | Solution |
|---|---|
| ”SDK not initialized” | Call initAmplifySDK() before using hooks |
| ”Connect a wallet first” | Ensure user has connected wallet |
| Insufficient shares | Check vault share balance before withdrawing |
| Slippage exceeded | Increase slippage tolerance or try smaller amount |
| Liquidity unavailable | Vault may have withdrawal limits, try later |
prepareWithdrawTxData throws APIError when liquidity is unavailable or the vault is unsupported. Surface the endpoint and statusCode fields to help users understand failures.