Skip to main content

Para SDK Implementation Supplement

This document supplements llms-full.txt with concrete build-time details: exact packages, constructor calls, auth flow code, signing patterns, and configuration required to build any integration in examples-hub.

1. SDK Package Matrix

All @getpara/* packages must use the same version as each other. Always pin every @getpara/* dependency to the same release to avoid mismatches.

Core SDKs

PackageUse When
@getpara/react-sdkReact/Next.js apps with ParaProvider + hooks + ParaModal
@getpara/react-sdk-liteReact apps using Para only through a connector (Wagmi/Graz) - lighter bundle, exports ParaWeb
@getpara/web-sdkNon-React web frameworks (Vue, Svelte) - exports ParaWeb imperative class
@getpara/server-sdkNode.js/Bun/Deno server-side - exports Para (aliased as ParaServer)
@getpara/react-native-walletReact Native / Expo mobile apps

Signing Integration Packages

PackageWeb3 LibraryCompanion Deps
@getpara/viem-v2-integrationViem 2.xviem@^2.27.0
@getpara/ethers-v6-integrationEthers 6.xethers@^6.13.0
@getpara/ethers-v5-integrationEthers 5.xethers@^5.8.0
@getpara/solana-web3.js-v1-integrationSolana Web3.js 1.x@solana/web3.js@^1.98.0
@getpara/solana-signers-v2-integrationSolana Signers 2.x@solana/web3.js@^1.98.0
@getpara/cosmjs-v0-integrationCosmJS 0.34+@cosmjs/stargate@^0.34.0, @cosmjs/proto-signing@^0.34.0

Connector Packages

PackageConnector EcosystemCompanion Deps
@getpara/wagmi-v2-integrationWagmi 2.x / 3.xwagmi@^2.15.0, viem@^2.27.0, @tanstack/react-query@^5.0.0
@getpara/rainbowkit-walletRainbowKit@rainbow-me/rainbowkit@^2.0.0, wagmi, viem
@getpara/graz-integrationGraz (Cosmos)graz@^0.4.1

Wallet Connector Packages (for bulk pregen)

PackagePurpose
@getpara/evm-wallet-connectorsEVM external wallet connections
@getpara/solana-wallet-connectorsSolana external wallet connections
@getpara/cosmos-wallet-connectorsCosmos external wallet connections

Account Abstraction Companion Deps

AA ProviderKey Packages
Alchemy (ERC-4337)@aa-sdk/core, @account-kit/infra, @account-kit/smart-contracts
Alchemy (EIP-7702)Same as above, uses createModularAccountV2Client with mode: "7702"
ZeroDev (ERC-4337)@zerodev/sdk, @zerodev/ecdsa-validator
ZeroDev (EIP-7702)Same packages, uses create7702KernelAccount + KERNEL_V3_3
Gelato@gelatonetwork/smartwallet
Thirdwebthirdweb
Rhinestone@rhinestone/sdk
Portoporto

Required Companion Dependency

All React examples require:
@tanstack/react-query@^5.0.0

2. Initialization Patterns

React / Next.js (ParaProvider)

// src/components/ParaProvider.tsx
"use client";

import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Environment, ParaProvider as ParaSDKProvider } from "@getpara/react-sdk";

const API_KEY = process.env.NEXT_PUBLIC_PARA_API_KEY ?? "";
const ENVIRONMENT = (process.env.NEXT_PUBLIC_PARA_ENVIRONMENT as Environment) || Environment.BETA;

if (!API_KEY) {
  throw new Error("API key is not defined. Please set NEXT_PUBLIC_PARA_API_KEY in your environment variables.");
}

const queryClient = new QueryClient();

export function ParaProvider({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      <ParaSDKProvider
        paraClientConfig={{
          apiKey: API_KEY,
          env: ENVIRONMENT,
        }}
        config={{ appName: "My App" }}
        paraModalConfig={{
          disableEmailLogin: false,
          disablePhoneLogin: false,
          authLayout: ["AUTH:FULL", "EXTERNAL:FULL"],
          oAuthMethods: ["APPLE", "DISCORD", "FACEBOOK", "FARCASTER", "GOOGLE", "TWITTER"],
          onRampTestMode: true,
          theme: {
            foregroundColor: "#222222",
            backgroundColor: "#FFFFFF",
            accentColor: "#888888",
            darkForegroundColor: "#EEEEEE",
            darkBackgroundColor: "#111111",
            darkAccentColor: "#AAAAAA",
            mode: "light",
            borderRadius: "none",
            font: "Inter",
          },
          logo: "/logo.svg",
          recoverySecretStepEnabled: true,
          twoFactorAuthEnabled: false,
        }}>
        {children}
      </ParaSDKProvider>
    </QueryClientProvider>
  );
}
The config prop accepts:
interface ParaProviderConfig {
  appName: string;                        // required
  disableAutoSessionKeepAlive?: boolean;  // disable automatic session refresh
  disableEmbeddedModal?: boolean;
  rpcUrl?: string;
  farcasterMiniAppConfig?: FarcasterMiniAppConfig;
}
Wrap your app root (e.g., layout.tsx):
import { ParaProvider } from "@/components/ParaProvider";
export default function RootLayout({ children }) {
  return <html><body><ParaProvider>{children}</ParaProvider></body></html>;
}
ParaProvider also accepts an optional callbacks prop for event handling:
type Callbacks = {
  onLogout?: (event) => void;
  onLogin?: (event) => void;
  onAccountSetup?: (event) => void;
  onAccountCreation?: (event) => void;
  onSignMessage?: (event) => void;
  onSignTransaction?: (event) => void;
  onWalletsChange?: (event) => void;
  onWalletCreated?: (event) => void;
  onPregenWalletClaimed?: (event) => void;
};

Vue / Svelte (ParaWeb - imperative)

// src/lib/para.ts
import { Environment, ParaWeb } from "@getpara/web-sdk";

const API_KEY = import.meta.env.VITE_PARA_API_KEY;
const ENVIRONMENT = (import.meta.env.VITE_PARA_ENVIRONMENT as Environment) || Environment.BETA;

if (!API_KEY) {
  throw new Error("API key is not defined. Please set VITE_PARA_API_KEY in your environment variables.");
}

export const para = new ParaWeb(ENVIRONMENT, API_KEY);

Server (ParaServer)

import { Para as ParaServer, Environment } from "@getpara/server-sdk";

const PARA_API_KEY = process.env.PARA_API_KEY;
const PARA_ENVIRONMENT = (process.env.PARA_ENVIRONMENT as Environment) || Environment.BETA;

const para = new ParaServer(PARA_ENVIRONMENT, PARA_API_KEY);

React Native / Expo (ParaMobile)

import ParaMobile, { Environment } from "@getpara/react-native-wallet";

const para = new ParaMobile(
  Environment.BETA,
  process.env.EXPO_PUBLIC_PARA_API_KEY,
  undefined,
  { disableWorkers: true }
);
The { disableWorkers: true } option is required for React Native because Web Workers are not available.

Chrome Extension (storage overrides)

import { ParaWeb } from "@getpara/react-sdk";
import { chromeStorageOverrides } from "../chrome-storage";

export const para = new ParaWeb(ENVIRONMENT, API_KEY, {
  ...chromeStorageOverrides,
  useStorageOverrides: true,
});

// Shared init promise - await in any script that needs Para
export const paraReady = para.init();
The chromeStorageOverrides object maps chrome.storage.local / chrome.storage.session to replace localStorage / sessionStorage which are not available in extension service workers. See examples-hub/web/with-chrome-extension/src/lib/chrome-storage.ts for the full implementation.

3. Environment Variables

Naming Conventions by Framework

FrameworkPrefixExample
Next.js (client)NEXT_PUBLIC_NEXT_PUBLIC_PARA_API_KEY
Next.js (server)nonePARA_API_KEY
Vite (Vue/Svelte)VITE_VITE_PARA_API_KEY
ExpoEXPO_PUBLIC_EXPO_PUBLIC_PARA_API_KEY
Node.js servernonePARA_API_KEY

Common Variables

# Required - Para
PARA_API_KEY=your_api_key
PARA_ENVIRONMENT=BETA          # or PRODUCTION

# Client-side variants (pick one based on framework)
NEXT_PUBLIC_PARA_API_KEY=your_api_key
NEXT_PUBLIC_PARA_ENVIRONMENT=BETA
VITE_PARA_API_KEY=your_api_key
VITE_PARA_ENVIRONMENT=BETA
EXPO_PUBLIC_PARA_API_KEY=your_api_key

# Server-side pregen encryption
ENCRYPTION_KEY=a_32_byte_string_exactly_this_l   # Must be exactly 32 bytes

# Account Abstraction
ALCHEMY_API_KEY=your_alchemy_key
ALCHEMY_GAS_POLICY_ID=your_policy_id
ALCHEMY_RPC_URL=https://arb-sepolia.g.alchemy.com/v2/your_key
ZERODEV_PROJECT_ID=your_zerodev_project_id
ZERODEV_BUNDLER_RPC=https://rpc.zerodev.app/api/v2/bundler/...
ZERODEV_PAYMASTER_RPC=https://rpc.zerodev.app/api/v2/paymaster/...
ZERODEV_ARBITRUM_SEPOLIA_RPC=https://rpc.zerodev.app/api/v2/rpc/...

# REST API
PARA_REST_BASE_URL=https://api.beta.getpara.com   # or https://api.getpara.com

4. Authentication Flows

Email Auth (React hooks pattern)

import {
  useSignUpOrLogIn,
  useWaitForLogin,
  useWaitForWalletCreation,
  type AuthStateVerify,
} from "@getpara/react-sdk";

// In your hook/component:
const { signUpOrLogIn, isPending: isSigningUp } = useSignUpOrLogIn();
const { waitForLogin, isPending: isWaitingForLogin } = useWaitForLogin();
const { waitForWalletCreation, isPending: isWaitingForWallet } = useWaitForWalletCreation();

const shouldCancel = useRef(false);

// Step 1: Submit email
signUpOrLogIn(
  { auth: { email } },
  {
    onSuccess: (authState) => {
      if (authState?.stage === "verify") {
        const verifyState = authState as AuthStateVerify;
        // verifyState.loginUrl -> open in iframe or popup for passkey verification
        // verifyState.nextStage -> "signup" (new user) or "login" (returning user)
        const isNewUser = verifyState.nextStage === "signup";
        handleAuthComplete(isNewUser);
      }
    },
    onError: (err) => { /* handle error */ },
  }
);

