Skip to main content
Redeem shares in one transaction by pairing 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 fetchVaults and React Query).
  • Prepare withdrawal calldata with slippage protection.
  • Submit the transaction via Privy’s sendTransaction.
  • Track optimistic state and surface any APIError details.

Prerequisites

  • React 18+
  • @privy-io/react-auth >= 3
  • @paxoslabs/amplify-sdk
  • Vault metadata available in your component (e.g., from fetchVaults)

Withdraw Hook

import { useCallback, useMemo, useState } from "react";
import { encodeFunctionData } from "viem";
import {
  prepareApproveWithdrawTxData,
  prepareWithdrawTxData,
  type YieldType,
} from "@paxoslabs/amplify-sdk";
import { usePrivy, useWallets } from "@privy-io/react-auth";

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 run = 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,
        });

        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 { run, status, error, txHash };
}

Component Usage

import { useMemo } from "react";
import { useWallets } from "@privy-io/react-auth";
import { useQuery } from "@tanstack/react-query";
import { fetchSupportedAssets, 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: supportedAssets } = useQuery({
    queryKey: ["amplify-assets", mainnet.id],
    queryFn: () =>
      fetchSupportedAssets({
        yieldType: YieldType.PRIME,
      }),
    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 run({
      yieldType: YieldType.PRIME,
      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>
  );
}
prepareWithdrawTxData throws APIError when the vault or asset is unsupported. Log the endpoint and statusCode to help users fall back to alternate flows.