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

  /**
   * Execute withdraw transaction
   */
  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,
        });

        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",
  };
}

Component Usage

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 "./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.PRIME }),
    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.PRIME,
      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>
  );
}
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.