prepareWithdrawTxData with Privy’s wallet connection hooks. This pattern keeps signer UX inside Privy while letting the Amplify SDK handle calldata.
Overview
- Fetch the target vault (for example with
fetchVaultsand React Query). - Prepare withdrawal calldata with slippage protection.
- Submit the transaction via Privy’s
sendTransaction. - Track optimistic state and surface any
APIErrordetails.
Prerequisites
- React 18+
- @privy-io/react-auth >= 3
- @paxoslabs/amplify-sdk
- Vault metadata available in your component (e.g., from
fetchVaults)
Withdraw Hook
Copy
Ask AI
import { useCallback, useMemo, useState } from "react";
import { encodeFunctionData } from "viem";
import {
isWithdrawalSpendApproved,
prepareApproveWithdrawTxData,
prepareWithdrawTxData,
type YieldType,
} from "@paxoslabs/amplify-sdk";
import { usePrivy, useWallets } from "@privy-io/react-auth";
interface WithdrawParams {
yieldType: YieldType;
shareAmount: string;
wantAssetAddress: `0x${string}`;
chainId: number;
userAddress: `0x${string}`;
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" | "checking" | "approving" | "withdrawing" | "success"
>("idle");
const [error, setError] = useState<Error | null>(null);
const run = useCallback(
async ({
yieldType,
shareAmount,
wantAssetAddress,
chainId,
userAddress,
slippage = 75,
}: WithdrawParams) => {
if (!wallet) {
throw new Error("Connect a Privy wallet before withdrawing.");
}
setStatus("checking");
setError(null);
setTxHash(null);
try {
const approval = await isWithdrawalSpendApproved({
yieldType,
wantAssetAddress,
userAddress,
chainId,
});
if (!approval.isApproved) {
setStatus("approving");
const approvalTx = await prepareApproveWithdrawTxData({
yieldType,
wantAssetAddress,
shareAmount,
chainId,
});
await sendTransaction({
chainId: approvalTx.chainId,
to: approvalTx.address,
data: encodeFunctionData({
abi: approvalTx.abi,
functionName: approvalTx.functionName,
args: approvalTx.args,
}),
});
}
const tx = await prepareWithdrawTxData({
yieldType,
shareAmount,
wantAssetAddress,
chainId,
userAddress,
slippage,
});
setStatus("withdrawing");
const hash = await sendTransaction({
chainId: tx.chainId,
to: tx.address,
data: encodeFunctionData({
abi: tx.abi,
functionName: tx.functionName,
args: tx.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 { run, status, error, txHash };
}
Component Usage
Copy
Ask AI
import { useMemo } from "react";
import { useWallets } from "@privy-io/react-auth";
import { useQuery } from "@tanstack/react-query";
import { fetchVaults, YieldType } from "@paxoslabs/amplify-sdk";
import { mainnet } from "viem/chains";
import { usePrivyWithdraw } from "./usePrivyWithdraw";
export function PrivyWithdrawButton() {
const { wallets } = useWallets();
const wallet = useMemo(() => wallets.find(Boolean), [wallets]);
const { run, status, error, txHash } = usePrivyWithdraw();
const { data: vaults } = useQuery({
queryKey: ["core-vaults", mainnet.id],
queryFn: () =>
fetchVaults({
chainId: mainnet.id,
yieldType: YieldType.CORE,
}),
enabled: Boolean(wallet),
});
const vault = vaults?.[0];
if (!wallet) {
return <p>Connect with Privy to withdraw.</p>;
}
if (!vault) {
return <p>Loading vault metadata…</p>;
}
async function handleClick() {
await run({
yieldType: vault.yieldType,
shareAmount: "5.0",
wantAssetAddress: vault.depositTokenAddressId as `0x${string}`,
chainId: vault.chainId,
userAddress: wallet.address as `0x${string}`,
slippage: 75,
});
}
return (
<section>
<button onClick={handleClick} disabled={status !== "idle" && status !== "success"}>
{status === "checking" && "Checking allowance…"}
{status === "approving" && "Approving…"}
{status === "withdrawing" && "Withdrawing…"}
{status === "success" && "Withdraw Again"}
{(status === "idle" || status === "success") && "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>
);
}
prepareWithdrawTxData throws APIError when the vault or asset is
unsupported. Log the endpoint and statusCode to help users fall back to
alternate flows.