> ## Documentation Index
> Fetch the complete documentation index at: https://developers.paxoslabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> Spin up a React + Vite starter that deposits and withdraws with Privy wallets

This walkthrough bootstraps a Vite + React project that authenticates users with Privy, initializes the Amplify SDK, and wires up deposit plus withdrawal flows using the unified deposit API. Follow the steps end-to-end or cherry-pick the sections that fit your stack.

<Callout type="info">
  The snippets use the latest Privy React SDK (`@privy-io/react-auth`) APIs.
  Wait for `ready` from `usePrivy` and `useWallets` before inspecting user or
  wallet state to avoid race conditions.
</Callout>

## 1. Scaffold the Project

```bash theme={null}
pnpm create vite amplify-earn-starter --template react-ts
cd amplify-earn-starter
pnpm install
```

Install the runtime dependencies:

```bash theme={null}
pnpm add @paxoslabs/amplify-sdk viem @privy-io/react-auth @tanstack/react-query
```

Install type support if you use tests:

```bash theme={null}
pnpm add -D @types/node @types/react @types/react-dom
```

## 2. Configure Environment Variables

Create `.env.local` (ignored by Vite by default):

```bash theme={null}
VITE_PRIVY_APP_ID=your-privy-app-id
VITE_AMPLIFY_API_KEY=your-amplify-api-key
VITE_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/your-key
```

Never commit this file. Provide an `.env.example` without secrets for teammates.

## 3. Set Up Global Providers

Create `src/app/providers.tsx`:

```tsx theme={null}
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { PrivyProvider } from "@privy-io/react-auth";
import type { ReactNode } from "react";

const queryClient = new QueryClient();

export function AmplifyProviders({ children }: { children: ReactNode }) {
  if (!import.meta.env.VITE_PRIVY_APP_ID) {
    throw new Error("Missing VITE_PRIVY_APP_ID env variable");
  }

  return (
    <PrivyProvider appId={import.meta.env.VITE_PRIVY_APP_ID}>
      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
    </PrivyProvider>
  );
}
```

Wire the providers in `src/main.tsx`:

```tsx theme={null}
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./index.css";
import { AmplifyProviders } from "./providers.tsx";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <AmplifyProviders>
      <App />
    </AmplifyProviders>
  </StrictMode>,
);
```

## 4. Initialize the Amplify SDK

Create `src/hooks/useAmplify.ts`:

```ts theme={null}
import { initAmplifySDK, LogLevel } from "@paxoslabs/amplify-sdk";
import { useEffect } from "react";

let initialized = false;

export function useAmplify() {
  useEffect(() => {
    if (initialized) return;

    const apiKey = import.meta.env.VITE_AMPLIFY_API_KEY;
    if (!apiKey) {
      throw new Error("Missing VITE_AMPLIFY_API_KEY env variable");
    }

    // Initialize with optional configuration
    initAmplifySDK(apiKey, {
      logLevel: LogLevel.ERROR, // Set to DEBUG for development
      telemetry: true, // Enable error tracking
    });

    initialized = true;
  }, []);
}
```

Call the hook near the root of your app (inside `App` or a layout component).

For advanced configuration options, see [SDK Initialization](./sdk-initialization).

## 5. Build Wallet & Session State

Create `src/hooks/useWalletSession.ts`:

```ts theme={null}
import { usePrivy, useWallets } from "@privy-io/react-auth";
import { useMemo } from "react";

export function useWalletSession() {
  const { ready: privyReady, authenticated, login, logout } = usePrivy();
  const { ready: walletsReady, wallets } = useWallets();

  const wallet = useMemo(
    () =>
      wallets.find((item) => item.walletClientType === "privy") ?? wallets[0],
    [wallets],
  );

  return {
    ready: privyReady && walletsReady,
    authenticated,
    login,
    logout,
    wallet,
  };
}
```