// Step 2: Wait for auth completion
function handleAuthComplete(isNewUser: boolean) {
  if (isNewUser) {
    waitForWalletCreation(
      { isCanceled: () => shouldCancel.current },
      { onSuccess: () => { /* user is logged in with wallet */ } }
    );
  } else {
    waitForLogin(
      { isCanceled: () => shouldCancel.current },
      { onSuccess: () => { /* user is logged in */ } }
    );
  }
}
The loginUrl from Step 1 must be displayed to the user in an iframe or popup - it’s the passkey verification page hosted by Para.

Email Auth (Imperative / Svelte / Vue)

import { para } from "@/lib/para";

// Step 1: Submit email
const authState = await para.signUpOrLogIn({ auth: { email } });

if (authState.stage === "verify" && authState.loginUrl) {
  const isNewUser = authState.nextStage === "signup";
  // Show authState.loginUrl to user in iframe/popup

  // Step 2: Wait for completion
  if (isNewUser) {
    await para.waitForWalletCreation({ isCanceled: () => shouldCancel });
  } else {
    const result = await para.waitForLogin({ isCanceled: () => shouldCancel });
    if (result.needsWallet) {
      await para.waitForWalletCreation({ isCanceled: () => shouldCancel });
    }
  }
}

OAuth Auth (React hooks)

import {
  useVerifyOAuth,
  useVerifyFarcaster,
  useWaitForLogin,
  useWaitForWalletCreation,
  type TOAuthMethod,
} from "@getpara/react-sdk";

const { verifyOAuth } = useVerifyOAuth();
const { verifyFarcaster } = useVerifyFarcaster();

