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
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
VITE_APP_ETHEREUM_MAINNET_RPC=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
Createsrc/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>
);
}
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
Createsrc/hooks/useAmplify.ts:
import { initAmplifySDK, LogLevel } from "@paxoslabs/amplify-sdk";
import { useEffect, useState } from "react";
import { mainnet } from "viem/chains";
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, {
rpcUrls: {
[mainnet.id]: import.meta.env.VITE_APP_ETHEREUM_MAINNET_RPC,
},
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
Replacesrc/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
Next Steps
Deposits Guide
Complete deposit examples with hooks and components.
Withdrawals Guide
Add withdrawal functionality to your app.
Smart Wallets
Enable gas sponsorship and transaction batching.
API Reference
Explore all SDK functions and types.