Skip to main content
This React example pairs wagmi with prepareWithdrawTxData to offer immediate redemptions. The custom hook handles transaction preparation, submission, and receipt tracking so UI components can focus on user experience.

Overview

  • Fetch vault metadata (optionally cached with React Query or SWR).
  • Prepare withdrawal calldata with slippage tolerance.
  • Submit the transaction using wagmi’s useWriteContract.
  • Surface transaction state with useWaitForTransactionReceipt.

Prerequisites

  • React 18+
  • wagmi >= 2.0.0 and viem >= 2.0.0
  • @paxoslabs/amplify-sdk installed and initialized (initAmplifySDK called in a provider)
  • React Query (optional) for data fetching
  • Connected wallet with vault shares to redeem

Withdrawal Hook

import { useCallback, useEffect, useState } from "react";
import { useWaitForTransactionReceipt, useWriteContract } from "wagmi";
import { prepareWithdrawTxData, type YieldType } from "@paxoslabs/amplify-sdk";
import type { Address } from "viem";

interface BulkWithdrawParams {
  yieldType: YieldType;
  shareAmount: string;
  wantAssetAddress: Address;
  chainId: number;
  userAddress: Address;
  slippage?: number;
}

export function useWithdraw() {
  const [error, setError] = useState<Error | null>(null);
  const [status, setStatus] = useState<
    "idle" | "preparing" | "submitting" | "confirming" | "success"
  >("idle");

  // Wagmi hooks for writing contracts
  const {
    writeContractAsync: writeApproval,
    data: approvalHash,
    isPending: isApprovingToken,
  } = useWriteContract();

  const {
    writeContractAsync,
    data: hash,
    isPending: isSubmitting,
  } = useWriteContract();

  // Wait for transaction confirmations
  const { isLoading: isWaitingForApproval } = useWaitForTransactionReceipt({
    hash: approvalHash,
  });

  const { isLoading: isConfirming } = useWaitForTransactionReceipt({
    hash,
    confirmations: 1,
    query: { enabled: Boolean(hash) },
  });

  /**
   * Approve deposit tokens
   */
  const approve = useCallback(
    async (params: ApproveParams) => {
      setError(null);

      try {
        // Prepare approval transaction
        const approvalTx = await prepareApproveWithdrawTxData({
          yieldType: params.yieldType,
          depositToken: params.depositToken,
          depositAmount: params.depositAmount,
          chainId: params.chainId,
        });

        // Execute approval using wagmi
        await writeApproval(approvalTx);

        // Update approval state after successful transaction
        setIsApproved(true);
      } catch (err) {
        const error = err instanceof Error ? err : new Error(String(err));
        setError(error);
        throw error;
      }
    },
    [writeApproval]
  );

  /**
   * Execute withdraw transaction
   */
  const withdraw = useCallback(
    async ({
      yieldType,
      shareAmount,
      wantAssetAddress,
      chainId,
      userAddress,
      slippage = 75,
    }: BulkWithdrawParams) => {
      setError(null);
      setStatus("preparing");

      try {
        const tx = await prepareWithdrawTxData({
          yieldType,
          shareAmount,
          wantAssetAddress,
          chainId,
          userAddress,
          slippage,
        });

        setStatus("submitting");
        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 {
    bulkWithdraw,
    status,
    hash,
    error,
    isIdle: status === "idle",
    isSubmitting,
    isConfirming,
    isSuccess: status === "success",
  };
}

Component Usage

import { useAccount } from "wagmi";
import { useQuery } from "@tanstack/react-query";
import { fetchVaults, YieldType } from "@paxoslabs/amplify-sdk";
import { mainnet } from "viem/chains";
import { useBulkWithdraw } from "./useBulkWithdraw";

export function WithdrawForm() {
  const { address, chainId } = useAccount();
  const { bulkWithdraw, status, error, hash } = useBulkWithdraw();

  const { data: vaults } = useQuery({
    queryKey: ["core-vaults", chainId],
    queryFn: () =>
      fetchVaults({ chainId: mainnet.id, yieldType: YieldType.CORE }),
    enabled: Boolean(address),
  });

  const vault = vaults?.[0];

  if (!address) {
    return <p>Connect a wallet to redeem shares.</p>;
  }

  if (!vault) {
    return <p>Loading vault metadata…</p>;
  }

  async function handleWithdraw() {
    await withdraw({
      yieldType: vault.yieldType,
      shareAmount: "5.0",
      wantAssetAddress: vault.depositTokenAddressId as `0x${string}`,
      chainId: vault.chainId,
      userAddress: address,
      slippage: 75,
    });
  }

  return (
    <section>
      <button
        disabled={status === "submitting" || status === "confirming"}
        onClick={handleBulkWithdraw}
      >
        {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>
  );
}
prepareWithdrawTxData throws APIError when liquidity is unavailable or the vault is unsupported. Surface the endpoint and statusCode fields so users understand why the operation failed.