// Standard OAuth (Google, Apple, Discord, Facebook, Twitter)
verifyOAuth(
  {
    method: "GOOGLE", // TOAuthMethod excluding "TELEGRAM" | "FARCASTER"
    onOAuthUrl: (url) => {
      window.open(url, "oauth", "popup=true");
    },
    isCanceled: () => shouldCancel.current || !!popupWindow.current?.closed,
  },
  {
    onSuccess: (authState) => {
      if (authState.stage === "done") {
        handleAuthComplete(authState.isNewUser);
      }
    },
  }
);

// Farcaster
verifyFarcaster(
  {
    onConnectUri: (uri) => {
      window.open(uri, "farcaster", "popup=true");
    },
    isCanceled: () => shouldCancel.current || !!popupWindow.current?.closed,
  },
  {
    onSuccess: (authState) => {
      if (authState.stage === "done") {
        handleAuthComplete(authState.isNewUser);
      }
    },
  }
);

Verify iframe listener

When showing the loginUrl in an iframe, listen for the close message:
useEffect(() => {
  const portalBase = "https://app.beta.getpara.com"; // or https://app.getpara.com
  const handleMessage = (event: MessageEvent) => {
    if (!event.origin.startsWith(portalBase)) return;
    if (event.data?.type === "CLOSE_WINDOW" && event.data.success) {
      setVerifyUrl(null);
    }
  };
  window.addEventListener("message", handleMessage);
  return () => window.removeEventListener("message", handleMessage);
}, []);

5. Signing Integration Code

Viem v2

import { createParaAccount, createParaViemClient } from "@getpara/viem-v2-integration";
import { http, parseEther, parseGwei } from "viem";
import { sepolia } from "viem/chains";

// From client (using useClient hook):
import { useClient } from "@getpara/react-sdk";
const client = useClient(); // returns the Para client instance

// Create account + client
const viemParaAccount = createParaAccount(para); // para = ParaServer or client
const viemClient = createParaViemClient(para, {
  account: viemParaAccount,
  chain: sepolia,
  transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
});

// Sign transaction
const request = await viemClient.prepareTransactionRequest({
  account: viemParaAccount,
  to: "0x...",
  value: parseEther("0.0001"),
  gas: BigInt(21000),
  maxFeePerGas: parseGwei("20"),
  maxPriorityFeePerGas: parseGwei("3"),
  chain: sepolia,
});
const signedTx = await viemClient.signTransaction(request);

Ethers v6

import { ParaEthersSigner } from "@getpara/ethers-v6-integration";
import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
const signer = new ParaEthersSigner(para, provider as ethers.Provider);

const address = await signer.getAddress();
const tx = {
  to: address,
  value: ethers.parseEther("0.0001"),
  nonce: await provider.getTransactionCount(address),
  gasLimit: 21000,
  gasPrice: (await provider.getFeeData()).gasPrice,
};
await signer.signTransaction(tx);

Ethers v5

import { ParaEthersV5Signer } from "@getpara/ethers-v5-integration";
import { ethers } from "ethers";

const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const signer = new ParaEthersV5Signer(client, provider); // client from useClient()

await signer.signMessage("Hello, Para!");
await signer.sendTransaction(tx);

Solana Web3.js v1

import { ParaSolanaWeb3Signer } from "@getpara/solana-web3.js-v1-integration";
import { Connection, Transaction, SystemProgram, LAMPORTS_PER_SOL } from "@solana/web3.js";

const connection = new Connection("https://api.testnet.solana.com");
const signer = new ParaSolanaWeb3Signer(para, connection);

const { blockhash } = await connection.getLatestBlockhash();
const tx = new Transaction();
tx.recentBlockhash = blockhash;
tx.feePayer = signer.sender; // PublicKey from Para wallet

tx.add(
  SystemProgram.transfer({
    fromPubkey: signer.sender,
    toPubkey: signer.sender,
    lamports: LAMPORTS_PER_SOL / 1000,
  })
);

await signer.signTransaction(tx);

CosmJS

import { ParaProtoSigner } from "@getpara/cosmjs-v0-integration";
import { SigningStargateClient } from "@cosmjs/stargate";

const signer = new ParaProtoSigner(para, "cosmos"); // second arg is bech32 prefix

const stargateClient = await SigningStargateClient.connectWithSigner(
  "https://rpc-rs.cosmos.nodestake.top/",
  signer
);

const fee = { amount: [{ denom: "uatom", amount: "500" }], gas: "200000" };
await stargateClient.sign(
  signer.address,
  [{ typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: { fromAddress: signer.address, toAddress: "cosmos1...", amount: [{ denom: "uatom", amount: "1000" }] } }],
  fee,
  "Signed with Para"
);

6. Wallet Pre-generation (Server-Side)

Create pre-generated wallet

import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { encrypt } from "./encryption-utils";
import { setKeyShareInDB } from "./keySharesDB";

const para = new ParaServer(PARA_ENVIRONMENT, PARA_API_KEY);

// Check if wallet already exists
const walletExists = await para.hasPregenWallet({ pregenId: { email } });

// Create wallets for all chain types
const wallets = await para.createPregenWalletPerType({
  types: ["EVM", "SOLANA", "COSMOS"],
  pregenId: { email },
});

// Get and encrypt the user share
const keyShare = para.getUserShare();
const encryptedKeyShare = await encrypt(keyShare);
await setKeyShareInDB(email, encryptedKeyShare);

Restore user share for signing

const keyShare = await getKeyShareInDB(email);
const decryptedKeyShare = await decrypt(keyShare);
await para.setUserShare(decryptedKeyShare);

// Now para is ready for signing with any integration

AES-GCM Encryption Pattern

// encryption-utils.ts
const ALGORITHM = "AES-GCM";
const IV_LENGTH = 12;

// ENCRYPTION_KEY env var must be exactly 32 bytes
async function importSecretKey(keyString: string): Promise<CryptoKey> {
  const keyBuffer = Buffer.from(keyString, "utf-8");
  return await crypto.subtle.importKey("raw", keyBuffer, { name: ALGORITHM }, false, ["encrypt", "decrypt"]);
}

export async function encrypt(text: string): Promise<string> {
  const cryptoKey = await importSecretKey(process.env.ENCRYPTION_KEY);
  const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
  const encoded = new TextEncoder().encode(text);
  const encrypted = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, cryptoKey, encoded);
  return `${Buffer.from(iv).toString("base64")}:${Buffer.from(encrypted).toString("base64")}`;
}

