Skip to main content
This walkthrough bootstraps a Vite + React project that authenticates users with Privy, initializes the Amplify SDK, and wires up deposit flows using the unified deposit API.
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.

1. Scaffold the Project

pnpm create vite amplify-starter --template react-ts
cd amplify-starter
pnpm install
Install the runtime dependencies:
pnpm add @paxoslabs/amplify-sdk viem @privy-io/react-auth @tanstack/react-query

2. Configure Environment Variables

Create .env.local:
VITE_PRIVY_APP_ID=your-privy-app-id
VITE_AMPLIFY_API_KEY=pxl_your_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 Providers

Create src/providers.tsx:
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 Providers({ 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:
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { Providers } from "./providers.tsx";

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

4. Initialize the SDK

Create src/hooks/useAmplify.ts:
import { initAmplifySDK, LogLevel } from "@paxoslabs/amplify-sdk";
import { useEffect, useState } from "react";

let initialized = false;

export function useAmplify() {
  const [isReady, setIsReady] = useState(initialized);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    if (initialized) {
      setIsReady(true);
      return;
    }

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

    async function init() {
      try {
        await initAmplifySDK(apiKey, {
          logLevel: LogLevel.ERROR,
          telemetry: true,
        });
        initialized = true;
        setIsReady(true);
      } catch (err) {
        setError(err instanceof Error ? err : new Error("SDK initialization failed"));
      }
    }

    init();
  }, []);

  return { isReady, error };
}

5. Create the App

Replace src/App.tsx:
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { usePrivy, useWallets } from "@privy-io/react-auth";
import { encodeFunctionData } from "viem";
import { mainnet } from "viem/chains";
import {
  fetchSupportedAssets,
  prepareDepositAuthorization,
  prepareDeposit,
  isPermitAuth,
  isApprovalAuth,
  isAlreadyApprovedAuth,
  YieldType,
} from "@paxoslabs/amplify-sdk";
import { useAmplify } from "./hooks/useAmplify";

export default function App() {
  const { isReady, error: sdkError } = useAmplify();
  const { ready, authenticated, login, logout, sendTransaction } = usePrivy();
  const { wallets } = useWallets();
  const wallet = wallets[0];

  const [status, setStatus] = useState<string>("");
  const [amount, setAmount] = useState("100");

  const { data: assets } = useQuery({
    queryKey: ["assets", mainnet.id],
    queryFn: () => fetchSupportedAssets({ yieldType: YieldType.CORE }),
    enabled: isReady && authenticated,
  });

  // Loading states
  if (!ready || !isReady) return <p>Loading...</p>;
  if (sdkError) return <p>SDK Error: {sdkError.message}</p>;
  if (!authenticated) return <button onClick={login}>Connect with Privy</button>;
  if (!wallet) return <p>Connecting wallet...</p>;

  const asset = assets?.[0];
  const owner = wallet.address as `0x${string}`;

  async function handleDeposit() {
    if (!asset) return;

    const params = {
      yieldType: YieldType.CORE,
      depositAsset: asset.address as `0x${string}`,
      depositAmount: amount,
      to: owner,
      chainId: mainnet.id,
    };

    try {
      setStatus("Checking authorization...");
      const auth = await prepareDepositAuthorization(params);

      if (isPermitAuth(auth)) {
        setStatus("Please sign permit...");
        const provider = await wallet.getEthereumProvider();
        const signature = (await provider.request({
          method: "eth_signTypedData_v4",
          params: [owner, JSON.stringify(auth.permitData)],
        })) as `0x${string}`;

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

        await sendTransaction({
          chainId: prepared.txData.chainId,
          to: prepared.txData.address,
          data: encodeFunctionData({
            abi: prepared.txData.abi,
            functionName: prepared.txData.functionName,
            args: prepared.txData.args,
          }),
        });
      } else if (isApprovalAuth(auth)) {
        setStatus("Approving token...");
        await sendTransaction({
          chainId: mainnet.id,
          to: auth.txData.address,
          data: encodeFunctionData({
            abi: auth.txData.abi,
            functionName: auth.txData.functionName,
            args: auth.txData.args,
          }),
        });

        setStatus("Submitting deposit...");
        const prepared = await prepareDeposit(params);
        await sendTransaction({
          chainId: prepared.txData.chainId,
          to: prepared.txData.address,
          data: encodeFunctionData({
            abi: prepared.txData.abi,
            functionName: prepared.txData.functionName,
            args: prepared.txData.args,
          }),
        });
      } else if (isAlreadyApprovedAuth(auth)) {
        setStatus("Submitting deposit...");
        const prepared = await prepareDeposit(params);
        await sendTransaction({
          chainId: prepared.txData.chainId,
          to: prepared.txData.address,
          data: encodeFunctionData({
            abi: prepared.txData.abi,
            functionName: prepared.txData.functionName,
            args: prepared.txData.args,
          }),
        });
      }

      setStatus("Deposit complete!");
    } catch (err) {
      setStatus(`Error: ${err instanceof Error ? err.message : "Unknown"}`);
    }
  }

  return (
    <main style={{ padding: "2rem" }}>
      <h1>Amplify Starter</h1>
      <p>Connected: {wallet.address}</p>

      <div style={{ marginTop: "1rem" }}>
        <label>
          Amount ({asset?.symbol || "..."}):
          <input
            type="number"
            value={amount}
            onChange={(e) => setAmount(e.target.value)}
            style={{ marginLeft: "0.5rem" }}
          />
        </label>
      </div>

      <div style={{ marginTop: "1rem" }}>
        <button onClick={handleDeposit} disabled={!asset}>
          Deposit
        </button>
        <button onClick={logout} style={{ marginLeft: "1rem" }}>
          Disconnect
        </button>
      </div>

      {status && <p style={{ marginTop: "1rem" }}>{status}</p>}
    </main>
  );
}

6. Run the App

pnpm dev
Open http://localhost:5173 and test the deposit flow.

Next Steps