This guide covers the recommended project structure and SDK initialization patterns for Amplify SDK integrations.
Recommended Directory Structure
A consistent file layout keeps vault logic, wallet plumbing, and UI concerns easy to reason about:
├─ .env.local # Privy App ID, Amplify API key, RPC URLs (never commit)
├─ package.json
├─ src/
│ ├─ app/
│ │ ├─ App.tsx # Top-level routes, feature shells
│ │ └─ providers.tsx # React providers (PrivyProvider, AmplifyProvider, QueryClient)
│ ├─ components/
│ │ ├─ DepositForm.tsx
│ │ ├─ WithdrawForm.tsx
│ │ └─ StatusBanner.tsx
│ ├─ hooks/
│ │ ├─ useAmplify.ts # initAmplifySDK + helper wrappers
│ │ └─ useBalances.ts # data fetching with react-query / wagmi
│ ├─ lib/
│ │ ├─ privy.ts # Privy login/logout helpers
│ │ └─ viem.ts # Public/wallet client factories
│ ├─ pages/ # Optional route segments (Next.js / Remix)
│ ├─ styles/
│ └─ main.tsx # Vite entry point
├─ tests/ # Component and integration tests
└─ docs/ # Internal runbooks and integration notes
Separation of Concerns
- Providers layer – initialize Privy, React Query, and the Amplify SDK once. Re-export hooks for the rest of the app.
- Hooks layer – wrap SDK helpers (
prepareDepositTxData, prepareWithdrawTxData) with domain-specific logic and query invalidation.
- Components layer – pure UI components that receive prepared data plus callbacks for execution.
- Lib layer – interaction helpers for viem, Privy, or analytics that should not depend on React state.
SDK Initialization
All Amplify SDK functions require initialization before use. Call initAmplifySDK() once at application startup.
Basic Initialization
import { initAmplifySDK } from "@paxoslabs/amplify-sdk";
await initAmplifySDK("pxl_your_api_key");
With Configuration Options
import { initAmplifySDK, LogLevel } from "@paxoslabs/amplify-sdk";
await initAmplifySDK("pxl_your_api_key", {
telemetry: true, // Enable PostHog error tracking (default)
logLevel: LogLevel.ERROR, // Minimum log level (default)
});
React Integration Hook
Create a hook to initialize the SDK once and expose ready state:
// 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 };
}
Usage in App
// src/App.tsx
import { useAmplify } from "./hooks/useAmplify";
export function App() {
const { isReady, error } = useAmplify();
if (error) {
return <div>Failed to initialize SDK: {error.message}</div>;
}
if (!isReady) {
return <div>Initializing...</div>;
}
return <YourApp />;
}
Environment Variables
Create .env.local (ignored by Vite by default):
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 .env.local. Provide an .env.example without secrets for teammates.
Global Providers
Set up React Query and your wallet provider:
// src/app/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 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 your entry point:
// src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { AmplifyProviders } from "./providers.tsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<AmplifyProviders>
<App />
</AmplifyProviders>
</StrictMode>,
);
Server-Side Usage
If you execute transactions from a backend service, mirror the same separation:
server/app.ts – Express/Fastify entry point
server/clients/amplify.ts – initAmplifySDK and helper wrappers
server/services/deposits.ts – orchestrates approvals, permits, or withdrawals
Next Steps