export async function decrypt(encryptedText: string): Promise<string> {
  const [ivBase64, dataBase64] = encryptedText.split(":");
  const iv = new Uint8Array(Buffer.from(ivBase64, "base64"));
  const data = Buffer.from(dataBase64, "base64");
  const cryptoKey = await importSecretKey(process.env.ENCRYPTION_KEY);
  const decrypted = await crypto.subtle.decrypt({ name: ALGORITHM, iv }, cryptoKey, data);
  return new TextDecoder().decode(decrypted);
}

SQLite Storage Schema

// keySharesDB.ts - uses `sqlite` + `sqlite3` packages
import { open } from "sqlite";
import sqlite3 from "sqlite3";

const db = await open({ filename: "keyShares.db", driver: sqlite3.Database });

await db.exec(`
  CREATE TABLE IF NOT EXISTS keyShares (
    email TEXT PRIMARY KEY NOT NULL,
    keyShare TEXT NOT NULL
  )
`);

// Get
const row = await db.get("SELECT keyShare FROM keyShares WHERE email = ?", [email]);

// Upsert
await db.run(
  "INSERT INTO keyShares (email, keyShare) VALUES (?, ?) ON CONFLICT(email) DO UPDATE SET keyShare = excluded.keyShare",
  [email, encryptedKeyShare]
);

7. Account Abstraction Patterns

Alchemy ERC-4337

import { alchemy, arbitrumSepolia } from "@account-kit/infra";
import { WalletClientSigner } from "@aa-sdk/core";
import { createModularAccountAlchemyClient } from "@account-kit/smart-contracts";
import { createParaAccount, createParaViemClient } from "@getpara/viem-v2-integration";

const viemParaAccount = createParaAccount(para);
const viemClient = createParaViemClient(para, {
  account: viemParaAccount,
  chain: arbitrumSepolia,
  transport: http(ALCHEMY_RPC_URL),
});

const walletClientSigner = new WalletClientSigner(viemClient, "para");

const alchemyClient = await createModularAccountAlchemyClient({
  transport: alchemy({ rpcUrl: ALCHEMY_RPC_URL }),
  chain: arbitrumSepolia,
  signer: walletClientSigner,
  policyId: ALCHEMY_GAS_POLICY_ID,
});

const result = await alchemyClient.sendUserOperation({ uo: batchCalls });
await alchemyClient.waitForUserOperationTransaction(result);

Alchemy EIP-7702

import { createModularAccountV2Client } from "@account-kit/smart-contracts";
import { createParaAccount } from "@getpara/viem-v2-integration";
import type { SmartAccountSigner, LocalAccount } from "@aa-sdk/core";

const viemParaAccount = createParaAccount(para);

// Wrap as SmartAccountSigner
const paraSigner: SmartAccountSigner<LocalAccount> = {
  signerType: "para",
  inner: viemParaAccount,
  getAddress: async () => viemParaAccount.address,
  signMessage: async (message) => viemParaAccount.signMessage({ message }),
  signTypedData: async (typedData) => viemParaAccount.signTypedData(typedData),
  signAuthorization: async (auth) => viemParaAccount.signAuthorization(auth),
};

const alchemyClient = await createModularAccountV2Client({
  mode: "7702",
  transport: alchemy({ rpcUrl: ALCHEMY_RPC_URL }),
  chain: arbitrumSepolia,
  signer: paraSigner,
  policyId: ALCHEMY_GAS_POLICY_ID,
});

ZeroDev ERC-4337

import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator";
import { createKernelAccount, createKernelAccountClient, createZeroDevPaymasterClient } from "@zerodev/sdk";
import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants";

const viemParaAccount = createParaAccount(para);
const viemClient = createParaViemClient(para, { account: viemParaAccount, chain: arbitrumSepolia, transport: http(ZERODEV_RPC_URL) });
const publicClient = createPublicClient({ chain: arbitrumSepolia, transport: http(ZERODEV_RPC_URL) });

const entryPoint = getEntryPoint("0.7");

const ecdsaValidator = await signerToEcdsaValidator(viemClient, {
  signer: viemParaAccount,
  entryPoint,
  kernelVersion: KERNEL_V3_1,
});

const kernelAccount = await createKernelAccount(publicClient, {
  plugins: { sudo: ecdsaValidator },
  entryPoint,
  kernelVersion: KERNEL_V3_1,
});

const kernelClient = createKernelAccountClient({
  account: kernelAccount,
  chain: arbitrumSepolia,
  bundlerTransport: http(ZERODEV_BUNDLER_RPC),
  paymaster: {
    getPaymasterData: (userOp) =>
      createZeroDevPaymasterClient({ chain: arbitrumSepolia, transport: http(ZERODEV_PAYMASTER_RPC) })
        .sponsorUserOperation({ userOperation: userOp }),
  },
});

const hash = await kernelClient.sendUserOperation({ callData: await kernelClient.account.encodeCalls(calls) });
await kernelClient.waitForUserOperationReceipt({ hash, timeout: 30000 });

ZeroDev EIP-7702

import { create7702KernelAccount, create7702KernelAccountClient } from "@zerodev/ecdsa-validator";
import { KERNEL_V3_3 } from "@zerodev/sdk/constants";

const kernelAccount = await create7702KernelAccount(publicClient, {
  signer: viemParaAccount,
  entryPoint: getEntryPoint("0.7"),
  kernelVersion: KERNEL_V3_3,
});

const kernelClient = create7702KernelAccountClient({
  account: kernelAccount,
  chain: arbitrumSepolia,
  bundlerTransport: http(ZERODEV_BUNDLER_RPC),
  paymaster: createZeroDevPaymasterClient({ chain: arbitrumSepolia, transport: http(ZERODEV_PAYMASTER_RPC) }),
  client: publicClient,
});

const hash = await kernelClient.sendUserOperation({ calls });

Gelato ERC-4337

import { accounts, createGelatoSmartWalletClient } from "@gelatonetwork/smartwallet";

const viemParaAccount = createParaAccount(para);
const viemClient = createParaViemClient(para, { account: viemParaAccount, chain: sepolia, transport: http() });

