# Para
> Non-custodial embedded wallet infrastructure using MPC for building embedded wallet integrations across web, mobile, and server platforms supporting EVM, Solana, and Cosmos chains.
## Para SDK Implementation Supplement
> Concrete build-time details: packages, initialization, authentication, signing, connectors, account abstraction, and configuration for building Para integrations.
## 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
| Package | Use When |
|---------|----------|
| `@getpara/react-sdk` | React/Next.js apps with ParaProvider + hooks + ParaModal |
| `@getpara/react-sdk-lite` | React apps using Para only through a connector (Wagmi/Graz) - lighter bundle, exports `ParaWeb` |
| `@getpara/web-sdk` | Non-React web frameworks (Vue, Svelte) - exports `ParaWeb` imperative class |
| `@getpara/server-sdk` | Node.js/Bun/Deno server-side - exports `Para` (aliased as `ParaServer`) |
| `@getpara/react-native-wallet` | React Native / Expo mobile apps |
### Signing Integration Packages
| Package | Web3 Library | Companion Deps |
|---------|-------------|----------------|
| `@getpara/viem-v2-integration` | Viem 2.x | `viem@^2.27.0` |
| `@getpara/ethers-v6-integration` | Ethers 6.x | `ethers@^6.13.0` |
| `@getpara/ethers-v5-integration` | Ethers 5.x | `ethers@^5.8.0` |
| `@getpara/solana-web3.js-v1-integration` | Solana Web3.js 1.x | `@solana/web3.js@^1.98.0` |
| `@getpara/solana-signers-v2-integration` | Solana Signers 2.x | `@solana/web3.js@^1.98.0` |
| `@getpara/cosmjs-v0-integration` | CosmJS 0.34+ | `@cosmjs/stargate@^0.34.0`, `@cosmjs/proto-signing@^0.34.0` |
### Connector Packages
| Package | Connector Ecosystem | Companion Deps |
|---------|-------------------|----------------|
| `@getpara/wagmi-v2-integration` | Wagmi 2.x / 3.x | `wagmi@^2.15.0`, `viem@^2.27.0`, `@tanstack/react-query@^5.0.0` |
| `@getpara/rainbowkit-wallet` | RainbowKit | `@rainbow-me/rainbowkit@^2.0.0`, `wagmi`, `viem` |
| `@getpara/graz-integration` | Graz (Cosmos) | `graz@^0.4.1` |
### Wallet Connector Packages (for bulk pregen)
| Package | Purpose |
|---------|---------|
| `@getpara/evm-wallet-connectors` | EVM external wallet connections |
| `@getpara/solana-wallet-connectors` | Solana external wallet connections |
| `@getpara/cosmos-wallet-connectors` | Cosmos external wallet connections |
### Account Abstraction Companion Deps
| AA Provider | Key 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` |
| Thirdweb | `thirdweb` |
| Rhinestone | `@rhinestone/sdk` |
| Porto | `porto` |
### Required Companion Dependency
All React examples require:
```
@tanstack/react-query@^5.0.0
```
---
## 2. Initialization Patterns
### React / Next.js (ParaProvider)
```tsx
// 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 (
{children}
);
}
```
The `config` prop accepts:
```ts
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`):
```tsx
import { ParaProvider } from "@/components/ParaProvider";
export default function RootLayout({ children }) {
return
{children};
}
```
`ParaProvider` also accepts an optional `callbacks` prop for event handling:
```ts
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)
```ts
// 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)
```ts
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)
```ts
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)
```ts
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
| Framework | Prefix | Example |
|-----------|--------|---------|
| Next.js (client) | `NEXT_PUBLIC_` | `NEXT_PUBLIC_PARA_API_KEY` |
| Next.js (server) | none | `PARA_API_KEY` |
| Vite (Vue/Svelte) | `VITE_` | `VITE_PARA_API_KEY` |
| Expo | `EXPO_PUBLIC_` | `EXPO_PUBLIC_PARA_API_KEY` |
| Node.js server | none | `PARA_API_KEY` |
### Common Variables
```env
# 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)
```tsx
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)
```ts
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)
```tsx
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:
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
// 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 {
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 {
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 {
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
```ts
// 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
```ts
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
```ts
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 = {
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
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
```ts
// 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
```ts
// 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)
```tsx
// 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:
{children}
```
Note: `defineChainInfo` and `ParaGrazConfig` type are imported from `graz`, not from `@getpara/graz-integration`.
---
## 9. Session Management
### Check and keep alive
```ts
// 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)
```ts
// 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
```ts
// 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 `` (or the config object for `paraConnector`) accepts:
```ts
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;
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)
```json
{
"@craftzdog/react-native-buffer": "^6.1.0",
"@getpara/react-native-wallet": "",
"@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)
```js
// 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
```js
// 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`).
```ts
// 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:
```bash
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
| Environment | URL |
|------------|-----|
| Beta | `https://api.beta.getpara.com` |
| Production | `https://api.getpara.com` |
### Authentication Header
```
X-API-Key: your_api_key
X-Request-Id: # 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
```json
{
"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
```json
{
"data": "0xdeadbeef..."
}
```
### Error Codes
| Code | Meaning |
|------|---------|
| 201 | Created |
| 200 | Success |
| 400 | Bad request (invalid params) |
| 401 | Unauthorized (invalid API key) |
| 404 | Not found |
| 409 | Conflict (wallet already exists) |
| 429 | Rate limited |
| 500 | Server error |
### Example Node.js client
```ts
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(path: string, options: { method?: string; body?: unknown } = {}): Promise {
const { method = "GET", body } = options;
const headers: Record = {
"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:
```tsx
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
```ts
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
```ts
// 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](https://developer.getpara.com) -> 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:
```json
"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
| Directory | Framework | SDK | Key Concept |
|-----------|----------|-----|-------------|
| `web/with-react-nextjs/para-modal/` | Next.js | `@getpara/react-sdk` | ParaProvider + ParaModal quickstart |
| `web/with-react-vite/` | React + Vite | `@getpara/react-sdk` | Vite with React |
| `web/with-svelte-vite/` | Svelte + Vite | `@getpara/web-sdk` | Imperative ParaWeb init |
| `web/with-vue-vite/` | Vue + Vite | `@getpara/web-sdk` | Imperative ParaWeb init |
| `web/with-react-tanstack-start/` | TanStack Start | `@getpara/react-sdk` | Full-stack meta-framework |
| `web/with-chrome-extension/` | Chrome Ext | `@getpara/react-sdk` | Storage overrides |
| `web/with-pwa/` | PWA | `@getpara/react-sdk` | Progressive Web App |
### Web - Authentication
| Directory | Auth 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
| Directory | Library | Package |
|-----------|---------|---------|
| `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
| Directory | Connector | Package |
|-----------|-----------|---------|
| `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
| Directory | Provider | Standard |
|-----------|----------|----------|
| `web/with-react-nextjs/aa-alchemy-4337/` | Alchemy | ERC-4337 |
| `web/with-react-nextjs/aa-alchemy-7702/` | Alchemy | EIP-7702 |
| `web/with-react-nextjs/aa-zerodev-4337/` | ZeroDev | ERC-4337 |
| `web/with-react-nextjs/aa-zerodev-7702/` | ZeroDev | EIP-7702 |
| `web/with-react-nextjs/aa-gelato-4337/` | Gelato | ERC-4337 |
| `web/with-react-nextjs/aa-gelato-7702/` | Gelato | EIP-7702 |
| `web/with-react-nextjs/aa-thirdweb-4337/` | Thirdweb | ERC-4337 |
| `web/with-react-nextjs/aa-rhinestone-4337/` | Rhinestone | ERC-4337 |
| `web/with-react-nextjs/aa-porto-7702/` | Porto | EIP-7702 |
### Web - Modal Variants
| Directory | Chain 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
| Directory | Runtime | Key Features |
|-----------|---------|-------------|
| `server/with-node/` | Node.js | Pregen wallets, Viem/Ethers/Solana/CosmJS signing, Alchemy AA, ZeroDev AA |
| `server/with-bun/` | Bun | Session-based signing, Alchemy/ZeroDev EIP-7702 |
| `server/with-deno/` | Deno | Session-based signing, Alchemy/ZeroDev EIP-7702 |
| `server/rest-with-node/` | Node.js | REST API direct usage (no SDK, raw fetch) |
### Mobile
| Directory | Platform | Key Features |
|-----------|----------|-------------|
| `mobile/with-react-native/` | React Native | Standard RN setup |
| `mobile/with-expo-one-click-login/` | Expo | One-click login, passkeys, native crypto polyfills |
### Advanced Patterns
| Directory | Pattern |
|-----------|---------|
| `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
| Directory | Integration |
|-----------|------------|
| `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 |
---
---
# Command Reference
Source: https://docs.getpara.com/v2/cli/commands
import { Link } from "/snippets/v2/components/ui/link.mdx";
## Authentication
### `para login`
Authenticate with your Para developer account. Opens your browser for a secure OAuth flow with PKCE verification.
```bash
para login
```
| Flag | Description |
|---|---|
| `--no-browser` | Print a URL instead of opening the browser (for headless/CI environments) |
After login, the CLI automatically selects your first organization and project if none are configured.
Sessions are stored at `~/.config/para/credentials.json` with `0600` file permissions. The session is validated server-side on each CLI invocation.
Sessions are shared across key environments — logging in once gives you access to both beta and prod keys.
### `para logout`
Clear stored authentication credentials.
```bash
para logout
```
| Flag | Description |
|---|---|
| `--all` | Clear all stored sessions |
The CLI sends a best-effort server-side session invalidation.
### `para auth status`
Check whether your current session is valid. This performs a server-side validation, not just a local check.
```bash
para auth status
```
```bash Output
Status: Authenticated
Email: dev@example.com
Expires: 3/10/2026, 12:00:00 PM
```
With `--json`:
```json
{
"authenticated": true,
"email": "dev@example.com",
"userId": "usr_abc123",
"expiresAt": 1741608000000
}
```
### `para whoami`
Show the current authenticated user and active context — organization, project, and environment.
```bash
para whoami
```
```bash Output
Email: dev@example.com
User ID: usr_abc123
Key Environment: beta
Organization: My Company (admin)
Project: proj_def456
Session Expires: 3/10/2026, 12:00:00 PM
```
Organization shows the name and your role. Project shows the raw project ID. If the organization ID can't be resolved to a name, the raw ID is shown instead.
---
## Configuration
Configuration resolves from multiple sources: CLI flags, environment variables (`PARA_ENVIRONMENT`, `PARA_ORG_ID`, `PARA_PROJECT_ID`), `.pararc`, global config, then defaults. See [Installation](/v2/cli/installation#configuration) for the full resolution chain.
### `para config get`
Read configuration values. Without a key, shows all values from both global and project config with their source.
```bash
para config get
```
```bash Output
defaultEnvironment (global): beta
environment (.pararc): beta
organizationId (.pararc): org_xyz789
projectId (.pararc): proj_def456
```
Read a specific key:
```bash
para config get defaultEnvironment
```
Project config (`.pararc`) takes precedence over global config (`~/.config/para/config.json`).
### `para config set`
Set a configuration value.
```bash
para config set
```
| Flag | Description |
|---|---|
| `--local` | Write to `.pararc` in the current directory instead of global config |
#### Valid Keys
| Key | Values |
|---|---|
| `defaultEnvironment` | `beta`, `prod` |
| `defaultOrganizationId` | Any valid organization ID |
| `defaultProjectId` | Any valid project ID |
#### Examples
```bash
# Set global default key environment
para config set defaultEnvironment prod
# Set global default org
para config set defaultOrganizationId org_xyz789
```
`para config set --local` writes the key name as-is (e.g., `defaultEnvironment`) into `.pararc`, but `para init` uses different key names (`environment`, `organizationId`, `projectId`). Use `para init` to create project-level config — it ensures the correct key names are used.
### `para config unset`
Remove a configuration value.
```bash
para config unset
```
| Flag | Description |
|---|---|
| `--local` | Remove from `.pararc` instead of global config |
```bash
para config unset defaultOrganizationId
para config unset environment --local
```
### `para init`
Create a `.pararc` configuration file in the current directory. This pins the organization, project, and environment for anyone working in this directory.
```bash
para init
```
| Flag | Description |
|---|---|
| `--force` | Overwrite an existing `.pararc` file |
In interactive mode, you'll be prompted to select a key environment (`beta` or `prod`). In non-interactive mode (`--no-input`), the current key environment from global config is used.
The resulting `.pararc` file:
```json
{
"environment": "beta",
"organizationId": "org_xyz789",
"projectId": "proj_def456"
}
```
The `.pararc` writer rejects keys that contain sensitive terms (`session`, `token`, `secret`, `credential`, `password`, `apikey`, `api_key`) to prevent accidental credential storage in version-controlled files.
---
## Organizations
### `para orgs list`
List all organizations you belong to.
```bash
para orgs list
```
```bash Output
Name ID Plan
My Company org_xyz789 growth (active)
Side Project org_abc123 free
```
The `(active)` indicator shows which organization is currently selected. With `--json`, returns an array of organization objects.
### `para orgs switch`
Set the active organization. This updates your global config so subsequent commands use this org.
```bash
para orgs switch [org-id]
```
Without an `org-id`, an interactive selector is shown:
```bash
para orgs switch
```
```bash Output
◆ Select an organization
│ ○ My Company (org_xyz789) (current)
│ ● Side Project (org_abc123)
└
```
With a specific ID:
```bash
para orgs switch org_abc123
```
After switching organizations, your previous project ID remains in the config but may point to a project in the old org. Run `para projects switch` to select a project in the new organization.
---
## Projects
All project commands require an active organization. Set one with `para orgs switch` if you haven't already.
### `para projects list`
List projects in your active organization.
```bash
para projects list
```
| Flag | Description |
|---|---|
| `--include-archived` | Include archived projects in the list |
```bash Output
Name ID Framework
my-app proj_def456 nextjs (active)
backend proj_ghi789 vite
```
### `para projects switch`
Set the active project.
```bash
para projects switch [project-id]
```
Without a `project-id`, an interactive selector shows all active projects in the current organization.
### `para projects create`
Create a new project in the active organization.
```bash
para projects create
```
| Flag | Description |
|---|---|
| `-n, --name ` | Project name |
| `-d, --description ` | Project description |
| `--framework ` | Framework (`nextjs`, `vite`, `react-native`, etc.) |
Without flags, you'll be prompted interactively for a name.
```bash
para projects create -n "my-new-app" --framework nextjs
```
### `para projects update`
Update a project's name, description, or framework.
```bash
para projects update [project-id]
```
| Flag | Description |
|---|---|
| `-n, --name ` | New project name |
| `-d, --description ` | New description |
| `--framework ` | Framework (`REACT`, `NEXT`, `VITE`, etc.) |
| `--package-manager ` | Package manager (`NPM`, `YARN`, `PNPM`) |
Uses the active project if no `project-id` is given. Without flags, prompts interactively.
### `para projects archive`
Archive a project. Its API keys stop working immediately. This is reversible with `restore`.
```bash
para projects archive [project-id]
```
Uses the active project if no `project-id` is given.
| Flag | Description |
|---|---|
| `-y, --yes` | Skip confirmation prompt |
Archiving a project immediately disables all of its API keys. Active users will lose access.
### `para projects restore`
Restore a previously archived project.
```bash
para projects restore
```
Use `para projects list --include-archived` to find the ID of archived projects.
---
## API Keys
All key commands require an active organization and project. Set them with `para orgs switch` and `para projects switch`.
### `para keys list`
List API keys for the active project.
```bash
para keys list
```
| Flag | Description |
|---|---|
| `--include-archived` | Include archived keys |
```bash Output
Name ID API Key Env Status
prod-key key_abc123 para_beta_a1b2... BETA active
test-key key_def456 para_beta_c3d4... BETA archived
```
### `para keys get`
Get details of an API key. Without a key ID, the CLI auto-resolves the key from your active project and key environment.
```bash
para keys get [key-id]
```
| Flag | Description |
|---|---|
| `--show-secret` | Show the full secret key (unmasked) |
| `--copy` | Copy the public API key to clipboard |
| `--copy-secret` | Copy the secret key to clipboard |
```bash
para keys get # Get the beta key (default)
para keys get -e prod # Get the prod key
para keys get abc-123 --copy # Copy a specific key
```
### `para keys create`
Create a new API key in the active project.
```bash
para keys create
```
| Flag | Description |
|---|---|
| `-n, --name ` | Internal key name |
| `--display-name ` | Display name shown to users |
The secret key is only shown once at creation time. Save it immediately.
### `para keys rotate`
Rotate an API key. The old key stops working immediately. Without a key ID, the CLI auto-resolves the key from your active project and key environment.
```bash
para keys rotate [key-id]
```
| Flag | Description |
|---|---|
| `--secret` | Rotate the secret key instead of the public key |
| `-y, --yes` | Skip confirmation prompt |
```bash
para keys rotate # Rotate the beta key
para keys rotate -e prod # Rotate the prod key
para keys rotate --secret # Rotate the secret key
```
Key rotation is irreversible. The old key stops working immediately after rotation.
### `para keys archive`
Archive (revoke) an API key. The key stops working immediately. Without a key ID, the CLI auto-resolves the key from your active project and key environment.
```bash
para keys archive [key-id]
```
| Flag | Description |
|---|---|
| `-y, --yes` | Skip confirmation prompt |
```bash
para keys archive # Archive the beta key
para keys archive -e prod # Archive the prod key
```
### `para keys config`
Configure settings for an API key. Without a sub-category, opens an interactive menu to browse all categories.
```bash
para keys config [key-id]
```
The CLI auto-resolves the key from your active project and key environment (`-e beta` or `-e prod`). If multiple keys exist for the same environment, you'll be prompted to choose one.
#### Show Configuration
View the current configuration for an API key without entering an edit flow. Shows all settings organized by category.
```bash
para keys config show [category] [key-id]
```
| Argument | Description |
|---|---|
| `[category]` | Filter by category: `security`, `branding`, `setup`, `ramps`, `webhooks` |
| `[key-id]` | API key ID (resolved from project + environment if omitted) |
```bash
para keys config show # Show all configuration
para keys config show security # Show security settings only
para keys config show ramps # Show ramp settings only
para keys config show --json # Full config as JSON (for scripting)
para keys config show security --json # Single category as JSON
```
```bash Example output
Para (beta)
Security
Auth Methods PASSKEY, PASSWORD
Origins https://myapp.com
Session Length 1440 minutes (1 day)
Tx Popups enabled
IP Allowlist (none — all IPs allowed)
Branding
Foreground #333333
Background #ffffff
Accent (default)
Font Helvetica
...
```
Use `para keys config show --json` to pipe configuration into other tools or audit scripts. The JSON output uses raw values (arrays, booleans, `null`) rather than display strings.
#### Security
Configure auth methods, origins, session length, and IP restrictions.
```bash
para keys config security [key-id]
```
| Flag | Description |
|---|---|
| `--origins ` | Comma-separated allowed origins (empty string to clear) |
| `--auth-methods ` | Auth methods: `PASSKEY`, `PASSWORD`, `PIN` (comma-separated). `PASSWORD` and `PIN` cannot be enabled simultaneously |
| `--session-length ` | Session length in minutes (5–43200) |
| `--transaction-popups` | Enable transaction popups |
| `--no-transaction-popups` | Disable transaction popups |
| `--ip-allowlist ` | Comma-separated CIDR blocks (empty string to clear) |
```bash
para keys config security --origins "https://myapp.com,https://staging.myapp.com" --auth-methods "PASSKEY,PASSWORD"
```
#### Branding
Configure colors, fonts, social links, and email settings.
```bash
para keys config branding [key-id]
```
| Flag | Description |
|---|---|
| `--foreground-color ` | Foreground color (`#RGB` or `#RRGGBB`) |
| `--fg-color ` | Foreground color (alias) |
| `--background-color ` | Background color |
| `--bg-color ` | Background color (alias) |
| `--accent-color ` | Accent color |
| `--font ` | Font: `Arial`, `Courier New`, `Georgia`, `Helvetica`, `Lucida Sans`, `Tahoma`, `Times New Roman`, `Trebuchet MS` |
| `--homepage-url ` | Homepage URL (HTTPS) |
| `--twitter-url ` | Twitter/X profile URL |
| `--linkedin-url ` | LinkedIn company URL |
| `--github-url ` | GitHub URL |
| `--verify-url ` | Verification email URL (HTTPS) |
| `--email-welcome` / `--no-email-welcome` | Toggle welcome email |
| `--email-backup-kit` / `--no-email-backup-kit` | Toggle backup kit email |
#### Setup / Networks
Configure wallet types and native passkey settings.
```bash
para keys config setup [key-id]
```
| Flag | Description |
|---|---|
| `--wallet-types ` | Wallet types — prefix with `~` for optional: `"EVM,~SOLANA,~COSMOS,~STELLAR"` |
| `--cosmos-prefix ` | Cosmos address prefix |
| `--team-id ` | Apple Team ID (10 chars) |
| `--bundle-id ` | Apple bundle identifier |
| `--android-package ` | Android package name |
| `--android-fingerprints ` | Comma-separated SHA256 fingerprints |
#### On/Off Ramps
Configure buy, receive, and withdraw settings.
```bash
para keys config ramps [key-id]
```
| Flag | Description |
|---|---|
| `--buy-enabled` / `--no-buy-enabled` | Toggle buy |
| `--receive-enabled` / `--no-receive-enabled` | Toggle receive |
| `--withdraw-enabled` / `--no-withdraw-enabled` | Toggle withdraw |
| `--providers ` | Comma-separated ordered providers: `RAMP`, `STRIPE`, `MOONPAY` |
| `--ramp-api-key ` | Ramp API key |
| `--default-buy-amount ` | Default buy amount (0.0001–999999) |
| `--default-on-ramp-asset ` | Default on-ramp asset |
| `--default-on-ramp-network ` | Default on-ramp network |
#### Webhooks
Configure webhook endpoints, events, and signing secrets.
```bash
para keys config webhooks [key-id]
```
| Flag | Description |
|---|---|
| `--url ` | Webhook endpoint URL (HTTPS) |
| `--events ` | Comma-separated events: `user.created`, `wallet.created`, `transaction.signed`, `send.broadcasted`, `send.confirmed`, `send.failed`, `wallet.pregen_claimed`, `user.external_wallet_verified` |
| `--enabled` / `--no-enabled` | Toggle the webhook |
| `--status` | Show current webhook configuration |
| `--test` | Send a test webhook |
| `--rotate-secret` | Rotate the webhook signing secret |
| `--delete` | Remove webhook configuration |
| `-y, --yes` | Skip confirmation for destructive operations |
```bash
para keys config webhooks --url "https://api.myapp.com/webhooks" --events "user.created,wallet.created" --enabled
```
See for event type details and signature verification.
---
## Scaffold a Project
### `para create`
Scaffold a new application with Para SDK pre-configured. The interactive wizard walks you through template, network, auth, and wallet selection.
```bash
para create [app-name]
```
| Flag | Description |
|---|---|
| `-t, --template ` | Template: `nextjs` or `expo` |
| `--networks ` | Comma-separated: `evm`, `solana`, `cosmos` |
| `--email` | Enable email authentication |
| `--phone` | Enable phone authentication |
| `--oauth ` | Comma-separated: `GOOGLE`, `APPLE`, `TWITTER`, `DISCORD`, `FACEBOOK`, `FARCASTER` |
| `--wallets ` | Comma-separated wallets: `METAMASK`, `COINBASE`, `WALLETCONNECT`, `RAINBOW`, `ZERION`, `RABBY`, `PHANTOM`, `BACKPACK`, `SOLFLARE`, `GLOW`, `KEPLR`, `LEAP` (case-insensitive) |
| `--bundle-id ` | Bundle identifier (required for Expo) |
| `--package-manager ` | Package manager: `npm`, `yarn`, `pnpm`, `bun` |
| `--skip-install` | Skip dependency installation |
| `-y, --yes` | Accept all defaults (non-interactive) |
#### Interactive Flow
Without flags, `para create` walks you through each step:
1. **App name** — lowercase, numbers, and hyphens only
2. **Template** — Next.js or Expo
3. **Networks** — EVM, Solana, Cosmos, Stellar (Expo is EVM-only)
4. **Auth methods** — Email, Phone/SMS, OAuth
5. **OAuth providers** — Google, Apple, Twitter, Discord, Facebook, Farcaster (Expo supports Google and Apple only)
6. **External wallets** — Varies by network:
- EVM: MetaMask, Coinbase, WalletConnect, Rainbow, Zerion, Rabby
- Solana: Phantom, Backpack, Solflare, Glow
- Cosmos: Keplr, Leap
7. **Expo-specific** — Bundle identifier (e.g., `com.mycompany.myapp`)
#### Non-Interactive Mode
Non-interactive mode activates when an app name is provided along with `--networks` (for Next.js) or `--bundle-id` (for Expo). Email auth is enabled by default if no other auth method is specified.
```bash
para create my-app -t nextjs --networks evm,solana --oauth GOOGLE,APPLE -y
```
#### API Key Connection
If you're authenticated, the CLI offers to connect a Para project after scaffolding. This creates or selects an organization, project, and API key, then writes the key to the app's `.env` file.
#### Package Manager Detection
The CLI detects your package manager automatically:
1. `--package-manager` flag (highest priority)
2. How you invoked the CLI (`npx`, `yarn dlx`, `pnpm dlx`, `bunx`)
3. Lock files in the current directory
4. Falls back to `npm`
#### Example
```bash
$ para create my-dapp
◆ Select a template
│ ● Next.js
│ ○ Expo (React Native)
└
◆ Select networks
│ ◼ EVM (Ethereum, Polygon, Base, ...)
│ ◻ Solana
│ ◻ Cosmos
└
◆ Select authentication methods
│ ◼ Email (recommended)
│ ◻ Phone / SMS
│ ◻ OAuth
└
✔ Created my-dapp from nextjs template
✔ Installed dependencies with npm
Next steps:
cd my-dapp
npm run dev
```
---
## Diagnostics
### `para doctor`
Scan your project for common Para SDK integration issues. Checks configuration, dependencies, setup patterns, and best practices.
```bash
para doctor [path]
```
| Argument | Description | Default |
|---|---|---|
| `[path]` | Project path to diagnose | `.` (current directory) |
| Flag | Description |
|---|---|
| `--category ` | Filter by category: `configuration`, `dependencies`, `setup`, `best-practices` |
| `--severity ` | Minimum severity: `error`, `warning`, `info` |
#### What It Checks
| Check | Category | What It Looks For |
|---|---|---|
| API key env var | Configuration | API key environment variable is set correctly |
| Env var prefix | Configuration | Env var prefix matches framework (`NEXT_PUBLIC_`, `VITE_`, `EXPO_PUBLIC_`) |
| CSS import | Setup | Required Para CSS import is present |
| ParaProvider | Setup | `ParaProvider` component wraps the app |
| QueryClient | Setup | `QueryClient` is set up (required by React SDK) |
| `"use client"` directive | Setup | Next.js files using Para hooks have the directive |
| Version consistency | Dependencies | All `@getpara/*` packages are on the same version |
| Chain dependencies | Dependencies | Required chain packages are installed for selected networks |
| Deprecated packages | Dependencies | No deprecated `@usecapsule/*` packages are present |
#### Example Output
```bash
$ para doctor
Para Doctor — Diagnosing my-app
Project: my-app
Framework: nextjs
SDK: @getpara/react-sdk@2.1.0
Package Manager: npm
✔ API key environment variable configured
✔ Environment variable prefix matches framework
✔ Para CSS import found
✔ ParaProvider component detected
✔ QueryClient setup detected
✘ Missing "use client" directive in src/app/providers.tsx
⚠ @getpara/evm-wallet-connectors is on 2.0.9, expected 2.1.0
✔ Chain dependencies installed
✔ No deprecated packages found
7 passed · 1 failed · 1 warning
```
#### Filtering
Run only dependency checks:
```bash
para doctor --category dependencies
```
Show only errors (skip warnings and info):
```bash
para doctor --severity error
```
#### JSON Output
Use `--json` for CI/CD pipelines:
```bash
para doctor --json
```
```json
{
"projectInfo": {
"framework": "nextjs",
"sdkType": "@getpara/react-sdk",
"sdkVersion": "2.1.0",
"packageManager": "npm"
},
"results": [
{
"name": "use-client-directive",
"status": "fail",
"severity": "error",
"category": "setup",
"message": "Missing \"use client\" directive",
"recommendation": "Add \"use client\" to the top of src/app/providers.tsx"
}
],
"summary": {
"total": 9,
"passed": 7,
"failed": 1,
"warnings": 1
}
}
```
The command exits with code `1` if any error-severity checks fail, making it suitable for CI gates.
# Installation & Setup
Source: https://docs.getpara.com/v2/cli/installation
import { Card } from "/snippets/v2/components/ui/card.mdx";
## Install
```bash npm
npm install -g @getpara/cli
```
```bash yarn
yarn global add @getpara/cli
```
```bash pnpm
pnpm add -g @getpara/cli
```
Verify the installation:
```bash
para --version
```
You can also run the CLI without installing globally using `npx @getpara/cli@latest`, `yarn dlx @getpara/cli@latest`, or `pnpm dlx @getpara/cli@latest`.
## Authenticate
Log in with your Para developer account:
```bash
para login
```
This opens your browser for OAuth authentication. Once you approve, the CLI stores your session locally at `~/.config/para/credentials.json` with restricted file permissions (`0600`).
For headless or CI environments where a browser isn't available:
```bash
para login --no-browser
```
This prints a URL you can open on any device to complete authentication.
After login, the CLI automatically selects your first organization and project. Use `para whoami` to verify:
```bash
para whoami
```
## Configuration
The CLI resolves configuration from multiple sources in this order (highest priority first):
1. **CLI flags** (`-e`, `--org`, `--project`)
2. **Environment variables** (`PARA_ENVIRONMENT`, `PARA_ORG_ID`, `PARA_PROJECT_ID`)
3. **Project config** (`.pararc` in the current directory)
4. **Global config** (`~/.config/para/config.json`)
5. **Defaults** (key environment: `beta`)
### Project-Level Config
Pin your organization, project, and environment to a directory with `para init`:
```bash
para init
```
This creates a `.pararc` file in the current directory. Team members who clone the repo get the same defaults without manual setup. See the [config commands](/v2/cli/commands#configuration) page for details.
### Global Config
Set defaults that apply across all projects:
```bash
para config set defaultEnvironment beta
para config set defaultOrganizationId your-org-id
para config set defaultProjectId your-project-id
```
## Key Environment
Each Para project has two API key tiers: **beta** (for development and testing) and **prod** (for production). The `-e` flag selects which tier to operate on:
```bash
para keys list -e beta # List beta keys (default)
para keys list -e prod # List prod keys
```
| Key Environment | Description |
|---|---|
| `beta` | Development and testing keys |
| `prod` | Production keys |
The default is `beta`. Most commands auto-resolve the correct API key from your active project and key environment, so you rarely need to pass a key ID explicitly.
## Global Flags
These flags work with every command:
| Flag | Description |
|---|---|
| `-e, --environment ` | Key environment: `beta` or `prod` (default: `beta`) |
| `--json` | Output as JSON for scripting and CI/CD |
| `-q, --quiet` | Suppress non-essential output |
| `--no-input` | Disable interactive prompts (auto-detected in CI) |
| `--org ` | Override the active organization |
| `--project ` | Override the active project |
## Next Steps
# Para CLI
Source: https://docs.getpara.com/v2/cli/overview
import { Card } from "/snippets/v2/components/ui/card.mdx";
The Para CLI (`@getpara/cli`) lets you manage your Para integration without leaving the terminal. Create and configure API keys, switch between organizations and projects, scaffold new apps, and diagnose SDK issues — all from a single `para` command.
## When to Use
- **Manage API keys** without opening the Developer Portal — create, rotate, archive, and configure settings like auth methods, webhooks, and branding.
- **Scaffold new projects** with `para create` — pick a template, networks, and auth methods, then get a working app in seconds.
- **Automate workflows** in CI/CD pipelines with `--json` and `--no-input` flags for machine-readable, non-interactive output.
- **Run diagnostics** with `para doctor` to catch common SDK integration issues before they hit production.
- **Switch context** between organizations, projects, and environments without losing your place.
## Next Steps
# How Para Works
Source: https://docs.getpara.com/v2/concepts/architecture
Para makes it easy to add secure, non-custodial wallets to bring onchain capabilities to your applications and users. Users create wallets with a familiar login experience (email, social, or passkey) and get a wallet that works across apps, chains, and platforms without ever managing private keys or seed phrases.
**Para wallets are non-custodial.** The private key is never assembled in one place. Neither Para nor the integrating application can access users' full private keys. See [Security & Trust Model](/v2/concepts/security) for details.
## Custody Model and Regulatory Classification
Para uses a **2-of-2 Multi-Party Computation (MPC)** architecture where the private key is split between the user's device and Para's hardware security modules. Because the full key is never held by any single party:
- **Classification:** Para wallets are **self-custodial / non-custodial**. Your application does not take custody of user funds.
- **Faster global expansion:** Non-custodial architecture simplifies regulatory requirements, enabling faster launches in new markets without the licensing overhead associated with custodial wallet models.
- **Audit posture:** SOC 2 Type II compliant. Para regularly undergoes system-wide audits and penetration tests. Contact the Para team at **security@getpara.com** to request audit details.
- **Data handling:** All key material is encrypted at rest and in transit. User shares never leave the user's device unencrypted.
For detailed security mechanisms and audit information, see [Security & Trust Model](/v2/concepts/security).
## How the System Fits Together
Para's architecture has four pillars that work together to provide secure, portable, and controllable wallets:
2-of-2 MPC key splitting, hardware secure enclaves, passkey-based authentication, and phishing-resistant signing.
Policy-based rules that control what each application can do with a user's wallet. Server-side enforcement, default deny.
Recovery secrets, backup devices, and key rotation ensure users never lose access to their assets.
One wallet across your entire ecosystem. Users onboard once and use the same wallet in every connected app.
## Integration Patterns
Para supports multiple integration approaches depending on the product's needs:
The fastest path to integration. Use Para's pre-built modal component for wallet creation, login, and signing. Fully customizable styling and copy.
Build a fully custom wallet experience with Para's headless SDK. Full control over every screen and interaction while Para handles the cryptography.
Pre-generate wallets for users, sign transactions server-side, or build automated workflows. Ideal for onboarding users before they visit the app.
Para also works with **[ERC-4337 (Account Abstraction)](/v2/general/account-abstraction)** out of the box, making it easy to combine MPC-based key management with smart account capabilities like gas sponsorship, batched transactions, and custom validation logic.
## Supported Platforms and Chains
### Frameworks
Para provides SDKs for all major platforms:
- **Web:** [React](/v2/react/setup/vite), [Next.js](/v2/react/setup/nextjs), [Vue](/v2/vue/setup/overview), [Svelte](/v2/svelte/setup/overview), vanilla JavaScript
- **Mobile:** [React Native](/v2/react-native/setup/react-native), [Flutter](/v2/flutter/setup), [Swift](/v2/swift/setup)
- **Server:** [Node.js](/v2/server/setup) and [REST API](/v2/rest/overview)
- **CLI:** [Command-line SDK](/v2/cli/overview) for scripting and automation
See the [quickstart guides](/v2/introduction/welcome) for framework-specific setup instructions.
### Blockchain Networks
Para is designed to be blockchain-agnostic with native support for:
| Network | Coverage |
|---------|----------|
| **EVM chains** | Ethereum, Polygon, Arbitrum, Optimism, Base, and all EVM-compatible networks |
| **Solana** | Native support |
| **Stellar** | Native support |
| **Cosmos ecosystem** | Native support via CosmJS |
Developers can integrate with popular libraries like ethers.js, viem, wagmi, and CosmJS. For the full list of supported chains, see the [chain support documentation](/v2/introduction/chain-support).
## Architecture FAQs
Yes. Para is designed to work with [ERC-4337 (Account Abstraction)](/v2/general/account-abstraction) out of the box. This allows developers to leverage Para's MPC security while taking advantage of AA capabilities like gas sponsorship, batched transactions, and custom validation logic.
Para offers SDKs for React, Next.js, Vue, Svelte, and vanilla JavaScript on the web; React Native, Flutter, and Swift for mobile; Node.js and REST API for server-side; and a CLI SDK for scripting and automation.
Para is blockchain-agnostic with native support for all EVM-compatible chains, Solana, Stellar, and the Cosmos ecosystem. Developers can integrate using popular libraries like ethers.js, viem, wagmi, CosmJS, and the Stellar SDK. For the full list, see the [chain support documentation](/v2/introduction/chain-support).
# Audits & Compliance
Source: https://docs.getpara.com/v2/concepts/compliance
import { Link } from '/snippets/v2/components/ui/link.mdx';
This page is for **CISOs, compliance officers, and security engineers** evaluating Para's security posture. It covers custody classification, regulatory implications, audit history, data handling, and the controls that underpin Para's trust model.
## Custody Classification
Para uses a **2-of-2 Multi-Party Computation (MPC)** architecture where the private key is split between the user's device and Para's cloud hardware security modules (HSMs). The full key is never held by any single party (not Para, not the integrating application, not the user's device alone).
| Property | Details |
|----------|---------|
| **Custody model** | Self-custodial / non-custodial. The integrating application does not take custody of user funds |
| **Key assembly** | The full private key is never assembled at any point: not during generation, signing, or recovery |
| **Regulatory posture** | Non-custodial architecture simplifies regulatory requirements, enabling faster launches in new markets without the licensing overhead associated with custodial wallet models |
| **Para's role** | Infrastructure provider. Para holds one key share in HSMs but cannot produce valid signatures without the user's share |
## Regulatory Posture
| Area | Status |
|------|--------|
| **SOC 2 Type II** | Compliant |
| **Penetration testing** | Para regularly undergoes system-wide penetration tests |
| **GDPR / data privacy** | Para does not collect or store any personally identifying information unless used as a login method (e.g., email address or phone number) |
| **Insurance** | Self-Serve / BYO |
To request compliance documentation or audit reports, contact the Para team at **security@getpara.com**.
## Security Audits
Para is SOC 2 Type II compliant and regularly undergoes system-wide audits and penetration tests covering its cryptographic implementations, infrastructure, and API surface.
### Audit Scope
Audits cover the following areas:
| Area | What is reviewed |
|------|-----------------|
| **MPC implementation** | DKLS19 algorithm correctness, distributed key generation, signing ceremony integrity |
| **Cryptographic primitives** | Key derivation, elliptic curve operations (secp256k1 / secp256r1), random number generation |
| **Infrastructure** | Cloud HSM configuration, network segmentation, access controls, secrets management |
| **API security** | Authentication flows, session management, rate limiting, input validation |
| **Recovery flows** | Recovery secret generation, key rotation process, multi-factor verification, 48-hour delay enforcement |
## Data Handling
| Data type | How it's handled |
|-----------|-----------------|
| **User Share (MPC key)** | Stored on the user's device only. Never transmitted unencrypted. Never accessible to Para or the integrating application |
| **Para Share (MPC key)** | Stored in Para's cloud HSMs. Cannot produce a valid signature alone |
| **Recovery secret** | Generated client-side and shared with the user. Para does not have access to it |
| **User identity (email)** | Used for wallet association and recovery. Stored encrypted at rest |
| **Session data** | Short-lived (90-minute default). Used for signing authorization |
| **Transaction data** | Evaluated against permissions policy server-side. Not stored after signing |
## Encryption
| Layer | Implementation |
|-------|---------------|
| **In transit** | TLS for all network communications. End-to-end encryption for sensitive data between client and Para servers |
| **At rest** | All stored user data and key material is encrypted at rest |
| **Key material** | Para Share stored in cloud HSMs with hardware-level isolation. User Share protected by device secure enclave and passkey |
## Access Control and Permissions Enforcement
Para enforces a [policy-based permissions system](/v2/concepts/permissions) that provides an auditable control layer between applications and user wallets:
- Every transaction is evaluated against an approved policy **server-side** before it reaches the MPC signing ceremony
- Policies are **immutable**; changes require publishing a new version
- **`DENY` rules take precedence** over `ALLOW` rules. Default posture is deny-all
- Users explicitly consent to permission scopes during onboarding. No silent escalation
- Each application gets its own policy per API key. Cross-app access is scoped independently
This creates a clear audit trail: what was requested, what policy was in effect, and whether the action was allowed or denied.
## Non-Custodial Risk Model
For compliance teams evaluating what risks Para's architecture eliminates vs. what remains in the integrating team's scope:
| Risk | Para handles | App handles |
|------|-------------|-------------------|
| **Private key storage** | Keys are split via MPC. Para holds one share in HSMs, users hold the other on-device | N/A (neither party holds the full key) |
| **Key compromise (single point)** | Eliminated. Compromising one share reveals nothing useful | Ensure applications don't introduce vulnerabilities that expose the user's session |
| **Phishing** | Passkey-based auth is origin-bound and cannot be replayed on fake sites | Educate users on general phishing awareness |
| **Unauthorized transactions** | Server-side permissions enforcement blocks out-of-policy actions | Define appropriate permission policies for your use case |
| **Device loss** | Recovery via recovery secret, backup devices, and Para Portal | Encourage users to set up 2FA and backup devices |
| **Censorship / platform risk** | Users can export their Para Share and sign independently | N/A (users retain self-sovereignty) |
| **Insider threat (Para)** | Para cannot produce valid signatures with only its share | Monitor your own internal access to API keys and [Developer Portal](https://developer.getpara.com) |
| **Regulatory classification** | Non-custodial architecture avoids wallet-layer MSB/MTL obligations | Consult your own legal team for your specific jurisdiction and use case |
## Compliance FAQs
No. Para holds one share of a 2-of-2 MPC key pair. A valid signature requires both shares; Para's share alone cannot produce a signature or move funds. Para has no access to the user's recovery secret.
An attacker who compromises Para's infrastructure gains access to Para Shares stored in HSMs. However, these shares alone cannot produce valid signatures. The attacker would also need the user's share (stored on-device, protected by passkey/biometrics) to sign any transaction.
Users who have exported their Para Share (via Para Connect or the Para Backup Kit) can sign transactions independently without Para's servers. Users who have not exported can wait for service restoration; their funds remain safe and inaccessible to any party.
Yes. Para is SOC 2 Type II compliant.
Yes. Contact the Para team at **security@getpara.com** to request audit reports and details.
Full overview of Para's security architecture
MPC implementation, DKG, hardware secure enclaves
# Key Management
Source: https://docs.getpara.com/v2/concepts/key-management
import { Link } from '/snippets/v2/components/ui/link.mdx';
Para uses a **2-of-2 Multi-Party Computation (MPC)** system. When a user creates a wallet, the private key is generated in a distributed process; it is never assembled in one place at any point in its lifecycle.
## Key Shares
The two key shares are:
1. **User Share**: stored on the user's device, protected by their passkey and biometrics. Acts like a hot wallet for immediate signing.
2. **Para Share**: stored in Para's cloud hardware security modules (HSMs). Provides a secure off-device backup and enables recovery.
To sign a transaction, both shares participate in a cryptographic signing ceremony that produces a valid signature **without ever reconstructing the full private key**. Neither share alone can produce a signature.
Neither Para nor the integrating application ever sees the full private key. This is true during key generation, signing, and recovery.
## MPC Implementation
Para uses the **DKLS19 MPC algorithm**, leveraging an for core functions including distributed key generation (DKG), signing ceremonies, and non-custodial wallet generation.
**Distributed Key Generation (DKG):** When a user creates a wallet, Para initiates a DKG process that generates the User Share and Para Share without ever assembling the full private key. This ensures no single party has access to the complete key, even during generation.
**Signature structure:** Para uses the EIP-712 transaction signature interface. For Ethereum-based integrations, Para publishes an EIP-1193 Provider, most commonly used via Wagmi Connectors.
## Hardware Secure Enclaves and Passkeys
Modern devices include hardware secure enclaves: dedicated, isolated processors for storing and protecting sensitive data. These enclaves support the **secp256r1** elliptic curve, while most blockchains use **secp256k1**.
Para bridges this gap by generating a passkey (secp256r1) stored in the device's secure enclave. This passkey authorizes access to the Para Share, which then participates in a secp256k1 signing ceremony. The result: users authenticate with device-native biometrics (Face ID, fingerprint, etc.) while the system produces blockchain-compatible signatures.
| Property | Details |
|----------|---------|
| **Hardware-level protection** | Authentication key lives in the device's secure enclave, isolated from the OS |
| **Biometric confirmation** | Every sensitive operation requires physical presence via Face ID, fingerprint, or device PIN |
| **WebAuthn compliance** | Passkeys follow the WebAuthn standard for phishing-resistant authentication |
## MPC vs Multi-sig
| | MPC | Multi-sig |
|---|-----|-----------|
| **How it works** | Splits a single private key across multiple parties. Parties jointly compute a signature without ever reconstructing the key | Requires multiple separate private keys to approve a transaction. Each key is complete on its own |
| **Key exposure** | The full key never exists in memory at any point | Each individual key is a complete private key that could be compromised independently |
| **On-chain footprint** | Produces a standard single signature, indistinguishable from a regular wallet | Requires a smart contract or multi-sig scheme visible on-chain |
**These approaches are complementary, not competing.** Para can serve as signer infrastructure on a multi-sig setup using [Safe](https://safe.global/) or as a signer for [ERC-4337 smart accounts](/v2/general/account-abstraction). This combination offers MPC-level key security for each signer while still benefiting from multi-sig governance or smart account features like gas sponsorship and batched transactions.
## Key Management FAQs
Para uses the DKLS19 MPC algorithm and leverages an [open-source implementation](https://github.com/taurusgroup/multi-party-sig) for core functions like distributed key generation, signing ceremonies, and non-custodial wallet generation.
Para uses the EIP-712 transaction signature interface. Para also publishes an EIP-1193 Provider, most commonly used via Wagmi Connectors, ensuring compatibility with a wide range of Ethereum-based applications and tools.
The biometric key is stored on-device in a secure enclave. For Ethereum-based transactions, Para uses secp256k1 curve signatures. The secure enclave supports the secp256r1 curve, so Para generates a secp256r1 key that authorizes a secp256k1 curve signature for ECDSA signatures, bridging this compatibility gap securely.
Multi-sigs require separate private keys to approve a transaction. MPC splits a single private key across multiple parties, and parties jointly sign without ever reconstructing the key. Para can also serve as signer infrastructure on a multi-sig setup using [Safe](https://safe.global/) or [ERC-4337 smart accounts](/v2/general/account-abstraction).
Full security overview including authentication, encryption, and audits
How key shares are recovered after device loss
# Permissions Reference
Source: https://docs.getpara.com/v2/concepts/permissions-reference
This is a technical reference for engineers implementing permissions. For a high-level overview of how permissions work, see [Permissions & Access Control](/v2/concepts/permissions).
## Data Model
Permissions use a four-level hierarchy: **Policy → Scopes → Permission Templates → Conditions**.
```
Policy
└── Scope (user-facing consent group)
└── Permission Template (the rule)
├── type — what action (sign, transfer, smart contract call, smart contract deploy)
├── effect — ALLOW or DENY
├── chainId — which chain
├── smartContractAddress — (optional) specific contract address
├── smartContractFunction — (optional) specific function
└── Condition[] — (optional) additional restrictions
├── resource — what to inspect (value, address, etc.)
├── comparator — how to compare (equals, less than, etc.)
└── reference — the value to compare against
```
A **policy** belongs to a single API key. It contains **scopes**, which group related rules for user consent. Each scope contains one or more **permission templates** that define the actual rules. Templates can optionally have **conditions** that add further constraints.
### Policy
A **policy** defines the full set of actions an application may ever request from a user's wallet. If something is not included in the policy, it cannot happen.
| Property | Details |
|----------|---------|
| **App-specific** | Each API key has its own policy |
| **Immutable** | Changes require creating a new policy version |
### Scopes
Policies are broken down into **scopes**, which are the user-facing consent items. Each scope appears as a consent checkbox during onboarding or login.
Each scope has:
- A **name** and **description**, shown to the user in plain language
- A **required** flag: required scopes must be accepted to use the app; optional scopes can be declined
- One or more **permission templates**, the actual rules behind this scope
Scopes can also be **nested** (parent-child hierarchy), allowing developers to organize complex permission sets into logical groups.
## Permission Types
Each permission template specifies a **type** that determines which wallet actions it governs:
| Type | Description | When It Applies |
|------|-------------|-----------------|
| `SIGN_MESSAGE` | Sign arbitrary messages (personal_sign, signTypedData) | App requests a message signature |
| `TRANSFER` | Send native tokens (ETH, SOL, etc.) to an address | Transaction has a `to` address with no contract calldata |
| `CALL_CONTRACT` | Invoke a function on a deployed smart contract | Transaction includes calldata targeting a contract |
| `DEPLOY_CONTRACT` | Deploy a new smart contract | Transaction has no `to` address (contract creation) |
For `CALL_CONTRACT` permissions, developers can further restrict by `smartContractAddress` and `smartContractFunction`. This enables rules like "only allow calling the `swap` function on a specific DEX router contract."
## Effects: ALLOW vs DENY
Each permission template has an **effect**: either `ALLOW` or `DENY`.
When a transaction is submitted, Para evaluates all matching permission templates:
1. If **any** matching permission evaluates to `DENY` → the transaction is **blocked**
2. If **any** matching permission evaluates to `ALLOW` (and none evaluate to `DENY`) → the transaction is **allowed**
3. If **no** permissions match → the transaction is **blocked** (default-deny)
`DENY` always takes precedence over `ALLOW`. This means developers can create broad `ALLOW` rules and then add narrow `DENY` exceptions for specific cases.
## Conditions
Conditions add constraints to permission templates. They enable going beyond "allow transfers" to "allow transfers under 1 ETH to specific addresses."
Each permission template can have zero or more conditions. **All** conditions on a single permission must evaluate to true for that permission's effect to apply (logical AND). If any condition is false, the permission does not match and is skipped during evaluation.
### Resources
The **resource** field specifies what part of the transaction to inspect:
| Resource | Description | Applies To |
|----------|-------------|------------|
| `VALUE` | Transaction value in wei (as a string) | `TRANSFER`, `CALL_CONTRACT` |
| `TO_ADDRESS` | Destination address of the transaction | `TRANSFER`, `CALL_CONTRACT` |
| `MESSAGE` | The message content being signed | `SIGN_MESSAGE` |
| `ARGUMENTS` | Smart contract function arguments (by index, e.g., the first argument) | `CALL_CONTRACT` |
The `ARGUMENTS` resource allows inspecting specific parameters of a smart contract function call. For example, in an ERC-20 `transfer(address, uint256)` call, the first argument is the recipient address and the second is the amount.
### Comparators
The **comparator** field determines how the resource value is compared to the reference:
| Comparator | Description |
|------------|-------------|
| `EQUALS` | Exact match |
| `NOT_EQUALS` | Does not match |
| `GREATER_THAN` | Strictly greater than |
| `GREATER_THAN_OR_EQUALS` | Greater than or equal to |
| `LESS_THAN` | Strictly less than |
| `LESS_THAN_OR_EQUALS` | Less than or equal to |
| `CONTAINED_IN` | Value is in a provided list |
| `NOT_CONTAINED_IN` | Value is not in a provided list |
The **reference** field holds the value to compare against. It can be a string, number, or array (for `CONTAINED_IN` / `NOT_CONTAINED_IN`).
### Condition Type
All conditions currently use the `STATIC` type, meaning they are evaluated against the transaction data at the time of the request.
## Chain-Specific Scoping
Every permission template includes a `chainId` field. When a transaction is submitted, Para checks that the permission's chain matches the transaction's chain. This allows creating different rules for different chains. For example, allowing transfers on Ethereum mainnet but restricting them on other chains.
An empty `chainId` (`""`) means the permission applies to all chains.
## Current Scope
| Scope | Details |
|-------|---------|
| **EVM condition evaluation** | The detailed condition system (`VALUE`, `TO_ADDRESS`, `ARGUMENTS`) is implemented for EVM chains. Solana, Stellar, and Cosmos transactions are evaluated at the permission type level (allow/deny by type) but do not yet support fine-grained conditions |
| **Static conditions** | Conditions are evaluated against transaction data at request time. All condition values are set at policy creation time and cannot be customized by end users |
## Full Policy Schema
Here is the complete JSON structure of a policy with two scopes:
```json
{
"scopes": [
{
"name": "Basic Wallet Access",
"description": "Sign messages with your wallet",
"required": true,
"permissions": [
{
"type": "SIGN_MESSAGE",
"effect": "ALLOW",
"chainId": "",
"conditions": []
}
]
},
{
"name": "Token Transfers",
"description": "Send up to 1 ETH on Ethereum mainnet",
"required": false,
"permissions": [
{
"type": "TRANSFER",
"effect": "ALLOW",
"chainId": "1",
"conditions": [
{
"type": "STATIC",
"resource": "VALUE",
"comparator": "LESS_THAN_OR_EQUALS",
"reference": "1000000000000000000"
}
]
}
]
}
]
}
```
The `VALUE` resource uses wei denomination. 1 ETH = 1,000,000,000,000,000,000 wei (10^18).
## Examples
A simple permission that lets the app sign messages without restrictions.
```json
{
"type": "SIGN_MESSAGE",
"effect": "ALLOW",
"chainId": "",
"conditions": []
}
```
An empty `chainId` means the permission applies to all chains. No conditions means no additional restrictions.
Restrict transfers to a maximum value on a specific chain.
```json
{
"type": "TRANSFER",
"effect": "ALLOW",
"chainId": "1",
"conditions": [
{
"type": "STATIC",
"resource": "VALUE",
"comparator": "LESS_THAN_OR_EQUALS",
"reference": "1000000000000000000"
}
]
}
```
This permission only matches transactions on Ethereum mainnet (chain ID `1`) where the value is at most 1 ETH.
Restrict interactions to a single function on a specific smart contract. This example allows calling the `swap` function on a DEX router, but only when the first argument (the token address) is in an approved list.
```json
{
"type": "CALL_CONTRACT",
"effect": "ALLOW",
"chainId": "1",
"smartContractAddress": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
"smartContractFunction": "swap",
"conditions": [
{
"type": "STATIC",
"resource": "ARGUMENTS",
"comparator": "CONTAINED_IN",
"reference": [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"0xdAC17F958D2ee523a2206206994597C13D831ec7"
]
}
]
}
```
Use a `DENY` rule to block specific recipients while keeping a broad `ALLOW` rule for everything else. `DENY` takes precedence.
```json
[
{
"type": "TRANSFER",
"effect": "ALLOW",
"chainId": "1",
"conditions": []
},
{
"type": "TRANSFER",
"effect": "DENY",
"chainId": "1",
"conditions": [
{
"type": "STATIC",
"resource": "TO_ADDRESS",
"comparator": "EQUALS",
"reference": "0x000000000000000000000000000000000000dEaD"
}
]
}
]
```
The first permission allows all transfers on Ethereum mainnet. The second blocks transfers to a specific address. Because `DENY` always wins, the blocked address cannot receive transfers even though the broad `ALLOW` rule would otherwise match.
## Ready to Get Started?
Configure permissions policies in the Para Permissions Builder.
# Permissions & Access Control
Source: https://docs.getpara.com/v2/concepts/permissions
Traditional embedded wallets force a tradeoff: either prompt users to approve every single action, or give applications blanket signing access with no guardrails. Neither works when wallets are shared across applications or when compliance teams need to audit what an app can do.
Para's **policy-based permissions system** controls what each application can do with a user's wallet. Policies are defined per API key, enforced server-side on every transaction, and presented to users as plain-language consent items.
- **Declared up front:** every capability needed is specified in a policy before any user interaction
- **User consent is explicit:** permissions are grouped into readable scopes that users review and approve during onboarding
- **Server-side enforcement:** Para evaluates every transaction against the approved policy before it reaches the MPC signing ceremony. This cannot be bypassed client-side
- **Default deny:** any action not explicitly allowed in the policy is blocked
## How It Works
Each policy declares what your app can do: sign messages, transfer tokens, call specific smart contracts, or deploy contracts. Rules can include constraints like value caps, address allowlists, and per-chain scoping. Policies are configured in the [Para Permissions Builder](https://permissions.getpara.com) and are immutable once published; changes require a new version.
Permissions are grouped into user-facing consent items called scopes. Each scope has a plain-language name and description (for example, "Automated token swaps" rather than a raw contract address). Scopes can be marked as required or optional.
During onboarding or login, users see the scopes and choose which to approve. Required scopes must be accepted to use the app. Optional scopes can be declined without losing access.
When the application requests a transaction, Para evaluates it against the user's approved permissions. If the action falls outside the policy (wrong contract, value too high, unapproved chain), it's rejected before signing.
## Permission Configuration Options
Allow or deny the ability to sign arbitrary messages. Useful for login flows, attestations, and off-chain signatures.
Control who can send native tokens and how much. Set per-chain rules, cap transfer amounts, or restrict destination addresses.
Lock interactions to specific contracts and functions. For example, allow calling `swap` on a DEX router but nothing else.
Allow or block the ability to deploy new smart contracts entirely.
**`DENY` always overrides `ALLOW`.** Developers can create broad allow rules and add narrow deny exceptions for specific cases. If no rule matches an action, it's blocked by default.
## The User Experience
Users see permissions in plain language, not raw technical details. Here's what the consent flow looks like:
1. During onboarding, users see the scopes defined in the application's policy
2. Required scopes are pre-checked and must be accepted; optional scopes can be toggled
3. Users approve or decline each optional scope
4. Para stores consent and enforces only the approved permissions
5. Any out-of-policy action is rejected before it reaches signing
There is no silent permission creep; policy changes require a new policy version.
## Cross-App Permissions
Permissions become especially important with [universal wallets](/v2/concepts/universal-embedded-wallets), where a single wallet is used across multiple applications. Each app defines its own policy, and users approve permissions separately for each app. This means:
- A DeFi app can request swap permissions without gaining access to sign arbitrary messages
- A portfolio tracker can request read-only message signing without being able to transfer funds
- Users maintain visibility and control over what each app can do
Complete technical reference: full policy schema, permission types, condition system, comparators, and worked examples.
## Get Started
Permissions policies are configured in the [Para Permissions Builder](https://permissions.getpara.com). For help designing a policy that fits a specific use case, reach out at **hello@getpara.com**.
Implement client-side transaction confirmation dialogs
How wallets work across multiple apps
Para's security and trust model
# Security
Source: https://docs.getpara.com/v2/concepts/security
import { Link } from '/snippets/v2/components/ui/link.mdx';
Para's security model is built on a simple principle: **no single party (not Para, not the integrating application, not even the user's device alone) ever holds a complete private key.** This eliminates the most common attack vectors in wallet infrastructure and provides a foundation that compliance and security teams can trust.
## Security at a Glance
| Property | What it means |
|----------|---------------|
| **Non-custodial** | Para never has access to users' full private keys |
| **No single point of failure** | Keys are split across the user's device and Para's hardware security modules. Compromising one reveals nothing useful |
| **Phishing-resistant** | Passkey-based authentication means there are no passwords to steal, no seed phrases to phish |
| **Censorship-resistant** | Users can export their key share and sign transactions independently if Para is ever unavailable |
## Why This Matters
**For product teams:** Non-custodial wallets unlock capabilities that custodial models can't offer:
| Benefit | Why it matters |
|---------|---------------|
| **Day-1 asset access** | Users hold their own keys from the moment of wallet creation, with no waiting for custodial onboarding or KYC approval before they can receive and use assets |
| **Global expansion** | Non-custodial infrastructure avoids per-jurisdiction money transmitter licensing, enabling faster launches in new markets without wallet-layer regulatory blockers |
| **No key management burden** | Para handles MPC, HSMs, and signing ceremonies so our partners can confidently deliver new product features |
| **Seamless UX** | Biometric login, no seed phrases, instant wallet creation: users get a familiar experience while your app gets enterprise-grade security underneath |
| **User trust and retention** | Users own their assets outright. There's no platform risk: even if your app goes offline, users retain full control of their wallets |
**For compliance and risk teams:** Para's non-custodial architecture means the integrating application can move faster, minimizing licensing obligations that come with custodial wallet models. Every transaction is checked against an approved [permissions policy](/v2/concepts/permissions) before signing, creating an auditable enforcement layer.
**For security teams:** Threat models are significantly reduced. Integrating applications don't store private keys, so a breach of their infrastructure doesn't expose user assets. Para's MPC signing ensures that even a compromise of Para's systems alone cannot produce valid signatures.
## How Keys Are Protected
Para uses a **2-of-2 Multi-Party Computation (MPC)** system where the private key is split into two shares (one on the user's device, one in Para's cloud HSMs). To sign a transaction, both shares participate in a cryptographic ceremony that produces a valid signature **without ever reconstructing the full private key**.
Neither Para nor the integrating application ever sees the full key. This is true during key generation, signing, and recovery.
Deep dive into MPC implementation, distributed key generation, hardware secure enclaves, passkeys, and how MPC compares to multi-sig.
## Authentication
Para supports multiple authentication methods to fit different user bases and product requirements.
### Email and Social Logins
| Method | Details |
|--------|---------|
| **Email** | Passwordless login via email verification |
| **Phone** | SMS-based verification |
| **Google** | OAuth social login |
| **Apple** | OAuth social login |
| **Twitter/X** | OAuth social login |
| **Discord** | OAuth social login |
| **Facebook** | OAuth social login |
### Additional Account Protection Options
| Method | Details |
|--------|---------|
| **Passkey** | Built on the WebAuthn standard. Phishing-resistant, origin-bound, biometric verification via Face ID, fingerprint, or device PIN |
| **PIN** | Numeric PIN set by the user |
| **Password** | Traditional password-based login |
These options can be layered on top of authentication to provide additional security when authorizing transactions.
### Session Management
Para uses sessions as a security measure when signing transactions. The default session length is **90 minutes**. Sessions can be refreshed, and developers can configure session length in the [Security section of the Developer Portal](https://developer.getpara.com).
## Encryption and Secure Communication
All communication between the user's device, Para's servers, and connected applications is encrypted:
- **TLS** for all network communications
- **End-to-end encryption** for sensitive data in transit
- **Encryption at rest** for all stored user data
## Censorship Resistance
Para's architecture ensures users maintain control over their assets even if Para's services are unavailable:
- Users can **export their Para Share** at any time via
- With both shares, users can **sign transactions independently** without Para's servers
- The provides a censorship-resistant fallback for full self-sovereignty
This design ensures that Para cannot censor transactions and that users are never locked out of their own assets.
## Backup and Recovery
Device loss, theft, and hardware failure are inevitable. Para's recovery system ensures **users can always regain access to their wallet** without the need for application-level recovery flow build-outs. All recovery flows are handled through the Para Portal, a managed web experience that walks users through verification and key restoration.
| Mechanism | How it works |
|-----------|-------------|
| **Recovery secret** | A unique secret generated during wallet setup, stored by the user. Not related to the MPC key shares. Used solely to restore wallet access. Para never has access to it |
| **Para Backup Kit** | A copy of the Para Share given to the user at setup, providing censorship resistance and protection against downtime |
| **Backup devices** | Users can register secondary devices (laptop, smartwatch) during setup. If the primary device is lost, they log in from a backup and add new devices |
| **Key rotation** | After any recovery event, Para prompts a full key rotation, generating entirely new key shares and invalidating the old ones |
| **Multi-factor verification** | Recovery requires the recovery secret plus optional 2FA (TOTP), protecting against impersonation |
### Security Measures
| Measure | How it protects users |
|---------|----------------------|
| **Multi-factor verification** | Recovery requires the recovery secret plus optional 2FA (TOTP). No single factor alone can restore wallet access, protecting against social engineering and impersonation |
| **Key rotation** | After every recovery event, Para prompts a full key rotation, generating entirely new key shares and invalidating the old ones. Even if old keys were compromised, they become useless |
| **Backup devices** | Users can register multiple backup devices (laptop, smartwatch, tablet) during setup. If the primary device is lost, they log in from a backup device without needing to go through the full recovery flow |
| **48-hour recovery delay** | Recovery attempts initiated via the Para Portal include a waiting period, giving users time to detect and cancel unauthorized recovery attempts |
| **Para Portal managed flow** | All recovery is handled through the Para Portal, removing the need to build or manage recovery UI. This reduces implementation surface area and ensures consistent security across all integrating apps |
### Best Practices for Users
Store the recovery secret in a secure, offline location. Never share this secret with anyone, including Para.
Activate two-factor authentication for an additional layer of security during the recovery process.
Add multiple backup devices when possible to increase recovery options.
Periodically verify the ability to access the account from backup devices to ensure they remain functional.
## Audits and Compliance
Para is SOC 2 Type II compliant and regularly undergoes system-wide audits and penetration tests covering MPC implementation, infrastructure, API security, and recovery flows.
Custody classification, regulatory posture, audit history, data handling, encryption details, and risk model, for CISOs and compliance teams.
## Security FAQs
Para uses sessions as a security measure when signing transactions. By default, session length is 90 minutes. Developers can implement [session refresh logic](/v2/react/guides/sessions) in their applications to maintain longer sessions if required.
As long as the Cloud Share sent during onboarding is not deleted by the user, they can always refresh keys, export, or sign transactions independently. This design ensures that Para cannot censor transactions. See our blog post on [censorship resistance](https://blog.getpara.com/censorship-resistance-why-its-critical-and-how-were-tackling-it/) for more details.
Para supports sign-in via Google, Apple, Twitter/X, Discord, and Facebook. This allows developers to offer a range of authentication options to their users, increasing adoption and ease of use.
Para implements a robust recovery mechanism involving a recovery secret generated during wallet setup, optional backup devices, two-factor authentication, and a key rotation process after recovery. The recovery process is managed through the Para Portal, reducing the implementation burden on individual developers.
Yes. While most users don't need to export their private keys given Para wallets are universal and usable across apps and chains, users are able to do so in [Para Connect](https://connect.getpara.com/).
How Para enforces fine-grained access control
One wallet across your entire ecosystem
# Universal Wallets
Source: https://docs.getpara.com/v2/concepts/universal-embedded-wallets
With traditional embedded wallets, every application creates a separate wallet for each user. Users end up managing multiple wallets, moving assets between them, and repeating onboarding flows. For developers, this means every new user starts from zero: no history, no liquidity, no existing trust.
Para [universal embedded wallets](https://blog.getpara.com/universal-embedded-wallets/) solve this by associating wallets with **user identities** rather than individual applications. A user creates one wallet and uses it everywhere, across first party and partner apps, as well as the broader Para ecosystem.
Universal wallets are about accessing the same wallet from different applications, not transferring assets between separate wallets.
## Benefits
**Faster onboarding:** Users who already have a Para wallet can start using new apps immediately. No wallet creation, no seed phrase backup, no friction.
**Richer user context:** Shared wallets mean shared transaction history and token balances. An application can offer better experiences when it knows what users already hold.
**Ecosystem play:** Build a network of interconnected apps where users move fluidly between experiences. A DeFi protocol, an NFT marketplace, and a portfolio tracker can all share the same wallet while each maintaining their own permission boundaries.
**Reduced support burden:** One wallet means one recovery flow, one set of backup devices, one identity to manage, regardless of how many apps the user connects to.
We have also seen data-driven evidence that using a universal embedded wallet can boost retention and conversion for ecosystems. [Read more here](https://blog.getpara.com/para-gelato/).
## Key Components
Wallets are associated with user identities (typically email addresses) rather than individual applications.
Each application gets its own permission policy; users approve access per-app, and no app can exceed its granted scope.
Users log in to new applications with their Para credentials and automatically gain access to their existing wallet.
Para's MPC-based key management enables secure key sharing across applications without exposing the full private key.
## Benefits
- Access the same wallet across multiple applications without complex key exports
- No need to manage multiple wallets or move assets between them
- Consistent user experience across different platforms
- Transparent permissions with granular access control per app
- Easier onboarding of users who already have a Para wallet
- Access to richer transaction history and liquidity from shared wallets
- Build multi-app experiences and ecosystems with ease
- Request specific permissions tailored to each application's needs
## Universal Wallets vs. Traditional Approaches
| Feature | Third-Party Wallets | Traditional Embedded Wallets | Universal Embedded Wallets |
| ---------------------------------- | :-----------------: | :--------------------------: | :-------------------: |
| Portable across apps | ✔️ | | ✔️ |
| Smooth in-app UX | | ✔️ | ✔️ |
| Integrated with app functionality | | ✔️ | ✔️ |
| No browser extensions required | | ✔️ | ✔️ |
| Granular permissions per app | | | ✔️ |
| Cross-application capabilities without manual key management | | | ✔️ |
## Security Features
Each application is granted specific permissions, limiting potential damage if one app is compromised.
The User Share is encrypted specifically for each application, preventing unauthorized access.
Each key sharing process includes a signature verification step to ensure authenticity.
Thanks to MPC, the full private key is never exposed to any single application or stored in one place.
## How Wallet Portability Works
A user creates a Para wallet in one application. The wallet is associated with their identity (e.g., email address).
When the user wants to use their wallet in a new application, they log in with their Para credentials. The new application requests specific permissions, and upon approval, gains access to the user's wallet.
Para securely shares the necessary key information with the new application without exposing the full private key.
The user seamlessly uses their wallet across all connected applications, with each app respecting its granted permissions.
## Example Use Cases
1. **DeFi Dashboard**: An app that aggregates data from multiple DeFi protocols could request read-only permissions across various chains.
2. **Consortium / Chain Ecosystem Wallets**: A Layer 1 or app chain ecosystem could offer a single wallet that works across all apps built on their network, with each app scoped to its own permissions.
3. **Cross-Chain DEX**: Might request permissions for swap transactions across multiple chains.
4. **Identity Credentials for Payments**: A wallet that holds verifiable credentials (KYC, accreditation, age verification) could authorize payments across merchant apps without re-verifying at each one.
## Universal Wallets FAQs
Para's multi-app architecture allows the same wallet to be used across different applications while maintaining security. It uses a permissions scheme that specifies what types of transactions an application can perform and which ones require user approval. This is implemented through encrypted User Shares specific to each application and an allow-list mechanism managed by Para.
Universal embedded wallets are achieved by associating wallets with user identities (typically email addresses) rather than individual applications, using MPC for secure key sharing across applications, implementing a permissions framework for granular access control, and providing seamless authentication when users access new applications. Developers can leverage these features through Para's SDKs and APIs.
# Agent Skill
Source: https://docs.getpara.com/v2/developer-tools/ai-tooling/agent-skill
Give your AI coding agent everything it needs to set up and build with Para. Send it one message and it'll install the Para CLI, authenticate, save the skill for future sessions, and help you start building.
## Send This to Your Agent
Copy and paste this into Claude Code, Cursor, Windsurf, or any AI coding agent:
```text
Fetch https://docs.getpara.com/skill.md and help me build with Para
```
Your agent will:
1. **Save the skill** so it remembers Para in future sessions
2. **Install the Para CLI** (`npm install -g @getpara/cli`)
3. **Authenticate** via `para login` (opens your browser)
4. **Ask what you want to build** and use `para` commands to do it
## What You Can Ask
Once your agent has the skill, try:
- *"Set up Para in my Next.js app with EVM and Solana"*
- *"Scaffold an Expo app with Para wallets"*
- *"Why isn't my Para integration working?"*
- *"Configure webhooks for user.created and wallet.created"*
- *"Rotate my production API key"*
- *"Add Google and Apple OAuth to my config"*
Your agent uses `para` CLI commands to handle everything — no portal needed.
## What's in the Skill
The skill file at [`docs.getpara.com/skill.md`](https://docs.getpara.com/skill.md) teaches your agent:
| Area | What the agent learns |
|------|----------------------|
| **CLI commands** | All `para` commands — auth, orgs, projects, keys, config, scaffolding, diagnostics |
| **Key configuration** | Security, branding, wallet setup, webhooks, and ramp settings via `para keys config` |
| **SDK packages** | Which `@getpara/*` packages to install for React, Viem, Ethers, Solana, Cosmos |
| **Integration steps** | ParaProvider setup, CSS import, env var naming per framework, "use client" directives |
| **Diagnostics** | What `para doctor` checks and how to interpret results |
| **Environments** | Beta vs prod key management and config resolution |
# Integrate Para Docs MCP with AI Tools
Source: https://docs.getpara.com/v2/developer-tools/ai-tooling/mcp
Connect the Para Docs MCP server to AI tools for seamless access to documentation, code examples, and guides. This integration enables AI assistants to search Para Docs directly via the Model Context Protocol.
## Prerequisites
- Active accounts for target AI tools
- Para Docs MCP server URL: `http://docs.getpara.com/mcp`
- AI tool with remote MCP connection support (may be in beta)
## Installation and Setup
Set up connections by adding the MCP server as a custom connector in each tool's settings.
### Configure ChatGPT Connection
1. Open ChatGPT settings from your avatar menu
2. Select **Connectors** in the sidebar
3. Click **Create** to open the New Connector dialog
4. Enter the MCP server URL: `http://docs.getpara.com/mcp`
5. Configure authentication if required (Para Docs MCP uses no security by default)
6. Save and test the connection
### Configure Claude Desktop
1. Navigate to **Settings > Extensions** in Claude Desktop
2. Click **Advanced settings** and locate the Extension Developer section
3. Add a custom connector with the remote MCP URL: `http://docs.getpara.com/mcp`
4. Note: Remote support is in beta; use local STDIO if preferred
5. Verify the connection in Claude's interface
### Configure Claude Code
1. Run the CLI command: `claude mcp add`
2. Follow the wizard to input the MCP server URL: `http://docs.getpara.com/mcp`
3. Select remote MCP support
4. Integrate tools like search and fetch for Para Docs access
5. Restart Claude Code to apply changes
### Configure Cursor
1. Open Cursor settings and navigate to **Models** or **API Keys**
2. Disable unnecessary models if needed
3. Add a custom model or provider, overriding the base URL to `http://docs.getpara.com/mcp`
4. Use agent mode for MCP interactions (similar to VS Code Copilot)
5. Verify by testing a documentation query in the editor
## Usage
Query the MCP server via your AI tool's interface after setup. Provide search terms to the "SearchParaDocs" tool for relevant results.
Ask your AI: "Search Para Docs for [topic]"
The tool returns titles, snippets, and links to relevant documentation.
Review returned contextual content and follow links for full details.
Check for connection issues and retry if the server is unreachable.
## Example
### Query Example
```text
Search Para Docs for how to sign a basic message
```
### Expected Response
The MCP server returns structured results:
```json Response
{
"results": [
{
"title": "Sign Messages with Para",
"snippet": "Learn how to sign messages using Para's wallet integration...",
"link": "https://docs.getpara.com/v2/react/guides/web3-operations/sign-with-para"
},
{
"title": "Web3 Operations Guide",
"snippet": "Complete guide for signing transactions and messages...",
"link": "https://docs.getpara.com/v2/react/guides/web3-operations"
}
]
}
```
# AI Tooling
Source: https://docs.getpara.com/v2/developer-tools/ai-tooling/overview
import { Card } from "/snippets/v2/components/ui/card.mdx";
Para's AI tooling ecosystem lets you integrate wallet infrastructure into AI-powered development workflows. From documentation search in your IDE to automated key management through AI agents, these tools bring Para closer to where you already work.
## Available Now
Give your AI coding agent full context on Para — setup, CLI commands, SDK packages, and integration patterns. One command to make any agent a Para expert.
Connect the Para Docs MCP server to Claude, ChatGPT, Cursor, and other AI tools for direct documentation search and contextual coding assistance.
# Developer Tools
Source: https://docs.getpara.com/v2/developer-tools/overview
Para provides a growing set of developer tools to streamline how you build, configure, and manage your integration — from the command line, within your IDE, or through AI-powered workflows.
Want the fastest path? Send the [Agent Skill](/v2/developer-tools/ai-tooling/agent-skill) to your AI coding
agent — it'll install the CLI, authenticate, and start building for you.
## Overview
| Tool | Usage |
|------|----------|
| **CLI** | Managing API keys, scaffolding projects, and automating workflows from your terminal or CI/CD pipelines |
| **Agent Skill** | Giving your AI coding agent full context on Para so it can set up, build, and troubleshoot for you |
| **MCP Integration** | Connecting Para's docs and APIs to Claude, ChatGPT, Cursor, or other AI assistants for contextual help |
| **Migration MCP** | Automating a full migration from Privy, Reown, Web3Modal, or WalletConnect to Para using an AI agent |
## CLI
The Para CLI (`@getpara/cli`) lets you manage API keys, organizations, projects, and configuration directly from your terminal. It's ideal for local development, scripting, and CI/CD pipelines.
Manage API keys, projects, and config from your terminal
Install, authenticate, and configure the CLI
## AI Tooling
Leverage Skills and MCPs with Para to streamline LLM-assisted development workflows.
**Hint:** Give your agent access to Para's [llms.txt](https://docs.getpara.com/llms.txt) or [llms-full.txt](https://docs.getpara.com/llms-full.txt)
Explore all AI-powered tools for building with Para
Connect Para Docs to Claude, ChatGPT, Cursor, and more
Give your AI coding agent full context on Para — setup, CLI, SDKs, and integration patterns
AI-powered migration from Privy, Reown, Web3Modal, or WalletConnect to Para
# Flutter SDK API
Source: https://docs.getpara.com/v2/flutter/api/sdk
## Para Class
The `Para` class is the main entry point for the Para Flutter SDK, providing wallet operations, authentication, and blockchain interactions. The latest version includes passkey authentication, comprehensive multi-chain support, and deep linking capabilities.
### Constructor
Creates a new Para SDK instance using the v2 factory constructor.
Configuration object containing environment and API key.
Your app's deep link scheme (e.g., "yourapp").
```dart
final para = Para.fromConfig(
config: ParaConfig(
environment: Environment.beta,
apiKey: 'YOUR_API_KEY',
jsBridgeUri: Uri.parse('custom-bridge-url'), // Optional
relyingPartyId: 'custom.domain.com', // Optional
),
appScheme: 'yourapp',
);
```
### Authentication Methods
Initiates authentication flow for email or phone. Returns an `AuthState` indicating next steps.
Authentication object using `Auth.email()` or `Auth.phone()` helper methods.
```dart
// Email authentication
final authState = await para.initiateAuthFlow(
auth: Auth.email('user@example.com')
);
// Phone authentication
final authState = await para.initiateAuthFlow(
auth: Auth.phone('+1234567890')
);
```
Verifies the OTP code sent to email/phone during authentication.
The 6-digit OTP verification code.
```dart
final verifiedState = await para.verifyOtp(
otp: '123456'
);
```
Handles login for existing users with passkey authentication.
The auth state returned from `initiateAuthFlow()` for existing users.
```dart
final wallet = await para.handleLogin(
authState: authState,
);
```
Handles the complete signup flow for new users, including passkey creation and wallet setup.
The auth state returned from `verifyOtp()` for new users.
The signup method to use: `SignupMethod.passkey`.
```dart
final wallet = await para.handleSignup(
authState: authState,
method: SignupMethod.passkey,
);
```
Handles complete OAuth authentication flow using an external browser.
OAuth provider: `OAuthMethod.google`, `.twitter`, `.apple`, `.discord`, or `.facebook`.
Your app's deep link scheme for OAuth callback (e.g., 'yourapp').
```dart
final authState = await para.verifyOAuth(
provider: OAuthMethod.google,
appScheme: 'yourapp',
);
```
### Wallet Management
Creates a new wallet with enhanced multi-chain support. Returns a `ParaFuture` that can be cancelled.
The type of wallet to create: `WalletType.evm`, `.solana`, or `.cosmos` (default: `WalletType.evm`).
Whether to skip the distributed backup process.
```dart
final walletFuture = para.createWallet(
type: WalletType.evm,
skipDistribute: false,
);
final wallet = await walletFuture.future;
// Or cancel: await para.cancelOperationById(walletFuture.requestId);
```
Retrieves all wallets for the current user.
```dart
final walletsFuture = para.fetchWallets();
final wallets = await walletsFuture.future;
```
### Signing Operations
Signs a message with the specified wallet. Returns a cancellable `ParaFuture`.
The wallet ID to use for signing.
The message to sign, base64-encoded.
Optional timeout in milliseconds (default: 30000).
For Cosmos signing: The SignDoc as base64-encoded JSON. When provided, this method signs a Cosmos transaction instead of a generic message.
```dart
// Generic message signing
final signatureFuture = para.signMessage(
walletId: wallet.id,
messageBase64: base64Encode(utf8.encode('Hello, Para!')),
);
final signature = await signatureFuture.future;
// Cosmos transaction signing
final cosmosSignature = await para.signMessage(
walletId: cosmosWallet.id,
messageBase64: '', // Not used for Cosmos
cosmosSignDocBase64: base64Encode(utf8.encode(jsonEncode(signDoc))),
).future;
```
Signs an EVM transaction. Returns a cancellable `ParaFuture`.
This method is for EVM transactions only. For Solana, use `signMessage()` with the serialized transaction as `messageBase64`. For Cosmos, use `signMessage()` with `cosmosSignDocBase64`.
**EVM Transaction Return Value**: The `SuccessfulSignatureResult` contains the complete RLP-encoded transaction ready for broadcasting via the `signedTransaction` property.
**Solana/Cosmos Return Value**: For pre-serialized transactions, returns just the signature in `signedTransaction`. For constructed transactions, returns the complete signed transaction.
The wallet ID to use for signing.
RLP-encoded EVM transaction (base64).
Chain ID of the EVM network.
Optional timeout in milliseconds (default: 30000).
```dart
// EVM transaction signing
final signatureFuture = para.signTransaction(
walletId: evmWallet.id,
rlpEncodedTxBase64: base64Encode(rlpEncodedTx),
chainId: '1', // Ethereum mainnet
);
final signature = await signatureFuture.future;
```
Cancels an ongoing operation by its request ID.
The request ID from a `ParaFuture`.
```dart
final signatureFuture = para.signMessage(...);
// Cancel the operation
await para.cancelOperationById(signatureFuture.requestId);
```
### Session Management
Checks if there's an active user session.
```dart
final isActive = await para.isSessionActive();
```
Exports the current session as an encrypted string.
```dart
final sessionData = await para.exportSession();
// Save sessionData securely
```
Logs out the current user and clears session data.
```dart
await para.logout();
```
### Utility Methods
Gets the current user's basic profile and session status.
```dart
final user = await para.currentUser();
if (user.isLoggedIn) {
debugPrint('Logged in as ${user.userId}');
}
```
Returns a browser URL for One-Click (BASIC_LOGIN) authentication.
Login method identifier (e.g., `BASIC_LOGIN`).
Request a shortened URL.
```dart
final loginUrl = await para.getLoginUrl(authMethod: 'BASIC_LOGIN');
```
Presents an auth URL in a secure system web view and returns the callback URI (if any).
The authentication URL to open.
Platform-specific web authentication session.
Descriptive label for logging (default: `authentication`).
Whether to preload signing keyshares after completion.
```dart
final callback = await para.presentAuthUrl(
url: loginUrl,
webAuthenticationSession: webAuthSession,
context: 'One-Click login',
loadTransmissionKeyshares: true,
);
```
Waits for an ongoing login operation to complete.
Optional function to check if operation is canceled.
Polling interval in milliseconds (default: 2000).
```dart
final result = await para.waitForLogin(pollingIntervalMs: 1000);
```
Polls for completion of a pending One-Click signup.
Optional timeout in milliseconds (default: no timeout).
```dart
final completed = await para.waitForSignup(timeoutMs: 300000);
```
Waits for wallet creation to complete after signup.
Optional function to check if operation is canceled.
Polling interval in milliseconds (default: 2000).
```dart
final result = await para.waitForWalletCreation(pollingIntervalMs: 1000);
```
Refreshes the current session metadata and returns the latest snapshot (or `null` if nothing changed).
```dart
final snapshot = await para.touchSession();
if (snapshot != null) {
debugPrint('Session touched at ${snapshot['updatedAt']}');
}
```
Formats a phone number for authentication.
The phone number to format.
The country code (e.g., '1' for US, '44' for UK).
```dart
final formatted = para.formatPhoneNumber('1234567890', '1');
// Returns: '+11234567890' (exact format depends on implementation)
```
Checks if the current user is using an external wallet.
```dart
final isExternal = await para.isUsingExternalWallet();
```
Clears local storage data.
Whether to keep the Paillier secret key (default: false).
```dart
await para.clearStorage(false);
```
Disposes of SDK resources. Call when the SDK is no longer needed.
```dart
para.dispose();
```
## Extension Methods
Para provides extension methods for cleaner authentication flows (requires importing extensions):
```dart
import 'package:para/src/auth_extensions.dart';
```
### ParaAuthExtensions
Initiates authentication and returns the current state using `Auth` helper class.
Authentication method: `Auth.email()` or `Auth.phone()`.
```dart
// Must import extensions
import 'package:para/src/auth_extensions.dart';
final authState = await para.initiateAuthFlow(
auth: Auth.email('user@example.com')
);
```
Presents a password authentication URL in a secure web view.
The password authentication URL.
Web authentication session for handling the flow.
## Types and Enums
### Environment
```dart
enum Environment {
dev, // Development environment
beta, // Beta environment
prod // Production environment
}
```
### Auth
```dart
class Auth {
static AuthEmail email(String email);
static AuthPhone phone(String phoneNumber);
}
```
### AuthState
```dart
class AuthState {
final AuthStage stage; // Current authentication stage
final String? userId; // User's unique identifier
final AuthIdentity auth; // Authentication identity details
final String? displayName; // User's display name
final String? pfpUrl; // Profile picture URL
final String? username; // Username
final Map? externalWallet; // External wallet info
final String? loginUrl; // One-Click URL, when provided
final List? loginAuthMethods; // Advertised login methods (e.g., BASIC_LOGIN)
final List? signupAuthMethods; // Advertised signup methods
final String? passkeyUrl; // URL for passkey authentication
final String? passkeyId; // Passkey identifier
final String? passwordUrl; // URL for password authentication
final AuthStage? nextStage; // Optional next stage hint
// Helper getters exposed by the SDK:
bool get hasSloUrl; // loginUrl is non-empty
List get loginMethods; // loginAuthMethods ?? []
List get signupMethods; // signupAuthMethods ?? []
AuthStage get effectiveNextStage; // nextStage ?? stage
}
class AuthIdentity {
final String? email;
final String? phoneNumber;
final String? fid; // Farcaster ID
final String? telegramUserId;
// Other identity types
}
enum AuthStage {
verify, // Need to verify email/phone
login, // Existing user, proceed to login
signup // New user, proceed to signup
}
```
### SignupMethod
```dart
enum SignupMethod {
passkey, // Hardware-backed passkey
password // Password-based authentication
}
```
### OAuthMethod
```dart
enum OAuthMethod {
google,
twitter,
apple,
discord,
facebook
}
```
### WalletType
```dart
enum WalletType {
evm, // Ethereum and EVM-compatible chains
solana, // Solana blockchain
cosmos // Cosmos-based chains
}
```
### ParaFuture
```dart
class ParaFuture {
final Future future; // The actual future
final String requestId; // ID for cancellation
}
```
### Wallet
```dart
class Wallet {
final String id;
final String address;
final WalletType type;
final WalletScheme scheme;
final String? userId; // Associated user ID
final DateTime? createdAt; // Creation timestamp
final String? publicKey; // Wallet public key
final bool? isPregen; // Whether this is a pregenerated wallet
// Additional optional fields available
}
```
### SignatureResult
```dart
// Abstract base class
abstract class SignatureResult {}
// Successful signature
class SuccessfulSignatureResult extends SignatureResult {
final String signedTransaction; // For transactions: complete signed transaction ready for broadcasting
// For messages: just the signature
SuccessfulSignatureResult(this.signedTransaction);
/// Gets the transaction data ready for broadcasting.
String get transactionData => signedTransaction;
}
// Denied signature
class DeniedSignatureResult extends SignatureResult {
final String? pendingTransactionId;
DeniedSignatureResult(this.pendingTransactionId);
}
// Denied with URL
class DeniedSignatureResultWithUrl extends SignatureResult {
final String? pendingTransactionId;
final String url;
DeniedSignatureResultWithUrl({this.pendingTransactionId, required this.url});
}
```
# Mobile Examples
Source: https://docs.getpara.com/v2/flutter/examples
import { Card } from '/snippets/v2/components/ui/card.mdx';
Para provides a minimal, focused Flutter example demonstrating clean integration patterns with our v2 SDK. Our example app serves as a reference implementation that you can adapt to your specific needs.
## Para Flutter Example
Explore our complete Flutter application showcasing Para v2 integration:
### Highlights
- One-Click login, passkeys, and password flows in `lib/screens/auth_screen.dart`
- Social/OAuth providers and external wallets in `lib/features/auth`
- Multi-chain wallet management plus signing demos in `lib/features/wallets`
- End-to-end tests that exercise the full auth flow in `test_e2e/`
### Run It
Follow the project README for setup and commands:
- `README.md` → `mobile/with-flutter`: environment variables, build steps, troubleshooting
- `lib/client/para.dart`: replace the placeholder API key before running `flutter run`
Need another integration? File an issue on the repo or reach out to the Para team.
# Cosmos Integration
Source: https://docs.getpara.com/v2/flutter/guides/cosmos
import { Card } from '/snippets/v2/components/ui/card.mdx';
## Quick Start
```dart
import 'package:para/para.dart';
// Sign a Cosmos transaction
final para = Para(apiKey: 'your-api-key');
final wallet = (await para.fetchWallets()).firstWhere((w) => w.type == 'COSMOS');
final transaction = CosmosTransaction(
to: 'cosmos1recipient...',
amount: '1000000', // 1 ATOM in micro-units
chainId: 'theta-testnet-001', // Cosmos Hub testnet (use 'cosmoshub-4' for mainnet)
format: 'proto',
);
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: transaction.toJson(),
chainId: 'theta-testnet-001',
);
print('Transaction signed: ${result.signedTransaction}');
```
## Common Operations
### Sign Transactions for Different Chains
```dart
// Sign ATOM transaction on Cosmos Hub testnet
final atomTx = CosmosTransaction(
to: 'cosmos1recipient...',
amount: '1000000', // 1 ATOM
denom: 'uatom',
chainId: 'theta-testnet-001', // Testnet
format: 'proto',
);
// Sign OSMO transaction on Osmosis testnet
final osmoTx = CosmosTransaction(
to: 'osmo1recipient...',
amount: '1000000', // 1 OSMO
denom: 'uosmo',
chainId: 'osmo-test-5', // Testnet
format: 'proto',
);
// Sign JUNO transaction on Juno mainnet
final junoTx = CosmosTransaction(
to: 'juno1recipient...',
amount: '1000000', // 1 JUNO
denom: 'ujuno',
chainId: 'juno-1',
format: 'proto',
);
```
### Sign Transaction
```dart
final transaction = CosmosTransaction(
to: 'cosmos1recipient...',
amount: '1000000', // 1 ATOM
denom: 'uatom',
memo: 'Transfer via Para',
chainId: 'theta-testnet-001', // Testnet
format: 'proto', // or 'amino' for legacy
);
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: transaction.toJson(),
chainId: 'theta-testnet-001',
rpcUrl: 'https://rpc.sentry-01.theta-testnet.polypore.xyz',
);
// Cosmos returns: { signBytes, signDoc, format }
print('Signed: ${result.signedTransaction}');
```
### Check Balance
```dart
final balance = await para.getBalance(
walletId: wallet.id!,
rpcUrl: 'https://cosmos-rpc.publicnode.com',
chainPrefix: 'cosmos',
denom: 'uatom',
);
print('Balance: $balance uatom');
// Different chain
final osmoBalance = await para.getBalance(
walletId: wallet.id!,
rpcUrl: 'https://osmosis-rpc.publicnode.com',
chainPrefix: 'osmo',
denom: 'uosmo',
);
```
### Sign Message
```dart
final message = 'Hello, Cosmos!';
final result = await para.signMessage(
walletId: wallet.id!,
message: message,
);
print('Signature: ${result.signedTransaction}');
```
## Supported Networks
### Testnets
| Network | Chain ID | Prefix | Native Token | RPC URL |
|---------|----------|--------|--------------|---------|
| **Cosmos Hub Testnet** | `theta-testnet-001` | `cosmos` | `uatom` | `https://rpc.sentry-01.theta-testnet.polypore.xyz` |
| **Osmosis Testnet** | `osmo-test-5` | `osmo` | `uosmo` | `https://rpc.osmotest5.osmosis.zone` |
### Mainnets
| Network | Chain ID | Prefix | Native Token | Decimals | RPC URL |
|---------|----------|--------|--------------|----------|---------|
| **Cosmos Hub** | `cosmoshub-4` | `cosmos` | `uatom` | 6 | `https://cosmos-rpc.publicnode.com` |
| **Osmosis** | `osmosis-1` | `osmo` | `uosmo` | 6 | `https://osmosis-rpc.publicnode.com` |
| **Juno** | `juno-1` | `juno` | `ujuno` | 6 | `https://rpc-juno.itastakers.com` |
| **Stargaze** | `stargaze-1` | `stars` | `ustars` | 6 | `https://rpc.stargaze-apis.com` |
| **Akash** | `akashnet-2` | `akash` | `uakt` | 6 | `https://rpc.akash.forbole.com` |
| **Celestia** | `celestia` | `celestia` | `utia` | 6 | `https://rpc.celestia.pops.one` |
| **dYdX** | `dydx-mainnet-1` | `dydx` | `adydx` | 18 | `https://dydx-dao-api.polkachu.com` |
| **Injective** | `injective-1` | `inj` | `inj` | 18 | `https://injective-rpc.publicnode.com` |
## Complete Example
```dart
import 'package:flutter/material.dart';
import 'package:para/para.dart';
class CosmosWalletView extends StatefulWidget {
final Para para;
final Wallet wallet;
const CosmosWalletView({required this.para, required this.wallet});
@override
State createState() => _CosmosWalletViewState();
}
class _CosmosWalletViewState extends State {
String _selectedChain = 'theta-testnet-001';
bool _isLoading = false;
String? _result;
final chains = {
'theta-testnet-001': {'name': 'Cosmos Hub Testnet', 'rpc': 'https://rpc.sentry-01.theta-testnet.polypore.xyz', 'denom': 'uatom', 'prefix': 'cosmos'},
'osmo-test-5': {'name': 'Osmosis Testnet', 'rpc': 'https://rpc.osmotest5.osmosis.zone', 'denom': 'uosmo', 'prefix': 'osmo'},
};
@override
Widget build(BuildContext context) {
final chain = chains[_selectedChain]!;
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
DropdownButton(
value: _selectedChain,
items: chains.entries.map((e) =>
DropdownMenuItem(value: e.key, child: Text(e.value['name']!))
).toList(),
onChanged: (value) => setState(() => _selectedChain = value!),
),
SizedBox(height: 20),
Text(
widget.wallet.address ?? 'No address',
style: TextStyle(fontFamily: 'monospace', fontSize: 12),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _isLoading ? null : _signTransaction,
child: Text(_isLoading ? 'Signing...' : 'Sign Transaction for ${chain['name']}'),
),
if (_result != null)
Padding(
padding: const EdgeInsets.only(top: 20),
child: Text(_result!, style: TextStyle(fontSize: 12)),
),
],
),
);
}
Future _signTransaction() async {
setState(() => _isLoading = true);
final chain = chains[_selectedChain]!;
try {
final transaction = CosmosTransaction(
to: '${chain['prefix']}1recipient...', // Uses chain prefix
amount: '1000000', // 1 token in micro-units
denom: chain['denom']!,
memo: 'Test transaction from Flutter',
chainId: _selectedChain,
format: 'proto',
);
final result = await widget.para.signTransaction(
walletId: widget.wallet.id!,
transaction: transaction.toJson(),
chainId: _selectedChain,
rpcUrl: chain['rpc']!,
);
setState(() => _result = 'Signed! Signature: ${result.signature}');
} catch (e) {
setState(() => _result = 'Error: $e');
} finally {
setState(() => _isLoading = false);
}
}
}
```
## Proto vs Amino Formats
```dart
// Modern Proto format (recommended)
final protoTx = CosmosTransaction(
to: 'cosmos1recipient...',
amount: '1000000',
format: 'proto',
chainId: 'cosmoshub-4',
);
// Legacy Amino format (compatibility)
final aminoTx = CosmosTransaction(
to: 'cosmos1recipient...',
amount: '1000000',
format: 'amino',
chainId: 'cosmoshub-4',
);
// Use convenience constructor
final simpleTx = CosmosTransaction(
to: 'cosmos1recipient...',
amount: '1000000',
denom: 'uatom',
chainId: 'theta-testnet-001',
);
```
# EVM Integration
Source: https://docs.getpara.com/v2/flutter/guides/evm
import { Card } from '/snippets/v2/components/ui/card.mdx';
## Quick Start
### Transfer
Para handles signing and broadcasting in one call:
```dart
import 'package:para/para.dart';
final para = Para(apiKey: 'your-api-key');
final wallet = (await para.fetchWallets()).firstWhere((w) => w.type == 'EVM');
// Send ETH - Para signs and broadcasts
final result = await para.transfer(
walletId: wallet.id!,
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: '1000000000000000', // 0.001 ETH in wei
chainId: '11155111', // Optional: Sepolia testnet (defaults to wallet's chain)
rpcUrl: null, // Optional: override default RPC
);
print('Transaction sent: ${result.hash}');
print('From: ${result.from}, To: ${result.to}');
print('Amount: ${result.amount}, Chain: ${result.chainId}');
```
### Advanced Control
Sign with Para, then broadcast yourself for custom gas/RPC settings:
```dart
// Step 1: Sign transaction with Para
final transaction = EVMTransaction(
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
value: '1000000000000000',
gasLimit: '21000',
);
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: transaction.toJson(),
chainId: '11155111', // Sepolia testnet
);
// Step 2: Broadcast using your preferred library (e.g., web3dart)
// The transactionData getter provides the complete signed transaction
// final txHash = await broadcastWithWeb3Dart(result.transactionData);
```
## Common Operations
### Send ETH
```dart
// Para handles everything - signing and broadcasting
final result = await para.transfer(
walletId: wallet.id!,
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: '1000000000000000', // 0.001 ETH in wei
chainId: '11155111', // Optional: Sepolia testnet
rpcUrl: null, // Optional: custom RPC URL
);
print('Transaction hash: ${result.hash}');
print('From: ${result.from}, To: ${result.to}, Chain: ${result.chainId}');
```
### Sign Transaction
```dart
final transaction = EVMTransaction(
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
value: '1000000000000000',
gasLimit: '21000',
maxPriorityFeePerGas: '1000000000', // 1 Gwei
maxFeePerGas: '3000000000', // 3 Gwei
nonce: '0',
chainId: '11155111', // Sepolia
type: 2,
);
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: transaction.toJson(),
chainId: '11155111',
);
// The transactionData getter returns the complete RLP-encoded transaction
// ready for broadcasting via eth_sendRawTransaction
print('Signed transaction: ${result.transactionData}');
// For backward compatibility, signature field still contains the raw signature
print('Raw signature: ${result.signedTransaction}');
```
### Check Balance
```dart
// Native ETH balance
final ethBalance = await para.getBalance(walletId: wallet.id!);
// ERC-20 token balance
final tokenBalance = await para.getBalance(
walletId: wallet.id!,
token: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
);
```
### Sign Message
```dart
final message = 'Hello, Ethereum!';
final result = await para.signMessage(
walletId: wallet.id!,
message: message,
);
print('Signature: ${result.signedTransaction}');
```
## Networks
### Testnets
| Network | Chain ID | Native Token | Default RPC |
|---------|----------|--------------|-------------|
| **Sepolia** | `11155111` | ETH | `https://ethereum-sepolia-rpc.publicnode.com` |
| **Polygon Mumbai** | `80001` | MATIC | `https://rpc-mumbai.maticvigil.com` |
| **Base Sepolia** | `84532` | ETH | `https://sepolia.base.org` |
### Mainnets
| Network | Chain ID | Native Token | Default RPC |
|---------|----------|--------------|-------------|
| **Ethereum** | `1` | ETH | `https://eth.llamarpc.com` |
| **Polygon** | `137` | MATIC | `https://polygon-rpc.com` |
| **Base** | `8453` | ETH | `https://mainnet.base.org` |
| **Arbitrum** | `42161` | ETH | `https://arb1.arbitrum.io/rpc` |
| **Optimism** | `10` | ETH | `https://mainnet.optimism.io` |
## Complete Example
```dart
import 'package:flutter/material.dart';
import 'package:para/para.dart';
class EVMWalletView extends StatefulWidget {
final Para para;
final Wallet wallet;
const EVMWalletView({required this.para, required this.wallet});
@override
State createState() => _EVMWalletViewState();
}
class _EVMWalletViewState extends State {
bool _isLoading = false;
String? _txHash;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text(
widget.wallet.address ?? 'No address',
style: TextStyle(fontFamily: 'monospace', fontSize: 12),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _isLoading ? null : _sendETH,
child: Text(_isLoading ? 'Sending...' : 'Send 0.001 ETH'),
),
if (_txHash != null)
Padding(
padding: const EdgeInsets.only(top: 20),
child: Text('Sent: $_txHash', style: TextStyle(fontSize: 12)),
),
],
),
);
}
Future _sendETH() async {
setState(() => _isLoading = true);
try {
final result = await widget.para.transfer(
walletId: widget.wallet.id!,
to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
amount: '1000000000000000',
chainId: '11155111', // Optional: Sepolia testnet
rpcUrl: null, // Optional: custom RPC
);
setState(() => _txHash = result.hash);
} catch (e) {
print('Error: $e');
} finally {
setState(() => _isLoading = false);
}
}
}
```
## Smart Contract Interaction
**Transaction Data**: For EVM transactions, `result.transactionData` returns the complete RLP-encoded signed transaction that's ready to broadcast via `eth_sendRawTransaction`. The `signature` field contains just the raw signature for backward compatibility.
```dart
// Call a contract function
final contractTransaction = EVMTransaction(
to: '0x123abc...', // Contract address
value: '0',
gasLimit: '150000',
maxPriorityFeePerGas: '1000000000',
maxFeePerGas: '3000000000',
nonce: '0',
chainId: '11155111', // Sepolia testnet
smartContractAbi: '''[{
"inputs": [{"name":"num","type":"uint256"}],
"name": "store",
"type": "function"
}]''',
smartContractFunctionName: 'store',
smartContractFunctionArgs: ['42'],
type: 2,
);
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: contractTransaction.toJson(),
chainId: '11155111', // Sepolia testnet
);
// Use result.transactionData to get the complete signed transaction
print('Signed transaction: ${result.transactionData}');
```
### ERC20 Token Transfer
Transfer ERC20 tokens using the standard transfer function:
```dart
// Transfer USDC on Sepolia testnet
final usdcTransaction = EVMTransaction(
to: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', // USDC contract on Sepolia
value: '0', // No ETH being sent, only tokens
gasLimit: '100000', // Higher gas limit for token transfers
maxPriorityFeePerGas: '1000000000', // 1 Gwei
maxFeePerGas: '3000000000', // 3 Gwei
nonce: '0',
chainId: '11155111', // Sepolia testnet
// ERC20 transfer function ABI
smartContractAbi: '''[{
"inputs": [
{"name": "to", "type": "address"},
{"name": "amount", "type": "uint256"}
],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"type": "function"
}]''',
smartContractFunctionName: 'transfer',
smartContractFunctionArgs: [
'0x742d35Cc6634C0532925a3b844Bc454e4438f44e', // Recipient address
'100000', // 0.1 USDC (USDC has 6 decimals, so 100000 = 0.1 USDC)
],
type: 2, // EIP-1559 transaction
);
// Sign the token transfer transaction
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: usdcTransaction.toJson(),
chainId: '11155111',
);
// The Flutter SDK sends the ABI and function parameters separately
// The bridge handles the encoding to create the proper transaction data
print('Token transfer signed: ${result.transactionData}');
// You can now broadcast this transaction using eth_sendRawTransaction
// or use Para's transfer method for automatic broadcasting
```
# External Wallets
Source: https://docs.getpara.com/v2/flutter/guides/external-wallets
import EnvironmentInfo from "/snippets/v2/quick-start-environment-info.mdx";
import Prerequisites from "/snippets/v2/quick-start-prerequisites.mdx";
import { Card } from '/snippets/v2/components/ui/card.mdx';
import { Link } from '/snippets/v2/components/ui/link.mdx';
## Overview
Para v2 supports authentication with external wallets, allowing users to connect their existing MetaMask, Phantom, or other wallets to authenticate with your Flutter application. The Flutter SDK maintains blockchain packages (web3dart, solana_web3) to provide direct access to external wallet functionality while integrating with Para's unified wallet architecture.
This guide covers how to implement external wallet authentication and interaction using Para's connector classes.
## Prerequisites
Before implementing external wallet support, ensure you have:
1. Para SDK set up in your Flutter project (see the [Setup Guide](/v2/flutter/setup))
2. Deep linking configured for your app
3. Target external wallet apps installed on the device
## Deep Link Configuration
External wallet authentication requires deep linking to redirect users back to your app after wallet interaction. Configure this in both iOS and Android.
### Android Configuration
Add an intent filter to your `android/app/src/main/AndroidManifest.xml`:
```xml
```
### iOS Configuration
Add your URL scheme to `ios/Runner/Info.plist`:
```xml
CFBundleURLTypesCFBundleTypeRoleEditorCFBundleURLSchemesyourapp
```
## External Wallet Authentication
Para v2 provides a unified method for external wallet authentication:
```dart
import 'package:para/para.dart';
Future authenticateWithExternalWallet(
String externalAddress,
String walletType,
) async {
try {
// Login with external wallet address
await para.loginExternalWallet(
externalAddress: externalAddress,
type: walletType, // "EVM" or "SOLANA"
);
// Check if authentication was successful
final isActive = await para.isSessionActive().future;
if (isActive) {
final wallets = await para.fetchWallets().future;
print('Authenticated with ${wallets.length} wallets');
}
} catch (e) {
print('External wallet authentication failed: $e');
}
}
```
## Working with Specific Wallets
Para provides dedicated connectors for popular external wallets:
### MetaMask Integration
Para includes a MetaMask connector for EVM interactions:
```dart
import 'package:para/para.dart';
class MetaMaskService {
late ParaMetaMaskConnector _connector;
void initialize() {
_connector = ParaMetaMaskConnector(
para: para,
appUrl: 'https://yourapp.com',
appScheme: 'yourapp',
);
}
Future connectMetaMask() async {
try {
await _connector.connect();
print('MetaMask connected');
} catch (e) {
print('Failed to connect MetaMask: $e');
}
}
Future signMessage(String message) async {
if (_connector.accounts.isEmpty) {
throw Exception('No accounts connected');
}
final signature = await _connector.signMessage(
message,
_connector.accounts.first,
);
return signature;
}
Future sendTransaction({
required String toAddress,
required BigInt value,
}) async {
if (_connector.accounts.isEmpty) {
throw Exception('No accounts connected');
}
final transaction = Transaction(
from: EthereumAddress.fromHex(_connector.accounts.first),
to: EthereumAddress.fromHex(toAddress),
value: EtherAmount.inWei(value),
maxGas: 100000,
gasPrice: EtherAmount.inWei(BigInt.from(20000000000)), // 20 Gwei
);
final txHash = await _connector.sendTransaction(
transaction,
_connector.accounts.first,
);
return txHash;
}
}
```
### Phantom Integration
Para includes a Phantom connector for Solana interactions:
```dart
import 'package:para/para.dart';
import 'package:solana_web3/solana_web3.dart';
class PhantomService {
late ParaPhantomConnector _connector;
void initialize() {
_connector = ParaPhantomConnector(
para: para,
appUrl: 'https://yourapp.com',
appScheme: 'yourapp',
);
}
Future connectPhantom() async {
try {
await _connector.connect();
print('Phantom connected');
} catch (e) {
print('Failed to connect Phantom: $e');
}
}
Future signMessage(String message) async {
final signature = await _connector.signMessage(message);
return signature;
}
/// Sign a transaction using serialized transaction bytes
/// Returns: Base58 encoded signed transaction that must be sent to the network
Future signTransactionBytes(Uint8List transactionBytes) async {
final signedTxBase58 = await _connector.signTransactionBytes(transactionBytes);
return signedTxBase58;
}
}
```
## Example: Complete External Wallet Flow
Here's a complete example showing external wallet authentication and usage:
```dart
import 'package:flutter/material.dart';
import 'package:para/para.dart';
class ExternalWalletScreen extends StatefulWidget {
@override
_ExternalWalletScreenState createState() => _ExternalWalletScreenState();
}
class _ExternalWalletScreenState extends State {
ParaMetaMaskConnector? _metamaskConnector;
ParaPhantomConnector? _phantomConnector;
bool _isConnected = false;
@override
void initState() {
super.initState();
_initializeConnectors();
}
void _initializeConnectors() {
_metamaskConnector = ParaMetaMaskConnector(
para: para,
appUrl: 'https://yourapp.com',
appScheme: 'yourapp',
);
_phantomConnector = ParaPhantomConnector(
para: para,
appUrl: 'https://yourapp.com',
appScheme: 'yourapp',
);
}
Future _connectMetaMask() async {
try {
await _metamaskConnector!.connect();
setState(() => _isConnected = true);
// Check if Para session is active after connection
final isActive = await para.isSessionActive().future;
if (isActive) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('MetaMask connected and authenticated with Para!')),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to connect MetaMask: $e')),
);
}
}
Future _connectPhantom() async {
try {
await _phantomConnector!.connect();
setState(() => _isConnected = true);
// Check if Para session is active after connection
final isActive = await para.isSessionActive().future;
if (isActive) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Phantom connected and authenticated with Para!')),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to connect Phantom: $e')),
);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('External Wallets')),
body: Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
Text(
'Connect an external wallet to get started',
style: Theme.of(context).textTheme.headlineSmall,
),
SizedBox(height: 32),
// MetaMask Connection
ElevatedButton.icon(
onPressed: _connectMetaMask,
icon: Icon(Icons.account_balance_wallet),
label: Text('Connect MetaMask'),
style: ElevatedButton.styleFrom(
minimumSize: Size(double.infinity, 50),
),
),
SizedBox(height: 16),
// Phantom Connection
ElevatedButton.icon(
onPressed: _connectPhantom,
icon: Icon(Icons.account_balance_wallet),
label: Text('Connect Phantom'),
style: ElevatedButton.styleFrom(
minimumSize: Size(double.infinity, 50),
),
),
],
),
),
);
}
}
```
## Security Considerations
When working with external wallets:
1. **Validate Connections**: Always verify the wallet connection before performing operations
2. **Handle Errors Gracefully**: External wallet apps may not be installed or might reject connections
3. **User Experience**: Provide clear instructions for users on wallet installation and connection
4. **Permissions**: Ensure your app requests appropriate permissions for wallet interactions
5. **Deep Link Security**: Validate deep link callbacks to prevent malicious redirects
## Troubleshooting
Common issues and solutions:
### Connection Failures
```dart
Future connectWithRetry(VoidCallback connectFunction) async {
int retries = 3;
while (retries > 0) {
try {
await connectFunction();
return;
} catch (e) {
retries--;
if (retries == 0) {
throw Exception('Failed to connect after 3 attempts: $e');
}
await Future.delayed(Duration(seconds: 2));
}
}
}
```
### Phantom Transaction Errors
When working with Phantom, use the transaction helper for proper encoding:
```dart
import 'package:para/para.dart';
// Convert signed transaction for network submission
final signedTxBase64 = ParaPhantomTransactionHelper.signedTransactionToBase64(signedTxBase58);
// Send to Solana network
final signature = await solanaClient.rpcClient.sendTransaction(signedTxBase64);
```
### Wallet App Not Installed
```dart
Future isWalletInstalled(String walletScheme) async {
try {
return await canLaunchUrl(Uri.parse('$walletScheme://'));
} catch (e) {
return false;
}
}
Future openWalletInstallPage(String storeUrl) async {
if (await canLaunchUrl(Uri.parse(storeUrl))) {
await launchUrl(Uri.parse(storeUrl));
}
}
```
### Network Issues
Ensure you have the correct network selected in your external wallet:
- **Phantom**: Check that you're on the intended Solana network (mainnet-beta, devnet, testnet)
- **MetaMask**: Verify you're connected to the correct Ethereum network
- **Transactions**: Use testnet/devnet for development to avoid spending real funds
## Key Features
Para's Flutter SDK provides:
- **Unified Architecture**: External wallets integrate seamlessly with Para's unified wallet system
- **Direct Blockchain Access**: Uses web3dart for Ethereum and solana_web3 for Solana interactions
- **Deep Link Support**: Handles wallet app redirections automatically
- **Transaction Helpers**: Utility classes for transaction encoding/decoding
- **Error Handling**: Comprehensive error handling for wallet interactions
⚠️ Important Notes
- External wallets use blockchain packages directly for maximum compatibility
- The Flutter SDK maintains these dependencies to support external wallet features
- Phantom's `signAndSendTransaction` method is deprecated - use `signTransactionBytes` instead
- Always validate wallet connections before performing operations
## Resources
For more information about external wallet integration:
# Wallet Pregeneration
Source: https://docs.getpara.com/v2/flutter/guides/pregen
import { Link } from '/snippets/v2/components/ui/link.mdx';
Para's Wallet Pregeneration feature allows you to create wallets for users before they authenticate, giving you control over when and how users claim ownership of their wallets. This is particularly powerful in mobile applications, where you can leverage device-specific storage capabilities for enhanced user experiences.
## Mobile-Specific Benefits
While pregeneration works the same across all Para SDKs, Flutter applications offer unique advantages:
Pregeneration is especially valuable for devices that may not have full WebAuthn support for passkeys. It allows you to create Para wallets for users on any device while managing the security of the wallet yourself.
## Creating Pregenerated Wallets
In Flutter, you can create pregenerated wallets of multiple types with a single method call:
```dart
import 'package:para/para.dart';
Future> createPregenWallets() async {
final pregenWalletsFuture = para.createPregenWalletPerType(
pregenId: {'EMAIL': 'user@example.com'}, // Map format for pregen ID
types: [WalletType.evm], // Optionally specify wallet types
);
final pregenWallets = await pregenWalletsFuture.future;
// Get the user share
final userShareFuture = para.getUserShare();
final userShare = await userShareFuture.future;
// Store user share securely (see storage options below)
return pregenWallets;
}
```
## Mobile Storage Options
In Flutter applications, you have several options for securely storing the user share:
```dart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
final storage = FlutterSecureStorage();
// Store the user share
Future storeUserShare(String userShare) async {
try {
await storage.write(
key: 'para_user_share',
value: userShare,
);
} catch (e) {
// Handle error
}
}
// Retrieve the user share
Future retrieveUserShare() async {
try {
return await storage.read(key: 'para_user_share');
} catch (e) {
// Handle error
return null;
}
}
```
```dart
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
final encryptedPrefs = EncryptedSharedPreferences();
// Store the user share
Future storeUserShare(String userShare) async {
try {
await encryptedPrefs.setString('para_user_share', userShare);
} catch (e) {
// Handle error
}
}
// Retrieve the user share
Future retrieveUserShare() async {
try {
return await encryptedPrefs.getString('para_user_share');
} catch (e) {
// Handle error
return null;
}
}
```
Whichever storage method you choose, ensure you implement proper security measures. The user share is critical for wallet access, and if lost, the wallet becomes permanently inaccessible.
## Using Pregenerated Wallets in Flutter Apps
Once you have created a pregenerated wallet and stored the user share, you can use it for signing operations:
```dart
import 'dart:convert';
import 'package:para/para.dart';
Future usePregenWallet(String walletId) async {
// Retrieve the user share from your secure storage
final userShare = await retrieveUserShare();
if (userShare == null) {
throw Exception("User share not found");
}
// Load the user share into the Para client
await para.setUserShare(userShare);
// Now you can perform signing operations
final messageBase64 = base64Encode(utf8.encode("Hello, World!"));
final signatureFuture = para.signMessage(
walletId: walletId,
messageBase64: messageBase64,
);
final signatureResult = await signatureFuture.future;
// The signature is in the signedTransaction property
return (signatureResult as SuccessfulSignatureResult).signedTransaction;
}
```
### Mobile-Specific Use Cases
Create wallets that are bound to a specific device by using device-specific identifiers combined with secure local storage. This approach is ideal for multi-device users who need different wallets for different devices.
```dart
import 'package:device_info_plus/device_info_plus.dart';
import 'package:para/para.dart';
Future> createDeviceWallet() async {
final deviceInfo = DeviceInfoPlugin();
String deviceId;
if (Platform.isAndroid) {
final androidInfo = await deviceInfo.androidInfo;
deviceId = androidInfo.id;
} else if (Platform.isIOS) {
final iosInfo = await deviceInfo.iosInfo;
deviceId = iosInfo.identifierForVendor ?? 'unknown';
} else {
deviceId = 'unknown';
}
final pregenWalletsFuture = para.createPregenWalletPerType(
pregenId: {'CUSTOM_ID': 'device-$deviceId'},
);
final pregenWallets = await pregenWalletsFuture.future;
// Store the user share in device-specific secure storage
final userShare = await para.getUserShare().future;
await storeUserShare(userShare);
return pregenWallets;
}
```
Seamlessly introduce blockchain functionality to your existing app users without requiring them to understand wallets or crypto.
```dart
import 'package:para/para.dart';
Future> createWalletForExistingUser(String userId) async {
final pregenIdentifier = "user-$userId";
try {
final pregenWalletsFuture = para.createPregenWalletPerType(
pregenId: {'CUSTOM_ID': pregenIdentifier},
);
final pregenWallets = await pregenWalletsFuture.future;
final userShare = await para.getUserShare().future;
await storeUserShare(userShare);
return pregenWallets;
} catch (e) {
// Handle errors
throw e;
}
}
```
## Claiming Pregenerated Wallets
When a user is ready to take ownership of their pregenerated wallet, they can claim it once they've authenticated with Para:
```dart
import 'package:para/para.dart';
Future claimWallet(Map pregenId) async {
// Ensure user is authenticated
if (!(await para.isSessionActive().future)) {
throw Exception("User must be authenticated to claim wallets");
}
// Retrieve and load the user share
final userShare = await retrieveUserShare();
if (userShare != null) {
await para.setUserShare(userShare);
}
// Claim the wallet with the pregen ID
final claimFuture = para.claimPregenWallets(
pregenId: pregenId, // e.g., {'EMAIL': 'user@example.com'}
);
final recoverySecret = await claimFuture.future;
// Optionally, clear the locally stored user share after claiming
// since Para now manages it through the user's authentication
await clearUserShare();
return recoverySecret;
}
```
After claiming, Para will manage the user share through the user's authentication methods. You can safely remove the user share from your local storage if you no longer need to access the wallet directly.
## Best Practices for Mobile
1. **Utilize Device Security**: Leverage biometric authentication (TouchID/FaceID) to protect access to locally stored user shares.
2. **Implement Device Sync**: For users with multiple devices, consider implementing your own synchronization mechanism for user shares across devices.
3. **Handle Offline States**: Mobile applications often work offline. Design your pregenerated wallet system to function properly even when connectivity is limited.
4. **Backup Strategies**: Provide users with options to back up their wallet data, especially for device-specific wallets that might not be associated with their Para account.
5. **Clear Security Boundaries**: Clearly communicate to users when they're using an app-managed wallet versus a personally-owned wallet.
## Related Resources
# Flutter Session Management
Source: https://docs.getpara.com/v2/flutter/guides/sessions
import { Link } from '/snippets/v2/components/ui/link.mdx';
import { Card } from '/snippets/v2/components/ui/card.mdx';
Para provides a comprehensive set of methods for managing authentication sessions in Flutter applications. These sessions are crucial for secure transaction signing and other authenticated operations.
## Session Duration
The Para session length is `2 hours` by default, but can be configured to up to 30 days. To configure this parameter, please visit the Configuration section of the . A user signing a message or transaction extends the session by the duration of the session length.
## Managing Sessions
### Checking Session Status
Use `isSessionActive()` to verify whether a user's session is currently valid before performing authenticated operations.
```dart
Future isSessionActive()
```
In Flutter applications, it's especially important to check the session status before allowing users to access authenticated areas of your app due to the persistence of local storage between app launches.
Example usage:
```dart
import 'package:para/para.dart';
Future checkSession() async {
try {
final isActive = await para.isSessionActive().future;
if (!isActive) {
// First clear any existing data
await para.logout().future;
// Navigate to login screen
// Handle navigation according to your app's navigation strategy
} else {
// Session is valid, proceed with app flow
// Navigate to authenticated part of your app
}
} catch (e) {
// Handle error
}
}
```
### Refreshing Expired Sessions
When a session has expired, Para recommends initiating a full authentication flow rather than trying to refresh the session.
For Flutter applications, always call `logout()` before reinitiating authentication when a session has expired to ensure all stored data is properly cleared.
```dart
import 'package:para/para.dart';
Future handleSessionExpiration() async {
// When session expires, first clear storage
await para.logout().future;
// Then redirect to authentication screen
// Handle navigation according to your app's navigation strategy
}
```
## Exporting Sessions to Your Server
Use `exportSession()` when you need to transfer session state to your server for performing operations on behalf of the user.
```dart
String exportSession()
```
Example implementation:
```dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:para/para.dart';
Future