The `usePrivy` and `useWallets` hooks come directly from the official [Privy React SDK](https://docs.privy.io/basics/react/installation). Wait for `ready` before inspecting `wallets`.

## 6. Create the App with Unified Deposit API

Add `src/app/App.tsx`:

```tsx theme={null}
import {
  DepositAuthMethod,
  fetchSupportedAssets,
  prepareApproveWithdrawTxData,
  prepareDepositAuthorization,
  prepareDepositTxData,
  prepareDepositWithPermitTxData,
  prepareWithdrawTxData,
  YieldType,
  type SupportedAsset as AmplifySupportedAsset,
} from "@paxoslabs/amplify-sdk";
import { usePrivy } from "@privy-io/react-auth";
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { encodeFunctionData, isAddress, type Address } from "viem";
import { mainnet } from "viem/chains";
import "./App.css";
import { useAmplify } from "./hooks/use-amplify";
import { useWalletSession } from "./hooks/use-wallet-session";

function App() {
  useAmplify();
  const { ready, authenticated, login, logout, wallet } = useWalletSession();
  const { sendTransaction } = usePrivy();
  const [status, setStatus] = useState<string | null>(null);
  const [selectedAsset, setSelectedAsset] =
    useState<AmplifySupportedAsset | null>(null);
  const {
    data: supportedAssets,
    isLoading: isLoadingSupportedAssets,
    isError: isErrorSupportedAssets,
  } = useQuery({
    queryKey: ["amplify-vaults", mainnet.id],
    enabled: authenticated,
    queryFn: () =>
      fetchSupportedAssets({
        yieldType: YieldType.PRIME,
      }),
  });
  console.log(supportedAssets);
  if (!ready) {
    return <p>Loading Privy…</p>;
  }

  if (!authenticated || !wallet) {
    return <button onClick={login}>Connect with Privy</button>;
  }

  if (isLoadingSupportedAssets) {
    return <p>Loading assets...</p>;
  }

  if (isErrorSupportedAssets) {
    return <p>Error loading assets</p>;
  }

  const owner = wallet.address as `0x${string}`;
  const chainId = mainnet.id;

  const handleAssetChange = (address: Address) => {
    if (!isAddress(address)) {
      console.error("Invalid asset address");
      return;
    }
    setSelectedAsset(
      supportedAssets?.find((asset) => asset.address === address) ?? null,
    );
  };

  async function handleDeposit() {
    const params = {
      yieldType: YieldType.PRIME,
      depositToken: selectedAsset?.address as `0x${string}`,
      depositAmount: "100",
      recipientAddress: owner,
      chainId,
    };

    // Step 1: Get authorization method
    setStatus("Checking authorization...");
    const auth = await prepareDepositAuthorization(params);

    // Step 2: Handle based on method
    switch (auth.method) {
      case DepositAuthMethod.PERMIT: {
        setStatus("Please sign permit...");
        const provider = await wallet.getEthereumProvider();
        const signature = (await provider.request({
          method: "eth_signTypedData_v4",
          params: [
            owner,
            JSON.stringify({
              domain: auth.permitData.domain,
              types: auth.permitData.types,
              primaryType: auth.permitData.primaryType,
              message: auth.permitData.message,
            }),
          ],
        })) as `0x${string}`;

        setStatus("Submitting deposit...");
        const permitTx = await prepareDepositWithPermitTxData({
          ...params,
          signature,
          deadline: BigInt(auth.permitData.message.deadline),
        });

        // Extract and encode the permit deposit data
        const {
          abi: permitAbi,
          functionName: permitFn,
          args: permitArgs,
        } = permitTx.data;
        await sendTransaction({
          chainId: permitTx.chainId,
          to: permitTx.address,
          data: encodeFunctionData({
            abi: permitAbi,
            functionName: permitFn,
            args: permitArgs,
          }),
        });
        setStatus("Deposit complete!");
        break;
      }

      case DepositAuthMethod.APPROVAL: {
        setStatus("Approving token spend...");
        const {
          abi: approvalAbi,
          functionName: approvalFn,
          args: approvalArgs,
        } = auth.txData;
        await sendTransaction({
          chainId: chainId,
          to: auth.txData.address,
          data: encodeFunctionData({
            abi: approvalAbi,
            functionName: approvalFn,
            args: approvalArgs,
          }),
        });

        setStatus("Submitting deposit...");
        const depositTx = await prepareDepositTxData(params);
        const {
          abi: depositAbi,
          functionName: depositFn,
          args: depositArgs,
        } = depositTx;
        await sendTransaction({
          chainId: depositTx.chainId,
          to: depositTx.address,
          data: encodeFunctionData({
            abi: depositAbi,
            functionName: depositFn,
            args: depositArgs,
          }),
        });
        setStatus("Deposit complete!");
        break;
      }

      case DepositAuthMethod.ALREADY_APPROVED: {
        setStatus("Submitting deposit...");
        const directTx = await prepareDepositTxData(params);
        const { abi, functionName, args } = directTx;
        await sendTransaction({
          chainId: directTx.chainId,
          to: directTx.address,
          data: encodeFunctionData({ abi, functionName, args }),
        });
        setStatus("Deposit complete!");
        break;
      }
    }
  }

  async function handleWithdraw() {
    setStatus("Preparing withdrawal...");

    const approval = await prepareApproveWithdrawTxData({
      chainId,
      wantAssetAddress: selectedAsset?.address as `0x${string}`,
      yieldType: YieldType.PRIME,
    });

    setStatus("Approving withdrawal...");
    await sendTransaction({
      chainId,
      to: approval.address,
      data: encodeFunctionData({
        abi: approval.abi,
        functionName: approval.functionName,
        args: approval.args,
      }),
    });

    const withdraw = await prepareWithdrawTxData({
      yieldType: YieldType.PRIME,
      wantAssetAddress: selectedAsset?.address as `0x${string}`,
      offerAmount: "1",
      chainId,
    });

    setStatus("Submitting withdrawal...");
    const {
      abi: withdrawAbi,
      functionName: withdrawFn,
      args: withdrawArgs,
    } = withdraw;
    await sendTransaction({
      chainId: withdraw.chainId,
      to: withdraw.address,
      data: encodeFunctionData({
        abi: withdrawAbi,
        functionName: withdrawFn,
        args: withdrawArgs,
      }),
    });
    setStatus("Withdrawal complete!");
  }

  return (
    <main>
      <h1>Amplify Earn Starter</h1>
      <p>{wallet.address}</p>
      {status && <p>{status}</p>}
      <div>
        <select
          value={selectedAsset?.symbol ?? ""}
          onChange={(e) => {
            handleAssetChange(e.target.value as Address);
          }}
        >
          {supportedAssets?.map((asset) => (
            <option key={asset.address} value={asset.address}>
              {asset.symbol}
            </option>
          ))}
        </select>
      </div>
      <div>
        <div>
          <button onClick={handleDeposit}>Deposit</button>
          <button onClick={handleWithdraw}>Withdraw</button>
        </div>
      </div>
      <button onClick={logout}>Disconnect</button>
    </main>
  );
}

export default App;
```

## 7. Recommended Enhancements

* Replace hard-coded amounts with user inputs and vault selections sourced from the API.
* Add React Query mutations around each flow to display optimistic states and capture errors.
* Surface `APIError` payloads in telemetry and show a fallback UI for unsupported assets.
* Extract deposit and withdraw logic into reusable hooks (`useDeposit`, `useWithdraw`).
* Add analytics around wallet connection and transaction success rates.

## 8. Starter Repository Layout

```text theme={null}
amplify-earn-starter/
├─ src/
│  ├─ app/
│  │  ├─ App.tsx
│  │  └─ providers.tsx
│  ├─ hooks/
│  │  ├─ useAmplify.ts
│  │  └─ useWalletSession.ts
│  └─ main.tsx
├─ .env.example
└─ README.md
```

Clone this structure in your org repo and extend it with routing, feature flags, and analytics as needed. Tie everything back to the [Developer Guide](./developer-guide) for deployment practices and keep [Functions](./functions/index) handy as you add more flows.