const account = await accounts.kernel({ eip7702: false, signer: viemClient });
const gelatoClient = createGelatoSmartWalletClient({ account, apiKey: GELATO_API_KEY });

const result = await gelatoClient.execute({
  payment: { type: "sponsored" },
  calls: [{ to: "0x...", value: 0n, data: "0x" }],
});
await result.wait();

Gelato EIP-7702

import { gelato } from "@gelatonetwork/smartwallet/accounts";
import { createGelatoSmartWalletClient } from "@gelatonetwork/smartwallet";

const account = await gelato({ signer: viemClient }); // Different subpath from 4337's accounts.kernel()
const gelatoClient = createGelatoSmartWalletClient({ account, apiKey: GELATO_API_KEY });
// Same execute pattern as 4337

Thirdweb ERC-4337

import { createThirdwebClient, sendTransaction, prepareTransaction } from "thirdweb";
import { viemAdapter, smartWallet } from "thirdweb/wallets";
import { sepolia } from "thirdweb/chains";

const thirdwebClient = createThirdwebClient({ clientId: THIRDWEB_CLIENT_ID });
const personalAccount = viemAdapter.walletClient.fromViem({ walletClient: viemClient });
const wallet = smartWallet({ chain: sepolia, sponsorGas: true });
const smartAccount = await wallet.connect({ client: thirdwebClient, personalAccount });

const tx = prepareTransaction({ to: "0x...", value: 0n, chain: sepolia, client: thirdwebClient });
await sendTransaction({ account: smartAccount, transaction: tx });

Porto EIP-7702

Porto operates on Base Sepolia with a unique EOA-to-smart-account upgrade flow. See examples-hub/web/with-react-nextjs/aa-porto-7702/ for the full implementation using Account.from(), Key.createSecp256k1(), and RelayActions.prepareUpgradeAccount().

Rhinestone ERC-4337

Rhinestone provides cross-chain account abstraction using @rhinestone/sdk. See examples-hub/web/with-react-nextjs/aa-rhinestone-4337/ for cross-chain USDC transfers (Arbitrum->Base) with sponsored transactions.

8. Connector Integrations

Wagmi Connector

// src/config/wagmi.ts
import { paraConnector } from "@getpara/wagmi-v2-integration";
import { ParaWeb } from "@getpara/react-sdk-lite"; // or @getpara/react-sdk
import { createConfig, http, cookieStorage, createStorage } from "wagmi";
import { sepolia } from "wagmi/chains";

// Initialize ParaWeb (not ParaProvider - connector manages its own UI)
const para = new ParaWeb(Environment.BETA, API_KEY);

const connector = paraConnector({
  para,
  appName: "My App",
  chains: [sepolia],
  queryClient,
  authLayout: ["AUTH:FULL", "EXTERNAL:FULL"],
  oAuthMethods: ["APPLE", "DISCORD", "FACEBOOK", "FARCASTER", "GOOGLE", "TWITTER"],
  disableEmailLogin: false,
  disablePhoneLogin: false,
  logo: "/logo.svg",
  onRampTestMode: true,
  recoverySecretStepEnabled: true,
  twoFactorAuthEnabled: false,
  theme: {
    foregroundColor: "#222222",
    backgroundColor: "#FFFFFF",
    accentColor: "#888888",
    mode: "light",
    borderRadius: "none",
    font: "Inter",
  },
});

export const wagmiConfig = createConfig({
  chains: [sepolia],
  connectors: [connector, injected(), metaMask(), coinbaseWallet()],
  ssr: true,
  storage: createStorage({ storage: cookieStorage }),
  transports: { [sepolia.id]: http(RPC_URL) },
});
Important: The Wagmi connector uses @getpara/react-sdk-lite (not the full @getpara/react-sdk) to avoid bundle bloat, since the connector provides its own modal.

RainbowKit

// src/client/wagmi.ts
import { getParaWallet } from "@getpara/rainbowkit-wallet";
import { connectorsForWallets } from "@rainbow-me/rainbowkit";
import { createConfig, http } from "wagmi";
import { sepolia } from "wagmi/chains";

const paraWallet = getParaWallet({
  para,             // ParaWeb instance
  chains: [sepolia],
  appName: "My App",
  // Same modal config options as paraConnector: oAuthMethods, theme, etc.
});

const connectors = connectorsForWallets(
  [{ groupName: "Para", wallets: [paraWallet] }],
  { appName: "My App", projectId: WALLETCONNECT_PROJECT_ID }
);

export const wagmiConfig = createConfig({
  chains: [sepolia],
  connectors,
  transports: { [sepolia.id]: http(RPC_URL) },
});

// Providers: WagmiProvider + QueryClientProvider + RainbowKitProvider
// Then use wagmi hooks: useSignMessage(), useSendTransaction(), etc.

Graz (Cosmos)

// src/context/Provider.tsx
import { ParaGrazConnector } from "@getpara/graz-integration";
import { ParaWeb, Environment } from "@getpara/react-sdk-lite";
import { GrazProvider, defineChainInfo, type ParaGrazConfig } from "graz";

const para = new ParaWeb(Environment.BETA, API_KEY);

const cosmosTestnet = defineChainInfo({
  chainId: "provider",
  chainName: "Cosmos ICS Provider Testnet",
  rpc: "https://rpc.provider-sentry-01.ics-testnet.polypore.xyz",
  rest: "https://rest.provider-sentry-01.ics-testnet.polypore.xyz",
  bip44: { coinType: 118 },
  bech32Config: { bech32PrefixAccAddr: "cosmos", /* ... other prefixes */ },
  stakeCurrency: { coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6 },
  currencies: [{ coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6 }],
  feeCurrencies: [{ coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6 }],
});

const paraConfig: ParaGrazConfig = {
  paraWeb: para as ParaGrazConfig["paraWeb"],
  connectorClass: ParaGrazConnector,       // Note: connectorClass, not connector instance
  modalProps: { appName: "My App" },
  queryClient: queryClient,
};

// Wrap your app:
<GrazProvider
  grazOptions={{
    chains: [cosmosTestnet],
    paraConfig,
  }}>
  {children}
</GrazProvider>
Note: defineChainInfo and ParaGrazConfig type are imported from graz, not from @getpara/graz-integration.

9. Session Management

Check and keep alive

// Client-side (React hooks)
import { useAccount, useKeepSessionAlive } from "@getpara/react-sdk";
const { isConnected } = useAccount();

// Imperative
const isActive = await para.isSessionActive();
if (isActive) {
  await para.keepSessionAlive();
}

Export / Import session (server transfer)

// Client: export session string
const sessionString = await para.exportSession();

// Send sessionString to server via API call

// Server: import session
const serverPara = new ParaServer(PARA_ENVIRONMENT, PARA_API_KEY);
await serverPara.importSession(sessionString);

// Server can now sign on behalf of the user
const isActive = await serverPara.isSessionActive();

Issue JWT

// Client-side
import { useIssueJwt } from "@getpara/react-sdk";
const { issueJwt } = useIssueJwt();

const { token, keyId } = await issueJwt();
// Send `token` to your server for verification

// Imperative
const { token, keyId } = await para.issueJWT();

10. ParaModal Configuration

The paraModalConfig prop on <ParaSDKProvider> (or the config object for paraConnector) accepts:
interface ParaModalConfig {
  // Authentication layout
  authLayout?: ("AUTH:FULL" | "AUTH:CONDENSED" | "EXTERNAL:FULL" | "EXTERNAL:CONDENSED")[];

  // Email / Phone login toggles
  disableEmailLogin?: boolean;
  disablePhoneLogin?: boolean;
  defaultAuthIdentifier?: string; // Pre-fill email/phone

  // OAuth providers
  oAuthMethods?: TOAuthMethod[];
  // TOAuthMethod = "APPLE" | "DISCORD" | "FACEBOOK" | "FARCASTER" | "GOOGLE" | "TWITTER" | "TELEGRAM"

  // Branding
  logo?: string; // recommended 372x160px

  // Theme
  theme?: {
    foregroundColor?: string;
    backgroundColor?: string;
    accentColor?: string;
    darkForegroundColor?: string;
    darkBackgroundColor?: string;
    darkAccentColor?: string;
    overlayBackground?: string;
    mode?: "light" | "dark";
    borderRadius?: "none" | "xs" | "sm" | "md" | "lg" | "xl" | "full";
    font?: string;
    oAuthLogoVariant?: "dark" | "light" | "default";
  };

  // Features
  onRampTestMode?: boolean;
  recoverySecretStepEnabled?: boolean;
  twoFactorAuthEnabled?: boolean;
  hideWallets?: boolean;           // Hide wallet terminology
  isGuestModeEnabled?: boolean;    // Enable guest login
  bareModal?: boolean;             // No overlay backdrop
  embeddedModal?: boolean;         // Embedded styling
  className?: string;              // Custom CSS class

  // Account linking
  supportedAccountLinks?: ("EMAIL" | "PHONE" | "GOOGLE" | "EXTERNAL_WALLET")[];

  // Step override
  currentStepOverride?: ModalStepProp;
  // Steps: AUTH_MAIN, AUTH_MORE, AWAITING_OAUTH, VERIFICATIONS,
  //   BIOMETRIC_CREATION, PASSWORD_CREATION, SECRET, AWAITING_WALLET_CREATION,
  //   ACCOUNT_MAIN, ACCOUNT_PROFILE, CHAIN_SWITCH,
  //   EX_WALLET_MORE, EX_WALLET_SELECTED,
  //   ADD_FUNDS_BUY, ADD_FUNDS_RECEIVE, ADD_FUNDS_WITHDRAW,
  //   SETUP_2FA, VERIFY_2FA

  // Callbacks
  onModalStepChange?: (value: any) => void;
  onClose?: () => void;

  // Advanced overrides
  loginTransitionOverride?: (para: ParaWeb) => Promise<void>;
  createWalletOverride?: (para: ParaWeb) => Promise<{ recoverySecret?: string; walletIds: any }>;
}
Note: Password and PIN screens render in an iframe and use the Developer Portal theme settings, not paraModalConfig.theme.

11. Mobile Setup Requirements

Required Native Modules (Expo)

{
  "@craftzdog/react-native-buffer": "^6.1.0",
  "@getpara/react-native-wallet": "<same version as other @getpara/* packages>",
  "@peculiar/webcrypto": "^1.5.0",
  "@react-native-async-storage/async-storage": "^2.2.0",
  "react-native-keychain": "^10.0.0",
  "react-native-modpow": "^1.1.0",
  "react-native-passkey": "^3.3.2",
  "react-native-quick-base64": "^2.2.0",
  "react-native-quick-crypto": "^0.7.14",
  "react-native-worklets": "^0.5.1",
  "readable-stream": "^4.5.2"
}

Metro Config (polyfills)

// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);

config.resolver.extraNodeModules = {
  crypto: require.resolve('react-native-quick-crypto'),
  buffer: require.resolve('@craftzdog/react-native-buffer'),
};

module.exports = config;

Babel Config

// babel.config.js
module.exports = function (api) {
  api.cache(true);
  return {
    presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'],
    plugins: ['react-native-worklets/plugin'],
  };
};

iOS Passkey Setup

For passkey support on iOS, you must configure your app’s Associated Domains capability with a webcredentials: entry pointing to your domain. The domain must serve an apple-app-site-association file. See examples-hub/mobile/with-expo-one-click-login/ios/ for an example Xcode project configuration.

Key Init Option

Always pass { disableWorkers: true } when initializing ParaMobile - Web Workers are not available in React Native.

12. Vite Polyfill Configuration

Vue and Svelte projects using Vite need vite-plugin-node-polyfills because the Para SDK uses Node.js built-in modules (buffer, crypto, stream).
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue"; // or svelte()
import { nodePolyfills } from "vite-plugin-node-polyfills";

export default defineConfig({
  plugins: [vue(), nodePolyfills()],
});
Install:
npm install -D vite-plugin-node-polyfills
This is required for @getpara/web-sdk. The @getpara/react-sdk with Next.js does not need this (Next.js handles polyfills via npx setup-para postinstall script).

13. REST API Direct Usage

For server-to-server integrations without the SDK, use the REST API directly.

Base URLs

EnvironmentURL
Betahttps://api.beta.getpara.com
Productionhttps://api.getpara.com

Authentication Header

X-API-Key: your_api_key
X-Request-Id: <uuid>          # optional, for request tracing
Content-Type: application/json

Endpoints

POST /v1/wallets                     - Create wallet
GET  /v1/wallets/{walletId}          - Get wallet details
POST /v1/wallets/{walletId}/sign-raw - Sign raw bytes (0x-prefixed hex)

Create Wallet Request

{
  "type": "EVM",
  "userIdentifier": "alice@example.com",
  "userIdentifierType": "EMAIL",
  "scheme": "DKLS"
}
  • type: "EVM" | "SOLANA" | "COSMOS"
  • userIdentifierType: "EMAIL" | "PHONE" | "CUSTOM_ID" | "GUEST_ID" | "TELEGRAM" | "DISCORD" | "TWITTER"
  • scheme: "DKLS" | "CGGMP" | "ED25519" (optional, defaults based on wallet type)
  • cosmosPrefix: string (optional, for Cosmos wallets)

Sign Raw Request

{
  "data": "0xdeadbeef..."
}

Error Codes

CodeMeaning
201Created
200Success
400Bad request (invalid params)
401Unauthorized (invalid API key)
404Not found
409Conflict (wallet already exists)
429Rate limited
500Server error

Example Node.js client

const PARA_API_KEY = process.env.PARA_API_KEY;
const BASE_URL = process.env.PARA_REST_BASE_URL ?? "https://api.beta.getpara.com";

async function callPara<T>(path: string, options: { method?: string; body?: unknown } = {}): Promise<T> {
  const { method = "GET", body } = options;
  const headers: Record<string, string> = {
    "X-API-Key": PARA_API_KEY,
    "X-Request-Id": crypto.randomUUID(),
  };
  if (body) headers["Content-Type"] = "application/json";

  const res = await fetch(`${BASE_URL}${path}`, {
    method,
    headers,
    body: body ? JSON.stringify(body) : undefined,
  });

  const data = await res.json();
  if (!res.ok) throw new Error(`Para ${res.status}: ${JSON.stringify(data)}`);
  return data as T;
}

14. React Hook Reference

CSS Import

ParaModal requires its stylesheet:
import "@getpara/react-sdk/styles.css";
Add this in your root layout or ParaProvider component.

From @getpara/react-sdk

Account & Connection:
  • useAccount() -> { isConnected, isConnecting, address, embedded } - embedded.wallets contains wallet array
  • useClient() -> Para client instance (for passing to signer constructors)
  • useIsFullyLoggedIn() -> boolean | undefined
  • useParaStatus() -> ParaStatus (readiness and Farcaster Mini App detection)
  • useModal() -> { isOpen, openModal, closeModal }
  • useWallet() -> Wallet | null | undefined (active wallet)
  • useWalletState() -> { selectedWallet, setSelectedWallet, updateSelectedWallet }
  • useLinkedAccounts() -> LinkedAccounts & { userId: string }
Authentication:
  • useSignUpOrLogIn() -> { signUpOrLogIn, signUpOrLogInAsync, isPending }
  • useVerifyOAuth() -> { verifyOAuth, isPending }
  • useVerifyFarcaster() -> { verifyFarcaster, isPending }
  • useVerifyNewAccount() -> { verifyNewAccount, verifyNewAccountAsync }
  • useWaitForLogin() -> { waitForLogin, isPending }
  • useWaitForWalletCreation() -> { waitForWalletCreation, isPending }
  • useLoginExternalWallet() -> { loginExternalWallet, loginExternalWalletAsync }
  • useLogout() -> { logout, logoutAsync }
  • useAddAuthMethod() -> { addCredential, addCredentialAsync }
  • useLinkAccount() -> { linkAccount, isPending, error }
Signing (low-level):
  • useSignMessage() -> { signMessage, signMessageAsync } - signs base64-encoded message with wallet ID
  • useSignTransaction() -> { signTransaction, signTransactionAsync }
Session:
  • useKeepSessionAlive() -> { keepSessionAlive, keepSessionAliveAsync }
  • useIssueJwt() -> { issueJwt, issueJwtAsync } -> { token, keyId }
Wallet Management:
  • useCreateWallet() -> { createWallet, createWalletAsync }
  • useCreateWalletPerType() -> { createWalletPerType, createWalletPerTypeAsync } - create wallets for multiple chain types
  • useCreateGuestWallets() -> { createGuestWallets, createGuestWalletsAsync }
  • useWalletBalance() -> string | null - balance in native units

From @getpara/react-sdk/evm

EVM-specific hooks for direct Viem client access (requires @getpara/viem-v2-integration as a dependency):
  • useViemClient({ address, walletClientConfig }) -> { viemClient } - returns a Viem WalletClient pre-configured with the Para signer
import { useAccount } from "@getpara/react-sdk";
import { useViemClient } from "@getpara/react-sdk/evm";
import { http } from "viem";
import { sepolia } from "viem/chains";

const { embedded } = useAccount();
const address = embedded?.wallets?.[0]?.address as `0x${string}`;
const { viemClient } = useViemClient({
  address,
  walletClientConfig: { chain: sepolia, transport: http() },
});

// viemClient.sendTransaction, viemClient.signMessage, etc.
This is the preferred way to use Viem in React - no need to import createParaAccount / createParaViemClient directly.

From @getpara/react-sdk/viem

  • useViemAccount({ address? }) -> { viemAccount: Account | null, isLoading } - lower-level access to the viem Account without creating a full WalletClient

From @getpara/react-sdk/solana

  • useSolanaSigner({ rpc, walletId? }) -> { solanaSigner: SignerWalletAdapter | null, isLoading } - for Solana Signers v2 integration

From @getpara/react-sdk/cosmos

  • useCosmjsProtoSigner({ prefix?, walletId?, messageSigningTimeoutMs? }) -> { protoSigner: OfflineDirectSigner | null, isLoading } - for CosmJS proto signing
  • useCosmjsAminoSigner({ prefix?, walletId?, messageSigningTimeoutMs? }) -> { aminoSigner: OfflineAminoSigner | null, isLoading } - for CosmJS amino signing
// CosmJS example using hooks
import { useCosmjsProtoSigner } from "@getpara/react-sdk/cosmos";
import { SigningStargateClient } from "@cosmjs/stargate";

const { protoSigner } = useCosmjsProtoSigner({ prefix: "cosmos" });
const stargateClient = await SigningStargateClient.connectWithSigner(RPC_URL, protoSigner);

15. Testing Credentials

Beta Environment

  • Environment: Environment.BETA / "BETA"
  • Portal base URL: https://app.beta.getpara.com
  • API base URL: https://api.beta.getpara.com

Test Email Pattern

Use any email ending in @test.getpara.com (e.g., dev@test.getpara.com, test1@test.getpara.com). Any OTP code works (e.g., 123456, 000000).

Test Phone Numbers

Use US numbers (+1) with format (area code)-555-xxxx (e.g., (425)-555-1234, (206)-555-9876). Any OTP code works.

User Limits

50 users per beta account. Delete test users via Developer Portal -> Users section. Users can only be deleted in BETA; in production, wallets are permanent.

Post-Install Setup

Many Next.js examples include a postinstall script:
"postinstall": "npx setup-para"
This configures Next.js webpack to handle Para SDK’s WASM and worker files. Always run it after npm install.

16. Examples Hub Directory Map

Web - Framework Setup

DirectoryFrameworkSDKKey Concept
web/with-react-nextjs/para-modal/Next.js@getpara/react-sdkParaProvider + ParaModal quickstart
web/with-react-vite/React + Vite@getpara/react-sdkVite with React
web/with-svelte-vite/Svelte + Vite@getpara/web-sdkImperative ParaWeb init
web/with-vue-vite/Vue + Vite@getpara/web-sdkImperative ParaWeb init
web/with-react-tanstack-start/TanStack Start@getpara/react-sdkFull-stack meta-framework
web/with-chrome-extension/Chrome Ext@getpara/react-sdkStorage overrides
web/with-pwa/PWA@getpara/react-sdkProgressive Web App

Web - Authentication

DirectoryAuth Method
web/with-react-nextjs/custom-email-auth/Email + passkey verification
web/with-react-nextjs/custom-phone-auth/Phone + OTP verification
web/with-react-nextjs/custom-oauth-auth/OAuth (Google, Apple, etc.) + Farcaster
web/with-react-nextjs/custom-combined-auth/All auth methods combined

Web - Signing Libraries

DirectoryLibraryPackage
web/with-react-nextjs/signer-viem-v2/Viem 2.x@getpara/viem-v2-integration
web/with-react-nextjs/signer-ethers-v5/Ethers 5.x@getpara/ethers-v5-integration
web/with-react-nextjs/signer-ethers-v6/Ethers 6.x@getpara/ethers-v6-integration
web/with-react-nextjs/signer-solana-web3/Solana Web3.js@getpara/solana-web3.js-v1-integration
web/with-react-nextjs/signer-solana-anchor/Anchor@getpara/solana-web3.js-v1-integration
web/with-react-nextjs/signer-solana-signers-v2/Solana Signers v2@getpara/solana-signers-v2-integration
web/with-react-nextjs/signer-cosmjs/CosmJS@getpara/react-sdk (uses useCosmjsProtoSigner from /cosmos)

Web - Connectors

DirectoryConnectorPackage
web/with-react-nextjs/connector-wagmi/Wagmi v2@getpara/wagmi-v2-integration + @getpara/react-sdk-lite
web/with-react-nextjs/connector-rainbowkit/RainbowKit@getpara/rainbowkit-wallet
web/with-react-nextjs/connector-graz/Graz (Cosmos)@getpara/graz-integration
web/with-react-nextjs/connector-reown-appkit/Reown AppKit@getpara/wagmi-v2-integration

Web - Account Abstraction

DirectoryProviderStandard
web/with-react-nextjs/aa-alchemy-4337/AlchemyERC-4337
web/with-react-nextjs/aa-alchemy-7702/AlchemyEIP-7702
web/with-react-nextjs/aa-zerodev-4337/ZeroDevERC-4337
web/with-react-nextjs/aa-zerodev-7702/ZeroDevEIP-7702
web/with-react-nextjs/aa-gelato-4337/GelatoERC-4337
web/with-react-nextjs/aa-gelato-7702/GelatoEIP-7702
web/with-react-nextjs/aa-thirdweb-4337/ThirdwebERC-4337
web/with-react-nextjs/aa-rhinestone-4337/RhinestoneERC-4337
web/with-react-nextjs/aa-porto-7702/PortoEIP-7702

Web - Modal Variants

DirectoryChain Focus
web/with-react-nextjs/para-modal/Default (all chains)
web/with-react-nextjs/para-modal-evm/EVM only
web/with-react-nextjs/para-modal-solana/Solana only
web/with-react-nextjs/para-modal-cosmos/Cosmos only
web/with-react-nextjs/para-modal-multichain/Explicit multi-chain
web/with-react-nextjs/para-pregen-claim/Pre-generated wallet claiming

Server

DirectoryRuntimeKey Features
server/with-node/Node.jsPregen wallets, Viem/Ethers/Solana/CosmJS signing, Alchemy AA, ZeroDev AA
server/with-bun/BunSession-based signing, Alchemy/ZeroDev EIP-7702
server/with-deno/DenoSession-based signing, Alchemy/ZeroDev EIP-7702
server/rest-with-node/Node.jsREST API direct usage (no SDK, raw fetch)

Mobile

DirectoryPlatformKey Features
mobile/with-react-native/React NativeStandard RN setup
mobile/with-expo-one-click-login/ExpoOne-click login, passkeys, native crypto polyfills

Advanced Patterns

DirectoryPattern
advanced-patterns/client-auth-server-sign/Client authenticates, exports session, server signs
advanced-patterns/with-bulk-pregen/Bulk wallet pre-generation with multi-chain claiming UI

DeFi Integrations

DirectoryIntegration
defi-integrations/with-jupiter-dex-api/Jupiter DEX (Solana)
defi-integrations/with-squid-router-api/Squid cross-chain router
defi-integrations/with-relay-bridge-api/Relay Protocol bridge