# 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` |
### External Wallet Connector Packages
| 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; // set true only when rendering your own ParaModal
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. REST API — Default for Server-Side Wallets
**When a backend needs to create wallets or sign on behalf of users (pre-created wallets, agent wallets, automated signing), use the REST API.** It is the recommended path for nearly all server-side integrations: no SDK to install, no user share to store or encrypt, no MPC ceremony to run per signature. Para's enclave holds the key material; your server holds only an API key.
Only fall back to SDK-based pregeneration when the flow requires direct user-share control (see Section 7).
For typed TypeScript access, use `@getpara/rest-sdk`. Otherwise call the HTTP API from any language.
### 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)
```
The API also has `sign-transaction`, `sign-message`, `sign-typed-data`, `sign-authorization`, `transfer`, `balance`, `transactions`, and `estimate-fee` endpoints under `/v1/wallets/{walletId}/`. Full spec: https://docs.getpara.com/openapi.yaml
### Create Wallet Request
```json
{
"type": "EVM",
"userIdentifier": "alice@example.com",
"userIdentifierType": "EMAIL",
"scheme": "DKLS"
}
```
- `type`: `"EVM"` | `"SOLANA"` | `"COSMOS"` | `"STELLAR"`
- `userIdentifierType`: `"EMAIL"` | `"PHONE"` | `"CUSTOM_ID"` | `"GUEST_ID"` | `"TELEGRAM"` | `"DISCORD"` | `"TWITTER"` | `"FARCASTER"`
- `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;
}
```
### Wallet Claiming
Wallets created over REST are claimable the same way as SDK pregen wallets: when a user signs up through the client SDK with the same identifier (e.g., the same email), the wallet transfers to their account automatically. No server-side action is required.
---
## 4. 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
```
---
## 5. 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);
}, []);
```
---
## 6. 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"
);
```
---
## 7. Advanced: SDK Wallet Pregeneration
**Most server integrations should use the REST API (Section 3) instead of this section.** SDK pregeneration requires you to store, encrypt, and restore the wallet's user share yourself, and to run an MPC ceremony for every signature. Use it only when the flow needs direct user-share control — for example, signing with SDK ecosystem integrations (ethers, viem, @solana/web3.js adapters) before the wallet is claimed. Existing SDK pregen integrations can move over: https://docs.getpara.com/v3/rest/migrate-from-sdk-pregen
### 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]
);
```
---
## 8. 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.
---
## 9. 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`.
---
## 10. 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();
```
---
## 11. 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`.
---
## 12. 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.
---
## 13. 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).
---
## 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.
`ParaProvider` renders an embedded `ParaModal` by default. Do not render a separate ` ` unless `config.disableEmbeddedModal` is set to `true`.
### 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. `@getpara/react-sdk` includes `@getpara/viem-v2-integration`; install `viem` when your app imports Viem helpers directly:
- `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 |
---
---
# Agentic Development
Source: https://docs.getpara.com/v3/cli/agentic-development
import { Link } from "/snippets/v3/components/ui/link.mdx";
Use the Para CLI with Claude, Codex, Cursor, Windsurf, and other coding agents to manage project setup, API keys, and integration checks from the terminal.
## What You'll Learn
- How to give an agent a safe Para CLI workflow
- Which CLI commands are useful for setup, key management, and diagnostics
- How agents can inspect the command catalog instead of guessing command syntax
- Which operations should require your approval before the agent runs them
## Start With CLI Context
Install and authenticate the CLI before asking an agent to configure Para:
```bash
npm install -g @getpara/cli
para login
para whoami
```
Then let the agent inspect the machine-readable command catalog:
```bash
para commands --json
```
The catalog includes command paths, arguments, options, JSON support, interactivity, side effects, and examples. Agents should use it before running unfamiliar `para` commands.
For broader Para context, give your agent the or connect the .
## Prompt Claude or Codex
Use a prompt that gives the agent a clear boundary:
```text
Use the Para CLI to help me finish my Para integration.
First run read-only commands:
- para commands --json
- para auth status --json
- para whoami --json
- para keys list --json
- para keys config show --json
Then propose the exact commands needed to configure my local app.
Ask before creating, rotating, archiving, or changing API keys or projects.
Do not print secret keys in the chat unless I explicitly ask.
```
For a new Next.js app, ask for a full setup path:
```text
Use the Para CLI to scaffold a Next.js app with EVM and Solana support.
After scaffolding, connect it to my selected Para project, write the API key to the app env file, and run para doctor on the result.
Use JSON output where available and explain any command that needs my approval before running it.
```
For an existing app, ask the agent to inspect first:
```text
Use the Para CLI to audit this existing Para integration.
Run para whoami, para keys config show --json, and para doctor.
Compare the API key config to the app's env vars and provider setup.
Recommend fixes before changing files or API key configuration.
```
## Common Agent Workflows
### Confirm Context
```bash
para auth status --json
para whoami --json
para orgs list --json
para projects list --json
para keys list --json
```
Use these commands before any mutation so the agent knows which organization, project, and key environment are active.
### Save Project Defaults
```bash
para orgs switch
para projects switch
para init --yes
```
`para init --yes` writes `.pararc` for the current directory using the resolved org, project, and key environment. Commit `.pararc` only if your team wants shared project defaults, and never store secrets in it.
### Manage API Key Settings
```bash
para keys config show --json
para keys config security --origins "https://app.example.com,http://localhost:3000"
para keys config auth --oauth-methods "GOOGLE,APPLE"
para keys config setup --wallet-types "EVM,~SOLANA"
para keys config webhooks --url "https://api.example.com/webhooks" --events "user.created,wallet.created" --enabled
```
Use category commands for focused changes. Most `para keys config` category commands can run interactively or with flags.
### Diagnose an Integration
```bash
para doctor
para doctor --json
para doctor --category configuration
para doctor --severity error
```
Agents can use `para doctor --json` to identify missing env vars, framework-specific setup issues, API key mismatches, duplicate modal instances, package issues, and other integration problems.
## Use JSON Safely
Many commands support `--json` for agent and CI workflows. Some commands require enough non-interactive flags before JSON output is allowed:
| Command | Required for non-interactive JSON |
|---|---|
| `para init --json` | Add `--yes` |
| `para create --json` | Provide an app name plus `--networks` for Next.js or `-t expo --bundle-id` for Expo |
| `para projects create --json` | Add `--name` |
| `para projects archive --json` | Add `--yes` |
| `para keys create --json` | Add `--name` |
| `para keys rotate --json` | Add `--yes` |
| `para keys archive --json` | Add `--yes` |
| `para keys config --json` | Provide at least one category flag |
| `para update --json` | Add `--check` or `--yes` |
Treat key rotation, key archival, project archival, migration apply, rollback apply, and CLI updates as approval-required operations. They can change live configuration or local files.
## Keep Secrets Out of Chat
- Prefer `para keys get --copy` when you need the public API key locally.
- Use `para keys get --copy-secret` only when you intentionally need the secret key.
- Avoid pasting secret keys into an agent conversation.
- Ask the agent to summarize configuration without printing full secrets.
## Useful Links
-
-
-
-
# Command Reference
Source: https://docs.getpara.com/v3/cli/commands
import { Link } from "/snippets/v3/components/ui/link.mdx";
## Global Options
These options work with all commands:
| Flag | Description |
|---|---|
| `-e, --environment ` | Key environment: `beta` or `prod` (default: `beta`) |
| `--json` | Output JSON when the command supports it |
| `--org ` | Override the active organization |
| `--project ` | Override the active project |
| `-q, --quiet` | Suppress non-essential output |
Use `para commands --json` to print the agent-readable command catalog. It includes command paths, arguments, options, examples, interactivity, JSON support, and side effects.
## Authentication
The full command paths are `para auth login`, `para auth logout`, and `para auth status`. `para login` and `para logout` are top-level shortcuts.
### `para login`
Authenticate with your Para developer account. Opens your browser for a secure OAuth flow with PKCE verification.
```bash
para login
```
After login, the CLI automatically selects your first organization and project if none are configured.
Sessions are stored at `~/.config/para/credentials.json`. 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](/v3/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
| Global Key | Project `.pararc` Key | Values |
|---|---|---|
| `defaultEnvironment` | `environment` | `beta`, `prod` |
| `defaultOrganizationId` | `organizationId` | Any valid organization ID |
| `defaultProjectId` | `projectId` | 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` accepts either global key names such as `defaultEnvironment` or project key names such as `environment`, then writes the `.pararc` key form.
### `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 |
| `-y, --yes` | Skip prompts and use the resolved defaults |
In interactive mode, you'll be prompted to select a key environment (`beta` or `prod`). With `--yes`, the current resolved key environment 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`, `auth`, `modal`, `external-wallets`, `links`, `app`, `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` |
| `--mode ` | Theme mode: `light` or `dark` |
| `--border-radius ` | Border radius |
| `--foreground-mix-ratio ` | Foreground mix ratio from `0` to `1` |
| `--css-override ` | Repeatable CSS override for supported modal CSS variables |
| `--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 |
#### Authentication
Configure login methods, OAuth providers, 2FA, and guest mode.
```bash
para keys config auth [key-id]
```
| Flag | Description |
|---|---|
| `--oauth-methods ` | Comma-separated OAuth methods |
| `--disable-email-login` / `--no-disable-email-login` | Disable or enable email login |
| `--disable-phone-login` / `--no-disable-phone-login` | Disable or enable phone login |
| `--two-factor-auth` / `--no-two-factor-auth` | Enable or disable two-factor auth |
| `--guest-mode` / `--no-guest-mode` | Enable or disable guest mode |
```bash
para keys config auth --oauth-methods "GOOGLE,APPLE" --two-factor-auth
```
#### Custom OIDC
Configure your own OpenID Connect provider as a login method. See [Set up Custom OIDC](/v3/general/developer-portal-custom-oidc) for the full walkthrough.
```bash
para keys config oidc set [key-id]
```
| Flag | Description |
|---|---|
| `--issuer ` | Provider issuer URL (HTTPS, no query string) |
| `--client-id ` | OIDC client ID |
| `--scopes ` | Space-separated scopes (defaults to `openid email profile`) |
| `--auth-method ` | Token endpoint auth: `client_secret_post`, `client_secret_basic`, or `none` |
| `--label ` | Sign-in label shown on the login button |
```bash
# Configure the provider
para keys config oidc set --issuer https://idp.example.com --client-id my-client --label "Sign in with Acme"
# Store the client secret (prompted; never echoed or written to .pararc)
para keys config oidc set-secret
# Verify Para can reach the provider
para keys config oidc verify
# Show the current config (and whether a secret is set)
para keys config oidc show
```
Enable Custom OIDC once configured with `para keys config auth --oauth-methods "...,CUSTOM_OIDC"`.
#### Modal
Configure modal UI behavior for an API key.
```bash
para keys config modal [key-id]
```
| Flag | Description |
|---|---|
| `--hide-wallets` / `--no-hide-wallets` | Hide or show wallet terminology and displays |
| `--auth-layout ` | Comma-separated auth layout slots |
| `--hide-logo` / `--no-hide-logo` | Hide or show the partner logo |
| `--disable-add-funds-prompt` / `--no-disable-add-funds-prompt` | Disable or enable the add-funds prompt |
```bash
para keys config modal --hide-wallets --auth-layout "AUTH:FULL,EXTERNAL:FULL"
```
#### External Wallets
Configure external wallet support for an API key.
```bash
para keys config external-wallets [key-id]
```
| Flag | Description |
|---|---|
| `--wallet-connect-project-id ` | WalletConnect project ID |
| `--app-description ` | App description shown to wallet users |
| `--wallets ` | Comma-separated external wallet IDs |
| `--mode ` | External wallet connection mode |
```bash
para keys config external-wallets --wallets "METAMASK,RAINBOW" --mode para-account
```
#### Links
Configure partner social and support links.
```bash
para keys config links [key-id]
```
| Flag | Description |
|---|---|
| `--x-url ` | X/Twitter profile URL |
| `--linkedin-url ` | LinkedIn company URL |
| `--github-url ` | GitHub URL |
| `--homepage-url ` | Homepage URL (HTTPS) |
```bash
para keys config links --homepage-url "https://example.com" --github-url "https://github.com/example"
```
#### App
Configure app-level settings.
```bash
para keys config app [key-id]
```
| Flag | Description |
|---|---|
| `--rpc-url ` | RPC URL |
| `--display-name ` | Display name |
| `--farcaster-mini-app-url ` | Farcaster mini-app URL |
| `--farcaster-splash-image-url ` | Farcaster splash image URL |
| `--farcaster-splash-background-color ` | Farcaster splash background color |
```bash
para keys config app --display-name "My App" --rpc-url "https://rpc.example.com"
```
#### 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 |
| `--send-enabled` / `--no-send-enabled` | Toggle Send |
| `--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.
---
## Users
### `para users list`
List users for the active project API key. The CLI resolves the API key from the active project and key environment unless you pass `--key`.
```bash
para users list
```
| Flag | Description |
|---|---|
| `--limit ` | Number of users to fetch per page (default: 25, max: 100) |
| `--page ` | Page number to fetch (default: 1) |
| `--all` | Fetch all pages |
| `--method ` | Comma-separated login method filter |
| `--key ` | API key ID override |
```bash
para users list --key abc-123
para users list --method email,google
para users list --json
```
---
## 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 (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 with `--networks` for Next.js or with `-t expo --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 |
| Duplicate ParaModal instances | Setup | A separate ` ` is not rendered while `ParaProvider`'s embedded modal is enabled |
| 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
✔ No duplicate ParaModal instances 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
8 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.
---
## Migrations
### `para migrate v3`
Plan or apply a Para v2 to v3 app migration. The command inspects local project files and can include API key configuration updates when you pass `--key`.
```bash
para migrate v3 [path]
```
| Argument | Description | Default |
|---|---|---|
| `[path]` | Project path to migrate | `.` |
| Flag | Description |
|---|---|
| `--dry-run` | Plan changes without writing local files or API key config |
| `--apply` | Apply approved local and API key changes |
| `--key ` | API key ID to migrate |
| `--target-version ` | Target `@getpara` package version (default: `3.0.0`) |
| `--allow-dirty` | Allow a dirty git tree after enumerating dirty files |
| `--manifest ` | Manifest path for apply or dry-run output |
```bash
para migrate v3
para migrate v3 ./apps/web --dry-run
para migrate v3 ./apps/web --key key_123 --target-version 3.0.0
```
Review the migration plan before using `--apply`. Applying a migration can change local project files and API key configuration.
### `para migrate rollback`
Roll back a Para v3 migration manifest.
```bash
para migrate rollback
```
| Flag | Description |
|---|---|
| `--manifest ` | Migration manifest path |
| `--apply` | Apply rollback instead of printing a dry run |
```bash
para migrate rollback --manifest .para-migrate-v3.json
para migrate rollback --manifest .para-migrate-v3.json --apply
```
---
## Command Catalog
### `para commands`
Print the CLI command catalog. Use `--json` for deterministic metadata that agents and scripts can inspect without scraping help text.
```bash
para commands
para commands --json
```
The JSON catalog includes command paths, arguments, options, examples, requirements, read/write side effects, interactivity, JSON support, output shape, and related Developer Portal area.
See for Claude, Codex, and automation workflows that use the catalog.
---
## Updating the CLI
### `para update`
Check for and explicitly update the Para CLI.
```bash
para update
```
| Flag | Description |
|---|---|
| `--manager ` | Package manager to use: `npm`, `yarn`, or `pnpm` |
| `-y, --yes` | Skip confirmation and run the update |
| `--check` | Check for updates without installing |
```bash
para update
para update --yes
para update --manager pnpm --yes
para update --check --json
```
The CLI also performs a notice-only update check during normal human-readable commands. Set `PARA_DISABLE_UPDATE_CHECK=1` to disable update checks and explicit update installs.
# Installation & Setup
Source: https://docs.getpara.com/v3/cli/installation
import { Card } from "/snippets/v3/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 starts a Developer Portal sign-in flow and prints the browser instructions in your terminal. Once you approve, the CLI stores your session locally at `~/.config/para/credentials.json`.
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](/v3/cli/commands#configuration) page for details.
Use `para init --yes` when you already have the right org, project, and key environment selected and want to skip the environment prompt.
### 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 |
| `--org ` | Override the active organization |
| `--project ` | Override the active project |
The CLI does not use a global no-input flag. Commands that can run non-interactively expose their own required flags, such as `para init --yes`, `para keys rotate --yes`, or `para create my-app -t nextjs --networks evm`.
## Next Steps
# Para CLI
Source: https://docs.getpara.com/v3/cli/overview
import { Card } from "/snippets/v3/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 and agentic tools with `--json`, command-specific non-interactive flags, and the `para commands --json` catalog.
- **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/v3/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](/v3/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](/v3/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)](/v3/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](/v3/react/setup/vite), [Next.js](/v3/react/setup/nextjs), [Vue](/v3/vue/setup/vite), [Svelte](/v3/svelte/setup/vite), vanilla JavaScript
- **Mobile:** [React Native](/v3/react-native/setup/react-native), [Flutter](/v3/flutter/setup), [Swift](/v3/swift/setup)
- **Server:** [Node.js](/v3/server/setup) and [REST API](/v3/rest/overview)
- **CLI:** [Command-line SDK](/v3/cli/overview) for scripting and automation
See the [quickstart guides](/v3/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](/v3/introduction/chain-support).
## Architecture FAQs
Yes. Para is designed to work with [ERC-4337 (Account Abstraction)](/v3/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](/v3/introduction/chain-support).
# Audits & Compliance
Source: https://docs.getpara.com/v3/concepts/compliance
import { Link } from '/snippets/v3/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](/v3/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/v3/concepts/key-management
import { Link } from '/snippets/v3/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](/v3/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](/v3/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/v3/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](/v3/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 Types
| Type | Description |
|------|-------------|
| `STATIC` | Evaluates the transaction data at request time without storing any additional state |
| `WINDOWED_SPEND_LIMIT` | Tracks cumulative automatic signing attempts for a fixed time window before signing |
`WINDOWED_SPEND_LIMIT` applies to EVM direct native transfers and direct ERC-20 `transfer(address,uint256)` calls. The limit is scoped by API key, wallet, chain, asset, and window length. Gas and fees are not included in the amount counted against the limit.
Transactions that exceed the active window return `POLICY_DENIED` before signing. Para does not create a pending transaction review for windowed spend denials.
## 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** | Static 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 |
| **Windowed spend limits** | Windowed spend conditions support native EVM transfers and direct ERC-20 `transfer(address,uint256)` calls. They do not aggregate spend across assets, normalize to USD, include gas or fees, or cover ERC-20 approvals, `transferFrom`, router calls, multicalls, or non-transfer contract calls |
## 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/v3/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](/v3/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 Advisories
Source: https://docs.getpara.com/v3/concepts/security-advisories
import { Link } from '/snippets/v3/components/ui/link.mdx';
When a broad ecosystem supply-chain incident makes the news — a poisoned npm package, a compromised container image, a backdoored build tool — Para investigates whether the event affects our systems or the SDKs we ship. This page records each notable event we assess, where we are in the investigation, and whether there was any impact.
This page covers **third-party and supply-chain events** in the wider ecosystem. For the live availability of Para-hosted services, see .
## How to read an advisory
Each advisory carries one of the following statuses:
| Status | Meaning |
| --- | --- |
| **Investigating** | Para is aware of the event and is actively assessing potential impact. |
| **Resolved — No Impact** | Assessment complete. No Para systems, build artifacts, or shipped SDKs were affected. |
| **Resolved — Action Taken** | Assessment complete. Para was affected to some degree and has remediated; the entry describes what we did and any action customers should take. |
## Advisories
**Status:** Resolved — No Impact
**Summary:** On May 11, 2026, an attacker published 84 malicious versions across 42 `@tanstack/*` npm packages. The compromise was part of the self-propagating "Mini Shai-Hulud" worm that affected 160+ npm and PyPI packages across multiple organizations, and was carried out via GitHub Actions cache poisoning and theft of an OIDC publishing token rather than stolen npm credentials. See the for the full technical write-up.
**Para's assessment:** Para's Web SDK depends on several `@tanstack/*` packages, so we reviewed our exposure immediately. Para pins exact resolved dependency versions in committed lockfiles, and none of the compromised versions were ever resolved or installed in our builds or CI. No Para systems, build artifacts, or published SDK releases were affected.
**Customer action:** None required to continue using Para. If you install `@tanstack/*` packages directly in your own application, we recommend auditing your lockfiles against the affected versions listed in the postmortem.
Para updates this page as new ecosystem events are assessed. To report a suspected security issue or ask about a specific advisory or CVE, contact .
# Security
Source: https://docs.getpara.com/v3/concepts/security
import { Link } from '/snippets/v3/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](/v3/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. Session length is configured per API key, enforced by the Para API, and can be adjusted in the [Security section of the Developer Portal](https://developer.getpara.com) or CLI.
## 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. Developers configure session length per API key and can implement [session management logic](/v3/react/guides/sessions) in their applications to maintain active sessions when 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/v3/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/v3/developer-tools/ai-tooling/agent-skill
import { Link } from "/snippets/v3/components/ui/link.mdx";
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
Want a command-by-command workflow for Claude or Codex? See .
## 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"*
After authentication, your agent can use `para` CLI commands for setup, API key configuration, diagnostics, and project context without manually navigating the portal.
## Safe CLI Workflow
Ask your agent to inspect first, then propose changes:
```text
Use the Para CLI to inspect my current project.
Run para commands --json, para auth status --json, para whoami --json, and para keys config show --json.
Summarize the active organization, project, key environment, and API key configuration.
Ask before creating, rotating, archiving, or changing API keys or projects.
```
For a full setup, ask:
```text
Use the Para CLI to finish my Para integration.
If the CLI is missing, install @getpara/cli.
Authenticate with para login, help me select the right org and project, configure the beta API key for local development, and run para doctor.
Use JSON output where the CLI supports it.
```
## 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/v3/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/v3/react/guides/web3-operations/sign-with-para"
},
{
"title": "Web3 Operations Guide",
"snippet": "Complete guide for signing transactions and messages...",
"link": "https://docs.getpara.com/v3/react/guides/web3-operations"
}
]
}
```
# AI Tooling
Source: https://docs.getpara.com/v3/developer-tools/ai-tooling/overview
import { Card } from "/snippets/v3/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.
Use `para commands --json`, CLI setup commands, and safe approval boundaries with Claude, Codex, and other coding agents.
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/v3/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](/v3/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, troubleshoot, and use the CLI for API key configuration |
| **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
Use the CLI with Claude, Codex, and other coding agents
## 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/v3/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 configuration 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/v3/flutter/examples
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para provides a minimal, focused Flutter example demonstrating clean integration patterns with the Flutter 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 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.
# Account Abstraction
Source: https://docs.getpara.com/v3/flutter/guides/account-abstraction
import { Card } from '/snippets/v3/components/ui/card.mdx';
The Flutter SDK can spin up an Alchemy smart account for any EVM wallet your user holds. The Para wallet signs; the smart account is what shows up on-chain. You get gas sponsorship through Alchemy's Gas Manager and atomic batched calls through the Account Kit.
Only Alchemy (EIP-4337) is wired through the native bridge today. ZeroDev, Pimlico, and the other providers already available on web and React Native are on the roadmap for Flutter.
## Setup
1. Create an [Alchemy account](https://dashboard.alchemy.com/signup), grab your **API key**, and create a Gas Manager **policy** for the chain you're targeting. The policy ID is what tells the paymaster to sponsor gas.
2. Drop the values into your `.env` file. `flutter_dotenv` is already wired into the Flutter example:
```bash .env
ALCHEMY_API_KEY=your_alchemy_api_key
ALCHEMY_GAS_POLICY_ID=your_gas_policy_id
```
You don't need to add a pub.dev dependency. The Flutter SDK reaches `@getpara/aa-alchemy` through the bridge on the web side.
## Usage
Create the smart account, then send a gasless transaction with it:
```dart
import 'package:para/para.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
// `para` is your authenticated Para instance
final info = await para.createSmartAccount(
apiKey: dotenv.env['ALCHEMY_API_KEY']!,
chainId: 11155111, // Sepolia
gasPolicyId: dotenv.env['ALCHEMY_GAS_POLICY_ID'],
);
print('Smart account: ${info.smartAccountAddress}');
// Zero-value tx to the burn address, paid for by the paymaster.
final receipt = await para.sendSmartAccountTransaction(
smartAccountAddress: info.smartAccountAddress,
chainId: 11155111,
to: '0x000000000000000000000000000000000000dEaD',
);
print('tx: ${receipt.transactionHash}, status: ${receipt.status}');
```
Heads up on timing: UserOp inclusion on a public chain usually lands in 30 to 90 seconds, and the call blocks until it does. `ParaConfig.requestTimeout` defaults to 120s for this reason. Override it when you build your `ParaConfig` if you'd rather bail earlier.
### Batched transactions
One UserOp, multiple calls, one receipt. Use it for approve + swap, approve + transfer, or anything that should succeed or fail together:
```dart
final batchReceipt = await para.sendSmartAccountBatchTransaction(
smartAccountAddress: info.smartAccountAddress,
chainId: 11155111,
calls: [
SmartAccountCall(to: '0xRecipientA', value: '10000000000000000'), // 0.01 ETH
SmartAccountCall(to: '0xRecipientB', data: '0xEncodedCallData'),
],
);
```
### Error handling
Provider failures come back with a `SmartAccountErrorCode` from `@getpara/core-sdk`. The ones you're most likely to see:
- `PROVIDER_RATE_LIMITED`: back off and retry.
- `SPONSORSHIP_DENIED`: the gas policy rejected this UserOp. Check the Alchemy dashboard.
- `TRANSACTION_REVERTED`: the target contract reverted at execution time.
- `MISSING_ACCOUNT_ADDRESS`: you called `sendSmartAccountTransaction` before `createSmartAccount` for this chain + address.
Errors surface as `ParaBridgeException` and keep the provider's original message.
## What gets returned
`createSmartAccount` returns a `SmartAccountInfo`:
```dart
class SmartAccountInfo {
final String smartAccountAddress;
final String mode; // "4337"
final String provider; // "ALCHEMY"
final int chainId;
}
```
`sendSmartAccountTransaction` and `sendSmartAccountBatchTransaction` return an `AATransactionReceipt`:
```dart
class AATransactionReceipt {
final String transactionHash;
final String blockHash;
final String blockNumber; // uint256, decimal string
final String from;
final String? to;
final String status; // "success" or "reverted"
final String gasUsed;
final String effectiveGasPrice;
}
```
BigInt fields arrive as decimal strings so you don't lose `uint256` precision across the bridge.
## Reference
Full working implementation: [`examples-hub/mobile/with-flutter/lib/features/smart_account/smart_account_screen.dart`](https://github.com/getpara/examples-hub/tree/3.0.0/mobile/with-flutter/lib/features/smart_account).
# Cosmos Integration
Source: https://docs.getpara.com/v3/flutter/guides/cosmos
import { Card } from '/snippets/v3/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/v3/flutter/guides/evm
import { Card } from '/snippets/v3/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/v3/flutter/guides/external-wallets
import EnvironmentInfo from "/snippets/v3/quick-start-environment-info.mdx";
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
## Overview
Para 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](/v3/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
CFBundleURLTypes
CFBundleTypeRole
Editor
CFBundleURLSchemes
yourapp
```
## External Wallet Authentication
Para 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/v3/flutter/guides/pregen
import { Link } from '/snippets/v3/components/ui/link.mdx';
import PregenRestApiCallout from '/snippets/v3/pregen-rest-api-callout.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. In mobile applications you can use device-specific storage for the user share.
## 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/v3/flutter/guides/sessions
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/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
Para session length is configured per API key and can be set up to 30 days through the Configuration section of the or CLI. The Para API enforces the configured duration. Signing a message or transaction, or calling the session keep-alive method, can extend an active session according to that configuration.
## 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> sendSessionToServer() async {
// Export session without signing capabilities
final sessionData = para.exportSession();
// Send to your server
try {
final response = await http.post(
Uri.parse('https://your-api.com/sessions'),
headers: {
'Content-Type': 'application/json',
},
body: jsonEncode({'session': sessionData}),
);
if (response.statusCode != 200) {
throw Exception('Failed to send session to server');
}
return jsonDecode(response.body);
} catch (e) {
// Handle error
throw e;
}
}
```
## Best Practices for Flutter
1. **Check Sessions on App Launch**: Verify session status when your app starts to determine if users need to reauthenticate.
```dart
import 'package:flutter/material.dart';
import 'package:para/para.dart';
// In your app's entry point or state initialization
@override
void initState() {
super.initState();
checkSessionOnLaunch();
}
Future checkSessionOnLaunch() async {
final isActive = await para.isSessionActive().future;
if (isActive) {
// Navigate to authenticated part of your app
} else {
await para.logout().future; // Clear any lingering data
// Navigate to login screen
}
}
```
2. **Handle App Lifecycle Changes**: Flutter apps can be backgrounded and foregrounded, which may affect session status.
```dart
import 'package:flutter/material.dart';
import 'package:para/para.dart';
class YourWidget extends StatefulWidget {
@override
_YourWidgetState createState() => _YourWidgetState();
}
class _YourWidgetState extends State with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// App came to foreground, check session
checkSession();
}
}
Future checkSession() async {
final isActive = await para.isSessionActive().future;
if (!isActive) {
await para.logout().future;
// Navigate to login screen
}
}
@override
Widget build(BuildContext context) {
// Your widget implementation
return Container();
}
}
```
## Next Steps
Explore more advanced features and integrations with Para in Flutter:
# Social Login
Source: https://docs.getpara.com/v3/flutter/guides/social-login
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para treats OAuth the same as email and phone authentication: trigger the flow, reuse the One-Click block, and fall back to
passkey/password only if you explicitly enable them.
Have your own OpenID Connect provider? Once you [set up Custom OIDC](/v3/general/developer-portal-custom-oidc), use `CUSTOM_OIDC` as the OAuth method — it works like any other provider.
## Before You Start
- Complete the base setup so your app has a custom URL scheme and a shared `FlutterWebAuthSession`
([setup guide](/v3/flutter/setup#build-your-authentication-flow)).
- Add social buttons alongside your email/phone inputs; users should see all auth options together.
## Handle Social Login
```dart lib/views/authentication_view.dart
Future handleSocialLogin(OAuthMethod provider) async {
try {
final authState = await para.verifyOAuth(
provider: provider,
appScheme: 'yourapp',
);
if (authState.loginUrl?.isNotEmpty == true) {
await para.presentAuthUrl(
url: authState.loginUrl!,
webAuthenticationSession: webAuthSession,
);
final nextStage = authState.effectiveNextStage;
if (nextStage == AuthStage.signup) {
await para.waitForSignup();
} else {
await para.waitForLogin();
}
await para.touchSession();
await para.fetchWallets();
return;
}
if (authState.stage == AuthStage.login) {
try {
await para.touchSession();
} catch (_) {
// Session refresh is best-effort for OAuth callbacks
}
await para.fetchWallets();
// Navigate to your main app flow
}
} catch (e) {
debugPrint('OAuth login failed: $e');
}
}
```
> Same pattern as the example app (`examples-hub/mobile/with-flutter/lib/screens/auth_screen.dart`).
## Provider Enum Reference
| Provider | Enum |
|----------|------|
| Google | `OAuthMethod.google` |
| Apple | `OAuthMethod.apple` |
| Discord | `OAuthMethod.discord` |
Other providers (Twitter, Facebook, Farcaster, etc.) are not part of the default Flutter example or this guide. Refer to the
API reference to see which ones your project supports today.
## See It in Action
# Solana Integration
Source: https://docs.getpara.com/v3/flutter/guides/solana
import { Card } from '/snippets/v3/components/ui/card.mdx';
## Quick Start
```dart
import 'package:para/para.dart';
// Sign a Solana transaction
final para = Para(apiKey: 'your-api-key');
final wallet = (await para.fetchWallets()).firstWhere((w) => w.type == 'SOLANA');
final transaction = SolanaTransaction(
to: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
lamports: '1000000', // 0.001 SOL
feePayer: null, // Uses wallet as fee payer
recentBlockhash: null, // Fetched automatically with RPC URL
);
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: transaction.toJson(),
chainId: null, // Not needed for Solana
rpcUrl: 'https://api.devnet.solana.com',
);
print('Transaction signed: ${result.signedTransaction}');
```
## Common Operations
### Sign Transaction
```dart
final transaction = SolanaTransaction(
to: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
lamports: '1000000', // 0.001 SOL
feePayer: null, // Uses wallet as fee payer
recentBlockhash: null, // Fetched automatically with RPC URL
);
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: transaction.toJson(),
chainId: null, // Not needed for Solana
rpcUrl: 'https://api.devnet.solana.com',
);
print('Signed: ${result.signedTransaction}');
```
### Check Balance
```dart
final balance = await para.getBalance(
walletId: wallet.id!,
token: null, // Native SOL
rpcUrl: 'https://api.devnet.solana.com',
);
// Convert lamports to SOL
final lamports = double.parse(balance);
final sol = lamports / 1000000000;
print('Balance: ${sol.toStringAsFixed(4)} SOL');
```
### Sign Message
```dart
final message = 'Hello, Solana!';
final result = await para.signMessage(
walletId: wallet.id!,
message: message,
);
print('Signature: ${result.signedTransaction}');
```
## Networks
### Testnets
| Network | RPC URL | Native Token |
|---------|---------|--------------|
| **Devnet** | `https://api.devnet.solana.com` | SOL |
| **Testnet** | `https://api.testnet.solana.com` | SOL |
### Mainnet
| Network | RPC URL | Native Token | Network Type |
|---------|---------|--------------|--------------|
| **Mainnet** | `https://api.mainnet-beta.solana.com` | SOL | Production |
| **Alchemy** | `https://solana-mainnet.g.alchemy.com/v2/YOUR_KEY` | SOL | Production |
## Complete Example
```dart
import 'package:flutter/material.dart';
import 'package:para/para.dart';
class SolanaWalletView extends StatefulWidget {
final Para para;
final Wallet wallet;
const SolanaWalletView({required this.para, required this.wallet});
@override
State createState() => _SolanaWalletViewState();
}
class _SolanaWalletViewState extends State {
bool _isLoading = false;
String? _signature;
final String _rpcUrl = 'https://api.devnet.solana.com';
@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 : _signTransaction,
child: Text(_isLoading ? 'Signing...' : 'Sign Transaction'),
),
if (_signature != null)
Padding(
padding: const EdgeInsets.only(top: 20),
child: Text('Signed: $_signature', style: TextStyle(fontSize: 12)),
),
],
),
);
}
Future _signTransaction() async {
setState(() => _isLoading = true);
try {
final transaction = SolanaTransaction(
to: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
lamports: '1000000', // 0.001 SOL
feePayer: null,
recentBlockhash: null,
);
final result = await widget.para.signTransaction(
walletId: widget.wallet.id!,
transaction: transaction.toJson(),
chainId: null, // Not needed for Solana
rpcUrl: _rpcUrl,
);
setState(() => _signature = result.signedTransaction);
} catch (e) {
print('Error: $e');
} finally {
setState(() => _isLoading = false);
}
}
}
```
## Advanced Transaction Options
```dart
// Transaction with memo
final transaction = SolanaTransaction(
to: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
lamports: '1000000',
memo: 'Payment for services',
);
// Transaction with custom blockhash
final transaction = SolanaTransaction(
to: '9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM',
lamports: '1000000',
recentBlockhash: 'custom_blockhash',
feePayer: wallet.address,
);
```
# Stellar Integration
Source: https://docs.getpara.com/v3/flutter/guides/stellar
Use `WalletType.stellar` to create or load a Stellar wallet, then pass a `StellarTransaction` payload to `signTransaction`.
## Quick start
```dart stellar_signing.dart
import 'package:para/para.dart';
final para = Para(apiKey: 'your-api-key');
Future getOrCreateStellarWallet(Para para) async {
final wallets = await para.fetchWallets();
for (final wallet in wallets) {
if (wallet.type == WalletType.stellar && wallet.id != null) {
return wallet;
}
}
return para.createWallet(
type: WalletType.stellar,
skipDistribute: false,
);
}
final wallet = await getOrCreateStellarWallet(para);
final transaction = StellarTransaction.payment(
to: 'GRECIPIENT_STELLAR_ADDRESS',
amount: '10',
networkPassphrase: StellarNetwork.testnetPassphrase,
memo: const StellarMemo.text('hello'),
);
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: transaction.toJson(),
);
if (result is SuccessfulSignatureResult) {
print('Signed transaction: ${result.signedTransaction}');
}
```
## Display the address
A Stellar wallet uses the same `Wallet` model as the other wallet types. Use `wallet.address` when it is present. If you only have the raw Ed25519 public key, derive the Stellar G-address with `getStellarAddress`.
```dart stellar_address.dart
import 'package:para/para.dart';
final address = wallet.address ??
(wallet.publicKey == null ? null : getStellarAddress(wallet.publicKey!));
print('Stellar address: $address');
```
You can also derive a Stellar address from a Solana-format Ed25519 public key:
```dart stellar_address_from_solana.dart
final stellarAddress = getStellarAddressFromSolana(solanaAddress);
```
## Sign a payment
`StellarTransaction.payment` supports native XLM payments and issued assets. The `networkPassphrase` must match the network the transaction will be submitted to.
```dart stellar_payment.dart
final xlmPayment = StellarTransaction.payment(
to: 'GRECIPIENT_STELLAR_ADDRESS',
amount: '10',
networkPassphrase: StellarNetwork.testnetPassphrase,
fee: '100',
timeout: 60,
);
final signed = await para.signTransaction(
walletId: wallet.id!,
transaction: xlmPayment.toJson(),
);
if (signed is SuccessfulSignatureResult) {
print('Signed transaction: ${signed.signedTransaction}');
}
```
For issued assets, include the asset code and issuer:
```dart stellar_asset_payment.dart
final usdcPayment = StellarTransaction.payment(
to: 'GRECIPIENT_STELLAR_ADDRESS',
amount: '5',
asset: const StellarAsset(
code: 'USDC',
issuer: 'GISSUER_STELLAR_ADDRESS',
),
networkPassphrase: StellarNetwork.publicPassphrase,
);
```
## Sign serialized XDR
If your backend or Stellar SDK code already builds the transaction, pass the serialized XDR directly.
```dart stellar_xdr.dart
final transaction = StellarTransaction.serializedXdr(
'AAAAAgAAA...',
networkPassphrase: StellarNetwork.publicPassphrase,
);
final result = await para.signTransaction(
walletId: wallet.id!,
transaction: transaction.toJson(),
);
if (result is SuccessfulSignatureResult) {
print('Signed transaction: ${result.signedTransaction}');
}
```
## Network passphrases
| Network | Constant |
|---------|----------|
| Stellar Testnet | `StellarNetwork.testnetPassphrase` |
| Stellar Public Network | `StellarNetwork.publicPassphrase` |
# Flutter SDK Overview
Source: https://docs.getpara.com/v3/flutter/overview
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para Flutter SDK eliminates the complexity of blockchain integration by providing a unified interface for wallet creation, authentication, and multi-chain transactions across iOS and Android.
## Quick Start
```dart main.dart
// Initialize Para
final para = Para.fromConfig(
config: ParaConfig(
apiKey: 'YOUR_API_KEY',
environment: Environment.beta,
),
appScheme: 'yourapp',
);
// Prepare a web authentication session for browser-based auth flows
final webAuthSession = FlutterWebAuthSession(
callbackUrlScheme: 'yourapp',
);
// Authenticate user
final authState = await para.initiateAuthFlow(
auth: Auth.email('user@example.com'),
);
switch (authState.stage) {
case AuthStage.login:
// One‑Click Login when a loginUrl is provided
final url = authState.loginUrl;
if (url?.isNotEmpty == true) {
await para.presentAuthUrl(
url: url!,
webAuthenticationSession: webAuthSession,
);
await para.waitForLogin();
await para.touchSession();
await para.fetchWallets();
break;
}
// Fallback to other login methods
await para.handleLogin(
authState: authState,
webAuthenticationSession: webAuthSession,
);
break;
case AuthStage.verify:
// Show OTP UI, then continue the flow
break;
case AuthStage.signup:
// Call handleSignup if passkeys/passwords are enabled
break;
}
```
## Sign Transactions
```dart transaction_handler.dart
// Get wallets
final wallets = await para.fetchWallets();
final evmWallet = wallets.firstWhere((w) => w.type == WalletType.evm && w.id != null);
final solanaWallet = wallets.firstWhere((w) => w.type == WalletType.solana && w.id != null);
// EVM transaction
final evmTx = EVMTransaction(
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f6E2c0',
value: '1000000000000000', // 0.001 ETH in wei
chainId: '11155111',
type: 2, // EIP-1559
);
final evmResult = await para.signTransaction(
walletId: evmWallet.id!,
transaction: evmTx.toJson(),
chainId: '11155111',
rpcUrl: 'https://rpc.ankr.com/eth_sepolia',
);
if (evmResult is SuccessfulSignatureResult) {
final signedTx = evmResult.signedTransaction;
// Broadcast signedTx using your RPC client
}
// Solana message signing
final solanaResult = await para.signMessage(
walletId: solanaWallet.id!,
message: 'Hello, Solana!',
);
if (solanaResult is SuccessfulSignatureResult) {
final signature = solanaResult.signedTransaction; // Base64 signature string
}
```
## Next Steps
# Setup
Source: https://docs.getpara.com/v3/flutter/setup
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import BetaCredentialsFull from "/snippets/v3/beta-credentials/beta-credentials-full.mdx";
import { Link } from '/snippets/v3/components/ui/link.mdx';
The Para Flutter SDK enables you to integrate secure wallet features including creation, passkey-based authentication, and transaction signing into your mobile applications. This guide covers all necessary steps from installation to implementing authentication flows.
## Install the SDK
Start by installing the Para SDK:
```bash
flutter pub add para
```
## Configure URL Scheme
Configure your app's URL scheme for OAuth authentication flows. This enables OAuth providers to redirect back to your app after authentication.
1. In Xcode, select your project in the navigator
2. Select your app target
3. Go to the **Info** tab
4. Scroll down to **URL Types** and click **+** to add a new URL type
5. Fill in the fields:
- **URL Schemes**: Enter your scheme name (e.g., `yourapp`, `myflutterapp`)
- **Role**: Select **Editor**
- **Identifier**: Use your bundle identifier or a descriptive name
Make sure your URL scheme is unique to avoid conflicts with other apps. Use a scheme related to your app's bundle ID (e.g., `com.mycompany.myapp`) for uniqueness.
Configure URL scheme handling in your `android/app/src/main/AndroidManifest.xml` file:
1. Locate your MainActivity in the AndroidManifest.xml
2. Update your MainActivity with the complete configuration below:
```xml
```
**Android 12+ Requirement:** The `android:exported="true"` attribute is mandatory for apps targeting Android 12 (API 31) and higher when the activity has intent filters.
Replace `yourapp` with your actual app scheme. This scheme must match the `appScheme` parameter used in Para initialization. The `android:launchMode="singleTop"` prevents multiple instances of your app from being created when deep links are opened.
## Optional: Configure Passkeys
Enable passkeys only if you’ve turned them on in the Para Developer Portal. Configure both iOS and Android platforms:
To enable passkeys on iOS, you need to configure Associated Domains:
1. Open your Flutter project's iOS folder in Xcode
2. In Xcode, go to **Signing & Capabilities** for your app target
3. Click **+ Capability** and add **Associated Domains**
4. Add the following entries:
```
webcredentials:app.usecapsule.com
webcredentials:app.beta.usecapsule.com
```
5. Register your Team ID + Bundle ID with Para via the
Without properly registering your Team ID and Bundle ID with Para, passkey authentication flows will fail. Contact Para support if you encounter issues with passkey registration.
Prepping for review? See for Sign in with Apple and reviewer guidance.
### Get SHA-256 Fingerprint
To get your SHA-256 fingerprint:
- For debug builds: `keytool -list -v -keystore ~/.android/debug.keystore`
- For release builds: `keytool -list -v -keystore `
Register your package name and SHA-256 fingerprint with Para via the
Without properly registering your package name and SHA-256 fingerprint with Para, passkey authentication flows will fail. Contact Para support if you encounter issues with passkey registration.
**Quick Testing Option**: You can use `com.getpara.example.flutter` as your package name for immediate testing. This package name is pre-registered and works with the SHA-256 certificate from the default debug.keystore, making it testable in debug mode for newly scaffolded Flutter apps.
### Fix Namespace Issue
For newer versions of Flutter, you need to add the following configuration block to your `android/build.gradle` file to resolve a namespace issue with the passkey dependency:
```gradle
subprojects {
afterEvaluate { project ->
if (project.hasProperty('android')) {
project.android {
if (namespace == null) {
namespace project.group
}
}
}
}
}
```
### Device Requirements
To ensure passkey functionality works correctly:
- Enable biometric or device unlock settings (fingerprint, face unlock, or PIN)
- Sign in to a Google account on the device (required for Google Play Services passkey management)
## Initialize Para
To use Para's features, you'll need to initialize a Para client instance that can be accessed throughout your app. This
client handles all interactions with Para's services, including authentication, wallet management, and transaction
signing.
Create a file (e.g., `lib/services/para_client.dart`) to initialize your Para client:
```dart lib/services/para_client.dart
import 'package:para/para.dart';
// Para Configuration
final config = ParaConfig(
apiKey: 'YOUR_PARA_API_KEY', // Get from: https://developer.getpara.com
environment: Environment.beta, // Use Environment.prod for production
);
// Initialize Para client instance
final para = Para.fromConfig(
config: config,
appScheme: 'yourapp', // Your app's scheme (without ://)
);
```
You can access `para` from anywhere in your app by importing the file where you initialized it. This singleton pattern
ensures consistent state management across your application.
## Authenticate Users
Para provides a unified authentication experience that supports email, phone, and social login methods. The SDK automatically determines whether a user is new or existing and guides you through the appropriate flow.
Create a single `FlutterWebAuthSession` and reuse it whenever you call `handleLogin` or `handleSignup`.
```dart lib/views/authentication_view.dart
final webAuthSession = FlutterWebAuthSession(
callbackUrlScheme: 'yourapp',
);
```
### Build Your Authentication Flow
Para’s One-Click Login is the default path. As soon as you receive an `AuthState`, check for `loginUrl` and complete the inline flow before falling back to other methods. The pattern below mirrors the example app while keeping the logic compact.
Para supports authentication with both email addresses and phone numbers.
Initiate authentication with email or phone:
```dart lib/views/authentication_view.dart
// Determine if input is email or phone
final Auth auth;
if (userInput.contains('@')) {
auth = Auth.email(userInput);
} else {
auth = Auth.phone(userInput); // Include country code
}
// SDK call: Initiate authentication
final authState = await para.initiateAuthFlow(auth: auth);
// One-Click login or signup
if (authState.loginUrl?.isNotEmpty == true) {
await para.presentAuthUrl(
url: authState.loginUrl!,
webAuthenticationSession: webAuthSession,
);
final nextStage = authState.effectiveNextStage;
if (nextStage == AuthStage.signup) {
await para.waitForSignup();
} else {
await para.waitForLogin();
}
await para.touchSession();
await para.fetchWallets();
return;
}
// Handle the result based on stage (only needed for passkey/password flows)
switch (authState.stage) {
case AuthStage.verify:
// New user - show verification UI (see optional passkey/password section)
break;
case AuthStage.login:
// Existing user - fall back to passkey/password flows
await para.handleLogin(
authState: authState,
webAuthenticationSession: webAuthSession,
);
break;
case AuthStage.signup:
// Complete signup with passkey/password if you enable them
break;
}
```
Social login is integrated directly into the unified authentication view. Users can authenticate with Google, Apple, or Discord.
Implement the social login handler:
```dart lib/views/authentication_view.dart
Future handleSocialLogin(OAuthMethod provider) async {
try {
final authState = await para.verifyOAuth(
provider: provider,
appScheme: 'yourapp',
);
if (authState.loginUrl?.isNotEmpty == true) {
await para.presentAuthUrl(
url: authState.loginUrl!,
webAuthenticationSession: webAuthSession,
);
final nextStage = authState.effectiveNextStage;
if (nextStage == AuthStage.signup) {
await para.waitForSignup();
} else {
await para.waitForLogin();
}
await para.touchSession();
await para.fetchWallets();
return;
}
if (authState.stage == AuthStage.login) {
try {
await para.touchSession();
} catch (_) {
// Session refresh is best-effort
}
await para.fetchWallets();
// Navigate to main app
return;
}
} catch (e) {
// Handle error
print('OAuth login failed: ${e.toString()}');
}
}
```
### Optional: Passkey/Password Signup
If you enable passkeys or passwords in the Para dashboard, you’ll need to handle the verification stage and call `handleSignup`.
```dart lib/views/authentication_view.dart
final verifiedState = await para.verifyOtp(otp: userCode);
// Reuse the One-Click block shown above here, then fall back to handleSignup.
await para.handleSignup(
authState: verifiedState,
signupMethod: SignupMethod.passkey, // or SignupMethod.password
webAuthenticationSession: webAuthSession,
);
```
## Returning Users
Existing users follow the same One-Click-first flow. If you’ve enabled passkey or password methods, fall back to `handleLogin`
after the One-Click block. You can also call `loginWithPasskey` directly when you know a user has registered one.
## Check Authentication Status
You can check if a user is already authenticated:
```dart lib/views/content_view.dart
final isLoggedIn = await para.isSessionActive().future;
if (isLoggedIn) {
// User is authenticated, proceed to main app flow
} else {
// Show login/signup UI
}
```
## Sign Out Users
To sign out a user and clear their session:
```dart lib/views/settings_view.dart
await para.logout().future;
```
## Create and Manage Wallets
After successful authentication, you can perform wallet operations:
```dart lib/views/wallet_view.dart
// Get all user wallets
await para.fetchWallets(); // Ensure we have the latest wallets
final wallets = await para.fetchWallets().future;
if (wallets.isEmpty) {
// No wallets, perhaps create one
final wallet = await para.createWallet(
type: WalletType.evm,
skipDistribute: false,
).future;
print('Created wallet: ${wallet.address}');
// Sign a simple message (SDK handles Base64 encoding internally)
final signature = await para.signMessage(
walletId: wallet.id,
messageBase64: base64Encode(utf8.encode('Hello, Para!')),
);
print('Signature: ${(signature as SuccessfulSignatureResult).signedTransaction}');
} else {
// Use existing wallet
final firstWallet = wallets.first;
// Sign a simple message (SDK handles Base64 encoding internally)
final signature = await para.signMessage(
walletId: firstWallet.id,
messageBase64: base64Encode(utf8.encode('Hello, Para!')),
);
print('Signature: ${(signature as SuccessfulSignatureResult).signedTransaction}');
}
```
For transaction signing by chain, use the EVM, Solana, Cosmos, and Stellar guides.
## Example
For a complete implementation example, check out our Flutter SDK example app:
## Next Steps
After setup, use these guides for wallet signing flows.
# Developer Portal Email Branding
Source: https://docs.getpara.com/v3/flutter/setup/developer-portal-email-branding
import DeveloperPortalEmailBranding from '/snippets/v3/developer-portal/email-branding.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Payment Integration
Source: https://docs.getpara.com/v3/flutter/setup/developer-portal-payments
import DeveloperPortalPayments from '/snippets/v3/developer-portal/payments.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Security Settings
Source: https://docs.getpara.com/v3/flutter/setup/developer-portal-security
import DeveloperPortalSecurity from '/snippets/v3/developer-portal/security.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Get Your API Key
Source: https://docs.getpara.com/v3/flutter/setup/developer-portal-setup
import DeveloperPortalSetup from '/snippets/v3/developer-portal/setup.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Flutter Troubleshooting Guide
Source: https://docs.getpara.com/v3/flutter/troubleshooting
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import { Link } from '/snippets/v3/components/ui/link.mdx';
This guide addresses common issues you might encounter when integrating Para with your Flutter application. It provides
solutions and best practices to ensure a smooth integration.
Using an LLM (ChatGPT, Claude) or Coding Assistant (Cursor, Github Copilot)? Here are a few tips:
1) Include the for the most up-to-date help
2) Check out the for an interactive LLM using Para Examples Hub
## General Troubleshooting Steps
Before diving into specific issues, try these general troubleshooting steps:
1. **Clean the project and get dependencies**:
```bash
flutter clean
flutter pub get
```
2. **Update Flutter and dependencies**:
```bash
flutter upgrade
flutter pub upgrade
```
3. **Ensure Para package is up to date**: Check your `pubspec.yaml` file and update the Para package version if
necessary.
4. **Rebuild the project**:
```bash
flutter run
```
## Common Issues and Solutions
### 1. Package Not Found or Version Conflicts
**Problem**: Dart can't find the Para package or there are version conflicts with other dependencies.
**Solution**: Ensure your `pubspec.yaml` file is correctly configured:
```yaml
dependencies:
flutter:
sdk: flutter
para: ^latest_version
dependency_overrides:
# Add any necessary overrides here
```
After updating `pubspec.yaml`, run:
```bash
flutter pub get
```
### 2. Platform-Specific Setup Issues
**Problem**: Para features not working on specific platforms (iOS/Android).
**Solution**: Ensure platform-specific configurations are correct:
For iOS (`ios/Runner/Info.plist`):
```xml
CFBundleURLTypes
CFBundleURLSchemes
para
```
For Android (`android/app/build.gradle`):
```gradle
android {
defaultConfig {
...
minSdkVersion 21
}
}
```
### 3. SDK Initialization Errors
**Problem**: Para fails to initialize or throws errors on startup.
**Solution**: Ensure proper initialization with required `appScheme` parameter:
```dart
import 'package:para/para.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final para = Para.fromConfig(
config: ParaConfig(
environment: Environment.beta,
apiKey: 'YOUR_API_KEY',
),
appScheme: 'yourapp', // Required for deep links
);
runApp(MyApp(para: para));
}
```
**Common Initialization Issues**:
- Missing `appScheme` parameter
- Incorrect environment configuration
- Invalid API key format
### 4. ParaFuture Handling Errors
**Problem**: Errors when handling `ParaFuture` operations.
**Solution**: Ensure proper `ParaFuture` handling with cancellation support:
```dart
try {
final walletFuture = para.createWallet(
type: WalletType.evm,
skipDistribute: false,
);
// Handle the ParaFuture properly
final wallet = await walletFuture.future;
print('Wallet created: ${wallet.address}');
// Cancel if needed
// await para.cancelOperationById(walletFuture.requestId);
} catch (e) {
print('Error creating wallet: $e');
// Handle ParaBridgeException specifically
if (e is ParaBridgeException) {
print('Bridge error code: ${e.code}');
}
}
```
**Common ParaFuture Issues**:
- Not awaiting the `.future` property
- Incorrect cancellation handling
- Missing error type checking for `ParaBridgeException`
### 5. UI Thread Blocking
**Problem**: Para operations blocking the UI thread.
**Solution**: Use `compute` function for heavy computations:
```dart
import 'package:flutter/foundation.dart';
Future createWalletAsync(Para para) async {
return compute(_createWallet, para);
}
Future _createWallet(Para para) async {
final walletFuture = para.createWallet(
type: WalletType.evm,
skipDistribute: false,
);
return await walletFuture.future;
}
// Usage
final wallet = await createWalletAsync(para);
```
### 6. Platform Channel Errors
**Problem**: Errors related to platform channel communication.
**Solution**: Ensure the latest version of Para Flutter plugin is used and platform-specific code is correctly
implemented. If issues persist, check the plugin's GitHub repository for any known issues or updates.
## SDK-Specific Issues
### 7. Authentication Flow Errors
**Problem**: Authentication flow failing or returning unexpected states.
**Solution**: Follow the correct authentication pattern:
```dart
// Correct flow
final authState = await para.initiateAuthFlow(
auth: Auth.email('user@example.com')
);
switch (authState.stage) {
case AuthStage.verify:
// Handle verification
final verifiedState = await para.verifyOtp(
otp: code
);
if (verifiedState.stage == AuthStage.signup) {
final wallet = await para.handleSignup(
authState: verifiedState,
method: SignupMethod.passkey,
);
}
break;
case AuthStage.login:
// Handle login
final wallet = await para.handleLogin(
authState: authState,
);
break;
}
```
### 8. OAuth Integration Issues
**Problem**: OAuth flows failing or not redirecting properly.
**Solution**: Ensure deep link configuration matches OAuth setup:
```dart
// Correct OAuth flow
final authState = await para.verifyOAuth(
provider: OAuthMethod.google,
appScheme: 'yourapp', // Must match your URL scheme
);
```
### 9. Extension Methods Not Found
**Problem**: `initiateAuthFlow()` or other extension methods not available.
**Solution**: Import the auth extensions:
```dart
import 'package:para/para.dart';
import 'package:para/src/auth_extensions.dart'; // Required for extension methods
// Now you can use
final authState = await para.initiateAuthFlow(
auth: Auth.email('user@example.com')
);
```
## Best Practices
1. **Use ParaFuture Properly**: Always await the `.future` property and handle cancellation where appropriate.
2. **Error Handling**: Implement robust error handling with specific exception types:
```dart
try {
// Para operation
} on ParaBridgeException catch (e) {
// Handle Para-specific errors
} catch (e) {
// Handle general errors
}
```
3. **Session Management**: Use session methods for persistence:
```dart
final isActive = await para.isSessionActive().future;
if (!isActive) {
// Show login screen
}
```
4. **State Management**: Use proper state management for authentication flows.
5. **Deep Link Security**: Validate deep link callbacks to prevent malicious redirects.
6. **Testing**: Write unit and integration tests for your Para integration.
7. **Performance Monitoring**: Monitor `ParaFuture` operations for performance.
8. **Keep Updated**: Regularly update to the latest SDK changes.
## Debugging Tips
1. **Enable Verbose Logging**: Enable verbose logging for Para operations to get more detailed information:
```dart
Para.fromConfig(
config: ParaConfig(
environment: Environment.beta,
apiKey: 'YOUR_API_KEY',
logLevel: ParaLogLevel.verbose,
),
appScheme: 'yourapp',
);
```
2. **Use Flutter DevTools**: Utilize Flutter DevTools for performance profiling and debugging.
3. **Platform-Specific Debugging**: For platform-specific issues, use Xcode for iOS and Android Studio for Android
debugging.
## Setup and Integration Issues
If you're having trouble initializing the Para SDK:
- Ensure you're providing the required `appScheme` parameter
- Verify that you're using the correct API key and environment
- Check that all necessary dependencies are installed properly
- Look for any Dart errors in your Flutter debug console
- Verify that your Flutter version is compatible with the Para SDK
If passkey creation, retrieval, or usage isn't working:
- Verify that you've set up associated domains correctly in your iOS project
- For Android, check that you've configured your `build.gradle` file with the namespace fix
- Make sure you've provided the correct SHA-256 fingerprint to the Para team for Android
- Ensure that biometric authentication is enabled on the test device
- For Android, confirm the test device has a Google account signed in
- Check that `WebAuthenticationSession` is properly configured
If you're experiencing authentication issues:
- Double-check that your API key is correct and properly set in your Para client initialization
- Verify you're using the correct environment (`beta` or `prod`) that matches your API key
- Ensure your account has the necessary permissions for the operations you're attempting
- Check that your deep link scheme matches what's configured in your app
- Verify the authentication flow is being followed correctly (verify → signup/login)
If you're migrating from V1 to V2:
- Replace `signUpOrLogIn()` with `initiateAuthFlow()`
- Replace `verifyNewAccount()` with `verifyOtp()`
- Replace `loginWithPasskey()` with `handleLogin()`
- Remove calls to `init()` - it no longer exists
- Update constructor to use `Para.fromConfig()` factory method
- Add the required `appScheme` parameter (now without ://callback suffix)
- Update wallet creation to use `handleSignup()` with `SignupMethod.passkey` or `.password`
By following these troubleshooting steps and best practices, you should be able to resolve most common issues when
integrating Para with your Flutter application.
# Account Abstraction
Source: https://docs.getpara.com/v3/general/account-abstraction
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Account Abstraction (AA) enables a more intuitive and flexible blockchain experience by allowing for programmable accounts with features like gasless transactions, batched operations, and custom authorization logic.
Para serves as the Signer and EOA (Externally Owned Account) for smart wallets and is not a smart wallet itself. Para does not provide gas sponsorship, but you can use any of the supported providers to implement smart wallet functionality for your users.
## Choose Your Platform
Note that client-side support for account abstraction is not currently available for Swift and Flutter platforms. If you need account abstraction functionality in Flutter or Swift applications, it's recommended to use server-side account abstraction integration.
## Supported Providers
Para integrates with several leading Account Abstraction providers:
-
-
-
-
-
-
-
-
# Go Live Checklist
Source: https://docs.getpara.com/v3/general/checklist
import { Link } from '/snippets/v3/components/ui/link.mdx';
Before going live, ensure you've completed the following steps:
* [x] 🔑 Create a account to manage your integration
* [x] Create and configure a `BETA` API Key for testing the integration
You can also create and configure API keys from the terminal with the : `para keys create` and `para keys config`.
* [x] : Get the SDK or Modal Up and Running
* [x] Check out the or sections for help!
- [ ] ⛓ Get signing & on-chain connectivity set up and make sure you're able to sign transactions
- [ ] **[EVM]**
- [ ] **[Cosmos]** : Add on the `cosm.js` signer or plug in one of our wallet adapters for popular libraries
- [ ] **[Stellar]** : Set up the Stellar SDK signer for Stellar and Soroban transactions
- [ ] : Decide if you'll be using Account Abstraction with Para
- [ ] : Connect Para users to your existing infrastructure
- [ ] 🔒 Ensure Security and Session Management works for your app
- [ ] : Decide what login methods you'd like to use
- [ ] Implement Logic
- [ ] Decide if you want to enable
- [ ] 🎨 Make Para your own with by adding the visual finishing touches
- [ ] : Configure Branding, UI and more
- [ ] 🚀 Test and go live!
- [ ] Make a `PRODUCTION` API Key (free tier includes 1,200 MAUs — billing only required if you exceed this)
- [ ] Walk through the
- [ ] Bookmark to monitor beta and production platform availability
- [ ] Ping us if you'd like help testing anything
## Para Environments: `BETA` vs `PRODUCTION`
Para has two different environments. **NOTE: Wallets are not shared across environments**
`BETA` is intended for you to develop and test against, **not for production builds or real users and funds**
* `BETA` can be used with any plan, including the free tier
* This is the correct environment to use for local or staging builds
* `BETA` users can be deleted for your convenience while testing, and is limited to *50 max users per project*
`PRODUCTION` is intended for any real users/funds
* This is the correct environment to use for release or production builds
* `PRODUCTION` users can NOT be deleted, and have no per-project limit
# Set up Custom OIDC
Source: https://docs.getpara.com/v3/general/developer-portal-custom-oidc
Custom OIDC lets you add **your own OpenID Connect (OIDC) identity provider** — an enterprise SSO, your existing auth system, or any standards-compliant IdP — as a login method in Para. Users sign in with your provider and get a Para embedded wallet, just like any other social login.
In this flow **Para is the OIDC client (relying party)** and your provider is the identity provider. You configure it once on your partner record — from the [Developer Portal](https://developer.getpara.com) or the [Para CLI](/v3/cli/overview) — and it works everywhere your app authenticates: the Para Modal renders it automatically, and your own custom UI can trigger it like any OAuth method.
Custom OIDC is for **end users signing in through Para** with your IdP. If your backend already authenticates users and you only need server-side wallets, see [Bring Your Own Auth](/v3/rest/byo-auth) instead — a different pattern.
## How it works
1. A user selects your provider on the Para login screen.
2. Para redirects them to your IdP to sign in.
3. Your IdP redirects back to Para's callback with an authorization code.
4. Para exchanges the code, verifies the identity, and creates or loads the user's Para wallet.
You give Para a **client ID** and **client secret** from your IdP; Para gives you a **redirect URI** to register with your IdP. That's the whole handshake.
## Before you start
- An OpenID Connect provider that supports OIDC Discovery (it serves a `/.well-known/openid-configuration` document).
- Admin access to register a new client with that provider.
- A Para project and API key — [create one](/v3/general/developer-portal-setup) if you haven't.
## 1. Register Para with your provider
In your identity provider, register a new OIDC client (sometimes called an "application" or "relying party") and note its **Client ID** and **Client secret**.
Add Para's redirect URI to the client's list of allowed redirect URIs:
```text Production
https://api.getpara.com/auth/custom_oidc/callback
```
```text Beta (testing)
https://api.beta.getpara.com/auth/custom_oidc/callback
```
Para uses one redirect URI per environment — your project context is carried in the OAuth `state` parameter. The exact URI is also shown when you configure Custom OIDC in the next step.
## 2. Configure Custom OIDC in Para
Configure the provider on your partner record from either the Developer Portal or the Para CLI — they write the same settings.
In the [Developer Portal](https://developer.getpara.com), select your project and API key, then go to **Authentication** and find the **Custom OIDC** section.
- **Issuer URL** — your provider's issuer, e.g. `https://idp.yourcompany.com`. Must be `https` with no query string.
- **Client ID** — the client ID from step 1.
- **Scopes** — space-separated (defaults to `openid email profile`).
- **Token endpoint authentication** — how Para authenticates to your token endpoint (see [authentication methods](#token-endpoint-authentication-methods)).
- **Sign-in label** — the text shown on the login button, e.g. "Sign in with Acme".
Enter the **Client secret** from step 1 and save it. It's stored encrypted and never shown again. (Skip this if your client uses the `none` authentication method.)
Copy the **Redirect URI** shown in the card and add it to your provider's allowed redirect URIs (if you didn't in step 1).
Click **Verify** — Para runs a live check against your provider's discovery document and confirms it can reach it.
In the **Embedded Wallets** method list, enable **Custom OIDC** so it appears as a login option.
Configure the same settings with the [Para CLI](/v3/cli/overview):
```bash
# Set the provider config
para keys config oidc set \
--issuer https://idp.yourcompany.com \
--client-id your-client-id \
--scopes "openid email profile" \
--auth-method client_secret_post \
--label "Sign in with Acme"
# → Updated Custom OIDC on My App (production)
# Store the client secret (prompted; the value is never echoed or written to .pararc)
para keys config oidc set-secret
# → Stored the Custom OIDC client secret on My App (production).
# Confirm Para can reach your provider
para keys config oidc verify
# → Custom OIDC provider verified on My App (production).
# Enable Custom OIDC as a login method
para keys config auth --oauth-methods CUSTOM_OIDC
```
Review the saved configuration at any time with `show`:
```bash
$ para keys config oidc show
Issuer https://idp.yourcompany.com
Client ID your-client-id
Scopes openid email profile
Auth method client_secret_post
Sign-in label Sign in with Acme
Client secret set
```
`--oauth-methods` sets the full OAuth list, so include any other providers you want enabled alongside `CUSTOM_OIDC`.
## 3. Use it in your app
Once Custom OIDC is enabled, no extra SDK code is required to make it appear.
### With the Para Modal
The Para Modal renders a **Custom OIDC** button automatically, labeled with your sign-in label. Users tap it, sign in with your provider, and land back in your app with a Para wallet.
### With your own UI
If you build your own login UI, trigger Custom OIDC like any other OAuth provider — pass `"CUSTOM_OIDC"` as the method:
```typescript
const result = await para.authenticateWithOAuth({
method: "CUSTOM_OIDC",
redirectCallbacks: {
onOAuthPopup: (popup) => {
// keep a handle to the popup if you want to track it
},
},
});
```
See [Build a Custom UI](/v3/react/guides/custom-ui-web-sdk#oauth-authentication) for the full OAuth flow. To label your own button consistently, read the configured value from `para.config.authConfig?.oidcConfig?.buttonLabel`.
Custom OIDC works the same on every platform — it's a `CUSTOM_OIDC` OAuth method wherever Para authenticates. On mobile, follow your platform's social-login guide and pass `CUSTOM_OIDC` as the method.
## Token endpoint authentication methods
**Token endpoint authentication** controls how Para authenticates to your provider's token endpoint when it exchanges the authorization code:
| Method | Behavior |
|---|---|
| `client_secret_post` | **Default.** Para sends the client ID and secret in the request body. |
| `client_secret_basic` | Para sends the client ID and secret as an HTTP Basic auth header. |
| `none` | Public client — no secret. Use only if your provider issued a public (PKCE) client. |
Choose the method your provider expects for the client you registered. `client_secret_post` and `client_secret_basic` require a client secret; `none` requires none.
## Security
- Your **client secret is write-only.** Para stores it with KMS envelope encryption and never returns it. The CLI prompts for it without echoing, and it's never written to `.pararc`.
- The **issuer must be `https` with no query string** — this keeps credentials out of the stored issuer and any logs. A path is fine (`https://idp.yourcompany.com/tenant-123`).
- Para validates your provider's discovery document over the network when it connects; private and internal addresses are rejected.
## Troubleshooting
The redirect URI registered with your provider must exactly match Para's callback for the environment you're using — `https://api.getpara.com/auth/custom_oidc/callback` (production) or `https://api.beta.getpara.com/auth/custom_oidc/callback` (beta). Re-copy it from the Custom OIDC configuration.
Your **token endpoint authentication** method must match what your provider expects. If your client is confidential, use `client_secret_post` or `client_secret_basic` and make sure a secret is set. If it's a public client, use `none`.
The issuer must be `https` with no query string, fragment, or credentials — just `https://host/path`. Para fetches your discovery document from `{issuer}/.well-known/openid-configuration`.
Para couldn't load your discovery document. Confirm the issuer is publicly reachable over HTTPS and serves a valid `/.well-known/openid-configuration`.
## Custom OIDC vs Bring Your Own Auth
Both let you use your own identity system, but they're different integrations:
| | Custom OIDC | [Bring Your Own Auth](/v3/rest/byo-auth) |
|---|---|---|
| Who authenticates the user | Your OIDC provider, through the Para login flow | Your backend, before it calls Para |
| Where it runs | Client — the Para Modal or your own UI | Server-to-server, over the REST API |
| Use when | You want a login button for your IdP or SSO | You already authenticate users and want server-side wallets |
## Next steps
The full Authentication screen — login methods, layout, and more.
Authenticate without the Para Modal.
# Developer Portal Email Branding
Source: https://docs.getpara.com/v3/general/developer-portal-email-branding
import DeveloperPortalEmailBranding from '/snippets/v3/developer-portal/email-branding.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Payment Integration
Source: https://docs.getpara.com/v3/general/developer-portal-payments
import DeveloperPortalPayments from '/snippets/v3/developer-portal/payments.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Security Settings
Source: https://docs.getpara.com/v3/general/developer-portal-security
import DeveloperPortalSecurity from '/snippets/v3/developer-portal/security.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Get Your API Key
Source: https://docs.getpara.com/v3/general/developer-portal-setup
import DeveloperPortalSetup from '/snippets/v3/developer-portal/setup.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Glossary
Source: https://docs.getpara.com/v3/general/glossary
### Identity and Wallets
**Embedded wallet** – A non-custodial wallet built into an app, often invisible to users. Enables users to control assets without managing keys or apps.
**Passkey** – A device-native authentication method [(based on WebAuthn)](https://docs.getpara.com/v3/concepts/security#authentication) used as a more secure and user-friendly alternative to passwords and private keys.
**Private key** – Cryptographic secret that grants control over a wallet. In [MPC setups](https://blog.getpara.com/what-is-mpc/), it’s never reconstructed or stored in full; access is managed through secure, distributed shares.
**Recovery (or Key Recovery)** – The process for regaining access to a wallet, typically complex and manual in crypto. [Solutions like social recovery and embedded wallet recovery with Para simplify this for mainstream users.](https://docs.getpara.com/v3/concepts/security#backup-and-recovery)
**Seed phrase** – A series of words that encode a wallet’s private keys. Often used for wallet backup, but vulnerable to phishing and user error. [(Increasingly replaced by passkeys or MPC.)](https://blog.getpara.com/what-is-mpc/)
**Session key** – A short-lived key used to sign actions during an app session without requiring full wallet access.
**Signer** – An entity (user, server, agent) that can authorize transactions for a wallet.
**Wallet address** – A unique identifier for a wallet, similar to a bank account number users can share to receive funds.
### App Infrastructure
**EVM / Solana / Cosmos / Stellar** – refer to [different blockchains](https://docs.getpara.com/introduction/chain-support), each with its own ecosystem of developer tools, standards, and communities.
**Gas** – The fee paid to execute transactions on a blockchain (like network fees in fintech). In modern apps, gas can be abstracted away from users for smoother UX.
**Onchain / Offchain** – Indicates whether an action occurs on the blockchain or off of it. Onchain means the operation is happening directly on the blockchain, while offchain references to actions that take place on traditional servers or outside the blockchain.
**RPC (Remote Procedure Call)** – The bridge between an app and the blockchain, used to read/write onchain data.
**Rollup** – A type of scaling solution (e.g., [Optimism](https://www.optimism.io/), [Arbitrum](https://arbitrum.io/)) that processes transactions off the main chain and posts a summary onchain. Speeds things up and reduces gas costs.
**Smart contract** – Self-executing code on a blockchain that defines how an app behaves.
### Security and Privacy
**Custody** – Defines who controls the private keys, and therefore access to the funds in a wallet setup. In crypto, custody determines who has actual control. Wallets can either be custodial or non-custodial.
**Custodial** – A third party holds wallet keys or crypto assets on behalf of a user.
**Distributed MPC (Multi-Party Computation)** – A cryptographic method where multiple parties collaboratively compute a result, like signing a transaction, without ever revealing or reconstructing the full private key. It’s typically used alongside [Distributed Key Generation (DKG)](https://docs.getpara.com/v3/concepts/security#how-keys-are-protected) for securely creating the key shares, and then applied during key signing. Enables secure, non-custodial access.
**Hardware wallet** – A physical device for storing crypto private keys offline.
**Non-custodial** – A setup where users retain full control of their wallet and assets. No third party can access their funds or sign on their behalf.
**Shamir Secret Sharing** – A cryptographic technique for splitting a secret (like a private key) into [multiple pieces](https://blog.getpara.com/what-is-mpc/#:~:text=number%20of%20shares.-,Shamir%20Secret%20Sharing,-2/2%20Shamir). A minimum number of these pieces must be recombined to form the original private key to sign transactins and messages. Less dynamic than MPC.
### Ecosystems & Fintech
**Liquidity** – How easily assets can be bought or sold without affecting price. Critical for user experience in swaps or trading.
**Stablecoin** – A token designed to maintain a stable value (often pegged to the US dollar). Widely used in fintech apps for payments.
**Token** – A digital asset, which can represent anything from currency to ownership in a protocol.
**USDC (USD Coin)** – A dollar-pegged stablecoin issued by [Circle](https://www.circle.com/usdc), backed 1:1 by cash and short-term U.S. government bonds. Widely used in crypto apps and exchanges.
**USDT (Tether)** – Another popular dollar-pegged stablecoin. Issued by [Tether](https://tether.to/), but with less transparency than USDC around its reserves.
# iOS App Store Submission
Source: https://docs.getpara.com/v3/general/ios-app-store-submission
Use this guide to cover the App Review items Apple most often flags for Para integrations.
---
## 1. Sign-in options
If your app includes any third-party logins (like Google or Facebook), Apple also expects a privacy-preserving option such as **Sign in with Apple**. This ensures users can sign in without sharing personal data or tracking identifiers.
**Actions:**
- Add **Sign in with Apple** anywhere other providers appear.
- Test it in a **release build** before submission.
If your app only uses first-party sign-ins (email, phone, or passkeys), Apple doesn’t require Sign in with Apple.
---
## 2. Reviewer login flow
Make it effortless for reviewers to log in—especially if your app also supports external wallets.
**Actions:**
- Show standard sign-in options (email, phone, or Apple) first.
- Add a line in onboarding: *“No external wallet required — continue with email, phone, or Apple.”*
- In your **Reviewer Notes**, list exact steps to log in without a wallet.
**Reviewer Notes example:**
```text
Use email or phone (OTP), or Sign in with Apple, to log in. No external wallet (e.g., MetaMask) is required to access the app.
```
---
## 3. Wallet-only positioning
If your app is a **non-custodial wallet** and **not an exchange**, make that clear. Reviewers often check for exchange or on-ramp features.
**Add to Reviewer Notes:**
```text
The app is a non-custodial wallet using the Para SDK. It does not include exchange, swap, bridge, or on/off-ramp services. No tokens are sold or issued.
Features:
- Creates/imports non-custodial wallets via passkeys or MPC.
- Displays addresses and signs user transactions.
- Uses public RPC endpoints only.
Exclusions:
- No buy/sell/swap/bridge flows.
- No fiat or crypto on-/off-ramp.
- No KYC/AML required (wallet-only app).
Para SDK:
- Handles authentication and signing only.
- Private keys never leave the user’s device.
Request:
- Review under Guideline 3.1.5(i) as a wallet-only app.
```
---
## 4. Account deletion
If users can sign up, Apple requires an **in-app account deletion option** (not just deactivation).
Keep it simple:
- Provide a Delete Account action somewhere obvious (Settings is fine).
- If you need Para to remove the user record as well, reach out to support after you handle your own data.
**Reviewer Notes example:**
```text
In-app account deletion is available at Settings → Account → Delete Account. Para is an SDK provider and does not host user accounts. Contact us if Para-level deletion is required.
```
---
## 5. Passkeys & entitlements
If you’re using passkeys or autofill, set up **Associated Domains** in Xcode and host an AASA file.
**Checklist:**
- Add **Associated Domains** capability.
- Include `webcredentials:your.domain`.
- Host `https://your.domain/.well-known/apple-app-site-association`.
This enables secure credential sharing and Apple’s passkey autofill.
---
## 6. Reviewer Notes checklist
Paste these details into **App Store Connect → Reviewer Notes**:
- Login path (e.g., *Continue → Sign in with Apple → Approve prompt*)
- Test credentials or OTP instructions
- Note that the app is wallet-only, not an exchange
- Location of Delete Account in settings
- Any feature flags or regional settings
- Confirmation that backend services are live
---
## 7. Privacy & SDK compliance
Apple now enforces privacy manifest rules for all third-party SDKs.
**Before you submit:**
- Include privacy manifests for every SDK.
- Ensure binary SDKs have valid signatures.
- Declare reasons for any required-reason APIs.
- Update your App Privacy answers to reflect data use accurately.
---
## 8. Export compliance
Para SDK uses standard encryption (TLS, secure enclave, etc.). Answer **Yes** to App Store Connect’s encryption question and select the “standard algorithms” exemption. If your app uses custom cryptography, you may need to upload documentation.
---
## 9. Final pre-submission checklist
### Sign-in & onboarding
- Sign in with Apple (if you offer other OAuth logins)
- Onboarding text: *No external wallet required*
- Associated Domains configured for passkeys
### Privacy & compliance
- In-app Delete Account button visible
- Privacy manifests complete & signed
- App Privacy details updated
- Export compliance questions answered
### Reviewer experience
- Reviewer Notes completed (using template)
- Test credentials provided
- Backend services online
- Privacy policy link included
---
## Related resources
- [Production Deployment](/v3/general/production-deployment)
- [Launch Checklist](/v3/general/checklist)
- [React Native iOS setup](/v3/react-native/setup/react-native#ios) · [Expo iOS](/v3/react-native/setup/expo#ios)
- [Swift setup](/v3/swift/setup#ios) · [Flutter iOS setup](/v3/flutter/setup#ios)
# Login Two-Factor Authentication (MFA)
Source: https://docs.getpara.com/v3/general/login-mfa
**Early access — hidden.** Login MFA isn't in the Para Modal yet, so there's no built-in UI for it. You integrate it with your **own** UI today (React hooks or fully custom). Reach out to your Para contact to enable it on your project.
Login MFA adds a **TOTP second factor at sign-in**: after a user authenticates with their first factor (email, phone, OAuth, your [Custom OIDC](/v3/general/developer-portal-custom-oidc) provider, etc.), Para can require them to set up and present a time-based one-time code from an authenticator app before the login completes and their wallet unlocks.
This is **login-time MFA** — a second factor on every sign-in. It's distinct from Para's **recovery 2FA** (`useSetup2fa` / `useVerify2fa`), which gates account recovery. The hooks and methods below (`enrollMfa` / `verifyMfa`) are login MFA only.
## How it works
When login MFA is owed, the SDK **pauses the login** on a challenge and surfaces it through the auth state. There are two challenge phases:
| Auth phase | Meaning | What your UI does |
|---|---|---|
| `awaiting_2fa_enrollment` | First time — the user has no factor yet | Call `enrollMfa` to mint a secret, render the QR, show backup codes, then collect the first code |
| `awaiting_2fa` | Returning — the user already has a factor | Prompt for a code |
Your UI watches for these phases, drives enrollment/verification, and submits the code. On a correct code the SDK **re-polls the login and advances on its own** toward the connected wallet — you don't need to restart the flow.
```text
first factor ──▶ awaiting_2fa_enrollment ──enroll──▶ awaiting_2fa ──verify ok──▶ authenticated
(new user) (or returning)
```
## Before you start
- Para has **enabled login MFA** on your project (see [Enabling login MFA](#enabling-login-mfa)).
- The Para React SDK (`@getpara/react-sdk`) or Web SDK (`@getpara/web-sdk`), v3.
- A **custom login UI** — login MFA has no Para Modal UI yet, so you render the challenge yourself.
## Enabling login MFA
Login MFA is configured on your project's partner record by the **Para team** — it isn't self-serve while the feature is in early access. Reach out to your Para contact and tell them which mode you want:
| Mode | Behavior |
|---|---|
| `optional` | Only users who have enrolled a factor are challenged. New users aren't forced to set one up. |
| `required` | Every user must set up a factor and pass it on each login. |
| `disabled` | Off (the default). |
The CLI/Developer Portal **"Enable two-factor authentication"** toggle controls **recovery** 2FA, not login MFA — don't use it for this. Login MFA is enabled separately by Para.
## Integrate it
Pick the path that matches how you build your login UI. Both drive the same `enrollMfa` / `verifyMfa` flow — the hooks just wrap it in React Query state.
Use **`useEnrollMfa`** and **`useVerifyMfa`** (from `@getpara/react-sdk`) together with the auth-state subscription. This mirrors the [Custom OIDC example](https://github.com/getpara/examples-hub/tree/3/web/with-react-nextjs/custom-oidc-auth) — a small hook surfaces the challenge from the SDK state and drives it.
### A challenge hook
Subscribe to the auth state to detect the hold, fetch the enrollment secret once, and expose a `verify` function:
```tsx
import { useCallback, useEffect, useRef, useState } from "react";
import type { StateSnapshot } from "@getpara/web-sdk";
import { useClient, useEnrollMfa, useVerifyMfa } from "@getpara/react-sdk";
type MfaMode = "enroll" | "verify";
export function useMfaChallenge() {
const para = useClient();
const { enrollMfaAsync } = useEnrollMfa();
const { verifyMfaAsync } = useVerifyMfa();
const [mode, setMode] = useState(null);
const [enrollment, setEnrollment] = useState<{ uri: string; backupCodes: string[] } | null>(null);
const [attemptsRemaining, setAttemptsRemaining] = useState(null);
const [error, setError] = useState(null);
// enrollMfa() mints a fresh secret + backup codes each call, so enroll once per challenge.
const hasEnrolled = useRef(false);
useEffect(() => {
if (!para) return;
const unsubscribe = para.onStatePhaseChange(async (snapshot: StateSnapshot) => {
if (snapshot.authPhase === "awaiting_2fa_enrollment") {
setMode("enroll");
if (hasEnrolled.current) return;
hasEnrolled.current = true;
try {
const { uri, backupCodes } = await enrollMfaAsync(); // otpauth:// uri + one-time codes
setEnrollment({ uri, backupCodes });
} catch (e) {
hasEnrolled.current = false; // allow a retry on the next state tick
setError("Could not start two-factor setup.");
}
} else if (snapshot.authPhase === "awaiting_2fa") {
setMode("verify");
} else {
// Left the hold (advanced, cancelled, or errored) — reset so stale codes never linger.
setMode(null);
setEnrollment(null);
setAttemptsRemaining(null);
setError(null);
hasEnrolled.current = false;
}
});
return () => unsubscribe();
}, [para, enrollMfaAsync]);
const verify = useCallback(
async (code: string): Promise => {
setError(null);
// On { ok: true } the SDK has already re-polled the login — the auth state advances
// toward the wallet on its own. On { ok: false } stay on the prompt.
const result = await verifyMfaAsync({ code });
if (result.ok) return true;
setAttemptsRemaining(result.attemptsRemaining ?? null);
setError(`Incorrect code. ${result.attemptsRemaining ?? 0} attempt(s) remaining.`);
return false;
},
[verifyMfaAsync],
);
return { mode, enrollment, attemptsRemaining, error, verify };
}
```
### Render the challenge
Render a QR from the `uri` (any QR component works), show the backup codes once during enrollment, and collect a 6-digit code:
```tsx
import { useState } from "react";
import { QRCodeSVG } from "qrcode.react";
import { useMfaChallenge } from "./useMfaChallenge";
export function MfaChallenge() {
const { mode, enrollment, attemptsRemaining, error, verify } = useMfaChallenge();
const [code, setCode] = useState("");
if (!mode) return null; // not parked on a 2FA challenge
return (
{mode === "enroll" && enrollment && (
<>
Scan this with your authenticator app:
Save these backup codes — they're shown only once:
{enrollment.backupCodes.map((c) => {c} )}
>
)}
{mode === "enroll" ? "Enter the code to finish setup" : "Enter your authentication code"}
setCode(e.target.value)} inputMode="numeric" />
verify(code)}>Verify
{error &&
{error}{attemptsRemaining === 0 && " Please sign in again."}
}
);
}
```
Each hook returns the standard React Query mutation fields, with `mutate`/`mutateAsync` renamed: `useEnrollMfa()` → `{ enrollMfa, enrollMfaAsync, isPending, ... }`, `useVerifyMfa()` → `{ verifyMfa, verifyMfaAsync, isPending, ... }`.
If you don't use the React hooks, call the methods on your Para client directly. The shape is identical — `para.enrollMfa()` and `para.verifyMfa()` — and you detect the hold with the same `onStatePhaseChange` subscription.
```ts
import { para } from "./your-para-client";
import type { StateSnapshot } from "@getpara/web-sdk";
// Watch the login for a 2FA hold.
para.onStatePhaseChange(async (snapshot: StateSnapshot) => {
if (snapshot.authPhase === "awaiting_2fa_enrollment") {
// New user — mint a secret and show setup UI.
const { uri, backupCodes } = await para.enrollMfa();
renderQrCode(uri); // otpauth:// — render as a QR
showBackupCodes(backupCodes); // one-time — show exactly once
showCodeInput();
} else if (snapshot.authPhase === "awaiting_2fa") {
// Returning user — just prompt for a code.
showCodeInput();
}
});
// When the user submits a TOTP or backup code:
async function submitMfaCode(code: string) {
const result = await para.verifyMfa({ code });
if (result.ok) {
// SDK re-polls the login automatically — the wallet unlocks on its own.
return;
}
// Wrong code — re-prompt. result.attemptsRemaining tells you how many tries are left.
showError(result.attemptsRemaining);
}
```
`enrollMfa()` resolves to `{ uri, backupCodes }`; `verifyMfa({ code })` resolves to `{ ok: true }` or `{ ok: false, attemptsRemaining }`. Treat `{ ok: false }` as a re-prompt, not a hard error.
## Backup codes
`enrollMfa` returns a set of **one-time backup codes** alongside the QR `uri`. Show them to the user **once**, during setup, and tell them to store the codes somewhere safe — they're how a user gets in if they lose their authenticator. Para stores only hashes and never shows them again.
A backup code is accepted anywhere a TOTP code is — pass it to `verifyMfa({ code })` exactly the same way. Each backup code works once.
## Handling wrong codes and lockout
`verifyMfa` returns `{ ok: false, attemptsRemaining }` for an incorrect code. Surface `attemptsRemaining` and let the user try again — don't treat it as a fatal error.
After too many wrong codes the session is **locked out**; the user must restart the login (a fresh sign-in resets the budget). When `attemptsRemaining` reaches `0`, prompt the user to sign in again.
## Security
- The TOTP secret is generated server-side and stored **encrypted** — your UI only ever receives the `otpauth://` provisioning `uri` to render.
- Backup codes are **single-use** and stored as hashes; they're returned in plaintext only once, at enrollment.
- A correct factor is what releases the user's wallet keyshares for the session — the gate is enforced on Para's backend, not in your UI, so it can't be bypassed client-side.
## Troubleshooting
Confirm Para has enabled login MFA on your project (`optional` or `required`). In `optional` mode, only users who have already enrolled a factor are challenged — a brand-new user won't be unless the mode is `required`. Also make sure your UI subscribes to `onStatePhaseChange` and renders on `awaiting_2fa` / `awaiting_2fa_enrollment`.
Each `enrollMfa()` call mints a fresh secret and a new set of backup codes. Call it **once** per enrollment challenge (guard it, as the example does with a ref) so the QR and backup codes are stable while the user scans them.
On `{ ok: true }` the SDK re-polls the login itself — don't also re-trigger authentication. If you're keeping the auth popup/window open, make sure it stays open until the wallet connects.
TOTP codes are time-based — make sure the user's device clock is accurate. Para accepts a small skew window. If they've lost their authenticator, have them use a backup code instead.
## Next steps
The full custom-login flow the MFA challenge plugs into.
Add your own identity provider as a first factor.
# Migrating from Capsule to Para
Source: https://docs.getpara.com/v3/general/migration-from-capsule
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
## Overview
This guide covers the migration process from Capsule to Para SDKs. The migration includes package namespace changes,
method signature updates to use object parameters, and introduces new React hooks for state management.
## Package Changes
All packages have been migrated from the `@usecapsule` namespace to `@getpara`. Update your dependencies by replacing
`@usecapsule` with `@getpara` in your package.json:
```diff
{
"dependencies": {
- "@usecapsule/react-sdk": "^3.0.0",
- "@usecapsule/evm-wallet-connectors": "^3.0.0"
+ "@getpara/react-sdk": "^1.0.0",
+ "@getpara/evm-wallet-connectors": "^1.0.0"
}
}
```
All packages have been reset to version 1.0.0 under the new namespace. The functionality and package names remain the
same - only the organization prefix has changed from `@usecapsule` to `@getpara`.
```bash npm
npm install @getpara/[package-name] --save-exact
```
```bash yarn
yarn add @getpara/[package-name] --exact
```
```bash pnpm
pnpm add @getpara/[package-name] --save-exact
```
## Mobile SDK Updates
### Flutter
The Flutter package has moved from `capsule` to `para` on pub.dev:
```diff
dependencies:
- capsule: 0.7.0
+ para: ^1.0.0
```
Create instances using `Para()` instead of `Capsule()`. All method signatures remain unchanged.
### Swift
The Swift SDK package is now available at `github.com/getpara/swift-sdk`. The main class has been renamed from
`CapsuleManager` to `ParaManager`, while maintaining the same method signatures:
```diff
- let manager = CapsuleManager()
+ let manager = ParaManager()
```
Method signatures and functionality remain identical for both mobile SDKs - only the package names and main class
names have changed.
## Breaking Changes
### Method Updates
All methods have been updated to use object parameters instead of multiple arguments. This change improves
extensibility, type safety, and reflects our commitment to consistent API design.
```typescript
// Old
createUser(email: string)
// New
createUser({ email: string })
```
```typescript
// Old
createUserByPhone(phone: string, countryCode: string)
// New
createUserByPhone({ phone: string, countryCode: string })
```
```typescript
// Old
externalWalletLogin(address: string, type: string, provider?: string, addressBech32?: string)
// New
externalWalletLogin({
address: string,
type: string,
provider?: string,
addressBech32?: string
})
```
```typescript
// Old
createWallet(type: WalletType, skipDistribute?: boolean)
// New
createWallet({
type: WalletType,
skipDistribute?: boolean = false
})
```
```typescript
// Old
createWalletPerType(skipDistribute?: boolean, types?: WalletType[])
// New. Note: Function name changed, default value added, and types is now required
createWalletPerType({
skipDistribute?: boolean = false,
types: WalletType[]
})
```
```typescript
// Old
distributeNewWalletShare(
walletId: string,
userShare?: string,
skipBiometricShareCreation?: boolean,
forceRefreshRecovery?: boolean
)
// New
distributeNewWalletShare({
walletId: string,
userShare?: string,
skipBiometricShareCreation?: boolean = false,
forceRefresh?: boolean = false
})
```
```typescript
// Old
createWalletPreGen(
type: WalletType,
pregenIdentifier: string,
pregenIdentifierType?: PregenIdentifierType
)
// New
createPregenWallet({
type: WalletType,
pregenIdentifier: string,
pregenIdentifierType?: PregenIdentifierType
})
```
```typescript
// Old - Note: Function name changed
updateWalletIdentifierPreGen(
newIdentifier: string,
walletId: string,
newType?: PregenIdentifierType
)
// New
updatePregenWalletIdentifier({
walletId: string,
newPregenIdentifier: string,
newPregenIdentifierType?: PregenIdentifierType
})
```
```typescript
// Old
signMessage(
walletId: string,
messageBase64: string,
timeoutMs?: number,
cosmosSignDocBase64?: string
)
// New
signMessage({
walletId: string,
messageBase64: string,
timeoutMs?: number,
cosmosSignDocBase64?: string
})
```
```typescript
// Old
signTransaction(
walletId: string,
rlpEncodedTxBase64: string,
timeoutMs?: number,
chainId: string
)
// New
signTransaction({
walletId: string,
rlpEncodedTxBase64: string,
timeoutMs?: number,
chainId: string
})
```
All methods now use object parameters with optional properties defaulting to reasonable values. This change makes the
SDK more maintainable and easier to extend in the future.
## New Features: React Hooks
Para now includes React hooks for easier state management and SDK interaction. Here's a basic setup:
```typescript
import { ParaProvider } from "@getpara/react-sdk";
function App() {
return (
);
}
```
### Available Hooks
Access current account state and connection status
Get current wallet information and state
Create a new Para user account
Check if a user exists by email
Start the login process
Handle user logout
Maintain active user session
Create wallet after passkey verification
Sign messages with connected wallet
Sign transactions with connected wallet
Handle login flow and initial setup
Monitor account creation process
Access Para client instance
Control Para modal visibility
Manage wallet state
## Next Steps
1. Update your package dependencies to use `@getpara/*` packages
2. Migrate method calls to use new object parameters
3. Consider implementing React hooks for simpler state management
4. Review framework-specific integration guides for detailed setup instructions
# Wallet Pregeneration
Source: https://docs.getpara.com/v3/general/pregen
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Wallet pregeneration means creating a wallet before a Para user owns it. The wallet is keyed by an identifier, such as an email, phone number, OAuth ID, or custom ID.
The pregenerated wallet can later be claimed by the user associated with that identifier, transferring ownership to them if your application permits it. To claim a wallet, the wallet's user share must be loaded into the client and the wallet identifier must match the user's authenticating identifier.
If you create a wallet with an internal custom ID, store your own mapping from that ID to the future claimant. Before
returning the user share for claim, update the wallet identifier to the identifier the user is authenticating with.
## Choose Your Approach
### Recommended: REST API
Use REST API by default for new server-created or pre-created wallet integrations. Your backend creates wallets, Para persists shares server-side, and signing happens over HTTP with your API key.
### SDK Integrations
Use SDK pregen when your flow depends on direct user-share control, SDK ecosystem signing before claim, or client/Portal private-key export paths. SDK pregen is still valid for those cases, but it is no longer the default for new server-side wallet creation.
## Common use cases
The [REST API](/v3/rest/overview) covers all of these:
- **Mass User Onboarding**: Create wallets for your existing user base or email lists instantly
- **Social Integration**: Generate wallets based on social identifiers like Twitter followers
- **Agent-Owned Wallets**: Give AI agents or bots their own wallets for specific functions
- **Server-Side Operations**: Create app-managed wallets to perform operations on behalf of users
- **Airdrops and Rewards**: Preload funds or NFTs into wallets that users can claim later
- **Staged Onboarding**: Let users experience your application before formally creating their wallet
If you already have SDK pregen wallets and want to move server-side signing to REST, use the [migration guide](/v3/rest/migrate-from-sdk-pregen).
# Deploy Para Integration to Production
Source: https://docs.getpara.com/v3/general/production-deployment
import { Link } from '/snippets/v3/components/ui/link.mdx';
Deploy your Para integration to production by updating configurations and ensuring secure settings for real users.
## Prerequisites
You need these components before deploying to production:
- A working Para integration in development/beta environment
- Production API credentials from the [Para Developer Portal](https://developer.getpara.com/)
- Production domain with HTTPS enabled
- Latest Para SDK version installed
## Deployment Steps
Update your Para SDK to the latest version before going live. Use the newest 3.0 release for all projects to benefit from improvements and fixes.
Check your @getpara/* package versions and update to the latest stable release. Latest SDK versions resolve many integration issues.
Align your project with the [current Para release](https://www.npmjs.com/package/@getpara/react-sdk?activeTab=versions) to access recent features and security patches for real users.
Create a production API key in the Para Developer Portal for your live environment.
**Free tier:** Para includes 1,200 free monthly active users — no credit card required. You only need to set up billing if you expect to exceed this limit.
**Existing wallets won't transfer.** Wallets created with your beta API key are not accessible with your production
API key. Production is a separate environment with its own users and wallets. Plan your migration accordingly —
users will need to create new wallets in production.
Replace development credentials with production values in your environment variables.
Update all secrets and URLs in your .env file for production, including callback URLs and API endpoints.
Configure your integration to use the production environment instead of beta/development. Para provides two hosted environments: BETA for testing and PROD for live use.
Update your initialization from:
```javascript Development
const para = new ParaWeb(Environment.BETA, PARA_API_KEY);
```
To:
```javascript Production
const para = new ParaWeb(Environment.PROD, PARA_API_KEY);
```
Para's Beta and Production are separate environments. Wallets and users are not shared between them. Your production environment starts with no users, and requires a distinct production API key.
Update allowed origin settings in the Para developer portal to include your production domain. This Domain Security setting restricts API usage to specified origins.
Add entries like `https://yourapp.com` and production subdomains to the Allowed Origins list. Remove development URLs like `http://localhost:3000` if no longer needed.
HTTPS is required in production. Configuring allowed origins prevents unauthorized domains from using your Para integration.
Allowed Origins are only supported for web integrations, not server or mobile. You can also manage origins from the terminal with `para keys config security --origins`. See the .
Review your Para integration settings for production, especially user communications and UI:
### Verification Redirect URL
Set the verification redirect URL to point to your production application's confirmation page. This URL appears in verification emails Para sends to users. Update from localhost URLs to your live domain.
### Email Notification Preferences
Choose appropriate email settings for production. Para can send welcome emails and optional backup kit instructions to new users.
Configure email templates and branding (logo, app name) for real user emails based on your user experience strategy.
### Branding and UI Configuration
Customize the Para modal or widget to match your production app's branding (colors, fonts, icons). Configure theme options through the Para SDK or developer portal's branding section.
Deploy your updated application with production settings and perform end-to-end testing with real scenarios:
### Test User Sign-Up/Login
Create a new user account using a real email address or phone number. Ensure OTP codes are delivered and users can complete verification and wallet creation.
Test emails like dev@test.getpara.com and dummy phone numbers will not work in production. Production requires real contact information and OTP verification.
Ensure you are signing transactions on the [appropriate network](https://docs.getpara.com/v3/introduction/chain-support).
### Functionality Check
Verify wallet operations work with production settings. Test retrieving wallet addresses or making test transactions on mainnet to confirm Para's production environment interacts correctly.
Verify session management and security features (persistent login/logout) behave as expected.
### Monitor and Review
Watch browser console and backend logs for errors like misconfigured API keys or CORS issues. Check for beta and production platform availability when investigating unexpected Para API, signing, wallet, portal, or developer portal behavior. If you encounter CORS or CSP errors, verify the origin matches your deployed URL exactly.
## Next Steps
import { Card } from '/snippets/v3/components/ui/card.mdx';
# Check Para System Status
Source: https://docs.getpara.com/v3/general/system-status
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para publishes hosted platform availability on . Check this page when validating a launch, investigating unexpected Para API behavior, or confirming whether an issue is tied to a broader service incident.
## What Para Status Tracks
Para Status reports availability for Para's beta and production environments. Each environment can include component-level health for:
- API: authentication, wallet management, and core platform operations
- Transaction Signing: MPC signing ceremonies and transaction processing
- Wallet Operations: key storage and wallet operations
- Portal: the hosted Para web application portal
- Developer Portal: the developer dashboard
Component states can show operational, degraded performance, partial outage, or major outage status. The page also provides incident history so you can distinguish active incidents from resolved events.
## When to Use It
Check Para Status before a production launch, during beta validation, and whenever your integration starts seeing unexpected failures from Para-hosted services. If Para Status shows all relevant components as operational, continue debugging your app configuration, API keys, allowed origins, sessions, chain connectivity, and backend logs.
Para Status covers Para-hosted platform services. It does not replace monitoring for your application, your backend, third-party RPC providers, or chain-level network health.
# Telegram Bots & Mini-Apps
Source: https://docs.getpara.com/v3/general/telegram
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
For signing in to Para with Telegram, refer to .
Para supports both Telegram Bots and Mini-Apps.
are a way to program logic natively into the Telegram App interface via text-based prompts and commands. Telegram Bots feel native to the UX of Telegram, but are limited to the UX and functionality of text-based options and menus.
are an easy way to serve hosted web applications from within Telegram. Given Mini-Apps are added functionality to an existing
web app, they are much more flexible but have a UX pattern that deviates from the native Telegram experience.
## Telegram Bot
The most popular way to use Para in a Telegram Bot is to leverage with the .
You have the option of allowing your users to their pregenerated wallets, which can happen directly within Telegram or in a standalone app.
## Mini-App
You can build Mini-Apps two ways:
1. Use the with the password option.
2. Use with the web SDK in a web framework of your choice. Once you have a web example working, use Telegram to create an interface for getting and setting data.
3. You'll need to decide where and how you want users to claim their pregenerated wallets. This can happen within
Telegram Mini-Apps or in a standalone app.
Telegram Storage APIs have some limitations on data size. You may need to implement chunking to work around this.
# Troubleshooting
Source: https://docs.getpara.com/v3/general/troubleshooting
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
Having trouble with your Para integration? You're in the right place. This section contains platform-specific troubleshooting guides to help you resolve common issues across different frameworks and environments.
Try running `para doctor` in your project directory for instant diagnostics. It checks API key configuration, SDK version consistency, missing imports, and more. See the for details.
Need to verify your API key's configuration? Run `para keys config show` to see all settings (auth methods, origins, wallet types, ramps, webhooks) without entering an edit flow. Use `para keys config show security` to check a specific category. See the for details.
Using an LLM (ChatGPT, Claude) or Coding Assistant (Cursor, Github Copilot)? Here are a few tips:
1) Include the for the most up-to-date help
2) Check out the for an interactive LLM using Para Examples Hub
## Choose Your Platform
### Web
### Mobile
## Popular Web Frameworks
If we're missing a troubleshooting guide for a framework you're using, please get in touch! We're constantly expanding our documentation to cover more environments.
# User Data Management
Source: https://docs.getpara.com/v3/general/user-data
Effective user data management is crucial when integrating Para into your application. This guide covers best practices
for handling user information, including storage, retrieval, and privacy considerations.
## User Data in Para
Para's approach to user data is designed with privacy and security in mind. Here's what you need to know:
Para only collects essential information required for account identification and recovery, typically just the
user's email address.
User data is securely stored and encrypted on Para's servers. However, the most sensitive information - the user's
private keys - are never fully stored in one place due to Para's MPC technology.
As a developer, you have limited direct access to user data stored by Para. This is by design to ensure user
privacy and security.
## Managing User Data in Your Application
While Para handles the core wallet functionality, you may need to manage additional user data in your application. Here
are some best practices:
### Storing User Information
When storing additional user information in your application:
1. Only store what's necessary for your application's functionality.
2. Use secure, encrypted storage methods.
3. Consider using Para's wallet ID as a unique identifier for your users.
Example of storing user data:
```typescript
type UserData = {
paraWalletId: string;
username: string;
preferences: Record;
};
// Assume you're using a secure database
async function storeUserData(userData: UserData) {
await database.users.insert(userData);
}
// Usage
const wallets = await para.getWallets();
const walletId = Object.values(wallets)[0].id;
await storeUserData({
paraWalletId: walletId,
username: "user123",
preferences: { theme: "dark" },
});
```
### Retrieving User Information
To retrieve user information:
1. Use Para's methods to get wallet-related information.
2. Fetch additional data from your own storage using the Para wallet ID as a reference.
Example:
```typescript
async function getUserData(email: string) {
const wallets = await para.getWallets();
const walletId = Object.values(wallets)[0].id;
// Fetch additional data from your storage
const userData = await database.users.findOne({ paraWalletId: walletId });
return {
walletId,
...userData,
};
}
```
### Updating User Data
When updating user data:
1. Use Para's methods for updating wallet-related information.
2. Update additional data in your own storage.
```typescript
async function updateUserPreferences(walletId: string, newPreferences: Record) {
// Update in your storage
await database.users.update({ paraWalletId: walletId }, { $set: { preferences: newPreferences } });
}
```
## Privacy and Security Considerations
When managing user data, always prioritize privacy and security:
Only collect and store data that is absolutely necessary for your application's functionality.
Always encrypt sensitive data, both in transit and at rest.
Implement strict access controls to ensure that only authorized personnel can access user data.
Conduct regular audits of your data management practices to ensure compliance with privacy regulations.
## Compliance with Regulations
Ensure your user data management practices comply with relevant regulations such as GDPR, CCPA, or other applicable
laws. This may include:
- Providing users with the ability to request their data
- Allowing users to delete their data
- Implementing data portability features
Example of a data deletion function:
```typescript
async function deleteUserData(walletId: string) {
// Delete from your storage
await database.users.delete({ paraWalletId: walletId });
// Note: Para wallet data cannot be deleted directly through the SDK
// Advise the user to contact Para support for complete account deletion
}
```
Remember that while you can delete user data from your own storage, Para wallet data is managed separately for
security reasons. Users should be directed to Para's official channels for complete account deletion requests.
## Best Practices for User Data Management
1. **Separation of Concerns**: Keep Para-related data separate from your application-specific user data.
2. **Regular Backups**: Implement a robust backup strategy for user data stored in your application.
3. **Transparent Policies**: Clearly communicate your data handling practices to users through privacy policies and
terms of service.
4. **Secure Transmission**: Always use secure, encrypted channels when transmitting user data.
5. **Data Validation**: Implement thorough input validation to prevent injection attacks and ensure data integrity.
## Troubleshooting
If you encounter issues with user data management:
1. Ensure you're using the latest version of the Para SDK.
2. Verify that you're correctly handling asynchronous operations when interacting with Para and your own data storage.
3. Double-check that you're using the correct wallet IDs and other identifiers.
4. Review your error handling to ensure you're catching and addressing all potential exceptions.
# Webhooks
Source: https://docs.getpara.com/v3/general/webhooks
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
Para webhooks send real-time HTTP POST requests to your server when events occur in your integration — such as a user
signing up, a wallet being created, or a transaction being signed. Use webhooks to trigger backend workflows, sync data,
or send notifications without polling.
## Configure Webhooks
Set up webhooks from your API key's **Webhooks** page in the .
### Set Your Endpoint URL
Enable the webhook toggle and enter your HTTPS endpoint URL. Para will send POST requests to this URL when subscribed
events occur.
Only HTTPS URLs are accepted. HTTP endpoints will be rejected.
### Select Events
Choose which events you want to receive. You must select at least one event type when webhooks are enabled.
### Save and Copy Your Secret
When you first save your webhook configuration, Para generates a signing secret (prefixed with `whsec_`). This secret is
**displayed only once** — copy and store it securely. You'll use it to
[verify webhook signatures](#verify-webhook-signatures).
Your webhook secret is shown only at creation and after rotation. If you lose it, you'll need to rotate to get a new
one.
### Test Your Endpoint
Click **Test Webhook** to send a `test.ping` event to your endpoint. This verifies connectivity without triggering a
real event.
## Event Types
| Event | Description |
| ------------------------------- | ------------------------------------------------------ |
| `user.created` | A new user was created |
| `wallet.created` | A new wallet was created for a user |
| `transaction.signed` | A transaction was signed |
| `send.broadcasted` | A Para Send transaction was broadcasted to the network |
| `send.confirmed` | A Para Send transaction was confirmed on-chain |
| `send.failed` | A Para Send transaction failed |
| `rest.transaction.confirmed` | A REST API v1 broadcast transaction was confirmed on-chain |
| `rest.transaction.failed` | A REST API v1 broadcast transaction reverted on-chain or failed during monitoring |
| `wallet.pregen_claimed` | A pre-generated wallet was claimed by a user |
| `user.external_wallet_verified` | A user verified an external wallet |
## Event Payload
Every webhook request contains a JSON body with a standard envelope wrapping the event-specific data:
```json
{
"id": "evt_550e8400-e29b-41d4-a716-446655440000",
"type": "user.created",
"createdAt": "2026-02-07T12:00:00.000Z",
"data": {
// event-specific fields
}
}
```
| Field | Description |
| ----------- | ------------------------------------------------ |
| `id` | Unique event ID prefixed with `evt_` |
| `type` | The event type string |
| `createdAt` | ISO 8601 timestamp of when the event was created |
| `data` | Event-specific payload (see below) |
### Event Data by Type
```json
{
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"userCreatedAt": "2026-02-07T12:00:00.000Z"
}
```
```json
{
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"walletId": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"walletAddress": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
"walletType": "EVM",
"walletCreatedAt": "2026-02-07T12:00:00.000Z"
}
```
```json
{
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"walletId": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"walletAddress": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
"chainId": 1,
"signedAt": "2026-02-07T12:00:00.000Z"
}
```
The `chainId` field is `null` for message signing operations where no chain is involved.
```json
{
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"walletId": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"txHash": "0xabc123...",
"type": "EVM",
"evmChainId": "1",
"isDevnet": false,
"broadcastedAt": "2026-02-07T12:00:00.000Z"
}
```
For Solana transactions, `type` will be `"SOLANA"` and `evmChainId` will be absent. `isDevnet` indicates if the transaction was on a devnet.
```json
{
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"walletId": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"txHash": "0xabc123...",
"type": "EVM",
"evmChainId": "1",
"isDevnet": false,
"confirmedAt": "2026-02-07T12:00:00.000Z"
}
```
```json
{
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"walletId": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"txHash": "0xabc123...",
"type": "EVM",
"evmChainId": "1",
"isDevnet": false,
"error": "insufficient funds",
"failedAt": "2026-02-07T12:00:00.000Z"
}
```
The `error` field is optional and may be absent if no error message was available.
```json
{
"transactionId": "550e8400-e29b-41d4-a716-446655440000",
"walletId": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"partnerId": "11111111-2222-3333-4444-555555555555",
"intentKind": "transfer",
"type": "EVM",
"hash": "0x1234abcd...",
"blockNumber": "5127103",
"blockHash": "0xdeadbeef...",
"resolvedAt": "2026-02-07T12:00:00.000Z"
}
```
`transactionId` matches the id returned from `POST /v1/wallets/:walletId/transfer` or
`POST /v1/wallets/:walletId/sign-transaction` with `broadcast: true` and used
by `GET /v1/wallets/:walletId/transactions/:transactionId`.
`intentKind` is `transfer` or `sign_transaction`.
```json
{
"transactionId": "550e8400-e29b-41d4-a716-446655440000",
"walletId": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"partnerId": "11111111-2222-3333-4444-555555555555",
"intentKind": "transfer",
"type": "EVM",
"status": "reverted",
"hash": "0x1234abcd...",
"failureMessage": "execution reverted",
"resolvedAt": "2026-02-07T12:00:00.000Z"
}
```
`status` is `reverted` (execution failed on-chain) or `failed` (never broadcast, rejected by the RPC, or the monitor gave up).
When `status = failed`, the payload also includes `failureStage` (`mpc_sign`, `signature_apply`,
`signer_verify`, `broadcast`, or `monitor_timeout`) and an optional `failureCode` from the broadcast helper.
```json
{
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"walletId": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"walletAddress": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
"walletType": "EVM",
"claimedAt": "2026-02-07T12:00:00.000Z"
}
```
```json
{
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"walletAddress": "0xaD6b78193b78e23F9aBBB675734f4a2B3559598D",
"walletProvider": "MetaMask",
"verifiedAt": "2026-02-07T12:00:00.000Z"
}
```
The `walletProvider` field is optional and may be absent depending on how the wallet was connected.
## Verify Webhook Signatures
Every webhook request includes headers that let you verify the request came from Para:
| Header | Description |
| ------------------- | --------------------------------------------------- |
| `webhook-id` | Unique event ID (matches payload `id`) |
| `webhook-timestamp` | Unix timestamp in seconds when the request was sent |
| `webhook-signature` | HMAC-SHA256 signature prefixed with `v1,` |
To verify a webhook:
1. Construct the signed message: `{webhook-timestamp}.{raw request body}`
2. Compute `HMAC-SHA256` using your webhook secret as the key
3. Base64-encode the result and compare it to the signature after the `v1,` prefix
```typescript Node.js
import crypto from "crypto";
function verifyWebhookSignature(
payload: string,
headers: Record,
secret: string
): boolean {
const timestamp = headers["webhook-timestamp"];
const signature = headers["webhook-signature"];
if (!timestamp || !signature) return false;
// Reject requests older than 5 minutes
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) return false;
const message = `${timestamp}.${payload}`;
const expected = crypto
.createHmac("sha256", secret)
.update(message)
.digest("base64");
// During secret rotation, multiple signatures may be
// space-delimited (one per active secret). Try each.
const signatures = signature.split(" ");
return signatures.some((sig) => {
const received = sig.replace("v1,", "");
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(received)
);
});
}
// Usage in an Express handler
app.post("/webhook", (req, res) => {
const rawBody = req.body; // use raw body string, not parsed JSON
const isValid = verifyWebhookSignature(
rawBody,
req.headers,
process.env.WEBHOOK_SECRET
);
if (!isValid) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(rawBody);
switch (event.type) {
case "user.created":
// handle user creation
break;
case "wallet.created":
// handle wallet creation
break;
// ...
}
res.status(200).send("OK");
});
````
```python Python
import hmac
import hashlib
import base64
import time
import json
def verify_webhook_signature(payload: str, headers: dict, secret: str) -> bool:
timestamp = headers.get("webhook-timestamp", "")
signature = headers.get("webhook-signature", "")
if not timestamp or not signature:
return False
# Reject requests older than 5 minutes
if abs(time.time() - int(timestamp)) > 300:
return False
message = f"{timestamp}.{payload}"
expected = base64.b64encode(
hmac.new(secret.encode(), message.encode(), hashlib.sha256).digest()
).decode()
# During secret rotation, multiple signatures may be
# space-delimited (one per active secret). Try each.
signatures = signature.split(" ")
return any(
hmac.compare_digest(expected, sig.replace("v1,", ""))
for sig in signatures
)
# Usage in a Flask handler
@app.route("/webhook", methods=["POST"])
def webhook():
raw_body = request.get_data(as_text=True)
is_valid = verify_webhook_signature(
raw_body,
request.headers,
os.environ["WEBHOOK_SECRET"],
)
if not is_valid:
return "Invalid signature", 401
event = json.loads(raw_body)
if event["type"] == "user.created":
pass # handle user creation
elif event["type"] == "wallet.created":
pass # handle wallet creation
return "OK", 200
````
Always use the raw request body string for verification — not a re-serialized JSON object. Re-serialization can change
formatting and break the signature check.
## Manage Your Webhook
### Rotate Secret
If your secret is compromised or you need a new one, rotate it from the **Danger Zone** section of the Webhooks page.
The previous secret remains valid for 24 hours to give you time to update your server. A new secret is displayed once.
### Disable or Delete
Toggle webhooks off to temporarily stop delivery without losing your configuration. To remove the webhook entirely, use
the **Delete Webhook** button — this removes the URL, events, and secret permanently.
## Best Practices
- **Respond quickly**: Return a `2xx` status within a few seconds. Offload heavy processing to a background job.
- **Verify signatures**: Always validate the `webhook-signature` header before trusting the payload.
- **Check timestamps**: Reject webhooks with timestamps more than 5 minutes old to prevent replay attacks.
- **Handle duplicates**: Use the `webhook-id` header to deduplicate events in case of retries.
- **Use HTTPS**: Your endpoint must be HTTPS. Para rejects HTTP URLs.
# Build Custom UI
Source: https://docs.getpara.com/v3/introduction/build-custom-ui
Use this path when your app owns the screens, copy, layout, and interaction model. Para handles authentication state, key management, embedded wallets, sessions, and signing, while your product decides how users move through the flow.
## Choose This Path If
- You want custom auth or wallet screens instead of Para's modal.
- You are building with Vue, Svelte, vanilla JavaScript, React Native, Flutter, Swift, or another non-React UI stack.
- You are building a headless React flow or a direct Web SDK integration.
- You still want Para to manage authentication, key shares, wallets, sessions, and signing.
Custom UI is a different path from modal customization. Para Modal is React-only. If you are building a React app and only need to style Para's modal, use [Use Para Modal](/v3/introduction/use-para-ui) and the [modal customization docs](/v3/react/guides/customization/modal).
## Integration Lifecycle
Create a project and API key in the [Developer Portal](https://developer.getpara.com/), then review [Developer Portal setup](/v3/general/developer-portal-setup).
Use [Custom UI with React hooks](/v3/react/guides/custom-ui-simplified) for headless React, [Custom UI with the Web SDK](/v3/react/guides/custom-ui-web-sdk) for framework-agnostic web apps, or the matching mobile setup for [React Native](/v3/react-native/setup/react-native), [Flutter](/v3/flutter/setup), or [Swift](/v3/swift/setup).
Implement the auth methods your app supports, then connect those screens to Para's auth state and session lifecycle. For React custom UI, review the v3 [configuration model](/v3/react/guides/customization/configuration) so your screens respect partner-level settings.
Add wallet creation, wallet selection, address display, signing requests, and post-signing UI using the SDK methods for your platform.
Use [Sign with Para](/v3/react/guides/web3-operations/sign-with-para) or the matching mobile signing guide to confirm the custom UI path end to end.
Review [session management](/v3/react/guides/sessions), the [Go Live Checklist](/v3/general/checklist), and [Production Deployment](/v3/general/production-deployment).
## Next Docs
Build custom auth and wallet screens on top of the React SDK hooks.
Build a custom wallet experience with the framework-agnostic Web SDK.
Build custom Svelte auth and wallet screens with the Web SDK.
Build custom Vue auth and wallet screens with the Web SDK.
Build mobile auth and wallet screens with the React Native SDK.
Build mobile auth and wallet screens with the Flutter SDK.
Build iOS auth and wallet screens with the Swift SDK.
Keep user authentication and wallet sessions aligned with your UI.
Confirm the custom UI path by signing a message or transaction.
# Chain Support
Source: https://docs.getpara.com/v3/introduction/chain-support
import RPCChainsTable from '/snippets/v3/rpc-chains-table.mdx';
Para integrates seamlessly with all EVM chains, Solana, Cosmos chains, and Stellar. To add support for any additional chains within these networks, include its chain ID (or network passphrase for Stellar) and RPC endpoint. Breakdown below:
# Changelog
Source: https://docs.getpara.com/v3/introduction/changelog
This release includes 2 bug fixes.
### Bug Fixes
- reject signatureless signing results
- handle custom oidc failed send state
### Maintenance
- refresh examples-hub lockfiles for v3.5.0
This release includes 3 new features, 2 bug fixes.
### Features
- login-time 2FA — SDK + portal + example (ENG-6906)
- Custom OIDC GA — dev-portal config + login branding (ENG-6976)
- custom oidc example flow permission polish
### Bug Fixes
- show partner logo + theme on non-iframed portal login (ENG-6960)
- dev portal user auth methods
This release includes 2 new features, 7 bug fixes.
### Features
- Custom OIDC auth method + custom-UI demo
- mobile navigation, responsive UI, and mobile fixes
### Bug Fixes
- approve Flutter Android permissions consent
- preserve oauth user id for consent
- require non-empty organization name (ENG-6959)
- support local dev URL overrides
- clarify TO_ADDRESS vs ARGUMENTS for contract calls (ENG-6953)
- preserve original error context in handleResponseError
- default Claude review to Opus
### Performance
- keep-alive connection reuse for the user-management client (ENG-6938)
### Maintenance
- filter public changelog entries
- refresh examples-hub lockfiles for v3.3.0
This release includes 4 new features, 4 bug fixes.
### Features
- port Para Connect v2 UI onto 3.0.0
- add partner supportUrl to dev portal and CLI
- extend para cli command telemetry
- add Safe smart account examples
### Bug Fixes
- open add-auth-method popup within the click gesture (mobile)
- normalize React Query errors before Sentry capture
- use reliable RPC endpoints for Solana/Cosmos node example sign tests
- query the key's environment on dev portal permissions page
### Maintenance
- add claude review model fallback
This release improves error reporting and debugging capabilities with enhanced MPC worker error tracking, while adding Stellar support to mobile examples and fixing several UI and infrastructure issues.
### Features
- Added platform tracking to analytics to distinguish mobile from web SDK usage
- Enhanced error reporting for key management operations with detailed context and backend tracking
- Added Stellar wallet support to Swift example app with signing capabilities
### Bug Fixes
- Fixed dependency update script to properly handle deeply nested example packages
- Fixed release pipeline to run non-interactively without manual confirmation prompts
- Fixed duplicate 'Change Wallets' buttons appearing for users with multiple connected partners
- Standardized error message styling to use consistent destructive styling across all components
- Improved error reporting for mobile bridge connections with better structured context
- Fixed worker crashes during failed signing operations by properly handling promise rejections
- Fixed Vercel deployment issues for example applications
- Updated changelog documentation to target v3 instead of v2
- Added loading UI for login upgrade screen
- Fixed wallet creation for users with incomplete wallets from failed key generation
- Updated release authentication to use GitHub App instead of personal access tokens
- Fixed simulate failure overlay blocking the send confirmation button on mobile
### Maintenance
- Refreshed web examples in examples hub
### Tests
- Added Stellar wallet testing coverage to Flutter mobile examples
Para Web SDK v3.0.0 introduces a completely redesigned authentication UI with improved theming capabilities and enhanced developer tooling.
### Breaking Changes
- **Theme type simplified**: `ParaTheme` reduced from 13 properties to 8. Removed `darkForegroundColor`, `darkBackgroundColor`, `darkAccentColor`, `overlayBackground`, `oAuthLogoVariant`, `customPalette`, `customFontSizes`, `customBorderRadii`
- **`foregroundColor` now drives the entire palette**: Buttons, accents, rings, and primary colors are generated from it via OKLCH color space
- **`accentColor` deprecated**: If provided, maps to `foregroundColor` for backward compatibility
- **`customPalette` replaced by `cssOverrides`**: Use `Record` for raw CSS variable overrides
### New Features
- Redesigned authentication UI with updated design system, improved theming options, and enhanced user experience throughout the sign-in flow
- **`foregroundMixRatio`**: Control how much the foreground color mixes into UI surfaces (default 0.04)
- **`cssOverrides`**: Advanced escape hatch for setting raw CSS custom properties
- **Automatic dark mode detection**: The system auto-detects dark/light mode from your background color lightness — just provide dark colors directly, no more separate dark mode color properties. Use `mode` only to override if auto-detection misclassifies your background
See the [v2.x to v3.0 Migration Guide](/v3/introduction/migration-to-v3) for detailed upgrade instructions.
For changelog entries prior to v3.0, switch to the 2.0 (LTS) docs from the version selector.
# Examples Hub
Source: https://docs.getpara.com/v3/introduction/examples
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para provides a comprehensive collection of examples to help you integrate our technology across various platforms, frameworks, and use cases. Our repository contains working code samples for all supported platforms.
Looking for community-created examples? Check out for community-featured examples and integrations from bounties and hackathons covering Account Abstraction, Agents, Pregeneration, and more!
## Web Framework Examples
## Server Examples
## Mobile Examples
Para SDK supports React Native, Flutter, and Swift for native mobile experiences:
## Popular Feature Examples
## Blockchain Integration Examples
## Transaction Signing Libraries
## DeFi Integration Examples
## Community Contributions
## Need Something Specific?
Don't see an example for your use case? Para's team is eager to create new examples to help you integrate with different libraries, third-party features, or providers.
# Framework Support
Source: https://docs.getpara.com/v3/introduction/framework-support
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { FeatureGrid } from "/snippets/v3/feature-grid.mdx";
This table outlines the current, quickly changing status of feature support across Para's SDKs.
We release new frameworks, then iteratively add features based on customer needs. If you don't see a feature you're
looking for here, - we may already be
working on it!
# Migrating from Reown to Para
Source: https://docs.getpara.com/v3/introduction/migration-from-reown
import MigrationFromReown from '/snippets/v3/migrations/from-reown.mdx';
# Migrating from Thirdweb to Para
Source: https://docs.getpara.com/v3/introduction/migration-from-thirdweb
import MigrationFromThirdweb from '/snippets/v3/migrations/from-thirdweb.mdx';
# Migrating from Web3Modal to Para
Source: https://docs.getpara.com/v3/introduction/migration-from-walletconnect
import MigrationFromWalletConnect from '/snippets/v3/migrations/from-walletconnect.mdx';
# Migrating from Para v1.x to v2.0
Source: https://docs.getpara.com/v3/introduction/migration-to-alpha
Use this guide for assistance migrating from Para version 1.x to Para's version 2.0 release.
## React SDK
### Summary of breaking changes
1. The `setup-para` CLI tool needs to run before running your app to ensure all `@getpara` libraries are properly polyfilled. It is recommended to do this in your `postinstall` step of your `package.json`, something like:
```bash
"postinstall": "yarn setup-para"
```
2. The `ParaProvider` is now required to be used when using the `@getpara/react-sdk`
3. A `queryClient` from `@tanstack/react-query` must be provided.
See the `@tanstack/react-query` [docs](https://tanstack.com/query/latest/docs/framework/react/quick-start) for more information on setting up a `QueryClient`
4. The `appName` prop has moved from a modal prop to a **required** config prop on the `ParaProvider`. This name will be used throughout the modal and in any external wallet providers that may be used.
5. The `ParaModal` no longer needs to be provided separately, it is automatically included with the `ParaProvider` and all modal props can be passed to the `ParaProvider`.
Do not keep a separate ` ` next to the embedded modal that `ParaProvider` renders by default. Remove the separate modal and pass modal options through `paraModalConfig`, or set `disableEmbeddedModal: true` in the `ParaProvider` config if you intentionally render your own modal.
6. The `ParaModal` props, `isOpen` and `onClose`, are no longer required (though they can be provided if desired). These values are now handled by the `ParaProvider` and developers can use the `useModal` hook to control the modal state.
7. When using external wallets the Para connector libraries no longer need to be provided, just installed. All config values for these connectors can be passed to the `ParaProvider`.
### Migration
Migration to V2 can be done one of two ways:
1. (**PREFERRED**) Remove the `ParaModal` component you are providing and use the modal that the updated `ParaProvider` provides. This option offers the most code reduction and overall simpler developer experience.
2. Continue to use the `ParaModal` component you are providing in your app. This option is a bit quicker than the first but will lead to a poorer developer experience.
Remove your `` and use the one built into ``.
**Starting Code:**
```tsx Starting Code (Existing Provider)
{...REST_OF_YOUR_APP}
```
```tsx Starting Code (Without Existing Provider)
const Para = new Para("YOUR_API_KEY")
<>
{...REST_OF_YOUR_APP}
>
```
**After Migration:**
```tsx After Migration {7-11}
{...REST_OF_YOUR_APP}
{/* Note: is removed from here */}
```
Continue using your own `` by disabling the built-in one.
**Starting Code:**
```tsx Starting Code (Existing Provider)
{...REST_OF_YOUR_APP}
```
```tsx Starting Code (Without Existing Provider)
const Para = new Para("YOUR_API_KEY")
<>
{...REST_OF_YOUR_APP}
>
```
**After Migration:**
```tsx After Migration {8-10, 16}
{...REST_OF_YOUR_APP}
```
### Adding External Wallets
If you're using external wallets with Para currently, those configs can now be passed to the `ParaProvider` and the connectors will be instantiated for you. Assuming you already followed the migration steps above, a successful migration would look like:
```tsx Starting Code [expandable]
{
SELECTED_CHAIN_STATE_UPDATER_FN();
}}
multiChain
walletConnect={{ options: { projectId: 'YOUR_WALLETCONNECT_PROJECT_ID' } }}
>
{...REST_OF_YOUR_APP}
```
```tsx After Migration {11-41} [expandable]
{
SELECTED_CHAIN_STATE_UPDATER_FN();
},
chains: YOUR_COSMOS_CHAINS,
},
// grazProviderProps={}
},
solanaConnector: {
config: {
endpoint: YOUR_SOLANA_ENDPOINT,
chain: YOUR_SOLANA_NETWORK,
},
},
walletConnect: {
projectId: 'YOUR_WALLETCONNECT_PROJECT_ID',
},
}}
>
{...REST_OF_YOUR_APP}
```
Notes on the external wallet migration:
1. All wallets are provided by default, if you wish for them all to be provided you can remove the `wallets` value in the `externalWalletConfig`
2. If you only wish to use EVM wallets only the `evmConnector` config needs to be passed, the other connectors will be skipped in that case.
## Pregen Wallet Methods
Methods dealing with pregen wallets now use a simpler object-based notation to specify the type and identifier the wallet belongs to.
```tsx Email
// 1.x
await para.createPregenWallet({
pregenIdentifier: 'email@email.com',
pregenIdentifierType: 'EMAIL'
});
// 2.x
await para.createPregenWallet({ pregenId: { email: 'email@email.com' } });
```
```tsx Phone
// 1.x
await para.createPregenWallet({
pregenIdentifier: '+13105551234',
pregenIdentifierType: 'PHONE'
});
// 2.x
await para.createPregenWallet({ pregenId: { phone: '+13105551234' } });
```
```tsx Farcaster
// 1.x
await para.createPregenWallet({
pregenIdentifier: 'FarcasterUsername',
pregenIdentifierType: 'FARCASTER'
});
// 2.x
await para.createPregenWallet({ pregenId: { farcasterUsername: 'FarcasterUsername' } });
```
```tsx Telegram
// 1.x
await para.createPregenWallet({
pregenIdentifier: '1234567890',
pregenIdentifierType: 'TELEGRAM'
});
// 2.x
await para.createPregenWallet({ pregenId: { telegramUserId: '1234567890' } });
```
```tsx Discord
// 1.x
await para.createPregenWallet({
pregenIdentifier: 'DiscordUsername',
pregenIdentifierType: 'DISCORD'
});
// 2.x
await para.createPregenWallet({ pregenId: { discordUsername: 'DiscordUsername' } });
```
```tsx X (Twitter)
// 1.x
await para.createPregenWallet({
pregenIdentifier: 'XUsername',
pregenIdentifierType: 'TWITTER'
});
// 2.x
await para.createPregenWallet({ pregenId: { xUsername: 'XUsername' } });
```
```tsx Custom ID
// 1.x
await para.createPregenWallet({
pregenIdentifier: 'my-custom-id',
pregenIdentifierType: 'CUSTOM_ID'
});
// 2.x
await para.createPregenWallet({ pregenId: { customId: 'my-custom-id' } });
```
## Common Enums (optional)
Enum types used in certain methods, while still available, are now replaced with string union types. You will not be required to import these enums for methods that accept them.
```tsx {4}
import { WalletType } from '@getpara/web-sdk';
// Either is allowed
para.createWallet({ type: WalletType.EVM });
para.createWallet({ type: 'EVM' });
```
### Type Definitions
Ethereum Virtual Machine compatible wallet
Solana wallet
Cosmos wallet
DKLS wallet scheme
ED25519 wallet scheme
CGGMP wallet scheme
Google OAuth
Apple OAuth
Twitter/X OAuth
Discord OAuth
Facebook OAuth
Telegram OAuth
Farcaster OAuth
## Auth Objects
User identity attestations are now represented as auth objects with a single key and value, representing both the type of attestation and the relevant identifier. These objects are used for methods that require an attestation of this type, primarily those for authentication and pregenerated wallet management.
### Auth Object Examples
```tsx Email
const auth = { email: 'email@test.com' };
await para.signUpOrLogIn({ auth });
```
```tsx Phone
const auth = { phone: '+13105551234' };
await para.signUpOrLogIn({ auth });
```
```tsx Farcaster
const pregenId = { farcasterUsername: 'MyUsername' };
await para.createPregenWallet({ pregenId, type: 'EVM' });
```
```tsx Telegram
const pregenId = { telegramUserId: '1234567890' };
await para.createPregenWallet({ pregenId, type: 'EVM' });
```
```tsx Discord
const pregenId = { discordUsername: 'MyUsername' };
await para.createPregenWallet({ pregenId, type: 'EVM' });
```
```tsx X/Twitter
const pregenId = { xUsername: 'MyUsername' };
await para.createPregenWallet({ pregenId, type: 'EVM' });
```
```tsx Custom Pregen ID
const pregenId = { customId: 'my-custom-id' };
await para.createPregenWallet({ pregenId, type: 'EVM' });
```
Phone number auth objects expect a string in international format, beginning with a `+` and containing only numbers without spaces or extra characters, i.e.: `+${number}`. If your UI deals in separated country codes and national phone numbers, you may use the exported `formatPhoneNumber` function to combine them into a correctly formatted string.
```tsx
import { formatPhoneNumber } from '@getpara/web-sdk';
await para.signUpOrLogIn({ auth: { phone: '+13105551234' } });
// or, if your country code and national number are distinct:
await para.signUpOrLogIn({ auth: { phone: formatPhoneNumber('3105551234', '1') } });
```
## Cancelable Methods (optional)
This feature is available in the following SDKs:
- `@getpara/web-sdk`
- `@getpara/react-sdk`
- `@getpara/react-native-sdk`
For methods that wait for user action, such as `waitForLogin`, you may now pass a callback that is invoked on each polling interval, as well as a callback to indicate whether the method should be canceled and another invoked upon cancelation.
```tsx
let i = 0, popupWindow: Window;
await para.waitForLogin({
isCanceled: () => popupWindow?.closed,
onPoll: () => {
console.log(`Waiting for login, polled ${++i} times...`)
},
onCancel: () => {
console.log('Login canceled after popup window closed!');
}
});
```
## New Authentication Flow
The primary methods for authenticating via phone, email address, or third-party services have been overhauled and greatly simplified. If you are using a custom authentication UI, refer to the [Custom Authentication UI](/v3/react/guides/custom-ui-simplified) page for detailed instructions and code samples. For new developers, the [Para Modal](/v3/react/overview) is the preferred option to handle user authentication in your app.
## Modified Core Methods
We've streamlined and improved several core methods in version 2.0.0. The following sections outline what's changed and what actions you need to take.
These changes are required when upgrading to version 2.0.0. Make sure to update your code accordingly to avoid breaking your application.
- `checkIfUserExists`
- `initiateUserLogin`
- `createUser`
- `checkIfUserExistsByPhone`
- `initiateUserLoginForPhone`
- `createUserByPhone`
`signUpOrLogIn`
Modify your current authentication flow to use the new simplified `signUpOrLogIn` method as detailed on our Custom Authentication UI page.
This change simplifies the authentication process by consolidating multiple methods into a single, more intuitive function.
- `waitForLoginAndSetup`
- `waitForAccountCreation`
- `waitForPasskeyAndCreateWallet`
- `waitForLogin`
- `waitForSignup`
- `waitForWalletCreation`
Modify your current authentication flow to use the new simplified methods as detailed on our Custom Authentication UI page.
- `createPregenWallet`
- `createPregenWalletPerType`
- `updatePregenWalletIdentifier`
- `hasPregenWallet`
- `claimPregenWallets`
Modified to accept a single `pregenId` argument
Update your functions to use the new notation.
```javascript Before
para.createPregenWallet({
pregenIdentifier: 'email@email.com',
pregenIdentifierType: 'EMAIL'
})
```
```javascript After
para.createPregenWallet({
pregenId: {
email: 'email@email.com'
}
})
```
- `initiateFarcasterLogin`
- `waitForFarcasterStatus`
`verifyFarcaster`
Use the simplified `verifyFarcaster` method as detailed on our Custom Authentication UI page.
- `getOAuthUrl`
- `waitForOAuth`
`verifyOAuth`
Use the simplified `verifyOAuth` method as detailed on our Custom Authentication UI page.
`touchSession`
Destructuring is simpler, returning an object of type `SessionInfo` rather than `{ data: SessionInfo }`
Update existing usages to destructure the return value correctly.
- `check2FAStatus` (Deprecated)
- `enable2FA`
- `verify2FA`
- `setup2FA`
- `setup2fa` (Replaces `check2FAStatus`)
- `enable2fa`
- `verify2fa`
- Replace calls to `check2FAStatus` with `setup2fa`, which will now either return `{ isSetup: true }` or `{ isSetup: false, uri: string }` depending on the current user's settings
- Update function instances to use the new spelling with lowercase "fa"
React Native, Flutter, Swift
`login`
`loginWithPasskey`
Update function instances to use the new name.
# Migrate to Para
Source: https://docs.getpara.com/v3/introduction/migration-to-para
## Automated Migration
Use AI-powered tooling to automatically migrate your codebase from another provider to Para.
AI-powered migration from Privy, Reown, Web3Modal, or WalletConnect to Para
## Overview
Para makes it easy to migrate users and/or wallets from another system. As with any migration, there are many nuances to
consider, and a variety of options for how to approach a migration. First, you'll need to determine what type of
integration you want to complete.
## Migration Scope
New pregenerated wallets will be created for your existing users, which they can claim when they log in. Please see
our [GitHub repository](https://github.com/getpara/examples-hub/tree/3.0.0/web/) for a sample code snippet and
instructions on how to leverage pregenerated wallets.
In addition to creating new wallets, assets can be transferred either in a batch (if migrating from a custodial system) or just-in-time as users claim their wallet and log in. In this method, assets can be ported over, but users will receive a new wallet address with Para.
If you are following this path, please get in touch with Para as the specifics of a migration path and options vary based on your current provider.
Para can also support importing a private key if your provider offers an export private key option. Using this path, users can keep the same wallet address they currently have.
This feature is experimental and availability varies based on the provider currently being used. If you would like to follow this path, please get in touch with Para for more information and to get access.
## Migration Timing
For each of these options, there are two paths to migrating:
Move users over proactively. This option works best if: - You don't have assets to migrate - You're migrating with a
smart contract account setup - You're migrating from a custodian that has a batch export feature
As users onboard, migrate over their account and/or assets. This option works best if you need users to: - Sign a
transaction to onboard - Take an action such as exporting a private key and importing it into Para's system
Migrations can be tricky business! We're here to help, please get in touch and consider us your thought partners in
this process.
# Migrating from Para v2.x to v3.0
Source: https://docs.getpara.com/v3/introduction/migration-to-v3
Use this guide for assistance migrating from Para version 2.x to Para's version 3.0 release.
## Summary of Breaking Changes
The v3.0 release simplifies the modal theming system and updates the portal URL helper return shape for advanced custom UI consumers:
1. **`foregroundColor` now drives the entire UI palette** — buttons, accents, rings, and primary colors are all generated from it via OKLCH color space. Previously, `foregroundColor` was the text/icon color; that role is now auto-generated from the background.
2. **`accentColor` is deprecated** — it still works for backward compatibility (and takes precedence over `foregroundColor` when both are provided), but you should migrate to using `foregroundColor` instead.
3. **Dark mode variant colors removed** — `darkForegroundColor`, `darkBackgroundColor`, and `darkAccentColor` no longer exist. Just provide your dark colors as `backgroundColor` and `foregroundColor` directly — the system auto-detects dark/light mode from the background color lightness.
4. **`customPalette`, `customFontSizes`, `customBorderRadii` removed** — replaced by `cssOverrides` for advanced cases.
5. **`overlayBackground` removed** from the theme type.
6. **`oAuthLogoVariant` removed** from the theme type.
7. **New properties added** — `foregroundMixRatio` and `cssOverrides`.
8. **Portal URL helpers now return `{ url, fullUrl }`** - default modal users do not need to change anything, but custom UI consumers that read portal URL fields directly should review the [Portal URL Return Shape](#portal-url-return-shape) section.
Beyond theming, v3 also moves most configuration (auth methods, external wallets, branding, and more) to the **partner record** in the Developer Portal. That change is **non-breaking**; your existing `paraModalConfig` props keep working as a deprecated fallback, and `configOverrides` is available when a setting must still be controlled in code. See [Configuration moves to the partner record](#configuration-moves-to-the-partner-record) below.
## Property Mapping
| v2 Property | v3 Equivalent |
|---|---|
| `foregroundColor` | `foregroundColor` (new semantics: drives accent palette) |
| `backgroundColor` | `backgroundColor` (unchanged) |
| `accentColor` | `foregroundColor` (`accentColor` still accepted but deprecated) |
| `darkForegroundColor` | Removed — provide dark colors via `foregroundColor` directly |
| `darkBackgroundColor` | Removed — provide dark colors via `backgroundColor` directly |
| `darkAccentColor` | Removed — provide dark colors via `foregroundColor` directly |
| `overlayBackground` | Removed |
| `oAuthLogoVariant` | Removed |
| `customPalette` | `cssOverrides` |
| `customFontSizes` | `cssOverrides` |
| `customBorderRadii` | `cssOverrides` |
| `mode` | `mode` (unchanged) |
| `borderRadius` | `borderRadius` (unchanged) |
| `font` | `font` (unchanged) |
| — | `foregroundMixRatio` (new, default 0.04) |
| — | `cssOverrides` (new) |
## Before & After
```tsx v2.x Theme
paraModalConfig={{
theme: {
foregroundColor: "#333333",
backgroundColor: "#FFFFFF",
accentColor: "#007AFF",
darkForegroundColor: "#FFFFFF",
darkBackgroundColor: "#1C1C1E",
darkAccentColor: "#0A84FF",
mode: "light",
borderRadius: "md",
font: "Inter, sans-serif",
overlayBackground: "rgba(0, 0, 0, 0.5)",
oAuthLogoVariant: "default",
customPalette: {
text: {
primary: "#333333",
secondary: "#666666"
},
primaryButton: {
surface: {
default: "#007AFF",
hover: "#0056CC"
}
}
}
}
}}
```
```tsx v3.0 Theme
paraModalConfig={{
theme: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
mode: "light",
borderRadius: "md",
font: "Inter, sans-serif"
}
}}
```
In most cases, the v3.0 theme is dramatically simpler. The OKLCH color generation system automatically produces a harmonious palette from your `foregroundColor` and `backgroundColor`. Only use `cssOverrides` if you need to fine-tune specific generated colors.
## Detailed Changes
**What changed:** `foregroundColor` previously set the text and icon color. In v3, it sets the primary interactive color (buttons, accents, rings, links) and the text color is auto-generated from the background lightness.
**`accentColor` deprecated:** If you were using `accentColor` to set the interactive color, rename it to `foregroundColor`. If you pass both, `accentColor` takes precedence for backward compatibility.
**Dark mode variants removed:** Instead of maintaining separate `darkForegroundColor`, `darkBackgroundColor`, and `darkAccentColor`, provide your dark colors directly as `backgroundColor` and `foregroundColor`. The system auto-detects dark mode from the background color lightness:
```tsx
// v2.x
theme: {
foregroundColor: "#333333",
backgroundColor: "#FFFFFF",
accentColor: "#007AFF",
darkForegroundColor: "#FFFFFF",
darkBackgroundColor: "#1C1C1E",
darkAccentColor: "#0A84FF",
}
// v3.0 — dark mode is auto-detected from the background color
theme: {
foregroundColor: "#0A84FF",
backgroundColor: "#1C1C1E",
}
```
If the auto-detection misclassifies your background color (e.g., a color near the light/dark boundary), use `mode` to override it:
```tsx
theme: {
foregroundColor: "#0A84FF",
backgroundColor: "#4A4A4A", // ambiguous lightness
mode: "dark" // override: treat as dark
}
```
**What changed:** The deeply nested `customPalette`, `customFontSizes`, and `customBorderRadii` objects have been replaced by a flat `cssOverrides` map.
**Migration:** Identify the specific colors you customized and map them to CSS custom properties:
```tsx
// v2.x
theme: {
customPalette: {
primaryButton: {
surface: {
default: "#007AFF",
hover: "#0056CC"
}
},
text: {
secondary: "#666666"
}
}
}
// v3.0
theme: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
cssOverrides: {
"--para-color-primary": "#007AFF",
"--para-color-muted-foreground": "#666666"
}
}
```
Most `customPalette` usage is no longer needed. The OKLCH color generation from `foregroundColor` and `backgroundColor` handles palette creation automatically. Only use `cssOverrides` for specific overrides.
See the [Style the Modal](/v3/react/guides/customization/modal-theming#available-css-variables) guide for the full list of available CSS variables.
**`overlayBackground`** — The modal overlay backdrop styling is no longer configurable through the theme. Use the `className` prop on `paraModalConfig` for custom overlay styling if needed.
**`oAuthLogoVariant`** — The OAuth provider logo variant (`'dark' | 'light' | 'default'`) has been removed. Logos are automatically styled based on the theme mode.
**`foregroundMixRatio`** (default: `0.04`): Controls how much the `foregroundColor` mixes into UI surfaces like buttons, inputs, and borders. Useful for fine-tuning the visual weight of your accent color across the interface.
```tsx
theme: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
foregroundMixRatio: 0.12 // stronger accent presence
}
```
**`cssOverrides`** — A `Record` for setting raw CSS custom properties after theme generation. This is an advanced escape hatch for overriding specific generated colors without affecting the rest of the palette.
```tsx
theme: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
cssOverrides: {
"--para-color-destructive": "#E74C3C",
"--para-radius": "0.75rem"
}
}
```
## Backward Compatibility
Existing v2.x theme configurations will largely continue to work:
- `accentColor` is still accepted and maps to `foregroundColor`
- `backgroundColor`, `mode`, `borderRadius`, and `font` work identically
- Unknown properties (like `darkForegroundColor` or `customPalette`) are silently ignored
The main action required is updating `accentColor` to `foregroundColor` to avoid deprecation warnings, and replacing `customPalette` with `cssOverrides` if you used advanced palette customization.
## Configuration moves to the partner record
In v3, your **partner record** (managed in the [Developer Portal](https://developer.getpara.com)) is the source of truth for most configuration, including authentication methods, theme, external wallets, links, and more. The SDK reads that record at runtime, then applies any explicit `configOverrides`. The matching `paraModalConfig` props still work but are now **deprecated**: they're the lowest-priority fallback and will be removed in the next major release. Each logs a one-time deprecation warning.
This change is **non-breaking**. If you upgrade without touching the Developer Portal, your existing props keep working exactly as before — migrate at your own pace. For the full model (layering, `configOverrides`, enforcement), see [How Configuration Works](/v3/react/guides/customization/configuration).
### What to do instead
Move each setting to the partner record in the Developer Portal. For overrides that must live in code (e.g. one API key powering multiple brand contexts), use the `configOverrides` prop on `ParaProvider` instead of `paraModalConfig`.
### Migrate with the Para CLI
Use the Para CLI migration when you want an automated first pass before reviewing and committing the v3 changes. In this walkthrough, you will install the latest CLI, authenticate, run a dry run, review the planned local and API-key changes, apply the migration, and verify the result. Run it from a clean git branch so you can review or discard the generated diff safely.
The migration command is intended for apps that use `ParaProvider` with static `paraModalConfig` or `externalWalletConfig` props. If your app uses a custom provider abstraction, custom UI flow, or stores Para configuration outside `ParaProvider`, move those settings manually in the Developer Portal, then use `configOverrides` only for values that must stay in code.
First, install or update the CLI:
```bash
npm install -g @getpara/cli@latest
para --version
```
Authenticate with the Developer Portal account that owns the project you are migrating:
```bash
para login
para whoami
```
If the CLI is not already pointed at the right organization and project, select them before applying changes:
```bash
para orgs list
para projects list
para keys list
para config set defaultOrganizationId
para config set defaultProjectId
```
From the app directory, start with a dry run. The JSON output shows package changes, files that would be edited, and each config value the command inspected.
```bash
para migrate v3 . --dry-run --json
```
Review the plan before applying it. Static `paraModalConfig` and `externalWalletConfig` values can move to API-key configuration, including auth methods, disabled login methods, guest mode, 2FA, modal layout, theme, logo, external wallet lists, and external wallet behavior. Runtime values, imported values, connector instances, environment-derived values, and values without a deterministic portal equivalent stay in code or are flagged for manual review.
Passing `--key` tells the CLI which API key should receive migrated configuration. The command refuses to overwrite existing non-empty API-key config when it conflicts with local props.
Apply the migration once the dry run looks correct:
```bash
para migrate v3 . --apply --key
```
The command updates supported `@getpara/*` dependencies, removes migrated static props from `ParaProvider`, writes the matching values to the selected API key, and creates a `.para-migrate-v3.json` rollback manifest in the app directory. After applying, inspect both the code diff and the API-key configuration:
```bash
git diff
para keys config show --json
```
The API key should now contain the moved settings under fields such as `authConfig`, `modalConfig`, `themeConfig`, `logoUrl`, `externalWalletConfig`, and `partnerLinks`. The SDK reads these settings from the partner record at runtime, and any explicit `configOverrides` in code still take precedence.
If you need to undo the migration, use the rollback manifest. Rollback restores the edited files and the captured previous API-key configuration.
```bash
para migrate rollback --manifest ./.para-migrate-v3.json --apply
```
| Deprecated `paraModalConfig` prop | Configure instead |
|---|---|
| `oAuthMethods` | Portal → Authentication, or `configOverrides.authConfig.oAuthMethods` |
| `disableEmailLogin` / `disablePhoneLogin` | Portal → Authentication, or `configOverrides.authConfig.*` |
| `twoFactorAuthEnabled` | Portal → Authentication, or `configOverrides.authConfig.twoFactorAuthEnabled` |
| `isGuestModeEnabled` | Portal → Authentication, or `configOverrides.authConfig.isGuestModeEnabled` |
| `authLayout` | Portal → Authentication, or `configOverrides.modalConfig.authLayout` |
| `hideWallets` | Portal, or `configOverrides.modalConfig.hideWallets` |
| `theme` | Portal → Branding, or `configOverrides.themeConfig` |
| `logo` | Portal → Branding, or `configOverrides.modalConfig.logo` |
| `supportedAccountLinks` | Portal (partner-authoritative) |
| `balances` | Portal (partner-authoritative) |
### Before & After
```tsx v2.x (config in props)
paraModalConfig={{
oAuthMethods: ["GOOGLE", "APPLE"],
disableEmailLogin: false,
twoFactorAuthEnabled: true,
isGuestModeEnabled: false,
theme: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
},
}}
```
```tsx v3.0 (partner record + optional code override)
// OAuth methods, 2FA, guest mode, and theme typically live on the partner
// record (Developer Portal). Configure them there once; changing them later
// needs no redeploy.
// Only reach for configOverrides when you need a per-instance value in code:
{children}
```
### New enforcement behavior
Because the SDK now enforces the resolved auth and wallet configuration at its entry points, calling a method for something that is **explicitly disabled** throws a `PartnerConfigError` (with a stable `.code`, e.g. `GUEST_MODE_DISABLED`). This applies to direct SDK calls and custom UI; the default ` ` simply hides disabled options.
Only an **explicit** disable in the resolved config triggers this. A setting you never configured stays permissive, so upgrading without changing the portal or adding `configOverrides` won't start throwing. See [How Configuration Works → Enforcement](/v3/react/guides/customization/configuration#enforcement-partnerconfigerror) for the full list of error codes.
## Portal URL Return Shape
v3.0 introduces companion `*FullUrl` fields on the auth state types and changes the internal portal URL helpers to return `{ url, fullUrl }` instead of a bare string. This powers the new preloaded portal iframe — navigating a preloaded iframe to a shortened URL forces a redirect that negates the preload, so the SDK now surfaces the unshortened URL alongside the (possibly shortened) one.
### Who needs to care
- **Default modal users (` `):** No action required. The modal consumes the new fields internally.
- **Custom UI consumers** (callers of `signUpOrLogIn`, `verifyOAuth`, `verifyNewAccount`, `onStatePhaseChange`, etc. who read `passkeyUrl` / `passwordUrl` / `pinUrl` / `loginUrl` themselves): read the note below.
- **Callers of `PortalUrlService.constructPortalUrl` / `PortalUrlService.getLoginUrl`, or subclasses that call `ParaCore.constructPortalUrl` directly:** the return value is now `{ url, fullUrl }` instead of `string`. Update your call sites to destructure.
### What changed on the auth state types
`AuthStateVerify`, `AuthStateLogin`, and `AuthStateSignup` each gained optional `*FullUrl` siblings next to their existing URL fields:
| Existing field | New companion field | Where it appears |
|---|---|---|
| `loginUrl` | `loginFullUrl` | [`AuthStateVerify`](/v3/references/types/authstateverify) |
| `passkeyUrl` | `passkeyFullUrl` | [`AuthStateLogin`](/v3/references/types/authstatelogin), [`AuthStateSignup`](/v3/references/types/authstatesignup) |
| `passwordUrl` | `passwordFullUrl` | [`AuthStateLogin`](/v3/references/types/authstatelogin), [`AuthStateSignup`](/v3/references/types/authstatesignup) |
| `pinUrl` | `pinFullUrl` | [`AuthStateLogin`](/v3/references/types/authstatelogin), [`AuthStateSignup`](/v3/references/types/authstatesignup) |
When `useShortUrls` is `false` (the default), `*Url` and `*FullUrl` hold the same value. When `useShortUrls` is `true`, `*Url` is the shortened URL and `*FullUrl` is the unshortened one.
The same fields are mirrored on `authStateInfo` exposed by `onStatePhaseChange` / `useParaStatus`: `passkeyFullUrl`, `passwordFullUrl`, `pinFullUrl`, and `verificationFullUrl`.
The OAuth callback signature also picked up the full URL as a second argument:
```ts
// v2
redirectCallbacks: {
onOAuthUrl: (url: string) => {},
}
// v3
redirectCallbacks: {
onOAuthUrl: (url: string, fullUrl?: string) => {},
}
```
### Picking which URL to open
Keep using the existing `passkeyUrl` / `passwordUrl` / `pinUrl` / `loginUrl` / `url` values. You get short URLs when you opt in via `useShortUrls: true`, which is still the right call for QR codes and links you pass around.
Use the `*FullUrl` variant (or the `fullUrl` second arg on `onOAuthUrl`). A shortened URL would force a redirect inside the iframe and throw away the preloaded state.
### Custom UI example
```tsx
// v2 — still works in v3, but you miss out on the preloaded iframe optimization
const { passkeyUrl, passwordUrl, pinUrl } = authStateInfo;
window.open(passkeyUrl ?? passwordUrl ?? pinUrl, "ParaPortal", "popup");
// v3 — keep the short URL for popups; use *FullUrl for preloaded iframe nav
const { passkeyUrl, passkeyFullUrl, passwordUrl, passwordFullUrl, pinUrl, pinFullUrl } = authStateInfo;
// Popups: short URL is fine
window.open(passkeyUrl ?? passwordUrl ?? pinUrl, "ParaPortal", "popup");
// Preloaded iframe: point at the full URL instead
iframeRef.current.src = passkeyFullUrl ?? passwordFullUrl ?? pinFullUrl ?? "";
```
### Service-layer callers (advanced)
If you call `PortalUrlService` directly, or subclass `ParaCore` and call `constructPortalUrl`, the method signatures changed:
```ts
// v2
const url: string = await portalUrlService.constructPortalUrl("loginAuth", opts);
const loginUrl: string = await portalUrlService.getLoginUrl(opts);
// v3
const { url, fullUrl } = await portalUrlService.constructPortalUrl("loginAuth", opts);
const { url: loginUrl, fullUrl: loginFullUrl } = await portalUrlService.getLoginUrl(opts);
```
`ParaCore.getLoginUrl()`, `ParaCore.getOAuthUrl()`, and `ParaCore.addCredential()` still return a bare `string`; those are unchanged.
# Use Para from Your Backend
Source: https://docs.getpara.com/v3/introduction/use-para-api
Use this path when your backend is the integration boundary. This usually means your server owns user identity, needs to provision wallets programmatically, automates wallet operations, or calls Para from server-side code.
## Choose This Path If
- Your backend owns user authentication or maps existing users to Para wallets.
- You want to create or pregenerate wallets before users visit your app.
- You want HTTP endpoints, cURL, or language-native clients instead of a client SDK.
- You want server-side helpers for sessions, pregeneration, automation, or chain operations.
REST API and Node & Server are related but not identical. REST API is the direct HTTP surface. Node & Server is the SDK path for backend JavaScript integrations.
## Integration Lifecycle
Create a project and API key in the [Developer Portal](https://developer.getpara.com/), then review [Developer Portal setup](/v3/general/developer-portal-setup). Protect backend API keys before making requests.
Use the [REST API](/v3/rest/overview) for direct HTTP wallet operations. Use [Node & Server](/v3/server/overview) when you want server-side SDK helpers in a JavaScript backend.
For REST, start with [REST setup](/v3/rest/setup), [REST SDK](/v3/rest/sdk), [multi-wallet setup](/v3/rest/multi-wallet), or [permissions](/v3/rest/permissions). For server SDK, start with [sessions](/v3/server/guides/sessions) or [pregen](/v3/server/guides/pregen).
Use the REST endpoints or server SDK chain guides for EVM, Solana, Cosmos, or Stellar signing.
Review IP restrictions, API key handling, [webhooks](/v3/general/webhooks), the [Go Live Checklist](/v3/general/checklist), and [Production Deployment](/v3/general/production-deployment).
## Next Docs
Create wallets and sign raw bytes from your backend over HTTP.
Use the TypeScript client for backend wallet operations.
Use Para's server-side SDK for JavaScript backend integrations.
Create wallets before users sign up or visit your app.
Subscribe your backend to Para wallet and user events.
Move backend integrations from `BETA` to `PRODUCTION`.
# Use Para Modal
Source: https://docs.getpara.com/v3/introduction/use-para-ui
Use this path when your React app can rely on Para's prebuilt modal for authentication, embedded wallet creation, wallet management, and signing prompts. Your app controls when the modal opens and how it fits into your product, while Para owns the wallet UI itself.
## Choose This Path If
- You are building a React, Next.js, Vite, or TanStack Start web app.
- You want Para auth, embedded wallets, wallet screens, and signing prompts out of the box.
- You want branding, supported auth methods, external wallets, and security posture managed from the Developer Portal.
Para Modal is React-only. If your app owns the screens, uses Vue, Svelte, the Web SDK directly, React Native, Flutter, or Swift, use [Build Custom UI](/v3/introduction/build-custom-ui). If your backend owns auth or wallet operations, use [Use Para from Your Backend](/v3/introduction/use-para-api).
## Integration Lifecycle
Create a project and API key in the [Developer Portal](https://developer.getpara.com/), then review [Developer Portal setup](/v3/general/developer-portal-setup).
Add Para to your app with the [React quickstart](/v3/react/quickstart), then choose the setup guide for [Next.js](/v3/react/setup/nextjs), [Vite](/v3/react/setup/vite), or [TanStack Start](/v3/react/setup/tanstack-start).
Review [How Configuration Works](/v3/react/guides/customization/configuration), configure authentication in the [Developer Portal](/v3/react/guides/customization/developer-portal-authentication), then customize the [Para Modal](/v3/react/guides/customization/modal).
Use [Sign with Para](/v3/react/guides/web3-operations/sign-with-para) to confirm that authentication, wallet creation, and signing work end to end.
Review the [Go Live Checklist](/v3/general/checklist) and [Production Deployment](/v3/general/production-deployment) before moving from `BETA` to `PRODUCTION`.
## Next Docs
Add Para's web SDK, provider, and modal to a React app.
Understand the v3 partner record, SDK overrides, and deprecated modal props.
Choose the login methods, external wallets, and security posture your modal offers.
Update colors, fonts, and CSS overrides for v3's simplified theme system.
Verify the modal path by signing a message or transaction.
# Welcome
Source: https://docs.getpara.com/v3/introduction/welcome
import { Card as ImageCard } from "/snippets/v3/components/ui/card.mdx";
import { IconTile } from "/snippets/v3/components/ui/icon-tile.mdx";
import { IconTileRow } from "/snippets/v3/components/ui/icon-tile-row.mdx";
import { IconTileGrid } from "/snippets/v3/components/ui/icon-tile-grid.mdx";
import { Link } from "/snippets/v3/components/ui/link.mdx";
Para at a glance
Para is an all-in-one developer suite to easily provision and manage embedded wallets directly within applications. Para is trusted by 100+ teams and their 15M+ users .
## Choose a Path
Start by deciding who owns the user interface, who owns authentication, and where wallet operations run. Choose the path that matches those decisions, then use the platform docs as implementation details.
## Set Up Your Project
Every path starts with a Para project and API key. Create your project in the , then follow the setup docs that match your integration.
Create projects, API keys, domains, and environment settings.
Compare SDK support across web, mobile, server, and REST integrations.
Start from a complete app or verify how an integration is wired.
## Why Teams Choose Para
The core product model stays the same across every path.
Users sign up with email, phone, social login, or passkey. No seed phrases or browser extensions required, ever.
Private keys are split via MPC between the user's device and Para's secure infrastructure. Neither Para nor your application ever holds complete keys.
Support for EVM, Solana, Cosmos, and Stellar with SDKs for web, mobile, and server.
## Browse by Platform
If you already know the SDK or framework you need, jump directly into its setup docs.
## Chain Integration Guides
Para supports EVM, Solana, Cosmos, and Stellar chains and works with popular signing libraries and wallet connectors.
## Migrating from Another Provider
Already using an embedded wallet provider? These guides help you switch without rethinking the whole integration.
## Additional Resources
**Using an AI coding agent?** Paste this into Claude Code, Cursor, or any agent to get it set up with Para:
```
Fetch https://docs.getpara.com/skill.md and help me build with Para
```
Your agent will install the , authenticate, and help you build.
# What's New in v3.0
Source: https://docs.getpara.com/v3/introduction/whats-new
## Simplified Theming
The v3.0 release dramatically simplifies modal theming. Instead of managing 13+ theme properties with separate dark mode colors and deeply nested custom palettes, you now set a `foregroundColor` and `backgroundColor` and the system generates a complete, harmonious palette using OKLCH color space.
### One Color Drives Everything
Set `foregroundColor` and buttons, accents, rings, and interactive elements are automatically derived:
```tsx
paraModalConfig={{
theme: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF"
}
}}
```
### No More Dark/Light Variant Properties
No more maintaining separate `darkForegroundColor`, `darkBackgroundColor`, and `darkAccentColor`. Just provide your dark colors directly — the system auto-detects dark mode from the background color lightness:
```tsx
paraModalConfig={{
theme: {
foregroundColor: "#0A84FF",
backgroundColor: "#1C1C1E",
}
}}
```
If the auto-detection misclassifies your background color (e.g., an ambiguous color near the light/dark boundary), use `mode` to override it.
### CSS Overrides for Power Users
Replace `customPalette` with `cssOverrides` for raw CSS variable control when you need granular adjustments:
```tsx
paraModalConfig={{
theme: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
cssOverrides: {
"--para-color-accent": "#1A1A2E",
"--para-color-destructive": "#E74C3C"
}
}
}}
```
### Fine-Tune with Foreground Mix Ratio
The new `foregroundMixRatio` property lets you control how much your accent color bleeds into surface colors:
```tsx
paraModalConfig={{
theme: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
foregroundMixRatio: 0.12 // stronger accent presence
}
}}
```
## Upgrading from v2.x
# React Native & Expo SDK
Source: https://docs.getpara.com/v3/react-native/api/sdk
## ParaMobile Class
The `ParaMobile` class represents a mobile implementation of the Para SDK, extending the `CorePara` class.
### Methods
Creates an instance of ParaMobile.
The environment to use (BETA or PROD).
The API key for authentication.
The relying party ID for WebAuthn.
Additional constructor options.
Verifies an email and returns the biometrics ID.
The verification code sent to the email.
The biometrics ID.
Verifies a phone number and returns the biometrics ID.
The verification code sent to the phone.
The biometrics ID.
Registers a passkey for the user.
The user's email or phone number.
The biometrics ID obtained from verification.
The Web Crypto API instance.
The type of identifier used.
The country calling code for phone numbers.
A promise that resolves when the passkey is registered.
Logs in the user using either email or phone number.
The user's email address.
The user's phone number.
The country calling code for phone numbers.
An array of user wallets.
If neither email nor both phone and countryCode are provided.
# Passkey Error Codes
Source: https://docs.getpara.com/v3/react-native/error-codes
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
The React Native SDK wraps native passkey errors from iOS and Android into `ParaPasskeyError`, a structured error class
with a typed error code, platform detection, an actionable suggestion, and a link back to this page. Every passkey
operation (`registerPasskey`, `loginWithPasskey`) throws a `ParaPasskeyError` on failure.
```ts
import { ParaPasskeyError } from "@getpara/react-native-sdk";
try {
await para.registerPasskey(authState);
} catch (error) {
if (error instanceof ParaPasskeyError) {
console.log(error.code); // "RP_ID_MISMATCH"
console.log(error.platform); // "android"
console.log(error.suggestion); // actionable fix
}
}
```
Each error includes these properties:
| Property | Type | Description |
|----------|------|-------------|
| `code` | `ParaPasskeyErrorCode` | Machine-readable error code from the table below |
| `platform` | `'ios' \| 'android' \| 'unknown'` | Platform that produced the error |
| `message` | `string` | Original error message from the native layer |
| `suggestion` | `string` | Actionable guidance for resolving the error |
| `docsUrl` | `string` | Direct link to the relevant section on this page |
| `cause` | `unknown` | Original error object for deep debugging |
## USER_CANCELLED
The user dismissed the passkey prompt (tapped cancel, swiped away, or declined biometric authentication).
**Platforms:** iOS, Android
**What to do:** This is expected behavior. Handle it gracefully by catching the error and allowing the user to retry.
Do not treat this as a fatal error or show an error screen.
## TIMED_OUT
The passkey operation exceeded the system timeout before the user completed the biometric prompt.
**Platforms:** iOS, Android
**What to do:** Prompt the user to try again. If this happens frequently, check whether the timeout configuration in
your passkey request is too short for your users' environment.
## BAD_CONFIGURATION
The app's entitlements, associated domains, or passkey request parameters are misconfigured.
**Platforms:** iOS, Android
**What to do:**
- **iOS:** Verify that your Xcode project (or `app.json` for Expo) includes the associated domains entitlement with
`webcredentials:app.beta.usecapsule.com` and `webcredentials:app.usecapsule.com`. Confirm your Team ID and Bundle ID
are registered in the .
- **Android:** Verify your SHA-256 signing certificate fingerprint is registered in the under your API key's Native Passkey
Configuration.
This code also covers `InvalidChallenge` and `InvalidUser` errors from the native layer, which indicate malformed
request parameters. These are typically SDK internal issues — if you encounter them, please .
## NOT_CONFIGURED
The credential provider is not set up on the device.
**Platforms:** Android
**What to do:** This is Android-specific. Ensure Google Play Services is available and that a Google account is signed
in on the device. Devices without Google Play Services (some Huawei devices, custom ROMs) cannot use passkeys through
the Credential Manager API.
## RP_ID_MISMATCH
The app's signing certificate does not match what is registered for the relying party domain.
**Platforms:** Android
**What to do:** This is the most common Android passkey error. Debug and release builds use different signing keys, and
the SHA-256 fingerprint registered in the Developer Portal must match the build you are testing.
Get your debug fingerprint:
```bash
keytool -list -v -keystore ~/.android/debug.keystore \
-alias androiddebugkey -storepass android -keypass android
```
Get your release fingerprint:
```bash
keytool -list -v -keystore
```
Register both fingerprints in the to avoid this
error across build types.
After registering a new fingerprint, Google can take up to 24 hours to verify it. Check the verification status in
the Developer Portal under your API key's Native Passkey Configuration.
## NOT_SUPPORTED
Passkeys are not supported on this device.
**Platforms:** iOS, Android
**What to do:** Passkeys require iOS 16+ or Android API 28+. If the device does not meet these requirements, fall back
to an alternative authentication method (email OTP, password, or social login). Use this error code to detect
unsupported devices and present the appropriate fallback UI.
## NO_CREDENTIALS
No passkeys are stored on this device for the requested user.
**Platforms:** Android (login only)
**What to do:** This means the user has not registered a passkey on this device, or is on a different device than where
the passkey was created. Ensure your auth flow calls `registerPasskey()` for new users before attempting
`loginWithPasskey()`. If the user previously registered on another device, they will need to register again on this one
or use cross-device passkey authentication if supported.
## REQUEST_FAILED
The passkey request failed at the system level.
**Platforms:** iOS, Android
**What to do:**
- **iOS:** Check that your associated domains entitlement is configured correctly and that your Team ID and Bundle ID
are registered in the . Also verify the device
can reach Apple's CDN to validate the Apple App Site Association file.
- **Android:** Verify your SHA-256 fingerprint is registered correctly. See the [RP_ID_MISMATCH](#rp_id_mismatch)
section for fingerprint instructions.
## INTERRUPTED
The passkey operation was interrupted by the system before it could complete.
**Platforms:** iOS, Android
**What to do:** This can happen when the app is backgrounded during a passkey operation, or when another authentication
prompt takes priority. The operation can be safely retried.
## UNKNOWN
An unrecognized error occurred during the passkey operation.
**Platforms:** iOS, Android
**What to do:** Check the `cause` property on the error object for the original native error details. If the error
persists, with the full error output from the
development console. In development builds, the SDK automatically logs a diagnostic block with the error code, platform,
relying party ID, and suggestion.
# Mobile Examples
Source: https://docs.getpara.com/v3/react-native/examples
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para offers a collection of mobile examples to help you integrate our technology into your React Native and Expo applications. These examples demonstrate minimal implementation requirements with clean, focused code that you can easily adapt to your specific application needs.
## Para Mobile Examples
Browse our mobile examples showcasing Para integration with React Native and Expo:
## Need Something Specific?
Don't see an example for your use case? Para's team is eager to create new examples to help you integrate with different libraries, third-party features, or providers.
# Account Abstraction
Source: https://docs.getpara.com/v3/react-native/guides/account-abstraction
import { Link } from '/snippets/v3/components/ui/link.mdx';
import SmartAccountOverview from '/snippets/v3/aa/smart-account-overview.mdx';
## Provider Guides
Select a provider below for installation and usage instructions. Each tab shows two patterns:
- **Custom Hook** — a React component pattern that initializes the smart account once in a `useEffect` and manages send state with `useMutation` from `@tanstack/react-query`. Good for screens that need reactive loading and error state out of the box.
- **Action** — the raw `async` function call. Use this directly in event handlers, background tasks, or any state management setup you already have.
You'll need an initialized and authenticated Para instance before calling any `createXxxSmartAccount` function. See the for details.
provides modular smart accounts with built-in gas sponsorship via Alchemy's Gas Manager. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create an and obtain your **API key** and **Gas Policy ID** from the Alchemy dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-alchemy viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-alchemy viem
```
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx AlchemyScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createAlchemySmartAccount } from "@getpara/aa-alchemy";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
export function AlchemyScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
createAlchemySmartAccount({
para,
apiKey: process.env.EXPO_PUBLIC_ALCHEMY_API_KEY!,
chain: sepolia,
gasPolicyId: process.env.EXPO_PUBLIC_ALCHEMY_GAS_POLICY_ID,
mode: "4337",
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript alchemy-action.ts
import { createAlchemySmartAccount } from "@getpara/aa-alchemy";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const smartAccount = await createAlchemySmartAccount({
para,
apiKey: process.env.EXPO_PUBLIC_ALCHEMY_API_KEY!,
chain: sepolia,
gasPolicyId: process.env.EXPO_PUBLIC_ALCHEMY_GAS_POLICY_ID,
mode: "4337", // or "7702"
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
Alchemy requires chains from `@account-kit/infra` (e.g. `sepolia`, `baseSepolia`). Plain viem chains are automatically mapped if an Alchemy equivalent exists. For gasless transactions, set up a Gas Manager Policy in your and pass the policy ID as `gasPolicyId`.
provides Kernel smart accounts with gas sponsorship, session keys, recovery, and chain abstraction. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **Project ID** from the ZeroDev dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-zerodev viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-zerodev viem
```
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx ZeroDevScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createZeroDevSmartAccount } from "@getpara/aa-zerodev";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
export function ZeroDevScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
createZeroDevSmartAccount({
para,
projectId: process.env.EXPO_PUBLIC_ZERODEV_PROJECT_ID!,
chain: sepolia,
mode: "4337",
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript zerodev-action.ts
import { createZeroDevSmartAccount } from "@getpara/aa-zerodev";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const smartAccount = await createZeroDevSmartAccount({
para,
projectId: process.env.EXPO_PUBLIC_ZERODEV_PROJECT_ID!,
chain: sepolia,
mode: "4337", // or "7702"
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
You can optionally pass `bundlerUrl` and `paymasterUrl` to use custom infrastructure instead of ZeroDev's defaults. See the for details.
provides AA infrastructure via permissionless.js, a lightweight TypeScript library built on viem. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **API key** from the Pimlico dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-pimlico viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-pimlico viem
```
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx PimlicoScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createPimlicoSmartAccount } from "@getpara/aa-pimlico";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
export function PimlicoScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
createPimlicoSmartAccount({
para,
apiKey: process.env.EXPO_PUBLIC_PIMLICO_API_KEY!,
chain: sepolia,
mode: "4337",
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript pimlico-action.ts
import { createPimlicoSmartAccount } from "@getpara/aa-pimlico";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const smartAccount = await createPimlicoSmartAccount({
para,
apiKey: process.env.EXPO_PUBLIC_PIMLICO_API_KEY!,
chain: sepolia,
mode: "4337", // or "7702"
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
The Pimlico bundler/paymaster URL is automatically constructed from your API key and chain name. You can override it with a custom `rpcUrl`.
provides smart accounts with cross-chain orchestration via its Multi-chain Execution Environment (MEE). Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **API key** from the Biconomy dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-biconomy viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-biconomy viem
```
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx BiconomyScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createBiconomySmartAccount } from "@getpara/aa-biconomy";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
export function BiconomyScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
createBiconomySmartAccount({
para,
apiKey: process.env.EXPO_PUBLIC_BICONOMY_API_KEY!,
chain: sepolia,
mode: "4337",
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript biconomy-action.ts
import { createBiconomySmartAccount } from "@getpara/aa-biconomy";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const smartAccount = await createBiconomySmartAccount({
para,
apiKey: process.env.EXPO_PUBLIC_BICONOMY_API_KEY!,
chain: sepolia,
mode: "4337", // or "7702"
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
Biconomy transactions are executed via the MEE (Multi-chain Execution Environment). You can optionally pass a custom `meeUrl` to use your own MEE node.
provides smart wallets with built-in gas sponsorship. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **Client ID** from the Thirdweb dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-thirdweb viem @thirdweb-dev/react-native-adapter react-native-mmkv react-native-nitro-modules @react-native-community/netinfo react-native-svg @coinbase/wallet-mobile-sdk
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-thirdweb viem @thirdweb-dev/react-native-adapter react-native-mmkv react-native-nitro-modules @react-native-community/netinfo react-native-svg @coinbase/wallet-mobile-sdk
```
Thirdweb requires additional native dependencies for React Native beyond what other AA providers need. The packages above include `@thirdweb-dev/react-native-adapter` and its peer dependencies. See the for full details.
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx ThirdwebScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createThirdwebSmartAccount } from "@getpara/aa-thirdweb";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
export function ThirdwebScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
createThirdwebSmartAccount({
para,
clientId: process.env.EXPO_PUBLIC_THIRDWEB_CLIENT_ID!,
chain: sepolia,
sponsorGas: true,
mode: "4337",
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript thirdweb-action.ts
import { createThirdwebSmartAccount } from "@getpara/aa-thirdweb";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const smartAccount = await createThirdwebSmartAccount({
para,
clientId: process.env.EXPO_PUBLIC_THIRDWEB_CLIENT_ID!,
chain: sepolia,
sponsorGas: true,
mode: "4337", // or "7702"
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
Set `sponsorGas: false` to disable gas sponsorship. In EIP-4337 mode, you can optionally provide `factoryAddress` and `accountAddress` for custom smart wallet deployments.
provides native EIP-7702 smart accounts with built-in gas sponsorship via relay infrastructure. Gelato only supports EIP-7702 mode.
### Setup
1. Create a and obtain your **API key** from the Gelato dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-gelato viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-gelato viem
```
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx GelatoScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createGelatoSmartAccount } from "@getpara/aa-gelato";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
export function GelatoScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
createGelatoSmartAccount({
para,
apiKey: process.env.EXPO_PUBLIC_GELATO_API_KEY!,
chain: sepolia,
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript gelato-action.ts
import { createGelatoSmartAccount } from "@getpara/aa-gelato";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const smartAccount = await createGelatoSmartAccount({
para,
apiKey: process.env.EXPO_PUBLIC_GELATO_API_KEY!,
chain: sepolia,
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
Gelato only supports EIP-7702 mode. Gas sponsorship is built into Gelato's relay infrastructure — no separate paymaster configuration is needed.
provides 7702-native smart accounts with a built-in relay — no bundler or paymaster needed. Porto only supports EIP-7702 mode.
### Setup
1. No API key needed. Porto uses its own relay RPC. For **mainnet gas sponsorship**, set up a and pass your endpoint as `merchantUrl`.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-porto viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-porto viem
```
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx PortoScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createPortoSmartAccount } from "@getpara/aa-porto";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { base } from "viem/chains";
import { parseEther } from "viem";
export function PortoScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
// Testnets: gas sponsored by default. Mainnet: merchantUrl required.
createPortoSmartAccount({
para,
chain: base,
merchantUrl: process.env.EXPO_PUBLIC_PORTO_MERCHANT_URL,
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript porto-action.ts
import { createPortoSmartAccount } from "@getpara/aa-porto";
import { parseEther } from "viem";
import { base } from "viem/chains";
// Testnets: gas sponsored by default. Mainnet: merchantUrl required.
const smartAccount = await createPortoSmartAccount({
para,
chain: base,
merchantUrl: process.env.EXPO_PUBLIC_PORTO_MERCHANT_URL,
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
Porto only supports EIP-7702 mode. Supports including Base, Optimism, Arbitrum, and Ethereum. On testnets, gas is sponsored by default. For mainnet, a is required.
provides multi-signature smart contract wallets with ERC-4337 compatibility, powered by Pimlico's bundler and paymaster. Safe only supports EIP-4337 mode.
### Setup
1. Create a and obtain your **API key** — Safe uses Pimlico for bundler and paymaster services.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-safe viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-safe viem
```
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx SafeScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createSafeSmartAccount } from "@getpara/aa-safe";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
export function SafeScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
createSafeSmartAccount({
para,
pimlicoApiKey: process.env.EXPO_PUBLIC_PIMLICO_API_KEY!,
chain: sepolia,
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript safe-action.ts
import { createSafeSmartAccount } from "@getpara/aa-safe";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const smartAccount = await createSafeSmartAccount({
para,
pimlicoApiKey: process.env.EXPO_PUBLIC_PIMLICO_API_KEY!,
chain: sepolia,
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
Safe only supports EIP-4337 mode. You can optionally pass `safeVersion` (default: `"1.4.1"`) and `saltNonce` for deterministic address generation.
provides cross-chain smart accounts with intent-based transactions and automatic bridging. Rhinestone only supports EIP-4337 mode.
### Setup
1. Obtain your **Rhinestone API key** and optionally a **Pimlico API key** for bundler/paymaster infrastructure.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-rhinestone viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-rhinestone viem
```
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx RhinestoneScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createRhinestoneSmartAccount } from "@getpara/aa-rhinestone";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
export function RhinestoneScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
createRhinestoneSmartAccount({
para,
chain: sepolia,
rhinestoneApiKey: process.env.EXPO_PUBLIC_RHINESTONE_API_KEY!,
pimlicoApiKey: process.env.EXPO_PUBLIC_PIMLICO_API_KEY,
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript rhinestone-action.ts
import { createRhinestoneSmartAccount } from "@getpara/aa-rhinestone";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const smartAccount = await createRhinestoneSmartAccount({
para,
chain: sepolia,
rhinestoneApiKey: process.env.EXPO_PUBLIC_RHINESTONE_API_KEY!,
pimlicoApiKey: process.env.EXPO_PUBLIC_PIMLICO_API_KEY,
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
Rhinestone only supports EIP-4337 mode. Both `rhinestoneApiKey` and `pimlicoApiKey` are optional but recommended for production. See the for advanced cross-chain use cases.
provides Coinbase smart accounts on Base using viem's built-in `toCoinbaseSmartAccount`. CDP only supports EIP-4337 mode on Base and Base Sepolia.
### Setup
1. Create a and obtain your **RPC token** from the paymaster URL in the CDP dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-cdp viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-cdp viem
```
### Usage
Initializes the smart account once when the component mounts using `useEffect`, then wraps `sendTransaction` in a `useMutation` from React Query for built-in `isPending` and error state.
```tsx CDPScreen.tsx
import { useState, useEffect } from "react";
import { useMutation } from "@tanstack/react-query";
import { createCDPSmartAccount } from "@getpara/aa-cdp";
import { usePara } from "@getpara/react-native-wallet";
import type { SmartAccount } from "@getpara/react-native-wallet";
import { baseSepolia } from "viem/chains";
import { parseEther } from "viem";
export function CDPScreen() {
const para = usePara();
const [smartAccount, setSmartAccount] = useState(null);
useEffect(() => {
createCDPSmartAccount({
para,
rpcToken: process.env.EXPO_PUBLIC_CDP_RPC_TOKEN!,
chain: baseSepolia,
}).then(setSmartAccount);
}, []);
const { mutate: sendTx, isPending } = useMutation({
mutationFn: () =>
smartAccount!.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
}),
onSuccess: (receipt) =>
console.log("Transaction hash:", receipt.transactionHash),
});
return (
Smart Account: {smartAccount?.smartAccountAddress}
sendTx()}
disabled={!smartAccount || isPending}
/>
);
}
```
Directly awaits the smart account creation and calls `sendTransaction` as a plain async function. Use this in event handlers, background tasks, or with any state management solution you already have.
```typescript cdp-action.ts
import { createCDPSmartAccount } from "@getpara/aa-cdp";
import { baseSepolia } from "viem/chains";
import { parseEther } from "viem";
const smartAccount = await createCDPSmartAccount({
para,
rpcToken: process.env.EXPO_PUBLIC_CDP_RPC_TOKEN!,
chain: baseSepolia,
});
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
```
CDP only supports EIP-4337 mode and is limited to **Base** and **Base Sepolia** chains.
In EIP-4337 mode, funds must be sent to `smartAccount.smartAccountAddress` — not to the Para EOA address.
## Key Considerations for Mobile
- Complete authentication with Para before the component mounts — the Para instance must have an active session
- The `useEffect` pattern above fires once on mount; add config values to the dependency array if they can change at runtime
- Wrap `sendTransaction` calls in `useMutation` for loading state and error handling in mobile UIs
- Test on physical devices to verify signing flows, particularly for EIP-7702 delegation transactions
# Email & Phone Authentication
Source: https://docs.getpara.com/v3/react-native/guides/add-email-phone
import { Card } from '/snippets/v3/components/ui/card.mdx';
import AuthenticateWithEmailOrPhone from '/snippets/v3/definitions/core/authenticateWithEmailOrPhone.mdx';
import StatePhaseReference from '/snippets/v3/definitions/core/statePhaseReference.mdx';
import AuthStateInfo from '/snippets/v3/definitions/types/AuthStateInfo.mdx';
import AuthenticateResponse from '/snippets/v3/definitions/types/AuthenticateResponse.mdx';
Para doesn't provide a pre-built modal for mobile -- you authenticate by calling SDK methods directly. This guide shows how to authenticate users with email or phone using `authenticateWithEmailOrPhone()`, which handles the entire auth flow in a single call.
These methods are **long-running** -- they internally poll for session completion and wait for the user to finish interacting with the portal. Call them from a **provider or higher-order component** that will not unmount during the authentication flow. If the component unmounts while the method is running, the authentication will be interrupted.
You are responsible for **opening the portal URLs** that Para generates during authentication. Use `para.onStatePhaseChange()` to listen for these URLs and open them in the device browser. See [Handling Portal URLs](#handling-portal-urls) below.
## Prerequisites
Before implementing authentication, ensure you have completed the basic Para setup for your React Native or Expo application.
## Email / Phone Authentication
Use `para.authenticateWithEmailOrPhone()` to authenticate a user by email or phone. The method handles the complete flow: determining whether the user is new or returning, session polling, and wallet creation. Combine it with the state listener below to open portal URLs, and handle OTP input when `authPhase` is `'awaiting_account_verification'`.
```typescript
import { useState } from "react";
import { para } from "../your-para-client";
function EmailAuthScreen() {
const [isAuthActive, setIsAuthActive] = useState(false);
// Use the state listener hook from below
useParaAuthStateListener(isAuthActive);
const handleEmailAuth = async (email: string) => {
setIsAuthActive(true);
try {
const result = await para.authenticateWithEmailOrPhone({
auth: { email },
});
if (result.hasCreatedWallets && result.recoverySecret) {
// Non-basic-login new user — display or securely store the recovery secret
console.log("Recovery secret:", result.recoverySecret);
}
// User is now fully authenticated
console.log("Auth info:", result.authInfo);
// Navigate to your authenticated screen
} catch (error) {
console.error("Authentication failed:", error);
} finally {
setIsAuthActive(false);
}
};
// ... render your email input UI
}
```
For phone number authentication, pass `{ phone: '+1234567890' }` instead of `{ email }`:
```typescript
const result = await para.authenticateWithEmailOrPhone({
auth: { phone: `+${countryCode}${phoneNumber}` as `+${number}` },
});
```
## Handling Portal URLs
During authentication, Para's state machine emits portal URLs that the user must interact with (e.g. entering a verification code, creating a passkey, or entering a password). Since you're building a custom UI, you need to subscribe to state changes and open these URLs using the in-app browser.
Use `para.onStatePhaseChange()` to receive a `StateSnapshot` containing `authStateInfo` -- a flat object with all the URLs and flags you need:
```typescript
import { useEffect, useRef } from "react";
import { para } from "../your-para-client";
import { InAppBrowser } from "react-native-inappbrowser-reborn";
import type { StateSnapshot } from "@getpara/react-native-wallet";
const APP_SCHEME = "your-app-scheme";
function useParaAuthStateListener(isAuthActive: boolean) {
const lastUrlRef = useRef(null);
useEffect(() => {
if (!isAuthActive) return;
const unsubscribe = para.onStatePhaseChange((snapshot: StateSnapshot) => {
const { authStateInfo } = snapshot;
// Verification URL (basic login verification)
if (authStateInfo.verificationUrl && authStateInfo.verificationUrl !== lastUrlRef.current) {
lastUrlRef.current = authStateInfo.verificationUrl;
InAppBrowser.openAuth(authStateInfo.verificationUrl, APP_SCHEME, {
ephemeralWebSession: false,
showTitle: false,
});
return;
}
// Biometric / security URLs (passkey, password, PIN)
const url =
authStateInfo.passkeyKnownDeviceUrl ||
authStateInfo.passkeyUrl ||
authStateInfo.passwordUrl ||
authStateInfo.pinUrl;
if (url && url !== lastUrlRef.current) {
lastUrlRef.current = url;
InAppBrowser.openAuth(url, APP_SCHEME, {
ephemeralWebSession: false,
showTitle: false,
});
}
});
return () => {
unsubscribe();
lastUrlRef.current = null;
};
}, [isAuthActive]);
}
```
```typescript
import { useEffect, useRef } from "react";
import { para } from "../your-para-client";
import { openAuthSessionAsync } from "expo-web-browser";
import type { StateSnapshot } from "@getpara/react-native-wallet";
const APP_SCHEME = "your-app-scheme";
function useParaAuthStateListener(isAuthActive: boolean) {
const lastUrlRef = useRef(null);
useEffect(() => {
if (!isAuthActive) return;
const unsubscribe = para.onStatePhaseChange((snapshot: StateSnapshot) => {
const { authStateInfo } = snapshot;
// Verification URL (basic login verification)
if (authStateInfo.verificationUrl && authStateInfo.verificationUrl !== lastUrlRef.current) {
lastUrlRef.current = authStateInfo.verificationUrl;
openAuthSessionAsync(authStateInfo.verificationUrl, APP_SCHEME, {
preferEphemeralSession: false,
});
return;
}
// Biometric / security URLs (passkey, password, PIN)
const url =
authStateInfo.passkeyKnownDeviceUrl ||
authStateInfo.passkeyUrl ||
authStateInfo.passwordUrl ||
authStateInfo.pinUrl;
if (url && url !== lastUrlRef.current) {
lastUrlRef.current = url;
openAuthSessionAsync(url, APP_SCHEME, {
preferEphemeralSession: false,
});
}
});
return () => {
unsubscribe();
lastUrlRef.current = null;
};
}, [isAuthActive]);
}
```
When `authPhase` is `'awaiting_account_verification'`, the user is a **non-basic-login new signup** who has been sent an OTP code via email or SMS. There is no URL to open -- you must show a code input field and call `para.verifyNewAccount({ verificationCode })`. If the user needs a new code, call `para.resendVerificationCode({ type: 'SIGNUP' })`. The simplified method is waiting for this step to complete before it proceeds. See the [full example above](#email--phone-authentication).
## Method Reference
## Cancelling Authentication
The method accepts polling callbacks with an `isCanceled` function. Return `true` from `isCanceled` to stop the polling loop -- for example, when the user dismisses the in-app browser or navigates away. The cancellation is clean: no error is thrown, and the optional `onCancel` callback is fired.
```typescript
const result = await para.authenticateWithEmailOrPhone({
auth: { email },
sessionPollingCallbacks: {
isCanceled: () => userDismissedBrowser,
onCancel: () => {
console.log("User canceled authentication");
},
},
});
```
Calling `para.logout()` also cancels all active polling and resets the state phases back to `unauthenticated`. This is useful for implementing a "Cancel" button that fully resets the auth flow:
```typescript
const handleCancel = async () => {
await para.logout();
// All polling stops, state phases reset to unauthenticated
};
```
## Handling Results
## Next Steps
# Add Native Passkeys
Source: https://docs.getpara.com/v3/react-native/guides/add-passkeys
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Passkeys provide native biometric authentication using Face ID, Touch ID, or fingerprint -- no browser needed for the passkey step itself. This is an optional enhancement on top of the default email/phone login flow. When enabled, the SDK calls the device's native passkey APIs directly instead of opening a portal URL.
## Prerequisites
## Platform Configuration
Configure your app's passkey credentials so the OS can associate your app with Para's domain.
In order for passkeys to work, you need to set up associated domains in your Xcode project linked to the Para domain.
#### Set Up Associated Domains
1. Open your project in Xcode
2. Select your target and go to "Signing & Capabilities"
3. Click "+ Capability" and add "Associated Domains"
4. Add the following domains:
- webcredentials:app.beta.usecapsule.com
- webcredentials:app.usecapsule.com
For additional information on associated domains, refer to the .
**Important**: Your `teamId + bundleIdentifier` must be registered with the Para team to set up associated domains. For example, if your Team ID is `A1B2C3D4E5` and Bundle Identifier is `com.yourdomain.yourapp`, provide `A1B2C3D4E5.com.yourdomain.yourapp` to Para. This is required by Apple for passkey security. **Note:** Allow up to 24 hours for domain propagation.
Getting ready for App Review? See for Para-specific review tips.
#### Install CocoaPods for native dependencies:
```bash
cd ios
bundle install
bundle exec pod install
cd ..
```
Remember to run `pod install` after adding new dependencies to your project.
#### Set Up Digital Asset Links
For Android setup, you need to provide your app's SHA-256 certificate fingerprint to Para.
**Quick Testing Option**: You can use `com.getpara.example.reactnative` as your package name for immediate testing. This package name is pre-registered and works with the SHA-256 certificate from the default debug.keystore.
The debug keystore at `~/.android/debug.keystore` is auto-generated by the Android SDK on your first build. You do not need to create it manually. Use `keytool -list` to **read** the existing keystore — do not use `keytool -genkey` which creates a new one and may cause a fingerprint mismatch.
To get your SHA-256 fingerprint:
- For debug builds: `keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android`
- For release builds: `keytool -list -v -keystore `
**Debug and release builds use different signing keys.** The SHA-256 fingerprint you register in the Developer Portal must match the keystore used to sign the build you are testing. If you register your release fingerprint but test with a debug build (or vice versa), passkeys will fail with an RP ID validation error. For development, register your debug keystore fingerprint. For production, register your release keystore fingerprint. You can register both in the Developer Portal.
For production apps, you'll need to: 1. Upgrade your plan in the Developer Portal 2. Register your actual package name 3. Provide your app's SHA-256 fingerprint 4. Wait up to 24 hours for the Digital Asset Links to propagate
#### Verify Digital Asset Links
After registering your package name and SHA-256 fingerprint, check the verification status in the under your API key's Native Passkey Configuration. The portal automatically polls Google's Digital Asset Links service and will update the status once verification completes. Allow up to 24 hours for propagation.
#### Device Requirements
To ensure passkey functionality works correctly:
- Enable biometric or device unlock settings (fingerprint, face unlock, or PIN)
- Sign in to a Google account on the device (required for Google Play Services passkey management)
Configure your `app.json` file to enable passkey functionality and secure communication:
```json app.json
{
"expo": {
"ios": {
"bundleIdentifier": "your.app.bundleIdentifier",
"associatedDomains": [
"webcredentials:app.beta.usecapsule.com?mode=developer",
"webcredentials:app.usecapsule.com"
]
}
}
}
```
**Important**: Your `teamId + bundleIdentifier` must be registered with the Para team to set up associated domains. For example, if your Team ID is `A1B2C3D4E5` and Bundle Identifier is `com.yourdomain.yourapp`, provide `A1B2C3D4E5.com.yourdomain.yourapp` to Para. This is required by Apple for passkey security. Allow up to 24 hours for domain propagation. You can find this setting in the Developer Portal under the 'Configuration' tab of the API key label as Native Passkey Configuration.
Prepping for App Review? Check for Sign in with Apple tips, reviewer notes, and account deletion requirements.
For Android setup in Expo, you'll need to configure your package name and provide your SHA-256 certificate fingerprint.
**Quick Testing Option**: You can use `com.getpara.example.expo` as your package name in `app.json` for immediate testing. This package name is pre-registered but only works with the default debug.keystore generated by Expo.
The debug keystore at `~/.android/debug.keystore` is auto-generated by the Android SDK on your first build. You do not need to create it manually. Use `keytool -list` to **read** the existing keystore — do not use `keytool -genkey` which creates a new one and may cause a fingerprint mismatch.
Configure your `app.json`:
```json app.json
{
"expo": {
"android": {
"package": "com.getpara.example.expo" // For testing
// or your actual package name for production
}
}
}
```
To get your SHA-256 fingerprint:
- For debug builds: `keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android`
- For release builds: `keytool -list -v -keystore `
**Important**: Your SHA-256 certificate fingerprint must be registered with the Para team to set up associated domains. This is required by Google for passkey security. Allow up to 24 hours for domain propagation. You can find this setting in the Developer Portal under the 'Configuration' tab of the API key label as Native Passkey Configuration.
**Debug and release builds use different signing keys.** The SHA-256 fingerprint you register in the Developer Portal must match the keystore used to sign the build you are testing. If you register your release fingerprint but test with a debug build (or vice versa), passkeys will fail with an RP ID validation error (`[50152]`). For development, register your debug keystore fingerprint. For production, register your release keystore fingerprint. You can register both in the Developer Portal.
## State Listener with Native Passkeys
Modify the state listener to intercept passkey states and call `registerPasskey()` or `loginWithPasskey()` natively, falling back to portal URLs only for password/PIN users:
```typescript
import { useEffect, useRef } from "react";
import { para } from "../your-para-client";
import { InAppBrowser } from "react-native-inappbrowser-reborn";
import type { StateSnapshot } from "@getpara/react-native-wallet";
const APP_SCHEME = "your-app-scheme";
function useParaAuthStateListener(isAuthActive: boolean) {
const lastUrlRef = useRef(null);
const handledRef = useRef(null);
useEffect(() => {
if (!isAuthActive) return;
const unsubscribe = para.onStatePhaseChange(async (snapshot: StateSnapshot) => {
const { authPhase, authStateInfo } = snapshot;
// Native passkey signup — register using the credential ID
if (
authStateInfo.isNewUser &&
authStateInfo.passkeyId &&
handledRef.current !== authStateInfo.passkeyId
) {
handledRef.current = authStateInfo.passkeyId;
await para.registerPasskey(authStateInfo.passkeyId);
return;
}
// Native passkey login — trigger biometric prompt directly
if (
!authStateInfo.isNewUser &&
authStateInfo.hasPasskey &&
authPhase === "awaiting_session_start" &&
handledRef.current !== "login"
) {
handledRef.current = "login";
await para.loginWithPasskey();
return;
}
// Verification URL (basic login verification)
if (authStateInfo.verificationUrl && authStateInfo.verificationUrl !== lastUrlRef.current) {
lastUrlRef.current = authStateInfo.verificationUrl;
InAppBrowser.openAuth(authStateInfo.verificationUrl, APP_SCHEME, {
ephemeralWebSession: false,
showTitle: false,
});
return;
}
// Fallback: password/PIN portal URLs for non-passkey users
const url = authStateInfo.passwordUrl || authStateInfo.pinUrl;
if (url && url !== lastUrlRef.current) {
lastUrlRef.current = url;
InAppBrowser.openAuth(url, APP_SCHEME, {
ephemeralWebSession: false,
showTitle: false,
});
}
});
return () => {
unsubscribe();
lastUrlRef.current = null;
handledRef.current = null;
};
}, [isAuthActive]);
}
```
```typescript
import { useEffect, useRef } from "react";
import { para } from "../your-para-client";
import { openAuthSessionAsync } from "expo-web-browser";
import type { StateSnapshot } from "@getpara/react-native-wallet";
const APP_SCHEME = "your-app-scheme";
function useParaAuthStateListener(isAuthActive: boolean) {
const lastUrlRef = useRef(null);
const handledRef = useRef(null);
useEffect(() => {
if (!isAuthActive) return;
const unsubscribe = para.onStatePhaseChange(async (snapshot: StateSnapshot) => {
const { authPhase, authStateInfo } = snapshot;
// Native passkey signup — register using the credential ID
if (
authStateInfo.isNewUser &&
authStateInfo.passkeyId &&
handledRef.current !== authStateInfo.passkeyId
) {
handledRef.current = authStateInfo.passkeyId;
await para.registerPasskey(authStateInfo.passkeyId);
return;
}
// Native passkey login — trigger biometric prompt directly
if (
!authStateInfo.isNewUser &&
authStateInfo.hasPasskey &&
authPhase === "awaiting_session_start" &&
handledRef.current !== "login"
) {
handledRef.current = "login";
await para.loginWithPasskey();
return;
}
// Verification URL (basic login verification)
if (authStateInfo.verificationUrl && authStateInfo.verificationUrl !== lastUrlRef.current) {
lastUrlRef.current = authStateInfo.verificationUrl;
openAuthSessionAsync(authStateInfo.verificationUrl, APP_SCHEME, {
preferEphemeralSession: false,
});
return;
}
// Fallback: password/PIN portal URLs for non-passkey users
const url = authStateInfo.passwordUrl || authStateInfo.pinUrl;
if (url && url !== lastUrlRef.current) {
lastUrlRef.current = url;
openAuthSessionAsync(url, APP_SCHEME, {
preferEphemeralSession: false,
});
}
});
return () => {
unsubscribe();
lastUrlRef.current = null;
handledRef.current = null;
};
}, [isAuthActive]);
}
```
Native passkeys use the device's biometric prompt (Face ID, Touch ID, fingerprint) directly -- no browser or portal needed. For users with a password or PIN instead of a passkey, the listener falls back to opening the portal URL.
## How It Works
`ParaMobile` sets `isNativePasskey = true` internally when initializing the React Native SDK. This causes the SDK to suppress `passkeyUrl` from auth states. Instead:
- **Signup**: `passkeyId` is provided in the auth state. Pass it to `registerPasskey()` to trigger native credential creation.
- **Login**: `hasPasskey` is `true` in the auth state. Call `loginWithPasskey()` to trigger the device's native biometric prompt.
These methods interact with the OS passkey APIs directly, so the user sees a native Face ID, Touch ID, or fingerprint prompt rather than a browser-based flow.
## Next Steps
# Add Password or PIN
Source: https://docs.getpara.com/v3/react-native/guides/add-password-pin
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para supports password and PIN-based authentication as alternatives to passkeys. When enabled on your API key, users create or enter their credentials on Para's secure portal, which opens in the device browser. This is an optional enhancement — basic email/phone login works without any additional security method.
## Prerequisites
## Enable in Developer Portal
Password and PIN authentication must be enabled in your under the Security settings for your API key. Once enabled, `authenticateWithEmailOrPhone()` will return `passwordUrl` or `pinUrl` in the auth state, and the state listener will automatically open them.
## How It Works
When a user signs up or logs in with password/PIN enabled, the SDK generates a `passwordUrl` or `pinUrl`. This URL points to Para's hosted portal where the user enters their credentials securely.
On mobile, you open this URL in the device's in-app browser. After the user completes the portal flow, call `waitForWalletCreation()` (new users) or `waitForLogin()` (existing users) to poll until the session is confirmed.
## Implementation
```typescript
import InAppBrowser from 'react-native-inappbrowser-reborn';
const APP_SCHEME = 'your-app-scheme';
// After authenticateWithEmailOrPhone resolves with passwordUrl or pinUrl in authStateInfo:
// For new users creating a password
if (authState.passwordUrl) {
await InAppBrowser.openAuth(authState.passwordUrl, `${APP_SCHEME}://para`);
await para.waitForWalletCreation({});
}
// For existing users logging in with password
if (authState.passwordUrl) {
await InAppBrowser.openAuth(authState.passwordUrl, `${APP_SCHEME}://para`);
await para.waitForLogin({});
}
```
```typescript
import { openAuthSessionAsync } from 'expo-web-browser';
const APP_SCHEME = 'your-app-scheme';
// After authenticateWithEmailOrPhone resolves with passwordUrl or pinUrl in authStateInfo:
// For new users creating a password
if (authState.passwordUrl) {
await openAuthSessionAsync(authState.passwordUrl, `${APP_SCHEME}://para`);
await para.waitForWalletCreation({});
}
// For existing users logging in with password
if (authState.passwordUrl) {
await openAuthSessionAsync(authState.passwordUrl, `${APP_SCHEME}://para`);
await para.waitForLogin({});
}
```
The same pattern applies for PIN authentication. Replace `authState.passwordUrl` with `authState.pinUrl` depending on which method is configured for your API key.
## Using with the State Listener
If you're using `authenticateWithEmailOrPhone()` with the `onStatePhaseChange` listener from the [authentication guide](/v3/react-native/guides/add-email-phone#handling-portal-urls), `passwordUrl` and `pinUrl` are already handled automatically. The listener opens these URLs when they appear in `authStateInfo`, so no additional code is needed.
## Next Steps
# Social Login
Source: https://docs.getpara.com/v3/react-native/guides/add-social-login
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import AuthenticateWithOAuth from '/snippets/v3/definitions/core/authenticateWithOAuth.mdx';
Para supports OAuth authentication with providers like Google, Apple, Discord, X, and Facebook. Use `authenticateWithOAuth()` to handle the entire OAuth flow in a single call -- it manages the redirect, polls for completion, and creates wallets for new users.
Have your own OpenID Connect provider? Once you [set up Custom OIDC](/v3/general/developer-portal-custom-oidc), pass `"CUSTOM_OIDC"` as the method here — it works like any other provider.
## Prerequisites
Before implementing OAuth authentication, ensure you have completed the basic Para setup for your React Native or Expo application.
## Standard OAuth
Use `para.authenticateWithOAuth()` to authenticate a user via a third-party OAuth provider. The method manages the OAuth redirect, polls for completion, waits for session establishment, and creates wallets for new signups.
For standard OAuth providers, use the `onOAuthUrl` callback to open the OAuth URL in an in-app browser.
### Installation
Install the In-App Browser package to handle OAuth redirects:
```bash
npm install react-native-inappbrowser-reborn
# or
yarn add react-native-inappbrowser-reborn
```
For iOS, add the following to your `Info.plist` to define your URL scheme:
```xml
CFBundleURLTypes
CFBundleURLSchemes
your-app-scheme
```
For Android, add your URL scheme to `AndroidManifest.xml`:
```xml
```
### Implementation
```typescript
import { para } from "../your-para-client";
import { OAuthMethod } from "@getpara/react-native-wallet";
import { InAppBrowser } from "react-native-inappbrowser-reborn";
const APP_SCHEME = "your-app-scheme";
async function handleOAuthLogin(provider: OAuthMethod) {
try {
const result = await para.authenticateWithOAuth({
method: provider,
appScheme: APP_SCHEME,
redirectCallbacks: {
onOAuthUrl: async (url) => {
await InAppBrowser.openAuth(url, APP_SCHEME, {
ephemeralWebSession: false,
showTitle: false,
enableUrlBarHiding: true,
});
},
},
});
if (result.hasCreatedWallets && result.recoverySecret) {
console.log("Recovery secret:", result.recoverySecret);
}
console.log("Auth info:", result.authInfo);
// Navigate to your authenticated screen
} catch (error) {
console.error("OAuth failed:", error);
}
}
```
### Installation
Install the Expo Web Browser package:
```bash
npx expo install expo-web-browser
```
Configure your `app.json` with the URL scheme:
```json
{
"expo": {
"scheme": "your-app-scheme",
"ios": {
"bundleIdentifier": "com.yourcompany.yourappname"
},
"android": {
"package": "com.yourcompany.yourappname"
}
}
}
```
After updating your `app.json`, rebuild native files:
```bash
npx expo prebuild --clean
```
### Implementation
```typescript
import { para } from "../your-para-client";
import { OAuthMethod } from "@getpara/react-native-wallet";
import { openAuthSessionAsync } from "expo-web-browser";
const APP_SCHEME = "your-app-scheme";
async function handleOAuthLogin(provider: OAuthMethod) {
try {
const result = await para.authenticateWithOAuth({
method: provider,
appScheme: APP_SCHEME,
redirectCallbacks: {
onOAuthUrl: async (url) => {
await openAuthSessionAsync(url, APP_SCHEME, {
preferEphemeralSession: false,
});
},
},
});
if (result.hasCreatedWallets && result.recoverySecret) {
console.log("Recovery secret:", result.recoverySecret);
}
console.log("Auth info:", result.authInfo);
// Navigate to your authenticated screen
} catch (error) {
console.error("OAuth failed:", error);
}
}
```
Both `react-native-inappbrowser-reborn` and `expo-web-browser` use secure browser implementations that leverage the device's native browser engine rather than a WebView. This provides stronger security protections and support for modern authentication methods.
## Telegram & Farcaster
Telegram and Farcaster authentication require a different approach than standard OAuth. These providers authenticate through Para's hosted portal, which needs to send events back to the SDK when authentication completes. On mobile, this requires opening the portal in a **WebView** (not an in-app browser) so the portal can communicate via `window.ReactNativeWebView.postMessage()`.
You must forward messages from the WebView to the SDK using `para.handleWebViewMessage()`.
Telegram and Farcaster **cannot** use `InAppBrowser` or `expo-web-browser` because the portal needs a live message channel to send authentication events (`TELEGRAM_SUCCESS`, `FARCASTER_SUCCESS`) back to the SDK. Only a WebView provides this channel via `onMessage`.
```tsx
import { useState } from "react";
import { Modal } from "react-native";
import { WebView, type WebViewMessageEvent } from "react-native-webview";
import { para } from "../your-para-client";
import type { OAuthMethod } from "@getpara/react-native-wallet";
const APP_SCHEME = "your-app-scheme";
function TelegramOrFarcasterAuth() {
const [portalUrl, setPortalUrl] = useState(null);
const handleAuth = async (method: OAuthMethod) => {
try {
const result = await para.authenticateWithOAuth({
method,
appScheme: APP_SCHEME,
redirectCallbacks: {
onOAuthUrl: (url) => {
// Show the portal URL in a WebView instead of an in-app browser
setPortalUrl(url);
},
},
});
setPortalUrl(null);
if (result.hasCreatedWallets && result.recoverySecret) {
console.log("Recovery secret:", result.recoverySecret);
}
console.log("Auth info:", result.authInfo);
// Navigate to your authenticated screen
} catch (error) {
setPortalUrl(null);
console.error("Auth failed:", error);
}
};
const handleWebViewMessage = (event: WebViewMessageEvent) => {
try {
const data = JSON.parse(event.nativeEvent.data);
// Forward portal events (TELEGRAM_SUCCESS, FARCASTER_SUCCESS, etc.) to the SDK
para.handleWebViewMessage(data);
} catch {
// Ignore non-JSON messages
}
};
return (
<>
{/* Your auth buttons */}
handleAuth("TELEGRAM")} />
handleAuth("FARCASTER")} />
{/* WebView modal for portal interaction */}
>
);
}
```
Install `react-native-webview` if you haven't already:
```bash
npm install react-native-webview
# or for Expo:
npx expo install react-native-webview
```
## Method Reference
## Cancelling Authentication
Both OAuth polling and session polling accept an `isCanceled` callback. Return `true` from `isCanceled` to stop the polling loop -- for example, when the user dismisses the in-app browser or navigates away. The cancellation is clean: no error is thrown, and the optional `onCancel` callback fires.
```typescript
const result = await para.authenticateWithOAuth({
method: "GOOGLE",
appScheme: APP_SCHEME,
oAuthPollingCallbacks: {
isCanceled: () => userClickedCancel,
onCancel: () => console.log("OAuth polling canceled"),
},
sessionPollingCallbacks: {
isCanceled: () => userClickedCancel,
onCancel: () => console.log("Session polling canceled"),
},
});
```
Calling `para.logout()` also cancels all active polling and resets the state phases back to `unauthenticated`. This is useful for implementing a "Cancel" button that fully resets the auth flow:
```typescript
const handleCancel = async () => {
await para.logout();
// All polling stops, state phases reset to unauthenticated
};
```
## Handling Results
`authenticateWithOAuth()` returns the same `AuthenticateResponse` as email/phone auth. See the [authentication guide](/v3/react-native/guides/add-email-phone) for full response type documentation.
## Next Steps
# Cosmos Support
Source: https://docs.getpara.com/v3/react-native/guides/cosmos
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para's React Native SDK supports Cosmos blockchain interactions through compatible libraries like CosmJS. After authenticating a user with native passkeys in your React Native or Expo application, all Cosmos-related operations function identically to our web SDKs.
## Implementation and References
Once a user has successfully authenticated with the Para React Native SDK using native passkeys, you can immediately sign Cosmos transactions and messages, interact with different Cosmos chains (including Cosmos Hub, Osmosis, and Juno), connect with IBC-enabled networks, and perform staking, delegation, and other operations. The CosmJS library and related Cosmos SDK tools work identically to their web implementations when used with Para's React Native SDK.
## Key Considerations for Mobile
When implementing Cosmos functionality in React Native applications:
- Initialize the Para SDK with your project ID before attempting any Cosmos operations
- Complete the authentication flow with native passkeys
- Consider mobile-specific UI patterns for transaction approvals
- Test on physical devices to ensure proper integration with native security features
While the integration steps are nearly identical to web implementations, mobile devices offer enhanced security through hardware-backed passkeys.
# Custom Storage with MMKV
Source: https://docs.getpara.com/v3/react-native/guides/custom-storage
Para's React Native SDK uses AsyncStorage and Keychain Storage by default. However, you can configure Para to use MMKV for improved performance. This guide shows you how to implement a custom storage solution using the MMKV library.
## Installation
First, install the MMKV package:
```bash
npm install react-native-mmkv
# or
yarn add react-native-mmkv
```
## Implementation
Create your MMKV storage instance and configure Para to use it:
```typescript
import { MMKV } from 'react-native-mmkv';
import { ParaMobile } from '@getpara/react-native-wallet';
// Initialize MMKV storage instances
const storage = new MMKV({
id: 'para-storage'
});
// Initialize Para client with MMKV storage
const para = new ParaMobile(
"YOUR_API_KEY",
undefined,
{
// Custom storage overrides
localStorageGetItemOverride: async (key) => {
const value = storage.getString(key);
return value ?? null;
},
localStorageSetItemOverride: async (key, value) => {
storage.set(key, value);
},
sessionStorageGetItemOverride: async (key) => {
const value = storage.getString(key);
return value ?? null;
},
sessionStorageSetItemOverride: async (key, value) => {
storage.set(key, value);
},
sessionStorageRemoveItemOverride: async (key) => {
storage.delete(key);
},
clearStorageOverride: async () => {
storage.clearAll();
}
}
);
export { para };
```
The custom storage implementation must handle serialization and deserialization of JSON data. All values are stored as strings, so your implementation should handle converting values correctly.
# EVM Support
Source: https://docs.getpara.com/v3/react-native/guides/evm
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para's React Native SDK provides full support for EVM chains through popular libraries like Ethers.js and Viem. Once a user is authenticated using native passkeys in your React Native or Expo application, all EVM-related operations work the same way as in our web SDKs.
## Implementation and References
After authenticating with the Para React Native SDK using native passkeys, you can seamlessly sign transactions and messages, interact with smart contracts, connect to different EVM networks, and perform other EVM operations. Both Ethers.js and Viem libraries are fully compatible with Para's React Native SDK, working identically to their web counterparts.
## Key Considerations for Mobile
When implementing EVM functionality in React Native applications:
- Ensure you've properly initialized the Para SDK with your project ID
- Complete authentication with native passkeys before attempting any signing operations
- Consider mobile UI/UX design for transaction approval flows
- Test on actual devices to verify the signing experience
The underlying code for signing transactions and messages remains identical between web and mobile implementations.
# Export Private Key
Source: https://docs.getpara.com/v3/react-native/guides/export-private-key
import ExportPrivateKeyLimitations from '/snippets/v3/export-private-key-limitations.mdx';
The `useExportPrivateKey` hook returns a URL where the user can reauthenticate and view their private key. On React Native, you open this URL in the system browser or an in-app browser.
## Integration
```tsx
import { useExportPrivateKey, useWallet } from "@getpara/react-native-wallet";
import { Button, Alert, Linking } from "react-native";
export function ExportKeyButton() {
const { data: wallet } = useWallet();
const { exportPrivateKeyAsync, isPending } = useExportPrivateKey();
const handleExport = async () => {
try {
const { url } = await exportPrivateKeyAsync({
walletId: wallet?.id,
});
if (url) {
const canOpen = await Linking.canOpenURL(url);
if (canOpen) {
await Linking.openURL(url);
} else {
Alert.alert("Error", "Unable to open export URL");
}
}
} catch (err) {
console.error("Failed to export private key:", err);
Alert.alert("Error", "Failed to initiate private key export");
}
};
return (
);
}
```
For a better user experience, consider using `expo-web-browser` or `react-native-inappbrowser-reborn` to open the export URL within your app instead of leaving it.
# Guest Mode
Source: https://docs.getpara.com/v3/react-native/guides/guest-mode
import GuestModeConcept from '/snippets/v3/guest-mode-concept.mdx';
import ExportPrivateKeyLimitations from '/snippets/v3/export-private-key-limitations.mdx';
## Create Guest Wallets
Use the `useCreateGuestWallets` hook to create guest wallets programmatically:
```tsx AppContent.tsx
import { useCreateGuestWallets } from "@getpara/react-native-wallet";
import { Button, Alert } from "react-native";
function GuestLoginButton() {
const { createGuestWallets, isPending, isError } = useCreateGuestWallets();
const onPressGuestLogin = () => {
createGuestWallets(undefined, {
onSuccess: (wallets) => {
console.log("Guest wallets created:", wallets);
},
onError: (error) => {
Alert.alert("Error", "Failed to create guest wallets");
console.error(error);
},
});
};
return (
);
}
```
## Tracking Guest Wallet Creation
Monitor wallet creation status with the `useCreateGuestWallets` hook's own state, or use a separate state variable for cross-component access:
```tsx
import { useCreateGuestWallets } from "@getpara/react-native-wallet";
import { View, Text } from "react-native";
function GuestStatus() {
const { isPending, isError, data } = useCreateGuestWallets();
if (isPending) return Creating guest wallets... ;
if (isError) return Error creating guest wallets ;
if (data) return Ready! {data.length} wallet(s) created ;
return null;
}
```
We recommend using `getUserShare` and `setUserShare` to save and restore the user share for a guest wallet, just as you would for a pregenerated wallet.
## Limitations
Currently, guest wallets are prevented from buying or selling crypto through the integrated onramp providers. If a guest wallet is funded, you must be careful to maintain user access to the wallets to ensure no funds are lost.
We recommend using `getUserShare` and `setUserShare` to save and restore the user share for a guest wallet, just as you would for a pregenerated wallet.
# ParaProvider
Source: https://docs.getpara.com/v3/react-native/guides/hooks/para-provider
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
The `ParaProvider` component sets up the Para context for your React Native app, making all Para hooks available to any component in the tree. It handles client initialization, session persistence via AsyncStorage, and React Query integration.
## Import
```tsx
import { ParaProvider } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import "@getpara/react-native-wallet/shim";
import { ParaProvider } from "@getpara/react-native-wallet";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
export default function App() {
return (
);
}
```
## Loading State
By default, `ParaProvider` blocks rendering until the SDK is ready. You can customize this behavior:
### With Fallback UI
Show a loading indicator instead of a blank screen while the SDK initializes:
```tsx
}
>
```
### With Immediate Rendering
Render children immediately and let them handle the loading state using `useParaStatus()`:
```tsx
```
```tsx
import { useParaStatus } from "@getpara/react-native-wallet";
import { ActivityIndicator } from "react-native";
function AppContent() {
const { isReady } = useParaStatus();
if (!isReady) return ;
return ;
}
```
## With Event Callbacks
Receive lifecycle events like login, logout, and signing:
```tsx
console.log("Logged in", event.detail.data),
onLogout: () => console.log("Logged out"),
onSignMessage: (event) => console.log("Message signed", event.detail.data),
}}
>
```
## With Custom Storage
By default, `ParaProvider` persists session data using `@react-native-async-storage/async-storage`. You can provide your own storage adapter to use a different backend (e.g. SecureStore, MMKV, or an encrypted store):
```tsx
import { ParaProvider } from "@getpara/react-native-wallet";
import type { ParaStorageAdapter } from "@getpara/react-native-wallet";
import * as SecureStore from "expo-secure-store";
const secureStorageAdapter: ParaStorageAdapter = {
getItem: (key) => SecureStore.getItemAsync(key),
setItem: (key, value) => SecureStore.setItemAsync(key, value),
removeItem: (key) => SecureStore.deleteItemAsync(key),
};
export default function App() {
return (
);
}
```
## Props
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| `paraClientConfig` | `ParaMobile \| { apiKey: string; env?: Environment; opts?: ConstructorOpts }` | Yes | A pre-instantiated ParaMobile, or a config object with your API key. |
| `config` | `{ appName: string; rpcUrl?: string }` | Yes | Provider configuration. |
| `callbacks` | `CoreCallbacks` | No | Event callbacks (`onLogin`, `onLogout`, `onSignMessage`, etc.). |
| `storageAdapter` | `ParaStorageAdapter` | No | Custom storage adapter. Defaults to AsyncStorage. |
| `waitForReady` | `boolean` | No | When `true` (default), children are not rendered until the SDK is ready. Set to `false` to render immediately. |
| `fallback` | `ReactNode` | No | Content to render while the SDK is initializing. Only used when `waitForReady` is `true`. |
| `children` | `ReactNode` | Yes | Your app's component tree. |
# useAccount
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-account
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseAccount from '/snippets/v3/definitions/hooks/useAccount.mdx';
The `useAccount` hook returns the current user's account information including their connection status, user ID, and linked auth methods.
## Import
```tsx
import { useAccount } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useAccount } from "@getpara/react-native-wallet";
import { View, Text } from "react-native";
function AccountStatus() {
const { embedded, isConnected, connectionType, isLoading } = useAccount();
if (isLoading) return Loading... ;
return (
Status: {isConnected ? "Connected" : "Not connected"}
Connection: {connectionType}
{embedded?.userId && User ID: {embedded.userId} }
{embedded?.identifier && Identifier: {embedded.identifier} }
{embedded?.wallets?.map((w) => (
{w.type}: {w.address}
))}
);
}
```
## Return Value
| Property | Type | Description |
|---|---|---|
| `isConnected` | `boolean` | Whether any wallet is connected (embedded or external) |
| `isLoading` | `boolean` | Whether the account is still loading |
| `connectionType` | `'embedded' \| 'external' \| 'both' \| 'none'` | The type of active connection |
| `embedded` | `object` | The embedded Para account data |
| `embedded.userId` | `string` | The user's Para ID |
| `embedded.identifier` | `string` | The user's login identifier (email, phone, etc.) |
| `embedded.authType` | `string` | The authentication method used |
| `embedded.wallets` | `Wallet[]` | All wallets on the account |
| `embedded.isConnected` | `boolean` | Whether the embedded wallet specifically is connected |
| `embedded.isGuestMode` | `boolean` | Whether the user is in guest mode |
| `external` | `object` | External wallet data (web only, empty on React Native) |
# useAuthenticateWithEmailOrPhone
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-authenticate-with-email-or-phone
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseAuthenticateWithEmailOrPhone from '/snippets/v3/definitions/hooks/useAuthenticateWithEmailOrPhone.mdx';
The `useAuthenticateWithEmailOrPhone` hook handles the entire email or phone authentication flow — signup or login, verification, session waiting, and wallet creation — in a single call. It requires you to listen for state phase changes to handle the portal URLs that appear during the flow.
## Import
```tsx
import { useAuthenticateWithEmailOrPhone } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useAuthenticateWithEmailOrPhone, useClient } from "@getpara/react-native-wallet";
import { useEffect, useState } from "react";
import { View, TextInput, Button, Linking } from "react-native";
function LoginScreen() {
const [email, setEmail] = useState("");
const client = useClient();
const { authenticateWithEmailOrPhoneAsync, isPending } = useAuthenticateWithEmailOrPhone();
useEffect(() => {
if (!client) return;
// Listen for portal URLs (verification, passkey setup, etc.)
const unsub = client.onStatePhaseChange((phase) => {
if (phase.type === "VERIFICATION_REQUIRED" && phase.url) {
Linking.openURL(phase.url);
}
});
return unsub;
}, [client]);
const handleAuth = async () => {
try {
await authenticateWithEmailOrPhoneAsync({ auth: { email } });
} catch (err) {
console.error(err);
}
};
return (
);
}
```
# useAuthenticateWithOAuth
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-authenticate-with-o-auth
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseAuthenticateWithOAuth from '/snippets/v3/definitions/hooks/useAuthenticateWithOAuth.mdx';
The `useAuthenticateWithOAuth` hook handles the entire OAuth authentication flow — redirect, verification, session waiting, and wallet creation — in a single call. Supports Google, Apple, Discord, X, Facebook, Telegram, and Farcaster.
On React Native, provide `appScheme` so the OAuth redirect returns to your app, and use `onOAuthUrl` to open the URL in the system browser.
## Import
```tsx
import { useAuthenticateWithOAuth } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useAuthenticateWithOAuth } from "@getpara/react-native-wallet";
import { Linking } from "react-native";
import * as WebBrowser from "expo-web-browser";
function GoogleLoginButton() {
const { authenticateWithOAuthAsync, isPending } = useAuthenticateWithOAuth();
const handleLogin = async () => {
try {
await authenticateWithOAuthAsync({
method: "GOOGLE",
appScheme: "myapp",
redirectCallbacks: {
onOAuthUrl: (url) => WebBrowser.openBrowserAsync(url),
},
});
} catch (err) {
console.error(err);
}
};
return (
);
}
```
# useClaimPregenWallets
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-claim-pregen-wallets
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseClaimPregenWallets from '/snippets/v3/definitions/hooks/useClaimPregenWallets.mdx';
The `useClaimPregenWallets` hook transfers pregenerated wallets to the newly authenticated user. Call it after signup completes for users who had wallets pre-created for them.
## Import
```tsx
import { useClaimPregenWallets } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useClaimPregenWallets } from "@getpara/react-native-wallet";
function ClaimWalletsStep({ userEmail }: { userEmail: string }) {
const { claimPregenWalletsAsync, isPending } = useClaimPregenWallets();
const handleClaim = async () => {
try {
await claimPregenWalletsAsync({ pregenId: { email: userEmail } });
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useClient
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-client
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseClient from '/snippets/v3/definitions/hooks/useClient.mdx';
The `useClient` hook returns the Para client instance provided to `ParaProvider`. Use it to call any method on the client directly, such as `getDisplayAddress` or `supportedWalletTypes`.
## Import
```tsx
import { useClient } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useClient, useWallet } from "@getpara/react-native-wallet";
import { View, Text } from "react-native";
function WalletAddress() {
const client = useClient();
const { data: wallet } = useWallet();
if (!client || !wallet) return null;
const evmAddress = client.getDisplayAddress(wallet.id, { addressType: "EVM" });
return Address: {evmAddress} ;
}
```
# useCreateGuestWallets
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-create-guest-wallets
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseCreateGuestWallets from '/snippets/v3/definitions/hooks/useCreateGuestWallets.mdx';
The `useCreateGuestWallets` hook creates wallets for a guest (unauthenticated) session. Guest wallets can be claimed later when the user signs up.
## Import
```tsx
import { useCreateGuestWallets } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useCreateGuestWallets } from "@getpara/react-native-wallet";
import { Button } from "react-native";
function GuestWalletButton() {
const { createGuestWalletsAsync, isPending } = useCreateGuestWallets();
const handleCreate = async () => {
try {
const wallets = await createGuestWalletsAsync();
console.log("Guest wallets:", wallets.map((w) => w.id));
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useCreatePregenWalletPerType
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-create-pregen-wallet-per-type
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseCreatePregenWalletPerType from '/snippets/v3/definitions/hooks/useCreatePregenWalletPerType.mdx';
The `useCreatePregenWalletPerType` hook creates pregenerated wallets for multiple chain types for a user who hasn't signed up yet.
## Import
```tsx
import { useCreatePregenWalletPerType } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useCreatePregenWalletPerType } from "@getpara/react-native-wallet";
function PregenWalletsCreator({ userEmail }: { userEmail: string }) {
const { createPregenWalletPerTypeAsync, isPending } = useCreatePregenWalletPerType();
const handleCreate = async () => {
try {
const wallets = await createPregenWalletPerTypeAsync({
types: ["EVM", "COSMOS"],
pregenId: { email: userEmail },
});
console.log("Created wallets:", wallets.map((w) => w.id));
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useCreatePregenWallet
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-create-pregen-wallet
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseCreatePregenWallet from '/snippets/v3/definitions/hooks/useCreatePregenWallet.mdx';
The `useCreatePregenWallet` hook creates a pregenerated wallet for a user who hasn't signed up yet, identified by an email, phone, or custom ID. The wallet is automatically claimed when the user later completes signup.
## Import
```tsx
import { useCreatePregenWallet } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useCreatePregenWallet } from "@getpara/react-native-wallet";
function PregenWalletCreator({ userEmail }: { userEmail: string }) {
const { createPregenWalletAsync, isPending } = useCreatePregenWallet();
const handleCreate = async () => {
try {
const wallet = await createPregenWalletAsync({
type: "EVM",
pregenId: { email: userEmail },
});
console.log("Pregen wallet:", wallet.id);
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useCreateWalletPerType
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-create-wallet-per-type
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseCreateWalletPerType from '/snippets/v3/definitions/hooks/useCreateWalletPerType.mdx';
The `useCreateWalletPerType` hook creates wallets for multiple chain types in a single call, returning each wallet along with its ID.
## Import
```tsx
import { useCreateWalletPerType } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useCreateWalletPerType } from "@getpara/react-native-wallet";
import { Button } from "react-native";
function CreateWalletsButton() {
const { createWalletPerTypeAsync, isPending } = useCreateWalletPerType();
const handleCreate = async () => {
try {
const result = await createWalletPerTypeAsync({ types: ["EVM", "COSMOS"] });
console.log("Created wallets:", result.wallets.map((w) => w.id));
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useCreateWallet
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-create-wallet
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseCreateWallet from '/snippets/v3/definitions/hooks/useCreateWallet.mdx';
The `useCreateWallet` hook creates a new wallet for the authenticated user. Call it after signup completes if automatic wallet creation isn't configured for your app.
## Import
```tsx
import { useCreateWallet } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useCreateWallet } from "@getpara/react-native-wallet";
import { Button } from "react-native";
function CreateWalletButton() {
const { createWalletAsync, isPending } = useCreateWallet();
const handleCreate = async () => {
try {
const [wallet] = await createWalletAsync({ type: "EVM" });
console.log("Created wallet:", wallet.id);
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useEnable2fa
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-enable-2fa
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseEnable2fa from '/snippets/v3/definitions/hooks/useEnable2fa.mdx';
The `useEnable2fa` hook completes 2FA enrollment by verifying the code from the user's authenticator app. Call it after `useSetup2fa` has returned the QR code and the user has scanned it.
## Import
```tsx
import { useEnable2fa } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useEnable2fa } from "@getpara/react-native-wallet";
import { useState } from "react";
import { View, TextInput, Button } from "react-native";
function Enable2faScreen() {
const [code, setCode] = useState("");
const { enable2faAsync, isPending } = useEnable2fa();
const handleEnable = async () => {
try {
await enable2faAsync({ verificationCode: code });
} catch (err) {
console.error(err);
}
};
return (
);
}
```
# useExportPrivateKey
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-export-private-key
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseExportPrivateKey from '/snippets/v3/definitions/hooks/useExportPrivateKey.mdx';
The `useExportPrivateKey` hook initiates a private key export for a given wallet. It returns a URL pointing to the Para portal where the key is displayed securely. Open this URL in a browser — never render the key directly in your app.
## Import
```tsx
import { useExportPrivateKey } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useExportPrivateKey, useWallet } from "@getpara/react-native-wallet";
import { Alert, Linking } from "react-native";
function ExportKeyButton() {
const { data: wallet } = useWallet();
const { exportPrivateKeyAsync, isPending } = useExportPrivateKey();
const handleExport = () => {
Alert.alert(
"Export Private Key",
"Your private key grants full access to your wallet. Never share it with anyone.\n\nAre you sure?",
[
{ text: "Cancel", style: "cancel" },
{
text: "Export",
style: "destructive",
onPress: async () => {
if (!wallet?.id) return;
try {
const result = await exportPrivateKeyAsync({ walletId: wallet.id });
if (result?.url) {
await Linking.openURL(result.url);
}
} catch (err) {
console.error(err);
}
},
},
]
);
};
return (
);
}
```
# useHasPregenWallet
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-has-pregen-wallet
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseHasPregenWallet from '/snippets/v3/definitions/hooks/useHasPregenWallet.mdx';
The `useHasPregenWallet` hook checks whether a pregenerated wallet has already been created for the given identifier.
## Import
```tsx
import { useHasPregenWallet } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useHasPregenWallet } from "@getpara/react-native-wallet";
function CheckPregenWallet({ userEmail }: { userEmail: string }) {
const { hasPregenWalletAsync, data } = useHasPregenWallet();
const handleCheck = async () => {
await hasPregenWalletAsync({ pregenId: { email: userEmail } });
};
return (
{data !== undefined && Has wallet: {data ? "Yes" : "No"} }
);
}
```
# useIsFullyLoggedIn
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-is-fully-logged-in
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseIsFullyLoggedIn from '/snippets/v3/definitions/hooks/useIsFullyLoggedIn.mdx';
The `useIsFullyLoggedIn` hook returns whether the current user has a valid, active session. Use it to gate authenticated screens or trigger navigation after login.
## Import
```tsx
import { useIsFullyLoggedIn } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useIsFullyLoggedIn } from "@getpara/react-native-wallet";
import { View, Text } from "react-native";
function App() {
const { data: isLoggedIn } = useIsFullyLoggedIn();
if (isLoggedIn === undefined) {
return Loading... ;
}
return (
{isLoggedIn ? : }
);
}
```
# useIssueJwt
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-issue-jwt
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseIssueJwt from '/snippets/v3/definitions/hooks/useIssueJwt.mdx';
The `useIssueJwt` hook issues a signed JWT that your backend can verify to authenticate the current Para user. Useful for linking Para sessions to your own API.
## Import
```tsx
import { useIssueJwt } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useIssueJwt } from "@getpara/react-native-wallet";
import { Button } from "react-native";
function BackendAuthButton() {
const { issueJwtAsync, isPending } = useIssueJwt();
const handleIssue = async () => {
try {
const { token, keyId } = await issueJwtAsync({});
// Send token to your backend for verification
await fetch("https://api.example.com/auth", {
method: "POST",
headers: { Authorization: `Bearer ${token}` },
body: JSON.stringify({ keyId }),
});
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useKeepSessionAlive
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-keep-session-alive
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseKeepSessionAlive from '/snippets/v3/definitions/hooks/useKeepSessionAlive.mdx';
The `useKeepSessionAlive` hook extends the current session. Call it periodically when the user is active to prevent automatic session expiry.
## Import
```tsx
import { useKeepSessionAlive } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useKeepSessionAlive } from "@getpara/react-native-wallet";
import { useEffect } from "react";
function SessionKeepAlive() {
const { keepSessionAliveAsync } = useKeepSessionAlive();
useEffect(() => {
const interval = setInterval(() => {
keepSessionAliveAsync().catch(console.error);
}, 5 * 60 * 1000); // every 5 minutes
return () => clearInterval(interval);
}, [keepSessionAliveAsync]);
return null;
}
```
# useLoginExternalWallet
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-login-external-wallet
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseLoginExternalWallet from '/snippets/v3/definitions/hooks/useLoginExternalWallet.mdx';
The `useLoginExternalWallet` hook initiates the Sign-In With Ethereum (SIWE) flow for an external wallet. After calling it, use `useVerifyExternalWallet` to complete verification.
## Import
```tsx
import { useLoginExternalWallet } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useLoginExternalWallet } from "@getpara/react-native-wallet";
function ExternalWalletLogin({ walletInfo }) {
const { loginExternalWalletAsync, isPending } = useLoginExternalWallet();
const handleLogin = async () => {
try {
const result = await loginExternalWalletAsync({ externalWallet: walletInfo });
// Proceed to useVerifyExternalWallet with the result
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useLogout
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-logout
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseLogout from '/snippets/v3/definitions/hooks/useLogout.mdx';
The `useLogout` hook logs out the current user and clears their session. Optionally clears any pregenerated wallets stored for the user.
## Import
```tsx
import { useLogout } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useLogout } from "@getpara/react-native-wallet";
import { Alert, Button } from "react-native";
function LogoutButton() {
const { logoutAsync, isPending } = useLogout();
const handleLogout = () => {
Alert.alert("Sign Out", "Are you sure you want to sign out?", [
{ text: "Cancel", style: "cancel" },
{
text: "Sign Out",
style: "destructive",
onPress: async () => {
try {
await logoutAsync({});
} catch (err) {
console.error(err);
}
},
},
]);
};
return ;
}
```
# useParaCosmjsSignAndBroadcast
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-cosmjs-sign-and-broadcast
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing and broadcasting Cosmos transactions using a CosmJS signer and signing client.
Requires `@getpara/cosmjs-v0-integration` and `@cosmjs/stargate` as peer dependencies.
Get the `signer` parameter from the CosmJS setup patterns in [Setup Libraries](/v3/react-native/guides/web3-operations/cosmos/setup-libraries).
## Import
```tsx
import { useParaCosmjsSignAndBroadcast } from "@getpara/react-native-wallet/cosmos";
```
## Usage
```tsx
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from "@getpara/react-native-wallet/cosmos";
import { SigningStargateClient, coins } from "@cosmjs/stargate";
function SendTokens() {
const { protoSigner } = useParaCosmjsProtoSigner();
// Connect signing client (see useParaCosmjsProtoSigner docs)
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const handleSend = async () => {
const result = await signAndBroadcastAsync({
messages: [sendMsg],
fee: { amount: coins(5000, "uatom"), gas: "200000" },
});
console.log("Tx hash:", result.transactionHash);
};
return (
{isPending ? "Broadcasting..." : "Send Tokens"}
);
}
```
& { signAndBroadcast, signAndBroadcastAsync }", description: "Extends UseMutationResult with named signAndBroadcast (fire-and-forget) and signAndBroadcastAsync (returns Promise) aliases. isPending is true when signer/client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaEthersSendTransaction
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-ethers-send-transaction
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for sending transactions using an ethers signer from useParaEthersSigner.
Requires `@getpara/ethers-v6-integration` and `ethers` as peer dependencies.
Get the `signer` parameter from the ethers setup pattern in [Setup Libraries](/v3/react-native/guides/web3-operations/evm/setup-libraries).
## Import
```tsx
import { useParaEthersSendTransaction } from "@getpara/react-native-wallet/evm/ethers";
```
## Usage
```tsx
import { useParaEthersSigner, useParaEthersSendTransaction } from "@getpara/react-native-wallet/evm/ethers";
import { JsonRpcProvider, parseEther } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SendTransaction() {
const { ethersSigner } = useParaEthersSigner({ provider });
const { sendTransactionAsync, isPending } = useParaEthersSendTransaction(ethersSigner);
return (
sendTransactionAsync({ to: "0x...", value: parseEther("0.01") })} disabled={isPending}>
{isPending ? "Sending..." : "Send ETH"}
);
}
```
& { sendTransaction, sendTransactionAsync }", description: "Extends UseMutationResult with named sendTransaction (fire-and-forget) and sendTransactionAsync (returns Promise) aliases. isPending is true when signer is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaEthersSignMessage
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-ethers-sign-message
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing messages using an ethers signer from useParaEthersSigner.
Requires `@getpara/ethers-v6-integration` and `ethers` as peer dependencies.
Get the `signer` parameter from the ethers setup pattern in [Setup Libraries](/v3/react-native/guides/web3-operations/evm/setup-libraries).
## Import
```tsx
import { useParaEthersSignMessage } from "@getpara/react-native-wallet/evm/ethers";
```
## Usage
```tsx
import { useParaEthersSigner, useParaEthersSignMessage } from "@getpara/react-native-wallet/evm/ethers";
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignMessage() {
const { ethersSigner } = useParaEthersSigner({ provider });
const { signMessageAsync, isPending, data: signature } = useParaEthersSignMessage(ethersSigner);
return (
signMessageAsync("Hello")} disabled={isPending}>
{isPending ? "Signing..." : "Sign Message"}
{signature &&
Signature: {signature}
}
);
}
```
& { signMessage, signMessageAsync }", description: "Extends UseMutationResult with named signMessage (fire-and-forget) and signMessageAsync (returns Promise) aliases. isPending is true when signer is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaSolanaSignAndSend
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-solana-sign-and-send
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing and sending Solana transactions using a signer from useParaSolanaSigner.
Requires `@getpara/solana-signers-v2-integration` and `@solana/kit` as peer dependencies.
Get the `signer` parameter from the Solana setup pattern in [Setup Libraries](/v3/react-native/guides/web3-operations/solana/setup-libraries).
## Import
```tsx
import { useParaSolanaSignAndSend } from "@getpara/react-native-wallet/solana";
```
## Usage
```tsx
import { useParaSolanaSigner, useParaSolanaSignAndSend } from "@getpara/react-native-wallet/solana";
import { createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function SendTransaction() {
const { solanaSigner } = useParaSolanaSigner({ rpc });
const { signAndSendAsync, isPending } = useParaSolanaSignAndSend(solanaSigner);
return (
signAndSendAsync({ transactions: [compiledTx] })} disabled={isPending}>
{isPending ? "Sending..." : "Sign & Send"}
);
}
```
& { signAndSend, signAndSendAsync }", description: "Extends UseMutationResult with named signAndSend (fire-and-forget) and signAndSendAsync aliases. isPending is true when signer is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaStatus
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-status
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseParaStatus from '/snippets/v3/definitions/hooks/useParaStatus.mdx';
The `useParaStatus` hook returns the current initialization status of the Para client. Use it to delay rendering until the SDK is ready.
## Import
```tsx
import { useParaStatus } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useParaStatus } from "@getpara/react-native-wallet";
import { View, Text, ActivityIndicator } from "react-native";
function AppShell() {
const status = useParaStatus();
if (!status.isReady) {
return (
Initializing...
);
}
return ;
}
```
# useParaStellarSignTransaction
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-stellar-sign-transaction
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing Stellar transactions using a signer from useParaStellarSigner.
Requires `@getpara/stellar-sdk-v14-integration` and `@stellar/stellar-sdk` as peer dependencies.
Get the `signer` parameter from the Stellar setup pattern in [Setup Libraries](/v3/react-native/guides/web3-operations/stellar/setup-libraries).
## Import
```tsx
import { useParaStellarSignTransaction } from "@getpara/react-native-wallet/stellar";
```
## Usage
```tsx
import { useParaStellarSigner, useParaStellarSignTransaction } from "@getpara/react-native-wallet/stellar";
import { Networks } from "@stellar/stellar-sdk";
function SignTransaction() {
const { stellarSigner } = useParaStellarSigner({ networkPassphrase: Networks.PUBLIC });
const { signTransactionAsync, isPending } = useParaStellarSignTransaction(stellarSigner);
const handleSign = async () => {
if (!stellarSigner) return;
const { signedTxXdr } = await signTransactionAsync(transaction.toXDR());
console.log("Signed XDR:", signedTxXdr);
};
return (
{isPending ? "Signing..." : "Sign Transaction"}
);
}
```
& { signTransaction, signTransactionAsync }", description: "Extends UseMutationResult with named signTransaction (fire-and-forget) and signTransactionAsync (returns Promise<{ signedTxXdr }>) aliases. isPending is true when signer is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaViemSendTransaction
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-viem-send-transaction
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for sending transactions using a Viem WalletClient from useParaViemClient.
Requires `@getpara/viem-v2-integration` and `viem` as peer dependencies.
Get the `viemClient` parameter from the viem setup pattern in [Setup Libraries](/v3/react-native/guides/web3-operations/evm/setup-libraries).
## Import
```tsx
import { useParaViemSendTransaction } from "@getpara/react-native-wallet/evm/viem";
```
## Usage
```tsx
import { useParaViemClient, useParaViemSendTransaction } from "@getpara/react-native-wallet/evm/viem";
import { sepolia } from "viem/chains";
import { http, parseEther } from "viem";
function SendTransaction() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { sendTransactionAsync, isPending, data: txHash } = useParaViemSendTransaction(viemClient);
return (
sendTransactionAsync({ to: "0x...", value: parseEther("0.01") })} disabled={isPending}>
{isPending ? "Sending..." : "Send ETH"}
);
}
```
& { sendTransaction, sendTransactionAsync }", description: "Extends UseMutationResult with named sendTransaction (fire-and-forget) and sendTransactionAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaViemSignMessage
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-viem-sign-message
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing messages using a Viem WalletClient from useParaViemClient.
Requires `@getpara/viem-v2-integration` and `viem` as peer dependencies.
Get the `viemClient` parameter from the viem setup pattern in [Setup Libraries](/v3/react-native/guides/web3-operations/evm/setup-libraries).
## Import
```tsx
import { useParaViemSignMessage } from "@getpara/react-native-wallet/evm/viem";
```
## Usage
```tsx
import { useParaViemClient, useParaViemSignMessage } from "@getpara/react-native-wallet/evm/viem";
import { sepolia } from "viem/chains";
import { http } from "viem";
function SignMessage() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { signMessageAsync, isPending, data: signature } = useParaViemSignMessage(viemClient);
return (
signMessageAsync({ message: "Hello" })} disabled={isPending}>
{isPending ? "Signing..." : "Sign Message"}
{signature &&
Signature: {signature}
}
);
}
```
& { signMessage, signMessageAsync }", description: "Extends UseMutationResult with named signMessage (fire-and-forget) and signMessageAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaViemSignTransaction
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-viem-sign-transaction
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing a transaction without broadcasting it. Produces a signed serialized transaction that can be submitted later or inspected by permissions/transaction-review flows.
Requires `@getpara/viem-v2-integration` and `viem` as peer dependencies.
Get the `viemClient` parameter from the viem setup pattern in [Setup Libraries](/v3/react-native/guides/web3-operations/evm/setup-libraries).
## Import
```tsx
import { useParaViemSignTransaction } from "@getpara/react-native-wallet/evm/viem";
```
## Usage
```tsx
import { useParaViemClient, useParaViemSignTransaction } from "@getpara/react-native-wallet/evm/viem";
import { sepolia } from "viem/chains";
import { http, parseEther } from "viem";
import { View, Text, Button } from "react-native";
function SignTransaction() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { signTransactionAsync, isPending, data: signedTx } = useParaViemSignTransaction(viemClient);
const handleSign = async () => {
const signed = await signTransactionAsync({
to: "0x...",
value: parseEther("0"),
type: "eip1559",
chain: sepolia,
});
console.log("Signed transaction:", signed);
};
return (
{signedTx && Signed: {signedTx} }
);
}
```
& { signTransaction, signTransactionAsync }", description: "Extends UseMutationResult with named signTransaction (fire-and-forget) and signTransactionAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaViemSignTypedData
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-viem-sign-typed-data
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing EIP-712 typed data using a Viem WalletClient from useParaViemClient.
Requires `@getpara/viem-v2-integration` and `viem` as peer dependencies.
Get the `viemClient` parameter from the viem setup pattern in [Setup Libraries](/v3/react-native/guides/web3-operations/evm/setup-libraries).
## Import
```tsx
import { useParaViemSignTypedData } from "@getpara/react-native-wallet/evm/viem";
```
## Usage
```tsx
import { useParaViemClient, useParaViemSignTypedData } from "@getpara/react-native-wallet/evm/viem";
import { sepolia } from "viem/chains";
import { http } from "viem";
function SignTypedData() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { signTypedDataAsync, isPending } = useParaViemSignTypedData(viemClient);
const handleSign = async () => {
const sig = await signTypedDataAsync({
domain: { name: "MyDApp", version: "1", chainId: 11155111 },
types: { Message: [{ name: "content", type: "string" }] },
primaryType: "Message",
message: { content: "Hello" },
});
console.log("Signature:", sig);
};
return (
{isPending ? "Signing..." : "Sign Typed Data"}
);
}
```
& { signTypedData, signTypedDataAsync }", description: "Extends UseMutationResult with named signTypedData (fire-and-forget) and signTypedDataAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaViemWriteContract
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-para-viem-write-contract
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for calling state-changing contract functions using a Viem WalletClient from useParaViemClient.
Requires `@getpara/viem-v2-integration` and `viem` as peer dependencies.
Get the `viemClient` parameter from the viem setup pattern in [Setup Libraries](/v3/react-native/guides/web3-operations/evm/setup-libraries).
## Import
```tsx
import { useParaViemWriteContract } from "@getpara/react-native-wallet/evm/viem";
```
## Usage
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-native-wallet/evm/viem";
import { sepolia } from "viem/chains";
import { http, parseUnits } from "viem";
function TransferTokens() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { writeContractAsync, isPending } = useParaViemWriteContract(viemClient);
const handleTransfer = async () => {
const hash = await writeContractAsync({
address: "0xTokenAddress...",
abi: ERC20_ABI,
functionName: "transfer",
args: ["0xRecipient...", parseUnits("10", 18)],
});
console.log("Tx hash:", hash);
};
return (
{isPending ? "Sending..." : "Transfer Tokens"}
);
}
```
& { writeContract, writeContractAsync }", description: "Extends UseMutationResult with named writeContract (fire-and-forget) and writeContractAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useResendVerificationCode
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-resend-verification-code
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseResendVerificationCode from '/snippets/v3/definitions/hooks/useResendVerificationCode.mdx';
The `useResendVerificationCode` hook resends the verification code for signup, login, or account linking.
## Import
```tsx
import { useResendVerificationCode } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useResendVerificationCode } from "@getpara/react-native-wallet";
import { Button } from "react-native";
function ResendCodeButton() {
const { resendVerificationCodeAsync, isPending } = useResendVerificationCode();
return (
resendVerificationCodeAsync({ type: "SIGNUP" })}
disabled={isPending}
/>
);
}
```
# useSetup2fa
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-setup-2fa
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseSetup2fa from '/snippets/v3/definitions/hooks/useSetup2fa.mdx';
The `useSetup2fa` hook initiates the 2FA setup flow, returning a QR code URI and secret that the user can add to their authenticator app. Follow with `useEnable2fa` to complete enrollment.
## Import
```tsx
import { useSetup2fa } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useSetup2fa } from "@getpara/react-native-wallet";
import { View, Text, Button } from "react-native";
function Setup2faScreen() {
const { setup2faAsync, data, isPending } = useSetup2fa();
return (
{data && (
{data.uri}
Secret: {data.secret}
)}
setup2faAsync()} disabled={isPending} />
);
}
```
# useSignMessage
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-sign-message
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseSignMessage from '/snippets/v3/definitions/hooks/useSignMessage.mdx';
The `useSignMessage` hook signs a message using a specified wallet. The message must be base64-encoded before passing it to the hook.
## Import
```tsx
import { useSignMessage } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useSignMessage, useWallet } from "@getpara/react-native-wallet";
import { Button, Text } from "react-native";
import { useState } from "react";
function SignMessageButton() {
const { data: wallet } = useWallet();
const { signMessageAsync, isPending } = useSignMessage();
const [signature, setSignature] = useState(null);
const handleSign = async () => {
if (!wallet?.id) return;
try {
const messageBase64 = btoa("Hello from Para!");
const result = await signMessageAsync({ walletId: wallet.id, messageBase64 });
if (result && "signature" in result) {
setSignature(`0x${result.signature}`);
}
} catch (err) {
console.error(err);
}
};
return (
<>
{signature && {signature} }
>
);
}
```
# useSignTransaction
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-sign-transaction
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseSignTransaction from '/snippets/v3/definitions/hooks/useSignTransaction.mdx';
The `useSignTransaction` hook signs a raw transaction using a specified wallet. The transaction must be RLP-encoded and base64-encoded before passing it to the hook. For higher-level EVM transaction support, use the `@getpara/viem-v2-integration` package.
## Import
```tsx
import { useSignTransaction } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useSignTransaction, useWallet } from "@getpara/react-native-wallet";
import { Button } from "react-native";
function SignTxButton({ rlpEncodedTxBase64 }: { rlpEncodedTxBase64: string }) {
const { data: wallet } = useWallet();
const { signTransactionAsync, isPending } = useSignTransaction();
const handleSign = async () => {
if (!wallet?.id) return;
try {
const result = await signTransactionAsync({
walletId: wallet.id,
rlpEncodedTxBase64,
chainId: "1",
});
console.log("Signed tx:", result?.signature);
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useSignUpOrLogIn
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-sign-up-or-log-in
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseSignUpOrLogIn from '/snippets/v3/definitions/hooks/useSignUpOrLogIn.mdx';
The `useSignUpOrLogIn` hook initiates the Para signup or login flow for a given email or phone number. After calling it, use `useVerifyNewAccount` or `useWaitForLogin` to complete the flow.
## Import
```tsx
import { useSignUpOrLogIn } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useSignUpOrLogIn } from "@getpara/react-native-wallet";
import { useState } from "react";
import { View, TextInput, Button, Text } from "react-native";
function LoginScreen() {
const [email, setEmail] = useState("");
const { signUpOrLogInAsync, isPending } = useSignUpOrLogIn();
const handleSubmit = async () => {
try {
const result = await signUpOrLogInAsync({ auth: { email } });
// result is AuthStateVerify (new user) or AuthStateLogin (returning user)
} catch (err) {
console.error(err);
}
};
return (
);
}
```
# useUpdatePregenWalletIdentifier
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-update-pregen-wallet-identifier
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseUpdatePregenWalletIdentifier from '/snippets/v3/definitions/hooks/useUpdatePregenWalletIdentifier.mdx';
The `useUpdatePregenWalletIdentifier` hook updates the identifier (email, phone, or custom ID) associated with an existing pregenerated wallet.
## Import
```tsx
import { useUpdatePregenWalletIdentifier } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useUpdatePregenWalletIdentifier } from "@getpara/react-native-wallet";
function UpdateIdentifierStep({ walletId, newEmail }: { walletId: string; newEmail: string }) {
const { updatePregenWalletIdentifierAsync, isPending } = useUpdatePregenWalletIdentifier();
const handleUpdate = async () => {
try {
await updatePregenWalletIdentifierAsync({
walletId,
newPregenId: { email: newEmail },
});
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useVerify2fa
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-verify-2fa
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseVerify2fa from '/snippets/v3/definitions/hooks/useVerify2fa.mdx';
The `useVerify2fa` hook verifies a 2FA code as part of the login flow, for users who have 2FA enabled on their account.
## Import
```tsx
import { useVerify2fa } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useVerify2fa } from "@getpara/react-native-wallet";
import { useState } from "react";
import { View, TextInput, Button } from "react-native";
function Verify2faScreen({ userEmail }: { userEmail: string }) {
const [code, setCode] = useState("");
const { verify2faAsync, isPending } = useVerify2fa();
const handleVerify = async () => {
try {
await verify2faAsync({
auth: { email: userEmail },
verificationCode: code,
});
} catch (err) {
console.error(err);
}
};
return (
);
}
```
# useVerifyExternalWallet
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-verify-external-wallet
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseVerifyExternalWallet from '/snippets/v3/definitions/hooks/useVerifyExternalWallet.mdx';
The `useVerifyExternalWallet` hook completes the external wallet login by verifying the signed SIWE message. Call it after `useLoginExternalWallet` and after the user has signed the verification message in their wallet.
## Import
```tsx
import { useVerifyExternalWallet } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useVerifyExternalWallet } from "@getpara/react-native-wallet";
function VerifyWalletStep({ externalWallet, signedMessage }) {
const { verifyExternalWalletAsync, isPending } = useVerifyExternalWallet();
const handleVerify = async () => {
try {
await verifyExternalWalletAsync({ externalWallet, signedMessage });
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useVerifyFarcaster
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-verify-farcaster
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseVerifyFarcaster from '/snippets/v3/definitions/hooks/useVerifyFarcaster.mdx';
The `useVerifyFarcaster` hook handles Farcaster authentication. It provides a Connect URI that you display as a QR code or deep link so the user can approve login in their Farcaster client.
## Import
```tsx
import { useVerifyFarcaster } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useVerifyFarcaster } from "@getpara/react-native-wallet";
import { useState } from "react";
import { View, Text, Linking } from "react-native";
function FarcasterLogin() {
const [connectUri, setConnectUri] = useState(null);
const { verifyFarcasterAsync, isPending } = useVerifyFarcaster();
const handleLogin = async () => {
try {
await verifyFarcasterAsync({
onConnectUri: (uri) => {
setConnectUri(uri);
// Optionally open in Warpcast
Linking.openURL(`https://warpcast.com/~/sign-in?uri=${encodeURIComponent(uri)}`);
},
});
} catch (err) {
console.error(err);
}
};
return (
{connectUri && {connectUri} }
);
}
```
# useVerifyNewAccount
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-verify-new-account
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseVerifyNewAccount from '/snippets/v3/definitions/hooks/useVerifyNewAccount.mdx';
The `useVerifyNewAccount` hook verifies a new user's account using the code they received via email or SMS. Call this after `useSignUpOrLogIn` returns an `AuthStateVerify` result.
## Import
```tsx
import { useVerifyNewAccount } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useVerifyNewAccount } from "@getpara/react-native-wallet";
import { useState } from "react";
import { View, TextInput, Button } from "react-native";
function VerifyScreen() {
const [code, setCode] = useState("");
const { verifyNewAccountAsync, isPending } = useVerifyNewAccount();
const handleVerify = async () => {
try {
await verifyNewAccountAsync({ verificationCode: code });
// User is now logged in and wallets are being created
} catch (err) {
console.error(err);
}
};
return (
);
}
```
# useVerifyOAuth
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-verify-o-auth
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseVerifyOAuth from '/snippets/v3/definitions/hooks/useVerifyOAuth.mdx';
The `useVerifyOAuth` hook handles the OAuth redirect and polling step of the authentication flow. Use it as part of a custom step-by-step auth flow, or prefer `useAuthenticateWithOAuth` for an all-in-one approach.
## Import
```tsx
import { useVerifyOAuth } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useVerifyOAuth } from "@getpara/react-native-wallet";
import * as WebBrowser from "expo-web-browser";
function OAuthStep() {
const { verifyOAuthAsync, isPending } = useVerifyOAuth();
const handleVerify = async () => {
try {
const result = await verifyOAuthAsync({
method: "GOOGLE",
appScheme: "myapp",
onOAuthUrl: (url) => WebBrowser.openBrowserAsync(url),
});
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useVerifyTelegram
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-verify-telegram
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseVerifyTelegram from '/snippets/v3/definitions/hooks/useVerifyTelegram.mdx';
The `useVerifyTelegram` hook verifies a Telegram login response. Pass the `TelegramAuthResponse` received from the Telegram Login Widget or bot callback.
## Import
```tsx
import { useVerifyTelegram } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useVerifyTelegram } from "@getpara/react-native-wallet";
function TelegramLoginHandler({ telegramAuthResponse }) {
const { verifyTelegramAsync, isPending } = useVerifyTelegram();
const handleVerify = async () => {
try {
await verifyTelegramAsync({ telegramAuthResponse });
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useWaitForLogin
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-wait-for-login
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseWaitForLogin from '/snippets/v3/definitions/hooks/useWaitForLogin.mdx';
The `useWaitForLogin` hook polls the Para backend until a returning user's login is confirmed. Use it as part of a custom step-by-step auth flow after `useSignUpOrLogIn` returns `AuthStateLogin`.
## Import
```tsx
import { useWaitForLogin } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useWaitForLogin } from "@getpara/react-native-wallet";
function WaitingForLogin() {
const { waitForLoginAsync, isPending } = useWaitForLogin();
const handleWait = async () => {
try {
const result = await waitForLoginAsync({});
if (result.needsWallet) {
// Trigger wallet creation
}
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useWaitForSignup
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-wait-for-signup
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseWaitForSignup from '/snippets/v3/definitions/hooks/useWaitForSignup.mdx';
The `useWaitForSignup` hook polls until a new user's signup is fully confirmed. Use it as part of a custom step-by-step auth flow after the verification code step.
## Import
```tsx
import { useWaitForSignup } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useWaitForSignup } from "@getpara/react-native-wallet";
function WaitingForSignup() {
const { waitForSignupAsync, isPending } = useWaitForSignup();
const handleWait = async () => {
try {
await waitForSignupAsync({});
// Signup complete — proceed to wallet creation
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useWaitForWalletCreation
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-wait-for-wallet-creation
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseWaitForWalletCreation from '/snippets/v3/definitions/hooks/useWaitForWalletCreation.mdx';
The `useWaitForWalletCreation` hook polls until wallet creation completes. Use it as part of a custom auth flow after `useWaitForSignup` resolves.
## Import
```tsx
import { useWaitForWalletCreation } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useWaitForWalletCreation } from "@getpara/react-native-wallet";
function WaitingForWallet() {
const { waitForWalletCreationAsync, isPending } = useWaitForWalletCreation();
const handleWait = async () => {
try {
const result = await waitForWalletCreationAsync({});
console.log("Wallet IDs:", result.walletIds);
} catch (err) {
console.error(err);
}
};
return ;
}
```
# useWallet
Source: https://docs.getpara.com/v3/react-native/guides/hooks/use-wallet
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseWallet from '/snippets/v3/definitions/hooks/useWallet.mdx';
The `useWallet` hook provides access to the currently selected wallet's information. Use `useClient` alongside it to derive display addresses.
## Import
```tsx
import { useWallet } from "@getpara/react-native-wallet";
```
## Usage
```tsx
import { useWallet, useClient } from "@getpara/react-native-wallet";
import { View, Text } from "react-native";
function WalletInfo() {
const { data: wallet } = useWallet();
const client = useClient();
if (!wallet) return No wallet ;
const address = client?.getDisplayAddress(wallet.id, { addressType: "EVM" });
return (
Wallet ID: {wallet.id}
Address: {address}
);
}
```
# Permissions
Source: https://docs.getpara.com/v3/react-native/guides/permissions
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
When a partner enables policies in the , users see a permissions consent screen during login and may need to approve individual transactions that fall outside their granted permissions.
On web, the SDK handles this automatically with popups. On React Native, you need to tell the SDK how to open review URLs since there's no popup API available.
## Permissions Consent at Login
When policies are enabled, the Para portal shows a permissions consent screen after authentication. Users approve the requested scopes (e.g., "Message Signing", "Transaction Sending") before being redirected back to your app.
This works automatically with the one-click login flow (`openPopup` / `ASWebAuthenticationSession`) -- no extra code needed. The portal handles the consent UI internally.
## Transaction Review Setup
If a signing operation requires additional user approval (e.g., the transaction doesn't match any granted permission), the SDK needs to open a review URL. There are two ways to handle this:
### Option 1: Global Handler (Recommended)
Set a global handler once when you initialize Para. This covers all signing paths, including integration libraries like viem and ethers that don't pass per-call callbacks.
```typescript para.ts
import { ParaMobile, Environment } from '@getpara/react-native-wallet';
import { openBrowserAsync } from 'expo-web-browser';
const para = new ParaMobile(Environment.BETA, API_KEY, undefined, {
disableWorkers: true,
});
para.setTransactionReviewHandler((url) => {
openBrowserAsync(url);
});
export { para };
```
Any library that opens a browser works here -- `expo-web-browser`, `react-native-inappbrowser`, or a custom WebView.
### Option 2: Per-Call Callback
Pass `onTransactionReviewUrl` directly to `signMessage` or `signTransaction` for per-operation control:
```typescript
import { openBrowserAsync } from 'expo-web-browser';
const result = await para.signMessage({
walletId,
messageBase64,
onTransactionReviewUrl: (url) => {
openBrowserAsync(url);
},
});
```
This takes precedence over the global handler when provided.
## How It Works
1. Your app calls `signMessage()` or `signTransaction()`
2. The backend evaluates the operation against the user's granted permissions
3. If allowed, signing completes immediately and returns a signature
4. If not allowed, the backend returns a `pendingTransactionId` and the SDK opens a review URL
5. The user approves or denies in the browser
6. The SDK polls for the result and returns the signature (or throws on denial/timeout)
## Error Handling
Handle denial and timeout errors from the signing flow:
```typescript
import { TransactionReviewDenied, TransactionReviewTimeout } from '@getpara/react-native-wallet';
try {
const result = await para.signMessage({
walletId,
messageBase64,
});
console.log('Signature:', result.signature);
} catch (error) {
if (error instanceof TransactionReviewDenied) {
// User denied the transaction
console.warn('Transaction denied by user');
} else if (error instanceof TransactionReviewTimeout) {
// User didn't respond in time
console.warn('Transaction review timed out');
// error.transactionReviewUrl and error.pendingTransactionId
// are available for retry
}
}
```
The default approval timeout is 5 minutes for transactions that require review.
## Next Steps
# Wallet Pregeneration
Source: https://docs.getpara.com/v3/react-native/guides/pregen
import { Card } from '/snippets/v3/components/ui/card.mdx';
import PregenRestApiCallout from '/snippets/v3/pregen-rest-api-callout.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. In mobile applications you can use device-specific storage for the user share.
## Mobile-Specific Benefits
While pregeneration works the same across all Para SDKs, React Native and Expo 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 a Pregenerated Wallet
### Check if a wallet exists
```typescript
import { para } from '../your-para-client';
async function checkPregenWallet() {
const hasWallet = await para.hasPregenWallet({
pregenId: { email: "user@example.com" },
});
return hasWallet;
}
```
### Create a pregenerated wallet
```typescript
import { para } from '../your-para-client';
import { WalletType } from '@getpara/react-native-wallet';
async function createPregenWallet() {
const pregenWallet = await para.createPregenWallet({
type: 'EVM', // or 'SOLANA', 'COSMOS', 'STELLAR'
pregenId: {email: "user@example.com" },
});
console.log("Pregenerated Wallet ID:", pregenWallet.id);
return pregenWallet;
}
```
### Retrieve the user share
```typescript
import { para } from '../your-para-client';
async function getUserShare() {
const userShare = await para.getUserShare();
// Store this share securely
return userShare;
}
```
## Mobile Storage Options
In mobile applications, you have several options for securely storing the user share:
```typescript
import * as Keychain from 'react-native-keychain';
// Store the user share
async function storeUserShare(userShare) {
try {
await Keychain.setGenericPassword(
'para_user_share',
userShare,
{
service: 'com.yourapp.wallet',
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY
}
);
} catch (error) {
console.error("Error storing user share:", error);
}
}
// Retrieve the user share
async function retrieveUserShare() {
try {
const credentials = await Keychain.getGenericPassword({
service: 'com.yourapp.wallet'
});
if (credentials) {
return credentials.password;
}
return null;
} catch (error) {
console.error("Error retrieving user share:", error);
return null;
}
}
```
```typescript
import { MMKV } from 'react-native-mmkv';
// Initialize with encryption for better security
const storage = new MMKV({
id: 'wallet-storage',
encryptionKey: 'your-secure-encryption-key'
});
// Store the user share
function storeUserShare(userShare) {
storage.set('user_share', userShare);
}
// Retrieve the user share
function retrieveUserShare() {
return storage.getString('user_share');
}
```
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 Mobile Apps
Once you have created a pregenerated wallet and stored the user share, you can use it for signing operations:
```typescript
import { para } from '../your-para-client';
async function usePregenWallet() {
// Retrieve the user share from your secure storage
const userShare = await retrieveUserShare();
if (!userShare) {
console.error("User share not found");
return;
}
// Load the user share into the Para client
await para.setUserShare(userShare);
// Now you can perform signing operations
const messageBase64 = btoa("Hello, World!");
const signature = await para.signMessage({
walletId: "your-wallet-id",
messageBase64,
});
return signature;
}
```
### 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.
```typescript
import DeviceInfo from 'react-native-device-info';
async function createDeviceWallet() {
const deviceId = await DeviceInfo.getUniqueId();
const pregenWallet = await para.createPregenWallet({
type: 'EVM',
pregenId: { customId: `device-${deviceId}` },
});
// Store the user share in device-specific secure storage
const userShare = await para.getUserShare();
await storeUserShare(userShare);
return pregenWallet;
}
```
For iOS App Clips or Android Instant Apps, create temporary wallets that enable limited blockchain functionality without requiring full app installation or user authentication.
```typescript
async function createTemporaryWallet() {
// Generate a random identifier for this session
const sessionId = Math.random().toString(36).substring(2, 15);
const pregenWallet = await para.createPregenWallet({
type: 'EVM',
pregenId: { customId: `temp-${sessionId}` },
});
const userShare = await para.getUserShare();
// Store in memory for this session only
// (could also use temporary secure storage)
sessionStorage.userShare = userShare;
return pregenWallet;
}
```
Seamlessly introduce blockchain functionality to your existing app users without requiring them to understand wallets or crypto.
```typescript
async function createWalletForExistingUser(userId) {
// Check if we already created a wallet for this user
const hasWallet = await para.hasPregenWallet({
pregenId: { customId: `user-${userId}` },
});
if (!hasWallet) {
const pregenWallet = await para.createPregenWallet({
type: 'EVM',
pregenId: { customId: `user-${userId}` },
});
const userShare = await para.getUserShare();
await storeUserShare(userShare);
return pregenWallet;
} else {
// Retrieve existing wallet info
const wallets = await para.getPregenWallets({
pregenId: { customId: `user-${userId}` },
});
return wallets[0];
}
}
```
## 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:
```typescript
import { para } from '../your-para-client';
async function claimWallet() {
// Ensure user is authenticated
if (!(await para.isFullyLoggedIn())) {
console.error("User must be authenticated to claim wallets");
return;
}
// Retrieve and load the user share
const userShare = await retrieveUserShare();
await para.setUserShare(userShare);
// Claim the wallet
const recoverySecret = await para.claimPregenWallets();
// 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
# JWT Token Management
Source: https://docs.getpara.com/v3/react-native/guides/sessions-jwt
import UseIssueJwt from '/snippets/v3/definitions/hooks/useIssueJwt.mdx';
import SessionsJwtTokenStructure from '/snippets/v3/sessions-jwt-token-structure.mdx';
Once a user is signed in, you can request a Para JWT token. This token will provide attestations for the user's ID, their identity, any wallets they have provisioned via your application, and any connected wallets in their current session.
## Requesting a JWT Token
```typescript Core Method
import { ParaMobile } from "@getpara/react-native-wallet";
const para = new ParaMobile("your-api-key");
const { token, keyId } = await para.issueJwt();
```
```tsx React Native Hook
import { useIssueJwt } from "@getpara/react-native-wallet";
import { useState } from "react";
import { View, Button, Alert } from "react-native";
function JwtTokenManager() {
const { issueJwtAsync, isPending } = useIssueJwt();
const handleIssueToken = async () => {
try {
const result = await issueJwtAsync();
await sendTokenToBackend(result.token);
Alert.alert("Token issued", result.keyId);
} catch (err) {
console.error("Failed to issue JWT:", err);
}
};
return (
);
}
```
### Hook Reference
The token's expiry will be determined by your customized session length, or else will default to 30 minutes. Issuing a token, like most authenticated API operations, will also renew and extend the session for that duration.
The token's `aud` field will be set to your API key's unique ID, linking it specifically to your application.
## Best Practices
- **Session Verification**: For security-critical operations, verify JWT tokens on both client and server sides
- **Token Expiry**: Be aware that tokens expire based on your session configuration and plan accordingly
- **Secure Storage**: Never store JWT tokens in insecure locations for sensitive applications
# React Native Session Management
Source: https://docs.getpara.com/v3/react-native/guides/sessions
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para provides a comprehensive set of methods for managing authentication sessions in React Native applications. These sessions are crucial for secure transaction signing and other authenticated operations.
## Session Duration
Para session length is configured per API key and can be set up to 30 days through the Configuration section of the or CLI. The Para API enforces the configured duration. Signing a message or transaction, or calling `keepSessionAlive()`, can extend an active session according to that configuration.
## Managing Sessions
### Checking Session Status
Use `isSessionActive()` to verify whether a user's session is currently valid before performing authenticated operations.
```typescript
async isSessionActive(): Promise
```
In React Native 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:
```typescript
import { para } from '../your-para-client';
async function checkSession() {
try {
const isActive = await para.isSessionActive();
if (!isActive) {
// First clear any existing data
await para.logout();
// Navigate to login screen
navigation.navigate('Login');
} else {
// Session is valid, proceed with app flow
navigation.navigate('Dashboard');
}
} catch (error) {
console.error("Session check failed:", error);
// Handle error
}
}
```
### Maintaining Active Sessions
Para provides the `keepSessionAlive()` method to extend an active session without requiring full reauthentication.
```typescript
async keepSessionAlive(): Promise
```
Example usage:
```typescript
import { para } from '../your-para-client';
async function extendSession() {
try {
const success = await para.keepSessionAlive();
if (!success) {
// Session could not be extended
// Clear storage and navigate to login
await para.logout();
navigation.navigate('Login');
}
} catch (error) {
console.error("Session maintenance failed:", error);
}
}
```
### Refreshing Expired Sessions
When a session has expired, Para recommends initiating a full authentication flow rather than trying to refresh the session.
For React Native applications, always call `logout()` before reinitiating authentication when a session has expired to ensure all stored data is properly cleared.
```typescript
import { para } from '../your-para-client';
async function handleSessionExpiration() {
// When session expires, first clear storage
await para.logout();
// Then redirect to authentication screen
navigation.navigate('Login');
}
```
## 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.
```typescript
exportSession({ excludeSigners?: boolean }): string
```
If your server doesn't need to perform signing operations, use `{ excludeSigners: true }` when exporting sessions for enhanced security.
Example implementation:
```typescript
import { para } from '../your-para-client';
async function sendSessionToServer() {
// Export session without signing capabilities
const sessionData = para.exportSession({ excludeSigners: true });
// Send to your server
try {
const response = await fetch('https://your-api.com/sessions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ session: sessionData }),
});
if (!response.ok) {
throw new Error('Failed to send session to server');
}
return await response.json();
} catch (error) {
console.error('Error sending session to server:', error);
throw error;
}
}
```
## Best Practices for React Native
1. **Check Sessions on App Launch**: Verify session status when your app starts to determine if users need to reauthenticate.
```typescript
// In your app's entry point or navigation setup
useEffect(() => {
async function checkSessionOnLaunch() {
const isActive = await para.isSessionActive();
if (isActive) {
navigation.navigate('Dashboard');
} else {
await para.logout(); // Clear any lingering data
navigation.navigate('Login');
}
}
checkSessionOnLaunch();
}, []);
```
2. **Implement Automatic Session Extension**: For long app usage sessions, periodically call `keepSessionAlive()` to prevent unexpected session expirations.
```typescript
useEffect(() => {
const sessionInterval = setInterval(async () => {
try {
const isActive = await para.isSessionActive();
if (isActive) {
await para.keepSessionAlive();
} else {
// Session expired, handle accordingly
await para.logout();
navigation.navigate('Login');
}
} catch (error) {
console.error('Error maintaining session:', error);
}
}, 30 * 60 * 1000); // Check every 30 minutes
return () => clearInterval(sessionInterval);
}, []);
```
3. **Handle Background/Foreground State**: React Native apps can be backgrounded and foregrounded, which may affect session status.
```typescript
import { AppState } from 'react-native';
useEffect(() => {
const subscription = AppState.addEventListener('change', async (nextAppState) => {
if (nextAppState === 'active') {
// App came to foreground, check session
const isActive = await para.isSessionActive();
if (!isActive) {
await para.logout();
navigation.navigate('Login');
}
}
});
return () => {
subscription.remove();
};
}, []);
```
4. **Secure Storage Configuration**: For enhanced security, consider implementing a to manage sensitive session data.
## Next Steps
Explore more advanced features and integrations with Para in React Native:
# Solana Support in React Native
Source: https://docs.getpara.com/v3/react-native/guides/solana
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para's React Native SDK provides comprehensive support for Solana blockchain operations through libraries like Solana Web3.js and Anchor. After authenticating users with native passkeys in your React Native or Expo application, all Solana-related operations work exactly the same as in our web SDKs.
## Implementation and References
Once authentication is complete with the Para React Native SDK using native passkeys, you can immediately sign Solana transactions and messages, interact with Solana programs, connect to different Solana networks (mainnet, devnet, testnet), and leverage Solana Pay and other ecosystem tools. Both Solana Web3.js and Anchor framework work seamlessly with Para's React Native SDK, functioning identically to their web implementations.
## Key Considerations for Mobile
When implementing Solana functionality in React Native applications:
- Ensure proper initialization of the Para SDK with your project ID
- Complete authentication with native passkeys before attempting any signing operations
- Design mobile-friendly UI for transaction approval flows
- Test transaction signing on actual devices to verify the end-to-end experience
The underlying code for Solana transaction construction and signing remains identical between web and mobile implementations, making it easy to maintain consistency across platforms.
# Claim Staking Rewards
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/claim-rewards
import { Card } from '/snippets/v3/components/ui/card.mdx';
Withdraw your accumulated staking rewards from validators on Cosmos chains using CosmJS in React Native.
## Prerequisites
## Claim Rewards
```typescript
import { useMemo } from 'react';
import { View, Text, Button } from 'react-native';
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from '@getpara/react-native-wallet/cosmos';
import { SigningStargateClient, coins } from '@cosmjs/stargate';
import { MsgWithdrawDelegatorReward } from 'cosmjs-types/cosmos/distribution/v1beta1/tx';
const RPC_URL = 'https://rpc.cosmos.directory/cosmoshub';
function ClaimRewards() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const client = useMemo(
() => protoSigner ? SigningStargateClient.connectWithSigner(RPC_URL, protoSigner) : undefined,
[protoSigner]
);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const claimStakingRewards = async () => {
if (!protoSigner || !address) return;
const validator = 'cosmosvaloper1...';
const msgWithdrawReward = {
typeUrl: '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward',
value: MsgWithdrawDelegatorReward.fromPartial({
delegatorAddress: address,
validatorAddress: validator,
}),
};
const fee = {
amount: coins(5000, 'uatom'),
gas: '200000',
};
try {
const result = await signAndBroadcastAsync({
messages: [msgWithdrawReward],
fee,
memo: 'Claiming rewards via Para',
});
console.log('Rewards claimed:', result.transactionHash);
console.log('Gas used:', result.gasUsed);
} catch (error) {
console.error('Failed to claim rewards:', error);
}
};
if (isLoading) return Loading... ;
return (
Delegator: {address}
);
}
```
## Next Steps
# Configure RPC Nodes with Cosmos Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/configure-rpc
import { Card } from '/snippets/v3/components/ui/card.mdx';
Learn how to configure custom RPC endpoints for different Cosmos-based chains when using CosmJS with Para in React Native.
## Prerequisites
## Configure Chain-Specific RPC
```typescript
import { useState } from 'react';
import { View, Text, Button } from 'react-native';
import { useParaCosmjsProtoSigner } from '@getpara/react-native-wallet/cosmos';
import { StargateClient } from '@cosmjs/stargate';
const CHAIN_CONFIGS = {
cosmos: {
rpc: 'https://rpc.cosmos.directory/cosmoshub',
chainId: 'cosmoshub-4'
},
osmosis: {
rpc: 'https://rpc.cosmos.directory/osmosis',
chainId: 'osmosis-1'
},
celestia: {
rpc: 'https://celestia-rpc.publicnode.com',
chainId: 'celestia'
},
dydx: {
rpc: 'https://dydx-dao-rpc.polkachu.com',
chainId: 'dydx-mainnet-1'
}
};
function MultiChainExample() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const [heights, setHeights] = useState>({});
const checkChainStatus = async () => {
const cosmosClient = await StargateClient.connect(CHAIN_CONFIGS.cosmos.rpc);
const osmosisClient = await StargateClient.connect(CHAIN_CONFIGS.osmosis.rpc);
const cosmosHeight = await cosmosClient.getHeight();
const osmosisHeight = await osmosisClient.getHeight();
setHeights({ cosmos: cosmosHeight, osmosis: osmosisHeight });
console.log('Cosmos block height:', cosmosHeight);
console.log('Osmosis block height:', osmosisHeight);
};
if (isLoading) return Loading... ;
return (
{Object.entries(heights).map(([chain, height]) => (
{chain}: {height}
))}
);
}
```
## Next Steps
# Execute Transactions with Cosmos Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/execute-transactions
import { Card } from '/snippets/v3/components/ui/card.mdx';
Execute custom messages and interact with Cosmos modules using CosmJS with Para wallets in React Native.
## Prerequisites
## Execute Custom Messages
```typescript
import { useMemo } from 'react';
import { View, Text, Button } from 'react-native';
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from '@getpara/react-native-wallet/cosmos';
import { SigningStargateClient, coins } from '@cosmjs/stargate';
import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx';
const RPC_URL = 'https://rpc.cosmos.directory/cosmoshub';
function CustomTransaction() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const client = useMemo(
() => protoSigner ? SigningStargateClient.connectWithSigner(RPC_URL, protoSigner) : undefined,
[protoSigner]
);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const executeCustomMessage = async () => {
if (!protoSigner || !address) return;
const msgSend = {
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
value: MsgSend.fromPartial({
fromAddress: address,
toAddress: 'cosmos1...',
amount: coins(1000000, 'uatom'),
}),
};
const fee = {
amount: coins(5000, 'uatom'),
gas: '200000',
};
try {
const result = await signAndBroadcastAsync({
messages: [msgSend],
fee,
memo: 'Custom message via Para',
});
console.log('Transaction hash:', result.transactionHash);
console.log('Code:', result.code);
} catch (error) {
console.error('Transaction failed:', error);
}
};
if (isLoading) return Loading... ;
return (
From: {address}
);
}
```
## Next Steps
# Sponsor Gas Fees on Cosmos
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/gas-sponsorship
import { Card } from '/snippets/v3/components/ui/card.mdx';
This guide demonstrates how to implement gas sponsorship on Cosmos networks using fee grants. Unlike EVM chains that use account abstraction for gasless transactions, Cosmos networks natively support gas sponsorship through fee grants, allowing a grantor address to pay for another account's transaction fees.
## Prerequisites
## Understanding Fee Grants
Fee grants in Cosmos allow one account (grantor) to pay transaction fees for another account (grantee). This mechanism provides native gas sponsorship without requiring smart contracts or account abstraction.
Key concepts:
- **Grantor**: The account that pays for gas fees
- **Grantee**: The account whose transactions are sponsored
- **Allowance**: Defines spending limits and expiration for the grant
Only one fee grant is allowed per granter-grantee pair. Self-grants are not permitted.
## Creating a Basic Fee Grant
Create a basic allowance to grant gas sponsorship to another address:
```typescript
import { MsgGrantAllowance } from 'cosmjs-types/cosmos/feegrant/v1beta1/tx';
import { BasicAllowance } from 'cosmjs-types/cosmos/feegrant/v1beta1/feegrant';
const granterAddress = paraSigner.address; // From your Para signer setup
const granteeAddress = 'cosmos1grantee...'; // Replace with actual grantee address
// Create basic allowance with spending limits
const basicAllowance = BasicAllowance.fromPartial({
spendLimit: [{
denom: 'uatom',
amount: '1000000' // 1 ATOM limit
}],
expiration: {
seconds: BigInt(Math.floor(Date.now() / 1000) + 86400), // 24 hours from now
nanos: 0
}
});
// Create the grant message
const grantMsg = {
typeUrl: '/cosmos.feegrant.v1beta1.MsgGrantAllowance',
value: MsgGrantAllowance.fromPartial({
granter: granterAddress,
grantee: granteeAddress,
allowance: {
typeUrl: '/cosmos.feegrant.v1beta1.BasicAllowance',
value: BasicAllowance.encode(basicAllowance).finish()
}
})
};
// Sign and broadcast the grant transaction
const result = await client.signAndBroadcast(
granterAddress,
[grantMsg],
'auto', // Let the client estimate gas
'Granting fee allowance'
);
```
## Using Fee Grants as a Grantee
Once a fee grant is established, the grantee can perform transactions with sponsored gas fees:
```typescript
import { useClient, useAccount } from '@getpara/react-native-wallet';
import { createParaProtoSigner } from '@getpara/cosmjs-v0-integration';
// Create a signer for the grantee
const para = useClient();
const granteeSigner = createParaProtoSigner({ para: para, prefix: 'cosmos' });
const granteeClient = await SigningStargateClient.connectWithSigner(rpcUrl, granteeSigner);
// Send tokens with sponsored gas
const result = await granteeClient.sendTokens(
granteeAddress,
'cosmos1recipient...',
[{ denom: 'uatom', amount: '100000' }], // 0.1 ATOM
{
amount: [{ denom: 'uatom', amount: '5000' }],
gas: '200000',
granter: granterAddress // This tells the network to use the fee grant
}
);
```
## Querying Fee Grants
Check existing grants before creating new ones:
```typescript
// Query grants for a specific grantee
const grantsByGrantee = await fetch(
`${restUrl}/cosmos/feegrant/v1beta1/allowances/${granteeAddress}`
).then(res => res.json());
// Query all grants by a specific granter
const grantsByGranter = await fetch(
`${restUrl}/cosmos/feegrant/v1beta1/issued/${granterAddress}`
).then(res => res.json());
// Query a specific grant
const specificGrant = await fetch(
`${restUrl}/cosmos/feegrant/v1beta1/allowance/${granterAddress}/${granteeAddress}`
).then(res => res.json());
```
## Other Allowance Types
### Periodic Allowance
Resets spending limits periodically:
```typescript
import { PeriodicAllowance } from 'cosmjs-types/cosmos/feegrant/v1beta1/feegrant';
const periodicAllowance = PeriodicAllowance.fromPartial({
basic: {
spendLimit: [{
denom: 'uatom',
amount: '10000000' // 10 ATOM total limit
}],
expiration: null // No expiration
},
period: { seconds: BigInt(86400), nanos: 0 }, // 24 hours
periodSpendLimit: [{
denom: 'uatom',
amount: '1000000' // 1 ATOM per period
}]
});
```
### Allowed Message Allowance
Restricts which message types can be sponsored:
```typescript
import { AllowedMsgAllowance } from 'cosmjs-types/cosmos/feegrant/v1beta1/feegrant';
const allowedMsgAllowance = AllowedMsgAllowance.fromPartial({
allowance: {
typeUrl: '/cosmos.feegrant.v1beta1.BasicAllowance',
value: BasicAllowance.encode(basicAllowance).finish()
},
allowedMessages: [
'/cosmos.bank.v1beta1.MsgSend',
'/cosmos.staking.v1beta1.MsgDelegate'
]
});
```
## Revoking Fee Grants
Remove a fee grant when it's no longer needed:
```typescript
import { MsgRevokeAllowance } from 'cosmjs-types/cosmos/feegrant/v1beta1/tx';
const revokeMsg = {
typeUrl: '/cosmos.feegrant.v1beta1.MsgRevokeAllowance',
value: MsgRevokeAllowance.fromPartial({
granter: granterAddress,
grantee: granteeAddress
})
};
const result = await client.signAndBroadcast(
granterAddress,
[revokeMsg],
'auto'
);
```
## Advanced: Server-Controlled Gas Sponsorship
For production applications, use server-side controlled wallets as grantors while allowing users to authenticate client-side as grantees. This pattern uses Para's pregenerated wallets to create an app-controlled grantor wallet.
### Server-Side Setup
Create and manage a grantor wallet on your server:
```typescript
// server.ts
import { Para } from '@getpara/server-sdk';
import { SigningStargateClient } from '@cosmjs/stargate';
import { createParaProtoSigner } from '@getpara/cosmjs-v0-integration';
const serverPara = new Para(process.env.PARA_API_KEY);
// Create a pregen wallet for gas sponsorship
const grantorWallet = await serverPara.createPregenWallet({
type: 'COSMOS',
pregenId: { customId: 'app-gas-sponsor-wallet' }
});
// Store the user share securely
const userShare = await serverPara.getUserShare();
// Store userShare in your secure database
```
### API Endpoint for Creating Grants
```typescript
// POST /api/create-fee-grant
async function createFeeGrantForUser(userAddress: string) {
// Load the grantor wallet
await serverPara.setUserShare(storedUserShare);
// Set up Cosmos client
const granterSigner = createParaProtoSigner({ para: serverPara, prefix: 'cosmos' });
const client = await SigningStargateClient.connectWithSigner(rpcUrl, granterSigner);
// Check if grant already exists
const existingGrant = await fetch(
`${restUrl}/cosmos/feegrant/v1beta1/allowance/${granterSigner.address}/${userAddress}`
).then(res => res.json());
if (existingGrant.allowance) {
// Revoke existing grant first
const revokeMsg = {
typeUrl: '/cosmos.feegrant.v1beta1.MsgRevokeAllowance',
value: MsgRevokeAllowance.fromPartial({
granter: granterSigner.address,
grantee: userAddress
})
};
await client.signAndBroadcast(
granterSigner.address,
[revokeMsg],
'auto'
);
}
// Create new grant with daily limit
const basicAllowance = BasicAllowance.fromPartial({
spendLimit: [{
denom: 'uatom',
amount: '1000000' // 1 ATOM daily limit
}],
expiration: {
seconds: BigInt(Math.floor(Date.now() / 1000) + 86400),
nanos: 0
}
});
const grantMsg = {
typeUrl: '/cosmos.feegrant.v1beta1.MsgGrantAllowance',
value: MsgGrantAllowance.fromPartial({
granter: granterSigner.address,
grantee: userAddress,
allowance: {
typeUrl: '/cosmos.feegrant.v1beta1.BasicAllowance',
value: BasicAllowance.encode(basicAllowance).finish()
}
})
};
const result = await client.signAndBroadcast(
granterSigner.address,
[grantMsg],
'auto'
);
return {
transactionHash: result.transactionHash,
granterAddress: granterSigner.address
};
}
```
### Client-Side Integration
```typescript
import { useState } from 'react';
import { View, Text, Button } from 'react-native';
import { useParaCosmjsProtoSigner } from '@getpara/react-native-wallet/cosmos';
import { SigningStargateClient } from '@cosmjs/stargate';
const RPC_URL = 'https://rpc.cosmos.directory/cosmoshub';
function MyApp() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const [granterAddress, setGranterAddress] = useState();
const setupGasSponsorship = async () => {
if (!address) return;
const response = await fetch('/api/create-fee-grant', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userAddress: address })
});
const { granterAddress: granter } = await response.json();
setGranterAddress(granter);
};
const performSponsoredTransaction = async (recipientAddress: string, amount: string) => {
if (!protoSigner || !address || !granterAddress) return;
const client = await SigningStargateClient.connectWithSigner(RPC_URL, protoSigner);
const result = await client.sendTokens(
address,
recipientAddress,
[{ denom: 'uatom', amount }],
{
amount: [{ denom: 'uatom', amount: '5000' }],
gas: '200000',
granter: granterAddress
}
);
return result.transactionHash;
};
if (isLoading) return Loading... ;
return (
Address: {address}
performSponsoredTransaction('cosmos1...', '100000')}
/>
);
}
```
## Best Practices
- Set appropriate spending limits based on expected transaction volume
- Use expiration times to automatically clean up unused grants
- Monitor grant usage to control costs and detect abuse
- Consider using periodic allowances for regular users
- Use allowed message allowances to restrict transaction types
- Remember that creating and revoking grants also incur gas costs
Fee grants provide native gas sponsorship on Cosmos networks without requiring smart contracts, making them more efficient than EVM account abstraction solutions.
# IBC Cross-Chain Transfers
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/ibc-transfers
import { Card } from '/snippets/v3/components/ui/card.mdx';
Transfer tokens between different Cosmos chains using IBC (Inter-Blockchain Communication) with CosmJS in React Native.
## Prerequisites
## IBC Transfer
```typescript
import { useMemo } from 'react';
import { View, Text, Button } from 'react-native';
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from '@getpara/react-native-wallet/cosmos';
import { SigningStargateClient, coins } from '@cosmjs/stargate';
import { MsgTransfer } from 'cosmjs-types/ibc/applications/transfer/v1/tx';
import { Height } from 'cosmjs-types/ibc/core/client/v1/client';
const RPC_URL = 'https://rpc.cosmos.directory/cosmoshub';
function IBCTransfer() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const client = useMemo(
() => protoSigner ? SigningStargateClient.connectWithSigner(RPC_URL, protoSigner) : undefined,
[protoSigner]
);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const sendIBCTransfer = async () => {
if (!protoSigner || !address || !client) return;
const resolvedClient = await client;
const currentHeight = await resolvedClient.getHeight();
const timeoutHeight = Height.fromPartial({
revisionNumber: 1n,
revisionHeight: BigInt(currentHeight + 1000),
});
const msgTransfer = {
typeUrl: '/ibc.applications.transfer.v1.MsgTransfer',
value: MsgTransfer.fromPartial({
sourcePort: 'transfer',
sourceChannel: 'channel-141',
token: {
denom: 'uatom',
amount: '1000000',
},
sender: address,
receiver: 'osmo1...',
timeoutHeight: timeoutHeight,
timeoutTimestamp: 0n,
}),
};
const fee = {
amount: coins(5000, 'uatom'),
gas: '250000',
};
try {
const result = await signAndBroadcastAsync({
messages: [msgTransfer],
fee,
memo: 'IBC transfer via Para',
});
console.log('IBC transfer initiated:', result.transactionHash);
} catch (error) {
console.error('IBC transfer failed:', error);
}
};
if (isLoading) return Loading... ;
return (
From: {address}
);
}
```
## Next Steps
# Query Wallet Balances with Cosmos Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/query-balances
import { Card } from '/snippets/v3/components/ui/card.mdx';
Query token balances for any Cosmos address or your connected Para wallet using CosmJS in React Native.
## Prerequisites
## Query Balances
```typescript
import { useState, useEffect } from 'react';
import { View, Text, Button } from 'react-native';
import { useParaCosmjsProtoSigner } from '@getpara/react-native-wallet/cosmos';
import { StargateClient } from '@cosmjs/stargate';
const RPC_URL = 'https://rpc.cosmos.directory/cosmoshub';
function BalanceDisplay() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const [balances, setBalances] = useState<{ denom: string; amount: string }[]>([]);
const queryBalances = async () => {
if (!address) return;
const client = await StargateClient.connect(RPC_URL);
const allBalances = await client.getAllBalances(address);
setBalances(allBalances);
const atomBalance = await client.getBalance(address, 'uatom');
console.log('ATOM balance:', atomBalance.amount, atomBalance.denom);
};
if (isLoading) return Loading... ;
return (
Address: {address}
{balances.map(balance => (
{balance.amount} {balance.denom}
))}
);
}
```
## Next Steps
# Query Validator Information
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/query-validators
import { Card } from '/snippets/v3/components/ui/card.mdx';
Query validator information to make informed staking decisions on Cosmos chains using CosmJS in React Native.
## Prerequisites
## Query Validators
```typescript
import { useState } from 'react';
import { View, Text, Button } from 'react-native';
import { useParaCosmjsProtoSigner } from '@getpara/react-native-wallet/cosmos';
import { setupStakingExtension, QueryClient } from '@cosmjs/stargate';
import { Tendermint37Client } from '@cosmjs/tendermint-rpc';
const RPC_URL = 'https://rpc.cosmos.directory/cosmoshub';
interface ValidatorInfo {
name: string;
operatorAddress: string;
tokens: string;
commission: string;
}
function ValidatorList() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const [validators, setValidators] = useState([]);
const queryValidators = async () => {
const tmClient = await Tendermint37Client.connect(RPC_URL);
const queryClient = QueryClient.withExtensions(tmClient, setupStakingExtension);
try {
const { validators: activeValidators } = await queryClient.staking.validators('BOND_STATUS_BONDED');
const validatorInfo = activeValidators.slice(0, 10).map(validator => ({
name: validator.description?.moniker || 'Unknown',
operatorAddress: validator.operatorAddress,
tokens: validator.tokens,
commission: validator.commission?.commissionRates?.rate || '0',
}));
setValidators(validatorInfo);
if (address) {
const delegations = await queryClient.staking.delegatorDelegations(address);
console.log('Your delegations:', delegations);
}
} catch (error) {
console.error('Failed to query validators:', error);
}
};
if (isLoading) return Loading... ;
return (
Address: {address}
{validators.map(v => (
{v.name} - Commission: {v.commission}
))}
);
}
```
## Next Steps
# Send Tokens with Cosmos Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/send-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
Transfer tokens between Cosmos accounts using CosmJS with Para's secure wallet infrastructure in React Native.
## Prerequisites
## Send Tokens
```typescript
import { View, Text, Button } from 'react-native';
import { useParaCosmjsProtoSigner } from '@getpara/react-native-wallet/cosmos';
import { SigningStargateClient, coins } from '@cosmjs/stargate';
const RPC_URL = 'https://rpc.cosmos.directory/cosmoshub';
function TokenTransfer() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const sendTokens = async () => {
if (!protoSigner || !address) return;
const recipient = 'cosmos1...';
const amount = coins(1000000, 'uatom');
const fee = {
amount: coins(5000, 'uatom'),
gas: '200000',
};
try {
const client = await SigningStargateClient.connectWithSigner(RPC_URL, protoSigner);
const result = await client.sendTokens(
address,
recipient,
amount,
fee,
'Sent via Para'
);
console.log('Transaction hash:', result.transactionHash);
console.log('Gas used:', result.gasUsed);
} catch (error) {
console.error('Transfer failed:', error);
}
};
if (isLoading) return Loading... ;
return (
From: {address}
);
}
```
## Next Steps
# Setup Cosmos Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/setup-libraries
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Learn how to set up with Para SDK to interact with Cosmos-based blockchains.
## Prerequisites
Use the Proto signer for transaction operations like sending tokens, staking, and IBC transfers.
## Install
```bash
npm install @getpara/react-native-wallet @getpara/cosmjs-v0-integration @cosmjs/stargate
```
`@getpara/cosmjs-v0-integration` is a separate package — install it alongside `@getpara/react-native-wallet`.
## Usage
Use the hook to create a CosmJS `OfflineDirectSigner` for your user's Para embedded wallet or external wallet. The hook wraps `signAndBroadcast` in a React Query mutation.
```tsx
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from "@getpara/react-native-wallet/cosmos";
import { SigningStargateClient, coins } from "@cosmjs/stargate";
import { useState, useEffect } from "react";
import { Text, TouchableOpacity } from "react-native";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
function SendTokens() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const [client, setClient] = useState();
useEffect(() => {
if (protoSigner) {
SigningStargateClient.connectWithSigner(RPC_URL, protoSigner).then(setClient);
}
}, [protoSigner]);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const handleSend = async () => {
const result = await signAndBroadcastAsync({
messages: [{ typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: {
fromAddress: protoSigner!.address,
toAddress: "cosmos1...",
amount: coins(1000000, "uatom"),
}}],
fee: { amount: coins(5000, "uatom"), gas: "200000" },
});
console.log("Tx hash:", result.transactionHash);
};
if (isLoading) return Loading... ;
return (
{isPending ? "Broadcasting..." : "Send 1 ATOM"}
);
}
```
### Wallet Resolution
When no `address` or `walletId` is passed, the hook resolves the wallet in this order:
1. **Selected wallet** — if the user selected a Cosmos wallet in the UI. If there is only one Cosmos wallet in the session, it is already selected by default
2. **First Cosmos wallet** — the first available Cosmos wallet on the account
To target a specific wallet:
```tsx
const { protoSigner } = useParaCosmjsProtoSigner({
address: "cosmos1...", // or walletId: "uuid-..."
prefix: "osmo", // optional chain prefix, defaults to "cosmos"
});
```
Use `createParaProtoSigner` to create a signer directly.
```typescript
import { createParaProtoSigner } from "@getpara/cosmjs-v0-integration";
import { SigningStargateClient } from "@cosmjs/stargate";
import Para from "@getpara/core-sdk";
const para = new Para("YOUR_API_KEY");
// Authenticate first...
const signer = createParaProtoSigner({ para, prefix: "cosmos" });
const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer);
```
### Wallet Resolution
When no `address` or `walletId` is passed, the factory picks the first available Cosmos wallet. To target a specific wallet:
```typescript
const signer = createParaProtoSigner({ para, prefix: "cosmos", address: "cosmos1..." });
```
Use the Amino signer for message signing (ADR-036) and authentication flows.
## Install
```bash
npm install @getpara/react-native-wallet @getpara/cosmjs-v0-integration @cosmjs/amino
```
## Usage
Use the hook to create a CosmJS `OfflineAminoSigner` for message signing (ADR-036) and authentication flows. The mutation hook also accepts an amino signer.
```tsx
import { useParaCosmjsAminoSigner } from "@getpara/react-native-wallet/cosmos";
import { makeSignDoc } from "@cosmjs/amino";
import { Text, Button } from "react-native";
function SignMessage() {
const { aminoSigner, isLoading } = useParaCosmjsAminoSigner();
const address = aminoSigner?.address;
const handleSign = async () => {
if (!aminoSigner || !address) return;
const signDoc = makeSignDoc(
[{ type: "sign/MsgSignData", value: { signer: address, data: btoa("Hello!") } }],
{ amount: [], gas: "0" }, "cosmoshub-4", "", 0, 0,
);
const { signature } = await aminoSigner.signAmino(address, signDoc);
console.log("Signature:", signature.signature);
};
if (isLoading) return Loading... ;
return ;
}
```
Wallet resolution works the same as the Proto signer hook.
```typescript
import { createParaAminoSigner } from "@getpara/cosmjs-v0-integration";
const signer = createParaAminoSigner({ para, prefix: "cosmos" });
```
## Metro Configuration
`@cosmjs/crypto` depends on `libsodium-wrappers-sumo`, a WASM-based cryptography library that is incompatible with React Native. Since Para handles signing server-side via MPC, this library is not needed at runtime. However, Metro will still bundle it and crash when the WASM module loads.
To fix this, stub out the module in your `metro.config.js` using a custom `resolveRequest`:
```js metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
const emptyModule = require.resolve('./lib/empty-module.js');
// libsodium-wrappers-sumo uses WASM which doesn't work in React Native.
// Since Para signs via MPC, this module is not needed.
// We must use resolveRequest (not extraNodeModules) because the package
// is installed in node_modules and extraNodeModules only acts as a fallback.
const stubbedModules = new Set(['libsodium-wrappers-sumo']);
const originalResolveRequest = config.resolver.resolveRequest;
config.resolver.resolveRequest = (context, moduleName, platform) => {
if (stubbedModules.has(moduleName)) {
return { type: 'sourceFile', filePath: emptyModule };
}
if (originalResolveRequest) {
return originalResolveRequest(context, moduleName, platform);
}
return context.resolveRequest(context, moduleName, platform);
};
module.exports = config;
```
Create the empty module stub:
```js lib/empty-module.js
module.exports = {};
```
Using `extraNodeModules` will **not** work for this case. Metro's `extraNodeModules` only acts as a
fallback when a module can't be found. Since `libsodium-wrappers-sumo` is installed as a transitive
dependency of `@cosmjs/crypto`, Metro resolves it from `node_modules` before checking
`extraNodeModules`. The `resolveRequest` override intercepts resolution before that happens.
## Next Steps
# Sign Messages with Cosmos Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/sign-messages
import { Card } from '/snippets/v3/components/ui/card.mdx';
Sign arbitrary messages for authentication or verification using CosmJS with Para wallets in React Native.
## Prerequisites
## Sign Messages
Sign arbitrary messages using the ADR-036 standard for Cosmos authentication.
```typescript
import { useMutation } from '@tanstack/react-query';
import { View, Text, Button } from 'react-native';
import { useParaCosmjsAminoSigner } from '@getpara/react-native-wallet/cosmos';
import { makeSignDoc } from '@cosmjs/amino';
const CHAIN_ID = 'cosmoshub-4';
function MessageSigning() {
const { aminoSigner, isLoading } = useParaCosmjsAminoSigner();
const address = aminoSigner?.address;
const { mutate: signMessage, data: signature, isPending } = useMutation({
mutationFn: async () => {
if (!aminoSigner || !address) throw new Error('Signer not ready');
const message = 'Sign this message to authenticate with Para';
const signDoc = makeSignDoc(
[{ type: 'sign/MsgSignData', value: { signer: address, data: btoa(message) } }],
{ amount: [], gas: '0' },
CHAIN_ID,
'',
0,
0
);
const { signature: sig } = await aminoSigner.signAmino(address, signDoc);
return sig.signature;
},
});
if (isLoading) return Loading... ;
return (
Address: {address}
signMessage()}
disabled={!aminoSigner || isPending}
/>
{signature && Signature: {signature} }
);
}
```
## Next Steps
# Stake Tokens to Validators
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/stake-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
Delegate your tokens to validators on Cosmos chains to earn staking rewards using CosmJS in React Native.
## Prerequisites
## Stake Tokens
```typescript
import { useMemo } from 'react';
import { View, Text, Button } from 'react-native';
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from '@getpara/react-native-wallet/cosmos';
import { SigningStargateClient, coins } from '@cosmjs/stargate';
import { MsgDelegate } from 'cosmjs-types/cosmos/staking/v1beta1/tx';
const RPC_URL = 'https://rpc.cosmos.directory/cosmoshub';
function StakeTokens() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const client = useMemo(
() => protoSigner ? SigningStargateClient.connectWithSigner(RPC_URL, protoSigner) : undefined,
[protoSigner]
);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const delegateToValidator = async () => {
if (!protoSigner || !address) return;
const validator = 'cosmosvaloper1...';
const msgDelegate = {
typeUrl: '/cosmos.staking.v1beta1.MsgDelegate',
value: MsgDelegate.fromPartial({
delegatorAddress: address,
validatorAddress: validator,
amount: {
denom: 'uatom',
amount: '1000000',
},
}),
};
const fee = {
amount: coins(5000, 'uatom'),
gas: '250000',
};
try {
const result = await signAndBroadcastAsync({
messages: [msgDelegate],
fee,
memo: 'Staking with Para',
});
console.log('Delegation successful:', result.transactionHash);
} catch (error) {
console.error('Delegation failed:', error);
}
};
if (isLoading) return Loading... ;
return (
Delegator: {address}
);
}
```
## Next Steps
# Verify Signatures with Cosmos Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/cosmos/verify-signatures
import { Card } from '/snippets/v3/components/ui/card.mdx';
Verify signatures from signed messages to authenticate users or validate transactions using CosmJS in React Native.
## Prerequisites
## Verify Signatures
Verify ADR-036 signatures using CosmJS crypto utilities.
```typescript
import { useState } from 'react';
import { View, Text, Button } from 'react-native';
import { fromBase64, toUtf8 } from '@cosmjs/encoding';
import { Secp256k1, Secp256k1Signature, sha256 } from '@cosmjs/crypto';
import { serializeSignDoc, makeSignDoc } from '@cosmjs/amino';
function SignatureVerification() {
const [isValid, setIsValid] = useState(null);
const verifyADR036Signature = async () => {
const pubkeyBase64 = 'A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ';
const signatureBase64 = '...'; // Signature from signAmino
const originalMessage = 'Hello Para!';
const signerAddress = 'cosmos1...';
const chainId = 'cosmoshub-4';
try {
const pubkey = fromBase64(pubkeyBase64);
const signature = fromBase64(signatureBase64);
const signDoc = makeSignDoc(
[{ type: 'sign/MsgSignData', value: { signer: signerAddress, data: btoa(originalMessage) } }],
{ amount: [], gas: '0' },
chainId,
'',
0,
0
);
const serialized = serializeSignDoc(signDoc);
const messageHash = sha256(serialized);
const sig = Secp256k1Signature.fromDer(signature);
const valid = await Secp256k1.verifySignature(sig, messageHash, pubkey);
setIsValid(valid);
console.log('Signature valid:', valid);
} catch (error) {
console.error('Verification failed:', error);
setIsValid(false);
}
};
return (
{isValid !== null && Valid: {isValid ? 'Yes' : 'No'} }
);
}
```
## Next Steps
# Account Abstraction Integrations
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/account-abstraction
import { Card } from '/snippets/v3/components/ui/card.mdx';
import SmartAccountOverview from '/snippets/v3/aa/smart-account-overview-rn.mdx';
## Provider Guides
Select a provider below for installation and usage instructions.
Alchemy Account Kit provides modular smart accounts with built-in gas sponsorship via Alchemy's Gas Manager. Create and manage smart accounts, submit gasless transactions, and execute batched UserOperations — all without requiring users to leave your application. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create an [Alchemy account](https://dashboard.alchemy.com/signup) and obtain your **API key** and **Gas Policy ID** from the Alchemy dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-alchemy viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-alchemy viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-alchemy viem
```
### Usage
The simplest way to integrate Alchemy smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx AlchemySmartAccount.tsx
import { useAlchemySmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const ALCHEMY_API_KEY = process.env.EXPO_PUBLIC_ALCHEMY_API_KEY!;
const GAS_POLICY_ID = process.env.EXPO_PUBLIC_ALCHEMY_GAS_POLICY_ID!;
export function AlchemySmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// account client setup, and paymaster configuration internally.
// It re-creates the account automatically if any config value changes.
const { smartAccount, isLoading, error } = useAlchemySmartAccount({
apiKey: ALCHEMY_API_KEY,
chain: sepolia,
gasPolicyId: GAS_POLICY_ID, // enables gas sponsorship
mode: "4337", // or "7702" — see EIP comparison above
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
// All calls are bundled into a single on-chain transaction.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript alchemy-action.ts
import { createAlchemySmartAccount } from "@getpara/aa-alchemy";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createAlchemySmartAccount({
para,
apiKey: "YOUR_ALCHEMY_API_KEY",
chain: sepolia,
gasPolicyId: "YOUR_GAS_POLICY_ID",
mode: "4337", // or "7702"
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Alchemy requires chains from `@account-kit/infra` (e.g. `sepolia`, `baseSepolia`). Plain viem chains are automatically mapped if an Alchemy equivalent exists. For gasless transactions, set up a Gas Manager Policy in your [Alchemy Dashboard](https://dashboard.alchemy.com) and pass the policy ID as `gasPolicyId`.
[ZeroDev](https://docs.zerodev.app/) is an embedded AA wallet powering many smart accounts across EVM chains. Known for its extensive feature set including gas sponsorship, session keys, recovery, multisig, and chain abstraction. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a [ZeroDev account](https://dashboard.zerodev.app/) and obtain your **Project ID** from the ZeroDev dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-zerodev viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-zerodev viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-zerodev viem
```
### Usage
The simplest way to integrate ZeroDev smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx ZeroDevSmartAccount.tsx
import { useZeroDevSmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const ZERODEV_PROJECT_ID = process.env.EXPO_PUBLIC_ZERODEV_PROJECT_ID!;
export function ZeroDevSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Kernel account setup, and ECDSA validator configuration internally.
const { smartAccount, isLoading, error } = useZeroDevSmartAccount({
projectId: ZERODEV_PROJECT_ID,
chain: sepolia,
mode: "4337", // or "7702"
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript zerodev-action.ts
import { createZeroDevSmartAccount } from "@getpara/aa-zerodev";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createZeroDevSmartAccount({
para,
projectId: "YOUR_ZERODEV_PROJECT_ID",
chain: sepolia,
mode: "4337", // or "7702"
});
if (smartAccount) {
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
You can optionally pass `bundlerUrl` and `paymasterUrl` to use custom infrastructure instead of ZeroDev's defaults. For more on managing your ZeroDev project and RPC endpoints, see the [ZeroDev RPC documentation](https://docs.zerodev.app/).
[Pimlico](https://docs.pimlico.io/) provides flexible bundler and paymaster infrastructure for account abstraction. Built on the permissionless.js SDK, it supports a wide range of smart account types and any EVM chain. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a [Pimlico account](https://dashboard.pimlico.io/) and obtain your **API key** from the Pimlico dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-pimlico viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-pimlico viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-pimlico viem
```
### Usage
The simplest way to integrate Pimlico smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx PimlicoSmartAccount.tsx
import { usePimlicoSmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const PIMLICO_API_KEY = process.env.EXPO_PUBLIC_PIMLICO_API_KEY!;
export function PimlicoSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// simple account setup, and Pimlico paymaster configuration internally.
const { smartAccount, isLoading, error } = usePimlicoSmartAccount({
apiKey: PIMLICO_API_KEY,
chain: sepolia,
mode: "4337", // or "7702"
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript pimlico-action.ts
import { createPimlicoSmartAccount } from "@getpara/aa-pimlico";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createPimlicoSmartAccount({
para,
apiKey: "YOUR_PIMLICO_API_KEY",
chain: sepolia,
mode: "4337", // or "7702"
});
if (smartAccount) {
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
The Pimlico bundler/paymaster URL is automatically constructed from your API key and chain name. You can override it with a custom `rpcUrl`. Pimlico's permissionless.js also supports other account types (Safe, Kernel, Biconomy, SimpleAccount) — see the [permissionless.js tutorial](https://docs.pimlico.io/).
[Biconomy](https://docs.biconomy.io/) provides the Nexus smart account with cross-chain transaction orchestration via the MEE (Multi-chain Execution Environment). Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a [Biconomy account](https://dashboard.biconomy.io/) and obtain your **API key** from the Biconomy dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-biconomy viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-biconomy viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-biconomy viem
```
### Usage
The simplest way to integrate Biconomy smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx BiconomySmartAccount.tsx
import { useBiconomySmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const BICONOMY_API_KEY = process.env.EXPO_PUBLIC_BICONOMY_API_KEY!;
export function BiconomySmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Nexus account setup, and MEE client configuration internally.
const { smartAccount, isLoading, error } = useBiconomySmartAccount({
apiKey: BICONOMY_API_KEY,
chain: sepolia,
mode: "4337", // or "7702"
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript biconomy-action.ts
import { createBiconomySmartAccount } from "@getpara/aa-biconomy";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createBiconomySmartAccount({
para,
apiKey: "YOUR_BICONOMY_API_KEY",
chain: sepolia,
mode: "4337", // or "7702"
});
if (smartAccount) {
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Biconomy transactions are executed via the MEE (Multi-chain Execution Environment). You can optionally pass a custom `meeUrl` to use your own MEE node.
[Thirdweb](https://portal.thirdweb.com/) provides smart wallets with gas sponsorship and a broad ecosystem of tools. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a [Thirdweb account](https://thirdweb.com/dashboard) and obtain your **Client ID** from the Thirdweb dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-thirdweb viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-thirdweb viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-thirdweb viem
```
### Usage
The simplest way to integrate Thirdweb smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx ThirdwebSmartAccount.tsx
import { useThirdwebSmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const THIRDWEB_CLIENT_ID = process.env.EXPO_PUBLIC_THIRDWEB_CLIENT_ID!;
export function ThirdwebSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// smart wallet setup, and gas sponsorship configuration internally.
const { smartAccount, isLoading, error } = useThirdwebSmartAccount({
clientId: THIRDWEB_CLIENT_ID,
chain: sepolia,
sponsorGas: true, // enable gas sponsorship (default)
mode: "4337", // or "7702"
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript thirdweb-action.ts
import { createThirdwebSmartAccount } from "@getpara/aa-thirdweb";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createThirdwebSmartAccount({
para,
clientId: "YOUR_THIRDWEB_CLIENT_ID",
chain: sepolia,
sponsorGas: true,
mode: "4337", // or "7702"
});
if (smartAccount) {
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Set `sponsorGas: false` to disable gas sponsorship. In EIP-4337 mode, you can optionally provide `factoryAddress` and `accountAddress` for custom smart wallet deployments.
[Gelato](https://docs.gelato.network/) provides native EIP-7702 smart accounts with built-in gas sponsorship via Gelato's relay infrastructure. No separate paymaster configuration is needed.
### Setup
1. Create a [Gelato account](https://app.gelato.network/) and obtain your **API key** from the Gelato dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-gelato viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-gelato viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-gelato viem
```
### Usage
The simplest way to integrate Gelato smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx GelatoSmartAccount.tsx
import { useGelatoSmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const GELATO_API_KEY = process.env.EXPO_PUBLIC_GELATO_API_KEY!;
export function GelatoSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Gelato account setup, and 7702 delegation internally.
// Gelato only supports EIP-7702 mode.
const { smartAccount, isLoading, error } = useGelatoSmartAccount({
apiKey: GELATO_API_KEY,
chain: sepolia,
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript gelato-action.ts
import { createGelatoSmartAccount } from "@getpara/aa-gelato";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createGelatoSmartAccount({
para,
apiKey: "YOUR_GELATO_API_KEY",
chain: sepolia,
});
if (smartAccount) {
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Gelato only supports EIP-7702 mode. Gas sponsorship is built into Gelato's relay infrastructure — no separate paymaster configuration is needed.
[Porto](https://porto.sh) is a 7702-native relay with merchant-based gas sponsorship, supporting multiple chains including Base, Optimism, Arbitrum, and Ethereum mainnet.
### Setup
Porto requires no external API key. On testnets, gas is sponsored automatically. For mainnet, configure a merchant account in your Porto setup.
Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-porto viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-porto viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-porto viem
```
### Usage
The simplest way to integrate Porto smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx PortoSmartAccount.tsx
import { usePortoSmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { parseEther } from "viem";
import { base } from "viem/chains";
export function PortoSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation
// and Porto relay configuration internally.
// Testnets: gas is sponsored by default, no merchantUrl needed.
// Mainnet: merchantUrl is required for gas sponsorship.
const { smartAccount, isLoading, error } = usePortoSmartAccount({
chain: base,
merchantUrl: "/porto/merchant", // required for mainnet
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript porto-action.ts
import { createPortoSmartAccount } from "@getpara/aa-porto";
import { parseEther } from "viem";
import { base } from "viem/chains";
// `para` is your authenticated Para instance
const smartAccount = await createPortoSmartAccount({
para,
chain: base,
merchantUrl: "/porto/merchant", // required for mainnet
});
if (smartAccount) {
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Porto only supports EIP-7702 mode. The Porto relay supports multiple chains including Base, Optimism, Arbitrum, Ethereum, and several testnets. On testnets, gas fees are sponsored by default. For mainnet, a merchant account is required — pass `merchantUrl` to enable gas sponsorship.
[Safe](https://docs.safe.global/) provides battle-tested multi-signature smart accounts with modular extension support, deployed on hundreds of EVM chains. Uses Pimlico for bundler and paymaster infrastructure. Supports EIP-4337 mode only.
### Setup
1. Obtain a [Pimlico API key](https://dashboard.pimlico.io/) for bundler and paymaster infrastructure.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-safe viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-safe viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-safe viem
```
### Usage
The simplest way to integrate Safe smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx SafeSmartAccount.tsx
import { useSafeSmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const PIMLICO_API_KEY = process.env.EXPO_PUBLIC_PIMLICO_API_KEY!;
export function SafeSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Safe account setup, and Pimlico paymaster configuration internally.
// Safe only supports EIP-4337 mode.
const { smartAccount, isLoading, error } = useSafeSmartAccount({
pimlicoApiKey: PIMLICO_API_KEY,
chain: sepolia,
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript safe-action.ts
import { createSafeSmartAccount } from "@getpara/aa-safe";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createSafeSmartAccount({
para,
pimlicoApiKey: "YOUR_PIMLICO_API_KEY",
chain: sepolia,
});
if (smartAccount) {
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Safe only supports EIP-4337 mode. You can optionally pass `safeVersion` (default: `"1.4.1"`) and `saltNonce` for deterministic address generation.
[Rhinestone](https://docs.rhinestone.wtf/) enables cross-chain smart account orchestration with automatic bridging. Built on the modular account stack, it supports cross-chain token transfers and transactions from a single account. Supports EIP-4337 mode only.
### Setup
1. Obtain a [Rhinestone API key](https://dashboard.rhinestone.wtf/) and a [Pimlico API key](https://dashboard.pimlico.io/).
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-rhinestone viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-rhinestone viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-rhinestone viem
```
### Usage
The simplest way to integrate Rhinestone smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx RhinestoneSmartAccount.tsx
import { useRhinestoneSmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const RHINESTONE_API_KEY = process.env.EXPO_PUBLIC_RHINESTONE_API_KEY!;
const PIMLICO_API_KEY = process.env.EXPO_PUBLIC_PIMLICO_API_KEY!;
export function RhinestoneSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Rhinestone account setup, and orchestrator configuration internally.
// Rhinestone only supports EIP-4337 mode.
const { smartAccount, isLoading, error } = useRhinestoneSmartAccount({
chain: sepolia,
rhinestoneApiKey: RHINESTONE_API_KEY,
pimlicoApiKey: PIMLICO_API_KEY,
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript rhinestone-action.ts
import { createRhinestoneSmartAccount } from "@getpara/aa-rhinestone";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createRhinestoneSmartAccount({
para,
chain: sepolia,
rhinestoneApiKey: "YOUR_RHINESTONE_API_KEY",
pimlicoApiKey: "YOUR_PIMLICO_API_KEY",
});
if (smartAccount) {
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Rhinestone only supports EIP-4337 mode. Both `rhinestoneApiKey` and `pimlicoApiKey` are optional but recommended for production use. For advanced cross-chain use cases with automatic bridging, see the [Rhinestone documentation](https://docs.rhinestone.wtf/).
[Coinbase Developer Platform (CDP)](https://docs.cdp.coinbase.com/) provides Coinbase smart accounts on Base and Base Sepolia. Uses viem's built-in `toCoinbaseSmartAccount` with CDP's bundler and paymaster. Supports EIP-4337 mode only.
### Setup
1. Create a [Coinbase Developer Platform account](https://portal.cdp.coinbase.com/) and obtain your **RPC token**.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @getpara/aa-cdp viem
```
```bash yarn
yarn add @getpara/react-native-wallet @getpara/aa-cdp viem
```
```bash pnpm
pnpm add @getpara/react-native-wallet @getpara/aa-cdp viem
```
### Usage
The simplest way to integrate CDP smart accounts into your React Native application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx CDPSmartAccount.tsx
import { useCDPSmartAccount } from "@getpara/react-native-wallet";
import { useMutation } from "@tanstack/react-query";
import { View, Text, Button } from "react-native";
import { baseSepolia } from "viem/chains";
import { parseEther } from "viem";
const CDP_RPC_TOKEN = process.env.EXPO_PUBLIC_CDP_RPC_TOKEN!;
export function CDPSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Coinbase account setup, and CDP paymaster configuration internally.
// CDP only supports EIP-4337 mode on Base and Base Sepolia.
const { smartAccount, isLoading, error } = useCDPSmartAccount({
rpcToken: CDP_RPC_TOKEN,
chain: baseSepolia,
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account... ;
if (error) return Error: {error.message} ;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()}
disabled={isSending}
/>
{sendError && Send failed: {sendError.message} }
sendBatch()}
disabled={isBatching}
/>
{batchError && Batch failed: {batchError.message} }
);
}
```
For use outside React components or when you need more control over initialization.
```typescript cdp-action.ts
import { createCDPSmartAccount } from "@getpara/aa-cdp";
import { baseSepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createCDPSmartAccount({
para,
rpcToken: "YOUR_CDP_RPC_TOKEN",
chain: baseSepolia,
});
if (smartAccount) {
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
CDP only supports EIP-4337 mode and is limited to Base and Base Sepolia chains. No additional bundler packages are needed — CDP uses viem's built-in `toCoinbaseSmartAccount`.
## Next Steps
# Configure RPC Endpoints
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/configure-rpc
import { Card } from '/snippets/v3/components/ui/card.mdx';
Configure custom RPC endpoints and chains for networks using Web3 libraries. This guide uses public testnet RPCs for demonstration and shows minimal setup with predefined and custom chains where applicable.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Configure Custom RPC Endpoints
```typescript
import { ethers } from "ethers";
import { ParaEthersSigner } from "@getpara/ethers-v6-integration";
const chainId = 11155111;
const rpcUrl = "https://ethereum-sepolia-rpc.publicnode.com";
const provider = new ethers.JsonRpcProvider(rpcUrl, chainId, {
staticNetwork: true
});
const signer = new ParaEthersSigner(para, provider);
```
```typescript
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
import { createParaViemClient, createParaViemAccount } from "@getpara/viem-v2-integration";
const chain = sepolia;
const transport = http("https://ethereum-sepolia-rpc.publicnode.com");
const publicClient = createPublicClient({ chain, transport });
const account = await createParaViemAccount(para);
const walletClient = createParaViemClient(para, { account, chain, transport });
```
For custom chains:
```typescript
const customChain = {
id: 99999,
name: "Custom Chain",
network: "custom",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: {
default: { http: ["https://custom-rpc.example.com"] },
},
};
const customTransport = http("https://custom-rpc.example.com");
const customPublicClient = createPublicClient({
chain: customChain,
transport: customTransport
});
const customAccount = await createParaViemAccount(para);
const customWalletClient = createParaViemClient(para, {
account: customAccount,
chain: customChain,
transport: customTransport
});
```
## Next Steps
# Estimate Gas for Transactions
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/estimate-gas
import { Card } from '/snippets/v3/components/ui/card.mdx';
Accurately estimate gas costs for transactions to ensure reliable execution without overpaying.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Estimate Gas for a Transaction
```typescript
import { ethers } from "ethers";
async function estimateGas(
provider: ethers.Provider,
to: string,
value: string,
data?: string,
maxFeePerGas?: string,
maxPriorityFeePerGas?: string
) {
const tx = {
to,
value: ethers.parseEther(value),
data: data || "0x",
...(maxFeePerGas ? { maxFeePerGas: ethers.parseUnits(maxFeePerGas, "gwei") } : {}),
...(maxPriorityFeePerGas ? { maxPriorityFeePerGas: ethers.parseUnits(maxPriorityFeePerGas, "gwei") } : {})
};
const gasEstimate = await provider.estimateGas(tx);
return gasEstimate;
}
```
```typescript
import { parseEther, parseGwei } from "viem";
async function estimateGas(
publicClient: PublicClient,
account: Account | `0x${string}`,
to: `0x${string}`,
value: string,
data?: `0x${string}`,
maxFeePerGas?: string,
maxPriorityFeePerGas?: string
) {
const gasEstimate = await publicClient.estimateGas({
account,
to,
value: parseEther(value),
data: data || "0x",
...(maxFeePerGas ? { maxFeePerGas: parseGwei(maxFeePerGas) } : {}),
...(maxPriorityFeePerGas ? { maxPriorityFeePerGas: parseGwei(maxPriorityFeePerGas) } : {})
});
return gasEstimate;
}
```
## Next Steps
# Execute Transactions
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/execute-transactions
import { Card } from '/snippets/v3/components/ui/card.mdx';
Execute complex transactions with custom data and manage their lifecycle using Para.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Execute Raw Transactions
```tsx
import { useParaEthersSigner, useParaEthersSendTransaction } from "@getpara/react-native-wallet/evm/ethers";
import { ethers, JsonRpcProvider } from "ethers";
import { View, Text, TouchableOpacity } from "react-native";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function ExecuteTransaction() {
const { ethersSigner } = useParaEthersSigner({ provider });
const { sendTransactionAsync, isPending, data: receipt } = useParaEthersSendTransaction(ethersSigner);
return (
sendTransactionAsync({
to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
data: "0x",
value: ethers.parseEther("0.01"),
})
}
disabled={isPending}
>
{isPending ? "Executing..." : "Execute Transaction"}
{receipt && Transaction: {receipt.hash} }
);
}
```
## Execute Contract Functions
```tsx
import { useParaEthersSigner, useParaEthersWriteContract } from "@getpara/react-native-wallet/evm/ethers";
import { JsonRpcProvider } from "ethers";
import { View, Text, TouchableOpacity } from "react-native";
const CONTRACT_ABI = [
"function setGreeting(string memory _greeting)",
"function greet() view returns (string)"
];
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function ContractInteraction({ contractAddress }: { contractAddress: string }) {
const { ethersSigner } = useParaEthersSigner({ provider });
const { writeContractAsync, isPending, data: receipt } = useParaEthersWriteContract(ethersSigner);
return (
writeContractAsync({
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "setGreeting",
args: ["Hello from Para!"],
})
}
disabled={isPending}
>
{isPending ? "Updating..." : "Update Greeting"}
{receipt && Transaction: {receipt.hash} }
);
}
```
## Execute Raw Transactions
```tsx
import { useParaViemClient, useParaViemSendTransaction } from "@getpara/react-native-wallet/evm/viem";
import { parseEther, http } from "viem";
import { sepolia } from "viem/chains";
import { View, Text, TouchableOpacity } from "react-native";
function ExecuteTransaction() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { sendTransactionAsync, isPending, data: hash } = useParaViemSendTransaction(viemClient);
return (
sendTransactionAsync({
to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
data: "0x",
value: parseEther("0.01"),
})
}
disabled={isPending}
>
{isPending ? "Executing..." : "Execute Transaction"}
{hash && Transaction: {hash} }
);
}
```
## Execute Contract Functions
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-native-wallet/evm/viem";
import { http } from "viem";
import { sepolia } from "viem/chains";
import { View, Text, TouchableOpacity } from "react-native";
const CONTRACT_ABI = [
{
name: "setGreeting",
type: "function",
stateMutability: "nonpayable",
inputs: [{ name: "_greeting", type: "string" }],
outputs: [],
},
] as const;
function ContractInteraction({
contractAddress,
}: {
contractAddress: `0x${string}`;
}) {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { writeContractAsync, isPending, data: hash } = useParaViemWriteContract(viemClient);
return (
writeContractAsync({
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "setGreeting",
args: ["Hello from Para!"],
})
}
disabled={isPending}
>
{isPending ? "Updating..." : "Update Greeting"}
{hash && Transaction: {hash} }
);
}
```
## Next Steps
# Fund Testnet Wallet
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/fund-testnet-wallet
Request testnet tokens directly through Para without relying on external faucets. The `useRequestFaucet` hook handles the request and returns the transaction details once tokens are sent.
Use this guide before running Sepolia examples that send ETH, transfer ERC-20 tokens, or write to contracts. The faucet sends testnet ETH to a Para EVM wallet so your first integration transactions have gas.
## Fund the Active Wallet
The simplest usage funds whichever wallet is currently active. This works well right after wallet creation:
```tsx
import { useRequestFaucet, useCreateWallet } from "@getpara/react-native-wallet";
function CreateAndFund() {
const { createWalletAsync } = useCreateWallet();
const { requestFaucetAsync, isPending } = useRequestFaucet();
const [txHash, setTxHash] = useState("");
const handleCreateAndFund = async () => {
await createWalletAsync({ type: "EVM" });
const result = await requestFaucetAsync();
setTxHash(result.transactionHash);
};
return (
{txHash && Transaction: {txHash} }
);
}
```
Calling `requestFaucetAsync()` without a `walletId` requires an active wallet. If no active wallet is set and no `walletId` is passed, the hook throws an error.
## Fund a Specific Wallet
Pass an explicit `walletId` to target a particular wallet:
```tsx
import { useRequestFaucet } from "@getpara/react-native-wallet";
function FundWallet({ walletId }: { walletId: string }) {
const { requestFaucetAsync, isPending, data } = useRequestFaucet();
return (
requestFaucetAsync({ walletId })}
disabled={isPending}
title={isPending ? "Requesting..." : "Get Testnet ETH"}
/>
{data && (
Sent {data.amount} ETH to {data.address}
)}
);
}
```
## Direct SDK Method
For non-hook flows, call `requestFaucet` on your Para client after you have the wallet ID:
```ts
const result = await para.requestFaucet({
walletId,
chain: "ETHEREUM_SEPOLIA",
});
console.log(result.transactionHash);
```
Omit `chain` to use the default Ethereum Sepolia faucet. Wait for the returned `transactionHash` to confirm before assuming the funds are spendable.
## Supported Chains
| Chain | Identifier | Token |
| ------------------ | ------------------- | ----- |
| Ethereum Sepolia | `ETHEREUM_SEPOLIA` | ETH |
The faucet is rate limited to 10 requests per API key per day. Each wallet has a 24-hour cooldown between faucet requests.
## Error Handling
The hook surfaces errors through the standard `error` field on the mutation result. Common error scenarios:
| Status | Cause |
| ------ | ------------------------------------------------------------------------------- |
| `400` | Invalid request (missing walletId, unsupported chain, or no address) |
| `403` | Missing or invalid API key |
| `404` | Wallet not found (also returned when the wallet belongs to a different API key) |
| `429` | Rate limit exceeded or wallet cooldown active |
| `500` | Faucet transaction failed |
A 429 response includes a `Retry-After` header indicating when the next request will be accepted.
# Get Transaction Receipt
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/get-transaction-receipt
import { Card } from '/snippets/v3/components/ui/card.mdx';
After sending a transaction, you need to monitor its status and retrieve the receipt to confirm its execution.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Get Transaction Receipt
```typescript
import { ethers } from "ethers";
async function getTransactionReceipt(
provider: ethers.Provider,
txHash: string
) {
const receipt = await provider.getTransactionReceipt(txHash);
console.log("Transaction Receipt:", receipt);
return receipt;
}
async function waitForTransaction(provider: ethers.Provider, txHash: string) {
const receipt = await provider.waitForTransaction(txHash);
console.log("Transaction Confirmed:", receipt);
return receipt;
}
```
```typescript
async function getTransactionReceipt(
publicClient: any,
txHash: `0x${string}`
) {
const receipt = await publicClient.getTransactionReceipt({
hash: txHash,
});
console.log("Transaction Receipt:", receipt);
return receipt;
}
async function waitForTransactionReceipt(
publicClient: any,
txHash: `0x${string}`
) {
const receipt = await publicClient.waitForTransactionReceipt({
hash: txHash,
});
console.log("Transaction Confirmed:", receipt);
return receipt;
}
```
## Next Steps
# Interact with Contracts
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/interact-with-contracts
import { Card } from '/snippets/v3/components/ui/card.mdx';
Read data from smart contracts and execute write operations using Para's wallet infrastructure.
Sepolia examples need testnet ETH before writing contracts. Get `requestFaucetAsync` from `useRequestFaucet()` and call `requestFaucetAsync({ chain: "ETHEREUM_SEPOLIA" })` after the user has an EVM wallet. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react-native/guides/web3-operations/evm/fund-testnet-wallet) for the full example.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Read Contract Data
```typescript
import { ethers } from "ethers";
const CONTRACT_ABI = [
"function balanceOf(address owner) view returns (uint256)",
"function totalSupply() view returns (uint256)",
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)"
];
async function readContractData(
provider: ethers.Provider,
contractAddress: string,
userAddress: string
) {
const contract = new ethers.Contract(
contractAddress,
CONTRACT_ABI,
provider
);
const [balance, totalSupply, name, symbol, decimals] = await Promise.all([
contract.balanceOf(userAddress),
contract.totalSupply(),
contract.name(),
contract.symbol(),
contract.decimals()
]);
return {
balance: ethers.formatUnits(balance, decimals),
totalSupply: ethers.formatUnits(totalSupply, decimals),
name,
symbol,
decimals
};
}
```
## Write Contract Data
```typescript
const STAKING_ABI = [
"function stake(uint256 amount) payable",
"function unstake(uint256 amount)",
"function getStakedBalance(address user) view returns (uint256)",
"event Staked(address indexed user, uint256 amount)",
"event Unstaked(address indexed user, uint256 amount)"
];
async function stakeTokens(
signer: any,
contractAddress: string,
amount: string,
decimals: number
) {
const contract = new ethers.Contract(contractAddress, STAKING_ABI, signer);
const parsedAmount = ethers.parseUnits(amount, decimals);
const tx = await contract.stake(parsedAmount);
await tx.wait();
const newBalance = await contract.getStakedBalance(await signer.getAddress());
return {
hash: tx.hash,
stakedAmount: ethers.formatUnits(newBalance, decimals)
};
}
```
## Read Contract Data
Read operations don't need mutation helpers -- use the Viem `publicClient` directly:
```typescript
import { createPublicClient, http, formatUnits } from "viem";
import { sepolia } from "viem/chains";
const CONTRACT_ABI = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "owner", type: "address" }],
outputs: [{ type: "uint256" }],
},
{
name: "totalSupply",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "uint256" }],
},
{
name: "name",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }],
},
{
name: "symbol",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }],
},
{
name: "decimals",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "uint8" }],
},
] as const;
const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
});
async function readContractData(
contractAddress: `0x${string}`,
userAddress: `0x${string}`
) {
const results = await publicClient.multicall({
contracts: [
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "balanceOf",
args: [userAddress],
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "totalSupply",
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "name",
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "symbol",
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "decimals",
},
],
});
const [balance, totalSupply, name, symbol, decimals] = results.map(
(r) => r.result
);
return {
balance: formatUnits(balance, decimals),
totalSupply: formatUnits(totalSupply, decimals),
name,
symbol,
decimals,
};
}
```
## Write Contract Data
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-native-wallet/evm/viem";
import { parseUnits, http } from "viem";
import { sepolia } from "viem/chains";
import { View, Text, TouchableOpacity } from "react-native";
const STAKING_ABI = [
{
name: "stake",
type: "function",
stateMutability: "payable",
inputs: [{ name: "amount", type: "uint256" }],
outputs: [],
},
] as const;
function StakeTokens({
contractAddress,
decimals = 18,
}: {
contractAddress: `0x${string}`;
decimals?: number;
}) {
const amount = "100";
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { writeContractAsync, isPending, data: hash } = useParaViemWriteContract(viemClient);
return (
writeContractAsync({
address: contractAddress,
abi: STAKING_ABI,
functionName: "stake",
args: [parseUnits(amount, decimals)],
})
}
disabled={isPending}
>
{isPending ? "Staking..." : `Stake ${amount} Tokens`}
{hash && Transaction: {hash} }
);
}
```
## Next Steps
# Manage Token Allowances
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/manage-allowances
import { Card } from '/snippets/v3/components/ui/card.mdx';
Manage ERC-20 token allowances to allow smart contracts to spend tokens on behalf of a user.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Manage Token Allowances
```typescript
import { ethers } from "ethers";
const ERC20_ABI = [
"function allowance(address owner, address spender) view returns (uint256)",
"function approve(address spender, uint256 amount) returns (bool)",
];
async function checkAllowance(
provider: ethers.Provider,
tokenAddress: string,
owner: string,
spender: string
) {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
const allowance = await contract.allowance(owner, spender);
return allowance;
}
async function approveToken(
signer: ethers.Signer,
tokenAddress: string,
spender: string,
amount: string
) {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
const tx = await contract.approve(spender, ethers.parseEther(amount));
await tx.wait();
return tx;
}
```
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-native-wallet/evm/viem";
import { parseEther, http } from "viem";
import { sepolia } from "viem/chains";
import { View, Text, TouchableOpacity } from "react-native";
const ERC20_ABI = [
{
name: "approve",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ type: "bool" }],
},
] as const;
function TokenAllowance({
tokenAddress,
spender,
}: {
tokenAddress: `0x${string}`;
spender: `0x${string}`;
}) {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { writeContractAsync, isPending, data: hash } = useParaViemWriteContract(viemClient);
return (
writeContractAsync({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "approve",
args: [spender, parseEther("100")],
})
}
>
{isPending ? "Approving..." : "Approve 100 Tokens"}
{hash && Transaction: {hash} }
);
}
```
## Next Steps
# Query Wallet Balances
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/query-balances
import { Card } from '/snippets/v3/components/ui/card.mdx';
Query ETH and ERC-20 token balances for Para wallets across different networks.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Query ETH Balance
```typescript
import { ethers } from "ethers";
import { ParaEthersSigner } from "@getpara/ethers-v6-integration";
async function getETHBalance(signer: ParaEthersSigner, provider: ethers.Provider) {
const address = await signer.getAddress();
const balance = await provider.getBalance(address);
const formattedBalance = ethers.formatEther(balance);
console.log(`ETH Balance: ${formattedBalance} ETH`);
return {
wei: balance.toString(),
ether: formattedBalance
};
}
```
## Query ERC-20 Token Balance
```typescript
import { ethers } from "ethers";
const ERC20_ABI = [
"function balanceOf(address owner) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)"
];
async function getTokenBalance(
signer: any,
tokenAddress: string,
provider: ethers.Provider
) {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
const address = await signer.getAddress();
const [balance, decimals, symbol] = await Promise.all([
contract.balanceOf(address),
contract.decimals(),
contract.symbol()
]);
const formattedBalance = ethers.formatUnits(balance, decimals);
console.log(`${symbol} Balance: ${formattedBalance}`);
return {
raw: balance.toString(),
formatted: formattedBalance,
symbol,
decimals
};
}
```
## Query ETH Balance
```typescript
import { formatEther } from "viem";
import { createParaViemAccount } from "@getpara/viem-v2-integration";
async function getETHBalance(publicClient: any, para: any) {
const account = await createParaViemAccount(para);
const balance = await publicClient.getBalance({
address: account.address
});
const formattedBalance = formatEther(balance);
console.log(`ETH Balance: ${formattedBalance} ETH`);
return {
wei: balance.toString(),
ether: formattedBalance
};
}
```
## Query ERC-20 Token Balance
```typescript
import { formatUnits } from "viem";
const ERC20_ABI = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "owner", type: "address" }],
outputs: [{ type: "uint256" }]
},
{
name: "decimals",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "uint8" }]
},
{
name: "symbol",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }]
}
] as const;
async function getTokenBalance(
publicClient: any,
account: any,
tokenAddress: `0x${string}`
) {
const [balance, decimals, symbol] = await Promise.all([
publicClient.readContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "balanceOf",
args: [account.address]
}),
publicClient.readContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "decimals"
}),
publicClient.readContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "symbol"
})
]);
const formattedBalance = formatUnits(balance, decimals);
console.log(`${symbol} Balance: ${formattedBalance}`);
return {
raw: balance.toString(),
formatted: formattedBalance,
symbol,
decimals
};
}
```
## Next Steps
# Send Tokens
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/send-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
Send ETH and ERC-20 token transfers securely using Para's wallet infrastructure.
Sepolia examples need testnet ETH before sending transactions. Get `requestFaucetAsync` from `useRequestFaucet()` and call `requestFaucetAsync({ chain: "ETHEREUM_SEPOLIA" })` after the user has an EVM wallet. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react-native/guides/web3-operations/evm/fund-testnet-wallet) for the full example.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Send ETH
```tsx
import { useParaEthersSigner, useParaEthersSendTransaction } from "@getpara/react-native-wallet/evm/ethers";
import { ethers, JsonRpcProvider } from "ethers";
import { View, Text, TouchableOpacity } from "react-native";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SendETH() {
const { ethersSigner } = useParaEthersSigner({ provider });
const { sendTransactionAsync, isPending, data: receipt } = useParaEthersSendTransaction(ethersSigner);
return (
sendTransactionAsync({
to: "0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
value: ethers.parseEther("0.01"),
})
}
disabled={isPending}
>
{isPending ? "Sending..." : "Send 0.01 ETH"}
{receipt && Transaction Hash: {receipt.hash} }
);
}
```
## Send ERC-20 Tokens
```tsx
import { useParaEthersSigner, useParaEthersWriteContract } from "@getpara/react-native-wallet/evm/ethers";
import { ethers, JsonRpcProvider } from "ethers";
import { Text, TouchableOpacity } from "react-native";
const ERC20_ABI = [
"function transfer(address to, uint256 amount) returns (bool)"
];
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SendToken({
tokenAddress,
decimals,
}: {
tokenAddress: string;
decimals: number;
}) {
const { ethersSigner } = useParaEthersSigner({ provider });
const { writeContractAsync, isPending, data: receipt } = useParaEthersWriteContract(ethersSigner);
return (
writeContractAsync({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "transfer",
args: [
"0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
ethers.parseUnits("10", decimals),
],
})
}
disabled={isPending}
>
{isPending ? "Sending..." : "Send 10 Tokens"}
);
}
```
## Send ETH
```tsx
import { useParaViemClient, useParaViemSendTransaction } from "@getpara/react-native-wallet/evm/viem";
import { parseEther, http } from "viem";
import { sepolia } from "viem/chains";
import { View, Text, TouchableOpacity } from "react-native";
function SendETH() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { sendTransactionAsync, isPending, data: hash } = useParaViemSendTransaction(viemClient);
return (
sendTransactionAsync({
to: "0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
value: parseEther("0.01"),
})
}
disabled={isPending}
>
{isPending ? "Sending..." : "Send 0.01 ETH"}
{hash && Transaction Hash: {hash} }
);
}
```
## Send ERC-20 Tokens
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-native-wallet/evm/viem";
import { parseUnits, http } from "viem";
import { sepolia } from "viem/chains";
import { Text, TouchableOpacity } from "react-native";
const ERC20_ABI = [
{
name: "transfer",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ type: "bool" }],
},
] as const;
function SendToken({
tokenAddress,
decimals,
}: {
tokenAddress: `0x${string}`;
decimals: number;
}) {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { writeContractAsync, isPending, data: hash } = useParaViemWriteContract(viemClient);
return (
writeContractAsync({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "transfer",
args: [
"0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
parseUnits("10", decimals),
],
})
}
disabled={isPending}
>
{isPending ? "Sending..." : "Send 10 Tokens"}
);
}
```
## Next Steps
# Setup Web3 Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/setup-libraries
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Learn how to set up popular Web3 libraries with Para. Choose your library below.
## Prerequisites
Before setting up Web3 libraries, you need an authenticated Para session.
Sepolia examples need testnet ETH before sending transactions or writing contracts. Get `requestFaucetAsync` from `useRequestFaucet()` and call `requestFaucetAsync({ chain: "ETHEREUM_SEPOLIA" })` after the user has an EVM wallet. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react-native/guides/web3-operations/evm/fund-testnet-wallet) for the full example.
## Install
```bash
npm install @getpara/react-native-wallet @getpara/ethers-v6-integration ethers
```
`@getpara/ethers-v6-integration` is a separate package — install it alongside `@getpara/react-native-wallet`.
## Usage
Use the hook to create an ethers signer for your user's Para embedded wallet or external wallet. For convenience, the and hooks wrap common signer methods in a React Query mutation.
```tsx
import { useParaEthersSigner, useParaEthersSignMessage } from "@getpara/react-native-wallet/evm/ethers";
import { JsonRpcProvider } from "ethers";
import { Text, TouchableOpacity } from "react-native";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignWithEthers() {
const { ethersSigner, isLoading } = useParaEthersSigner({ provider });
const { signMessageAsync, isPending } = useParaEthersSignMessage(ethersSigner);
const handleSign = async () => {
const signature = await signMessageAsync("Hello from Para!");
console.log("Signature:", signature);
};
if (isLoading) return Loading... ;
return (
{isPending ? "Signing..." : "Sign Message"}
);
}
```
### Wallet Resolution
When no `address` or `walletId` is passed, the hook resolves the wallet in this order:
1. **Selected wallet** — if the user selected an EVM wallet in the UI. If there is only one EVM wallet in the session, it is already selected by default
2. **First EVM wallet** — the first available EVM wallet on the account
To target a specific wallet, pass `address` or `walletId`:
```tsx
const { ethersSigner } = useParaEthersSigner({
provider,
address: "0x1234...", // or walletId: "uuid-..."
});
```
Use `createParaEthersSigner` to create a signer directly.
```typescript
import { createParaEthersSigner } from "@getpara/ethers-v6-integration";
import { ethers } from "ethers";
import Para from "@getpara/core-sdk";
const para = new Para("YOUR_API_KEY");
const provider = new ethers.JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
// Authenticate first...
const signer = createParaEthersSigner({ para, provider });
const signature = await signer.signMessage("Hello from Para!");
```
### Wallet Resolution
When no `address` or `walletId` is passed, the factory picks the first available EVM wallet.
To target a specific wallet:
```typescript
const signer = createParaEthersSigner({
para,
provider,
address: "0x1234...", // looks up by address
// or walletId: "uuid-...", // looks up by ID
});
```
## Install
```bash
npm install @getpara/react-native-wallet @getpara/viem-v2-integration viem
```
`@getpara/viem-v2-integration` is included as a dependency of `@getpara/react-native-wallet`, but you should install it explicitly to ensure version alignment.
## Usage
Use the hook to create a viem `WalletClient` for your user's Para embedded wallet or external wallet. For convenience, the , , , and hooks wrap common client methods in a React Query mutation.
```tsx
import { useParaViemClient, useParaViemSignMessage } from "@getpara/react-native-wallet/evm/viem";
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
import { Text, TouchableOpacity } from "react-native";
const publicClient = createPublicClient({
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
});
function SignWithViem() {
const { viemClient, isLoading } = useParaViemClient({
walletClientConfig: {
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
},
});
const { signMessageAsync, isPending } = useParaViemSignMessage(viemClient);
const handleSign = async () => {
const signature = await signMessageAsync({ message: "Hello from Para!" });
console.log("Signature:", signature);
};
if (isLoading) return Loading... ;
return (
{isPending ? "Signing..." : "Sign Message"}
);
}
```
### Wallet Resolution
When no `address` or `walletId` is passed, the hook resolves the wallet in this order:
1. **Selected wallet** — if the user selected an EVM wallet in the UI. If there is only one EVM wallet in the session, it is already selected by default
2. **First EVM wallet** — the first available EVM wallet on the account
To target a specific wallet:
```tsx
const { viemClient } = useParaViemClient({
address: "0x1234...", // or walletId: "uuid-..."
walletClientConfig: { chain: sepolia, transport: http() },
});
```
Use `createParaViemAccount` and `createParaViemClient` to create a wallet client directly.
```typescript
import { createParaViemAccount, createParaViemClient } from "@getpara/viem-v2-integration";
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
import Para from "@getpara/core-sdk";
const para = new Para("YOUR_API_KEY");
// Authenticate first...
const account = createParaViemAccount({ para });
const walletClient = createParaViemClient({ para, walletClientConfig: {
account,
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
}});
const signature = await walletClient.signMessage({ message: "Hello from Para!" });
```
### Wallet Resolution
When no `address` or `walletId` is passed, the factory picks the first available EVM wallet.
To target a specific wallet:
```typescript
const account = createParaViemAccount({
para,
address: "0x1234...", // looks up by address
// or walletId: "uuid-...", // looks up by ID
});
```
## Next Steps
# Sign Messages
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/sign-messages
import { Card } from '/snippets/v3/components/ui/card.mdx';
Sign plain text messages using Para's secure signing infrastructure.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Sign Personal Messages
```tsx
import { useParaEthersSigner, useParaEthersSignMessage } from "@getpara/react-native-wallet/evm/ethers";
import { ethers } from "ethers";
import { JsonRpcProvider } from "ethers";
import { View, Text, TouchableOpacity } from "react-native";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignMessage() {
const { ethersSigner } = useParaEthersSigner({ provider });
const { signMessageAsync, isPending, data: signature } = useParaEthersSignMessage(ethersSigner);
return (
signMessageAsync("Hello from Para!")}
>
{isPending ? "Signing..." : "Sign Message"}
{signature && Signature: {signature.slice(0, 20)}... }
);
}
```
## Sign Structured Messages
```tsx
import { useParaEthersSigner, useParaEthersSignMessage } from "@getpara/react-native-wallet/evm/ethers";
import { JsonRpcProvider } from "ethers";
import { View, Text, TouchableOpacity } from "react-native";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignStructuredMessage() {
const { ethersSigner } = useParaEthersSigner({ provider });
const { signMessageAsync, isPending, data: signature } = useParaEthersSignMessage(ethersSigner);
const handleSign = () => {
const data = {
action: "authenticate",
timestamp: Date.now(),
nonce: Math.random().toString(36).substring(7),
};
signMessageAsync(JSON.stringify(data, null, 2));
};
return (
{isPending ? "Signing..." : "Sign Structured Data"}
{signature && Signature: {signature.slice(0, 30)}... }
);
}
```
## Sign Personal Messages
```tsx
import { useParaViemClient, useParaViemSignMessage } from "@getpara/react-native-wallet/evm/viem";
import { http } from "viem";
import { sepolia } from "viem/chains";
import { View, Text, TouchableOpacity } from "react-native";
function SignMessage() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { signMessageAsync, isPending, data: signature } = useParaViemSignMessage(viemClient);
return (
signMessageAsync({ message: "Hello from Para!" })}
>
{isPending ? "Signing..." : "Sign Message"}
{signature && Signature: {signature.slice(0, 20)}... }
);
}
```
## Sign Structured Messages
```tsx
import { useParaViemClient, useParaViemSignMessage } from "@getpara/react-native-wallet/evm/viem";
import { http } from "viem";
import { sepolia } from "viem/chains";
import { View, Text, TouchableOpacity } from "react-native";
function SignStructuredMessage() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { signMessageAsync, isPending, data: signature } = useParaViemSignMessage(viemClient);
const handleSign = () => {
const data = {
action: "authenticate",
timestamp: Date.now(),
nonce: Math.random().toString(36).substring(7),
};
signMessageAsync({ message: JSON.stringify(data, null, 2) });
};
return (
{isPending ? "Signing..." : "Sign Structured Data"}
{signature && Signature: {signature.slice(0, 30)}... }
);
}
```
## Next Steps
# Sign Typed Data (EIP-712)
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/sign-typed-data
import { Card } from '/snippets/v3/components/ui/card.mdx';
Sign structured data according to the EIP-712 standard, which provides a more readable and secure signing experience for users.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Sign Typed Data
```tsx
import { useParaEthersSigner, useParaEthersSignTypedData } from "@getpara/react-native-wallet/evm/ethers";
import { JsonRpcProvider } from "ethers";
import { View, Text, TouchableOpacity } from "react-native";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignTypedData({
domain,
types,
value,
}: {
domain: any;
types: any;
value: any;
}) {
const { ethersSigner } = useParaEthersSigner({ provider });
const { signTypedDataAsync, isPending, data: signature } = useParaEthersSignTypedData(ethersSigner);
return (
signTypedDataAsync({ domain, types, value })}
>
{isPending ? "Signing..." : "Sign Typed Data"}
{signature && Signature: {signature} }
);
}
```
```tsx
import { useParaViemClient, useParaViemSignTypedData } from "@getpara/react-native-wallet/evm/viem";
import { http } from "viem";
import { sepolia } from "viem/chains";
import { View, Text, TouchableOpacity } from "react-native";
function SignTypedData({
domain,
types,
primaryType,
message,
}: {
domain: any;
types: any;
primaryType: string;
message: any;
}) {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { signTypedDataAsync, isPending, data: signature } = useParaViemSignTypedData(viemClient);
return (
signTypedDataAsync({ domain, types, primaryType, message })}
>
{isPending ? "Signing..." : "Sign Typed Data"}
{signature && Signature: {signature} }
);
}
```
## Next Steps
# Verify Signatures with EVM Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/verify-signatures
import { Card } from '/snippets/v3/components/ui/card.mdx';
Verify the authenticity of signed messages and typed data to ensure they originated from the expected address.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Verify Personal Signatures
```typescript
import { ethers } from "ethers";
async function verifyPersonalSignature(
message: string,
signature: string,
signerAddress: string
) {
const recoveredAddress = ethers.verifyMessage(message, signature);
const isValid = recoveredAddress.toLowerCase() === signerAddress.toLowerCase();
console.log("Signature valid:", isValid);
return isValid;
}
```
## Verify Typed Data Signatures (EIP-712)
```typescript
import { ethers } from "ethers";
async function verifyTypedDataSignature(
domain: any,
types: any,
value: any,
signature: string,
signerAddress: string
) {
const recoveredAddress = ethers.verifyTypedData(domain, types, value, signature);
const isValid = recoveredAddress.toLowerCase() === signerAddress.toLowerCase();
console.log("Signature valid:", isValid);
return isValid;
}
```
## Verify Personal Signatures
```typescript
import { verifyMessage } from "viem";
async function verifyPersonalSignature(
address: `0x${string}`,
message: string,
signature: `0x${string}`
) {
const isValid = await verifyMessage({
address,
message,
signature,
});
console.log("Signature valid:", isValid);
return isValid;
}
```
## Verify Typed Data Signatures (EIP-712)
```typescript
import { verifyTypedData } from "viem";
async function verifyTypedDataSignature(
address: `0x${string}`,
domain: any,
types: any,
primaryType: string,
message: any,
signature: `0x${string}`
) {
const isValid = await verifyTypedData({
address,
domain,
types,
primaryType,
message,
signature,
});
console.log("Signature valid:", isValid);
return isValid;
}
```
## Next Steps
# Watch Contract Events
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/evm/watch-events
import { Card } from '/snippets/v3/components/ui/card.mdx';
Subscribe to and filter smart contract events to react to on-chain activity in real-time.
Wagmi is not available on React Native. Use Ethers.js or Viem for EVM operations.
## Watch Contract Events
```typescript
import { ethers } from "ethers";
const ERC20_ABI = [
"event Transfer(address indexed from, address indexed to, uint256 value)"
];
async function watchTransferEvents(
provider: ethers.Provider,
tokenAddress: string
) {
const contract = new ethers.Contract(
tokenAddress,
ERC20_ABI,
provider
);
contract.on("Transfer", (from, to, value, event) => {
console.log(`Transfer Event: ${from} -> ${to}, Value: ${ethers.formatUnits(value, 18)}`);
console.log("Event details:", event);
});
console.log(`Listening for Transfer events on ${tokenAddress}...`);
}
async function stopWatchingTransferEvents(
provider: ethers.Provider,
tokenAddress: string
) {
const contract = new ethers.Contract(
tokenAddress,
ERC20_ABI,
provider
);
contract.off("Transfer");
console.log(`Stopped listening for Transfer events on ${tokenAddress}.`);
}
```
```typescript
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
const ERC20_ABI = [
{
anonymous: false,
inputs: [
{ indexed: true, name: "from", type: "address" },
{ indexed: true, name: "to", type: "address" },
{ indexed: false, name: "value", type: "uint256" },
],
name: "Transfer",
type: "event",
},
] as const;
async function watchTransferEvents(
publicClient: any,
tokenAddress: `0x${string}`
) {
const unwatch = publicClient.watchContractEvent({
address: tokenAddress,
abi: ERC20_ABI,
eventName: "Transfer",
onLogs: (logs) => {
for (const log of logs) {
console.log(`Transfer Event: ${log.args.from} -> ${log.args.to}, Value: ${log.args.value}`);
console.log("Log details:", log);
}
},
});
console.log(`Listening for Transfer events on ${tokenAddress}...`);
return unwatch; // Return the unwatch function to stop listening later
}
```
## Next Steps
# Get Wallet Data
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/get-wallet-address
import UseWallet from '/snippets/v3/definitions/hooks/useWallet.mdx';
import UseAccount from '/snippets/v3/definitions/hooks/useAccount.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
## Get Current Wallet
Use the `useWallet` hook to access the currently active wallet.
```tsx
import { useWallet } from "@getpara/react-native-wallet";
import { View, Text } from "react-native";
export default function CurrentWallet() {
const { data: wallet } = useWallet();
if (!wallet) return No wallet connected ;
return (
Address: {wallet.address}
Type: {wallet.scheme}
ID: {wallet.id}
);
}
```
## Get All Wallets
Use the `useAccount` hook to access all embedded wallets for the current user.
```tsx
import { useAccount } from "@getpara/react-native-wallet";
import { View, Text, FlatList } from "react-native";
export default function AllWallets() {
const { embedded } = useAccount();
const walletList = Object.values(embedded.wallets);
return (
w.id}
renderItem={({ item }) => (
{item.scheme}: {item.address}
)}
/>
);
}
```
## Filter Wallets by Type
Access wallets filtered by blockchain type using the Para client directly.
```tsx
import { useClient } from "@getpara/react-native-wallet";
export default function WalletsByType() {
const para = useClient();
const evmWallets = para.getWalletsByType("EVM");
const solanaWallets = para.getWalletsByType("SOLANA");
const cosmosWallets = para.getWalletsByType("COSMOS");
const stellarWallets = para.getWalletsByType("STELLAR");
}
```
## Next Steps
# Sign Messages with Para
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/sign-with-para
import UseSignMessage from '/snippets/v3/definitions/hooks/useSignMessage.mdx';
import SignWithParaWarning from '/snippets/v3/sign-with-para-warning.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
The `signMessage` method is a **low-level API** that signs raw bytes directly without any modifications. This is useful for verifying your Para integration with a simple "Hello, Para!" test after initial setup and authentication.
## Message Signing
This method signs the exact bytes you provide — perfect for initial "hello world" testing:
```tsx SignMessageExample.tsx
import { useSignMessage, useWallet } from "@getpara/react-native-wallet";
import { View, Button, Alert } from "react-native";
export default function SignMessageExample() {
const { signMessageAsync, isPending } = useSignMessage();
const { data: wallet } = useWallet();
const handleSign = async () => {
if (!wallet) return;
const message = "Hello, Para!";
const messageBase64 = btoa(message);
try {
const result = await signMessageAsync({
walletId: wallet.id,
messageBase64,
});
Alert.alert("Signature", `0x${result.signature}`);
} catch (err) {
console.error("Failed to sign:", err);
}
};
return (
);
}
```
## Next Steps
Now that you've verified your Para setup, explore chain-specific libraries for more advanced operations:
# Set Compute Units and Priority Fees
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/compute-units
import { Card } from '/snippets/v3/components/ui/card.mdx';
import ComputeUnitsSnippet from '/snippets/v3/web3/solana/compute-units.mdx';
Optimize your Solana transactions by setting compute unit limits and priority fees. This helps ensure transactions succeed during network congestion and controls execution costs.
## Next Steps
# Configure RPC Endpoints with Solana Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/configure-rpc
import { Card } from '/snippets/v3/components/ui/card.mdx';
import ConfigureRpcSnippet from '/snippets/v3/web3/solana/configure-rpc.mdx';
Configure custom RPC endpoints for Solana to optimize performance, use private nodes, or connect to different networks. This guide covers RPC setup for all supported libraries.
## Next Steps
# Execute Transactions with Solana Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/execute-transactions
import { Card } from '/snippets/v3/components/ui/card.mdx';
import ExecuteTransactionsSnippet from '/snippets/v3/web3/solana/execute-transactions.mdx';
Execute transactions on the Solana blockchain using Para's integrated signers. This includes signing and broadcasting transactions to the network.
## Next Steps
# Get Solana Transaction Status
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/get-transaction-status
import { Card } from '/snippets/v3/components/ui/card.mdx';
import GetTransactionStatusSnippet from '/snippets/v3/web3/solana/get-transaction-status.mdx';
Monitor transaction confirmation status and wait for finality on Solana. This guide covers checking transaction results and understanding commitment levels.
## Next Steps
# Interact with Solana Programs
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/interact-with-programs
import { Card } from '/snippets/v3/components/ui/card.mdx';
import InteractWithProgramsSnippet from '/snippets/v3/web3/solana/interact-with-programs.mdx';
Interact with Solana programs by calling instructions and working with Anchor IDLs. This guide covers manual instruction creation and Anchor's type-safe program interaction.
## Next Steps
# Manage SPL Token Accounts
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/manage-token-accounts
import { Card } from '/snippets/v3/components/ui/card.mdx';
import ManageTokenAccountsSnippet from '/snippets/v3/web3/solana/manage-token-accounts.mdx';
Create and manage SPL token accounts for holding tokens on Solana. This includes creating Associated Token Accounts (ATAs) and closing empty accounts to reclaim SOL.
## Next Steps
# Query Wallet Balances with Solana Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/query-balances
import { Card } from '/snippets/v3/components/ui/card.mdx';
import QueryBalancesSnippet from '/snippets/v3/web3/solana/query-balances.mdx';
Query SOL and SPL token balances for Para wallets using Solana libraries. This guide covers checking native SOL balances and SPL token balances.
## Next Steps
# Send Tokens with Solana Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/send-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
import SendTokensSnippet from '/snippets/v3/web3/solana/send-tokens.mdx';
Transfer SOL tokens between wallets using Para's integrated signers with different Solana libraries.
## Next Steps
# Setup Solana Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/setup-libraries
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para supports multiple Solana libraries. Choose your library below.
## Prerequisites
The modern Solana library with the `@solana/signers` interface. Recommended for new projects.
## Install
```bash
npm install @getpara/react-native-wallet @getpara/solana-signers-v2-integration @solana/kit
```
`@getpara/solana-signers-v2-integration` is a separate package — install it alongside `@getpara/react-native-wallet`.
## Usage
Use the hook to create a Solana signer for your user's Para embedded wallet or external wallet. The hook wraps transaction signing in a React Query mutation.
```tsx
import { useParaSolanaSigner } from "@getpara/react-native-wallet/solana";
import { createSolanaRpc } from "@solana/kit";
import { Text } from "react-native";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function SolanaExample() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
if (isLoading) return Loading... ;
return Address: {solanaSigner?.address} ;
}
```
### Wallet Resolution
When no `address` or `walletId` is passed, the hook resolves the wallet in this order:
1. **Selected wallet** — if the user selected a Solana wallet in the UI. If there is only one Solana wallet in the session, it is already selected by default
2. **First Solana wallet** — the first available Solana wallet on the account
To target a specific wallet:
```tsx
const { solanaSigner } = useParaSolanaSigner({ rpc, address: "SoLaNa..." });
```
Use `createParaSolanaSigner` to create a signer directly.
```typescript
import { createParaSolanaSigner } from "@getpara/solana-signers-v2-integration";
import { createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const signer = createParaSolanaSigner({ para, rpc });
console.log("Address:", signer.address);
```
### Wallet Resolution
When no `address` or `walletId` is passed, the factory picks the first available Solana wallet.
The legacy Solana Web3.js library. Use for compatibility with existing projects.
## Install
```bash
npm install @getpara/solana-web3.js-v1-integration @solana/web3.js
```
There is no built-in hook for `@solana/web3.js` — use the constructor directly or create a custom hook.
## Usage
```typescript
import { ParaSolanaWeb3Signer } from "@getpara/solana-web3.js-v1-integration";
import { Connection, clusterApiUrl } from "@solana/web3.js";
const connection = new Connection(clusterApiUrl("mainnet-beta"));
const signer = new ParaSolanaWeb3Signer(para, connection);
```
Use `useParaSolanaSigner` with [Codama](https://github.com/solana-program/codama)-generated clients for type-safe Anchor program interaction. No separate Anchor integration package needed — the signer from the @solana/kit tab works directly.
```typescript
import { useParaSolanaSigner } from "@getpara/react-native-wallet/solana";
import { createSolanaRpc } from "@solana/kit";
// Import from your Codama-generated client
import { getYourInstruction } from "./generated";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function MyComponent() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
// Use solanaSigner with Codama-generated instructions
// Same pattern as the @solana/kit tab
}
```
Generate your client: `npx codama idl path/to/your_program.json -o src/generated`
## Next Steps
# Sign Messages with Solana Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/sign-messages
import { Card } from '/snippets/v3/components/ui/card.mdx';
import SignMessagesSnippet from '/snippets/v3/web3/solana/sign-messages.mdx';
Sign messages to prove ownership of a Solana address without submitting a transaction. This is commonly used for authentication and verification purposes.
## Next Steps
# Verify Signatures with Solana Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/solana/verify-signatures
import { Card } from '/snippets/v3/components/ui/card.mdx';
import VerifySignaturesSnippet from '/snippets/v3/web3/solana/verify-signatures.mdx';
Verify Ed25519 signatures to confirm that a message was signed by a specific Solana address. Essential for authentication and ensuring data integrity.
## Next Steps
# Configure Horizon Endpoints with Stellar Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/stellar/configure-rpc
import { Card } from '/snippets/v3/components/ui/card.mdx';
Configure Horizon API endpoints for Stellar to connect to different networks, use custom nodes, or optimize performance. Stellar uses [Horizon](https://developers.stellar.org/docs/data/horizon) as its HTTP API layer instead of traditional RPC endpoints.
## Configure Horizon Server
```typescript
import { Horizon, Networks } from "@stellar/stellar-sdk";
// Public Horizon endpoints
const mainnetServer = new Horizon.Server("https://horizon.stellar.org");
const testnetServer = new Horizon.Server("https://horizon-testnet.stellar.org");
// Custom Horizon endpoint (e.g., self-hosted or third-party provider)
const customServer = new Horizon.Server("https://your-custom-horizon.example.com", {
allowHttp: false, // set to true only for local development
});
```
## Switch Networks
When switching between mainnet and testnet, update both the Horizon URL and the network passphrase used for signing:
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { Horizon, Networks } from "@stellar/stellar-sdk";
type StellarNetwork = "mainnet" | "testnet";
const NETWORK_CONFIG = {
mainnet: {
horizonUrl: "https://horizon.stellar.org",
networkPassphrase: Networks.PUBLIC,
},
testnet: {
horizonUrl: "https://horizon-testnet.stellar.org",
networkPassphrase: Networks.TESTNET,
},
} as const;
function useStellarNetwork(network: StellarNetwork) {
const config = NETWORK_CONFIG[network];
const { stellarSigner, isLoading } = useParaStellarSigner({
networkPassphrase: config.networkPassphrase,
});
const server = new Horizon.Server(config.horizonUrl);
return { server, stellarSigner, isLoading, networkPassphrase: config.networkPassphrase };
}
```
## Check Server Health
```typescript
import { Horizon } from "@stellar/stellar-sdk";
async function checkHorizonHealth() {
const server = new Horizon.Server("https://horizon.stellar.org");
// Get ledger info
const ledger = await server.ledgers().order("desc").limit(1).call();
const latestLedger = ledger.records[0];
console.log("Latest ledger:", latestLedger.sequence);
console.log("Closed at:", latestLedger.closed_at);
// Get fee stats
const feeStats = await server.feeStats();
console.log("Base fee:", feeStats.last_ledger_base_fee);
console.log("Fee charged (p50):", feeStats.fee_charged.p50);
}
```
## Next Steps
# Execute Transactions with Stellar Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/stellar/execute-transactions
import { Card } from '/snippets/v3/components/ui/card.mdx';
Build and sign complex Stellar transactions using Para's integrated signers. This includes multi-operation transactions, fee bumps, XDR signing, and Soroban smart contract authorization.
Combine multiple operations into a single atomic transaction:
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { TransactionBuilder, Operation, Asset, BASE_FEE, Horizon, Networks } from "@stellar/stellar-sdk";
import { Button } from "react-native";
const server = new Horizon.Server("https://horizon.stellar.org");
function MultiOpTransaction() {
const { stellarSigner } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const execute = async () => {
if (!stellarSigner) return;
const sourceAccount = await server.loadAccount(stellarSigner.address);
const transaction = new TransactionBuilder(sourceAccount, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.payment({
destination: "GRECIPI...",
asset: Asset.native(),
amount: "10",
})
)
.addOperation(
Operation.manageData({
name: "memo",
value: "payment-ref-123",
})
)
.setTimeout(180)
.build();
const { signedTxXdr } = await stellarSigner.signTransaction(transaction.toXDR());
const tx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Transaction hash:", result.hash);
};
return ;
}
```
Wrap an existing transaction with a higher fee to prioritize it during network congestion:
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { TransactionBuilder, Operation, Asset, Horizon, Networks } from "@stellar/stellar-sdk";
import { Button } from "react-native";
const server = new Horizon.Server("https://horizon.stellar.org");
function FeeBumpTransaction() {
const { stellarSigner } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const execute = async () => {
if (!stellarSigner) return;
const sourceAccount = await server.loadAccount(stellarSigner.address);
// Build the inner transaction with a low base fee
const innerTx = new TransactionBuilder(sourceAccount, {
fee: "100",
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.payment({
destination: "GRECIPI...",
asset: Asset.native(),
amount: "10",
})
)
.setTimeout(180)
.build();
// Sign the inner transaction
const { signedTxXdr: signedInnerXdr } = await stellarSigner.signTransaction(innerTx.toXDR());
const signedInner = TransactionBuilder.fromXDR(signedInnerXdr, Networks.PUBLIC);
// Wrap with a fee bump
const feeBumpTx = TransactionBuilder.buildFeeBumpTransaction(
stellarSigner.address,
"500", // higher fee
signedInner,
Networks.PUBLIC
);
// Sign the fee bump transaction
const { signedTxXdr } = await stellarSigner.signTransaction(feeBumpTx.toXDR());
const tx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Fee bump transaction hash:", result.hash);
};
return ;
}
```
Sign a pre-built transaction provided as an XDR string (e.g., from a server or dApp):
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { TransactionBuilder, Networks, Horizon } from "@stellar/stellar-sdk";
import { Button } from "react-native";
const server = new Horizon.Server("https://horizon.stellar.org");
function SignXDR() {
const { stellarSigner } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const signAndSubmit = async (xdrString: string) => {
if (!stellarSigner) return;
// Sign the XDR directly
const signedXdr = await stellarSigner.signTransactionXDR(
xdrString,
Networks.PUBLIC
);
// Submit to the network
const tx = TransactionBuilder.fromXDR(signedXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Transaction hash:", result.hash);
};
return signAndSubmit("AAAA...")} />;
}
```
Create a trustline to hold a custom asset (required before receiving non-XLM tokens):
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { TransactionBuilder, Operation, Asset, BASE_FEE, Horizon, Networks } from "@stellar/stellar-sdk";
import { Button } from "react-native";
const server = new Horizon.Server("https://horizon.stellar.org");
function ChangeTrust() {
const { stellarSigner } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const addTrustline = async (assetCode: string, issuer: string) => {
if (!stellarSigner) return;
const sourceAccount = await server.loadAccount(stellarSigner.address);
const asset = new Asset(assetCode, issuer);
const transaction = new TransactionBuilder(sourceAccount, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(Operation.changeTrust({ asset }))
.setTimeout(180)
.build();
const { signedTxXdr } = await stellarSigner.signTransaction(transaction.toXDR());
const tx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Trustline added:", result.hash);
};
return (
addTrustline(
"USDC",
"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
)
}
/>
);
}
```
Sign authorization entries for Soroban smart contract interactions:
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { Networks } from "@stellar/stellar-sdk";
import { Button } from "react-native";
function SorobanAuth() {
const { stellarSigner } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const signAuth = async (authEntryXdr: string) => {
if (!stellarSigner) return;
// Sign the authorization entry
const { signedAuthEntry, signerAddress } =
await stellarSigner.signAuthEntry(authEntryXdr);
console.log("Signed auth entry:", signedAuthEntry);
console.log("Signer:", signerAddress);
return signedAuthEntry;
};
return (
signAuth("AAAA...")} />
);
}
```
The `signAuthEntry` method is compatible with Stellar SDK's `contract.Client`, allowing Para wallets to authorize Soroban smart contract invocations. The auth entry XDR is typically provided by the contract client during simulation.
## Next Steps
# Query Wallet Balances with Stellar Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/stellar/query-balances
import { Card } from '/snippets/v3/components/ui/card.mdx';
Query XLM and token balances for your connected Para wallet or any Stellar address using the Horizon API.
## Query Balances
```typescript
import { useState } from "react";
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { Horizon, Networks } from "@stellar/stellar-sdk";
import { View, Text, Button } from "react-native";
const server = new Horizon.Server("https://horizon.stellar.org");
function BalanceDisplay() {
const { stellarSigner, isLoading } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const [balances, setBalances] = useState<{ asset: string; balance: string }[]>([]);
const queryBalances = async () => {
if (!stellarSigner) return;
const account = await server.loadAccount(stellarSigner.address);
const parsed = account.balances.map((b) => {
if (b.asset_type === "native") {
return { asset: "XLM", balance: b.balance };
}
return { asset: `${b.asset_code}:${b.asset_issuer}`, balance: b.balance };
});
setBalances(parsed);
console.log("Balances:", parsed);
};
if (isLoading) return Loading... ;
return (
Address: {stellarSigner?.address}
{balances.map((b) => (
{b.balance} {b.asset}
))}
);
}
```
## Query a Specific Asset
```typescript
import { Horizon } from "@stellar/stellar-sdk";
async function getAssetBalance(address: string, assetCode: string, assetIssuer: string) {
const server = new Horizon.Server("https://horizon.stellar.org");
const account = await server.loadAccount(address);
const match = account.balances.find(
(b) => b.asset_type !== "native" && b.asset_code === assetCode && b.asset_issuer === assetIssuer
);
return match ? match.balance : "0";
}
// Example: Check USDC balance
const usdcBalance = await getAssetBalance(
"GABCD...",
"USDC",
"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
);
```
A balance of `"0"` for a custom asset means the account has a trustline but no tokens. If `find` returns `undefined`, the account has no trustline for that asset and cannot receive it until one is created. See [Execute Transactions](/v3/react-native/guides/web3-operations/stellar/execute-transactions) for how to add trustlines.
## Next Steps
# Send Tokens with Stellar Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/stellar/send-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
Transfer XLM tokens between wallets using Para's Stellar signer with the Stellar SDK.
## Send XLM
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { TransactionBuilder, Operation, Asset, BASE_FEE, Horizon, Networks } from "@stellar/stellar-sdk";
import { View, Text, Button } from "react-native";
const server = new Horizon.Server("https://horizon.stellar.org");
function SendXLM() {
const { stellarSigner, isLoading } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const sendPayment = async (recipient: string, amount: string) => {
if (!stellarSigner) {
console.error("No signer available. Connect wallet first.");
return;
}
// Load the sender's account from the network
const sourceAccount = await server.loadAccount(stellarSigner.address);
// Build the payment transaction
const transaction = new TransactionBuilder(sourceAccount, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.payment({
destination: recipient,
asset: Asset.native(),
amount, // e.g. "10" for 10 XLM
})
)
.setTimeout(180)
.build();
// Sign the transaction with Para
const { signedTxXdr } = await stellarSigner.signTransaction(transaction.toXDR());
// Submit to the network
const tx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Transaction hash:", result.hash);
return result;
};
if (isLoading) return Loading... ;
return (
Address: {stellarSigner?.address}
sendPayment("GRECIPI...", "10")} />
);
}
```
## Send Custom Assets
To send a custom asset (like USDC on Stellar), replace `Asset.native()` with the specific asset:
```typescript
import { Asset } from "@stellar/stellar-sdk";
// Example: USDC on Stellar
const usdc = new Asset(
"USDC",
"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
);
// Use in your payment operation
Operation.payment({
destination: recipient,
asset: usdc,
amount: "100", // 100 USDC
});
```
The recipient must have a trustline for the custom asset before they can receive it. See [Execute Transactions](/v3/react-native/guides/web3-operations/stellar/execute-transactions) for how to create trustlines.
## Next Steps
# Setup Stellar Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/stellar/setup-libraries
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para supports Stellar blockchain interactions through the . Use our integration package to sign Stellar transactions with Para's MPC wallets.
## Installation
```bash npm
npm install @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --save-exact
```
```bash yarn
yarn add @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --exact
```
```bash pnpm
pnpm add @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --save-exact
```
```bash bun
bun add @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --exact
```
## Library Setup
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { Horizon, Networks } from "@stellar/stellar-sdk";
import { Text } from "react-native";
const server = new Horizon.Server("https://horizon.stellar.org");
function StellarExample() {
const { stellarSigner, isLoading } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
if (isLoading) return Loading... ;
console.log("Stellar address:", stellarSigner?.address);
return Address: {stellarSigner?.address} ;
}
```
```typescript
import { createParaStellarSigner } from "@getpara/stellar-sdk-v14-integration";
import { Horizon, Networks } from "@stellar/stellar-sdk";
import { para } from "./para";
const server = new Horizon.Server("https://horizon.stellar.org");
const signer = createParaStellarSigner({
para,
networkPassphrase: Networks.PUBLIC,
});
console.log("Stellar address:", signer.address);
```
To use the **Stellar testnet**, replace `Networks.PUBLIC` with `Networks.TESTNET` and use the testnet Horizon URL: `https://horizon-testnet.stellar.org`. You can fund testnet accounts using Stellar's [Friendbot](https://friendbot.stellar.org).
## Next Steps
# Sign Messages with Stellar Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/stellar/sign-messages
import { Card } from '/snippets/v3/components/ui/card.mdx';
Sign arbitrary bytes to prove ownership of a Stellar address without submitting a transaction. This is useful for authentication and off-chain verification.
## Sign Bytes
Use the `ParaStellarSigner` class API to sign arbitrary bytes directly:
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { Networks } from "@stellar/stellar-sdk";
import { View, Text, Button } from "react-native";
function SignMessage() {
const { stellarSigner, isLoading } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const signMessage = async () => {
if (!stellarSigner) return;
const message = "Hello, Stellar!";
const messageBytes = Buffer.from(new TextEncoder().encode(message));
const signature = await stellarSigner.signBytes(messageBytes);
console.log("Message:", message);
console.log("Signature:", signature.toString("hex"));
console.log("Signer:", stellarSigner.address);
};
if (isLoading) return Loading... ;
return ;
}
```
Stellar does not have a standardized message signing format like EIP-191 on Ethereum. The `signBytes` method signs raw bytes using Ed25519. Both parties must agree on how to encode and hash the message before signing.
## Next Steps
# Verify Signatures with Stellar Libraries
Source: https://docs.getpara.com/v3/react-native/guides/web3-operations/stellar/verify-signatures
import { Card } from '/snippets/v3/components/ui/card.mdx';
Verify Ed25519 signatures to confirm that a message was signed by a specific Stellar address. Essential for authentication and ensuring data integrity.
## Verify Signatures
```typescript
import { useParaStellarSigner } from "@getpara/react-native-wallet/stellar";
import { Networks, StrKey } from "@stellar/stellar-sdk";
import { Text, Button } from "react-native";
import nacl from "tweetnacl";
function VerifySignature() {
const { stellarSigner, isLoading } = useParaStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const verifyMessage = async () => {
if (!stellarSigner) return;
const message = "Hello, Stellar!";
const messageBytes = Buffer.from(new TextEncoder().encode(message));
// Sign the message
const signature = await stellarSigner.signBytes(messageBytes);
// Extract the raw Ed25519 public key from the Stellar G-address
const publicKeyBytes = StrKey.decodeEd25519PublicKey(stellarSigner.address);
// Verify the signature using tweetnacl
const isValid = nacl.sign.detached.verify(
new Uint8Array(messageBytes),
new Uint8Array(signature),
publicKeyBytes
);
console.log("Message:", message);
console.log("Signature:", signature.toString("hex"));
console.log("Signature valid:", isValid);
console.log("Signer:", stellarSigner.address);
return isValid;
};
if (isLoading) return Loading... ;
return ;
}
```
Install `tweetnacl` for Ed25519 signature verification: `npm install tweetnacl`. The `StrKey.decodeEd25519PublicKey` method from `@stellar/stellar-sdk` extracts the raw 32-byte public key from a Stellar G-address.
## Next Steps
# Overview
Source: https://docs.getpara.com/v3/react-native/overview
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para supports modern mobile development frameworks, giving you the flexibility to implement our SDKs in your native mobile applications. Each integration guide provides step-by-step instructions tailored to specific mobile development environments.
## Set Up Your Framework
## Add Authentication
## Enhance Security
## Sign & Transact
## Sessions & Advanced
# React Native Quickstart
Source: https://docs.getpara.com/v3/react-native/quickstart
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
## Prerequisites
Before integrating Para into your React Native app, make sure you have:
1. **An API key** — create your account at the
2. **A React Native project** — bare workflow or Expo development build (managed workflow is not supported)
## Install
```bash yarn
yarn add @getpara/react-native-wallet @tanstack/react-query @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto
```
```bash npm
npm install @getpara/react-native-wallet @tanstack/react-query @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto
```
For iOS, run `cd ios && pod install` after installing to link native dependencies.
## Set Up the Provider
Import the crypto shim at your app's entry point, then wrap your app with `ParaProvider` and a React Query `QueryClientProvider`:
```tsx App.tsx
import "@getpara/react-native-wallet/shim";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ParaProvider } from "@getpara/react-native-wallet";
const queryClient = new QueryClient();
export default function App() {
return (
);
}
```
If you're using a legacy API key (one without an environment prefix), pass `env` explicitly: `paraClientConfig={{ apiKey: "YOUR_API_KEY", env: Environment.BETA }}`.
## Authenticate a User
Use the built-in hooks to handle authentication:
```tsx AuthScreen.tsx
import {
useSignUpOrLogIn,
useVerifyNewAccount,
useIsFullyLoggedIn,
} from "@getpara/react-native-wallet";
import { useState } from "react";
import { View, TextInput, Button, Text } from "react-native";
export function AuthScreen() {
const [email, setEmail] = useState("");
const [code, setCode] = useState("");
const { data: isFullyLoggedIn } = useIsFullyLoggedIn();
const { signUpOrLogInAsync, isPending: isSending } = useSignUpOrLogIn();
const { verifyNewAccountAsync, isPending: isVerifying } =
useVerifyNewAccount();
if (isFullyLoggedIn) {
return You're logged in! ;
}
const handleSendCode = async () => {
await signUpOrLogInAsync({ email });
};
const handleVerify = async () => {
await verifyNewAccountAsync({ verificationCode: code });
};
return (
);
}
```
## Sign a Message
Once authenticated, use the `useSignMessage` hook:
```tsx SignScreen.tsx
import { useSignMessage, useWallet } from "@getpara/react-native-wallet";
import { Button, Alert } from "react-native";
export function SignScreen() {
const { data: wallet } = useWallet();
const { signMessageAsync, isPending } = useSignMessage();
const handleSign = async () => {
if (!wallet) return;
const result = await signMessageAsync({
walletId: wallet.id,
messageBase64: btoa("Hello, Para!"),
});
Alert.alert("Signature", `0x${result.signature}`);
};
return (
);
}
```
**Need an API Key?** Head to the to create your account and get your API key.
## Next Steps
Platform-specific configuration for iOS and Android (passkeys, associated
domains, etc.)
Integrate Para with Viem for EVM chain operations
Manage user sessions and authentication state
Explore all available React Native hooks
# Developer Portal Email Branding
Source: https://docs.getpara.com/v3/react-native/setup/developer-portal-email-branding
import DeveloperPortalEmailBranding from '/snippets/v3/developer-portal/email-branding.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Payment Integration
Source: https://docs.getpara.com/v3/react-native/setup/developer-portal-payments
import DeveloperPortalPayments from '/snippets/v3/developer-portal/payments.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Security Settings
Source: https://docs.getpara.com/v3/react-native/setup/developer-portal-security
import DeveloperPortalSecurity from '/snippets/v3/developer-portal/security.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Get Your API Key
Source: https://docs.getpara.com/v3/react-native/setup/developer-portal-setup
import DeveloperPortalSetup from '/snippets/v3/developer-portal/setup.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Expo
Source: https://docs.getpara.com/v3/react-native/setup/expo
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import BetaCredentialsFull from "/snippets/v3/beta-credentials/beta-credentials-full.mdx";
is a framework built on React Native that provides many out-of-the-box features, similar to NextJS for web but designed
for mobile development. Para provides a `@getpara/react-native-wallet` package that works seamlessly in both React Native bare and Expo workflows.
## Prerequisites
To use Para, you need an API key. This key authenticates your requests to Para services and is essential for integration.
Don't have an API key yet? Request access to the to create API keys, manage billing, teams, and more.
## Dependency Installation
Install the required dependencies:
```bash npm
npm install @getpara/react-native-wallet @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-nitro-modules react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto viem --save-exact
```
```bash yarn
yarn add @getpara/react-native-wallet @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-nitro-modules react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto viem --exact
```
```bash pnpm
pnpm add @getpara/react-native-wallet @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-nitro-modules react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto viem --save-exact
```
```bash bun
bun add @getpara/react-native-wallet @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-nitro-modules react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto viem --exact
```
## Project Setup
If you plan to use native passkeys, additional platform configuration (associated domains, SHA-256 fingerprints) is required. See for iOS and Android setup.
### Configure Metro Bundler
Create or update `metro.config.js` so Metro knows how to resolve the Node polyfills Para depends on (for example `crypto` and `buffer`). For more details on Metro configuration, see the .
```javascript 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;
```
### Import Required Shims
Import the Para Wallet shim in your root layout file to ensure proper global module shimming. This ensures that the necessary modules are available globally in your application. Ensure this is the very first import in your root layout file.
```typescript app/_layout.tsx
import "@getpara/react-native-wallet/shim";
// ... rest of your imports and layout code
```
Alternatively, you can create a custom entry point to handle the shimming. This will ensure that the shim occurs before the Expo Router entry point.
Create `index.js` in your project root and add the following imports:
```javascript index.js
import "@getpara/react-native-wallet/shim";
import "expo-router/entry";
```
Update `package.json` to point to your new entry file:
```json package.json
{
"main": "index.js"
}
```
### Prebuild and Run
Since native modules are required, you'll need to use Expo Development Build to ensure that linking is successful. This means using the `expo prebuild` command to generate the necessary native code and then run your app using `expo run:ios` or `expo run:android`.
```bash
npx expo prebuild
npx expo run:ios
npx expo run:android
```
You **cannot** use Expo Go as it doesn't support native module linking. When running via `yarn start`, switch to development mode by pressing `s`, then `i` for iOS or `a` for Android.
## Initialize the SDK
Set up the Para client singleton and initialize it in your app:
```typescript para.ts
import { ParaMobile } from "@getpara/react-native-wallet";
export const para = new ParaMobile(YOUR_API_KEY, undefined, {
disableWorkers: true,
});
```
Initialize it in your app entry point:
```typescript app/_layout.tsx
import { para } from "../para";
import { useEffect } from "react";
export default function Layout() {
useEffect(() => {
const initPara = async () => {
await para.init();
};
initPara();
}, []);
// ... rest of your layout code
}
```
If you're using a legacy API key (one without an environment prefix) you must provide the `Environment` as the first argument to the `ParaMobile` constructor. You can retrieve your updated API key from the Para Developer Portal at https://developer.getpara.com/
## Examples
## Troubleshooting
If you're having trouble initializing the Para SDK:
- Ensure that you've called `para.init()` after creating the Para instance.
- Verify that you're using the correct API key and environment.
- Check that all necessary dependencies are installed and linked properly.
- Look for any JavaScript errors in your Expo bundler console.
If you're seeing errors about missing native modules:
- Ensure you've run `expo prebuild` to generate native code.
- Run `expo run:ios` and `expo run:android` to rebuild your app after adding new native dependencies.
- Verify that your `app.json` file includes the necessary configurations for native modules.
If you're seeing errors related to crypto functions:
- Ensure you've properly set up the `expo-crypto` and `react-native-quick-crypto` polyfills.
- Verify that your `metro.config.js` file is configured correctly to include the necessary Node.js core modules.
- Check that you've imported the shim file at the top of your root layout or `index.js` file.
If you're experiencing authentication issues:
- Double-check that your API key is correct and properly set in your environment variables.
- Verify you're using the correct environment (`BETA` or `PRODUCTION`) that matches your API key.
- Ensure your account has the necessary permissions for the operations you're attempting.
- Check your network requests for any failed API calls and examine the error messages.
If you're encountering problems during the Expo build process:
- Ensure you're using a compatible Expo SDK version with all your dependencies.
- Run `expo doctor` to check for any configuration issues in your project.
- For EAS builds, check your `eas.json` configuration and ensure it's set up correctly for both iOS and Android.
For a more comprehensive list of solutions, visit our .
## Next Steps
# Bare Workflow
Source: https://docs.getpara.com/v3/react-native/setup/react-native
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import BetaCredentialsFull from "/snippets/v3/beta-credentials/beta-credentials-full.mdx";
The Para SDK for React Native allows you to easily integrate secure and scalable wallet functionalities into your mobile
applications. This guide covers the installation and project setup for the Para SDK in a bare React Native workflow.
## Prerequisites
To use Para, you need an API key. This key authenticates your requests to Para services and is essential for integration.
Don't have an API key yet? Request access to the to create API keys, manage billing, teams, and more.
## Dependency Installation
Install the required packages for Para SDK integration:
```bash npm
npm install @getpara/react-native-wallet @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-nitro-modules react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto viem --save-exact
```
```bash yarn
yarn add @getpara/react-native-wallet @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-nitro-modules react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto viem --exact
```
```bash pnpm
pnpm add @getpara/react-native-wallet @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-nitro-modules react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto viem --save-exact
```
```bash bun
bun add @getpara/react-native-wallet @react-native-async-storage/async-storage react-native-keychain react-native-modpow react-native-nitro-modules react-native-passkey react-native-quick-base64 @craftzdog/react-native-buffer react-native-quick-crypto viem --exact
```
## Project Setup
### iOS Setup
Install CocoaPods for native dependencies:
```bash
cd ios
bundle install
bundle exec pod install
cd ..
```
Remember to run `pod install` after adding new dependencies to your project.
Getting ready for App Review? See for Para-specific review tips.
If you plan to use native passkeys, additional platform configuration is required. See for iOS and Android setup.
### Configure Metro Bundler
Create or update `metro.config.js` in your project root:
```javascript metro.config.js
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
const config = {
resolver: {
extraNodeModules: {
crypto: require.resolve("react-native-quick-crypto"),
buffer: require.resolve("@craftzdog/react-native-buffer"),
},
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
```
### Add Para Shim
Import the Para shim as the FIRST import in your application's entry file (typically `index.js`):
```javascript
import "@getpara/react-native-wallet/shim";
// Other imports...
```
**Important**: The shim import must come before any other imports to ensure required modules are available.
## Initialize the SDK
Set up the Para client singleton to enable SDK interactions:
```typescript para.ts
import { ParaMobile } from "@getpara/react-native-wallet";
export const para = new ParaMobile(YOUR_API_KEY, undefined, {
disableWorkers: true,
});
```
Initialize it in your app entry point:
```typescript index.js
import "@getpara/react-native-wallet/shim";
import { para } from "./para";
// Initialize Para before rendering
para.init();
```
If you're using a legacy API key (one without an environment prefix) you must provide the `Environment` as the first argument to the `ParaMobile` constructor. You can retrieve your updated API key from the Para Developer Portal at https://developer.getpara.com/
## Examples
## Troubleshooting
If you're having trouble initializing the Para SDK:
- Ensure that you've called `para.init()` after creating the Para instance.
- Verify that you're using the correct API key and environment.
- Check that all necessary dependencies are installed and linked properly.
- Look for any JavaScript errors in your Metro bundler console.
If you're seeing errors about missing native modules:
- Run `pod install` in the `ios` directory to ensure all CocoaPods dependencies are installed.
- For Android, make sure your `android/app/build.gradle` file includes the necessary dependencies.
- Rebuild your app after adding new native dependencies.
If you're seeing errors related to crypto functions:
- Ensure you've properly set up the `react-native-get-random-values` and `react-native-quick-crypto` polyfills.
- Verify that your `metro.config.js` file is configured correctly to include the necessary Node.js core modules.
- Check that you've imported the shim file at the top of your root `index.js` file.
If you're encountering blob URL creation errors:
- Ensure you're using `{ disableWorkers: true }` in the ParaMobile constructor
- Verify the Para SDK shim is imported first in your `index.js` file
- This error occurs when Web Workers are enabled in React Native environments
If you're experiencing authentication issues:
- Double-check that your API key is correct and properly set in your environment variables.
- Verify you're using the correct environment (`BETA` or `PRODUCTION`) that matches your API key.
- Ensure your account has the necessary permissions for the operations you're attempting.
- Check your network requests for any failed API calls and examine the error messages.
For a more comprehensive list of solutions, visit our .
## Next Steps
# React Native and Expo Troubleshooting
Source: https://docs.getpara.com/v3/react-native/troubleshooting
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
When incorporating Para into React Native or Expo applications, developers may face specific hurdles. This guide offers
solutions to common problems and provides best practices for a smooth integration process.
Using an LLM (ChatGPT, Claude) or Coding Assistant (Cursor, Github Copilot)? Here are a few tips:
1) Include the for the most up-to-date help
2) Check out the for an interactive LLM using Para Examples Hub
## General Troubleshooting Steps
Before addressing specific issues, try these general troubleshooting steps for both React Native and Expo projects:
```bash
# For React Native
rm -rf node_modules
npm cache clean --force
npm install
# For Expo
expo r -c
```
```bash expo prebuild --clean ```
```bash
# For React Native
npx react-native run-ios
npx react-native run-android
# For Expo
expo run:ios
expo run:android
```
## Passkey Error Codes
The React Native SDK provides structured passkey error handling through `ParaPasskeyError`. Every passkey operation
throws a typed error with a machine-readable code, platform detection, and an actionable suggestion. See the
reference for the full list of error codes
and platform-specific troubleshooting steps.
## Common Issues and Solutions
**Error**: Errors related to missing modules like `crypto`, `buffer`, or `stream`.
**Solution**: Update your `metro.config.js` to include necessary polyfills:
```javascript
const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config");
const nodeLibs = require("node-libs-react-native");
const config = {
resolver: {
extraNodeModules: {
...nodeLibs,
crypto: require.resolve("react-native-quick-crypto"),
buffer: require.resolve("@craftzdog/react-native-buffer"),
},
},
};
module.exports = mergeConfig(getDefaultConfig(__dirname), config);
```
**Error**: Errors persist despite Metro configuration, often related to global objects.
**Solution**: Create a `shim.js` file at the root of your project and import it in your entry file.
For the complete `shim.js` setup, refer to the Polyfills and Shims section in the and setup guides.
**Error**: Errors related to `crypto.subtle` API or missing cryptographic functions.
**Solution**: Add the `PolyfillCrypto` component to your root `App` component.
```jsx
import PolyfillCrypto from "react-native-webview-crypto";
export default function App() {
return (
<>
{/* Your app components */}
>
);
}
**Error**: Passkey functionality not working. Errors related to `ASAuthorizationController` or `ASWebAuthenticationSession` on iOS and `CredentialManager` on Android.
**Solution**:
Passkeys requires configuring both the iOS and Android environments with the Para associated domains and web credentials.
Please check Step one of the Project Setup section for the and setup guides for detailed instructions.
**Error**: Passkeys not functioning despite correct configuration.
**Solution**: Ensure your app's bundle identifier and team ID are registered with Para.
Contact Para support to associate your app correctly. Provide your app's bundle identifier and team ID. You can find your team ID in the Apple Developer portal.
**Error**: `{"error": "Native error", "message": "Error: [50152] RP ID cannot be validated."}`
This is an Android-specific error from Google Play Services indicating your app's signing certificate doesn't match what's registered in the Digital Asset Links for Para's domain.
**Common causes:**
- **Debug vs release key mismatch**: Debug builds use `~/.android/debug.keystore` while release builds use your production keystore. The SHA-256 fingerprint registered in the Developer Portal must match the keystore used to sign the build you're testing.
- **Verification still pending**: After registering your SHA-256 fingerprint, Google can take up to 24 hours to verify. Check the status in the under your API key's Native Passkey Configuration.
- **Incorrect fingerprint**: Verify you copied the correct SHA-256 fingerprint (not SHA-1 or MD5).
**Solution**: Get the correct fingerprint for your current build type and register it in the Developer Portal:
```bash
# Debug builds
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
# Release builds
keytool -list -v -keystore
```
You can register both debug and release fingerprints in the Developer Portal to avoid this issue across build types.
**Error**: `UserCancelled` or `NoCredentials`
**Solutions:**
- `UserCancelled`: The user dismissed the passkey prompt. This is expected behavior — handle it gracefully in your app by catching the error and allowing the user to retry.
- `NoCredentials`: No passkeys are stored on this device for the user. This typically means the user hasn't registered a passkey yet or is on a different device than where the passkey was created. Ensure your auth flow calls `registerPasskey()` for new users before attempting `loginWithPasskey()`.
**Error**: `RequestFailed` from ASAuthorization
This iOS error indicates the passkey operation failed at the system level.
**Common causes:**
- The associated domains entitlement is not configured correctly in Xcode (or `app.json` for Expo).
- Your `teamId + bundleIdentifier` is not registered with Para.
- The device cannot reach Apple's CDN to validate the Apple App Site Association file.
**Solution**: Verify your associated domains are set to `webcredentials:app.beta.usecapsule.com` and `webcredentials:app.usecapsule.com`, and confirm your Team ID + Bundle ID is registered in the .
**Error**: `com.oblador.keychain.exceptions.CryptoFailedException: Decryption failed: Authentication tag verification failed.`
This is expected during development when you rebuild or reinstall the app. The previous keychain data was encrypted with a key tied to the old app installation.
**Solution**: Clear the app data or uninstall and reinstall the app. This will not affect production users since they won't be reinstalling the app during normal use.
**Error**: Native modules not working in Expo Go.
**Solution**: Para is reliant on native modules and will not work with Expo Go. Use Expo's build service to create a standalone app. You can do this by running `expo build:ios` or `expo build:android`. This will create the corresponding iOS or Android folders in your project and link the native modules correctly. Alternative use expo prebuild to create the native folders for both platforms.
**Error**: Native modules not linking correctly. Build errors related to missing pods. Build stalls at the linking stage.
**Solution**: iOS in React Native projects requires manual linking of pods. Ensure the pods are correctly linked by running `pod install` in the `ios` directory. Expo auto links the pods, but you can run `expo prebuild --clean` to ensure the pods are correctly linked.
```bash
cd ios
pod install
cd ..
npx react-native run-ios
```
**Error**: Errors retrieving stored items.
**Solution**: Ensure React Native Async Storage and Keychain are installed and linked. Additionally ensure that you run `para.init()` as it asynchronusly initializes the storage.
### Best Practices
1. Implement robust error handling for all Para operations.
2. Use secure storage methods for sensitive data.
3. Keep your project's native code up to date with the latest Para SDK requirements.
For comprehensive setup instructions and the most up-to-date integration guide, please refer to our and quick start guides.
# Web Examples
Source: https://docs.getpara.com/v3/react/examples
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para offers a comprehensive collection of web examples to help you integrate our technology across your preferred
frontend frameworks. Our examples-hub repository follows a consistent naming convention with folders structured as
(e.g., `with-react-vite` or `with-vue-vite`).
Each framework folder contains lightweight, standalone examples that focus on specific Para SDKs or features,
demonstrating minimal implementation requirements. These examples are designed to showcase individual capabilities with
clean, focused code that you can easily adapt to your specific application needs and use cases.
## Para Web Examples
Browse our web examples showcasing Para integration with popular frameworks:
## Signing Examples
Explore different signing implementations with Para:
## Wallet Pregeneration
Para also supports specialized web integration scenarios:
## DeFi Integrations
Discover how Para integrates with DeFi protocols:
## Need Something Specific?
Don't see an example for your use case? Para's team is eager to create new examples to help you integrate with different libraries, third-party features, or providers.
# Custom Auth UI with React Hooks
Source: https://docs.getpara.com/v3/react/guides/custom-ui-simplified
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import UseAuthenticateWithEmailOrPhone from '/snippets/v3/definitions/hooks/useAuthenticateWithEmailOrPhone.mdx';
import UseAuthenticateWithOAuth from '/snippets/v3/definitions/hooks/useAuthenticateWithOAuth.mdx';
import StatePhaseReference from '/snippets/v3/definitions/core/statePhaseReference.mdx';
Para provides simplified React hooks that handle the entire authentication flow in a single call. Instead of managing multiple steps (signup/login, verification, session polling, wallet creation) separately, these hooks orchestrate everything automatically and return a unified response.
Available in v2.13.0+
These hooks are **long-running** — they internally poll for session completion and wait for the user to finish interacting with the portal. Place the hook call in a **provider or higher-order component** that will not unmount during the authentication flow. If the component unmounts while the hook is running, the authentication will be interrupted.
While the hooks manage the flow end-to-end, you are responsible for **opening the portal URLs** that Para generates during authentication (for verification, passkey creation, password entry, etc.). Use `para.onStatePhaseChange()` to listen for these URLs and open them. **Passkey URLs must be opened in a popup** — WebAuthn does not work in iframes. See [Handling State Changes](#handling-state-changes) below.
## Prerequisites
You must have a Para account set up with authentication methods enabled in your Developer Portal. Install the React SDK:
```bash
npm install @getpara/react-sdk --save-exact
```
Ensure your app is wrapped with the `ParaProvider` as described in the [React quickstart guide](/v3/react/quickstart).
## Hooks Reference
## Handling State Changes
During authentication, Para's state machine progresses through phases that require user interaction — either opening portal URLs (for basic login users and biometric flows) or showing a code input (for non-basic-login new signups). Since you're building a custom UI without the `ParaModal`, you need to subscribe to state changes and handle them yourself.
**Passkey URLs must be opened in a popup window**, not an iframe. WebAuthn/passkey operations require a top-level browsing context and will fail silently in iframes due to browser security restrictions. Password and PIN URLs can be opened in either a popup or an iframe. Verification URLs can also use either approach.
Use `para.onStatePhaseChange()` to receive a `StateSnapshot`. The snapshot contains `authPhase` (what stage the flow is in) and `authStateInfo` (URLs and flags for the current stage):
```tsx
import { useEffect, useRef, useState } from "react";
import { useClient } from "@getpara/react-sdk";
import type { StateSnapshot, AuthPhase } from "@getpara/web-sdk";
function useParaAuthStateListener() {
const para = useClient();
const popupRef = useRef(null);
const lastUrlRef = useRef(null);
const [authPhase, setAuthPhase] = useState("unauthenticated");
const [authStateInfo, setAuthStateInfo] = useState(null);
useEffect(() => {
const unsubscribe = para.onStatePhaseChange((snapshot) => {
const { authStateInfo, authPhase } = snapshot;
setAuthPhase(authPhase);
setAuthStateInfo(authStateInfo);
// Basic login verification URL — open in popup or iframe
if (authStateInfo.verificationUrl && authStateInfo.verificationUrl !== lastUrlRef.current) {
lastUrlRef.current = authStateInfo.verificationUrl;
popupRef.current = window.open(
authStateInfo.verificationUrl,
"ParaVerification",
"popup,width=400,height=500"
);
return;
}
// Biometric / security URLs
const { passkeyUrl, passwordUrl, pinUrl } = authStateInfo;
// Passkey URLs MUST use a popup — WebAuthn does not work inside iframes
if (passkeyUrl && passkeyUrl !== lastUrlRef.current) {
lastUrlRef.current = passkeyUrl;
popupRef.current = window.open(passkeyUrl, "ParaPasskey", "popup,width=400,height=500");
} else if (passwordUrl && passwordUrl !== lastUrlRef.current) {
lastUrlRef.current = passwordUrl;
popupRef.current = window.open(passwordUrl, "ParaPassword", "popup,width=400,height=500");
} else if (pinUrl && pinUrl !== lastUrlRef.current) {
lastUrlRef.current = pinUrl;
popupRef.current = window.open(pinUrl, "ParaPIN", "popup,width=400,height=500");
}
});
return () => {
unsubscribe();
lastUrlRef.current = null;
};
}, [para]);
return { authPhase, authStateInfo, popupRef };
}
```
When `authPhase` is `'awaiting_account_verification'`, the user is a **non-basic-login new signup** who has been sent an OTP code via email or SMS. There is no URL to open — you must show a code input field and call `useVerifyNewAccount()` to submit the code. If the user needs a new code, use `useResendVerificationCode()`. The simplified hook is waiting for this step to complete before it proceeds. See the [full example below](#email--phone-authentication).
### `authStateInfo` Fields
| Field | Type | Description |
| --- | --- | --- |
| `verificationUrl` | `string \| null` | Portal URL for basic login users to complete auth (OTP, passkey, etc.) in the hosted portal. Only set during `awaiting_session_start` for basic login flows. Can be opened in a popup or iframe. |
| `passkeyUrl` | `string \| null` | Portal URL for passkey login or creation. **Must be opened in a popup** — WebAuthn does not work in iframes. |
| `passwordUrl` | `string \| null` | Portal URL for password login or creation. Can be opened in a popup or iframe. |
| `pinUrl` | `string \| null` | Portal URL for PIN login or creation. Can be opened in a popup or iframe. |
| `isPasskeySupported` | `boolean` | Whether the user's device supports passkeys/WebAuthn. |
| `isNewUser` | `boolean` | Whether this is a new signup flow. |
| `passkeyHints` | `BiometricLocationHint[] \| null` | Hints for known device detection. |
## Email / Phone Authentication
Use `useAuthenticateWithEmailOrPhone` to authenticate a user by email or phone number. The hook handles the complete flow: it determines whether the user is new or returning, manages session polling, waits for session establishment, and creates wallets for new signups.
You need to handle two things alongside the hook:
1. **State listener** — subscribe to `onStatePhaseChange` to open portal URLs (verification, passkey, password, PIN) when they become available.
2. **OTP code input** — when `authPhase` is `'awaiting_account_verification'`, show a code input and call `useVerifyNewAccount()` to submit the code. The hook is waiting for this before it proceeds.
```tsx
import { useState } from "react";
import {
useAuthenticateWithEmailOrPhone,
useVerifyNewAccount,
useResendVerificationCode,
} from "@getpara/react-sdk";
function EmailAuth() {
const {
authenticateWithEmailOrPhoneAsync,
isPending,
error,
} = useAuthenticateWithEmailOrPhone();
const { verifyNewAccountAsync } = useVerifyNewAccount();
const { resendVerificationCodeAsync } = useResendVerificationCode();
// Use the state listener hook from the section above
const { authPhase, popupRef } = useParaAuthStateListener();
const [email, setEmail] = useState("");
const [verificationCode, setVerificationCode] = useState("");
const handleAuth = async () => {
try {
const result = await authenticateWithEmailOrPhoneAsync({
auth: { email },
sessionPollingCallbacks: {
onPoll: () => {
if (popupRef.current?.closed) {
popupRef.current = null;
}
},
},
});
if (result.hasCreatedWallets && result.recoverySecret) {
// Non-basic-login new user — display or store the recovery secret
console.log("Recovery secret:", result.recoverySecret);
}
// User is now fully authenticated
console.log("Auth info:", result.authInfo);
} catch (err) {
console.error("Authentication failed:", err);
}
};
// Show OTP input when awaiting verification for non-basic-login new signups
if (authPhase === "awaiting_account_verification") {
return (
);
}
return (
setEmail(e.target.value)}
placeholder="Enter your email"
/>
{isPending ? "Authenticating..." : "Continue"}
{error &&
{error.message}
}
);
}
```
For phone number authentication, pass `{ phone: '+1234567890' }` instead of `{ email }`:
```tsx
authenticateWithEmailOrPhoneAsync({
auth: { phone: `+${countryCode}${phoneNumber}` as `+${number}` },
});
```
## OAuth Authentication
Use `useAuthenticateWithOAuth` to authenticate a user via a third-party OAuth provider. The hook manages the OAuth redirect/popup, polls for completion, waits for session establishment, and creates wallets for new signups.
Bringing your own [Custom OIDC](/v3/general/developer-portal-custom-oidc) provider? It works like a standard OAuth provider here — pass `"CUSTOM_OIDC"` as the method once it's configured.
### Standard OAuth (Google, Apple, Discord, X, Facebook)
For standard OAuth providers, the `onOAuthPopup` callback gives you the initial popup window. The state listener handles biometric URLs that appear after the OAuth step completes (e.g. when a returning user needs to authenticate with their passkey).
```tsx
import { useAuthenticateWithOAuth } from "@getpara/react-sdk";
function OAuthLogin() {
const {
authenticateWithOAuthAsync,
isPending,
error,
} = useAuthenticateWithOAuth();
// Use the state listener hook from the section above
const { popupRef } = useParaAuthStateListener();
const handleOAuth = async (method: "GOOGLE" | "APPLE" | "DISCORD" | "X" | "FACEBOOK" | "CUSTOM_OIDC") => {
try {
const result = await authenticateWithOAuthAsync({
method,
redirectCallbacks: {
onOAuthPopup: (popup) => {
popupRef.current = popup;
},
},
oAuthPollingCallbacks: {
onPoll: () => {
if (popupRef.current?.closed) {
popupRef.current = null;
}
},
},
});
if (result.hasCreatedWallets && result.recoverySecret) {
console.log("Recovery secret:", result.recoverySecret);
}
console.log("Auth info:", result.authInfo);
} catch (err) {
console.error("OAuth failed:", err);
}
};
return (
handleOAuth("GOOGLE")} disabled={isPending}>
Continue with Google
handleOAuth("APPLE")} disabled={isPending}>
Continue with Apple
handleOAuth("DISCORD")} disabled={isPending}>
Continue with Discord
{error &&
{error.message}
}
);
}
```
### Telegram
Telegram authentication works the same way — the hook manages the Telegram bot interaction automatically:
```tsx
const result = await authenticateWithOAuthAsync({
method: "TELEGRAM",
redirectCallbacks: {
onOAuthPopup: (popup) => {
popupRef.current = popup;
},
},
});
```
### Farcaster
Farcaster uses a QR code flow. Use the `redirectCallbacks.onOAuthUrl` callback to receive the Farcaster Connect URI and display it as a QR code:
```tsx
const [farcasterUri, setFarcasterUri] = useState(null);
const handleFarcaster = async () => {
try {
const result = await authenticateWithOAuthAsync({
method: "FARCASTER",
redirectCallbacks: {
onOAuthUrl: (url) => {
setFarcasterUri(url);
},
},
oAuthPollingCallbacks: {
isCanceled: () => !farcasterUri,
},
});
setFarcasterUri(null);
console.log("Auth info:", result.authInfo);
} catch (err) {
console.error("Farcaster auth failed:", err);
}
};
```
## Cancelling Authentication
Both hooks accept polling callbacks with an `isCanceled` function. Return `true` from `isCanceled` to stop the polling loop — for example, when the user closes a popup or navigates away. The cancellation is clean: no error is thrown, and the optional `onCancel` callback is fired.
```tsx
const result = await authenticateWithEmailOrPhoneAsync({
auth: { email },
sessionPollingCallbacks: {
isCanceled: () => {
// Cancel if the user closed the popup
return popupRef.current === null || popupRef.current.closed;
},
onCancel: () => {
console.log("User canceled authentication");
},
},
});
```
For OAuth, you can cancel both the OAuth polling and session polling independently:
```tsx
const result = await authenticateWithOAuthAsync({
method: "GOOGLE",
oAuthPollingCallbacks: {
isCanceled: () => userClickedCancel,
onCancel: () => console.log("OAuth polling canceled"),
},
sessionPollingCallbacks: {
isCanceled: () => userClickedCancel,
onCancel: () => console.log("Session polling canceled"),
},
});
```
Calling `logout` also cancels all active polling and resets the state phases back to `unauthenticated`. This is useful for implementing a "Cancel" button that fully resets the auth flow:
```tsx
import { useLogout } from "@getpara/react-sdk";
const { logoutAsync } = useLogout();
const handleCancel = async () => {
await logoutAsync();
// All polling stops, state phases reset to unauthenticated
};
```
## Handling Results
Both hooks return an `AuthenticateResponse` object with the same shape:
```typescript
type AuthenticateResponse = {
authInfo: CoreAuthInfo; // The user's authentication info (email, userId, etc.)
hasCreatedWallets: boolean; // Whether new wallets were created during this flow
recoverySecret?: string; // Recovery secret for non-basic-login newly created wallets
};
```
| Field | Description |
| --- | --- |
| `authInfo` | Contains the user's primary authentication information such as `email`, `phone`, `userId`, and any auth extras. |
| `hasCreatedWallets` | `true` if the user is new and wallets were auto-created during signup. `false` for returning users. |
| `recoverySecret` | Present only for **non-basic-login** when new wallets are created. Basic login users do not receive a recovery secret. This should be displayed to the user or stored securely — it cannot be retrieved again. |
## Next Steps
# Custom Auth UI with Web SDK
Source: https://docs.getpara.com/v3/react/guides/custom-ui-web-sdk
import { Card } from '/snippets/v3/components/ui/card.mdx';
import AuthenticateWithEmailOrPhone from '/snippets/v3/definitions/core/authenticateWithEmailOrPhone.mdx';
import AuthenticateWithOAuth from '/snippets/v3/definitions/core/authenticateWithOAuth.mdx';
import VerifyNewAccount from '/snippets/v3/definitions/core/verifyNewAccount.mdx';
import StatePhaseReference from '/snippets/v3/definitions/core/statePhaseReference.mdx';
This guide covers building custom authentication UI using the `@getpara/web-sdk` client directly — no React required. This approach works with any JavaScript framework (Vue, Svelte, Angular, etc.) or plain vanilla JS.
Available in v2.13.0+
The simplified methods (`authenticateWithEmailOrPhone`, `authenticateWithOAuth`) are **long-running** — they internally poll for session completion and wait for the user to finish interacting with the portal. Ensure the calling context will not be destroyed while the method is running (e.g. avoid calling from a component that may unmount mid-flow).
While these methods manage the flow end-to-end, you are responsible for **opening the portal URLs** that Para generates during authentication (for verification, passkey creation, password entry, etc.). Use `para.onStatePhaseChange()` to listen for these URLs and open them. **Passkey URLs must be opened in a popup** — WebAuthn does not work in iframes. See [Handling State Changes](#handling-state-changes) below.
## Prerequisites
Install the Web SDK:
```bash
npm install @getpara/web-sdk
```
## Client Setup
Create a Para client instance:
```typescript
import { ParaWeb } from "@getpara/web-sdk";
const PARA_API_KEY = "YOUR_API_KEY";
export const para = new ParaWeb(PARA_API_KEY);
```
## Method Reference
## Handling State Changes
During authentication, Para's state machine progresses through phases that require user interaction — either opening portal URLs (for basic login users and biometric flows) or showing a code input (for non-basic-login new signups). You need to subscribe to state changes and handle them yourself.
**Passkey URLs must be opened in a popup window**, not an iframe. WebAuthn/passkey operations require a top-level browsing context and will fail silently in iframes due to browser security restrictions. Password and PIN URLs can be opened in either a popup or an iframe. Verification URLs can also use either approach.
Use `para.onStatePhaseChange()` to receive a `StateSnapshot`. The snapshot contains `authPhase` (what stage the flow is in) and `authStateInfo` (URLs and flags for the current stage):
```typescript
import { para } from "./your-para-client";
import type { StateSnapshot, AuthPhase } from "@getpara/web-sdk";
let lastUrl: string | null = null;
let popup: Window | null = null;
const unsubscribe = para.onStatePhaseChange((snapshot: StateSnapshot) => {
const { authStateInfo, authPhase } = snapshot;
// Basic login verification URL — open in popup or iframe
if (authStateInfo.verificationUrl && authStateInfo.verificationUrl !== lastUrl) {
lastUrl = authStateInfo.verificationUrl;
popup = window.open(
authStateInfo.verificationUrl,
"ParaVerification",
"popup,width=400,height=500"
);
return;
}
// Biometric / security URLs
const { passkeyUrl, passwordUrl, pinUrl } = authStateInfo;
// Passkey URLs MUST use a popup — WebAuthn does not work inside iframes
if (passkeyUrl && passkeyUrl !== lastUrl) {
lastUrl = passkeyUrl;
popup = window.open(passkeyUrl, "ParaPasskey", "popup,width=400,height=500");
} else if (passwordUrl && passwordUrl !== lastUrl) {
lastUrl = passwordUrl;
popup = window.open(passwordUrl, "ParaPassword", "popup,width=400,height=500");
} else if (pinUrl && pinUrl !== lastUrl) {
lastUrl = pinUrl;
popup = window.open(pinUrl, "ParaPIN", "popup,width=400,height=500");
}
});
// Call unsubscribe() when your auth UI is torn down
```
When `authPhase` is `'awaiting_account_verification'`, the user is a **non-basic-login new signup** who has been sent an OTP code via email or SMS. There is no URL to open — you must show a code input field and call `para.verifyNewAccount({ verificationCode })`. If the user needs a new code, call `para.resendVerificationCode({ type: 'SIGNUP' })`. The simplified method is waiting for this step to complete before it proceeds. See the [full example below](#email--phone-authentication).
### `authStateInfo` Fields
| Field | Type | Description |
| --- | --- | --- |
| `verificationUrl` | `string \| null` | Portal URL for basic login users to complete auth (OTP, passkey, etc.) in the hosted portal. Only set during `awaiting_session_start` for basic login flows. Can be opened in a popup or iframe. |
| `passkeyUrl` | `string \| null` | Portal URL for passkey login or creation. **Must be opened in a popup** — WebAuthn does not work in iframes. |
| `passwordUrl` | `string \| null` | Portal URL for password login or creation. Can be opened in a popup or iframe. |
| `pinUrl` | `string \| null` | Portal URL for PIN login or creation. Can be opened in a popup or iframe. |
| `isPasskeySupported` | `boolean` | Whether the user's device supports passkeys/WebAuthn. |
| `isNewUser` | `boolean` | Whether this is a new signup flow. |
## Email / Phone Authentication
Use `para.authenticateWithEmailOrPhone()` to authenticate a user by email or phone. The method handles the complete flow: determining whether the user is new or returning, session polling, and wallet creation. Combine it with the state listener above to open portal URLs, and handle OTP input when `authPhase` is `'awaiting_account_verification'`.
```typescript
import { para } from "./your-para-client";
import type { AuthPhase } from "@getpara/web-sdk";
// Track auth phase for UI updates
let currentAuthPhase: AuthPhase = "unauthenticated";
const unsubscribe = para.onStatePhaseChange((snapshot) => {
currentAuthPhase = snapshot.authPhase;
// Update your UI based on currentAuthPhase
renderAuthUI();
});
async function handleEmailAuth(email: string) {
try {
const result = await para.authenticateWithEmailOrPhone({
auth: { email },
});
if (result.hasCreatedWallets && result.recoverySecret) {
// Non-basic-login new user — display or store the recovery secret
console.log("Recovery secret:", result.recoverySecret);
}
// User is now fully authenticated
console.log("Auth info:", result.authInfo);
} catch (error) {
console.error("Authentication failed:", error);
} finally {
unsubscribe();
}
}
// When authPhase is "awaiting_account_verification", show a code input
// and call this with the user's code:
async function handleVerifyCode(verificationCode: string) {
await para.verifyNewAccount({ verificationCode });
}
// To resend the verification code:
async function handleResendCode() {
await para.resendVerificationCode({ type: "SIGNUP" });
}
```
For phone number authentication, pass `{ phone: '+1234567890' }` instead of `{ email }`:
```typescript
const result = await para.authenticateWithEmailOrPhone({
auth: { phone: `+${countryCode}${phoneNumber}` as `+${number}` },
});
```
## OAuth Authentication
Use `para.authenticateWithOAuth()` to authenticate a user via a third-party OAuth provider. The method manages the OAuth redirect/popup, polls for completion, waits for session establishment, and creates wallets for new signups.
Bringing your own [Custom OIDC](/v3/general/developer-portal-custom-oidc) provider? It works like a standard OAuth provider here — pass `"CUSTOM_OIDC"` as the method once it's configured.
### Standard OAuth (Google, Apple, Discord, X, Facebook)
For standard OAuth providers, the `onOAuthPopup` callback gives you the popup window. The state listener handles biometric URLs that appear after the OAuth step completes (e.g. when a returning user needs to authenticate with their passkey).
```typescript
import { para } from "./your-para-client";
let popup: Window | null = null;
async function handleOAuthLogin(method: "GOOGLE" | "APPLE" | "DISCORD" | "X" | "FACEBOOK" | "CUSTOM_OIDC") {
try {
const result = await para.authenticateWithOAuth({
method,
redirectCallbacks: {
onOAuthPopup: (oauthPopup) => {
popup = oauthPopup;
},
},
oAuthPollingCallbacks: {
onPoll: () => {
if (popup?.closed) {
popup = null;
}
},
},
});
if (result.hasCreatedWallets && result.recoverySecret) {
console.log("Recovery secret:", result.recoverySecret);
}
console.log("Auth info:", result.authInfo);
} catch (error) {
console.error("OAuth failed:", error);
}
}
```
### Telegram
Telegram authentication works the same way — pass `"TELEGRAM"` as the method:
```typescript
const result = await para.authenticateWithOAuth({
method: "TELEGRAM",
redirectCallbacks: {
onOAuthPopup: (popup) => {
// The popup contains the Telegram bot interaction
},
},
});
```
### Farcaster
Farcaster uses a connect URI flow. Use the `redirectCallbacks.onOAuthUrl` callback to receive the Farcaster Connect URI and display it as a QR code:
```typescript
let farcasterUri: string | null = null;
async function handleFarcaster() {
try {
const result = await para.authenticateWithOAuth({
method: "FARCASTER",
redirectCallbacks: {
onOAuthUrl: (url) => {
farcasterUri = url;
// Render this URL as a QR code for the user to scan
},
},
oAuthPollingCallbacks: {
isCanceled: () => !farcasterUri,
},
});
farcasterUri = null;
console.log("Auth info:", result.authInfo);
} catch (error) {
console.error("Farcaster auth failed:", error);
}
}
```
## Cancelling Authentication
Both methods accept polling callbacks with an `isCanceled` function. Return `true` from `isCanceled` to stop the polling loop — for example, when the user closes a popup or navigates away. The cancellation is clean: no error is thrown, and the optional `onCancel` callback is fired.
```typescript
const result = await para.authenticateWithEmailOrPhone({
auth: { email },
sessionPollingCallbacks: {
isCanceled: () => {
// Cancel if the user closed the popup
return popup === null || popup.closed;
},
onCancel: () => {
console.log("User canceled authentication");
},
},
});
```
For OAuth, you can cancel both the OAuth polling and session polling independently:
```typescript
const result = await para.authenticateWithOAuth({
method: "GOOGLE",
oAuthPollingCallbacks: {
isCanceled: () => userClickedCancel,
onCancel: () => console.log("OAuth polling canceled"),
},
sessionPollingCallbacks: {
isCanceled: () => userClickedCancel,
onCancel: () => console.log("Session polling canceled"),
},
});
```
Calling `para.logout()` also cancels all active polling and resets the state phases back to `unauthenticated`. This is useful for implementing a "Cancel" button that fully resets the auth flow:
```typescript
async function handleCancel() {
await para.logout();
// All polling stops, state phases reset to unauthenticated
}
```
## Handling Results
Both methods return an `AuthenticateResponse` object with the same shape:
```typescript
type AuthenticateResponse = {
authInfo: CoreAuthInfo; // The user's authentication info (email, userId, etc.)
hasCreatedWallets: boolean; // Whether new wallets were created during this flow
recoverySecret?: string; // Recovery secret for non-basic-login newly created wallets
};
```
| Field | Description |
| --- | --- |
| `authInfo` | Contains the user's primary authentication information such as `email`, `phone`, `userId`, and any auth extras. |
| `hasCreatedWallets` | `true` if the user is new and wallets were auto-created during signup. `false` for returning users. |
| `recoverySecret` | Present only for **non-basic-login** when new wallets are created. Basic login users do not receive a recovery secret. This should be displayed to the user or stored securely — it cannot be retrieved again. |
## Framework Examples
### Vue 3 (Composition API)
```vue
```
### Svelte
```svelte
```
## Next Steps
# Configure Balance Display
Source: https://docs.getpara.com/v3/react/guides/customization/balances
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
The Para Modal provides comprehensive balance display capabilities, allowing users to view their wallet balances across multiple assets and networks. You can configure how balances are displayed and aggregated, and programmatically access balance data using the `useProfileBalance` hook.
## Balance Display Configuration
You can customize how balances are displayed and calculated through the `paraModalConfig.balances` property in your `ParaProvider` configuration. This setting will impact both the Para Modal's balance display and instances where you use the `useProfileBalance` hook.
Para supports two primary balance display modes, `AGGREGATED` and `CUSTOM_ASSET`. You can also specify whether to fetch balances for both `MAINNET_AND_TESTNET` (default) or one or the other only (`MAINNET` or `TESTNET`).
### Defining Custom Networks and Assets
To include custom assets when fetching connected wallet balances, you will need to supply implementation metadata for each asset, including:
- Each asset must include basic metadata, including `name`, `symbol`, and `logoUrl` (optional).
- Each asset must include an `implementations` array for each network you wish to query for balances.
- Each implementation object must include:
- Price data:
- **For a fixed price:**
- Include a `price` object with the asset's price in the format `{ value: number; currency: 'USD' }`.
- **For a volatile price:**
- Include a `priceUrl` string. This endpoint must respond to GET requests with a JSON object with the asset's current price in the format `{ value: number; currency: 'USD' }`.
- Network information:
- **For a custom EVM network:**
- Include a `network` object specifying the network's `name`, `evmChainId`, and an `rpcUrl` where asset balances can be queried.
- If the network is a testnet, include `isTestnet: true`.
- Include a `nativeTokenSymbol` if the network's native token is not Ethereum (ETH).
- Optional: Include a `logoUrl` for the network.
- Optional: Include an `explorer` object for the network, including:
- The explorer's display name `name`.
- The explorer's homepage URL `url`.
- An explorer URL template `txUrlFormat` for broadcast transactions, using `{HASH}` as the placeholder for the transaction hash. Example: `https://etherscan.io/tx/{HASH}`
- **If the asset is the network's native token:**
- No additional configuration is needed.
- **If the asset is an ERC-20 token:**
- Include a `contractAddress` string.
- **For a standard EVM or Solana network:**
- Include a `network` string, matching one of the networks enumerated in the `TNetwork` type. For example, `'ETHEREUM'` or `'SOLANA'`.
- Include a `contractAddress` string.
Refer to the code snippets below for example configurations.
### Aggregated Mode (Default)
Aggregated Mode automatically aggregates balances across all detected chains and assets and displays them in USD value. If you supply additional assets and price data, these totals will be included in the calculation.
The aggregated total will include most commonly used assets across many networks. If you want to only include totals from your customized tokens, set `excludeStandardAssets` to `true`.
An example configuration with additional assets is below:
```tsx
',
// A price endpoint for the asset:
priceUrl: '',
implementations: [
// A custom EVM network where the asset is the native token:
{
network: {
name: 'Custom Chain',
evmChainId: '12345',
rpcUrl: 'https://rpc.customexplorer.com',
logoUrl: 'https://cdn.customexplorer.com/logo.png',
nativeTokenSymbol: 'CUSTOM',
explorer: {
name: 'Custom Chain Explorer',
url: 'https://customexplorer.com',
txUrlFormat: 'https://customexplorer.com/tx/{HASH}',
}
},
},
// A custom EVM network where the asset is an ERC-20 token:
{
network: {
name: 'Another Custom Chain',
evmChainId: '12345',
rpcUrl: '',
logoUrl: '',
explorer: {
name: 'Another Custom Chain Explorer',
url: 'https://anothercustomexplorer.com',
txUrlFormat: 'https://anothercustomexplorer.com/tx/{HASH}',
}
},
contractAddress: '0x...',
},
// An implementation of the token on Solana:
{
network: 'SOLANA',
contractAddress: 'Ep4r...',
},
// A testnet ERC-20 implementation of the token:
{
network: {
name: 'Custom Chain Testnet',
evmChainId: '12346',
rpcUrl: '',
logoUrl: '',
nativeTokenSymbol: 'CUSTOM',
explorer: {
name: 'Custom Chain Testnet Explorer',
url: 'https://testnet.customexplorer.com',
txUrlFormat: 'https://testnet.customexplorer.com/tx/{HASH}',
},
isTestnet: true,
},
contractAddress: '0x...',
},
],
},
{
name: 'Custom Stablecoin',
symbol: 'CSTABLE',
logoUrl: '',
// A fixed price for the asset
price: {
value: 1,
currency: 'USD',
},
networks: [
// A custom network where the asset is an ERC-20 token:
{
network: {
name: 'Custom Chain',
evmChainId: '12345',
rpcUrl: '',
logoUrl: '',
nativeTokenSymbol: 'CUSTOM',
explorer: {
name: 'Custom Chain Explorer',
url: 'https://customexplorer.com',
txUrlFormat: 'https://customexplorer.com/tx/{HASH}',
}
},
contractAddress: '0x...',
},
],
},
]
}
}}
>
{children}
```
#### Balance Overrides
In Aggregated Mode, if you wish, you can manually override the displayed fiat balance for each wallet in the Para Modal. This is useful if you are using a custom chain with many assets that are not included in the calculated total. You can replace the default USD balance for each connected wallet.
The override will alter both the per-wallet USD balance displayed in the 'Profile' modal screen and the cumulative USD balance displayed on the main 'Account' screen. It will also alter the result from the `useProfileBalance` hook.
To do this, you first set the `useBalanceOverrides` flag to `true` in your `ParaProvider` modal configuration:
```tsx
{children}
```
Then, within the `ParaProvider`, you can calculate and set an override object using the `useSetBalanceOverrides` hook. Pass an object where the keys are the addresses for your connected wallets (available in the `useAccount` hook) and the values are numbers representing the USD balance for each wallet.
An example usage:
```tsx App.tsx
import { useAccount, useSetBalanceOverrides } from "@getpara/react-sdk";
import { fetchAdditionalBalances } from "@your-domain/your-api-library";
// Assuming your API function accepts an array of wallet addresses and returns a Record:
declare global {
fetchAdditionalBalances: (opts: { addresses: string[] }) => Promise>;
}
export const App = () => {
const { embedded: embeddedWallets } = useAccount();
const setBalanceOverrides = useSetBalanceOverrides();
useEffect(() => {
const interval = setInterval(async () => {
const balances = await fetchAdditionalBalances({ addresses: embeddedWallets.map(w => w.address!) });
// Example response:
// {
// '0x123...': 123.45,
// '0x456...': 6.78,
// }
// Set the balance overrides:
setBalanceOverrides(balances);
}, 30000);
return () => clearInterval(interval);
}, [embeddedWallets]);
// ...
};
```
### Custom Asset Mode
In Custom Asset Mode, the Para Modal will only display balances of a chosen asset, with no fiat currency conversion. This is ideal for cases where your app uses a particular token that may not have price information available.
Like in Aggregated Mode, you will need to supply implementation metadata for the asset, including any custom network definitions so that its balances can be queried for the session's connected wallets. However, in this mode, you do not need to include a price object or a price URL.
```tsx
',
networks: [
// A custom network where the asset is the native token:
{
network: {
name: 'Custom Chain',
evmChainId: '12345',
rpcUrl: '',
logoUrl: '',
nativeTokenSymbol: 'CUSTOM',
explorer: {
name: 'Custom Chain Explorer',
url: 'https://customexplorer.com',
txUrlFormat: 'https://customexplorer.com/tx/{HASH}',
}
},
},
// A known network where the asset is an ERC-20 token:
{
contractAddress: '0x...',
network: 'ETHEREUM',
}
],
},
},
}}
>
{children}
```
## useProfileBalance Hook
The `useProfileBalance` hook allows you to query the current aggregated or custom asset balance of all wallets in the current session.
Balances are normally cached on the server for five minutes. You can supply a `refetchTrigger` to the hook to manually refetch balances when desired, using a unique number or string.
```tsx
import { useProfileBalance } from "@getpara/react-sdk";
function BalanceDisplay() {
const [refetchTrigger, setRefetchTrigger] = useState(0);
const { data: profileBalance, isLoading, error } = useProfileBalance({
// Balances will be refetched whenever `refetchTrigger` changes
refetchTrigger,
});
if (isLoading) return Loading balances...
;
if (error) return Error loading balances: {error.message}
;
if (!profileBalance) return No balance data available
;
return (
Total Balance: ${profileBalance.value.value.toFixed(2)}
{profileBalance.wallets.map((wallet) => (
Wallet: {wallet.address}
{wallet.assets.map((asset) => (
{asset.symbol}: ${asset.balance}
(${asset.value.value.toFixed(2)})
))}
))}
setRefetchTrigger(prev => prev + 1)}>Refresh Balances
);
}
```
Depending on the display type, the `ProfileBalance` object returned by `useProfileBalance` has the following structure:
```tsx
{
value: {
value: 200,
currency: 'USD',
},
wallets: [
{
type: 'EVM',
address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
value: {
value: 200,
currency: 'USD',
},
assets: [
{
metadata: {
internalId: 'ETHEREUM',
zerionId: 'eth',
name: 'Ethereum',
symbol: 'ETH',
rpcUrl: 'https://eth.llamarpc.com',
logoUrl: 'https://cdn.zerion.io/eth.png',
explorer: {
name: 'Etherscan',
url: 'https://etherscan.io',
txUrlFormat: 'https://etherscan.io/tx/{HASH}',
},
price: {
value: 4000,
currency: 'USD',
}
},
quantity: 0.03,
value: {
value: 120,
currency: 'USD',
},
networks: [
{
metadata: {
internalId: 'ETHEREUM',
zerionId: 'ethereum',
evmChainId: '1',
name: 'Ethereum',
rpcUrl: 'https://eth.llamarpc.com',
logoUrl: 'https://cdn.zerion.io/eth.png',
explorer: {
name: 'Etherscan',
url: 'https://etherscan.io',
txUrlFormat: 'https://etherscan.io/tx/{HASH}',
}
},
quantity: 0.02,
value: {
value: 80,
currency: 'USD',
},
},
{
metadata: {
internalId: 'BASE',
zerionId: 'base',
evmChainId: '8453',
name: 'Base',
rpcUrl: 'https://base.llamarpc.com',
logoUrl: 'https://cdn.zerion.io/base.png',
explorer: {
name: 'Basescan',
url: 'https://basescan.org',
txUrlFormat: 'https://basescan.org/tx/{HASH}',
}
},
quantity: 0.01,
value: {
value: 40,
currency: 'USD',
},
}
]
},
{
metadata: {
internalId: 'USDC',
zerionId: 'usdc',
name: 'USD Coin',
symbol: 'USDC',
price: {
value: 1,
currency: 'USD',
}
},
quantity: 80,
value: {
value: 80,
currency: 'USD',
},
networks: [
{
metadata: {
internalId: 'ETHEREUM',
zerionId: 'ethereum',
evmChainId: '1',
name: 'Ethereum',
rpcUrl: 'https://eth.llamarpc.com',
logoUrl: 'https://cdn.zerion.io/eth.png',
explorer: {
name: 'Etherscan',
url: 'https://etherscan.io',
txUrlFormat: 'https://etherscan.io/tx/{HASH}',
}
},
quantity: 50,
value: {
value: 50,
currency: 'USD',
},
contractAddress: '0x...',
},
{
metadata: {
internalId: 'BASE',
zerionId: 'base',
evmChainId: '8453',
name: 'Base',
rpcUrl: 'https://base.llamarpc.com',
logoUrl: 'https://cdn.zerion.io/base.png',
explorer: {
name: 'Basescan',
url: 'https://basescan.org',
txUrlFormat: 'https://basescan.org/tx/{HASH}',
}
},
quantity: 20,
value: {
value: 20,
currency: 'USD',
},
contractAddress: '0x...',
},
{
metadata: {
internalId: 'SOLANA',
name: 'Solana',
},
quantity: 10,
value: {
value: 10,
currency: 'USD',
},
contractAddress: '0x...',
}
]
}
],
networks: [
{
metadata: {
internalId: 'ETHEREUM',
zerionId: 'ethereum',
evmChainId: '1',
name: 'Ethereum',
},
value: {
value: 130,
currency: 'USD',
},
assets: [
{
metadata: {
internalId: 'ETHEREUM',
zerionId: 'ethereum',
evmChainId: '1',
name: 'Ethereum',
symbol: 'ETH',
rpcUrl: 'https://eth.llamarpc.com',
logoUrl: 'https://cdn.zerion.io/eth.png',
explorer: {
name: 'Etherscan',
url: 'https://etherscan.io',
txUrlFormat: 'https://etherscan.io/tx/{HASH}',
}
},
quantity: 0.02,
value: {
value: 80,
currency: 'USD',
},
contractAddress: '0x...',
},
{
metadata: {
internalId: 'USDC',
zerionId: 'usdc',
name: 'USD Coin',
symbol: 'USDC',
price: {
value: 1,
currency: 'USD',
},
},
quantity: 50,
value: {
value: 50,
currency: 'USD',
},
contractAddress: '0x...',
}
]
},
{
metadata: {
internalId: 'BASE',
zerionId: 'base',
evmChainId: '8453',
name: 'Base',
logoUrl: 'https://cdn.zerion.io/base.png',
rpcUrl: 'https://base.llamarpc.com',
explorer: {
name: 'Basescan',
url: 'https://basescan.org',
txUrlFormat: 'https://basescan.org/tx/{HASH}',
}
},
value: {
value: 60,
currency: 'USD',
},
assets: [
{
metadata: {
internalId: 'ETHEREUM',
zerionId: 'eth',
evmChainId: '1',
name: 'Ethereum',
logoUrl: 'https://cdn.zerion.io/eth.png',
},
quantity: 0.01,
value: {
value: 40,
currency: 'USD',
},
contractAddress: '0x...',
},
{
metadata: {
internalId: 'USDC',
zerionId: 'usdc',
name: 'USD Coin',
symbol: 'USDC',
logoUrl: 'https://cdn.zerion.io/usdc.png',
price: {
value: 1,
currency: 'USD',
},
},
quantity: 20,
value: {
value: 20,
currency: 'USD',
},
contractAddress: '0x...',
}
]
},
{
metadata: {
internalId: 'SOLANA',
name: 'Solana',
rpcUrl: 'https://api.mainnet-beta.solana.com',
logoUrl: 'https://cdn.zerion.io/solana.png',
explorer: {
name: 'Solana Explorer',
url: 'https://explorer.solana.com/',
txUrlFormat: 'https://explorer.solana.com/tx/{HASH}',
}
},
value: {
value: 10,
currency: 'USD',
},
assets: [
{
metadata: {
internalId: 'USDC',
zerionId: 'usdc',
name: 'USD Coin',
symbol: 'USDC',
price: {
value: 1,
currency: 'USD',
},
},
quantity: 10,
value: {
value: 10,
currency: 'USD',
},
contractAddress: '0x...',
}
]
}
]
}
]
}
```
```tsx
{
wallets: [
{
type: 'EVM',
address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
assets: [
{
metadata: {
name: 'Custom Asset',
symbol: 'CUSTOM',
customAssetId: 'custom_CUSTOM',
logoUrl: '',
},
quantity: 1.5,
networks: [
{
metadata: {
internalId: 'ETHEREUM',
zerionId: 'ethereum',
name: 'Ethereum',
evmChainId: '1',
rpcUrl: 'https://eth.llamarpc.com',
logoUrl: 'https://cdn.zerion.io/eth.png',
explorer: {
name: 'Etherscan',
url: 'https://etherscan.io',
txUrlFormat: 'https://etherscan.io/tx/{HASH}',
}
},
quantity: 1.0,
contractAddress: '0x...',
},
{
metadata: {
customNetworkId: 'custom_12345',
name: 'Custom Chain',
evmChainId: '12345',
rpcUrl: 'https://rpc.customexplorer.com',
logoUrl: 'https://cdn.customexplorer.com/logo.png',
nativeTokenSymbol: 'CUSTOM',
explorer: {
name: 'Custom Chain Explorer',
url: 'https://customexplorer.com',
txUrlFormat: 'https://customexplorer.com/tx/{HASH}',
}
},
quantity: 0.5,
contractAddress: '0x...',
}
]
}
],
networks: [
{
metadata: {
internalId: 'ETHEREUM',
zerionId: 'ethereum',
name: 'Ethereum',
evmChainId: '1',
rpcUrl: 'https://eth.llamarpc.com',
logoUrl: 'https://cdn.zerion.io/eth.png',
explorer: {
name: 'Etherscan',
url: 'https://etherscan.io',
txUrlFormat: 'https://etherscan.io/tx/{HASH}',
}
},
assets: [
{
metadata: {
name: 'Custom Asset',
symbol: 'CUSTOM',
customAssetId: 'custom_CUSTOM',
},
quantity: 1.5,
contractAddress: '0x...',
}
]
},
{
metadata: {
customNetworkId: 'custom_12345',
name: 'Custom Chain',
evmChainId: '12345',
rpcUrl: 'https://rpc.customexplorer.com',
logoUrl: 'https://cdn.customexplorer.com/logo.png',
explorer: {
name: 'Custom Chain Explorer',
url: 'https://customexplorer.com',
txUrlFormat: 'https://customexplorer.com/tx/{HASH}',
}
},
assets: [
{
metadata: {
name: 'Custom Asset',
symbol: 'CUSTOM',
customAssetId: 'custom_CUSTOM',
},
quantity: 0.5,
contractAddress: '0x...',
}
]
}
]
}
]
}
```
# How Configuration Works
Source: https://docs.getpara.com/v3/react/guides/customization/configuration
In v3, your **partner record** is the source of truth for how your integration looks and behaves — authentication methods, theme, external wallets, links, and more. You manage it in the [Developer Portal](https://developer.getpara.com), and the SDK reads it at runtime. Most settings no longer need to live in your app code.
**What you'll learn**
- Where Para reads each setting from, and how the layers combine
- What you can override in code with `configOverrides`, and what is partner-authoritative
- How the SDK enforces resolved configuration with `PartnerConfigError`
- Which `paraModalConfig` props are deprecated and what replaces them
## Where configuration comes from
Para resolves each setting from three layers. A higher layer only overrides the specific fields it sets — everything else falls through to the layer below.
```text
SDK configOverrides (code, per-instance) ← highest priority
▼
Partner record (Developer Portal) ← the source of truth
▼
Deprecated ParaModal props (paraModalConfig.*) ← fallback for un-migrated apps
```
This is a per-field merge, not all-or-nothing. If your partner record sets an OAuth allowlist but you override only the theme in code, you get your code theme **and** the partner's OAuth allowlist.
## The partner record (source of truth)
The partner record is fetched from your API key when the SDK initializes. Configure it in the Developer Portal. It governs:
- **Authentication** — OAuth methods, email/phone toggles, 2FA, guest mode, auth layout
- **Branding & theme** — colors, font, border radius, logo, "hide wallet" language
- **External wallets** — supported wallets, connection mode, WalletConnect project ID, app description
- **App identity & links** — display name, homepage/social/support URLs, RPC URL
- **Other** — supported wallet types, account links, balance display
Because it lives on the partner record, you can change any of these in the portal **without redeploying your app**.
## Overriding in code with `configOverrides`
For per-instance overrides, pass `configOverrides` to `ParaProvider`. This is the supported, non-deprecated way to override partner config from code — useful when one API key powers multiple brand contexts, or for pointing `rpcUrl` at a local node during development.
```tsx
{children}
```
`configOverrides` is typed as `Partial` — the compiler only lets you override fields that are safe to set per-instance.
### What you can override
| Area | Overridable fields |
|---|---|
| `themeConfig` | All theme fields (colors, font, border radius, mix ratio, `cssOverrides`) |
| `authConfig` | `oAuthMethods`, `disableEmailLogin`, `disablePhoneLogin`, `twoFactorAuthEnabled`, `isGuestModeEnabled` |
| `modalConfig` | `hideWallets`, `authLayout`, `hideLogo`, `disableAddFundsPrompt` |
| `externalWalletConfig` | `wallets`, `connectionOnly`, `includeWalletVerification`, `createLinkedEmbeddedForExternalWallets`, `walletConnectProjectId`, `appDescription` |
| `rpcUrl` | The chain RPC URL (handy for local dev) |
### What is partner-authoritative (cannot be overridden)
These are excluded from `SdkOverridableAppConfig` at the type level — the SDK cannot weaken them, and they live only on the partner record:
| Field | Why |
|---|---|
| `authConfig.supportedAuthMethods` | Security posture |
| `supportedWalletTypes` | Security/policy boundary |
| `supportedAccountLinks` | Security/policy boundary |
| `balancesConfig` | Partner-level setting |
| `partnerLinks` (homepage/social/support URLs) | One per project |
| `appName` | One per project |
| `farcasterConfig` | Read only by the backend (email templates) |
## Enforcement: `PartnerConfigError`
The SDK enforces the resolved auth and wallet configuration at its entry points (sign-up/login, OAuth, 2FA setup, guest wallet creation), not just in the modal. If you trigger something that is **explicitly disabled**, the call throws a `PartnerConfigError` with a stable `.code`.
```ts
import { PartnerConfigError } from "@getpara/react-sdk";
try {
await para.createGuestWallets();
} catch (e) {
if (e instanceof PartnerConfigError && e.code === "GUEST_MODE_DISABLED") {
// Guest mode is turned off for this partner — handle gracefully.
}
}
```
| `code` | Thrown when |
|---|---|
| `EMAIL_LOGIN_DISABLED` | `authConfig.disableEmailLogin` is `true` |
| `PHONE_LOGIN_DISABLED` | `authConfig.disablePhoneLogin` is `true` |
| `OAUTH_METHOD_NOT_ALLOWED` | An OAuth method isn't in the resolved `oAuthMethods` allowlist (`.detail` holds the method) |
| `TWO_FACTOR_DISABLED` | `authConfig.twoFactorAuthEnabled` is explicitly `false` |
| `GUEST_MODE_DISABLED` | `authConfig.isGuestModeEnabled` is explicitly `false` |
**Unset means "not configured", not "disabled."** A field that has never been set stays permissive; the gate only fires on an explicit choice (a `true` disable flag, an explicit `false` opt-in flag, or a defined OAuth allowlist). This keeps upgrades non-breaking: restrictions take effect once you configure them in the portal or with `configOverrides`, not before.
The default ` ` hides disabled options, so you typically only hit `PartnerConfigError` from **custom UI** or **direct SDK calls**, or when a deprecated fallback conflicts with higher-priority resolved config.
## Deprecated: configuring via `paraModalConfig` props
These `paraModalConfig` props still work, but they're the **lowest-priority fallback** and are deprecated — they'll be removed in the next major release. Each emits a one-time console warning. Configure them on the partner record (Developer Portal) instead, or use `configOverrides` for per-instance overrides.
| Deprecated `paraModalConfig` prop | Configure instead via |
|---|---|
| `oAuthMethods` | Portal authentication / `configOverrides.authConfig.oAuthMethods` |
| `disableEmailLogin`, `disablePhoneLogin` | Portal authentication / `configOverrides.authConfig.*` |
| `twoFactorAuthEnabled` | Portal authentication / `configOverrides.authConfig.twoFactorAuthEnabled` |
| `isGuestModeEnabled` | Portal authentication / `configOverrides.authConfig.isGuestModeEnabled` |
| `authLayout` | Portal authentication / `configOverrides.modalConfig.authLayout` |
| `hideWallets` | Portal / `configOverrides.modalConfig.hideWallets` |
| `theme` | Portal branding / `configOverrides.themeConfig` |
| `logo` | Portal branding / `configOverrides.modalConfig.logo` |
| `supportedAccountLinks` | Portal (partner-authoritative) |
| `balances` | Portal (partner-authoritative) |
Because props are the lowest layer, a value you set in code is **overridden** by the same setting on the partner record. If a prop seems to have no effect, check whether the partner record sets it.
See the [Migration Guide](/v3/introduction/migration-to-v3) for step-by-step migration of each prop.
## Next steps
- [Style the Modal](/v3/react/guides/customization/modal-theming) — theme, fonts, and CSS overrides
- [Customization Basics](/v3/react/guides/customization/modal) — the full `paraModalConfig` reference
- [Migrating to v3](/v3/introduction/migration-to-v3) — what changed and how to move config to the partner record
# Configure Authentication
Source: https://docs.getpara.com/v3/react/guides/customization/developer-portal-authentication
The **Authentication** screen in the [Developer Portal](https://developer.getpara.com) is where you configure how users sign in. These settings live on your **partner record** (the source of truth), so the SDK reads them at runtime and you can change them without redeploying your app.
These settings supersede the matching deprecated `paraModalConfig` props (`oAuthMethods`, `disableEmailLogin`, `disablePhoneLogin`, `authLayout`, `twoFactorAuthEnabled`, `isGuestModeEnabled`, `hideWallets`). See [How Configuration Works](/v3/react/guides/customization/configuration) for how layering and overrides behave.
To open it: select your project and API key, then go to **Authentication** in the sidebar.
## Display Order
Controls which login sections the modal shows and the order they appear. There are two reorderable rows — **Embedded Wallets** and **External Wallets** — each with:
- An **on/off** toggle — whether the section appears in the modal.
- A **Full Display / Condensed Display** choice — Full shows the section's options inline; Condensed collapses them behind a single entry button.
Drag the rows to set their order in the modal. At least one section must stay enabled — you can't turn both off.
This maps to the SDK's `authLayout`. The default is Embedded Wallets (Full) above External Wallets (Full).
## Embedded Wallets
Configures the Para-native login methods used to create embedded wallets.
- **Email** and **Phone** — toggle each on or off.
- **OAuth providers** — select which social providers to offer (Google, X, Apple, Discord, Facebook, Farcaster, Telegram). Drag to reorder; the modal shows them in this order.
- **Custom OIDC** — bring your own OpenID Connect provider (enterprise SSO or your own auth) as a login method. Configure it in the **Custom OIDC** section, then enable it here. See [Set up Custom OIDC](/v3/general/developer-portal-custom-oidc) for the full walkthrough.
If you haven't selected any OAuth providers, the modal shows no social-login buttons. Editing an unrelated setting won't accidentally disable OAuth — Para only restricts the provider list once you've explicitly configured it.
## External Wallets
Configures third-party wallet connections (MetaMask, Phantom, Keplr, and more).
### Connection Type
How a connected external wallet maps to a Para account:
| Option | Behavior |
|---|---|
| **Standard Connection** | Creates a Para user account linked to the connected wallet. No signature required. |
| **With Verification** | Creates a Para user account and asks the user to sign a message to prove wallet ownership. |
| **Para Account** | Creates a full Para account with an embedded wallet alongside the user's connected external wallet. Requires a verification signature. |
| **Connection Only** | Connects the wallet for use in your app only. No Para session is created, and Para features (sessions, recovery, embedded wallets) are unavailable. |
### Other settings
- **Project ID** — your WalletConnect project ID (up to 64 characters), used to enable WalletConnect-based wallets.
- **App Description** — a short description (up to 256 characters) surfaced to some wallets and networks during connection (e.g. WalletConnect session metadata).
- **Wallet list** — select which external wallets to offer; drag to reorder. Available wallets are filtered by the networks you enabled in **Setup**.
## Two-Factor Authentication
Requires users to set up a TOTP authenticator (e.g. Google Authenticator) when creating their account. The 2FA challenge is used during the **recovery flow** — it does not gate normal logins.
The Developer Portal should be the default place to manage 2FA, but SDK instances can still override it with `configOverrides.authConfig.twoFactorAuthEnabled` when a per-instance value is required.
## Guest Mode
Allows a user to **Continue as Guest** — this bypasses authentication and normal onboarding and creates a wallet for the user.
## Hide "Wallet" Language
Suppresses wallet-related terminology and on-chain wallet displays in the modal. Useful for apps that want a familiar, non-crypto feel for their users.
## How these are enforced
The SDK enforces the resolved settings at its auth and wallet entry points, not just in the modal. Calling a method for something you've **explicitly disabled** (e.g. an OAuth provider not in your list, or guest mode turned off) throws a `PartnerConfigError`. See [How Configuration Works → Enforcement](/v3/react/guides/customization/configuration#enforcement-partnerconfigerror) for the error codes.
## Next steps
- [How Configuration Works](/v3/react/guides/customization/configuration) — layering, overrides, and enforcement
- [Style the Modal](/v3/react/guides/customization/modal-theming) — colors, fonts, and CSS overrides
# Developer Portal Email Branding
Source: https://docs.getpara.com/v3/react/guides/customization/developer-portal-email-branding
import DeveloperPortalEmailBranding from '/snippets/v3/developer-portal/email-branding.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Payment Integration
Source: https://docs.getpara.com/v3/react/guides/customization/developer-portal-payments
import DeveloperPortalPayments from '/snippets/v3/developer-portal/payments.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Security Settings
Source: https://docs.getpara.com/v3/react/guides/customization/developer-portal-security
import DeveloperPortalSecurity from '/snippets/v3/developer-portal/security.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Get Your API Key
Source: https://docs.getpara.com/v3/react/guides/customization/developer-portal-setup
import DeveloperPortalSetup from '/snippets/v3/developer-portal/setup.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Export Private Key
Source: https://docs.getpara.com/v3/react/guides/customization/export-private-key
## Integration Methods
The `useExportPrivateKey` hook will automatically open a popup window where your user can reauthenticate their session and then view and copy the private key for one of their connected wallets.
The hook can only be used from within your app's `ParaProvider` context. By default, the key exported will be that for the currently selected wallet, available from the `useWallet` or `useWalletState` hooks.
```tsx App.tsx
import { useExportPrivateKey, useWallet} from "@getpara/react-sdk";
export function App() {
const { data: activeWallet } = useWallet();
const { mutate: exportPrivateKey, isPending } = useExportPrivateKey();
return (
{
exportPrivateKey({
walletId: activeWallet?.id, // Optional
});
}}
>
Export Private Key
);
}
```
If you are using a custom UI implementation or you would like to control how the portal URL is managed, you can use the client's `exportPrivateKey` method directly.
```tsx AppContent.tsx
import { useClient, useWallet} from "@getpara/react-sdk";
export function App() {
const para = useClient();
const { data: activeWallet } = useWallet();
const [isPending, setIsPending] = useState(false);
return (
{
setIsPending(true);
para.exportPrivateKey({
shouldOpenPopup: false, // If false, only the URL is created and `popupWindow` will be undefined
walletId: activeWallet?.id, // Optional
})
.then(({ url, popupWindow }) => {
// Do something with the URL and/or popupWindow
})
.catch((error) => {
console.error('Error exporting private key:', error);
})
.finally(() => {
setIsPending(false);
})
}}
>
Export Private Key
);
}
```
import ExportPrivateKeyLimitations from '/snippets/v3/export-private-key-limitations.mdx';
# Guest Mode
Source: https://docs.getpara.com/v3/react/guides/customization/guest-mode
import GuestModeConcept from '/snippets/v3/guest-mode-concept.mdx';
## Integration Methods
There are two primary ways to implement Guest Mode:
The easiest way to implement Guest Mode is via the Para Modal. Simply set the corresponding configuration setting (`isGuestModeEnabled`) in your `ParaProvider` configuration to `true`.
This setting adds a "Continue as Guest" option to the modal sign-in screen, which closes the modal and performs wallet setup in the background. If the modal is reopened, guest users will see a special version of the account screen, from which they can proceed to finish signing up and then claim their existing wallets.
```tsx App.tsx {13}
import { ParaProvider, Environment } from "@getpara/react-sdk";
function App() {
return (
{
console.log('Guest wallets created!', event.detail);
}
}}
>
);
}
```
If you are using a custom UI implementation or you would like to enter Guest Mode before your user opens the Para Modal, you can use the `useCreateGuestWallets` hook to create guest wallets programmatically:
```tsx AppContent.tsx
import { useCreateGuestWallets } from "@getpara/react-sdk";
// This component must be wrapped within a `ParaProvider` to function properly
function AppContent() {
const { createGuestWallets, isPending, isError, error } = useCreateGuestWallets();
const onClickGuestLoginButton = () => {
createGuestWallets(
undefined,
{
onSuccess: (wallets) => {
console.log('Guest wallets created, app is now in Guest Mode:', wallets);
},
onError: (error) => {
console.error('Error creating guest wallets:', error);
},
onSettled: () => {
console.log('Guest wallets creation process settled.');
}
}
)
};
// ...
}
```
### Tracking Guest Wallet Creation
Whether you use the modal or a custom solution, you can monitor and reflect guest wallet creation status by using the `useCreateGuestWalletsState` hook. For example, you will likely want to block any signing-related interface actions until the wallets have been created.
```tsx AppContent.tsx
import { useCreateGuestWalletsState } from "@getpara/react-sdk";
function AppContent() {
const { isPending: isCreatingGuestWallets, error } = useCreateGuestWalletsState();
if (error) {
console.error('Error creating guest wallets:', error);
return Error creating guest wallets.
;
}
return (
{isCreatingGuestWallets ? (
Creating guest wallets...
) : (
Guest wallets created successfully!
)}
)
}
```
Due to implementation details of React Query, the `useCreateGuestWallets` hook will not reliably reflect the status of the Para Modal's guest wallet creation process. To monitor the modal operation's status from the rest of your app, you *must* instead use `useCreateGuestWalletsState`. The hook's return type resembles React Query's `UseMutationReturnType` type and has most of the same fields.
## Limitations
Currently, guest wallets are prevented from buying or selling crypto through the integrated onramp providers. If a guest wallet is funded, a message is presented to the user in the Para Modal account screen encouraging them to sign up and retain access to their funds. You are, of course, free to fund guest wallets if you desire, but note that you must be careful to maintain user access to the wallets to ensure no funds are lost.
We recommend using `getUserShare` and `setUserShare` to save and restore the user share for a guest wallet, just as you would for a pregenerated wallet.
# Configure Authentication
Source: https://docs.getpara.com/v3/react/guides/customization/modal-authentication
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import OAuthMethods from '/snippets/v3/definitions/types/oAuthMethods.mdx';
import DisableEmailLogin from '/snippets/v3/definitions/types/disableEmailLogin.mdx';
import DisablePhoneLogin from '/snippets/v3/definitions/types/disablePhoneLogin.mdx';
import DefaultAuthIdentifier from '/snippets/v3/definitions/types/defaultAuthIdentifier.mdx';
import AuthLayout from '/snippets/v3/definitions/types/authLayout.mdx';
import IsGuestModeEnabled from '/snippets/v3/definitions/types/isGuestModeEnabled.mdx';
Configure how users authenticate through the Para Modal, including OAuth providers, email/phone options, and layout customization.
## OAuth Methods
```tsx
paraModalConfig={{
oAuthMethods: ["GOOGLE", "TWITTER", "DISCORD", "APPLE"]
}}
```
Available OAuth providers:
- `GOOGLE` - Google OAuth
- `TWITTER` - Twitter/X OAuth
- `APPLE` - Apple OAuth
- `DISCORD` - Discord OAuth
- `FACEBOOK` - Facebook OAuth
- `FARCASTER` - Farcaster OAuth
- `TELEGRAM` - Telegram OAuth
- `CUSTOM_OIDC` - Your own OpenID Connect provider
`CUSTOM_OIDC` is enabled here like any other method, but its provider details (issuer, client ID, secret) are configured on your partner record, not via `paraModalConfig`. See [Set up Custom OIDC](/v3/general/developer-portal-custom-oidc).
## Email and Phone Login
```tsx
paraModalConfig={{
disableEmailLogin: false,
disablePhoneLogin: true, // Only allow email and OAuth
oAuthMethods: ["GOOGLE", "TWITTER"]
}}
```
## Default Authentication Identifier
```tsx
paraModalConfig={{
defaultAuthIdentifier: "user@example.com" // or "+15555555555"
}}
```
Phone numbers should be in international format: `+15555555555`
You can also pass `defaultAuthIdentifier` directly to `openModal()` for dynamic scenarios where the identifier is known at the time of opening:
```tsx
const { openModal } = useModal();
// Pass identifier at open time instead of at the provider level
openModal({ defaultAuthIdentifier: "user@example.com" });
```
## Authentication Layout
```tsx
paraModalConfig={{
authLayout: ["AUTH:CONDENSED", "EXTERNAL:FULL"]
}}
```
Available layout options:
- `AUTH:FULL` - Full authentication component
- `AUTH:CONDENSED` - Condensed authentication component
- `EXTERNAL:FULL` - Full external wallet component
- `EXTERNAL:CONDENSED` - Condensed external wallet component
Use our to visualize different layout configurations before implementing them.
## Guest Mode
```tsx
paraModalConfig={{
isGuestModeEnabled: true
}}
```
Guest mode allows users to interact with your application without completing full authentication. Users receive provisional wallets that can later be upgraded to full accounts.
## Complete Authentication Example
```tsx
{children}
```
# Handle Modal Events
Source: https://docs.getpara.com/v3/react/guides/customization/modal-events
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import OnModalStepChange from '/snippets/v3/definitions/types/onModalStepChange.mdx';
import OnClose from '/snippets/v3/definitions/types/onClose.mdx';
import SupportedAccountLinksConfig from '/snippets/v3/definitions/types/supportedAccountLinksConfig.mdx';
import BareModal from '/snippets/v3/definitions/types/bareModal.mdx';
import HideWallets from '/snippets/v3/definitions/types/hideWallets.mdx';
import OnRampTestMode from '/snippets/v3/definitions/types/onRampTestMode.mdx';
import ClassName from '/snippets/v3/definitions/types/className.mdx';
import LoginTransitionOverride from '/snippets/v3/definitions/types/loginTransitionOverride.mdx';
import CreateWalletOverride from '/snippets/v3/definitions/types/createWalletOverride.mdx';
Handle modal lifecycle events, configure account linking, and customize advanced modal behaviors.
## Event Callbacks
### Modal Step Changes
### Modal Close
```tsx
paraModalConfig={{
onModalStepChange: (stepInfo) => {
console.log('Modal step changed:', stepInfo);
// Track analytics, update UI state, etc.
},
onClose: () => {
console.log('Modal closed');
// Clean up, redirect, etc.
}
}}
```
## Account Linking
```tsx
paraModalConfig={{
supportedAccountLinks: [
"EMAIL",
"PHONE",
"GOOGLE",
"TWITTER",
"EXTERNAL_WALLET"
]
}}
```
## Advanced Configuration
### Bare Modal
```tsx
paraModalConfig={{
bareModal: true
}}
```
Use this when embedding the modal inline or when you want to provide your own backdrop.
### Hide Wallet Terminology
```tsx
paraModalConfig={{
hideWallets: true
}}
```
Use this for applications where wallet terminology may confuse users or isn't relevant to your use case.
### On-Ramp Test Mode
```tsx
paraModalConfig={{
onRampTestMode: true
}}
```
Enable this during development to test on-ramp flows without processing real transactions.
### Custom CSS Class
```tsx
paraModalConfig={{
className: "my-custom-modal"
}}
```
## Custom Overrides
### Login Transition Override
### Wallet Creation Override
```tsx
paraModalConfig={{
loginTransitionOverride: async (para) => {
// Custom login transition logic
await customLoginHandler(para);
},
createWalletOverride: async (para) => {
// Custom wallet creation logic
const result = await customWalletCreation(para);
return {
walletIds: result.walletIds,
recoverySecret: result.secret
};
}
}}
```
## Complete Events Example
```tsx
{
console.log('Step changed:', stepInfo);
},
onClose: () => {
console.log('Modal closed');
},
// Account linking
supportedAccountLinks: [
"EMAIL",
"PHONE",
"GOOGLE",
"TWITTER",
"EXTERNAL_WALLET"
],
// Advanced options
bareModal: false,
hideWallets: false,
onRampTestMode: false,
className: "my-app-modal"
}}
>
{children}
```
# Configure Security
Source: https://docs.getpara.com/v3/react/guides/customization/modal-security
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import TwoFactorAuthEnabled from '/snippets/v3/definitions/types/twoFactorAuthEnabled.mdx';
import RecoverySecretStepEnabled from '/snippets/v3/definitions/types/recoverySecretStepEnabled.mdx';
import CurrentStepOverride from '/snippets/v3/definitions/types/currentStepOverride.mdx';
Configure security features for the Para Modal including two-factor authentication and recovery options.
## Two-Factor Authentication
```tsx
paraModalConfig={{
twoFactorAuthEnabled: true
}}
```
When enabled, users will be prompted to set up two-factor authentication during the account creation flow.
## Recovery Secret
```tsx
paraModalConfig={{
recoverySecretStepEnabled: true
}}
```
When enabled, users will be shown their recovery secret during wallet creation, allowing them to back up their wallet.
## Combined Security Configuration
```tsx
paraModalConfig={{
twoFactorAuthEnabled: true,
recoverySecretStepEnabled: true
}}
```
## Step Override
Control which step the modal displays when opened:
```tsx
paraModalConfig={{
currentStepOverride: "ACCOUNT_MAIN" // or "account_main"
}}
```
Authentication Steps:
- `AUTH_MAIN` - Main authentication options
- `AUTH_MORE` - Additional authentication methods
- `AWAITING_OAUTH` - OAuth authentication in progress
- `VERIFICATIONS` - Email/phone verification
Wallet Creation Steps:
- `BIOMETRIC_CREATION` - Biometric setup
- `PASSWORD_CREATION` - Password creation
- `SECRET` - Recovery secret display
- `AWAITING_WALLET_CREATION` - Wallet creation in progress
Account Management Steps:
- `ACCOUNT_MAIN` - Main account view
- `ACCOUNT_PROFILE` - Profile management
- `CHAIN_SWITCH` - Network selection
External Wallet Steps:
- `EX_WALLET_MORE` - External wallet options
- `EX_WALLET_SELECTED` - Selected external wallet
Funds Management Steps:
- `ADD_FUNDS_BUY` - Buy crypto interface
- `ADD_FUNDS_RECEIVE` - Receive crypto interface
- `ADD_FUNDS_WITHDRAW` - Withdraw crypto interface
Security Steps:
- `SETUP_2FA` - Two-factor authentication setup
- `VERIFY_2FA` - Two-factor authentication verification
Setting an invalid step or a step that requires previous steps to be completed may cause unexpected behavior. Ensure the step override makes sense in your authentication flow.
## Complete Security Example
```tsx
{children}
```
# Style the Modal
Source: https://docs.getpara.com/v3/react/guides/customization/modal-theming
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import Logo from '/snippets/v3/definitions/types/logo.mdx';
import ThemeConfig from '/snippets/v3/definitions/types/themeConfig.mdx';
Customize the visual appearance of the Para Modal to match your application's design system. Configure branding in the Developer Portal by default, and use `configOverrides` when a modal instance needs code-level overrides.
## Logo Configuration
```tsx
configOverrides={{
modalConfig: {
logo: "https://yourdomain.com/logo.png"
}
}}
```
For optimal display, use a logo image with dimensions of 372px x 160px.
## Theme Configuration
In v3, the theme system uses **OKLCH color mixing** to generate a complete, harmonious palette from just two colors: `foregroundColor` and `backgroundColor`. You no longer need to specify individual colors for buttons, accents, text, or dark mode variants.
### Basic Theme Example
```tsx
configOverrides={{
themeConfig: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
mode: "light",
borderRadius: "md",
font: "Arial, sans-serif"
}
}}
```
### Dark Mode Configuration
Just provide dark colors for `backgroundColor` and `foregroundColor`. The system auto-detects dark mode from the background color lightness and generates an appropriate palette:
```tsx
configOverrides={{
themeConfig: {
foregroundColor: "#0A84FF",
backgroundColor: "#1C1C1E",
}
}}
```
In v3, separate dark mode color properties (`darkForegroundColor`, `darkBackgroundColor`, `darkAccentColor`) are no longer needed.
The palette is auto-generated from your `foregroundColor` and `backgroundColor`, and dark/light mode is auto-detected from the background color lightness.
Use the `mode` property only if you need to override the auto-detection (e.g., for a background color near the light/dark boundary that gets misclassified).
## Foreground Mix Ratio
The `foregroundMixRatio` controls how much your `foregroundColor` bleeds into UI surfaces like buttons, inputs, and borders. The default is `0.04`.
```tsx
configOverrides={{
themeConfig: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
foregroundMixRatio: 0.12
}
}}
```
| Value | Effect |
|-------|--------|
| `0.04` | Default, subtle tinting: surfaces stay close to the background color |
| `0.08` | Moderate tinting: foreground color is more visible in surfaces |
| `0.15` | Strong tinting: foreground color is more prominent across all surfaces |
If your background has color (is not a neutral gray/white/black), the theme system uses the background color's hue for surface variations.
If your background is neutral, the foreground color provides the hue for surfaces via the mix ratio.
## Advanced Customization with CSS Overrides
For granular control over specific UI elements, use the `cssOverrides` property to set raw CSS custom properties. Overrides are applied **after** the palette is generated — they replace specific generated values without affecting the rest of the theme — and are **scoped to the Para modal**, so they never leak into your app's styles.
```tsx
configOverrides={{
themeConfig: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
mode: "light",
cssOverrides: {
"--para-color-primary": "#007AFF",
"--para-color-accent": "#1A1A2E",
"--para-color-destructive": "#E74C3C"
}
}
}}
```
`cssOverrides` is an advanced escape hatch. Most applications will get excellent results using just `foregroundColor` and `backgroundColor`.
Only use `cssOverrides` when you need to override a specific generated color.
### Where to set overrides
| Surface | Where | Scope |
|---|---|---|
| `themeConfig.cssOverrides` | In code, on `configOverrides.themeConfig` | This modal instance |
The SDK can read CSS override values that already exist on partner config, but the Developer Portal does not expose a CSS override editor today.
### Available CSS Variables
These are the custom properties the modal actually consumes, so overriding them takes effect:
| Variable | Description |
|----------|-------------|
| `--para-color-background` | Modal background |
| `--para-color-foreground` | Primary text color |
| `--para-color-primary` | Primary accent (buttons, links, active states) |
| `--para-color-primary-foreground` | Text on primary-colored elements |
| `--para-color-secondary` | Secondary element backgrounds |
| `--para-color-secondary-foreground` | Text on secondary elements |
| `--para-color-muted` | Muted / input backgrounds |
| `--para-color-muted-foreground` | Placeholder and subtle text |
| `--para-color-accent` | Accent highlights |
| `--para-color-accent-foreground` | Text on accent elements |
| `--para-color-popover` | Popover background |
| `--para-color-popover-foreground` | Popover text |
| `--para-color-border` | Border color |
| `--para-color-input` | Input background |
| `--para-color-ring` | Focus ring color |
| `--para-color-destructive` | Destructive / error color |
| `--para-radius` | Global border radius |
To change the font, use the `font` property (below) — **not** a CSS override. The font family is applied directly to the modal, not through a `--para-*` variable.
## Custom Fonts
You can use custom fonts by importing them in your global CSS and specifying the font family:
```tsx
configOverrides={{
themeConfig: {
font: "Inter, sans-serif"
}
}}
```
Ensure your custom font is loaded before the modal renders for the best user experience.
## Password & PIN Screen Theme Limitations
Password and PIN authentication screens are rendered in an iframe. The SDK passes the resolved theme when it builds portal URLs, so iframe screens can reflect the same Developer Portal theme or `configOverrides.themeConfig`.
Dynamic theme changes made after an iframe URL has been generated may not affect that iframe until a new URL is generated.
## Complete Theming Example
```tsx
{children}
```
## Migrating from v2.x Theming
If you're upgrading from v2.x, see the [Migration Guide](/v3/introduction/migration-to-v3) for a detailed property mapping and before/after examples.
## Modal Designer
Test your theme configuration with our interactive tool:
# Customization Basics
Source: https://docs.getpara.com/v3/react/guides/customization/modal
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import ParaModalConfig from '/snippets/v3/definitions/types/paraModalConfig.mdx';
The `paraModalConfig` prop passed to `ParaProvider` controls the modal's runtime behavior and accepts a number of customization options.
In v3, the **partner record** is the source of truth for configuration (auth methods, theme, 2FA, guest mode, layout, branding). The matching `paraModalConfig` props below — such as `oAuthMethods`, `theme`, `twoFactorAuthEnabled`, `isGuestModeEnabled`, `authLayout`, `logo` — are **deprecated** and act only as a lowest-priority fallback. Configure these in the [Developer Portal](https://developer.getpara.com), and read [How Configuration Works](/v3/react/guides/customization/configuration) for the supported in-code override (`configOverrides`). The props that remain code-only are runtime/behavior props like `currentStepOverride`, `defaultAuthIdentifier`, and `onClose`.
## Basic Setup
Pass the `paraModalConfig` object to your `ParaProvider` to customize the modal:
```tsx
{children}
```
## Configuration Options
A full list of available configuration options for the `paraModalConfig` prop available on the `ParaProvider` component:
## Complete Example
Here's a comprehensive example showcasing multiple configuration options:
```tsx
{
console.log('Modal closed');
},
// ⚠️ DEPRECATED — these are now configured on the partner record
// (Developer Portal), or via `configOverrides` on ParaProvider. Shown here
// only to map the old prop names. See:
// /v3/react/guides/customization/configuration
logo: "https://yourdomain.com/logo.png",
oAuthMethods: ["GOOGLE", "TWITTER", "DISCORD"],
disablePhoneLogin: false,
disableEmailLogin: false,
authLayout: ["AUTH:FULL", "EXTERNAL:CONDENSED"],
twoFactorAuthEnabled: true,
isGuestModeEnabled: false,
theme: {
foregroundColor: "#007AFF",
backgroundColor: "#FFFFFF",
mode: "light",
borderRadius: "md",
font: "Inter, sans-serif",
foregroundMixRatio: 0.04
},
supportedAccountLinks: ["EMAIL", "PHONE", "GOOGLE", "TWITTER", "EXTERNAL_WALLET"]
}}
>
{children}
```
## Modal Designer
Test your modal configuration with our interactive tool:
## Next Steps
Explore detailed configuration guides for specific aspects of the modal:
# Overview
Source: https://docs.getpara.com/v3/react/guides/customization/overview
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para offers extensive customization options to help you create a seamless, branded authentication experience for your
users — visual appearance, authentication methods, security features, and third-party integrations.
In v3, your **partner record** is the source of truth for these settings. You manage it in the Para Developer Portal,
and the SDK reads it at runtime, so most configuration no longer needs to live in your app code. The Para Modal still
accepts props for runtime behavior and per-instance overrides, but the config-related props are now deprecated in favor
of the partner record.
## Developer Portal Customization
The is where you configure the core settings for your Para integration. These settings control technical configuration, email branding, payment providers, and security features.
## Modal Customization
The Para Modal accepts props for runtime behavior (open state, callbacks, default identifier) and per-instance overrides.
Note that the config-related props — auth methods, theme, 2FA, guest mode, layout — are now **deprecated** in favor of
the partner record; see [How Configuration Works](/v3/react/guides/customization/configuration) for the supported way to
override settings in code (`configOverrides`).
The is the easiest way to visualize your preferred modal configuration. You can then export the configuration directly into your app.
## Custom UI Authentication Options
## External Wallet Support
## Partner Add-Ons
Extend Para even further with Third Party Integrations!
# Connect Cosmos Wallets
Source: https://docs.getpara.com/v3/react/guides/external-wallets/cosmos
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import ExternalWalletConfig from '/snippets/v3/definitions/types/ExternalWalletConfig.mdx';
import ParaModalProps from '/snippets/v3/definitions/types/ParaModalProps.mdx';
This guide will walk you through the process of integrating Cosmos Wallets into your Para Modal and Para-enabled
application, allowing you to onboard new users and connect with existing users who may already have external wallets
like Keplr, Leap, Cosmostation and more.
## Prerequisites
Before integrating wallet connections, ensure you have an existing Para project with the Para Modal set up. If you
haven't set up Para yet, follow one of our Framework Setup guides like this guide.
### Setting up Cosmos Wallets
Para integrates with leading Cosmos wallets. Our integration leverages a modified fork of the React library.
#### Supported Wallets
Para supports the following Cosmos wallets:
- - A secure and user-friendly wallet for the Cosmos ecosystem
- - The interchain wallet for the Cosmos ecosystem
- - A comprehensive wallet and validator operator for Cosmos SDK-based blockchains
Import the necessary wallet connectors and chain configurations:
```typescript main.tsx
import { useState } from "react";
import {
ParaProvider,
ExternalWallet,
} from "@getpara/react-sdk";
import { cosmoshub, osmosis } from "graz/chains";
import { type ChainInfo } from "keplr-wallet/types";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
```
If you encounter type issues with `graz/chains`, you'll need to generate the chain files. You can:
1. Add a postinstall script to your package.json:
```json
{
"scripts": {
"postinstall": "graz --generate"
}
}
```
2. Or run `npx graz --generate` manually after installation
Set up your Cosmos chain configurations with the necessary RPC and REST endpoints. You can extend the existing chain configurations with your custom endpoints:
```typescript main.tsx
const cosmosChains: ChainInfo[] = [
{
...cosmoshub,
rpc: "https://rpc.cosmos.directory/cosmoshub", // Replace with your RPC endpoint
rest: "https://rest.cosmos.directory/cosmoshub", // Replace with your REST endpoint
},
{
...osmosis,
rpc: "https://rpc.cosmos.directory/osmosis",
rest: "https://rest.cosmos.directory/osmosis",
},
];
```
Similar to the Cosmos and Solana providers, configure the `ParaProvider` component by wrapping your application content in the `ParaProvider` component. Pass in the required configuration props:
```typescript main.tsx
export const App = () => {
const [chainId, setChainId] = useState(cosmoshub.chainId);
return (
{
setChainId(chainId);
},
chains: cosmosChains,
},
},
}}>{
/* Your app content */}
);
};
```
For the ParaCosmosProvider wrapping you don't need to include a QueryClientProvider as the provider already includes
one.
### External Wallet Configuration
The `ParaProvider` is built on top of and supports all of its configuration options.
## External Wallets with Linked Embedded Wallets
You can also provision linked embedded wallets for external wallets.
In this case, the external wallet would be the Para auth method for the user's embedded wallet (instead of an email or social login). Embedded wallets would be created according to your API key settings.
To enable this, you can include the `createLinkedEmbeddedForExternalWallets` prop to indicate which external wallets this setting should be applied to.
The mapping between an external wallet and its linked embedded wallet is maintained by Para and is **not deterministically generated**. If your application needs access to this mapping, you can use Para's lookup methods (such as `getWallets`) to retrieve it, or store the association in an on or off-chain system (e.g. a database, smart contract, or program).
## Advanced Provider Pattern
Setting up a dedicated provider component that encapsulates all the necessary providers and modal state management is
considered a best practice. This pattern makes it easier to manage the modal state globally and handle session
management throughout your application.
### Server-Side Rendering Considerations
When using Next.js or other SSR frameworks, proper client-side initialization is crucial since web3 functionality relies
on browser APIs. There are two main approaches:
1. Using the `'use client'` directive in Next.js 13+:
- Add the directive at the component level where browser APIs are needed
- Ensures the CosmosProvider component and its dependencies only run on the client
- Maintains better code splitting and page performance
2. Using dynamic imports:
- Lazily loads the provider component
- Automatically handles client-side only code
- Provides fallback options during loading
## Configuring the Para Modal
After setting up your providers you need to configure the ParaModal component to display the external wallets and
authentication options to your users. You need to pass in the `externalWallets` and `authLayout` configuration options
to the ParaModal component to control which of the wallets show in the modal that were specified in the provider
configuration.
### Set the modal props
```typescript
paraModalConfig={{
authLayout: ["AUTH:FULL", "EXTERNAL:FULL"],
theme: {
mode: "light",
foregroundColor: "#000000",
backgroundColor: "#FFFFFF",
},
logo: yourLogoUrl,
// ... other modal config
}}
```
#### Modal Props Config
Modal prop options for customizing the Para Modal are included below. For advanced customization options, refer to
.
## External Wallet Verification
External wallet verification adds a verification step during external connection to ensure the user owns the wallet.
Enabling this feature establishes a valid Para session, which you can later use in your app to securely validate wallet ownership.
To enable this, set the following option on your `externalWalletConfig` of your `ParaProvider`:
```
externalWalletConfig={{
includeWalletVerification: true,
...REST_OF_CONFIG
}}
```
## Connection Only Wallets
Connection only external wallets bypass all Para functionality (account creation, user tracking, etc.) when connecting an external wallet. To enable this, set the following option on your `externalWalletConfig` of your `ParaProvider`:
```
externalWalletConfig={{
connectionOnly: true,
...REST_OF_CONFIG
}}
```
Since connection only wallets bypass Para, most Para functionality will be unavailable. This includes linked embedded wallets, external wallet verification, on & off ramping, etc.
## Examples
For an example of what the Para External Wallets Modal might look like in your application, check out our live demo:
For an example code implementation using Cosmos Wallets, check out our GitHub repository:
## Next Steps
Now that you have integrated Cosmos wallets into your Para Modal, you can explore more advanced features like signing
using the Para SDK with popular libraries like `CosmJS`.
# Connect EVM Wallets
Source: https://docs.getpara.com/v3/react/guides/external-wallets/evm
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import ExternalWalletConfig from '/snippets/v3/definitions/types/ExternalWalletConfig.mdx';
import ParaModalProps from '/snippets/v3/definitions/types/ParaModalProps.mdx';
This guide will walk you through the process of integrating EVM Wallets into your Para Modal and Para-enabled
application, allowing you to onboard new users and connect with existing users who may already have external wallets
like MetaMask, Coinbase Wallet and more.
## Prerequisites
Before integrating wallet connections, ensure you have an existing Para project with the Para Modal set up. If you
haven't set up Para yet, follow one of our Framework Setup guides like this guide.
## How It Works
Para's EVM wallet integration is powered by , the popular React hooks library for Ethereum. When you configure the `ParaProvider` with external wallet support, Para automatically creates and manages the Wagmi provider internally. This means:
- **No manual Wagmi setup required** - Para handles all Wagmi provider configuration
- **All Wagmi hooks available** - Use any Wagmi hook in your application alongside Para hooks
- **Unified configuration** - Configure chains and settings through Para's `externalWalletConfig`
- **Automatic connector management** - Para controls wallet connectors based on your configuration
## Setting up EVM Wallets
Setup is simple - just wrap your app in a provider and pass the appropriate props and configuration options to the
provider. Once configured, the Para modal and wallet options will automatically appear in the modal when opened.
Para provides seamless integration with popular EVM wallets including
, , , , , , , , , , and .
**Safe App Registration**: To use Safe as an external wallet, you'll need to register your application as a Safe App in the . This is required because Safe apps need to run within Safe's context to ensure proper security and functionality. The registration process involves providing your app's details and undergoing a review by the Safe team.
### Import components
Import the wallet connectors and supporting components you need. Adjust the imports based on which wallets you want to support:
```typescript main.tsx
import {
ParaProvider,
ExternalWallet,
} from "@getpara/react-sdk";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { sepolia, celo, mainnet, polygon } from "wagmi/chains";
```
### Configure the providers
Configure the `ParaProvider` component by wrapping your application content in the `QueryClientProvider` and `ParaProvider` components. Pass in the required configuration props:
```typescript main.tsx
const queryClient = new QueryClient();
export const App = () => {
return (
{/* Your app content */}
);
};
```
The `ParaProvider` automatically creates and manages the Wagmi provider internally. You only need to provide the `QueryClientProvider` - Para handles all Wagmi setup for you.
### External Wallet Configuration
**WalletConnect (Reown) Setup:** You'll need a WalletConnect project ID if you're using their connector. Get one from
the .
You can use an empty string for testing, but this isn't recommended for production.
The `ParaProvider` extends Wagmi's provider functionality, giving you access to all in your application. Place the provider near the root of your component tree for optimal performance.
## Accessing the Wagmi Configuration
Para provides two methods to access the Wagmi configuration depending on your use case:
### During Runtime (Inside ParaProvider)
Use the `getWagmiConfig` function when you need the configuration inside the ParaProvider context but outside of React hooks:
```typescript
import { getWagmiConfig } from "@getpara/evm-wallet-connectors";
// Use anywhere inside ParaProvider context
const wagmiConfig = getWagmiConfig();
// Now you can use with @wagmi/core actions
import { getAccount } from '@wagmi/core'
const account = getAccount(wagmiConfig)
```
### Before ParaProvider Initialization
If you need the Wagmi config before the ParaProvider mounts (e.g., for server-side operations or early initialization), use `createParaWagmiConfig`:
```typescript
import { createParaWagmiConfig } from "@getpara/evm-wallet-connectors";
import ParaWeb from "@getpara/react-sdk";
// Initialize your Para client
// IMPORTANT: This client MUST be provided to the paraClientConfig object of the ParaProvider!
const para = new ParaWeb(YOUR_ENV, YOUR_API_KEY)
// Initialize config early
const wagmiConfig = createParaWagmiConfig(para, {
chains: [mainnet, polygon],
// ... other wagmi config options
});
// ParaProvider will automatically use this pre-created config
// when it mounts later
```
The `createParaWagmiConfig` function creates the same configuration that ParaProvider would create internally. This enables you to use the config with `@wagmi/core` actions before your React app renders.
## Server-Side Rendering (SSR) Considerations
When using Next.js or other SSR frameworks, proper client-side initialization is crucial since web3 functionality relies on browser APIs. **SSR management is the developer's responsibility**, and Para provides the tools to handle it effectively.
### Handling Hydration Issues
If you encounter hydration errors related to `@getpara/evm-wallet-connectors`, this indicates that Wagmi's store hydration is happening too eagerly. Since Para uses Wagmi internally, all apply:
1. **Enable SSR mode** in your configuration:
```typescript
externalWalletConfig={{
evmConnector: {
config: {
chains: [mainnet],
ssr: true, // Enable SSR support
},
},
// ... rest of config
}}
```
2. **Use dynamic imports** for client-side only rendering:
```typescript
import dynamic from 'next/dynamic'
const ParaProvider = dynamic(
() => import('@getpara/react-sdk').then(mod => mod.ParaProvider),
{ ssr: false }
)
```
3. **Add the `'use client'` directive** in Next.js 13+:
```typescript
'use client'
import { ParaProvider } from '@getpara/react-sdk'
export function Providers({ children }) {
// Provider implementation
}
```
### Cookie-Based Persistence
For advanced SSR scenarios where you want to persist wallet connection state across server renders, implement :
```typescript
import { cookieStorage, createStorage } from 'wagmi'
externalWalletConfig={{
evmConnector: {
config: {
chains: [mainnet],
ssr: true,
storage: createStorage({
storage: cookieStorage,
}),
},
},
// ... rest of config
}}
```
Cookie-based persistence requires proper cookie handling on your server. This is entirely managed by the developer - Para provides the configuration options, but implementation depends on your server framework.
## External Wallets with Linked Embedded Wallets
You can also provision linked embedded wallets for external wallets.
In this case, the external wallet would be the Para auth method for the user's embedded wallet (instead of an email or social login). Embedded wallets would be created according to your API key settings.
To enable this, you can include the `createLinkedEmbeddedForExternalWallets` prop to indicate which external wallets this setting should be applied to.
The mapping between an external wallet and its linked embedded wallet is maintained by Para and is **not deterministically generated**. If your application needs access to this mapping, you can use Para's lookup methods (such as `getWallets`) to retrieve it, or store the association in an on or off-chain system (e.g. a database, smart contract, or program).
## Advanced Provider Pattern
Setting up a dedicated provider component that encapsulates all the necessary providers and modal state management is
considered a best practice. This pattern makes it easier to manage the modal state globally and handle session
management throughout your application.
## Configuring the Para Modal
After setting up your providers you need to configure the ParaModal component to display the external wallets and
authentication options to your users. You need to pass in the `externalWallets` and `authLayout` configuration options
to the ParaModal component to control which of the wallets show in the modal that were specified in the provider
configuration.
### Set the modal props
```typescript
paraModalConfig={{
authLayout: ["AUTH_FULL","EXTERNAL_FULL"]
theme: {
mode: "light",
foregroundColor: "#000000",
backgroundColor: "#FFFFFF",
}
logo: yourLogoUrl
// ... other modal config
}}
```
#### Modal Props Config
Modal prop options for customizing the Para Modal are included below. For advanced customization options, refer to
.
## External Wallet Verification via SIWE
External wallet verification via Sign in With Ethereum adds a verification step during external connection to ensure the user owns the wallet.
Enabling this feature establishes a valid Para session, which you can later use in your app to securely validate wallet ownership.
To enable this, set the following option on your `externalWalletConfig` of your `ParaProvider`:
```
externalWalletConfig={{
includeWalletVerification: true,
...REST_OF_CONFIG
}}
```
## Connection Only Wallets
Connection only external wallets bypass all Para functionality (account creation, user tracking, etc.) when connecting an external wallet. To enable this, set the following option on your `externalWalletConfig` of your `ParaProvider`:
```
externalWalletConfig={{
connectionOnly: true,
...REST_OF_CONFIG
}}
```
Since connection only wallets bypass Para, most Para functionality will be unavailable. This includes linked embedded wallets, external wallet verification, on & off ramping, etc.
## Examples
For an example of what the Para External Wallets Modal might look like in your application, check out our live demo:
For an example code implementation using EVM Wallets, check out our GitHub repository:
## Next Steps
Now that you have integrated EVM wallets into your Para Modal, you can explore more advanced features like signing using
the Para SDK with popular libraries like `Ethers.js`.
# Connect Multichain Wallets
Source: https://docs.getpara.com/v3/react/guides/external-wallets/multichain
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
This guide will walk you through the process of integrating multiple blockchain wallets into your Para Modal and
Para-enabled application. By combining EVM, Solana, and Cosmos wallet support, you can provide users with a seamless
multi-chain experience.
## Prerequisites
Before integrating wallet connections, ensure you have an existing Para
project with the Para Modal set up. If you haven't set up Para yet, follow one
of our Framework Setup guides like this guide.
## Setting up Multichain Support
Supporting multiple blockchain ecosystems is simple, all you need to do is install the necessary wallet connectors and
configure them within your Para Provider. Instructions for each connector can be found in the respective guides for each
ecosystem:
Multi chain wallets can only be connected to one chain at a time. Any wallets
that Para is setup to support across ecosystems will give the give the user a
choice of ecosystem selection before they connect their wallet.
## Examples
Check out our live demo of the Para Modal to configure all wallets:
For a code implementation, check out our GitHub repository:
## Next Steps
Now that you have integrated multichain wallet support, explore chain-specific features and integrations:
# Connect Solana Wallets
Source: https://docs.getpara.com/v3/react/guides/external-wallets/solana
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import ExternalWalletConfig from '/snippets/v3/definitions/types/ExternalWalletConfig.mdx';
import ParaModalProps from '/snippets/v3/definitions/types/ParaModalProps.mdx';
This guide will walk you through the process of integrating Solana Wallets into your Para Modal and Para-enabled
application, allowing you to onboard new users and connect with existing users who may already have external wallets
like Phantom, Backpack and more.
## Prerequisites
Before integrating wallet connections, ensure you have an existing Para project with the Para Modal set up. If you
haven't set up Para yet, follow one of our Framework Setup guides like this guide.
## Setting up Solana Wallets
Setup is simple - just wrap your app in a provider and pass the appropriate props and configuration options to the
provider. Once configured, the Para modal and wallet options will automatically appear in the modal when opened.
Para provides seamless integration with popular Solana wallets including
, , , and .
### Import components
Import the wallet connectors and supporting components you need. Adjust the imports based on which wallets you want to support:
```typescript main.tsx
import { WalletAdapterNetwork } from "@solana/wallet-adapter-base";
import { clusterApiUrl } from "@solana/web3.js";
import {
ParaProvider,
ExternalWallet,
} from "@getpara/react-sdk";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
```
### Configure the Solana network
Set up your Solana network configuration. Choose the appropriate network for your deployment environment:
```typescript main.tsx
const solanaNetwork = WalletAdapterNetwork.Devnet;
const endpoint = clusterApiUrl(solanaNetwork);
```
### Configure the providers
Configure the `ParaProvider` component by wrapping your application content in the `QueryClientProvider` and `ParaProvider` components. Pass in the required configuration props:
```typescript main.tsx
export const App = () => {
return (
{/* Your app content */}
);
};
```
#### External Wallet Configuration
## External Wallets with Linked Embedded Wallets
You can also provision linked embedded wallets for external wallets.
In this case, the external wallet would be the Para auth method for the user's embedded wallet (instead of an email or social login). Embedded wallets would be created according to your API key settings.
To enable this, you can include the `createLinkedEmbeddedForExternalWallets` prop to indicate which external wallets this setting should be applied to.
The mapping between an external wallet and its linked embedded wallet is maintained by Para and is **not deterministically generated**. If your application needs access to this mapping, you can use Para's lookup methods (such as `getWallets`) to retrieve it, or store the association in an on or off-chain system (e.g. a database, smart contract, or program).
## Advanced Provider Pattern
Setting up a dedicated provider component that encapsulates all the necessary providers and modal state management is
considered a best practice. This pattern makes it easier to manage the modal state globally and handle session
management throughout your application.
### Server-Side Rendering Considerations
When using Next.js or other SSR frameworks, proper client-side initialization is crucial since web3 functionality relies
on browser APIs. There are two main approaches:
1. Using the `'use client'` directive in Next.js 13+:
- Add the directive at the component level where browser APIs are needed. If using a custom provider, add the
directive to the top of the provider file.
- Ensures the Web3Provider component and its dependencies only run on the client side
2. Using dynamic imports:
- In Next.js, use the `dynamic` function to import the provider component with `{ ssr: false }`.
- Lazily loads the provider component
- Automatically handles client-side only code
- Provides fallback options during loading
## Configuring the Para Modal
After setting up your providers you need to configure the ParaModal component to display the external wallets and
authentication options to your users. You need to pass in the `externalWallets` and `authLayout` configuration options
to the ParaModal component to control which of the wallets show in the modal that were specified in the provider
configuration.
### Set the modal props
```typescript
paraModalConfig={{
authLayout: ["AUTH_FULL","EXTERNAL_FULL"]
theme: {
mode: "light",
foregroundColor: "#000000",
backgroundColor: "#FFFFFF",
}
logo: yourLogoUrl
// ... other modal config
}}
```
#### Modal Props Config
Modal prop options for customizing the Para Modal are included below. For advanced customization options, refer to
.
## External Wallet Verification
External wallet verification adds a verification step during external connection to ensure the user owns the wallet.
Enabling this feature establishes a valid Para session, which you can later use in your app to securely validate wallet ownership.
To enable this, set the following option on your `externalWalletConfig` of your `ParaProvider`:
```
externalWalletConfig={{
includeWalletVerification: true,
...REST_OF_CONFIG
}}
```
## Connection Only Wallets
Connection only external wallets bypass all Para functionality (account creation, user tracking, etc.) when connecting an external wallet. To enable this, set the following option on your `externalWalletConfig` of your `ParaProvider`:
```
externalWalletConfig={{
connectionOnly: true,
...REST_OF_CONFIG
}}
```
Since connection only wallets bypass Para, most Para functionality will be unavailable. This includes linked embedded wallets, external wallet verification, on & off ramping, etc.
## Examples
For an example of what the Para External Wallets Modal might look like in your application, check out our live demo:
For an example code implementation using Solana Wallets, check out our GitHub repository:
## Next Steps
Now that you have integrated Solana wallets into your Para Modal, you can explore more advanced features like signing using the Para SDK with popular libraries like `Web3js`.
# React Hooks
Source: https://docs.getpara.com/v3/react/guides/hooks
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para's React hooks provide an intuitive way to manage wallet state, handle transactions, and interact with the Para SDK. These hooks are built on top of TanStack Query (React Query) for efficient data fetching and state management.
## Prerequisites
Before using Para's React hooks, ensure you have:
1. Set up the Para Modal in your application following one of our framework integration guides
2. Wrapped your application with the `ParaProvider`
3. Installed the required dependencies:
```bash
npm install @getpara/react-sdk @tanstack/react-query --save-exact
```
## Provider Setup
To use Para's React hooks, wrap your application with `ParaProvider`:
```tsx
import { ParaProvider } from "@getpara/react-sdk";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
function App() {
return (
);
}
```
`ParaProvider` renders an embedded `ParaModal` by default. Do not also render a separate ` ` unless you set `config={{ disableEmbeddedModal: true }}` on the provider.
If you're using a legacy API key (one without an environment prefix) you must provide a value to the `paraClientConfig.environment`. You can retrieve your updated API key from the Para Developer Portal at https://developer.getpara.com/
## Quick Example
Here's a simple example using multiple hooks together:
```tsx
import { useAccount, useWallet, useSignMessage, useModal } from "@getpara/react-sdk";
function WalletComponent() {
const account = useAccount();
const { data: wallet } = useWallet();
const { signMessageAsync } = useSignMessage();
const { openModal } = useModal();
const handleSign = async () => {
if (!wallet) return;
const result = await signMessageAsync({
messageBase64: Buffer.from("Hello Para!").toString("base64"),
});
console.log("Signature:", result.signature);
};
return (
{account?.isConnected ? (
Sign Message
) : (
openModal()}>Connect Wallet
)}
);
}
```
## Hooks
#### Authentication
#### Wallet Operations
#### Session Management
#### Utility Hooks
Utility hooks provide access to core functionality without React Query:
# ParaProvider
Source: https://docs.getpara.com/v3/react/guides/hooks/para-provider
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import ParaProvider from '/snippets/v3/definitions/hooks/ParaProvider.mdx';
The `ParaProvider` component wraps your React application to provide access to Para hooks and manage the SDK instance.
`ParaProvider` includes an embedded `ParaModal` by default. Do not render another ` ` in the same provider tree unless you set `config={{ disableEmbeddedModal: true }}` and intentionally manage the separate modal yourself.
## Import
```tsx
import { ParaProvider } from "@getpara/react-sdk";
```
## Usage
```tsx
import { ParaProvider } from "@getpara/react-sdk";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
const queryClient = new QueryClient();
function App() {
return (
);
}
```
## Modal Ownership
Use the embedded modal in `ParaProvider` for the standard setup. Pass modal options through `paraModalConfig` instead of rendering a separate `ParaModal`:
```tsx
```
If your app must render its own `ParaModal`, disable the embedded modal on the provider:
```tsx
```
## Loading State
By default, `ParaProvider` blocks rendering until the SDK is ready. You can customize this behavior:
### With Fallback UI
Show a loading indicator instead of a blank screen while the SDK initializes:
```tsx
}
>
```
### With Immediate Rendering
Render children immediately and let them handle the loading state using `useParaStatus()`:
```tsx
```
```tsx
import { useParaStatus } from "@getpara/react-sdk";
function YourApp() {
const { isReady } = useParaStatus();
if (!isReady) return ;
return ;
}
```
## Advanced Usage
### With Event Callbacks
```tsx
function AppWithCallbacks() {
return (
{
console.log("User logged in:", event.detail.data);
navigate("/dashboard");
},
onLogout: (event) => {
console.log("User logged out");
clearUserData();
navigate("/");
},
onWalletCreated: (event) => {
console.log("New wallet:", event.detail.data);
toast.success("Wallet created successfully!");
},
onSignMessage: (event) => {
console.log("Message signed:", event.detail.data);
analytics.track("message_signed", {
walletType: event.detail.data.walletType
});
}
}}>
);
}
```
### With Custom Para Instance
This can be useful if you need to use the Para instance outside of the React tree, i.e. in the callbacks on the ParaProvider.
```tsx
function AppWithCustomClient() {
const paraClient = useMemo(() => {
return new ParaWeb("your-api-key", {
debugMode: true,
customHeaders: {
"X-Custom-Header": "value"
}
});
}, []);
return (
);
}
```
## Notes
- The `ParaProvider` must wrap any components that use Para hooks
- It requires `QueryClientProvider` from React Query as a parent
- Event callbacks receive events with a `detail` property containing `data` and optional `error`
- The provider automatically manages session keep-alive unless disabled
- The provider automatically renders the modal unless `config.disableEmbeddedModal` is set to `true`
- By default, children are not rendered until the SDK is ready. Use `fallback` to show loading UI, or `waitForReady={false}` to render children immediately
- All child components can access Para hooks without additional setup
# useAccount
Source: https://docs.getpara.com/v3/react/guides/hooks/use-account
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseAccount from '/snippets/v3/definitions/hooks/useAccount.mdx';
The `useAccount` hook provides access to the current user's account state, including both embedded Para accounts and connected external wallets across EVM, Cosmos, and Solana chains.
## Import
```tsx
import { useAccount } from "@getpara/react-sdk";
```
## Usage
```tsx
function AccountInfo() {
const account = useAccount();
if (account.isLoading) return Loading account...
;
if (!account.isConnected) {
return Not connected
;
}
return (
Connection Type: {account.connectionType}
{account.embedded.isConnected && (
Embedded Account
User ID: {account.embedded.userId}
Auth Type: {account.embedded.authType}
Email: {account.embedded.email}
Wallets: {account.embedded.wallets?.length || 0}
)}
{account.external.connectedNetworks.length > 0 && (
External Wallets
Connected Networks: {account.external.connectedNetworks.join(', ')}
{account.external.evm.isConnected && (
EVM Address: {account.external.evm.address}
)}
{account.external.cosmos.isConnected && (
Cosmos Connected
)}
{account.external.solana.isConnected && (
Solana Public Key: {account.external.solana.publicKey?.toString()}
)}
)}
);
}
```
## Return Value Structure
The hook returns a `UseAccountReturn` object with the following properties:
### Top-Level Properties
- `isConnected: boolean` - Whether there is a wallet connected (either embedded, external or both)
- `isLoading: boolean` - Whether the account is currently loading
- `connectionType: 'embedded' | 'external' | 'both' | 'none'` - The type of connection for the account
- `'embedded'` - Only the embedded account is connected
- `'external'` - Only an external wallet is connected
- `'both'` - Both embedded and external wallets are connected
- `'none'` - No wallets are connected
### Embedded Account Properties
`embedded` object contains:
- `isConnected: boolean` - Whether the embedded Para account is connected
- `isGuestMode?: boolean` - Whether the user is in guest mode
- `userId?: string` - Unique identifier for the user
- `authType?: 'email' | 'phone' | 'farcaster' | 'telegram' | 'externalWallet'` - Authentication method used
- `email?: string` - User's email address (only if authType is 'email')
- `phone?: string` - User's phone number (only if authType is 'phone')
- `farcasterUsername?: string` - Farcaster username (only if authType is 'farcaster')
- `telegramUserId?: string` - Telegram user ID (only if authType is 'telegram')
- `externalWalletAddress?: string` - External wallet address (only if authType is 'externalWallet')
- `wallets?: Array` - Array of available wallets for the user
### External Wallet Properties
`external` object contains:
- `connectedNetworks: Array<'evm' | 'cosmos' | 'solana'>` - List of connected external networks
- `evm` - EVM wallet connection data (if connected)
- `isConnected: boolean`
- `address?: string`
- `addresses?: string[]`
- `chain?: Chain`
- `chainId?: number`
- `status: 'connected' | 'reconnecting' | 'connecting' | 'disconnected'`
- `cosmos` - Cosmos wallet connection data (if connected)
- `isConnected: boolean`
- Additional Cosmos-specific properties
- `solana` - Solana wallet adapter data (if connected)
- `isConnected: boolean`
- `isConnecting?: boolean`
- `publicKey?: PublicKey`
- `name?: string`
- `icon?: string`
## Examples
### Basic Connection Check
```tsx
function ConnectionStatus() {
const { isConnected, connectionType } = useAccount();
return (
Connected: {isConnected ? 'Yes' : 'No'}
Connection Type: {connectionType}
);
}
```
### Accessing Embedded Wallet Address
```tsx
function WalletAddress() {
const account = useAccount();
if (!account.isConnected || !account.embedded.wallets?.length) {
return No wallet connected
;
}
return (
Address: {account.embedded.wallets[0].address}
);
}
```
### Working with External Wallets
```tsx
function ExternalWalletInfo() {
const { external } = useAccount();
return (
{external.evm.isConnected && (
EVM Address: {external.evm.address}
)}
{external.cosmos.isConnected && (
Cosmos wallet connected
)}
{external.solana.isConnected && (
Solana: {external.solana.name}
)}
);
}
```
### Complete Connect Wallet Component
```tsx
function ConnectWallet() {
const { openConnectModal, openWalletModal } = useModal();
const account = useAccount();
if (account.isConnected && account.embedded.wallets?.length) {
return (
{account.embedded.wallets[0].address.slice(0, 6)}...{account.embedded.wallets[0].address.slice(-4)}
);
}
return (
Connect Wallet
);
}
```
## Notes
- The hook automatically refetches when the user's authentication state changes
- Use `isLoading` to show loading states while fetching account data
- The `embedded` account refers to Para's native wallet system
- External wallets are third-party wallets connected via standard wallet connectors
- When both embedded and external wallets are connected, `connectionType` will be `'both'`
# useAddAuthMethod
Source: https://docs.getpara.com/v3/react/guides/hooks/use-add-auth-method
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
import UseAddAuthMethod from '/snippets/v3/definitions/hooks/useAddAuthMethod.mdx';
The `useAddAuthMethod` hook returns URL that allows a users to securely add or change their auth method.
This hook is useful to upgrade passkey, password or PIN users to basic login OR to allow basic login users migrate to a passkey, password or PIN auth method.
## Import
```tsx
import { useAddAuthMethod } from "@getpara/react-sdk";
```
## Usage
```tsx
function AddAuthMethod() {
const { addAuthMethodAsync, isPending, error } = useAddAuthMethod({
openPopup: false,
});
const [email, setEmail] = useState("");
const handleAddAuthMethod = async () => {
try {
const url = await addAuthMethodAsync();
console.log("Add auth method url:", url);
// Open the URL in a new window
window.open(url, "_blank", "width=500,height=700");
} catch (err) {
console.error("Add auth method failed:", err);
}
};
return (
Add Auth Method
);
}
```
# useClient
Source: https://docs.getpara.com/v3/react/guides/hooks/use-client
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import ParaClientReturn from '/snippets/v3/definitions/types/paraClientReturn.mdx';
The `useClient` hook provides direct access to the Para client instance, allowing you to call any method available on the Para SDK.
## Import
```tsx
import { useClient } from "@getpara/react-sdk";
```
## Usage
```tsx
function ClientExample() {
const para = useClient();
const { data: wallet } = useWallet();
const getFormattedAddress = () => {
if (!para || !wallet) return "No wallet";
return para.getDisplayAddress(wallet.id, {
truncate: true,
addressType: wallet.type
});
};
const checkSessionStatus = async () => {
if (!para) return;
const isActive = await para.isSessionActive();
console.log("Session active:", isActive);
};
return (
Address: {getFormattedAddress()}
Check Session
);
}
```
## Parameters
This hook does not accept any parameters.
## Return Type
## Available Methods
When you have the Para client instance, you can access all SDK methods including:
- `getDisplayAddress()` - Format wallet addresses
- `isSessionActive()` - Check session status
- `exportSession()` - Export session for server-side use
- `findWallet()` - Find a specific wallet
- `getUserId()` - Get the current user ID
- And many more...
## Example: Advanced Usage
```tsx
function AdvancedClientUsage() {
const para = useClient();
const [sessionInfo, setSessionInfo] = useState("");
const exportCurrentSession = () => {
if (!para) return;
// Export session without signers for security
const session = para.exportSession({ excludeSigners: true });
setSessionInfo(session);
};
const checkUserDetails = async () => {
if (!para) return;
const userId = para.getUserId();
const authInfo = para.authInfo;
console.log("User ID:", userId);
console.log("Auth Info:", authInfo);
};
return (
Export Session
Check User Details
{sessionInfo &&
Session: {sessionInfo.substring(0, 50)}...
}
);
}
```
## Notes
- The client is undefined until the `ParaProvider` is fully initialized
- Always check if the client exists before using it
- The client instance is the same one passed to the `ParaProvider`
# useCreateWallet
Source: https://docs.getpara.com/v3/react/guides/hooks/use-create-wallet
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseCreateWallet from '/snippets/v3/definitions/hooks/useCreateWallet.mdx';
The `useCreateWallet` hook provides functionality to create new blockchain wallets for the authenticated user.
## Import
```tsx
import { useCreateWallet } from "@getpara/react-sdk";
```
## Usage
```tsx
function WalletCreator() {
const { createWallet, createWalletAsync, isPending, error } = useCreateWallet();
const { refetch: refetchAccount } = useAccount();
const handleCreateWallet = async () => {
try {
const result = await createWalletAsync({
wallets: [
{ type: "EVM" },
{ type: "SOLANA" }
]
});
console.log("Created wallets:", result.wallets);
await refetchAccount();
} catch (err) {
console.error("Failed to create wallets:", err);
}
};
return (
{isPending ? "Creating..." : "Create EVM & Solana Wallets"}
);
}
```
## Parameters for createWallet/createWalletAsync
The mutation functions accept a `CreateWalletParams` object with the following structure:
```typescript
interface CreateWalletParams {
wallets: WalletCreationSpec[];
}
interface WalletCreationSpec {
type: 'EVM' | 'SOLANA' | 'COSMOS' | 'STELLAR';
}
```
## Response Structure
When successful, the mutation returns a `CreateWalletResponse` object:
```typescript
interface CreateWalletResponse {
wallets: Wallet[];
}
interface Wallet {
id: string;
type: 'EVM' | 'SOLANA' | 'COSMOS' | 'STELLAR';
address: string;
}
```
## Example: Conditional Wallet Creation
```tsx
function ConditionalWalletCreator() {
const { createWalletAsync } = useCreateWallet();
const { data: account } = useAccount();
const [walletType, setWalletType] = useState("EVM");
const createWalletIfNeeded = async () => {
if (!account?.isConnected) return;
const hasWalletType = account.wallets.some(w => w.type === walletType);
if (hasWalletType) {
console.log(`User already has ${walletType} wallet`);
return;
}
try {
const result = await createWalletAsync({
wallets: [{ type: walletType }]
});
console.log(`Created ${walletType} wallet:`, result.wallets[0]);
} catch (err) {
console.error("Wallet creation failed:", err);
}
};
return (
setWalletType(e.target.value as TWalletType)}
>
EVM
Solana
Cosmos
Stellar
Create {walletType} Wallet
);
}
```
## Events
The wallet creation process triggers a `WalletCreatedEvent` that can be listened to via the `ParaProvider` callbacks:
```tsx
{
console.log("New wallet created:", event.detail);
}
}}
>
{/* Your app */}
```
## Notes
- This hook requires the user to be authenticated before creating wallets
- The hook automatically invalidates account queries on success to refresh wallet lists
- Multiple wallets can be created in a single mutation by passing multiple specifications
- Each wallet type (EVM, SOLANA, COSMOS) can only be created once per user
# useIssueJwt
Source: https://docs.getpara.com/v3/react/guides/hooks/use-issue-jwt
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseIssueJwt from '/snippets/v3/definitions/hooks/useIssueJwt.mdx';
The `useIssueJwt` hook provides functionality to request Para JWT tokens that contain attestations for the user's ID, identity, and provisioned wallets.
## Import
```tsx
import { useIssueJwt } from "@getpara/react-sdk";
```
## Usage
```tsx
function JwtTokenManager() {
const { issueJwt, issueJwtAsync, isPending, error } = useIssueJwt();
const [tokenInfo, setTokenInfo] = useState<{ token: string; keyId: string } | null>(null);
const handleIssueToken = async () => {
try {
const result = await issueJwtAsync();
setTokenInfo({
token: result.token,
keyId: result.keyId
});
await sendTokenToBackend(result.token);
} catch (err) {
console.error("Failed to issue JWT:", err);
}
};
return (
{isPending ? "Issuing..." : "Issue JWT Token"}
);
}
```
# useKeepSessionAlive
Source: https://docs.getpara.com/v3/react/guides/hooks/use-keep-session-alive
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseKeepSessionAlive from '/snippets/v3/definitions/hooks/useKeepSessionAlive.mdx';
The `useKeepSessionAlive` hook provides functionality to extend the current user session without requiring re-authentication.
## Import
```tsx
import { useKeepSessionAlive } from "@getpara/react-sdk";
```
## Usage
```tsx
function SessionManager() {
const { keepSessionAlive, keepSessionAliveAsync, isPending } = useKeepSessionAlive();
const [lastRefresh, setLastRefresh] = useState(null);
const handleKeepAlive = async () => {
try {
const success = await keepSessionAliveAsync();
if (success) {
setLastRefresh(new Date());
console.log("Session extended successfully");
}
} catch (err) {
console.error("Session extension error:", err);
}
};
return (
{isPending ? "Extending..." : "Extend Session"}
);
}
```
# useLogout
Source: https://docs.getpara.com/v3/react/guides/hooks/use-logout
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseLogout from '/snippets/v3/definitions/hooks/useLogout.mdx';
The `useLogout` hook provides functionality to log out the current user and optionally clear pregenerated wallets.
## Import
```tsx
import { useLogout } from "@getpara/react-sdk";
```
## Usage
```tsx
function LogoutButton() {
const { logout, logoutAsync, isPending } = useLogout();
const { data: account } = useAccount();
const handleLogout = async () => {
try {
await logoutAsync({
clearPregenWallets: false // Keep pregenerated wallets
});
console.log("Successfully logged out");
} catch (err) {
console.error("Logout failed:", err);
}
};
if (!account?.isConnected) {
return null;
}
return (
{isPending ? "Logging out..." : "Logout"}
);
}
```
# useModal
Source: https://docs.getpara.com/v3/react/guides/hooks/use-modal
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import IsOpen from '/snippets/v3/definitions/types/isOpen.mdx';
import OpenModal from '/snippets/v3/definitions/types/openModal.mdx';
import CloseModal from '/snippets/v3/definitions/types/closeModal.mdx';
The `useModal` hook provides methods to control the Para modal's visibility. This is useful for programmatically opening the modal for authentication or wallet management.
## Import
```tsx
import { useModal } from "@getpara/react-sdk";
```
## Usage
```tsx
function ModalControl() {
const { isOpen, openModal, closeModal } = useModal();
const { data: account } = useAccount();
return (
openModal()}>
{account?.isConnected ? "Manage Wallet" : "Connect Wallet"}
{isOpen && (
Modal is currently open
Close Modal
)}
);
}
```
## Parameters
This hook does not accept any parameters.
## Return Type
## Example: Conditional Modal Control
```tsx
function ConditionalModalExample() {
const { openModal } = useModal();
const { data: account } = useAccount();
const { data: wallet } = useWallet();
const handleAction = () => {
if (!account?.isConnected) {
// Open modal for authentication
openModal();
} else if (!wallet) {
// Open modal to select/create wallet
openModal();
} else {
// User is ready, perform action
console.log("Ready to perform action with wallet:", wallet.id);
}
};
return (
Perform Action
);
}
```
## Example: Dynamic Auth Identifier
Pass `defaultAuthIdentifier` directly to `openModal` to pre-populate the auth input at the moment the modal opens, without needing to set it at the provider level.
```tsx
function DynamicAuthExample() {
const { openModal } = useModal();
const handleLogin = (userEmail: string) => {
// Pass the identifier when opening — no need to update ParaProvider props
openModal({ defaultAuthIdentifier: userEmail });
};
return (
handleLogin("user@example.com")}>
Log In
);
}
```
## Example: Auto-open on Mount
```tsx
function AutoOpenModal() {
const { openModal } = useModal();
const { data: account } = useAccount();
useEffect(() => {
// Automatically open modal if user is not connected
if (account && !account.isConnected) {
openModal();
}
}, [account, openModal]);
return Welcome to our app!
;
}
```
## Notes
- `ParaProvider` renders the modal by default, so `useModal` can open and close that embedded modal
- If you render your own ` `, set `config={{ disableEmbeddedModal: true }}` on `ParaProvider` to avoid two modal instances
- Opening the modal when a user is not connected will show the authentication flow
- Opening the modal when a user is connected will show wallet management options
- The modal can be closed by the user clicking outside or using the close button
# useParaCosmjsAminoSigner
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-cosmjs-amino-signer
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
import UseParaCosmjsAminoSigner from '/snippets/v3/definitions/hooks/useParaCosmjsAminoSigner.mdx';
import AminoSigner from '/snippets/v3/definitions/types/aminoSigner.mdx';
import UseCosmjsAminoSignerIsLoading from '/snippets/v3/definitions/types/UseCosmjsAminoSignerIsLoading.mdx';
The `useParaCosmjsAminoSigner` hook returns a CosmJS `OfflineAminoSigner` for the user's Cosmos wallet. It supports both embedded Para wallets and external wallets.
If the user has multiple Cosmos wallets, pass `address` or `walletId` to select one. When omitted, the active wallet is used automatically.
`@getpara/react-sdk` includes `@getpara/cosmjs-v0-integration`. Install CosmJS packages such as `@cosmjs/stargate` when your app creates clients or imports CosmJS helpers directly.
## Import
```tsx
import { useParaCosmjsAminoSigner } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaCosmjsAminoSigner } from "@getpara/react-sdk";
function SignAmino() {
const { aminoSigner, isLoading } = useParaCosmjsAminoSigner();
const handleSign = async () => {
if (!aminoSigner) return;
const signDoc = {
chain_id: "cosmoshub-4",
account_number: "0",
sequence: "0",
fee: { amount: [], gas: "200000" },
msgs: [],
memo: "Hello",
};
const result = await aminoSigner.signAmino(aminoSigner.address, signDoc);
console.log("Signature:", result.signature.signature);
};
if (isLoading) return Loading...
;
return (
Address: {aminoSigner?.address}
Sign Message
);
}
```
# useParaCosmjsProtoSigner
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-cosmjs-proto-signer
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
import UseParaCosmjsProtoSigner from '/snippets/v3/definitions/hooks/useParaCosmjsProtoSigner.mdx';
import ProtoSigner from '/snippets/v3/definitions/types/protoSigner.mdx';
import UseCosmjsProtoSignerIsLoading from '/snippets/v3/definitions/types/UseCosmjsProtoSignerIsLoading.mdx';
The `useParaCosmjsProtoSigner` hook returns a CosmJS `OfflineDirectSigner` for the user's Cosmos wallet. It supports both embedded Para wallets and external wallets.
If the user has multiple Cosmos wallets, pass `address` or `walletId` to select one. When omitted, the active wallet is used automatically.
`@getpara/react-sdk` includes `@getpara/cosmjs-v0-integration`. Install CosmJS packages such as `@cosmjs/stargate` when your app creates clients or imports CosmJS helpers directly.
Use the companion mutation hook [useParaCosmjsSignAndBroadcast](/v3/react/guides/hooks/use-para-cosmjs-sign-and-broadcast) to sign and broadcast transactions.
## Import
```tsx
import { useParaCosmjsProtoSigner } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaCosmjsProtoSigner } from "@getpara/react-sdk";
import { SigningStargateClient } from "@cosmjs/stargate";
function SignDirect() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const handleSign = async () => {
if (!protoSigner) return;
const signDoc = {
bodyBytes: new TextEncoder().encode(
JSON.stringify({ messages: [], memo: "Hello" })
),
authInfoBytes: new Uint8Array(0),
chainId: "",
accountNumber: BigInt(0),
};
const result = await protoSigner.signDirect(protoSigner.address, signDoc);
console.log("Signature:", result.signature.signature);
};
if (isLoading) return Loading...
;
return (
Address: {protoSigner?.address}
Sign Message
);
}
```
# useParaCosmjsSignAndBroadcast
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-cosmjs-sign-and-broadcast
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing and broadcasting Cosmos transactions using a CosmJS signer and signing client.
`@getpara/react-sdk` includes `@getpara/cosmjs-v0-integration`. Install `@cosmjs/stargate` when your app creates signing clients or imports Stargate helpers directly.
Get the `signer` parameter from [useParaCosmjsProtoSigner](/v3/react/guides/hooks/use-para-cosmjs-proto-signer) or [useParaCosmjsAminoSigner](/v3/react/guides/hooks/use-para-cosmjs-amino-signer).
## Import
```tsx
import { useParaCosmjsSignAndBroadcast } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from "@getpara/react-sdk";
import { SigningStargateClient, coins } from "@cosmjs/stargate";
function SendTokens() {
const { protoSigner } = useParaCosmjsProtoSigner();
// Connect signing client (see useParaCosmjsProtoSigner docs)
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const handleSend = async () => {
const result = await signAndBroadcastAsync({
messages: [sendMsg],
fee: { amount: coins(5000, "uatom"), gas: "200000" },
});
console.log("Tx hash:", result.transactionHash);
};
return (
{isPending ? "Broadcasting..." : "Send Tokens"}
);
}
```
& { signAndBroadcast, signAndBroadcastAsync }", description: "Extends UseMutationResult with named signAndBroadcast (fire-and-forget) and signAndBroadcastAsync (returns Promise) aliases. isPending is true when signer/client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaEthersSendTransaction
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-ethers-send-transaction
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for sending transactions using an ethers signer from useParaEthersSigner.
`@getpara/react-sdk` includes `@getpara/ethers-v6-integration`. Install `ethers` when your app creates providers or imports ethers helpers directly.
Get the `signer` parameter from [useParaEthersSigner](/v3/react/guides/hooks/use-para-ethers-signer).
## Import
```tsx
import { useParaEthersSendTransaction } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaEthersSigner, useParaEthersSendTransaction } from "@getpara/react-sdk";
import { JsonRpcProvider, parseEther } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SendTransaction() {
const { ethersSigner } = useParaEthersSigner({ provider });
const { sendTransactionAsync, isPending } = useParaEthersSendTransaction(ethersSigner);
return (
sendTransactionAsync({ to: "0x...", value: parseEther("0.01") })} disabled={isPending}>
{isPending ? "Sending..." : "Send ETH"}
);
}
```
& { sendTransaction, sendTransactionAsync }", description: "Extends UseMutationResult with named sendTransaction (fire-and-forget) and sendTransactionAsync (returns Promise) aliases. isPending is true when signer is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaEthersSignMessage
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-ethers-sign-message
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing messages using an ethers signer from useParaEthersSigner.
`@getpara/react-sdk` includes `@getpara/ethers-v6-integration`. Install `ethers` when your app creates providers or imports ethers helpers directly.
Get the `signer` parameter from [useParaEthersSigner](/v3/react/guides/hooks/use-para-ethers-signer).
## Import
```tsx
import { useParaEthersSignMessage } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaEthersSigner, useParaEthersSignMessage } from "@getpara/react-sdk";
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignMessage() {
const { ethersSigner } = useParaEthersSigner({ provider });
const { signMessageAsync, isPending, data: signature } = useParaEthersSignMessage(ethersSigner);
return (
signMessageAsync("Hello")} disabled={isPending}>
{isPending ? "Signing..." : "Sign Message"}
{signature &&
Signature: {signature}
}
);
}
```
& { signMessage, signMessageAsync }", description: "Extends UseMutationResult with named signMessage (fire-and-forget) and signMessageAsync (returns Promise) aliases. isPending is true when signer is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaEthersSigner
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-ethers-signer
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
import UseParaEthersSigner from '/snippets/v3/definitions/hooks/useParaEthersSigner.mdx';
The `useParaEthersSigner` hook returns an ethers.js `AbstractSigner` for the user's EVM wallet. It supports both embedded Para wallets and external wallets.
If the user has multiple EVM wallets, pass `address` or `walletId` to select one. When omitted, the active wallet is used automatically.
`@getpara/react-sdk` includes `@getpara/ethers-v6-integration`. Install `ethers` when your app creates providers or imports ethers helpers directly.
Use the companion mutation hooks for common operations: [useParaEthersSignMessage](/v3/react/guides/hooks/use-para-ethers-sign-message) and [useParaEthersSendTransaction](/v3/react/guides/hooks/use-para-ethers-send-transaction).
## Import
```tsx
import { useParaEthersSigner } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaEthersSigner } from "@getpara/react-sdk";
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignMessage() {
const { ethersSigner, isLoading } = useParaEthersSigner({ provider });
const handleSign = async () => {
if (!ethersSigner) return;
const signature = await ethersSigner.signMessage("Hello");
console.log("Signature:", signature);
};
if (isLoading) return Loading...
;
return (
Sign Message
);
}
```
# useParaSolanaSignAndSend
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-solana-sign-and-send
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing and sending Solana transactions using a signer from useParaSolanaSigner.
`@getpara/react-sdk` includes `@getpara/solana-signers-v2-integration`. Install `@solana/kit` when your app creates RPC clients, builds transactions, or imports Solana helpers directly.
Get the `signer` parameter from [useParaSolanaSigner](/v3/react/guides/hooks/use-para-solana-signer).
## Import
```tsx
import { useParaSolanaSignAndSend } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaSolanaSigner, useParaSolanaSignAndSend } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function SendTransaction() {
const { solanaSigner } = useParaSolanaSigner({ rpc });
const { signAndSendAsync, isPending } = useParaSolanaSignAndSend(solanaSigner);
return (
signAndSendAsync({ transactions: [compiledTx] })} disabled={isPending}>
{isPending ? "Sending..." : "Sign & Send"}
);
}
```
& { signAndSend, signAndSendAsync }", description: "Extends UseMutationResult with named signAndSend (fire-and-forget) and signAndSendAsync aliases. isPending is true when signer is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaSolanaSigner
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-solana-signer
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
import UseParaSolanaSigner from '/snippets/v3/definitions/hooks/useParaSolanaSigner.mdx';
import SolanaSigner from '/snippets/v3/definitions/types/solanaSigner.mdx';
import UseSolanaSignerIsLoading from '/snippets/v3/definitions/types/UseSolanaSignerIsLoading.mdx';
The `useParaSolanaSigner` hook returns a Solana signer for the user's Solana wallet. It supports both embedded Para wallets and external wallets.
If the user has multiple Solana wallets, pass `address` or `walletId` to select one. When omitted, the active wallet is used automatically.
`@getpara/react-sdk` includes `@getpara/solana-signers-v2-integration`. Install `@solana/kit` when your app creates RPC clients or imports Solana helpers directly.
Use the companion mutation hook [useParaSolanaSignAndSend](/v3/react/guides/hooks/use-para-solana-sign-and-send) to sign and send transactions.
## Import
```tsx
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
```
## Usage
```tsx
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc, getUtf8Encoder } from "@solana/kit";
import bs58 from "bs58";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function SignMessage() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const handleSign = async () => {
if (!solanaSigner) return;
const messageBytes = new Uint8Array(getUtf8Encoder().encode("Hello"));
const signatureResult = await solanaSigner.signMessages([
{ content: messageBytes, signatures: {} },
]);
const signatureBytes = signatureResult[0][solanaSigner.address];
console.log("Signature:", bs58.encode(signatureBytes));
};
if (isLoading) return Loading...
;
return (
Address: {solanaSigner?.address}
Sign Message
);
}
```
# useParaStellarSignTransaction
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-stellar-sign-transaction
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing Stellar transactions using a signer from useParaStellarSigner.
`@getpara/react-sdk` includes `@getpara/stellar-sdk-v14-integration`. Install `@stellar/stellar-sdk` when your app imports Stellar helpers such as `Networks` or transaction builders directly.
Get the `signer` parameter from [useParaStellarSigner](/v3/react/guides/hooks/use-para-stellar-signer).
## Import
```tsx
import { useParaStellarSignTransaction } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaStellarSigner, useParaStellarSignTransaction } from "@getpara/react-sdk";
import { Networks } from "@stellar/stellar-sdk";
function SignTransaction() {
const { stellarSigner } = useParaStellarSigner({ networkPassphrase: Networks.PUBLIC });
const { signTransactionAsync, isPending } = useParaStellarSignTransaction(stellarSigner);
const handleSign = async () => {
if (!stellarSigner) return;
const { signedTxXdr } = await signTransactionAsync(transaction.toXDR());
console.log("Signed XDR:", signedTxXdr);
};
return (
{isPending ? "Signing..." : "Sign Transaction"}
);
}
```
& { signTransaction, signTransactionAsync }", description: "Extends UseMutationResult with named signTransaction (fire-and-forget) and signTransactionAsync (returns Promise<{ signedTxXdr }>) aliases. isPending is true when signer is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaStellarSigner
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-stellar-signer
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
import UseParaStellarSigner from '/snippets/v3/definitions/hooks/useParaStellarSigner.mdx';
The `useParaStellarSigner` hook returns a Stellar signer for the user's Stellar wallet.
If the user has multiple Stellar wallets, pass `address` or `walletId` to select one. When omitted, the active wallet is used automatically.
`@getpara/react-sdk` includes `@getpara/stellar-sdk-v14-integration`. Install `@stellar/stellar-sdk` when your app imports Stellar helpers such as `Networks` or transaction builders directly.
Use the companion mutation hook [useParaStellarSignTransaction](/v3/react/guides/hooks/use-para-stellar-sign-transaction) to sign transactions.
## Import
```tsx
import { useParaStellarSigner } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaStellarSigner } from "@getpara/react-sdk";
import { Networks } from "@stellar/stellar-sdk";
function SignAuthEntry() {
const { stellarSigner, isLoading } = useParaStellarSigner({
networkPassphrase: Networks.TESTNET,
});
const handleSign = async () => {
if (!stellarSigner) return;
const result = await stellarSigner.signAuthEntry("base64EncodedEntry");
console.log("Signed:", result.signedAuthEntry);
};
if (isLoading) return Loading...
;
return (
Address: {stellarSigner?.address}
Sign Auth Entry
);
}
```
# useParaViemAccount
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-viem-account
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
import UseParaViemAccount from '/snippets/v3/definitions/hooks/useParaViemAccount.mdx';
import ViemAccount from '/snippets/v3/definitions/types/viemAccount.mdx';
import UseViemAccountIsLoading from '/snippets/v3/definitions/types/UseViemAccountIsLoading.mdx';
The `useParaViemAccount` hook returns a Viem `LocalAccount` for the user's EVM wallet. It supports both embedded Para wallets and external wallets.
If the user has multiple EVM wallets, pass `address` or `walletId` to select one. When omitted, the active wallet is used automatically.
`@getpara/react-sdk` includes `@getpara/viem-v2-integration`. Install `viem` when your app imports Viem helpers such as chains, transports, parsers, or types directly.
## Import
```tsx
import { useParaViemAccount } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaViemAccount } from "@getpara/react-sdk";
function WalletAddress() {
const { viemAccount, isLoading } = useParaViemAccount();
if (isLoading) return Loading...
;
return (
Address: {viemAccount?.address ?? "No wallet"}
);
}
```
# useParaViemClient
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-viem-client
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
import UseParaViemClient from '/snippets/v3/definitions/hooks/useParaViemClient.mdx';
import ViemClient from '/snippets/v3/definitions/types/viemClient.mdx';
import UseViemClientIsLoading from '/snippets/v3/definitions/types/UseViemClientIsLoading.mdx';
The `useParaViemClient` hook returns a Viem `WalletClient` for the user's EVM wallet. It supports both embedded Para wallets and external wallets.
If the user has multiple EVM wallets, pass `address` or `walletId` to select one. When omitted, the active wallet is used automatically.
`@getpara/react-sdk` includes `@getpara/viem-v2-integration`. Install `viem` when your app imports Viem helpers such as chains, transports, parsers, or types directly.
Use the companion mutation hooks for common operations: [useParaViemSignMessage](/v3/react/guides/hooks/use-para-viem-sign-message), [useParaViemSendTransaction](/v3/react/guides/hooks/use-para-viem-send-transaction), [useParaViemSignTypedData](/v3/react/guides/hooks/use-para-viem-sign-typed-data), and [useParaViemWriteContract](/v3/react/guides/hooks/use-para-viem-write-contract).
## Import
```tsx
import { useParaViemClient } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaViemClient } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { http } from "viem";
function SignMessage() {
const { viemClient, isLoading } = useParaViemClient({
walletClientConfig: {
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
},
});
const handleSign = async () => {
if (!viemClient) return;
const signature = await viemClient.signMessage({ message: "Hello" });
console.log("Signature:", signature);
};
if (isLoading) return Loading...
;
return (
Address: {viemClient?.account?.address}
Sign Message
);
}
```
# useParaViemSendTransaction
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-viem-send-transaction
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for sending transactions using a Viem WalletClient from useParaViemClient.
`@getpara/react-sdk` includes `@getpara/viem-v2-integration`. Install `viem` when your app imports Viem helpers such as chains, transports, parsers, or types directly.
Get the `viemClient` parameter from [useParaViemClient](/v3/react/guides/hooks/use-para-viem-client).
## Import
```tsx
import { useParaViemSendTransaction } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaViemClient, useParaViemSendTransaction } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { http, parseEther } from "viem";
function SendTransaction() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { sendTransactionAsync, isPending, data: txHash } = useParaViemSendTransaction(viemClient);
return (
sendTransactionAsync({ to: "0x...", value: parseEther("0.01") })} disabled={isPending}>
{isPending ? "Sending..." : "Send ETH"}
);
}
```
& { sendTransaction, sendTransactionAsync }", description: "Extends UseMutationResult with named sendTransaction (fire-and-forget) and sendTransactionAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaViemSignMessage
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-viem-sign-message
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing messages using a Viem WalletClient from useParaViemClient.
`@getpara/react-sdk` includes `@getpara/viem-v2-integration`. Install `viem` when your app imports Viem helpers such as chains, transports, parsers, or types directly.
Get the `viemClient` parameter from [useParaViemClient](/v3/react/guides/hooks/use-para-viem-client).
## Import
```tsx
import { useParaViemSignMessage } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaViemClient, useParaViemSignMessage } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { http } from "viem";
function SignMessage() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { signMessageAsync, isPending, data: signature } = useParaViemSignMessage(viemClient);
return (
signMessageAsync({ message: "Hello" })} disabled={isPending}>
{isPending ? "Signing..." : "Sign Message"}
{signature &&
Signature: {signature}
}
);
}
```
& { signMessage, signMessageAsync }", description: "Extends UseMutationResult with named signMessage (fire-and-forget) and signMessageAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaViemSignTransaction
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-viem-sign-transaction
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing a transaction without broadcasting it. Produces a signed serialized transaction that can be submitted later or inspected by permissions/transaction-review flows.
`@getpara/react-sdk` includes `@getpara/viem-v2-integration`. Install `viem` when your app imports Viem helpers such as chains, transports, parsers, or types directly.
Get the `viemClient` parameter from [useParaViemClient](/v3/react/guides/hooks/use-para-viem-client).
## Import
```tsx
import { useParaViemSignTransaction } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaViemClient, useParaViemSignTransaction } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { http, parseEther } from "viem";
function SignTransaction() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { signTransactionAsync, isPending, data: signedTx } = useParaViemSignTransaction(viemClient);
const handleSign = async () => {
const signed = await signTransactionAsync({
to: "0x...",
value: parseEther("0"),
type: "eip1559",
chain: sepolia,
});
console.log("Signed transaction:", signed);
};
return (
{isPending ? "Signing..." : "Sign Transaction"}
);
}
```
& { signTransaction, signTransactionAsync }", description: "Extends UseMutationResult with named signTransaction (fire-and-forget) and signTransactionAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaViemSignTypedData
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-viem-sign-typed-data
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for signing EIP-712 typed data using a Viem WalletClient from useParaViemClient.
`@getpara/react-sdk` includes `@getpara/viem-v2-integration`. Install `viem` when your app imports Viem helpers such as chains, transports, parsers, or types directly.
Get the `viemClient` parameter from [useParaViemClient](/v3/react/guides/hooks/use-para-viem-client).
## Import
```tsx
import { useParaViemSignTypedData } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaViemClient, useParaViemSignTypedData } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { http } from "viem";
function SignTypedData() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { signTypedDataAsync, isPending } = useParaViemSignTypedData(viemClient);
const handleSign = async () => {
const sig = await signTypedDataAsync({
domain: { name: "MyDApp", version: "1", chainId: 11155111 },
types: { Message: [{ name: "content", type: "string" }] },
primaryType: "Message",
message: { content: "Hello" },
});
console.log("Signature:", sig);
};
return (
{isPending ? "Signing..." : "Sign Typed Data"}
);
}
```
& { signTypedData, signTypedDataAsync }", description: "Extends UseMutationResult with named signTypedData (fire-and-forget) and signTypedDataAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useParaViemWriteContract
Source: https://docs.getpara.com/v3/react/guides/hooks/use-para-viem-write-contract
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
Mutation hook for calling state-changing contract functions using a Viem WalletClient from useParaViemClient.
`@getpara/react-sdk` includes `@getpara/viem-v2-integration`. Install `viem` when your app imports Viem helpers such as chains, transports, parsers, or types directly.
Get the `viemClient` parameter from [useParaViemClient](/v3/react/guides/hooks/use-para-viem-client).
## Import
```tsx
import { useParaViemWriteContract } from "@getpara/react-sdk";
```
## Usage
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { http, parseUnits } from "viem";
function TransferTokens() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const { writeContractAsync, isPending } = useParaViemWriteContract(viemClient);
const handleTransfer = async () => {
const hash = await writeContractAsync({
address: "0xTokenAddress...",
abi: ERC20_ABI,
functionName: "transfer",
args: ["0xRecipient...", parseUnits("10", 18)],
});
console.log("Tx hash:", hash);
};
return (
{isPending ? "Sending..." : "Transfer Tokens"}
);
}
```
& { writeContract, writeContractAsync }", description: "Extends UseMutationResult with named writeContract (fire-and-forget) and writeContractAsync (returns Promise) aliases. isPending is true when client is not ready or mutation is in-flight." }}
/>
`mutate`/`mutateAsync` are also available alongside the named aliases. All other `UseMutationResult` fields (`data`, `error`, `isSuccess`, `isError`, `reset`, etc.) work as expected.
# useRequestFaucet
Source: https://docs.getpara.com/v3/react/guides/hooks/use-request-faucet
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseRequestFaucet from '/snippets/v3/definitions/hooks/useRequestFaucet.mdx';
The `useRequestFaucet` hook requests testnet tokens for a Para wallet. It uses the currently active wallet by default, making it easy to fund a wallet immediately after creation.
Use this hook when Sepolia examples need testnet ETH for gas before sending transactions or writing contracts. For framework-agnostic Web SDK, Server SDK, or REST SDK flows, use the direct `requestFaucet({ walletId, chain: "ETHEREUM_SEPOLIA" })` method instead.
## Import
```tsx
import { useRequestFaucet } from "@getpara/react-sdk";
```
## Usage
With an active wallet, call the hook with no arguments to fund the current wallet:
```tsx
function FaucetButton() {
const { requestFaucetAsync, isPending, data } = useRequestFaucet();
return (
requestFaucetAsync()}
disabled={isPending}
>
{isPending ? "Requesting..." : "Get Testnet ETH"}
{data &&
Funded: {data.transactionHash}
}
);
}
```
## Return Value
The hook returns a standard React Query mutation result. The `data` field contains the faucet response once the request succeeds:
| Field | Type | Description |
| ----------------- | -------- | ---------------------------------------- |
| `transactionHash` | `string` | On-chain transaction hash |
| `amount` | `string` | Amount of testnet tokens sent |
| `chain` | `string` | Chain the tokens were sent on |
| `walletId` | `string` | ID of the wallet that received tokens |
| `address` | `string` | On-chain address that received tokens |
## Explicit Wallet ID
To fund a specific wallet instead of the active one, pass `walletId` directly:
```tsx
function FundSpecificWallet({ walletId }: { walletId: string }) {
const { requestFaucetAsync, isPending, data } = useRequestFaucet();
return (
requestFaucetAsync({ walletId })}
disabled={isPending}
>
{isPending ? "Requesting..." : "Fund Wallet"}
{data && (
Sent {data.amount} ETH to {data.address}
)}
);
}
```
## Notes
Rate limited to 10 requests per API key per day. Each wallet has a 24-hour cooldown between requests. Only testnet chains are supported.
A 429 response means the rate limit has been exceeded. Check the `Retry-After` header for when to retry.
# useSignMessage
Source: https://docs.getpara.com/v3/react/guides/hooks/use-sign-message
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseSignMessage from '/snippets/v3/definitions/hooks/useSignMessage.mdx';
The `useSignMessage` hook provides functionality to sign arbitrary messages with the user's wallet.
## Import
```tsx
import { useSignMessage } from "@getpara/react-sdk";
```
## Usage
```tsx
function MessageSigner() {
const { signMessage, signMessageAsync, isPending, error } = useSignMessage();
const [message, setMessage] = useState("");
const [signature, setSignature] = useState("");
const handleSign = async () => {
try {
const result = await signMessageAsync({
messageBase64: Buffer.from(message).toString("base64"),
});
if ("signature" in result) {
setSignature(result.signature);
}
} catch (err) {
console.error("Failed to sign message:", err);
}
};
return (
setMessage(e.target.value)}
placeholder="Enter message to sign"
/>
{isPending ? "Signing..." : "Sign Message"}
{signature &&
Signature: {signature}
}
);
}
```
# useSignTransaction
Source: https://docs.getpara.com/v3/react/guides/hooks/use-sign-transaction
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseSignTransaction from '/snippets/v3/definitions/hooks/useSignTransaction.mdx';
The `useSignTransaction` hook provides functionality to sign blockchain transactions with the user's wallet.
## Import
```tsx
import { useSignTransaction } from "@getpara/react-sdk";
```
## Usage
```tsx
function TransactionSigner() {
const { signTransactionAsync, isPending, error } = useSignTransaction();
const handleSignTransaction = async () => {
try {
const tx = {
to: "0x742d35Cc6634C0532925a3b844Bc9e7595f6E123",
value: "0x2386f26fc10000", // 0.01 ETH in wei
gasLimit: "0x5208", // 21000
gasPrice: "0x09184e72a000", // 10000000000000
};
const rlpEncoded = encodeTransaction(tx); // Your encoding logic
const result = await signTransactionAsync({
rlpEncodedTxBase64: Buffer.from(rlpEncoded).toString("base64"),
});
if ("signature" in result) {
console.log("Transaction signed:", result.signature);
}
} catch (err) {
console.error("Failed to sign transaction:", err);
}
};
return (
{isPending ? "Signing..." : "Sign Transaction"}
);
}
```
# useSignUpOrLogIn
Source: https://docs.getpara.com/v3/react/guides/hooks/use-sign-up-or-login
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseSignUpOrLogIn from '/snippets/v3/definitions/hooks/useSignUpOrLogIn.mdx';
The `useSignUpOrLogIn` hook initiates the authentication flow for new or existing users, handling both sign-up and login scenarios automatically.
## Import
```tsx
import { useSignUpOrLogIn } from "@getpara/react-sdk";
```
## Usage
```tsx
function AuthenticationFlow() {
const { signUpOrLogIn, signUpOrLogInAsync, isPending, error } = useSignUpOrLogIn();
const [email, setEmail] = useState("");
const handleAuth = async () => {
try {
const result = await signUpOrLogInAsync({
email,
isGuestMode: false
});
console.log("Authentication initiated:", result);
} catch (err) {
console.error("Authentication failed:", err);
}
};
return (
setEmail(e.target.value)}
placeholder="Enter your email"
/>
{isPending ? "Processing..." : "Sign Up / Log In"}
);
}
```
# useStellarSigner
Source: https://docs.getpara.com/v3/react/guides/hooks/use-stellar-signer
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { MethodDocs } from "/snippets/v3/components/method-doc.mdx";
import UseStellarSigner from '/snippets/v3/definitions/hooks/useStellarSigner.mdx';
import StellarSigner from '/snippets/v3/definitions/types/stellarSigner.mdx';
import UseStellarSignerIsLoading from '/snippets/v3/definitions/types/UseStellarSignerIsLoading.mdx';
The `useStellarSigner` hook provides a Stellar signer for a Para wallet.
## Import
```tsx
import { useStellarSigner } from "@getpara/react-sdk/stellar";
```
## Usage
```tsx
import { useStellarSigner } from "@getpara/react-sdk/stellar";
import { Networks, TransactionBuilder, Operation, Asset, BASE_FEE, Horizon } from "@stellar/stellar-sdk";
const server = new Horizon.Server("https://horizon.stellar.org");
function StellarSigner() {
const { stellarSigner, isLoading } = useStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const handleSign = async () => {
if (!stellarSigner) {
return;
}
const account = await server.loadAccount(stellarSigner.address);
const transaction = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.payment({
destination: "GRECIPI...",
asset: Asset.native(),
amount: "10",
})
)
.setTimeout(180)
.build();
const { signedTxXdr } = await stellarSigner.signTransaction(transaction.toXDR());
console.log("Signed XDR:", signedTxXdr);
};
if (isLoading) return Loading Stellar signer...
;
return (
Address: {stellarSigner?.address}
Sign Transaction
);
}
```
## Network Passphrase Override
The `signTransaction` method accepts an optional second argument to override the network passphrase per-call. This is
useful when signing transactions for a different network than the one configured in the hook:
```typescript
// Override passphrase for a single call
const { signedTxXdr } = await stellarSigner.signTransaction(transaction.toXDR(), {
networkPassphrase: Networks.TESTNET,
});
```
This matches the Stellar SDK's `contract.SignTransaction` interface, making `ParaStellarSigner` directly compatible
with `contract.Client`.
## Return Type
The hook returns a `UseStellarSignerReturn` object with the following properties:
# useVerifyNewAccount
Source: https://docs.getpara.com/v3/react/guides/hooks/use-verify-new-account
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseVerifyNewAccount from '/snippets/v3/definitions/hooks/useVerifyNewAccount.mdx';
The `useVerifyNewAccount` hook completes the account verification process after initiating authentication with email or phone number.
## Import
```tsx
import { useVerifyNewAccount } from "@getpara/react-sdk";
```
## Usage
```tsx
function VerificationFlow() {
const { verifyNewAccount, verifyNewAccountAsync, isPending, error } = useVerifyNewAccount();
const [verificationCode, setVerificationCode] = useState("");
const [identifier, setIdentifier] = useState(""); // email or phone
const handleVerification = async () => {
try {
const result = await verifyNewAccountAsync({
identifier,
verificationCode
});
console.log("Account verified successfully");
} catch (err) {
console.error("Verification failed:", err);
}
};
return (
setIdentifier(e.target.value)}
placeholder="Email or phone used for signup"
/>
setVerificationCode(e.target.value)}
placeholder="Enter verification code"
maxLength={6}
/>
{isPending ? "Verifying..." : "Verify Account"}
);
}
```
# useWalletBalance
Source: https://docs.getpara.com/v3/react/guides/hooks/use-wallet-balance
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseWalletBalance from '/snippets/v3/definitions/hooks/useWalletBalance.mdx';
The `useWalletBalance` hook fetches the current balance of the selected wallet, supporting both Para-managed wallets and external wallets.
## Import
```tsx
import { useWalletBalance } from "@getpara/react-sdk";
```
## Usage
```tsx
function WalletBalance() {
const { data: balance, isLoading, error } = useWalletBalance();
if (isLoading) return Loading balance...
;
if (error) return Error loading balance
;
if (!balance) {
return No balance available
;
}
return (
);
}
```
# useWalletState
Source: https://docs.getpara.com/v3/react/guides/hooks/use-wallet-state
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseWalletState from '/snippets/v3/definitions/hooks/useWalletState.mdx';
The `useWalletState` hook provides methods to get and set the currently selected wallet, which is used as the default for signing operations.
## Import
```tsx
import { useWalletState } from "@getpara/react-sdk";
```
## Usage
```tsx
function WalletSelector() {
const { selectedWallet, setSelectedWallet, updateSelectedWallet } = useWalletState();
const { data: account } = useAccount();
const handleWalletChange = (walletId: string, walletType: TWalletType) => {
setSelectedWallet({ id: walletId, type: walletType });
};
return (
Current Wallet ID: {selectedWallet.id || "None"}
Current Wallet Type: {selectedWallet.type || "None"}
{account?.wallets.map((wallet) => (
handleWalletChange(wallet.id, wallet.type)}
style={{
fontWeight: selectedWallet.id === wallet.id ? "bold" : "normal"
}}
>
Select {wallet.type} Wallet
))}
);
}
```
# useWallet
Source: https://docs.getpara.com/v3/react/guides/hooks/use-wallet
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseWallet from '/snippets/v3/definitions/hooks/useWallet.mdx';
The `useWallet` hook provides access to the currently selected wallet's information.
## Import
```tsx
import { useWallet } from "@getpara/react-sdk";
```
## Usage
```tsx
function WalletInfo() {
const { data: wallet, isLoading, error } = useWallet();
const para = useClient();
if (isLoading) return Loading wallet...
;
if (error) return Error loading wallet
;
if (!wallet) {
return No wallet selected
;
}
return (
Wallet ID: {wallet.id}
Type: {wallet.type}
Address: {para.getDisplayAddress(wallet.id, {
truncate: true,
addressType: wallet.type
})}
Is External: {wallet.isExternal ? "Yes" : "No"}
);
}
```
# Migrating from Reown to Para
Source: https://docs.getpara.com/v3/react/guides/migration-from-reown
import MigrationFromReown from '/snippets/v3/migrations/from-reown.mdx';
# Migrating from Thirdweb to Para
Source: https://docs.getpara.com/v3/react/guides/migration-from-thirdweb
Migrate your existing Thirdweb application to Para's unified wallet system. Para provides similar wallet connection capabilities with additional features like embedded wallets and session management while maintaining a simple integration.
## Installation
Replace Thirdweb with Para SDK:
```bash Terminal
npm uninstall thirdweb
npm install @getpara/react-sdk @tanstack/react-query wagmi@^2 viem
```
For a full list of dependencies, refer to the [Quick Start Guide](/v3/react/quickstart).
## Configuration Changes
### Before: Thirdweb Client
Your existing Thirdweb client configuration:
```typescript lib/client.ts
import { createThirdwebClient } from "thirdweb";
export const client = createThirdwebClient({
clientId: process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID!,
});
```
### After: Para Configuration
Replace with Para configuration:
```typescript src/config/constants.ts
import { Environment } from "@getpara/react-sdk";
export const API_KEY = process.env.NEXT_PUBLIC_PARA_API_KEY ?? "";
export const ENVIRONMENT =
(process.env.NEXT_PUBLIC_PARA_ENVIRONMENT as Environment) || Environment.BETA;
if (!API_KEY) {
throw new Error("Missing NEXT_PUBLIC_PARA_API_KEY environment variable");
}
```
## Provider Migration
### Before: Thirdweb Provider
Your existing Thirdweb provider in the layout:
```tsx app/layout.tsx
import type { Metadata } from "next";
import { ThirdwebProvider } from "thirdweb/react";
export const metadata: Metadata = {
title: "Your App",
description: "Your App Description",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{children}
);
}
```
### After: Para Provider Setup
Create a Para provider component:
```tsx src/context/ParaProvider.tsx
"use client";
import { ParaProvider as Provider } from "@getpara/react-sdk";
import { API_KEY, ENVIRONMENT } from "@/config/constants";
import { mainnet, polygon, arbitrum } from "wagmi/chains";
export function ParaProvider({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
Update your layout:
```tsx src/app/layout.tsx
import type { Metadata } from "next";
import "@getpara/react-sdk/styles.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ParaProvider } from "@/context/ParaProvider";
const queryClient = new QueryClient();
export const metadata: Metadata = {
title: "Your App",
description: "Your App Description",
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
## Connect Button Migration
### Before: Thirdweb ConnectButton
```tsx app/page.tsx
"use client";
import { ConnectButton } from "thirdweb/react";
import { client } from "@/lib/client";
export default function Home() {
return (
);
}
```
### After: Para Connect Button
```tsx src/components/ConnectButton.tsx
"use client";
import { useAccount, useModal, useWallet } from "@getpara/react-sdk";
export function ConnectButton() {
const { openModal } = useModal();
const { data: wallet } = useWallet();
const { isConnected } = useAccount();
if (isConnected && wallet?.address) {
return (
openModal()} className="connect-button">
{wallet.address.slice(0, 6)}...{wallet.address.slice(-4)}
);
}
return (
openModal()} className="connect-button">
Connect Wallet
);
}
```
Use it in your page:
```tsx src/app/page.tsx
import { ConnectButton } from "@/components/ConnectButton";
export default function Home() {
return (
);
}
```
## Hook Migration
### Account Information
```tsx Thirdweb
import { useActiveAccount } from "thirdweb/react";
function Account() {
const account = useActiveAccount();
return (
Address: {account?.address}
);
}
```
```tsx Para
import { useAccount } from "@getpara/react-sdk";
function Account() {
const { address } = useAccount();
return (
Address: {address}
);
}
```
### Wallet Connection Status
```tsx Thirdweb
import { useActiveWalletConnectionStatus } from "thirdweb/react";
function ConnectionStatus() {
const status = useActiveWalletConnectionStatus();
return (
Status: {status}
);
}
```
```tsx Para
import { useAccount } from "@getpara/react-sdk";
function ConnectionStatus() {
const { isConnected, isConnecting } = useAccount();
return (
Status: {isConnecting ? "connecting" : isConnected ? "connected" : "disconnected"}
);
}
```
### Disconnect Wallet
```tsx Thirdweb
import { useDisconnect } from "thirdweb/react";
function DisconnectButton() {
const { disconnect } = useDisconnect();
return (
Disconnect
);
}
```
```tsx Para
import { useDisconnect } from "wagmi";
function DisconnectButton() {
const { disconnect } = useDisconnect();
return (
disconnect()}>
Disconnect
);
}
```
## Smart Contract Interaction
Para uses Wagmi for contract interactions, providing a different approach than Thirdweb:
### Reading Contract Data
```tsx Thirdweb
import { getContract } from "thirdweb";
import { useReadContract } from "thirdweb/react";
import { client } from "@/lib/client";
const contract = getContract({
client,
chain: ethereum,
address: "0x...",
});
function ContractRead() {
const { data } = useReadContract({
contract,
method: "function balanceOf(address) returns (uint256)",
params: ["0x..."],
});
return {data?.toString()}
;
}
```
```tsx Para
import { useReadContract } from "wagmi";
const abi = [
{
name: "balanceOf",
type: "function",
inputs: [{ name: "owner", type: "address" }],
outputs: [{ name: "balance", type: "uint256" }],
},
] as const;
function ContractRead() {
const { data } = useReadContract({
address: "0x...",
abi,
functionName: "balanceOf",
args: ["0x..."],
});
return {data?.toString()}
;
}
```
### Writing to Contract
```tsx Thirdweb
import { prepareContractCall } from "thirdweb";
import { useSendTransaction } from "thirdweb/react";
function ContractWrite() {
const { mutate: sendTransaction } = useSendTransaction();
const handleTransfer = () => {
const transaction = prepareContractCall({
contract,
method: "function transfer(address, uint256)",
params: ["0x...", 100n],
});
sendTransaction(transaction);
};
return Transfer ;
}
```
```tsx Para
import { useWriteContract } from "wagmi";
function ContractWrite() {
const { writeContract } = useWriteContract();
const handleTransfer = () => {
writeContract({
address: "0x...",
abi,
functionName: "transfer",
args: ["0x...", 100n],
});
};
return Transfer ;
}
```
## Feature Comparison
| Feature | Thirdweb | Para |
|---------|----------|------|
| Wallet Connection | ✅ | ✅ |
| Social Login | Via Auth SDK | ✅ Built-in |
| Email/Phone Login | Via Auth SDK | ✅ Built-in |
| Smart Contracts | Custom SDK | Wagmi hooks |
| Chain Switching | ✅ | ✅ |
| Session Management | Limited | ✅ Advanced |
| Gas Sponsorship | Via Engine | ✅ Built-in |
## Advanced Features
### Social Login
Para includes social login without additional configuration:
```tsx src/context/ParaProvider.tsx
paraModalConfig={{
oAuthMethods: ["GOOGLE", "APPLE", "DISCORD", "TWITTER", "FACEBOOK"],
disableEmailLogin: false,
disablePhoneLogin: false,
}}
```
### Multi-Chain Support
```tsx src/context/ParaProvider.tsx
import { mainnet, polygon, arbitrum, optimism, base } from "wagmi/chains";
externalWalletConfig={{
evmConnector: {
config: {
chains: [mainnet, polygon, arbitrum, optimism, base],
},
},
}}
```
### Custom Theme
```tsx src/context/ParaProvider.tsx
paraModalConfig={{
theme: {
mode: "dark",
foregroundColor: "#FFFFFF",
backgroundColor: "#1A1A1A",
borderRadius: "medium",
font: "Inter",
},
logo: "/logo.svg",
}}
```
## Migration Checklist
- Remove `thirdweb` package
- Install `@getpara/react-sdk`, `wagmi`, `viem`
- Add `@tanstack/react-query`
- Run Para postinstall script
- Replace Thirdweb client with Para configuration
- Update environment variables
- Configure supported chains
- Set up WalletConnect project ID if needed
- Replace `ThirdwebProvider` with `ParaProvider`
- Update `ConnectButton` implementation
- Migrate hooks to Para/Wagmi equivalents
- Update contract interaction code
- Test wallet connections
- Verify contract interactions
- Check chain switching
- Test social login if enabled
## Common Patterns
### Balance Display
```tsx src/components/Balance.tsx
import { useAccount, useBalance } from "wagmi";
import { formatEther } from "viem";
export function Balance() {
const { address } = useAccount();
const { data } = useBalance({ address });
if (!data) return null;
return (
{formatEther(data.value)} {data.symbol}
);
}
```
### Transaction History
```tsx src/components/TransactionHistory.tsx
import { useWallet } from "@getpara/react-sdk";
export function TransactionHistory() {
const { data: wallet } = useWallet();
// Para provides transaction history through the wallet object
const transactions = wallet?.transactions || [];
return (
{transactions.map((tx) => (
{tx.hash}
))}
);
}
```
## Next Steps
Learn about Wagmi hooks for contract interactions
Explore Para's React hooks
Implement session management
# Migrating from Web3Modal to Para
Source: https://docs.getpara.com/v3/react/guides/migration-from-walletconnect
import MigrationFromWalletConnect from '/snippets/v3/migrations/from-walletconnect.mdx';
# Migration Guides
Source: https://docs.getpara.com/v3/react/guides/migrations
import { Card } from '/snippets/v3/components/ui/card.mdx';
Switching to Para from another wallet provider? These guides walk you through migrating your existing integration while preserving your application's functionality.
## Why Migrate to Para?
Para provides a unified wallet experience that combines embedded wallets with external wallet support through a single `ParaProvider` component. Benefits include:
- **Simplified setup**: One provider handles both embedded and external wallets
- **Full library compatibility**: Works with Wagmi, Viem, Ethers, and other popular libraries
- **Cross-chain support**: EVM, Solana, Cosmos, and Stellar from a single integration
- **Built-in authentication**: Email, phone, social logins, and passkeys out of the box
## Migration Guides
## What to Expect
Each migration guide covers:
1. **Dependency changes**: Which packages to remove and install
2. **Provider setup**: Replacing your existing provider with `ParaProvider`
3. **Hook migrations**: Mapping existing hooks to Para equivalents
4. **UI components**: Updating modal and button implementations
Most migrations can be completed in under an hour for typical applications.
# Permissions
Source: https://docs.getpara.com/v3/react/guides/permissions
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Permission prompts give applications the option to show users a Para-managed dialog to manually approve or deny any transaction or message signing events.
This feature is required when interacting with wallets created outside your app, but can also be enabled for all wallets/transactions in your application. We recommend enabling this feature if you prefer not to implement transaction approval/display flows, or if you want users to explicitly approve every transaction on an embedded wallet.
## How it Works
Permission prompts are mandatory in the following scenarios:
- **External Wallet Interaction**: When a user attempts to sign a message or execute a transaction using their wallet in an application that is different from where the wallet was created.
- **Multi-Application Transactions**: After a wallet has been used across multiple applications, any subsequent transactions will prompt the user for approval. This ensures that the user is aware of and consents to all interactions involving their wallet, regardless of the application being used.
To enable permission prompts for all transactions/wallets, activate "Always show transaction prompts" for your API key in the
## User Interaction
When a transaction or message signing event is initiated, users will see a popup containing the following details:
- **Message Details**: An encoded string representing the message.
- **Transaction Details**:
- `From`: The wallet address initiating the transaction.
- `To`: The recipient wallet address.
- `Amount`: The number of tokens being transferred.
- `Action`: The specific action being performed (e.g., token transfer, minting an NFT).
- `Conversion Rates`: Relevant exchange rates if applicable.
- `Chain Information`: Information about the blockchain being used.
## Technical Details
This feature is enabled by default when using the `signMessage` or `signTransaction` functions, either directly or
through supported signer libraries (e.g., Ethers, Cosmos).
There is a default 30-second timeout for approvals. If this does not work for your use case, please reach out to the
Para team for instructions on overriding this value.
If your API key has a `WINDOWED_SPEND_LIMIT` policy, under-window direct native and ERC-20 transfer transactions sign automatically when the rest of the policy matches. Over-window transactions return `POLICY_DENIED` before signing and do not create a transaction review.
### Transaction Events and Statuses
- **On Approval**: If the user approves the transaction or no approval is necessary, the `signMessage/signTransaction`
function will return a `SuccessfulSignatureRes` result, which will contain the signature.
- **On Denial or Timeout**: If the user denies the transaction or the timeout is reached, a `TransactionReviewError`
will be thrown that includes the `transactionReviewUrl` that must be handled by the implementing application.
## Error Handling
When implementing permission prompts, various errors can arise during the signing process. It's important to handle
these errors gracefully to ensure a smooth user experience. Below are common scenarios and recommended handling
strategies:
### 1. Transaction Denied
**Description**: The user denies the transaction or message signing request.
**Error Handling**:
```tsx
try {
await client.sendTokens(...);
} catch (error) {
if (error instanceof TransactionReviewDenied) {
console.warn("The transaction was denied by the user.");
}
}
```
### 2. Timeout Reached
**Description**: The user does not respond to the popup within the configured timeout period. This returns additional
properties of `transactionReviewUrl` and `pendingTransactionId`:
- `pendingTransactionId` - Can be used in conjunction with the `getPendingTransaction` function available via the
CorePara class (or WebPara, by extension). If it does not exist, that means the user has denied the transaction
request.
- `transactionReviewUrl` - Can be used to open a popup if desired, which will present the user with the sign message /
transaction popup.
**Error Handling**:
```tsx
try {
await client.sendTokens(...);
} catch (error) {
if (error instanceof TransactionReviewTimeout) {
console.warn("The transaction timed out. You can retry or direct the user to review.");
}
}
```
## Next Steps
To learn more about signing messages and transactions, check out the following guides:
# Wallet Pregeneration
Source: https://docs.getpara.com/v3/react/guides/pregen
import { Link } from '/snippets/v3/components/ui/link.mdx';
import PregenRestApiCallout from '/snippets/v3/pregen-rest-api-callout.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import CreatePregenWallet from '/snippets/v3/definitions/core/createPregenWallet.mdx';
import PregenAuth from '/snippets/v3/definitions/types/PregenAuth.mdx';
import GetPregenWallets from '/snippets/v3/definitions/core/getPregenWallets.mdx';
import HasPregenWallet from '/snippets/v3/definitions/core/hasPregenWallet.mdx';
import ClaimPregenWallets from '/snippets/v3/definitions/core/claimPregenWallets.mdx';
import UpdatePregenWalletIdentifier from '/snippets/v3/definitions/core/updatePregenWalletIdentifier.mdx';
import CreatePregenWalletPerType from '/snippets/v3/definitions/core/createPregenWalletPerType.mdx';
import GetUserShare from '/snippets/v3/definitions/core/getUserShare.mdx';
import SetUserShare from '/snippets/v3/definitions/core/setUserShare.mdx';
## Overview
Wallet Pregeneration allows you to create wallets before a user authenticates with Para. Your application controls the
wallet's user share until the wallet is claimed. After a successful claim, Para rotates the key material and protects
the user share with the user's authentication method.
Pregenerated wallets can be associated with an email address, a phone number, a third-party account identifier, or a
custom ID of your choosing. If you create a wallet with a custom ID and later want a user to claim it with email or
phone auth, update the pregenerated wallet identifier before returning the user share to the client.
- For email or phone, the user will need to have the same email address or phone number linked to their account.
- For Discord or Twitter, the user will need to have authenticated via those services on your application with the same
username.
- For a custom ID, your application is responsible for mapping that ID to the user who should be allowed to claim it.
## Creating a Pregenerated Wallet
Before creating a wallet for a user, it's a good practice to check if one already exists.
### Check if a pregenerated wallet exists
```typescript
const hasWallet = await para.hasPregenWallet({
pregenId: { email: "user@example.com" },
});
```
### Create a pregenerated wallet if needed
```typescript
if (!hasWallet) {
await para.createPregenWallet({
type: "EVM",
pregenId: { email: "user@example.com" },
});
}
```
### Method Parameters
### PregenAuth Type Definition
The identifier can be an email or phone number, a third-party user ID (for Farcaster, Telegram, Discord, or X), or a custom ID relevant to your application. Choose an identifier that works best for your application architecture.
## Storing and Managing User Share
After creating a pregenerated wallet, it's crucial to securely store the user share. This share is part of Para's 2/2
MPC protocol and remains the application's responsibility until the wallet is claimed.
To retrieve the user share for a pregenerated wallet, use the `getUserShare` method:
```typescript
const userShare: string = await para.getUserShare();
```
You must securely store this user share in your backend, associated with the user's identifier. If this share is lost,
the wallet becomes permanently inaccessible.
### Best Practices for Storing the UserShare
While temporarily managing the `UserShare`, it's important that you take extra care with how you store this information.
If you ever face a situation where data becomes compromised across your systems, reach out to the Para team so we can
work on possible key rotation. However, keep in mind that Para does not store backups of this share in case of data
loss.
To mitigate this category of risks, we've compiled a few best practices:
- Encrypt `UserShares` in-transit and at-rest.
- Ensure your database has backups and periodic replicas to mitigate against data deletion risks.
- Complete a fire drill prior to going live, testing scenarios such as:
- You are unable to access your DB
- Your DB is deleted
- An internal team member's credentials are compromised
Para is happy to offer pre-launch security reviews for teams in the Growth tier or above. Let us know if you need
help!
This share management is temporary - once the user claims their wallet, Para will handle the share security through the
user's authentication methods.
## Using a Pregenerated Wallet
Before using a pregenerated wallet for signing operations, you must first load the user share into your Para client
instance. Retrieve the `UserShare` from your secure storage and load it into Para using the `setUserShare` method:
```typescript
await para.setUserShare(userShare);
```
Once the share is loaded, the wallet becomes available for signing operations, just like any other Para wallet:
```typescript
const messageBase64 = btoa("Hello, World!");
const signature = await para.signMessage({
walletId,
messageBase64,
});
```
You can perform this operation using either `@getpara/server-sdk` or `@getpara/react-sdk`/`@getpara/web-sdk` depending
on your application architecture. The Para client that has the user share loaded is the one that can perform signing
operations.
### Using with Ecosystem Libraries
Once the `userShare` is set, your Para client functions like any standard wallet. You can now easily integrate with
popular blockchain libraries to perform transactions and other operations.
For detailed integration guides with blockchain ecosystems, see:
-
-
-
## Claiming a Pregenerated Wallet
Claiming pregenerated wallets must be done client-side with the Para Client SDK. The Server SDK does not support the
key rotation operations required for wallet claiming.
Claiming transfers ownership of a pregenerated wallet to a user's Para account. This process requires:
1. The wallet's user share is loaded into the Para client before the claim runs
2. The wallet's identifier matches the authenticating user's identifier
3. The claim runs from a client-side Para SDK instance
### Claiming with the modal
Configure `fetchPregenWalletsOverride` before opening the Para modal. During authentication, Para calls this function
with the identifier the user is authenticating with. Your backend uses that identifier to find the stored user share and
returns it to the SDK.
If you created the wallet with a custom ID, update the pregenerated wallet identifier to the authenticating identifier
before returning the user share. The returned share must describe the wallet with the same identifier Para is claiming.
```typescript
async function fetchPregenWalletsOverride(opts: { pregenId: PregenAuth }) => Promise<{ userShare?: string }> {
const email = "email" in opts.pregenId ? opts.pregenId.email : undefined;
if (!email) {
return { userShare: undefined };
}
const response = await fetch("/api/pregen/share", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email }),
});
const data = await response.json();
return { userShare: data.userShare };
}
```
Pass the callback when you create the Para client used by your provider.
```typescript
import ParaWeb, { Environment, ParaProvider } from "@getpara/react-sdk";
const para = new ParaWeb(Environment.BETA, process.env.NEXT_PUBLIC_PARA_API_KEY!, {
fetchPregenWalletsOverride,
});
{children}
```
Your backend should return the stored share only after it has prepared the wallet for the authenticating identifier.
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
export async function POST(request: Request) {
const { email } = await request.json();
const wallet = await getPregenWalletByEmail(email);
if (!wallet) {
return Response.json({ userShare: undefined });
}
const para = new ParaServer(process.env.PARA_API_KEY!);
const userShare = await decryptUserShare(wallet.encryptedUserShare);
await para.setUserShare(userShare);
await para.updatePregenWalletIdentifier({
walletId: wallet.walletId,
newPregenId: { email },
});
return Response.json({ userShare: await para.getUserShare() });
}
```
Use a fresh server-side Para client for request handlers that call `setUserShare`. Reusing one client across requests
can mix wallet shares from different users.
### Claiming manually
If you are not using the modal flow, authenticate the user first. Then retrieve the prepared user share from your backend,
load it into the client, and claim the wallet.
```typescript
const response = await fetch("/api/pregen/share", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: user.email }),
});
const { userShare } = await response.json();
await para.setUserShare(userShare);
const recoverySecret = await para.claimPregenWallets({
pregenId: { email: user.email },
});
```
You can also claim every pregenerated wallet whose loaded share and identifier match the authenticated user.
```typescript
const recoverySecret = await para.claimPregenWallets();
```
In the modal flow, you usually do not call `claimPregenWallets` directly. When the callback returns a matching
user share during authentication, Para loads the share and claims the wallet as part of the auth flow.
### Controlling When Wallets are Claimed
You have control over when users claim their pregenerated wallets:
- **Delayed Claiming**: Only load the userShare and update the identifier when you're ready for the user to claim the
wallet. This allows your application to continue using the wallet on behalf of the user.
- **Immediate Claiming**: If you want immediate claiming upon user authentication, load the userShare before
authentication and ensure the identifiers match.
- **No Claiming**: Keep using different identifiers for the wallet than the user's actual identifier to prevent
automatic claiming.
This flexibility lets you design the optimal user experience for your application.
## Core Pregeneration Methods
## Best Practices and Considerations
Pregenerated wallets are app-specific until claimed. Before claiming, they can only be used within your application through the `UserShare`. After a user claims the wallet, it becomes part of their Para account, allowing them to use it across different Para-integrated applications. This transition from app-managed to user-managed is a key consideration in your implementation strategy.
Choose identifiers that align with your application architecture: - **Email/Phone**: Most common for user-facing
applications - **OAuth Identifiers**: Useful for social login integrations (Discord, Twitter) - **Custom IDs**: Ideal
for internal user management systems Consider your user onboarding flow when choosing identifiers. If you use custom
IDs initially, you'll need to update them to match the user's actual identifier (email/phone) before claiming can
occur.
The user share is critical security information that must be protected: - **Encryption**: Always encrypt user shares
both in transit and at rest - **Database Security**: Implement proper access controls for your share database -
**Backups**: Maintain regular database backups to prevent data loss - **Disaster Recovery**: Create processes for
handling compromise scenarios - **Key Rotation**: Have a plan for working with Para if key rotation becomes necessary
Consider implementing a fire drill before launching to test scenarios like database loss, access issues, or credential
compromise. Para offers security reviews for teams on Growth tier and above.
Plan your user experience around wallet claiming: - **Delayed Claiming**: Keep control of wallets until users are
ready for full ownership - **Automatic Claiming**: Configure for immediate claiming during authentication -
**Progressive Onboarding**: Start users with app-managed wallets, then transition to self-custody - **Educational
Elements**: Help users understand the transition from app-managed to self-custody The claiming process should feel
seamless and intuitive to users while giving you flexibility in your application architecture.
Be deliberate about the wallet types you create:
- **Match Blockchain Needs**: Select wallet types (EVM, Solana, Cosmos, Stellar) based on your application's blockchain requirements
- **Multiple Types**: Consider creating multiple wallet types if your application spans multiple blockchains
- **Default Selection**: If your app supports multiple chains, create wallets for all required types during pregeneration
- **User Guidance**: Provide clear information about which blockchain networks are supported by each wallet
Understand the operational boundaries:
- **Server-side**: Create pregen wallets, store user shares, sign transactions (with loaded shares)
- **Client-side only**: Create and claim wallets, load user shares, sign transactions
Design your architecture with these constraints in mind, especially when planning how user shares will flow from your server to client during the claiming process. Implement secure methods to transfer the user share from your server to the client when needed for wallet claiming.
## Reference Example
For complete examples demonstrating the usage of pregeneration methods, refer to our examples repository:
# React SDK Lite
Source: https://docs.getpara.com/v3/react/guides/react-sdk-lite
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
Para offers a lightweight version of `@getpara/react-sdk` called `@getpara/react-sdk-lite`. It reduces your bundle size by letting you install only the chain-specific dependencies you actually need, rather than pulling in packages for all supported networks.
## Installation
Choose the tab for the network you plan to support. If you need multiple networks, install the dependencies from each relevant tab.
```bash npm
npm install @getpara/react-sdk-lite @getpara/evm-wallet-connectors wagmi@^2 viem --save-exact
```
```bash yarn
yarn add @getpara/react-sdk-lite @getpara/evm-wallet-connectors wagmi@^2 viem --exact
```
```bash pnpm
pnpm add @getpara/react-sdk-lite @getpara/evm-wallet-connectors wagmi@^2 viem --save-exact
```
```bash bun
bun add @getpara/react-sdk-lite @getpara/evm-wallet-connectors wagmi@^2 viem --exact
```
```bash npm
npm install @getpara/react-sdk-lite @getpara/solana-wallet-connectors @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js --save-exact
```
```bash yarn
yarn add @getpara/react-sdk-lite @getpara/solana-wallet-connectors @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js --exact
```
```bash pnpm
pnpm add @getpara/react-sdk-lite @getpara/solana-wallet-connectors @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js --save-exact
```
```bash bun
bun add @getpara/react-sdk-lite @getpara/solana-wallet-connectors @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js --exact
```
```bash npm
npm install @getpara/react-sdk-lite @getpara/cosmos-wallet-connectors graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet --save-exact
```
```bash yarn
yarn add @getpara/react-sdk-lite @getpara/cosmos-wallet-connectors graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet --exact
```
```bash pnpm
pnpm add @getpara/react-sdk-lite @getpara/cosmos-wallet-connectors graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet --save-exact
```
```bash bun
bun add @getpara/react-sdk-lite @getpara/cosmos-wallet-connectors graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet --exact
```
## Stub Unused Dependencies
After installing, add the `setup-para` postinstall script to your `package.json`. This CLI tool ships with `@getpara/react-sdk-lite` and stubs out chain dependencies you didn't install so your bundler won't try to include them.
```json package.json
{
"scripts": {
"postinstall": "setup-para"
}
}
```
If you already have a postinstall script, append `&& setup-para` to it.
If you are migrating from `@getpara/react-sdk`, remove any dependencies for networks you no longer need (e.g., `wagmi` and `viem` if you only use Solana, or `@solana/web3.js` if you only use EVM).
## Next Steps
Once the lite SDK is installed, follow the corresponding external wallets guide to finish your integration:
MetaMask, Coinbase Wallet, WalletConnect, and more
Phantom, Backpack, Solflare, and more
Keplr, Leap, Cosmostation, and more
# Reown AppKit Integration
Source: https://docs.getpara.com/v3/react/guides/reown-appkit
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Use Para as the exclusive wallet provider in (formerly WalletConnect Web3Modal). This integration provides a polished wallet connection UI with Para handling all authentication.
This guide uses Para as the only wallet option in AppKit. For a simpler integration with multiple wallets, see the external wallets guide.
## Prerequisites
Before integrating with Reown AppKit, ensure you have a Para account and project ID from Reown.
## Installation
Install Para's Wagmi integration and Reown AppKit:
```bash npm
npm install @getpara/wagmi-v2-integration @reown/appkit @reown/appkit-adapter-wagmi wagmi@^2 viem @tanstack/react-query --save-exact
```
```bash yarn
yarn add @getpara/wagmi-v2-integration @reown/appkit @reown/appkit-adapter-wagmi wagmi@^2 viem @tanstack/react-query --exact
```
```bash pnpm
pnpm add @getpara/wagmi-v2-integration @reown/appkit @reown/appkit-adapter-wagmi wagmi@^2 viem @tanstack/react-query --save-exact
```
```bash bun
bun add @getpara/wagmi-v2-integration @reown/appkit @reown/appkit-adapter-wagmi wagmi@^2 viem @tanstack/react-query --exact
```
## Configuration
Create the AppKit configuration with Para as the sole connector:
```typescript appkit.config.ts
import { createAppKit } from "@reown/appkit/react";
import { WagmiAdapter } from "@reown/appkit-adapter-wagmi";
import { paraConnector } from "@getpara/wagmi-v2-integration";
import { mainnet, polygon, arbitrum } from "@reown/appkit/networks";
import Para from "@getpara/web-sdk";
import { QueryClient } from "@tanstack/react-query";
// Initialize clients
const para = new Para("YOUR_PARA_API_KEY");
const queryClient = new QueryClient();
// Configure chains
const chains = [mainnet, polygon, arbitrum] as const;
// Create Para connector
const connector = paraConnector({
para,
chains: [...chains],
appName: "Your App Name"
});
// Setup Wagmi adapter
const wagmiAdapter = new WagmiAdapter({
networks: [...chains],
projectId: "YOUR_REOWN_PROJECT_ID",
connectors: [connector]
});
// Create AppKit instance
export const appKit = createAppKit({
adapters: [wagmiAdapter],
networks: [...chains],
projectId: "YOUR_REOWN_PROJECT_ID",
metadata: {
name: "Your App Name",
description: "Your app description",
url: "https://yourapp.com",
icons: ["https://yourapp.com/icon.png"]
},
features: {
analytics: true,
email: false, // Para handles auth
socials: false, // Para handles socials
},
allWallets: "HIDE" // Only show Para
});
export { wagmiAdapter };
```
## Provider Setup
Wrap your app with the necessary providers:
```tsx app.tsx
import { WagmiProvider } from "wagmi";
import { QueryClientProvider } from "@tanstack/react-query";
import { wagmiAdapter } from "./appkit.config";
const queryClient = new QueryClient();
function App() {
return (
{/* Your app */}
);
}
```
## Using AppKit
Open the connection modal using AppKit hooks:
```tsx connect-button.tsx
import { useAppKit } from "@reown/appkit/react";
function ConnectButton() {
const { open } = useAppKit();
return (
open()}>
Connect Wallet
);
}
```
## Configuration Options
Key configuration options for the Para + AppKit integration:
```typescript
const connector = paraConnector({
para, // Your Para instance
chains, // Supported chains
appName: "Your App Name", // Display name
queryClient, // TanStack Query client
oAuthMethods: ["GOOGLE", "TWITTER"], // Social login options
disableEmailLogin: false, // Enable email auth
disablePhoneLogin: false, // Enable phone auth
});
```
## Next Steps
# JWT Token Management
Source: https://docs.getpara.com/v3/react/guides/sessions-jwt
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import UseIssueJwt from '/snippets/v3/definitions/hooks/useIssueJwt.mdx';
import SessionsJwtTokenStructure from '/snippets/v3/sessions-jwt-token-structure.mdx';
Once a user is signed in, you can request a Para JWT token. This token will provide attestations for the user's ID, their identity, any wallets they have provisioned via your application, and any connected wallets in their current session.
## Requesting a JWT Token
You can request a JWT token using either the client method or the React hook. Both approaches return the token itself as well as the JWKS key ID (`kid`) for the keypair that signed it.
### Client Method
```typescript TypeScript
import { ParaWeb } from '@getpara/web-sdk';
const para = new ParaWeb('your-api-key');
const { token, keyId } = await para.issueJwt();
```
```tsx React Hook
import { useIssueJwt } from '@getpara/react-sdk';
function JwtTokenManager() {
const { issueJwt, issueJwtAsync, isPending, error } = useIssueJwt();
const [tokenInfo, setTokenInfo] = useState<{ token: string; keyId: string } | null>(null);
const handleIssueToken = async () => {
try {
const result = await issueJwtAsync();
setTokenInfo({
token: result.token,
keyId: result.keyId
});
await sendTokenToBackend(result.token);
} catch (err) {
console.error("Failed to issue JWT:", err);
}
};
return (
{isPending ? "Issuing..." : "Issue JWT Token"}
);
}
```
### React Hook
The token's expiry will be determined by your customized session length, or else will default to 30 minutes. Issuing a token, like most authenticated API operations, will also renew and extend the session for that duration.
The token's `aud` field will be set to your API key's unique ID, linking it specifically to your application.
## Best Practices
- **Session Verification**: For security-critical operations, verify JWT tokens on both client and server sides
- **Token Expiry**: Be aware that tokens expire based on your session configuration and plan accordingly
- **Secure Storage**: Never store JWT tokens in insecure locations like localStorage for sensitive applications
# Session Lifecycle
Source: https://docs.getpara.com/v3/react/guides/sessions-lifecycle
import IsSessionActive from '/snippets/v3/definitions/core/isSessionActive.mdx';
import KeepSessionAlive from '/snippets/v3/definitions/core/keepSessionAlive.mdx';
import RefreshSession from '/snippets/v3/definitions/core/refreshSession.mdx';
Learn how to check session status, maintain active sessions, and handle session expiration in Para web applications.
## Checking Session Status
Use `isSessionActive()` to verify whether a user's session is currently valid before performing authenticated operations.
Example usage:
```typescript
const para = new Para(apiKey);
const isActive = await para.isSessionActive();
if (!isActive) {
// Start a new authentication flow.
}
```
## Maintaining Active Sessions
Use `keepSessionAlive()` to extend an active session's validity without requiring full reauthentication.
`keepSessionAlive()` is also the method to extend a session that was imported into the Server SDK.
Example usage:
```typescript
const para = new Para(apiKey);
const success = await para.keepSessionAlive();
if (!success) {
// Start a new authentication flow.
}
```
### Automatic Session Management with React
If you're using the React SDK and the `ParaProvider` component, you can leverage automatic session management:
```typescript
// The ParaProvider will automatically keep sessions alive by default
// To disable automatic session management
```
When using the ParaProvider component from the React SDK, it automatically keeps sessions alive in the background by calling `keepSessionAlive()` periodically. You can disable this behavior by setting `config.disableAutoSessionKeepAlive` to `true` if you prefer to manage sessions manually.
## Refreshing Sessions
Para provides the `refreshSession()` method for flows that intentionally send the user through a session refresh or login URL.
`refreshSession()` is different from `keepSessionAlive()`. Use `keepSessionAlive()` to extend an active session. Use `refreshSession()` only when your app is starting a refresh or login flow for the user.
For most applications, when a session expires, it's better to guide users through a complete authentication process:
```typescript
const para = new Para(apiKey);
// When session expires, initiate a full authentication
if (!(await para.isSessionActive())) {
//route to authentication page
}
```
For server-side imported sessions, call `keepSessionAlive()` before expiry. If it fails, ask the client to authenticate again and export a new session.
## Best Practices
- **Proactive Session Management**: Always check session status before operations that require authentication
- **Regular Session Extension**: For long user sessions, periodically call `keepSessionAlive()` or leverage the `ParaProvider` automatic session management
- **Graceful Expiration Handling**: Provide a smooth re-authentication flow when sessions expire instead of showing errors
# Pregenerated Wallet Sessions
Source: https://docs.getpara.com/v3/react/guides/sessions-pregen
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import SetUserShare from '/snippets/v3/definitions/core/setUserShare.mdx';
import GetUserShare from '/snippets/v3/definitions/core/getUserShare.mdx';
When using pregenerated wallets, session management works differently as these wallets don't require traditional authentication.
## How Sessions Work with Pregenerated Wallets
For pregenerated wallets, the session is considered always active as long as the `UserShare` is loaded in the Para client instance. Traditional session expiration doesn't apply in this scenario.
```typescript
const para = new Para(apiKey);
// Set a pregenerated wallet
await para.setUserShare(userShare);
// Session checks will return true as long as userShare is loaded
const isActive = await para.isSessionActive(); // Always true for pre-gen wallets
```
## Session Management Methods for Pre-Generated Wallets
When a UserShare is loaded via `setUserShare()`, the session remains active indefinitely. Methods like `isSessionActive()` will return true as long as the UserShare remains loaded in the Para client instance.
## Learn More
## Best Practices
- **UserShare Management**: Ensure the UserShare remains loaded in the Para instance for continuous session availability
- **Security**: Store UserShares securely and never expose them in client-side code
- **Session Verification**: Remember that `isSessionActive()` will always return true for loaded pregenerated wallets
# Session Transfer
Source: https://docs.getpara.com/v3/react/guides/sessions-transfer
import { Card } from '/snippets/v3/components/ui/card.mdx';
import ImportSession from '/snippets/v3/definitions/core/importSession.mdx';
Learn how to securely transfer session state from your client application to your server for performing operations on behalf of authenticated users.
## Exporting Sessions for Server-Side Operations
Use `waitAndExportSession()` when you need to transfer an authenticated user's session state to your server.
```typescript
const serializedSession = await para.waitAndExportSession();
await fetch("/api/import-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session: serializedSession }),
});
```
`waitAndExportSession()` waits until the SDK has reached an authenticated state before exporting session data. Use it for client-to-server handoff flows immediately after login.
By default, the exported session includes signer data so your server can sign for the user. If your server needs to sign messages or transactions, do not use `excludeSigners`.
If the server only needs to validate the user's session and does not need signing capabilities, export without signer data:
```typescript
const sessionWithoutSigners = await para.waitAndExportSession({
excludeSigners: true,
});
```
## Importing Sessions
For cases where you need to import a previously exported session back into a Para client instance:
```typescript
const para = new Para(apiKey);
await para.importSession(exportedSessionString);
const isActive = await para.isSessionActive();
```
A Para instance can hold one active session at a time. On the server, create a fresh `ParaServer` instance for each imported user session.
## Server-Side Implementation
To learn more about handling sessions on the server, check out the following guide:
## Best Practices
- **Export for the operation you need**: Include signer data only when your server will sign for the user.
- **Secure transmission**: Always use HTTPS when transmitting exported sessions to your server. Do not log serialized sessions.
- **Session validation**: Verify the session validity on your server before performing authenticated operations.
# Web Session Management
Source: https://docs.getpara.com/v3/react/guides/sessions
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para provides a comprehensive set of methods for managing authentication sessions in web applications. These sessions are crucial for secure transaction signing and other authenticated operations. Proper session management helps maintain security while ensuring a seamless user experience.
## Session Configuration
Para session length is configured per API key and can be set up to 30 days through the or CLI. The Para API enforces the configured duration. Signing a message or transaction, or calling `keepSessionAlive()`, can extend an active session according to that configuration.
### Security Considerations
**Shorter Sessions:**
- Enhanced security for sensitive applications
- Reduced risk if device is compromised
- Better for shared or public devices
**Longer Sessions (1 Week - 1 Month):**
- Improved user experience with fewer logins
- Better for personal devices and trusted environments
- Consider implementing automatic session refresh
### Custom Session Length
For custom durations:
1. Select "Custom" option in the Developer Portal
2. Enter duration in minutes
3. Consider your application's specific security needs
4. Balance security with user experience
## Session Management Topics
Explore the different aspects of session management in Para:
## Quick Start
Here's a basic example of checking and maintaining a session:
```typescript
const para = new Para(apiKey);
// Check if session is active
const isActive = await para.isSessionActive();
if (!isActive) {
// Handle expired session - route to authentication
} else {
// Extend the session
await para.keepSessionAlive();
}
```
# Upgrade Auth Methods
Source: https://docs.getpara.com/v3/react/guides/upgrade-auth-methods
import { Link } from "/snippets/v3/components/ui/link.mdx";
import UpgradeAuthMethodsWhenToUse from '/snippets/v3/upgrade-auth-methods-when-to-use.mdx';
Para's default authentication flow provides a seamless onboarding experience with just an OTP code. However, some users may want to add stronger authentication methods to their account for enhanced security.
The `useAddAuthMethod` hook allows users to add a **passkey**, **password**, or **PIN** to their existing account.
## Add a Passkey
Passkeys provide the strongest security upgrade. Users can add a passkey to their account:
```tsx
import { useAddAuthMethod } from "@getpara/react-sdk";
import { AuthMethod } from "@getpara/core-sdk";
function AddPasskeyButton() {
const { addAuthMethodAsync, isPending } = useAddAuthMethod();
const handleAddPasskey = async () => {
try {
await addAuthMethodAsync({ authMethod: AuthMethod.Passkey });
} catch (err) {
console.error("Failed to add passkey:", err);
}
};
return (
{isPending ? "Adding..." : "Add Passkey"}
);
}
```
## Add a Password
Users can add a traditional password to their account:
```tsx
import { useAddAuthMethod } from "@getpara/react-sdk";
import { AuthMethod } from "@getpara/core-sdk";
function AddPasswordButton() {
const { addAuthMethodAsync, isPending } = useAddAuthMethod();
const handleAddPassword = async () => {
try {
await addAuthMethodAsync({ authMethod: AuthMethod.Password });
} catch (err) {
console.error("Failed to add password:", err);
}
};
return (
Add Password
);
}
```
## Add a PIN
For a simpler upgrade, users can add a PIN:
```tsx
import { useAddAuthMethod } from "@getpara/react-sdk";
import { AuthMethod } from "@getpara/core-sdk";
function AddPinButton() {
const { addAuthMethodAsync, isPending } = useAddAuthMethod();
const handleAddPin = async () => {
try {
await addAuthMethodAsync({ authMethod: AuthMethod.Pin });
} catch (err) {
console.error("Failed to add PIN:", err);
}
};
return (
Add PIN
);
}
```
## Handling the Popup
By default, `useAddAuthMethod` opens a popup window for the user to complete the auth method setup. You can control this behavior:
```tsx
const { addAuthMethodAsync } = useAddAuthMethod({
openPopup: false, // Handle the URL yourself
});
const handleAddAuthMethod = async () => {
const url = await addAuthMethodAsync();
// Open in a custom way (e.g., redirect, iframe, custom modal)
window.open(url, "_blank", "width=500,height=700");
};
```
## Adding Two-Factor Authentication (2FA)
For additional security beyond the primary auth method, see the documentation.
## API Reference
For complete hook details, parameters, and return types, see the hook reference.
# Wagmi Connector Integration
Source: https://docs.getpara.com/v3/react/guides/wagmi-connector
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Build custom wallet connection interfaces using Para as a connector. This guide shows how to integrate Para alongside other wallet options in your own UI.
Para's `ParaModal` is built with Wagmi under the hood. We recommend using ParaModal directly for a complete authentication experience. Use this guide only if you need a custom wallet selection UI while still including Para for social login.
## Prerequisites
Before building a custom Wagmi connector, ensure you have Para configured in your application.
## Installation
Install the Para Wagmi integration:
```bash npm
npm install @getpara/wagmi-v2-integration @tanstack/react-query wagmi@^2 viem --save-exact
```
```bash yarn
yarn add @getpara/wagmi-v2-integration @tanstack/react-query wagmi@^2 viem --exact
```
```bash pnpm
pnpm add @getpara/wagmi-v2-integration @tanstack/react-query wagmi@^2 viem --save-exact
```
```bash bun
bun add @getpara/wagmi-v2-integration @tanstack/react-query wagmi@^2 viem --exact
```
## Basic Setup
Create a Wagmi config with Para as a connector option:
```typescript wagmi.config.ts
import { paraConnector } from "@getpara/wagmi-v2-integration";
import { createConfig, http } from "wagmi";
import { sepolia } from "wagmi/chains";
import Para from "@getpara/web-sdk";
// Initialize Para
const para = new Para("YOUR_PARA_API_KEY");
// Create Para connector
const connector = paraConnector({
para,
chains: [sepolia],
appName: "Your App Name"
});
// Configure Wagmi
export const config = createConfig({
chains: [sepolia],
connectors: [connector], // Add other connectors as needed
transports: {
[sepolia.id]: http("https://ethereum-sepolia-rpc.publicnode.com")
}
});
```
## Provider Setup
Wrap your app with the necessary providers:
```tsx app.tsx
import { WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { config } from "./wagmi.config";
const queryClient = new QueryClient();
function App() {
return (
{/* Your app */}
);
}
```
You can learn more about the `WagmiProvider` and its definition in the .
## Using the Connector
Connect with Para using Wagmi hooks:
```tsx connect-button.tsx
import { useConnect } from "wagmi";
function ConnectWithPara() {
const { connect, connectors } = useConnect();
// Find Para connector
const paraConnector = connectors.find(c => c.id === "para");
return (
connect({ connector: paraConnector })}>
Connect with Para
);
}
```
## Configuration Options
The Para connector supports these options:
```typescript
const connector = paraConnector({
para, // Your Para instance (required)
chains, // Supported chains array (required)
appName: "Your App", // Display name (required)
nameOverride: "Para", // Connector name override
idOverride: "para", // Connector ID override
});
```
## Next Steps
# Claim Staking Rewards
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/claim-rewards
import { Card } from '/snippets/v3/components/ui/card.mdx';
Withdraw your accumulated staking rewards from validators on Cosmos chains using CosmJS.
## Prerequisites
## Claim Rewards
```typescript
import { useMemo } from "react";
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from "@getpara/react-sdk/cosmos";
import { SigningStargateClient, coins } from "@cosmjs/stargate";
import { MsgWithdrawDelegatorReward } from "cosmjs-types/cosmos/distribution/v1beta1/tx";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
function ClaimRewards() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const client = useMemo(
() => protoSigner ? SigningStargateClient.connectWithSigner(RPC_URL, protoSigner) : undefined,
[protoSigner]
);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const claimStakingRewards = async () => {
if (!protoSigner || !address) return;
const validator = "cosmosvaloper1...";
const msgWithdrawReward = {
typeUrl: "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward",
value: MsgWithdrawDelegatorReward.fromPartial({
delegatorAddress: address,
validatorAddress: validator,
}),
};
const fee = {
amount: coins(5000, "uatom"),
gas: "200000",
};
try {
const result = await signAndBroadcastAsync({
messages: [msgWithdrawReward],
fee,
memo: "Claiming rewards via Para",
});
console.log("Rewards claimed:", result.transactionHash);
console.log("Gas used:", result.gasUsed);
} catch (error) {
console.error("Failed to claim rewards:", error);
}
};
if (isLoading) return Loading...
;
return (
Delegator: {address}
{isPending ? "Claiming..." : "Claim Rewards"}
);
}
```
## Next Steps
# Configure RPC Nodes with Cosmos Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/configure-rpc
import { Card } from '/snippets/v3/components/ui/card.mdx';
Learn how to configure custom RPC endpoints for different Cosmos-based chains when using CosmJS with Para.
## Prerequisites
## Configure Chain-Specific RPC
```typescript
import { useState } from "react";
import { useParaCosmjsProtoSigner } from "@getpara/react-sdk";
import { StargateClient, SigningStargateClient } from "@cosmjs/stargate";
const CHAIN_CONFIGS = {
cosmos: {
rpc: "https://rpc.cosmos.directory/cosmoshub",
chainId: "cosmoshub-4"
},
osmosis: {
rpc: "https://rpc.cosmos.directory/osmosis",
chainId: "osmosis-1"
},
celestia: {
rpc: "https://celestia-rpc.publicnode.com",
chainId: "celestia"
},
dydx: {
rpc: "https://dydx-dao-rpc.polkachu.com",
chainId: "dydx-mainnet-1"
}
};
function MultiChainExample() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const [heights, setHeights] = useState>({});
const checkChainStatus = async () => {
const cosmosClient = await StargateClient.connect(CHAIN_CONFIGS.cosmos.rpc);
const osmosisClient = await StargateClient.connect(CHAIN_CONFIGS.osmosis.rpc);
const cosmosHeight = await cosmosClient.getHeight();
const osmosisHeight = await osmosisClient.getHeight();
setHeights({ cosmos: cosmosHeight, osmosis: osmosisHeight });
console.log("Cosmos block height:", cosmosHeight);
console.log("Osmosis block height:", osmosisHeight);
};
if (isLoading) return Loading...
;
return (
Check Chain Status
{Object.entries(heights).map(([chain, height]) => (
{chain}: {height}
))}
);
}
```
## Next Steps
# Execute Transactions with Cosmos Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/execute-transactions
import { Card } from '/snippets/v3/components/ui/card.mdx';
Execute custom messages and interact with Cosmos modules using CosmJS with Para wallets.
## Prerequisites
## Execute Custom Messages
```typescript
import { useMemo } from "react";
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from "@getpara/react-sdk/cosmos";
import { SigningStargateClient, coins } from "@cosmjs/stargate";
import { MsgSend } from "cosmjs-types/cosmos/bank/v1beta1/tx";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
function CustomTransaction() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const client = useMemo(
() => protoSigner ? SigningStargateClient.connectWithSigner(RPC_URL, protoSigner) : undefined,
[protoSigner]
);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const executeCustomMessage = async () => {
if (!protoSigner || !address) return;
const msgSend = {
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: MsgSend.fromPartial({
fromAddress: address,
toAddress: "cosmos1...",
amount: coins(1000000, "uatom"),
}),
};
const fee = {
amount: coins(5000, "uatom"),
gas: "200000",
};
try {
const result = await signAndBroadcastAsync({
messages: [msgSend],
fee,
memo: "Custom message via Para",
});
console.log("Transaction hash:", result.transactionHash);
console.log("Code:", result.code);
} catch (error) {
console.error("Transaction failed:", error);
}
};
if (isLoading) return Loading...
;
return (
From: {address}
{isPending ? "Executing..." : "Execute Custom Message"}
);
}
```
## Next Steps
# Sponsor Gas Fees on Cosmos
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/gas-sponsorship
import { Card } from '/snippets/v3/components/ui/card.mdx';
This guide demonstrates how to implement gas sponsorship on Cosmos networks using fee grants. Unlike EVM chains that use account abstraction for gasless transactions, Cosmos networks natively support gas sponsorship through fee grants, allowing a grantor address to pay for another account's transaction fees.
## Prerequisites
## Understanding Fee Grants
Fee grants in Cosmos allow one account (grantor) to pay transaction fees for another account (grantee). This mechanism provides native gas sponsorship without requiring smart contracts or account abstraction.
Key concepts:
- **Grantor**: The account that pays for gas fees
- **Grantee**: The account whose transactions are sponsored
- **Allowance**: Defines spending limits and expiration for the grant
Only one fee grant is allowed per granter-grantee pair. Self-grants are not permitted.
## Creating a Basic Fee Grant
Create a basic allowance to grant gas sponsorship to another address:
```typescript
import { MsgGrantAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/tx";
import { BasicAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant";
const granterAddress = paraSigner.address; // From your Para signer setup
const granteeAddress = "cosmos1grantee..."; // Replace with actual grantee address
// Create basic allowance with spending limits
const basicAllowance = BasicAllowance.fromPartial({
spendLimit: [{
denom: "uatom",
amount: "1000000" // 1 ATOM limit
}],
expiration: {
seconds: BigInt(Math.floor(Date.now() / 1000) + 86400), // 24 hours from now
nanos: 0
}
});
// Create the grant message
const grantMsg = {
typeUrl: "/cosmos.feegrant.v1beta1.MsgGrantAllowance",
value: MsgGrantAllowance.fromPartial({
granter: granterAddress,
grantee: granteeAddress,
allowance: {
typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
value: BasicAllowance.encode(basicAllowance).finish()
}
})
};
// Sign and broadcast the grant transaction
const result = await client.signAndBroadcast(
granterAddress,
[grantMsg],
"auto", // Let the client estimate gas
"Granting fee allowance"
);
```
## Using Fee Grants as a Grantee
Once a fee grant is established, the grantee can perform transactions with sponsored gas fees:
```typescript
// Create a signer for the grantee
const granteeSigner = createParaProtoSigner({ para: granteeParaInstance, prefix: "cosmos" });
const granteeClient = await SigningStargateClient.connectWithSigner(rpcUrl, granteeSigner);
// Send tokens with sponsored gas
const result = await granteeClient.sendTokens(
granteeAddress,
"cosmos1recipient...",
[{ denom: "uatom", amount: "100000" }], // 0.1 ATOM
{
amount: [{ denom: "uatom", amount: "5000" }],
gas: "200000",
granter: granterAddress // This tells the network to use the fee grant
}
);
```
## Querying Fee Grants
Check existing grants before creating new ones:
```typescript
// Query grants for a specific grantee
const grantsByGrantee = await fetch(
`${restUrl}/cosmos/feegrant/v1beta1/allowances/${granteeAddress}`
).then(res => res.json());
// Query all grants by a specific granter
const grantsByGranter = await fetch(
`${restUrl}/cosmos/feegrant/v1beta1/issued/${granterAddress}`
).then(res => res.json());
// Query a specific grant
const specificGrant = await fetch(
`${restUrl}/cosmos/feegrant/v1beta1/allowance/${granterAddress}/${granteeAddress}`
).then(res => res.json());
```
## Other Allowance Types
### Periodic Allowance
Resets spending limits periodically:
```typescript
import { PeriodicAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant";
const periodicAllowance = PeriodicAllowance.fromPartial({
basic: {
spendLimit: [{
denom: "uatom",
amount: "10000000" // 10 ATOM total limit
}],
expiration: null // No expiration
},
period: { seconds: BigInt(86400), nanos: 0 }, // 24 hours
periodSpendLimit: [{
denom: "uatom",
amount: "1000000" // 1 ATOM per period
}]
});
```
### Allowed Message Allowance
Restricts which message types can be sponsored:
```typescript
import { AllowedMsgAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/feegrant";
const allowedMsgAllowance = AllowedMsgAllowance.fromPartial({
allowance: {
typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
value: BasicAllowance.encode(basicAllowance).finish()
},
allowedMessages: [
"/cosmos.bank.v1beta1.MsgSend",
"/cosmos.staking.v1beta1.MsgDelegate"
]
});
```
## Revoking Fee Grants
Remove a fee grant when it's no longer needed:
```typescript
import { MsgRevokeAllowance } from "cosmjs-types/cosmos/feegrant/v1beta1/tx";
const revokeMsg = {
typeUrl: "/cosmos.feegrant.v1beta1.MsgRevokeAllowance",
value: MsgRevokeAllowance.fromPartial({
granter: granterAddress,
grantee: granteeAddress
})
};
const result = await client.signAndBroadcast(
granterAddress,
[revokeMsg],
"auto"
);
```
## Advanced: Server-Controlled Gas Sponsorship
For production applications, use server-side controlled wallets as grantors while allowing users to authenticate client-side as grantees. This pattern uses Para's pregenerated wallets to create an app-controlled grantor wallet.
### Server-Side Setup
Create and manage a grantor wallet on your server:
```typescript
// server.ts
import { Para } from "@getpara/server-sdk";
import { SigningStargateClient } from "@cosmjs/stargate";
import { createParaProtoSigner } from "@getpara/cosmjs-adapter";
const serverPara = new Para(process.env.PARA_API_KEY);
// Create a pregen wallet for gas sponsorship
const grantorWallet = await serverPara.createPregenWallet({
type: 'COSMOS',
pregenId: { customId: "app-gas-sponsor-wallet" }
});
// Store the user share securely
const userShare = await serverPara.getUserShare();
// Store userShare in your secure database
```
### API Endpoint for Creating Grants
```typescript
// POST /api/create-fee-grant
async function createFeeGrantForUser(userAddress: string) {
// Load the grantor wallet
await serverPara.setUserShare(storedUserShare);
// Set up Cosmos client
const granterSigner = createParaProtoSigner({ para: serverPara, prefix: "cosmos" });
const client = await SigningStargateClient.connectWithSigner(rpcUrl, granterSigner);
// Check if grant already exists
const existingGrant = await fetch(
`${restUrl}/cosmos/feegrant/v1beta1/allowance/${granterSigner.address}/${userAddress}`
).then(res => res.json());
if (existingGrant.allowance) {
// Revoke existing grant first
const revokeMsg = {
typeUrl: "/cosmos.feegrant.v1beta1.MsgRevokeAllowance",
value: MsgRevokeAllowance.fromPartial({
granter: granterSigner.address,
grantee: userAddress
})
};
await client.signAndBroadcast(
granterSigner.address,
[revokeMsg],
"auto"
);
}
// Create new grant with daily limit
const basicAllowance = BasicAllowance.fromPartial({
spendLimit: [{
denom: "uatom",
amount: "1000000" // 1 ATOM daily limit
}],
expiration: {
seconds: BigInt(Math.floor(Date.now() / 1000) + 86400),
nanos: 0
}
});
const grantMsg = {
typeUrl: "/cosmos.feegrant.v1beta1.MsgGrantAllowance",
value: MsgGrantAllowance.fromPartial({
granter: granterSigner.address,
grantee: userAddress,
allowance: {
typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
value: BasicAllowance.encode(basicAllowance).finish()
}
})
};
const result = await client.signAndBroadcast(
granterSigner.address,
[grantMsg],
"auto"
);
return {
transactionHash: result.transactionHash,
granterAddress: granterSigner.address
};
}
```
### Client-Side Integration
```typescript
import { useState } from "react";
import { useParaCosmjsProtoSigner } from "@getpara/react-sdk/cosmos";
import { SigningStargateClient } from "@cosmjs/stargate";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
function MyApp() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const [granterAddress, setGranterAddress] = useState();
const address = protoSigner?.address;
const setupGasSponsorship = async () => {
if (!address) return;
const response = await fetch('/api/create-fee-grant', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userAddress: address })
});
const { granterAddress: granter } = await response.json();
setGranterAddress(granter);
};
const performSponsoredTransaction = async (recipientAddress: string, amount: string) => {
if (!protoSigner || !address || !granterAddress) return;
const client = await SigningStargateClient.connectWithSigner(RPC_URL, protoSigner);
const result = await client.sendTokens(
address,
recipientAddress,
[{ denom: "uatom", amount }],
{
amount: [{ denom: "uatom", amount: "5000" }],
gas: "200000",
granter: granterAddress
}
);
return result.transactionHash;
};
if (isLoading) return Loading...
;
return (
<>
Address: {address}
Activate Gas Sponsorship
performSponsoredTransaction("cosmos1...", "100000")}>
Send Sponsored Transaction
>
);
}
```
### Complete Server Implementation
```typescript
// Complete server implementation with grant management
class FeeGrantService {
private serverPara: Para;
private granterAddress?: string;
constructor() {
this.serverPara = new Para(process.env.PARA_API_KEY);
}
async initialize() {
// Load or create grantor wallet
const userShare = await loadUserShareFromDatabase();
await this.serverPara.setUserShare(userShare);
const signer = createParaProtoSigner({ para: this.serverPara, prefix: "cosmos" });
this.granterAddress = signer.address;
}
async createGrant(userAddress: string, limitAmount: string, periodSeconds: number) {
const signer = createParaProtoSigner({ para: this.serverPara, prefix: "cosmos" });
const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer);
const allowance = BasicAllowance.fromPartial({
spendLimit: [{
denom: "uatom",
amount: limitAmount
}],
expiration: {
seconds: BigInt(Math.floor(Date.now() / 1000) + periodSeconds),
nanos: 0
}
});
const msg = {
typeUrl: "/cosmos.feegrant.v1beta1.MsgGrantAllowance",
value: MsgGrantAllowance.fromPartial({
granter: this.granterAddress,
grantee: userAddress,
allowance: {
typeUrl: "/cosmos.feegrant.v1beta1.BasicAllowance",
value: BasicAllowance.encode(allowance).finish()
}
})
};
return client.signAndBroadcast(this.granterAddress, [msg], "auto");
}
async revokeGrant(userAddress: string) {
const signer = createParaProtoSigner({ para: this.serverPara, prefix: "cosmos" });
const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer);
const msg = {
typeUrl: "/cosmos.feegrant.v1beta1.MsgRevokeAllowance",
value: MsgRevokeAllowance.fromPartial({
granter: this.granterAddress,
grantee: userAddress
})
};
return client.signAndBroadcast(this.granterAddress, [msg], "auto");
}
}
```
## Best Practices
- Set appropriate spending limits based on expected transaction volume
- Use expiration times to automatically clean up unused grants
- Monitor grant usage to control costs and detect abuse
- Consider using periodic allowances for regular users
- Use allowed message allowances to restrict transaction types
- Remember that creating and revoking grants also incur gas costs
Fee grants provide native gas sponsorship on Cosmos networks without requiring smart contracts, making them more efficient than EVM account abstraction solutions.
# IBC Cross-Chain Transfers
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/ibc-transfers
import { Card } from '/snippets/v3/components/ui/card.mdx';
Transfer tokens between different Cosmos chains using IBC (Inter-Blockchain Communication) with CosmJS.
## Prerequisites
## IBC Transfer
```typescript
import { useMemo } from "react";
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from "@getpara/react-sdk/cosmos";
import { SigningStargateClient, coins } from "@cosmjs/stargate";
import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx";
import { Height } from "cosmjs-types/ibc/core/client/v1/client";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
function IBCTransfer() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const client = useMemo(
() => protoSigner ? SigningStargateClient.connectWithSigner(RPC_URL, protoSigner) : undefined,
[protoSigner]
);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const sendIBCTransfer = async () => {
if (!protoSigner || !address || !client) return;
const resolvedClient = await client;
const currentHeight = await resolvedClient.getHeight();
const timeoutHeight = Height.fromPartial({
revisionNumber: 1n,
revisionHeight: BigInt(currentHeight + 1000),
});
const msgTransfer = {
typeUrl: "/ibc.applications.transfer.v1.MsgTransfer",
value: MsgTransfer.fromPartial({
sourcePort: "transfer",
sourceChannel: "channel-141",
token: {
denom: "uatom",
amount: "1000000",
},
sender: address,
receiver: "osmo1...",
timeoutHeight: timeoutHeight,
timeoutTimestamp: 0n,
}),
};
const fee = {
amount: coins(5000, "uatom"),
gas: "250000",
};
try {
const result = await signAndBroadcastAsync({
messages: [msgTransfer],
fee,
memo: "IBC transfer via Para",
});
console.log("IBC transfer initiated:", result.transactionHash);
} catch (error) {
console.error("IBC transfer failed:", error);
}
};
if (isLoading) return Loading...
;
return (
From: {address}
{isPending ? "Transferring..." : "Send 1 ATOM to Osmosis"}
);
}
```
## Next Steps
# Query Wallet Balances with Cosmos Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/query-balances
import { Card } from '/snippets/v3/components/ui/card.mdx';
Query token balances for any Cosmos address or your connected Para wallet using CosmJS.
## Prerequisites
## Query Balances
```typescript
import { useState, useEffect } from "react";
import { useParaCosmjsProtoSigner } from "@getpara/react-sdk/cosmos";
import { StargateClient } from "@cosmjs/stargate";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
function BalanceDisplay() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const [balances, setBalances] = useState<{ denom: string; amount: string }[]>([]);
const address = protoSigner?.address;
const queryBalances = async () => {
if (!address) return;
const client = await StargateClient.connect(RPC_URL);
const allBalances = await client.getAllBalances(address);
setBalances(allBalances);
const atomBalance = await client.getBalance(address, "uatom");
console.log("ATOM balance:", atomBalance.amount, atomBalance.denom);
};
if (isLoading) return Loading...
;
return (
Address: {address}
Query Balances
{balances.map(balance => (
{balance.amount} {balance.denom}
))}
);
}
```
## Next Steps
# Query Validator Information
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/query-validators
import { Card } from '/snippets/v3/components/ui/card.mdx';
Query validator information to make informed staking decisions on Cosmos chains using CosmJS.
## Prerequisites
## Query Validators
```typescript
import { useState } from "react";
import { useParaCosmjsProtoSigner } from "@getpara/react-sdk/cosmos";
import { StargateClient, setupStakingExtension, QueryClient } from "@cosmjs/stargate";
import { Tendermint37Client } from "@cosmjs/tendermint-rpc";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
interface ValidatorInfo {
name: string;
operatorAddress: string;
tokens: string;
commission: string;
}
function ValidatorInfo() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const [validators, setValidators] = useState([]);
const address = protoSigner?.address;
const queryValidators = async () => {
const tmClient = await Tendermint37Client.connect(RPC_URL);
const queryClient = QueryClient.withExtensions(tmClient, setupStakingExtension);
try {
const { validators: activeValidators } = await queryClient.staking.validators("BOND_STATUS_BONDED");
const validatorInfo = activeValidators.slice(0, 10).map(validator => ({
name: validator.description?.moniker || "Unknown",
operatorAddress: validator.operatorAddress,
tokens: validator.tokens,
commission: validator.commission?.commissionRates?.rate || "0",
}));
setValidators(validatorInfo);
if (address) {
const delegations = await queryClient.staking.delegatorDelegations(address);
console.log("Your delegations:", delegations);
}
} catch (error) {
console.error("Failed to query validators:", error);
}
};
if (isLoading) return Loading...
;
return (
Address: {address}
Query Top Validators
{validators.map(v => (
{v.name} - Commission: {v.commission}
))}
);
}
```
## Next Steps
# Send Tokens with Cosmos Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/send-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
Transfer tokens between Cosmos accounts using CosmJS with Para's secure wallet infrastructure.
## Prerequisites
## Send Tokens
```typescript
import { useParaCosmjsProtoSigner } from "@getpara/react-sdk/cosmos";
import { SigningStargateClient, coins } from "@cosmjs/stargate";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
function TokenTransfer() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const sendTokens = async () => {
if (!protoSigner || !address) return;
const recipient = "cosmos1...";
const amount = coins(1000000, "uatom");
const fee = {
amount: coins(5000, "uatom"),
gas: "200000",
};
try {
const client = await SigningStargateClient.connectWithSigner(RPC_URL, protoSigner);
const result = await client.sendTokens(
address,
recipient,
amount,
fee,
"Sent via Para"
);
console.log("Transaction hash:", result.transactionHash);
console.log("Gas used:", result.gasUsed);
} catch (error) {
console.error("Transfer failed:", error);
}
};
if (isLoading) return Loading...
;
return (
From: {address}
Send 1 ATOM
);
}
```
## Next Steps
# Setup Cosmos Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/setup-libraries
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Learn how to set up with Para SDK to interact with Cosmos-based blockchains.
## Prerequisites
Use the Proto signer for transaction operations like sending tokens, staking, and IBC transfers.
## Install
```bash
npm install @getpara/react-sdk @cosmjs/stargate
```
`@getpara/react-sdk` bundles `@getpara/cosmjs-v0-integration` — no separate integration package needed.
## Usage
Use the hook to create a CosmJS `OfflineDirectSigner` for your user's Para embedded wallet or external wallet. The hook wraps `signAndBroadcast` in a React Query mutation.
```tsx
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from "@getpara/react-sdk";
import { SigningStargateClient, coins } from "@cosmjs/stargate";
import { useState, useEffect } from "react";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
function SendTokens() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const [client, setClient] = useState();
useEffect(() => {
if (protoSigner) {
SigningStargateClient.connectWithSigner(RPC_URL, protoSigner).then(setClient);
}
}, [protoSigner]);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const handleSend = async () => {
const result = await signAndBroadcastAsync({
messages: [{ typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: {
fromAddress: protoSigner!.address,
toAddress: "cosmos1...",
amount: coins(1000000, "uatom"),
}}],
fee: { amount: coins(5000, "uatom"), gas: "200000" },
});
console.log("Tx hash:", result.transactionHash);
};
if (isLoading) return Loading...
;
return (
{isPending ? "Broadcasting..." : "Send 1 ATOM"}
);
}
```
### Wallet Resolution
When no `address` or `walletId` is passed, the hook resolves the wallet in this order:
1. **Selected wallet** — if the user selected a Cosmos wallet in the UI. If there is only one Cosmos wallet in the session, it is already selected by default
2. **First Cosmos wallet** — the first available Cosmos wallet on the account
To target a specific wallet:
```tsx
const { protoSigner } = useParaCosmjsProtoSigner({
address: "cosmos1...", // or walletId: "uuid-..."
prefix: "osmo", // optional chain prefix, defaults to "cosmos"
});
```
Use `createParaProtoSigner` to create a signer directly.
```typescript
import { createParaProtoSigner } from "@getpara/cosmjs-v0-integration";
import { SigningStargateClient } from "@cosmjs/stargate";
import Para from "@getpara/web-sdk";
const para = new Para("YOUR_API_KEY");
// Authenticate first...
const signer = createParaProtoSigner({ para, prefix: "cosmos" });
const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer);
```
### Wallet Resolution
When no `address` or `walletId` is passed, the factory picks the first available Cosmos wallet. To target a specific wallet:
```typescript
const signer = createParaProtoSigner({ para, prefix: "cosmos", address: "cosmos1..." });
```
Use the Amino signer for message signing (ADR-036) and authentication flows.
## Install
```bash
npm install @getpara/react-sdk @cosmjs/amino
```
## Usage
Use the hook to create a CosmJS `OfflineAminoSigner` for message signing (ADR-036) and authentication flows. The mutation hook also accepts an amino signer.
```tsx
import { useParaCosmjsAminoSigner } from "@getpara/react-sdk";
import { makeSignDoc } from "@cosmjs/amino";
function SignMessage() {
const { aminoSigner, isLoading } = useParaCosmjsAminoSigner();
const address = aminoSigner?.address;
const handleSign = async () => {
if (!aminoSigner || !address) return;
const signDoc = makeSignDoc(
[{ type: "sign/MsgSignData", value: { signer: address, data: btoa("Hello!") } }],
{ amount: [], gas: "0" }, "cosmoshub-4", "", 0, 0,
);
const { signature } = await aminoSigner.signAmino(address, signDoc);
console.log("Signature:", signature.signature);
};
if (isLoading) return Loading...
;
return Sign Message ;
}
```
Wallet resolution works the same as the Proto signer hook.
```typescript
import { createParaAminoSigner } from "@getpara/cosmjs-v0-integration";
const signer = createParaAminoSigner({ para, prefix: "cosmos" });
```
## Next Steps
# Sign Messages with Cosmos Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/sign-messages
import { Card } from '/snippets/v3/components/ui/card.mdx';
Sign arbitrary messages for authentication or verification using CosmJS with Para wallets.
## Prerequisites
## Sign Messages
Sign arbitrary messages using the ADR-036 standard for Cosmos authentication.
```typescript
import { useState } from "react";
import { useParaCosmjsAminoSigner } from "@getpara/react-sdk/cosmos";
import { makeSignDoc } from "@cosmjs/amino";
const CHAIN_ID = "cosmoshub-4";
function MessageSigning() {
const { aminoSigner, isLoading } = useParaCosmjsAminoSigner();
const [signature, setSignature] = useState();
const address = aminoSigner?.address;
const signArbitraryMessage = async () => {
if (!aminoSigner || !address) return;
const message = "Sign this message to authenticate with Para";
const signDoc = makeSignDoc(
[{ type: "sign/MsgSignData", value: { signer: address, data: btoa(message) } }],
{ amount: [], gas: "0" },
CHAIN_ID,
"",
0,
0
);
try {
const { signature: sig } = await aminoSigner.signAmino(address, signDoc);
setSignature(sig.signature);
console.log("Signature:", sig.signature);
} catch (error) {
console.error("Signing failed:", error);
}
};
if (isLoading) return Loading...
;
return (
Address: {address}
Sign Message
{signature &&
Signature: {signature}
}
);
}
```
## Next Steps
# Stake Tokens to Validators
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/stake-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
Delegate your tokens to validators on Cosmos chains to earn staking rewards using CosmJS.
## Prerequisites
## Stake Tokens
```typescript
import { useMemo } from "react";
import { useParaCosmjsProtoSigner, useParaCosmjsSignAndBroadcast } from "@getpara/react-sdk/cosmos";
import { SigningStargateClient, coins } from "@cosmjs/stargate";
import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx";
const RPC_URL = "https://rpc.cosmos.directory/cosmoshub";
function StakeTokens() {
const { protoSigner, isLoading } = useParaCosmjsProtoSigner();
const address = protoSigner?.address;
const client = useMemo(
() => protoSigner ? SigningStargateClient.connectWithSigner(RPC_URL, protoSigner) : undefined,
[protoSigner]
);
const { signAndBroadcastAsync, isPending } = useParaCosmjsSignAndBroadcast(protoSigner, client);
const delegateToValidator = async () => {
if (!protoSigner || !address) return;
const validator = "cosmosvaloper1...";
const msgDelegate = {
typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
value: MsgDelegate.fromPartial({
delegatorAddress: address,
validatorAddress: validator,
amount: {
denom: "uatom",
amount: "1000000",
},
}),
};
const fee = {
amount: coins(5000, "uatom"),
gas: "250000",
};
try {
const result = await signAndBroadcastAsync({
messages: [msgDelegate],
fee,
memo: "Staking with Para",
});
console.log("Delegation successful:", result.transactionHash);
} catch (error) {
console.error("Delegation failed:", error);
}
};
if (isLoading) return Loading...
;
return (
Delegator: {address}
{isPending ? "Staking..." : "Stake 1 ATOM"}
);
}
```
## Next Steps
# Verify Signatures with Cosmos Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/cosmos/verify-signatures
import { Card } from '/snippets/v3/components/ui/card.mdx';
Verify signatures from signed messages to authenticate users or validate transactions using CosmJS.
## Prerequisites
## Verify Signatures
Verify ADR-036 signatures using CosmJS crypto utilities.
```typescript
import { useState } from "react";
import { fromBase64, toUtf8 } from "@cosmjs/encoding";
import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto";
import { serializeSignDoc, makeSignDoc } from "@cosmjs/amino";
function SignatureVerification() {
const [isValid, setIsValid] = useState(null);
const verifyADR036Signature = async () => {
const pubkeyBase64 = "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ";
const signatureBase64 = "..."; // Signature from signAmino
const originalMessage = "Hello Para!";
const signerAddress = "cosmos1...";
const chainId = "cosmoshub-4";
try {
const pubkey = fromBase64(pubkeyBase64);
const signature = fromBase64(signatureBase64);
const signDoc = makeSignDoc(
[{ type: "sign/MsgSignData", value: { signer: signerAddress, data: btoa(originalMessage) } }],
{ amount: [], gas: "0" },
chainId,
"",
0,
0
);
const serialized = serializeSignDoc(signDoc);
const messageHash = sha256(serialized);
const sig = Secp256k1Signature.fromDer(signature);
const valid = await Secp256k1.verifySignature(sig, messageHash, pubkey);
setIsValid(valid);
console.log("Signature valid:", valid);
} catch (error) {
console.error("Verification failed:", error);
setIsValid(false);
}
};
return (
Verify Signature
{isValid !== null &&
Valid: {isValid ? "Yes" : "No"}
}
);
}
```
## Next Steps
# Account Abstraction Integrations
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/account-abstraction
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import SmartAccountOverview from '/snippets/v3/aa/smart-account-overview.mdx';
## Using with External Wallets
If your user signs in with a supported browser wallet (MetaMask, Rainbow, etc.) and has no embedded wallets in their session, invoking any of the hooks and not specifying an address (or specifying the external wallet's address) will use that wallet as the smart account signer, with no extra configuration needed.
* External wallets can only use **EIP-4337 mode**. EIP-7702 requires `signAuthorization`, which most browser wallets currently don't support — attempting it throws a `SmartAccountError` with code `INVALID_CONFIG`.
* The external wallet must be on the **same chain** as the smart account before calling `sendTransaction`. A mismatch throws a `SmartAccountError` with code `CHAIN_MISMATCH`. You can use wagmi's `useSwitchChain` to manage your connection state before attempting a transaction.
## Provider Guides
Select a provider below for installation and usage instructions.
provides modular smart accounts with built-in gas sponsorship via Alchemy's Gas Manager. Create and manage smart accounts, submit gasless transactions, and execute batched UserOperations — all without requiring users to leave your application. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create an and obtain your **API key** and **Gas Policy ID** from the Alchemy dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate Alchemy smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx AlchemySmartAccount.tsx
import { useAlchemySmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const ALCHEMY_API_KEY = process.env.NEXT_PUBLIC_ALCHEMY_API_KEY!;
const GAS_POLICY_ID = process.env.NEXT_PUBLIC_ALCHEMY_GAS_POLICY_ID!;
export function AlchemySmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// account client setup, and paymaster configuration internally.
// It re-creates the account automatically if any config value changes.
const { smartAccount, isLoading, error } = useAlchemySmartAccount({
apiKey: ALCHEMY_API_KEY,
chain: sepolia,
gasPolicyId: GAS_POLICY_ID, // enables gas sponsorship
mode: "4337", // or "7702" — see EIP comparison above
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
// All calls are bundled into a single on-chain transaction.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript alchemy-action.ts
import { createAlchemySmartAccount } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createAlchemySmartAccount({
para,
apiKey: "YOUR_ALCHEMY_API_KEY",
chain: sepolia,
gasPolicyId: "YOUR_GAS_POLICY_ID",
mode: "4337", // or "7702"
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Alchemy requires chains from `@account-kit/infra` (e.g. `sepolia`, `baseSepolia`). Plain viem chains are
automatically mapped if an Alchemy equivalent exists. For gasless transactions, set up a Gas Manager Policy in your and pass the policy ID as `gasPolicyId`.
**External wallet support:** Alchemy supports external wallets (e.g. MetaMask, Rainbow) as signers via the `signer` parameter in 4337 mode. Requesting `mode: "7702"` with an external wallet will throw a `SmartAccountError` — external wallets cannot produce the raw `signAuthorization` needed for EIP-7702.
is an embedded AA wallet powering many smart accounts across EVM chains. Known for its extensive feature set including gas sponsorship, session keys, recovery, multisig, and . Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **Project ID** from the ZeroDev dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate ZeroDev smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx ZeroDevSmartAccount.tsx
import { useZeroDevSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const ZERODEV_PROJECT_ID = process.env.NEXT_PUBLIC_ZERODEV_PROJECT_ID!;
export function ZeroDevSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Kernel account setup, and ECDSA validator configuration internally.
const { smartAccount, isLoading, error } = useZeroDevSmartAccount({
projectId: ZERODEV_PROJECT_ID,
chain: sepolia,
mode: "4337", // or "7702"
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript zerodev-action.ts
import { createZeroDevSmartAccount } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createZeroDevSmartAccount({
para,
projectId: "YOUR_ZERODEV_PROJECT_ID",
chain: sepolia,
mode: "4337", // or "7702"
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
You can optionally pass `bundlerUrl` and `paymasterUrl` to use custom infrastructure instead of ZeroDev's defaults. For more on managing your ZeroDev project and RPC endpoints, see the .
**External wallet support:** ZeroDev supports external wallets (e.g. MetaMask, Rainbow) as signers via the `signer` parameter in 4337 mode. Requesting `mode: "7702"` with an external wallet will throw a `SmartAccountError` — external wallets cannot produce the raw `signAuthorization` needed for EIP-7702.
provides account abstraction infrastructure via , a TypeScript library built on viem with no extra dependencies and a small bundle size. Pimlico supports multiple account implementations including Safe, Kernel, Biconomy, and SimpleAccount. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **API key** from the Pimlico dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate Pimlico smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx PimlicoSmartAccount.tsx
import { usePimlicoSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const PIMLICO_API_KEY = process.env.NEXT_PUBLIC_PIMLICO_API_KEY!;
export function PimlicoSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// simple account setup, and Pimlico paymaster configuration internally.
const { smartAccount, isLoading, error } = usePimlicoSmartAccount({
apiKey: PIMLICO_API_KEY,
chain: sepolia,
mode: "4337", // or "7702"
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript pimlico-action.ts
import { createPimlicoSmartAccount } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createPimlicoSmartAccount({
para,
apiKey: "YOUR_PIMLICO_API_KEY",
chain: sepolia,
mode: "4337", // or "7702"
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
The Pimlico bundler/paymaster URL is automatically constructed from your API key and chain name. You can override it with a custom `rpcUrl`. Pimlico's permissionless.js also supports other account types (Safe, Kernel, Biconomy, SimpleAccount) — see the .
**External wallet support:** Pimlico supports external wallets (e.g. MetaMask, Rainbow) as signers via the `signer` parameter in 4337 mode. Requesting `mode: "7702"` with an external wallet will throw a `SmartAccountError` — external wallets cannot produce the raw `signAuthorization` needed for EIP-7702.
is a full-stack AA toolkit built on ERC-4337 that provides smart accounts, paymasters, and bundlers. Its Multi-chain Execution Environment (MEE) enables cross-chain orchestration of transactions. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **API key** from the Biconomy dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate Biconomy smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx BiconomySmartAccount.tsx
import { useBiconomySmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const BICONOMY_API_KEY = process.env.NEXT_PUBLIC_BICONOMY_API_KEY!;
export function BiconomySmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Nexus account setup, and MEE client configuration internally.
const { smartAccount, isLoading, error } = useBiconomySmartAccount({
apiKey: BICONOMY_API_KEY,
chain: sepolia,
mode: "4337", // or "7702"
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript biconomy-action.ts
import { createBiconomySmartAccount } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createBiconomySmartAccount({
para,
apiKey: "YOUR_BICONOMY_API_KEY",
chain: sepolia,
mode: "4337", // or "7702"
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Biconomy transactions are executed via the MEE (Multi-chain Execution Environment). You can optionally pass a custom `meeUrl` to use your own MEE node.
**External wallet support:** Biconomy supports external wallets (e.g. MetaMask, Rainbow) as signers via the `signer` parameter in 4337 mode. Requesting `mode: "7702"` with an external wallet will throw a `SmartAccountError` — external wallets cannot produce the raw `signAuthorization` needed for EIP-7702.
provides smart wallets with built-in gas sponsorship.
Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **Client ID** from the Thirdweb dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate Thirdweb smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx ThirdwebSmartAccount.tsx
import { useThirdwebSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const THIRDWEB_CLIENT_ID = process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID!;
export function ThirdwebSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// smart wallet setup, and gas sponsorship configuration internally.
const { smartAccount, isLoading, error } = useThirdwebSmartAccount({
clientId: THIRDWEB_CLIENT_ID,
chain: sepolia,
sponsorGas: true, // enable gas sponsorship (default)
mode: "4337", // or "7702"
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript thirdweb-action.ts
import { createThirdwebSmartAccount } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createThirdwebSmartAccount({
para,
clientId: "YOUR_THIRDWEB_CLIENT_ID",
chain: sepolia,
sponsorGas: true, // enable gas sponsorship (default)
mode: "4337", // or "7702"
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Set `sponsorGas: false` to disable gas sponsorship. In EIP-4337 mode, you can optionally provide `factoryAddress` and `accountAddress` for custom smart wallet deployments.
**External wallet support:** Thirdweb supports external wallets (e.g. MetaMask, Rainbow) as signers via the `signer` parameter in 4337 mode. Requesting `mode: "7702"` with an external wallet will throw a `SmartAccountError` — external wallets cannot produce the raw `signAuthorization` needed for EIP-7702.
provides native EIP-7702 smart accounts with built-in gas sponsorship via relay infrastructure.
### Setup
1. Create a and obtain your **API key** from the Gelato dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate Gelato smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx GelatoSmartAccount.tsx
import { useGelatoSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const GELATO_API_KEY = process.env.NEXT_PUBLIC_GELATO_API_KEY!;
export function GelatoSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Gelato account setup, and 7702 delegation internally.
// Gelato only supports EIP-7702 mode.
const { smartAccount, isLoading, error } = useGelatoSmartAccount({
apiKey: GELATO_API_KEY,
chain: sepolia,
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript gelato-action.ts
import { createGelatoSmartAccount } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createGelatoSmartAccount({
para,
apiKey: "YOUR_GELATO_API_KEY",
chain: sepolia,
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Gelato only supports EIP-7702 mode. Gas sponsorship is built into Gelato's relay infrastructure — no separate paymaster configuration is needed.
**No external wallet support.** Gelato is 7702-only and requires `signAuthorization`, which external wallets (MetaMask, Rainbow, etc.) cannot produce. Passing an external wallet as `signer` will throw a `SmartAccountError`.
provides 7702-native smart accounts with a built-in relay — no bundler or paymaster needed.
### Setup
1. No API key or third-party account is needed. Porto uses its own relay RPC.
2. **Gas sponsorship:** On testnets, Porto's relay sponsors gas by default — no setup needed.
For **mainnet**, you must set up a to cover gas fees for your users. Run `pnpx porto onboard --admin-key` to create a merchant
account, deploy a server-side merchant route using `porto/server`, and pass your endpoint URL
as `merchantUrl` in the config below.
3. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate Porto smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx PortoSmartAccount.tsx
import { usePortoSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { parseEther } from "viem";
import { base } from "viem/chains";
export function PortoSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation
// and Porto relay configuration internally.
// Testnets: gas is sponsored by default, no merchantUrl needed.
// Mainnet: merchantUrl is required for gas sponsorship.
const { smartAccount, isLoading, error } = usePortoSmartAccount({
chain: base,
merchantUrl: '/porto/merchant', // required for mainnet
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript porto-action.ts
import { createPortoSmartAccount } from "@getpara/react-sdk";
import { parseEther } from "viem";
import { base } from "viem/chains";
// `para` is your authenticated Para instance
// Testnets: gas is sponsored by default, no merchantUrl needed.
// Mainnet: merchantUrl is required for gas sponsorship.
const smartAccount = await createPortoSmartAccount({
para,
chain: base,
merchantUrl: '/porto/merchant', // required for mainnet
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Porto only supports EIP-7702 mode. The Porto relay supports including Base, Optimism, Arbitrum, Ethereum, and several testnets. On testnets, gas fees are sponsored by default. For mainnet, a is required — pass `merchantUrl` to enable gas sponsorship.
**No external wallet support.** Porto is 7702-only and requires `signAuthorization`, which external wallets (MetaMask, Rainbow, etc.) cannot produce. Passing an external wallet as `signer` will throw a `SmartAccountError`.
(formerly Gnosis Safe) provides multi-signature smart contract wallets with ERC-4337 compatibility. The user's EOA from Para serves as the signer, while the Safe smart contract wallet holds assets and submits transactions. Powered by Pimlico's bundler and paymaster infrastructure.
### Setup
1. Create a and obtain your **API key** — Safe uses Pimlico for bundler and paymaster services.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate Safe smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx SafeSmartAccount.tsx
import { useSafeSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const PIMLICO_API_KEY = process.env.NEXT_PUBLIC_PIMLICO_API_KEY!;
export function SafeSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Safe account setup, and Pimlico paymaster configuration internally.
// Safe only supports EIP-4337 mode.
const { smartAccount, isLoading, error } = useSafeSmartAccount({
pimlicoApiKey: PIMLICO_API_KEY,
chain: sepolia,
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript safe-action.ts
import { createSafeSmartAccount } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createSafeSmartAccount({
para,
pimlicoApiKey: "YOUR_PIMLICO_API_KEY",
chain: sepolia,
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Safe only supports EIP-4337 mode. You can optionally pass `safeVersion` (default: `"1.4.1"`) and `saltNonce` for deterministic address generation.
**External wallet support:** Safe supports external wallets (e.g. MetaMask, Rainbow) as signers via the `signer` parameter.
provides cross-chain smart accounts with intent-based transactions, automatic bridging, and gas abstraction across multiple EVM chains. Transactions are routed through Rhinestone's orchestrator which handles cross-chain execution automatically.
### Setup
1. Obtain your **Rhinestone API key** and optionally a **Pimlico API key** for bundler/paymaster infrastructure.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate Rhinestone smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx RhinestoneSmartAccount.tsx
import { useRhinestoneSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const RHINESTONE_API_KEY = process.env.NEXT_PUBLIC_RHINESTONE_API_KEY!;
const PIMLICO_API_KEY = process.env.NEXT_PUBLIC_PIMLICO_API_KEY!;
export function RhinestoneSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Rhinestone account setup, and orchestrator configuration internally.
// Rhinestone only supports EIP-4337 mode.
const { smartAccount, isLoading, error } = useRhinestoneSmartAccount({
chain: sepolia,
rhinestoneApiKey: RHINESTONE_API_KEY,
pimlicoApiKey: PIMLICO_API_KEY,
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript rhinestone-action.ts
import { createRhinestoneSmartAccount } from "@getpara/react-sdk";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createRhinestoneSmartAccount({
para,
chain: sepolia,
rhinestoneApiKey: "YOUR_RHINESTONE_API_KEY",
pimlicoApiKey: "YOUR_PIMLICO_API_KEY",
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
Rhinestone only supports EIP-4337 mode. Both `rhinestoneApiKey` and `pimlicoApiKey` are optional but recommended for production use. For advanced cross-chain use cases with automatic bridging, see the .
**External wallet support:** Rhinestone supports external wallets (e.g. MetaMask, Rainbow) as signers via the `signer` parameter.
provides Coinbase smart accounts on Base. Uses viem's built-in `toCoinbaseSmartAccount` — no additional bundler packages required.
### Setup
1. Create a and obtain your **RPC token** from the paymaster URL in the CDP dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/react-sdk viem
```
```bash yarn
yarn add @getpara/react-sdk viem
```
```bash pnpm
pnpm add @getpara/react-sdk viem
```
### Usage
The simplest way to integrate CDP smart accounts into your Para React application. The hook manages initialization, caching, and re-creation automatically via React Query.
```tsx CDPSmartAccount.tsx
import { useCDPSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { baseSepolia } from "viem/chains";
import { parseEther } from "viem";
const CDP_RPC_TOKEN = process.env.NEXT_PUBLIC_CDP_RPC_TOKEN!;
export function CDPSmartAccount() {
// Initialize the smart account. The hook handles Para signer creation,
// Coinbase account setup, and CDP paymaster configuration internally.
// CDP only supports EIP-4337 mode on Base and Base Sepolia.
const { smartAccount, isLoading, error } = useCDPSmartAccount({
rpcToken: CDP_RPC_TOKEN,
chain: baseSepolia,
});
// Wrap sendTransaction in a mutation for loading/error state management.
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
// Wrap sendBatchTransaction for executing multiple calls atomically.
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Smart Account: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
{/* Single transaction */}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
Send failed: {sendError.message}
}
{/* Batched transactions */}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
Batch failed: {batchError.message}
}
);
}
```
For use outside React components or when you need more control over initialization.
```typescript cdp-action.ts
import { createCDPSmartAccount } from "@getpara/react-sdk";
import { baseSepolia } from "viem/chains";
import { parseEther } from "viem";
// `para` is your authenticated Para instance
const smartAccount = await createCDPSmartAccount({
para,
rpcToken: "YOUR_CDP_RPC_TOKEN",
chain: baseSepolia,
});
if (smartAccount) {
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
// Send batched transactions
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
}
```
CDP only supports EIP-4337 mode and is limited to **Base** and **Base Sepolia** chains. No additional bundler packages are needed — CDP uses viem's built-in `toCoinbaseSmartAccount`.
**No external wallet support.** CDP's Coinbase smart wallet uses raw `ecrecover` for signature verification, which is incompatible with the EIP-191 prefix that external wallets (MetaMask, Rainbow, etc.) add when signing. Passing an external wallet as `signer` will throw a `SmartAccountError`.
## Examples
Explore working examples of Para with AA providers:
Node.js example with Alchemy Account Kit
Node.js example with ZeroDev Kernel accounts
Next.js example with Rhinestone cross-chain accounts
# Configure RPC Endpoints
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/configure-rpc
import { Card } from '/snippets/v3/components/ui/card.mdx';
Configure custom RPC endpoints and chains for networks using Web3 libraries. This guide uses public testnet RPCs for demonstration and shows minimal setup with predefined and custom chains where applicable.
## Prerequisites
You need to start with setting up your Web3 library clients with Para. This guide assumes you have already configured your Para instance and are ready to integrate with Ethers.js, Viem, or Wagmi.
## Configure Custom RPC Endpoints
```typescript
import { useMemo } from "react";
import { ethers } from "ethers";
import { useParaEthersSigner } from "@getpara/react-sdk";
const chainId = 11155111;
const rpcUrl = "https://ethereum-sepolia-rpc.publicnode.com";
function ConfigureRPC() {
const provider = useMemo(
() => new ethers.JsonRpcProvider(rpcUrl, chainId, { staticNetwork: true }),
[]
);
const { ethersSigner } = useParaEthersSigner({ provider });
// ethersSigner is ready to use for signing and sending transactions
}
```
```typescript
import { useParaViemClient } from "@getpara/react-sdk";
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
const chain = sepolia;
const transport = http("https://ethereum-sepolia-rpc.publicnode.com");
const publicClient = createPublicClient({ chain, transport });
function ConfigureRPC() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain, transport }
});
// viemClient is ready to use for signing and sending transactions
}
```
For custom chains:
```typescript
const customChain = {
id: 99999,
name: "Custom Chain",
network: "custom",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: {
default: { http: ["https://custom-rpc.example.com"] },
},
};
const customTransport = http("https://custom-rpc.example.com");
const customPublicClient = createPublicClient({
chain: customChain,
transport: customTransport
});
function ConfigureCustomRPC() {
const { viemClient: customWalletClient } = useParaViemClient({
walletClientConfig: { chain: customChain, transport: customTransport }
});
}
```
```typescript
import { createConfig, http } from "wagmi";
import { sepolia } from "wagmi/chains";
import { paraConnector } from "@getpara/wagmi-v2-integration";
const chains = [sepolia] as const;
const connector = paraConnector({
para,
chains,
appName: "Your App"
});
const config = createConfig({
chains,
connectors: [connector],
transports: {
[sepolia.id]: http("https://ethereum-sepolia-rpc.publicnode.com")
}
});
```
For custom chains:
```typescript
const customChain = {
id: 99999,
name: "Custom Chain",
network: "custom",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: {
default: { http: ["https://custom-rpc.example.com"] },
},
};
const extendedChains = [sepolia, customChain] as const;
const extendedConnector = paraConnector({
para,
chains: extendedChains,
appName: "Your App"
});
const extendedConfig = createConfig({
chains: extendedChains,
connectors: [extendedConnector],
transports: {
[sepolia.id]: http("https://ethereum-sepolia-rpc.publicnode.com"),
[customChain.id]: http("https://custom-rpc.example.com")
}
});
```
## Next Steps
# Estimate Gas for Transactions
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/estimate-gas
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Accurately estimate gas costs for transactions to ensure reliable execution without overpaying. This guide covers gas estimation for simple transfers and smart contract interactions using , , and .
## Prerequisites
You need Web3 libraries configured with Para authentication.
## Estimate Gas for a Transaction
```typescript
import { ethers } from "ethers";
async function estimateGas(
provider: ethers.Provider,
to: string,
value: string,
data?: string,
maxFeePerGas?: string,
maxPriorityFeePerGas?: string
) {
const tx = {
to,
value: ethers.parseEther(value),
data: data || "0x",
...(maxFeePerGas ? { maxFeePerGas: ethers.parseUnits(maxFeePerGas, "gwei") } : {}),
...(maxPriorityFeePerGas ? { maxPriorityFeePerGas: ethers.parseUnits(maxPriorityFeePerGas, "gwei") } : {})
};
const gasEstimate = await provider.estimateGas(tx);
return gasEstimate;
}
```
```typescript
import { parseEther, parseGwei } from "viem";
async function estimateGas(
publicClient: PublicClient,
account: Account | `0x${string}`,
to: `0x${string}`,
value: string,
data?: `0x${string}`,
maxFeePerGas?: string,
maxPriorityFeePerGas?: string
) {
const gasEstimate = await publicClient.estimateGas({
account,
to,
value: parseEther(value),
data: data || "0x",
...(maxFeePerGas ? { maxFeePerGas: parseGwei(maxFeePerGas) } : {}),
...(maxPriorityFeePerGas ? { maxPriorityFeePerGas: parseGwei(maxPriorityFeePerGas) } : {})
});
return gasEstimate;
}
```
```typescript
import { useEstimateGas } from "@getpara/react-sdk/wagmi";
import { parseEther, parseGwei } from "viem";
function EstimateGas({
to,
value,
data,
maxFeePerGas,
maxPriorityFeePerGas,
}: {
to: `0x${string}`;
value: string;
data?: `0x${string}`;
maxFeePerGas?: string;
maxPriorityFeePerGas?: string;
}) {
const { data: gas } = useEstimateGas({
to,
value: parseEther(value),
data: data || "0x",
...(maxFeePerGas ? { maxFeePerGas: parseGwei(maxFeePerGas) } : {}),
...(maxPriorityFeePerGas ? { maxPriorityFeePerGas: parseGwei(maxPriorityFeePerGas) } : {})
});
return (
// Your component JSX here
)
}
```
# Execute Transactions
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/execute-transactions
import { Card } from '/snippets/v3/components/ui/card.mdx';
Execute complex transactions with custom data and manage their lifecycle using Para.
## Prerequisites
You need Web3 libraries configured with Para authentication.
## Execute Raw Transactions
```tsx
import { useParaEthersSigner, useParaEthersSendTransaction } from "@getpara/react-sdk";
import { ethers, JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function ExecuteTransaction() {
const { ethersSigner } = useParaEthersSigner({ provider });
const {
sendTransactionAsync,
isPending,
data: receipt,
} = useParaEthersSendTransaction(ethersSigner);
return (
sendTransaction({
to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
data: "0x",
value: ethers.parseEther("0.01"),
})
}
disabled={isPending}
>
{isPending ? "Executing..." : "Execute Transaction"}
{receipt &&
Transaction: {receipt.hash}
}
);
}
```
## Execute Contract Functions
```tsx
import { useParaEthersSigner, useParaEthersWriteContract } from "@getpara/react-sdk";
import { JsonRpcProvider } from "ethers";
const CONTRACT_ABI = [
"function setGreeting(string memory _greeting)",
"function greet() view returns (string)"
];
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function ContractInteraction({ contractAddress }: { contractAddress: string }) {
const { ethersSigner } = useParaEthersSigner({ provider });
const {
writeContractAsync,
isPending,
data: receipt,
} = useParaEthersWriteContract(ethersSigner);
return (
writeContract({
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "setGreeting",
args: ["Hello from Para!"],
})
}
disabled={isPending}
>
{isPending ? "Updating..." : "Update Greeting"}
{receipt &&
Transaction: {receipt.hash}
}
);
}
```
## Execute Raw Transactions
```tsx
import { useParaViemClient, useParaViemSendTransaction } from "@getpara/react-sdk";
import { parseEther, http } from "viem";
import { sepolia } from "viem/chains";
function ExecuteTransaction() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const {
sendTransactionAsync,
isPending,
data: hash,
} = useParaViemSendTransaction(viemClient);
return (
sendTransaction({
to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
data: "0x",
value: parseEther("0.01"),
})
}
disabled={isPending}
>
{isPending ? "Executing..." : "Execute Transaction"}
{hash &&
Transaction: {hash}
}
);
}
```
## Execute Contract Functions
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-sdk";
import { http } from "viem";
import { sepolia } from "viem/chains";
const CONTRACT_ABI = [
{
name: "setGreeting",
type: "function",
stateMutability: "nonpayable",
inputs: [{ name: "_greeting", type: "string" }],
outputs: [],
},
] as const;
function ContractInteraction({
contractAddress,
}: {
contractAddress: `0x${string}`;
}) {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const {
writeContractAsync,
isPending,
data: hash,
} = useParaViemWriteContract(viemClient);
return (
writeContract({
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "setGreeting",
args: ["Hello from Para!"],
})
}
disabled={isPending}
>
{isPending ? "Updating..." : "Update Greeting"}
{hash &&
Transaction: {hash}
}
);
}
```
## Execute Raw Transactions
```typescript
import { useSendTransaction } from "@getpara/react-sdk/wagmi";
import { parseEther } from "viem";
function ExecuteTransaction() {
const { data: hash, isPending, sendTransaction } = useSendTransaction();
return (
sendTransaction({
to: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
data: "0x",
value: parseEther("0.01")
})}
disabled={isPending}
>
{isPending ? "Executing..." : "Execute Transaction"}
{hash &&
Transaction: {hash}
}
);
}
```
## Execute Contract Functions
```typescript
import { useWriteContract, useReadContract } from "@getpara/react-sdk/wagmi";
const CONTRACT_ABI = [
{
name: "setGreeting",
type: "function",
stateMutability: "nonpayable",
inputs: [{ name: "_greeting", type: "string" }],
outputs: []
},
{
name: "greet",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }]
}
] as const;
function ContractInteraction({
contractAddress
}: {
contractAddress: `0x${string}`
}) {
const { data: hash, isPending, writeContract } = useWriteContract();
const { data: greeting } = useReadContract({
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "greet"
});
return (
Current greeting: {greeting}
writeContract({
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "setGreeting",
args: ["Hello from Para!"]
})}
disabled={isPending}
>
{isPending ? "Updating..." : "Update Greeting"}
{hash &&
Transaction: {hash}
}
);
}
```
## Next Steps
# Fund Testnet Wallet
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/fund-testnet-wallet
Request testnet tokens directly through Para without relying on external faucets. The `useRequestFaucet` hook handles the request and returns the transaction details once tokens are sent.
Use this guide before running Sepolia examples that send ETH, transfer ERC-20 tokens, or write to contracts. The faucet sends testnet ETH to a Para EVM wallet so your first integration transactions have gas.
## Fund the Active Wallet
The simplest usage funds whichever wallet is currently active. This works well right after wallet creation:
```tsx
import { useRequestFaucet, useCreateWallet } from "@getpara/react-sdk";
function CreateAndFund() {
const { createWalletAsync } = useCreateWallet();
const { requestFaucetAsync, isPending } = useRequestFaucet();
const [txHash, setTxHash] = useState("");
const handleCreateAndFund = async () => {
await createWalletAsync({ type: "EVM" });
const result = await requestFaucetAsync();
setTxHash(result.transactionHash);
};
return (
{isPending ? "Funding..." : "Create & Fund Wallet"}
{txHash &&
Transaction: {txHash}
}
);
}
```
Calling `requestFaucetAsync()` without a `walletId` requires an active wallet. If no active wallet is set and no `walletId` is passed, the hook throws an error.
## Fund a Specific Wallet
Pass an explicit `walletId` to target a particular wallet:
```tsx
import { useRequestFaucet } from "@getpara/react-sdk";
function FundWallet({ walletId }: { walletId: string }) {
const { requestFaucetAsync, isPending, data } = useRequestFaucet();
return (
requestFaucetAsync({ walletId })}
disabled={isPending}
>
{isPending ? "Requesting..." : "Get Testnet ETH"}
{data && (
Sent {data.amount} ETH to {data.address}
)}
);
}
```
## Direct SDK Method
For framework-agnostic Web SDK or Server SDK integrations, call `requestFaucet` after you have the Para wallet ID:
```ts
const result = await para.requestFaucet({
walletId,
chain: "ETHEREUM_SEPOLIA",
});
console.log(result.transactionHash);
```
Omit `chain` to use the default Ethereum Sepolia faucet. Wait for the returned `transactionHash` to confirm before assuming the funds are spendable.
With `@getpara/rest-sdk`, call `client.requestFaucet({ walletId, chain: "ETHEREUM_SEPOLIA" })` with the same request body.
## Supported Chains
| Chain | Identifier | Token |
| ------------------ | ------------------- | ----- |
| Ethereum Sepolia | `ETHEREUM_SEPOLIA` | ETH |
The faucet is rate limited to 10 requests per API key per day. Each wallet has a 24-hour cooldown between faucet requests.
## Error Handling
The hook surfaces errors through the standard `error` field on the mutation result. Common error scenarios:
| Status | Cause |
| ------ | ------------------------------------------------------------------------------- |
| `400` | Invalid request (missing walletId, unsupported chain, or no address) |
| `403` | Missing or invalid API key |
| `404` | Wallet not found (also returned when the wallet belongs to a different API key) |
| `429` | Rate limit exceeded or wallet cooldown active |
| `500` | Faucet transaction failed |
A 429 response includes a `Retry-After` header indicating when the next request will be accepted.
# Get Transaction Receipt
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/get-transaction-receipt
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
After sending a transaction, you need to monitor its status and retrieve the receipt to confirm its execution. This guide demonstrates how to get transaction receipts using , , and .
## Prerequisites
You need Web3 libraries configured with Para authentication.
## Get Transaction Receipt
```typescript
import { ethers } from "ethers";
async function getTransactionReceipt(
provider: ethers.Provider,
txHash: string
) {
const receipt = await provider.getTransactionReceipt(txHash);
console.log("Transaction Receipt:", receipt);
return receipt;
}
async function waitForTransaction(provider: ethers.Provider, txHash: string) {
const receipt = await provider.waitForTransaction(txHash);
console.log("Transaction Confirmed:", receipt);
return receipt;
}
```
```typescript
async function getTransactionReceipt(
publicClient: any,
txHash: `0x${string}`
) {
const receipt = await publicClient.getTransactionReceipt({
hash: txHash,
});
console.log("Transaction Receipt:", receipt);
return receipt;
}
async function waitForTransactionReceipt(
publicClient: any,
txHash: `0x${string}`
) {
const receipt = await publicClient.waitForTransactionReceipt({
hash: txHash,
});
console.log("Transaction Confirmed:", receipt);
return receipt;
}
```
```typescript
import { useTransactionReceipt, useWaitForTransactionReceipt } from "@getpara/react-sdk/wagmi";
function TransactionReceipt({ txHash }: { txHash: `0x${string}` }) {
const { data: receipt, isLoading } = useTransactionReceipt({
hash: txHash,
});
const { data: confirmedReceipt, isLoading: isConfirming } = useWaitForTransactionReceipt({
hash: txHash,
});
if (isLoading) return Loading receipt...
;
return (
Transaction Receipt
{JSON.stringify(receipt, null, 2)}
{isConfirming &&
Waiting for confirmation...
}
{confirmedReceipt && (
Confirmed Receipt
{JSON.stringify(confirmedReceipt, null, 2)}
)}
);
}
```
# Interact with Contracts
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/interact-with-contracts
import { Card } from '/snippets/v3/components/ui/card.mdx';
Read data from smart contracts and execute write operations using Para's wallet infrastructure.
## Prerequisites
You need Web3 libraries configured with Para authentication.
Sepolia examples need testnet ETH before writing contracts. Get `requestFaucetAsync` from `useRequestFaucet()` and call `requestFaucetAsync({ chain: "ETHEREUM_SEPOLIA" })` after the user has an EVM wallet. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react/guides/web3-operations/evm/fund-testnet-wallet) for the full example.
## Read Contract Data
```typescript
import { ethers } from "ethers";
const CONTRACT_ABI = [
"function balanceOf(address owner) view returns (uint256)",
"function totalSupply() view returns (uint256)",
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)"
];
async function readContractData(
provider: ethers.Provider,
contractAddress: string,
userAddress: string
) {
const contract = new ethers.Contract(
contractAddress,
CONTRACT_ABI,
provider
);
const [balance, totalSupply, name, symbol, decimals] = await Promise.all([
contract.balanceOf(userAddress),
contract.totalSupply(),
contract.name(),
contract.symbol(),
contract.decimals()
]);
return {
balance: ethers.formatUnits(balance, decimals),
totalSupply: ethers.formatUnits(totalSupply, decimals),
name,
symbol,
decimals
};
}
```
## Write Contract Data
```typescript
const STAKING_ABI = [
"function stake(uint256 amount) payable",
"function unstake(uint256 amount)",
"function getStakedBalance(address user) view returns (uint256)",
"event Staked(address indexed user, uint256 amount)",
"event Unstaked(address indexed user, uint256 amount)"
];
async function stakeTokens(
signer: any,
contractAddress: string,
amount: string,
decimals: number
) {
const contract = new ethers.Contract(contractAddress, STAKING_ABI, signer);
const parsedAmount = ethers.parseUnits(amount, decimals);
const tx = await contract.stake(parsedAmount);
await tx.wait();
const newBalance = await contract.getStakedBalance(await signer.getAddress());
return {
hash: tx.hash,
stakedAmount: ethers.formatUnits(newBalance, decimals)
};
}
```
## Read Contract Data
Read operations don't need mutation helpers -- use the Viem `publicClient` directly:
```typescript
import { createPublicClient, http, formatUnits } from "viem";
import { sepolia } from "viem/chains";
const CONTRACT_ABI = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "owner", type: "address" }],
outputs: [{ type: "uint256" }],
},
{
name: "totalSupply",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "uint256" }],
},
{
name: "name",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }],
},
{
name: "symbol",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }],
},
{
name: "decimals",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "uint8" }],
},
] as const;
const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
});
async function readContractData(
contractAddress: `0x${string}`,
userAddress: `0x${string}`
) {
const results = await publicClient.multicall({
contracts: [
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "balanceOf",
args: [userAddress],
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "totalSupply",
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "name",
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "symbol",
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "decimals",
},
],
});
const [balance, totalSupply, name, symbol, decimals] = results.map(
(r) => r.result
);
return {
balance: formatUnits(balance, decimals),
totalSupply: formatUnits(totalSupply, decimals),
name,
symbol,
decimals,
};
}
```
## Write Contract Data
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-sdk";
import { parseUnits, http } from "viem";
import { sepolia } from "viem/chains";
const STAKING_ABI = [
{
name: "stake",
type: "function",
stateMutability: "payable",
inputs: [{ name: "amount", type: "uint256" }],
outputs: [],
},
] as const;
function StakeTokens({
contractAddress,
decimals = 18,
}: {
contractAddress: `0x${string}`;
decimals?: number;
}) {
const amount = "100";
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const {
writeContractAsync,
isPending,
data: hash,
} = useParaViemWriteContract(viemClient);
return (
writeContract({
address: contractAddress,
abi: STAKING_ABI,
functionName: "stake",
args: [parseUnits(amount, decimals)],
})
}
disabled={isPending}
>
{isPending ? "Staking..." : `Stake ${amount} Tokens`}
{hash &&
Transaction: {hash}
}
);
}
```
## Read Contract Data
```typescript
import { useReadContracts } from "@getpara/react-sdk/wagmi";
import { formatUnits } from "viem";
const CONTRACT_ABI = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "owner", type: "address" }],
outputs: [{ type: "uint256" }]
},
{
name: "totalSupply",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "uint256" }]
},
{
name: "name",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }]
},
{
name: "symbol",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }]
},
{
name: "decimals",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "uint8" }]
}
] as const;
function ContractData({
contractAddress,
userAddress
}: {
contractAddress: `0x${string}`;
userAddress: `0x${string}`;
}) {
const { data, isPending } = useReadContracts({
contracts: [
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "balanceOf",
args: [userAddress]
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "totalSupply"
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "name"
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "symbol"
},
{
address: contractAddress,
abi: CONTRACT_ABI,
functionName: "decimals"
}
]
});
if (isPending) return Loading...
;
const [balance, totalSupply, name, symbol, decimals] =
data?.map(d => d.result) || [];
return (
Token: {name} ({symbol})
Balance: {formatUnits(balance || 0n, decimals || 18)}
Total Supply: {formatUnits(totalSupply || 0n, decimals || 18)}
);
}
```
## Write Contract Data
```typescript
import { useWriteContract, useWaitForTransactionReceipt, useReadContract } from "@getpara/react-sdk/wagmi";
import { parseUnits, formatUnits } from "viem";
const STAKING_ABI = [
{
name: "stake",
type: "function",
stateMutability: "payable",
inputs: [{ name: "amount", type: "uint256" }],
outputs: []
},
{
name: "getStakedBalance",
type: "function",
stateMutability: "view",
inputs: [{ name: "user", type: "address" }],
outputs: [{ type: "uint256" }]
}
] as const;
function StakeTokens({
contractAddress,
userAddress,
decimals = 18
}: {
contractAddress: `0x${string}`;
userAddress: `0x${string}`;
decimals?: number;
}) {
const amount = "100";
const { data: hash, isPending, writeContract } = useWriteContract();
const { isPending: isConfirming } = useWaitForTransactionReceipt({ hash });
const { data: stakedBalance } = useReadContract({
address: contractAddress,
abi: STAKING_ABI,
functionName: "getStakedBalance",
args: [userAddress]
});
return (
Staked: {formatUnits(stakedBalance || 0n, decimals)}
writeContract({
address: contractAddress,
abi: STAKING_ABI,
functionName: "stake",
args: [parseUnits(amount, decimals)]
})}
disabled={isPending || isConfirming}
>
{isPending ? "Staking..." : `Stake ${amount} Tokens`}
{hash &&
Transaction: {hash}
}
);
}
```
## Next Steps
# Manage Token Allowances
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/manage-allowances
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Manage ERC-20 token allowances to allow smart contracts to spend tokens on behalf of a user. This guide covers checking and setting allowances using , , and .
## Prerequisites
You need Web3 libraries configured with Para authentication.
## Manage Token Allowances
```typescript
import { ethers } from "ethers";
const ERC20_ABI = [
"function allowance(address owner, address spender) view returns (uint256)",
"function approve(address spender, uint256 amount) returns (bool)",
];
async function checkAllowance(
provider: ethers.Provider,
tokenAddress: string,
owner: string,
spender: string
) {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
const allowance = await contract.allowance(owner, spender);
return allowance;
}
async function approveToken(
signer: ethers.Signer,
tokenAddress: string,
spender: string,
amount: string
) {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
const tx = await contract.approve(spender, ethers.parseEther(amount));
await tx.wait();
return tx;
}
```
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-sdk";
import { parseEther, http } from "viem";
import { sepolia } from "viem/chains";
const ERC20_ABI = [
{
name: "approve",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ type: "bool" }],
},
] as const;
function TokenAllowance({
tokenAddress,
spender,
}: {
tokenAddress: `0x${string}`;
spender: `0x${string}`;
}) {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const {
writeContractAsync,
isPending,
data: hash,
} = useParaViemWriteContract(viemClient);
return (
writeContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "approve",
args: [spender, parseEther("100")],
})
}
>
{isPending ? "Approving..." : "Approve 100 Tokens"}
{hash &&
Transaction: {hash}
}
);
}
```
```typescript
import { useReadContract, useWriteContract } from "@getpara/react-sdk/wagmi";
import { parseEther } from "viem";
const ERC20_ABI = [
{
name: "allowance",
type: "function",
stateMutability: "view",
inputs: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
],
outputs: [{ type: "uint256" }],
},
{
name: "approve",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ type: "bool" }],
},
] as const;
function TokenAllowance(
tokenAddress: `0x${string}`,
owner: `0x${string}`,
spender: `0x${string}`
) {
const { data: allowance } = useReadContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "allowance",
args: [owner, spender],
});
const { writeContract } = useWriteContract();
return (
Allowance: {allowance?.toString()}
writeContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "approve",
args: [spender, parseEther("100")],
})
}
>
Approve 100 Tokens
);
}
```
# Query Wallet Balances
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/query-balances
import { Card } from '/snippets/v3/components/ui/card.mdx';
Query ETH and ERC-20 token balances for Para wallets across different networks.
## Prerequisites
You need Web3 libraries configured with Para authentication.
## Query ETH Balance
```typescript
import { ethers } from "ethers";
import { ParaEthersSigner } from "@getpara/ethers-v6-integration";
async function getETHBalance(signer: ParaEthersSigner, provider: ethers.Provider) {
const address = await signer.getAddress();
const balance = await provider.getBalance(address);
const formattedBalance = ethers.formatEther(balance);
console.log(`ETH Balance: ${formattedBalance} ETH`);
return {
wei: balance.toString(),
ether: formattedBalance
};
}
```
## Query ERC-20 Token Balance
```typescript
import { ethers } from "ethers";
const ERC20_ABI = [
"function balanceOf(address owner) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)"
];
async function getTokenBalance(
signer: any,
tokenAddress: string,
provider: ethers.Provider
) {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
const address = await signer.getAddress();
const [balance, decimals, symbol] = await Promise.all([
contract.balanceOf(address),
contract.decimals(),
contract.symbol()
]);
const formattedBalance = ethers.formatUnits(balance, decimals);
console.log(`${symbol} Balance: ${formattedBalance}`);
return {
raw: balance.toString(),
formatted: formattedBalance,
symbol,
decimals
};
}
```
## Query ETH Balance
```typescript
import { formatEther } from "viem";
import { useParaViemAccount } from "@getpara/react-sdk";
function useETHBalance(publicClient: any) {
const { viemAccount } = useParaViemAccount();
const getETHBalance = async () => {
if (!viemAccount) return;
const balance = await publicClient.getBalance({
address: viemAccount.address
});
const formattedBalance = formatEther(balance);
console.log(`ETH Balance: ${formattedBalance} ETH`);
return {
wei: balance.toString(),
ether: formattedBalance
};
};
return { getETHBalance };
}
```
## Query ERC-20 Token Balance
```typescript
import { formatUnits } from "viem";
const ERC20_ABI = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "owner", type: "address" }],
outputs: [{ type: "uint256" }]
},
{
name: "decimals",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "uint8" }]
},
{
name: "symbol",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }]
}
] as const;
async function getTokenBalance(
publicClient: any,
account: any,
tokenAddress: `0x${string}`
) {
const [balance, decimals, symbol] = await Promise.all([
publicClient.readContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "balanceOf",
args: [account.address]
}),
publicClient.readContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "decimals"
}),
publicClient.readContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "symbol"
})
]);
const formattedBalance = formatUnits(balance, decimals);
console.log(`${symbol} Balance: ${formattedBalance}`);
return {
raw: balance.toString(),
formatted: formattedBalance,
symbol,
decimals
};
}
```
## Query ETH Balance
```typescript
import { useBalance, useAccount } from "@getpara/react-sdk/wagmi";
function ETHBalance() {
const { address } = useAccount();
const { data, isError, isLoading } = useBalance({
address: address
});
if (isLoading) return Loading balance...
;
if (isError) return Error fetching balance
;
return (
ETH Balance: {data?.formatted} {data?.symbol}
Wei: {data?.value.toString()}
);
}
```
## Query ERC-20 Token Balance
```typescript
import { useAccount, useReadContracts } from "@getpara/react-sdk/wagmi";
const ERC20_ABI = [
{
name: "balanceOf",
type: "function",
stateMutability: "view",
inputs: [{ name: "owner", type: "address" }],
outputs: [{ type: "uint256" }]
},
{
name: "decimals",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "uint8" }]
},
{
name: "symbol",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ type: "string" }]
}
] as const;
function TokenBalance({ tokenAddress }: { tokenAddress: `0x${string}` }) {
const { address } = useAccount();
const { data, isLoading } = useReadContracts({
contracts: [
{
address: tokenAddress,
abi: ERC20_ABI,
functionName: "balanceOf",
args: [address!]
},
{
address: tokenAddress,
abi: ERC20_ABI,
functionName: "decimals"
},
{
address: tokenAddress,
abi: ERC20_ABI,
functionName: "symbol"
}
]
});
if (isLoading) return Loading token balance...
;
const balance = data?.[0]?.result;
const decimals = data?.[1]?.result;
const symbol = data?.[2]?.result;
if (!balance || !decimals) return No balance data
;
const formatted = (Number(balance) / 10 ** decimals).toFixed(4);
return (
{symbol} Balance: {formatted}
);
}
```
## Next Steps
# Send Tokens
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/send-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
Send ETH and ERC-20 token transfers securely using Para's wallet infrastructure.
## Prerequisites
You need Web3 libraries configured with Para authentication.
Sepolia examples need testnet ETH before sending transactions. Get `requestFaucetAsync` from `useRequestFaucet()` and call `requestFaucetAsync({ chain: "ETHEREUM_SEPOLIA" })` after the user has an EVM wallet. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react/guides/web3-operations/evm/fund-testnet-wallet) for the full example.
## Send ETH
```tsx
import { useParaEthersSigner, useParaEthersSendTransaction } from "@getpara/react-sdk";
import { ethers, JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SendETH() {
const { ethersSigner } = useParaEthersSigner({ provider });
const {
sendTransactionAsync,
isPending,
data: receipt,
} = useParaEthersSendTransaction(ethersSigner);
return (
sendTransaction({
to: "0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
value: ethers.parseEther("0.01"),
})
}
disabled={isPending}
>
{isPending ? "Sending..." : "Send 0.01 ETH"}
{receipt &&
Transaction Hash: {receipt.hash}
}
);
}
```
## Send ERC-20 Tokens
```tsx
import { useParaEthersSigner, useParaEthersWriteContract } from "@getpara/react-sdk";
import { ethers, JsonRpcProvider } from "ethers";
const ERC20_ABI = [
"function transfer(address to, uint256 amount) returns (bool)"
];
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SendToken({
tokenAddress,
decimals,
}: {
tokenAddress: string;
decimals: number;
}) {
const { ethersSigner } = useParaEthersSigner({ provider });
const {
writeContractAsync,
isPending,
data: receipt,
} = useParaEthersWriteContract(ethersSigner);
return (
writeContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "transfer",
args: [
"0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
ethers.parseUnits("10", decimals),
],
})
}
disabled={isPending}
>
{isPending ? "Sending..." : "Send 10 Tokens"}
);
}
```
## Send ETH
```tsx
import { useParaViemClient, useParaViemSendTransaction } from "@getpara/react-sdk";
import { parseEther, http } from "viem";
import { sepolia } from "viem/chains";
function SendETH() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const {
sendTransactionAsync,
isPending,
data: hash,
} = useParaViemSendTransaction(viemClient);
return (
sendTransaction({
to: "0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
value: parseEther("0.01"),
})
}
disabled={isPending}
>
{isPending ? "Sending..." : "Send 0.01 ETH"}
{hash &&
Transaction Hash: {hash}
}
);
}
```
## Send ERC-20 Tokens
```tsx
import { useParaViemClient, useParaViemWriteContract } from "@getpara/react-sdk";
import { parseUnits, http } from "viem";
import { sepolia } from "viem/chains";
const ERC20_ABI = [
{
name: "transfer",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ type: "bool" }],
},
] as const;
function SendToken({
tokenAddress,
decimals,
}: {
tokenAddress: `0x${string}`;
decimals: number;
}) {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const {
writeContractAsync,
isPending,
data: hash,
} = useParaViemWriteContract(viemClient);
return (
writeContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "transfer",
args: [
"0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
parseUnits("10", decimals),
],
})
}
disabled={isPending}
>
{isPending ? "Sending..." : "Send 10 Tokens"}
);
}
```
## Send ETH
```typescript
import { useSendTransaction, useWaitForTransactionReceipt } from "@getpara/react-sdk/wagmi";
import { parseEther } from "viem";
function SendETH() {
const { data: hash, isPending, sendTransaction } = useSendTransaction();
const { data: receipt, isPending: isConfirming } = useWaitForTransactionReceipt({
hash
});
return (
sendTransaction({
to: "0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
value: parseEther("0.01")
})}
disabled={isPending || isConfirming}
>
{isPending ? "Sending..." : "Send 0.01 ETH"}
{hash &&
Transaction Hash: {hash}
}
{receipt &&
Confirmed in block: {receipt.blockNumber}
}
);
}
```
## Send ERC-20 Tokens
```typescript
import { useWriteContract, useWaitForTransactionReceipt } from "@getpara/react-sdk/wagmi";
import { parseUnits } from "viem";
const ERC20_ABI = [
{
name: "transfer",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "to", type: "address" },
{ name: "amount", type: "uint256" }
],
outputs: [{ type: "bool" }]
}
] as const;
function SendToken({
tokenAddress,
decimals
}: {
tokenAddress: `0x${string}`;
decimals: number;
}) {
const { data: hash, isPending, writeContract } = useWriteContract();
const { isPending: isConfirming } = useWaitForTransactionReceipt({ hash });
return (
writeContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: "transfer",
args: [
"0x742d35Cc6634C0532925a3b844Bc9e7595f7BBB2",
parseUnits("10", decimals)
]
})}
disabled={isPending || isConfirming}
>
{isPending ? "Sending..." : "Send 10 Tokens"}
);
}
```
## Next Steps
# Setup Web3 Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/setup-libraries
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Learn how to set up popular Web3 libraries with Para. Choose your library below.
## Prerequisites
Before setting up Web3 libraries, you need an authenticated Para session.
Sepolia examples need testnet ETH before sending transactions or writing contracts. Get `requestFaucetAsync` from `useRequestFaucet()` and call `requestFaucetAsync({ chain: "ETHEREUM_SEPOLIA" })` after the user has an EVM wallet. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react/guides/web3-operations/evm/fund-testnet-wallet) for the full example.
## Install
```bash
npm install @getpara/react-sdk ethers
```
`@getpara/react-sdk` bundles `@getpara/ethers-v6-integration` — no separate integration package install needed.
## Usage
Use the hook to create an ethers signer for your user's Para embedded wallet or external wallet. For convenience, the and hooks wrap common signer methods in a React Query mutation.
```tsx
import { useParaEthersSigner, useParaEthersSignMessage } from "@getpara/react-sdk";
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignWithEthers() {
const { ethersSigner, isLoading } = useParaEthersSigner({ provider });
const { signMessageAsync, isPending } = useParaEthersSignMessage(ethersSigner);
const handleSign = async () => {
const signature = await signMessageAsync("Hello from Para!");
console.log("Signature:", signature);
};
if (isLoading) return Loading...
;
return (
{isPending ? "Signing..." : "Sign Message"}
);
}
```
### Wallet Resolution
When no `address` or `walletId` is passed, the hook resolves the wallet in this order:
1. **Selected wallet** — if the user selected an EVM wallet in the UI. If there is only one EVM wallet in the session, it is already selected by default
2. **First EVM wallet** — the first available EVM wallet on the account
To target a specific wallet, pass `address` or `walletId`:
```tsx
const { ethersSigner } = useParaEthersSigner({
provider,
address: "0x1234...", // or walletId: "uuid-..."
});
```
Use `createParaEthersSigner` to create a signer directly.
```typescript
import { createParaEthersSigner } from "@getpara/ethers-v6-integration";
import { ethers } from "ethers";
import Para from "@getpara/web-sdk";
const para = new Para("YOUR_API_KEY");
const provider = new ethers.JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
// Authenticate first...
const signer = createParaEthersSigner({ para, provider });
const signature = await signer.signMessage("Hello from Para!");
```
### Wallet Resolution
When no `address` or `walletId` is passed, the factory picks the first available EVM wallet.
To target a specific wallet:
```typescript
const signer = createParaEthersSigner({
para,
provider,
address: "0x1234...", // looks up by address
// or walletId: "uuid-...", // looks up by ID
});
```
## Install
```bash
npm install @getpara/react-sdk viem
```
`@getpara/react-sdk` bundles `@getpara/viem-v2-integration` — no separate integration package install needed.
## Usage
Use the hook to create a viem `WalletClient` for your user's Para embedded wallet or external wallet. For convenience, the , , , and hooks wrap common client methods in a React Query mutation.
```tsx
import { useParaViemClient, useParaViemSignMessage } from "@getpara/react-sdk";
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
const publicClient = createPublicClient({
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
});
function SignWithViem() {
const { viemClient, isLoading } = useParaViemClient({
walletClientConfig: {
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
},
});
const { signMessageAsync, isPending } = useParaViemSignMessage(viemClient);
const handleSign = async () => {
const signature = await signMessageAsync({ message: "Hello from Para!" });
console.log("Signature:", signature);
};
if (isLoading) return Loading...
;
return (
{isPending ? "Signing..." : "Sign Message"}
);
}
```
### Wallet Resolution
When no `address` or `walletId` is passed, the hook resolves the wallet in this order:
1. **Selected wallet** — if the user selected an EVM wallet in the UI. If there is only one EVM wallet in the session, it is already selected by default
2. **First EVM wallet** — the first available EVM wallet on the account
To target a specific wallet:
```tsx
const { viemClient } = useParaViemClient({
address: "0x1234...", // or walletId: "uuid-..."
walletClientConfig: { chain: sepolia, transport: http() },
});
```
Use `createParaViemAccount` and `createParaViemClient` to create a wallet client directly.
```typescript
import { createParaViemAccount, createParaViemClient } from "@getpara/viem-v2-integration";
import { createPublicClient, http } from "viem";
import { sepolia } from "viem/chains";
import Para from "@getpara/web-sdk";
const para = new Para("YOUR_API_KEY");
// Authenticate first...
const account = createParaViemAccount({ para });
const walletClient = createParaViemClient({ para, walletClientConfig: {
account,
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
}});
const signature = await walletClient.signMessage({ message: "Hello from Para!" });
```
### Wallet Resolution
When no `address` or `walletId` is passed, the factory picks the first available EVM wallet.
To target a specific wallet:
```typescript
const account = createParaViemAccount({
para,
address: "0x1234...", // looks up by address
// or walletId: "uuid-...", // looks up by ID
});
```
## Install
```bash
npm install @getpara/react-sdk
```
**ParaProvider already includes Wagmi under the hood.** No additional packages needed. If you've set up ParaProvider as shown in the , you can use Wagmi hooks directly.
## Usage
```tsx
import { useAccount, useBalance, useSendTransaction, useSignMessage } from "@getpara/react-sdk/wagmi";
function MyComponent() {
const { address, isConnected } = useAccount();
const { data: balance } = useBalance({ address });
const { signMessageAsync } = useSignMessage();
const { sendTransaction } = useSendTransaction();
if (!isConnected) return Not connected
;
return (
Address: {address}
Balance: {balance?.formatted}
signMessageAsync({ message: "Hello" })}>
Sign Message
);
}
```
### Wallet Resolution
Wagmi uses its own connector system. When using ParaProvider, the Para connector is the active connector and signing goes through Para's MPC. External wallets connected via wagmi (e.g. MetaMask) use their own signing providers.
For advanced users integrating Para with existing Wagmi setups that manage external wallets, see the .
## Next Steps
# Sign Messages
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/sign-messages
import { Card } from '/snippets/v3/components/ui/card.mdx';
Sign plain text messages using Para's secure signing infrastructure.
## Prerequisites
You need Web3 libraries configured with Para authentication.
## Sign Personal Messages
```tsx
import { useParaEthersSigner, useParaEthersSignMessage } from "@getpara/react-sdk";
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignMessage() {
const { ethersSigner } = useParaEthersSigner({ provider });
const {
signMessageAsync,
isPending,
data: signature,
} = useParaEthersSignMessage(ethersSigner);
return (
signMessage("Hello from Para!")}
>
{isPending ? "Signing..." : "Sign Message"}
{signature &&
Signature: {signature.slice(0, 20)}...
}
);
}
```
## Sign Structured Messages
```tsx
import { useParaEthersSigner, useParaEthersSignMessage } from "@getpara/react-sdk";
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignStructuredMessage() {
const { ethersSigner } = useParaEthersSigner({ provider });
const {
signMessageAsync,
isPending,
data: signature,
} = useParaEthersSignMessage(ethersSigner);
const handleSign = () => {
const data = {
action: "authenticate",
timestamp: Date.now(),
nonce: Math.random().toString(36).substring(7),
};
signMessage(JSON.stringify(data, null, 2));
};
return (
{isPending ? "Signing..." : "Sign Structured Data"}
{signature &&
Signature: {signature.slice(0, 30)}...
}
);
}
```
## Sign Personal Messages
```tsx
import { useParaViemClient, useParaViemSignMessage } from "@getpara/react-sdk";
import { http } from "viem";
import { sepolia } from "viem/chains";
function SignMessage() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const {
signMessageAsync,
isPending,
data: signature,
} = useParaViemSignMessage(viemClient);
return (
signMessage({ message: "Hello from Para!" })}
>
{isPending ? "Signing..." : "Sign Message"}
{signature &&
Signature: {signature.slice(0, 20)}...
}
);
}
```
## Sign Structured Messages
```tsx
import { useParaViemClient, useParaViemSignMessage } from "@getpara/react-sdk";
import { http } from "viem";
import { sepolia } from "viem/chains";
function SignStructuredMessage() {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const {
signMessageAsync,
isPending,
data: signature,
} = useParaViemSignMessage(viemClient);
const handleSign = () => {
const data = {
action: "authenticate",
timestamp: Date.now(),
nonce: Math.random().toString(36).substring(7),
};
signMessage({ message: JSON.stringify(data, null, 2) });
};
return (
{isPending ? "Signing..." : "Sign Structured Data"}
{signature &&
Signature: {signature.slice(0, 30)}...
}
);
}
```
## Sign Personal Messages
```typescript
import { useSignMessage } from "@getpara/react-sdk/wagmi";
import { verifyMessage } from "viem";
function SignMessage() {
const { data: signature, isPending, signMessage } = useSignMessage();
return (
signMessage({ message: "Hello from Para!" })}
disabled={isPending}
>
{isPending ? "Signing..." : "Sign Message"}
{signature &&
Signature: {signature.slice(0, 20)}...
}
);
}
```
## Sign Structured Messages
```typescript
import { useSignMessage } from "@getpara/react-sdk/wagmi";
function SignStructuredMessage() {
const { signMessage, data: signature, isPending } = useSignMessage();
const handleSign = () => {
const data = {
action: "authenticate",
timestamp: Date.now(),
nonce: Math.random().toString(36).substring(7)
};
signMessage({ message: JSON.stringify(data, null, 2) });
};
return (
{isPending ? "Signing..." : "Sign Structured Data"}
{signature &&
Signature: {signature.slice(0, 30)}...
}
);
}
```
## Next Steps
# Sign Typed Data (EIP-712)
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/sign-typed-data
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Sign structured data according to the EIP-712 standard, which provides a more readable and secure signing experience for users. This guide covers signing typed data with , , and .
## Prerequisites
You need Web3 libraries configured with Para authentication.
## Sign Typed Data
```tsx
import { useParaEthersSigner, useParaEthersSignTypedData } from "@getpara/react-sdk";
import { JsonRpcProvider } from "ethers";
const provider = new JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
function SignTypedData({
domain,
types,
value,
}: {
domain: any;
types: any;
value: any;
}) {
const { ethersSigner } = useParaEthersSigner({ provider });
const {
signTypedDataAsync,
isPending,
data: signature,
} = useParaEthersSignTypedData(ethersSigner);
return (
signTypedData({ domain, types, value })}
>
{isPending ? "Signing..." : "Sign Typed Data"}
{signature &&
Signature: {signature}
}
);
}
```
```tsx
import { useParaViemClient, useParaViemSignTypedData } from "@getpara/react-sdk";
import { http } from "viem";
import { sepolia } from "viem/chains";
function SignTypedData({
domain,
types,
primaryType,
message,
}: {
domain: any;
types: any;
primaryType: string;
message: any;
}) {
const { viemClient } = useParaViemClient({
walletClientConfig: { chain: sepolia, transport: http() },
});
const {
signTypedDataAsync,
isPending,
data: signature,
} = useParaViemSignTypedData(viemClient);
return (
signTypedData({ domain, types, primaryType, message })}
>
{isPending ? "Signing..." : "Sign Typed Data"}
{signature &&
Signature: {signature}
}
);
}
```
```typescript
import { useSignTypedData } from "@getpara/react-sdk/wagmi";
function SignTypedData(
domain: any,
types: any,
primaryType: string,
message: any
) {
const { data: signature, signTypedData } = useSignTypedData();
return (
signTypedData({
domain,
types,
primaryType,
message,
})
}
>
Sign Typed Data
{signature &&
Signature: {signature}
}
);
}
```
# Verify Signatures with EVM Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/verify-signatures
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Verify the authenticity of signed messages and typed data to ensure they originated from the expected address.
## Prerequisites
You need Web3 libraries configured with Para authentication.
## Verify Personal Signatures
```typescript
import { ethers } from "ethers";
async function verifyPersonalSignature(
message: string,
signature: string,
signerAddress: string
) {
const recoveredAddress = ethers.verifyMessage(message, signature);
const isValid = recoveredAddress.toLowerCase() === signerAddress.toLowerCase();
console.log("Signature valid:", isValid);
return isValid;
}
```
## Verify Typed Data Signatures (EIP-712)
```typescript
import { ethers } from "ethers";
async function verifyTypedDataSignature(
domain: any,
types: any,
value: any,
signature: string,
signerAddress: string
) {
const recoveredAddress = ethers.verifyTypedData(domain, types, value, signature);
const isValid = recoveredAddress.toLowerCase() === signerAddress.toLowerCase();
console.log("Signature valid:", isValid);
return isValid;
}
```
## Verify Personal Signatures
```typescript
import { verifyMessage } from "viem";
async function verifyPersonalSignature(
address: `0x${string}`,
message: string,
signature: `0x${string}`
) {
const isValid = await verifyMessage({
address,
message,
signature,
});
console.log("Signature valid:", isValid);
return isValid;
}
```
## Verify Typed Data Signatures (EIP-712)
```typescript
import { verifyTypedData } from "viem";
async function verifyTypedDataSignature(
address: `0x${string}`,
domain: any,
types: any,
primaryType: string,
message: any,
signature: `0x${string}`
) {
const isValid = await verifyTypedData({
address,
domain,
types,
primaryType,
message,
signature,
});
console.log("Signature valid:", isValid);
return isValid;
}
```
## Verify Personal Signatures
```typescript
import { useVerifyMessage } from "@getpara/react-sdk/wagmi";
function VerifyPersonalSignature({
address,
message,
signature,
}: {
address: `0x${string}`;
message: string;
signature: `0x${string}`;
}) {
const { data: isValid, isLoading } = useVerifyMessage({
address,
message,
signature,
});
if (isLoading) return Verifying signature...
;
return Signature valid: {isValid ? "Yes" : "No"}
;
}
```
## Verify Typed Data Signatures (EIP-712)
```typescript
import { useVerifyTypedData } from "@getpara/react-sdk/wagmi";
function VerifyTypedDataSignature({
address,
domain,
types,
primaryType,
message,
signature,
}: {
address: `0x${string}`;
domain: any;
types: any;
primaryType: string;
message: any;
signature: `0x${string}`;
}) {
const { data: isValid, isLoading } = useVerifyTypedData({
address,
domain,
types,
primaryType,
message,
signature,
});
if (isLoading) return Verifying typed data signature...
;
return Typed Data Signature valid: {isValid ? "Yes" : "No"}
;
}
```
## Next Steps
# Watch Contract Events
Source: https://docs.getpara.com/v3/react/guides/web3-operations/evm/watch-events
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Subscribe to and filter smart contract events to react to on-chain activity in real-time.
## Prerequisites
You need Web3 libraries configured with Para authentication.
## Watch Contract Events
```typescript
import { ethers } from "ethers";
const ERC20_ABI = [
"event Transfer(address indexed from, address indexed to, uint256 value)"
];
async function watchTransferEvents(
provider: ethers.Provider,
tokenAddress: string
) {
const contract = new ethers.Contract(
tokenAddress,
ERC20_ABI,
provider
);
contract.on("Transfer", (from, to, value, event) => {
console.log(`Transfer Event: ${from} -> ${to}, Value: ${ethers.formatUnits(value, 18)}`);
console.log("Event details:", event);
});
console.log(`Listening for Transfer events on ${tokenAddress}...`);
}
async function stopWatchingTransferEvents(
provider: ethers.Provider,
tokenAddress: string
) {
const contract = new ethers.Contract(
tokenAddress,
ERC20_ABI,
provider
);
contract.off("Transfer");
console.log(`Stopped listening for Transfer events on ${tokenAddress}.`);
}
```
```typescript
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
const ERC20_ABI = [
{
anonymous: false,
inputs: [
{ indexed: true, name: "from", type: "address" },
{ indexed: true, name: "to", type: "address" },
{ indexed: false, name: "value", type: "uint256" },
],
name: "Transfer",
type: "event",
},
] as const;
async function watchTransferEvents(
publicClient: any,
tokenAddress: `0x${string}`
) {
const unwatch = publicClient.watchContractEvent({
address: tokenAddress,
abi: ERC20_ABI,
eventName: "Transfer",
onLogs: (logs) => {
for (const log of logs) {
console.log(`Transfer Event: ${log.args.from} -> ${log.args.to}, Value: ${log.args.value}`);
console.log("Log details:", log);
}
},
});
console.log(`Listening for Transfer events on ${tokenAddress}...`);
return unwatch; // Return the unwatch function to stop listening later
}
```
```typescript
import { useWatchContractEvent } from "@getpara/react-sdk/wagmi";
const ERC20_ABI = [
{
anonymous: false,
inputs: [
{ indexed: true, name: "from", type: "address" },
{ indexed: true, name: "to", type: "address" },
{ indexed: false, name: "value", type: "uint256" },
],
name: "Transfer",
type: "event",
},
] as const;
function WatchTransferEvents({ tokenAddress }: { tokenAddress: `0x${string}` }) {
useWatchContractEvent({
address: tokenAddress,
abi: ERC20_ABI,
eventName: "Transfer",
onLogs: (logs) => {
for (const log of logs) {
console.log(`Transfer Event: ${log.args.from} -> ${log.args.to}, Value: ${log.args.value}`);
console.log("Log details:", log);
}
},
});
return Listening for Transfer events on {tokenAddress}...
;
}
```
## Next Steps
# Get Wallet Data
Source: https://docs.getpara.com/v3/react/guides/web3-operations/get-wallet-address
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import UseWallet from '/snippets/v3/definitions/hooks/useWallet.mdx';
import UseAccount from '/snippets/v3/definitions/hooks/useAccount.mdx';
import GetWalletsByType from '/snippets/v3/definitions/core/getWalletsByType.mdx';
import Wallet from '/snippets/v3/definitions/types/Wallet.mdx';
## Get Current Wallet
Use the `useWallet` hook to access the currently selected wallet in the `ParaModal`.
```tsx
import { useWallet } from '@getpara/react-sdk';
export default function CurrentWallet() {
const { data: wallet } = useWallet();
if (!wallet) return No wallet connected
;
return (
Address: {wallet.address}
Type: {wallet.scheme}
ID: {wallet.id}
);
}
```
## Get All Wallets
Use the `useAccount` hook to access all user wallets for both embedded and external types.
```tsx
import { useAccount } from '@getpara/react-sdk';
export default function AllWallets() {
const { embedded, external } = useAccount();
// Get all embedded wallets
const wallets = embedded.wallets; // Record
const walletList = Object.values(wallets);
return (
{walletList.map((wallet) => (
{wallet.scheme}: {wallet.address}
))}
);
}
```
## Filter Wallets by Type
Access wallets filtered by blockchain type using the Para client.
```tsx
import { useClient } from '@getpara/react-sdk';
export default function WalletsByType() {
// useClient hook to access Para client
const para = useClient();
// Get wallets by type
const evmWallets = para.getWalletsByType('EVM');
const solanaWallets = para.getWalletsByType('SOLANA');
const cosmosWallets = para.getWalletsByType('COSMOS');
const stellarWallets = para.getWalletsByType('STELLAR');
}
```
## Wallet Properties
Each wallet object for embedded wallets has the following properties:
## Next Steps
# Query Wallet Balance with Para
Source: https://docs.getpara.com/v3/react/guides/web3-operations/query-wallet-balance
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import UseWalletBalance from '/snippets/v3/definitions/hooks/useWalletBalance.mdx';
Para provides a simple React hook to query the native balance of a wallet. This is useful for checking available funds before performing transactions.
### useWalletBalance
### Basic Usage
```tsx
import { useWalletBalance } from '@getpara/react-sdk';
const { data: balance, isLoading, error } = useWalletBalance();
// Balance is returned as a string in wei (for EVM)
console.log(balance); // "1000000000000000000" (1 ETH in wei)
```
### Query Specific Wallet
```tsx
import { useWalletBalance } from '@getpara/react-sdk';
const { data: balance } = useWalletBalance({
walletId: 'wallet_123'
});
console.log(balance); // Balance in wei
```
### External Wallet with RPC
```tsx
import { useWalletBalance } from '@getpara/react-sdk';
const { data: balance } = useWalletBalance({
walletId: 'external_wallet_id',
rpcUrl: 'https://mainnet.infura.io/v3/YOUR_KEY'
});
console.log(balance); // Balance from external RPC
```
## Important Notes
- **EVM only**: Currently only supports EVM wallets. Returns `null` for COSMOS, SOLANA, and STELLAR wallets
- **Native balance only**: Does not support token balances
- **Wei format**: Returns balance as a string in wei (smallest unit)
- **No formatting**: Returns raw balance value without formatting or symbol information
## Next Steps
Work directly with popular blockchain libraries for a more comprehensive approach to wallet interactions.
# Sign Messages with Para
Source: https://docs.getpara.com/v3/react/guides/web3-operations/sign-with-para
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { LinkListCard } from '/snippets/v3/components/ui/link-list-card.mdx';
import UseSignMessage from '/snippets/v3/definitions/hooks/useSignMessage.mdx';
import SignWithParaWarning from '/snippets/v3/sign-with-para-warning.mdx';
The `signMessage` method is a **low-level API** that signs raw bytes directly without any modifications. This is useful for verifying your Para integration with a simple "Hello, Para!" test after initial setup and authentication.
### Message Signing
This method signs the exact bytes you provide - perfect for initial "hello world" testing:
```tsx SignMessageExample.tsx
import { useSignMessage, useWallet } from '@getpara/react-sdk';
export default function SignMessageExample() {
const { mutateAsync: signMessageAsync } = useSignMessage();
const { data: wallet } = useWallet();
const handleSign = async () => {
if (!wallet) return;
const message = "Hello, Para!";
// Encode message to base64
const messageBase64 = btoa(message);
const result = await signMessageAsync({
walletId: wallet.id,
messageBase64
});
console.log('Signature result:', result);
};
return (
Sign Message
);
}
```
## Next Steps
Now that you've successfully verified your Para setup with a simple message signature, it's time to move on to more advanced operations. Depending on your target blockchain, you can explore the following libraries that integrate seamlessly with Para for signing transactions, typed data, and more:
# Set Compute Units and Priority Fees
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/compute-units
import { Card } from '/snippets/v3/components/ui/card.mdx';
Optimize your Solana transactions by setting compute unit limits and priority fees. This helps ensure transactions succeed during network congestion and controls execution costs.
```typescript
import { useParaSolanaSigner, useParaSolanaSignAndSend } from '@getpara/react-sdk';
import { Address } from '@solana/addresses';
import {
createSolanaRpc,
pipe,
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
lamports,
} from '@solana/kit';
import { getTransferSolInstruction } from '@solana-program/system';
import {
getSetComputeUnitLimitInstruction,
getSetComputeUnitPriceInstruction,
} from '@solana-program/compute-budget';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function ComputeUnitsExample() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const { signAndSendAsync, isPending } = useParaSolanaSignAndSend(solanaSigner);
const sendWithPriorityFee = async () => {
if (!solanaSigner) return;
const recipient = "RECIPIENT_ADDRESS" as Address;
const response = await rpc.getLatestBlockhash().send();
const { blockhash, lastValidBlockHeight } = response.value;
const { value: recentFees } = await rpc.getRecentPrioritizationFees().send();
const avgFee = recentFees.reduce((sum, fee) => sum + fee.prioritizationFee, 0n) / BigInt(recentFees.length);
const priorityFee = (avgFee * 120n) / 100n;
const transaction = pipe(
createTransactionMessage({ version: "legacy" }),
tx => setTransactionMessageFeePayer(solanaSigner.address, tx),
tx => setTransactionMessageLifetimeUsingBlockhash({ blockhash, lastValidBlockHeight }, tx),
tx => appendTransactionMessageInstruction(getSetComputeUnitLimitInstruction({ units: 200000 }), tx),
tx => appendTransactionMessageInstruction(getSetComputeUnitPriceInstruction({ microLamports: priorityFee }), tx),
tx => appendTransactionMessageInstruction(
getTransferSolInstruction({
source: solanaSigner,
destination: recipient,
amount: lamports(100_000_000n),
}),
tx
)
);
const result = await signAndSendAsync({ transactions: [transaction] });
return result;
};
return Send with Priority Fee ;
}
```
```typescript
import { useParaSolana } from './hooks/useParaSolana';
import {
Transaction,
SystemProgram,
LAMPORTS_PER_SOL,
PublicKey,
ComputeBudgetProgram
} from '@solana/web3.js';
function ComputeUnitsExample() {
const { connection, signer } = useParaSolana();
const sendWithPriorityFee = async () => {
const recipient = new PublicKey("RECIPIENT_ADDRESS");
// Get recent prioritization fees
const recentFees = await connection.getRecentPrioritizationFees();
const avgFee = recentFees.reduce((sum, fee) => sum + fee.prioritizationFee, 0) / recentFees.length;
const priorityFee = Math.ceil(avgFee * 1.2); // 20% above average
const transaction = new Transaction()
.add(
// Set compute unit limit
ComputeBudgetProgram.setComputeUnitLimit({
units: 200000, // Adjust based on your transaction needs
})
)
.add(
// Set priority fee
ComputeBudgetProgram.setComputeUnitPrice({
microLamports: priorityFee,
})
)
.add(
// Your actual transaction instruction
SystemProgram.transfer({
fromPubkey: signer.sender,
toPubkey: recipient,
lamports: LAMPORTS_PER_SOL * 0.1,
})
);
try {
const signature = await signer.sendTransaction(transaction, {
skipPreflight: false,
maxRetries: 3,
});
console.log("Transaction sent with priority fee:", signature);
console.log("Priority fee used:", priorityFee, "microLamports");
const confirmation = await connection.confirmTransaction(signature, "confirmed");
console.log("Transaction confirmed:", confirmation);
} catch (error) {
console.error("Transaction failed:", error);
}
};
return Send with Priority Fee ;
}
```
Compute unit management works the same as @solana/kit — add compute budget instructions to your transaction:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstruction,
signAndSendTransactionMessageWithSigners } from "@solana/kit";
import { getSetComputeUnitLimitInstruction, getSetComputeUnitPriceInstruction } from "@solana-program/compute-budget";
import { getInitializeInstruction } from "./generated/instructions";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
function WithComputeUnits() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const execute = async () => {
if (!solanaSigner) return;
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const tx = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(solanaSigner, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstruction(getSetComputeUnitLimitInstruction({ units: 400_000 }), tx),
tx => appendTransactionMessageInstruction(getSetComputeUnitPriceInstruction({ microLamports: 1000n }), tx),
tx => appendTransactionMessageInstruction(getInitializeInstruction({ user: solanaSigner }), tx),
);
const signature = await signAndSendTransactionMessageWithSigners(tx);
console.log("Transaction with compute budget:", signature);
};
if (isLoading) return Loading...
;
return Execute with Compute Budget ;
}
```
## Next Steps
# Configure RPC Endpoints with Solana Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/configure-rpc
import { Card } from '/snippets/v3/components/ui/card.mdx';
Configure custom RPC endpoints for Solana to optimize performance, use private nodes, or connect to different networks. This guide covers RPC setup for all supported libraries.
```typescript
import { useParaSolanaSigner } from '@getpara/react-sdk';
import { createSolanaRpc } from '@solana/kit';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function ConfigureRPC() {
// Use different RPC endpoints by creating different rpc instances
const mainnetRpc = createSolanaRpc('https://api.mainnet-beta.solana.com');
const devnetRpc = createSolanaRpc('https://api.devnet.solana.com');
const customRpc = createSolanaRpc('https://your-custom-rpc-endpoint.com');
// Pass any rpc instance to the hook
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc: mainnetRpc });
const checkHealth = async () => {
const health = await mainnetRpc.getHealth().send();
const version = await mainnetRpc.getVersion().send();
return { health, version };
};
return { solanaSigner, checkHealth };
}
```
```typescript
import { ParaSolanaWeb3Signer } from '@getpara/solana-web3.js-v1-integration';
import { Connection, clusterApiUrl, Commitment } from '@solana/web3.js';
import { para } from './para';
function useCustomRPC() {
// Using Solana public RPC endpoints
const mainnetConnection = new Connection(
clusterApiUrl('mainnet-beta'),
'confirmed'
);
const devnetConnection = new Connection(
clusterApiUrl('devnet'),
'confirmed'
);
// Using custom RPC endpoint (e.g., QuickNode, Alchemy, Helius)
const customConnection = new Connection(
'https://your-custom-rpc-endpoint.com',
{
commitment: 'confirmed',
wsEndpoint: 'wss://your-custom-websocket-endpoint.com',
httpHeaders: {
'Authorization': 'Bearer YOUR_API_KEY',
},
disableRetryOnRateLimit: false,
confirmTransactionInitialTimeout: 30000,
}
);
// Create signers with different connections
const mainnetSigner = new ParaSolanaWeb3Signer(para, mainnetConnection);
const devnetSigner = new ParaSolanaWeb3Signer(para, devnetConnection);
const customSigner = new ParaSolanaWeb3Signer(para, customConnection);
// Example: Get network performance metrics
const checkPerformance = async () => {
const start = Date.now();
const slot = await customConnection.getSlot();
const latency = Date.now() - start;
console.log('Current slot:', slot);
console.log('RPC latency:', latency, 'ms');
const perfSamples = await customConnection.getRecentPerformanceSamples(5);
console.log('Recent performance:', perfSamples);
};
return { mainnetSigner, devnetSigner, customSigner, checkPerformance };
}
```
RPC configuration works the same as @solana/kit — pass different RPC URLs when creating the rpc client:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
const mainnetRpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const devnetRpc = createSolanaRpc("https://api.devnet.solana.com");
const customRpc = createSolanaRpc("https://your-custom-rpc-endpoint.com");
function ConfigureRPC() {
// Pass the desired RPC to the hook
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc: mainnetRpc });
if (isLoading) return Loading...
;
return Connected to: {solanaSigner?.address}
;
}
```
## Next Steps
# Execute Transactions with Solana Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/execute-transactions
import { Card } from '/snippets/v3/components/ui/card.mdx';
Execute transactions on the Solana blockchain using Para's integrated signers. This includes signing and broadcasting transactions to the network.
```typescript
import { useParaSolanaSigner, useParaSolanaSignAndSend } from '@getpara/react-sdk';
import { Address } from '@solana/addresses';
import {
createSolanaRpc,
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
lamports,
pipe,
} from '@solana/kit';
import { getTransferSolInstruction } from '@solana-program/system';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const LAMPORTS_PER_SOL = BigInt(1000000000);
function SendTransaction() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const { signAndSendAsync, isPending } = useParaSolanaSignAndSend(solanaSigner);
const sendSOL = async () => {
if (!solanaSigner) return;
const recipient = "RECIPIENT_ADDRESS_HERE" as Address;
const response = await rpc.getLatestBlockhash().send();
const { blockhash, lastValidBlockHeight } = response.value;
const transferInstruction = getTransferSolInstruction({
source: solanaSigner,
destination: recipient,
amount: lamports(LAMPORTS_PER_SOL / 10n),
});
const transaction = pipe(
createTransactionMessage({ version: "legacy" }),
(tx) => setTransactionMessageFeePayer(solanaSigner.address, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash({ blockhash, lastValidBlockHeight }, tx),
(tx) => appendTransactionMessageInstruction(transferInstruction, tx)
);
await signAndSendAsync({ transactions: [transaction] });
};
return Send 0.1 SOL ;
}
```
```typescript
import { useParaSolana } from './hooks/useParaSolana';
import { Transaction, SystemProgram, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
function SendTransaction() {
const { connection, signer } = useParaSolana();
const sendSOL = async () => {
if (!signer) {
console.error("No signer available. Connect wallet first.");
return;
}
const recipient = new PublicKey("RECIPIENT_ADDRESS_HERE");
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: signer.sender,
toPubkey: recipient,
lamports: LAMPORTS_PER_SOL * 0.1,
})
);
try {
const signature = await signer.sendTransaction(transaction, {
skipPreflight: false,
preflightCommitment: "confirmed",
});
console.log("Transaction signature:", signature);
const confirmation = await connection.confirmTransaction(signature, "confirmed");
console.log("Transaction confirmed:", confirmation);
} catch (error) {
console.error("Transaction failed:", error);
}
};
return Send 0.1 SOL ;
}
```
Use `useParaSolanaSigner` with Codama-generated program clients:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstruction,
signAndSendTransactionMessageWithSigners } from "@solana/kit";
// Generated from your Anchor IDL with Codama
import { getInitializeInstruction } from "./generated/instructions";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
function AnchorTransaction() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const executeProgram = async () => {
if (!solanaSigner) return;
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const instruction = getInitializeInstruction({
user: solanaSigner,
// other accounts are resolved by Codama from your IDL
});
const tx = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(solanaSigner, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstruction(instruction, tx),
);
const signature = await signAndSendTransactionMessageWithSigners(tx);
console.log("Transaction signature:", signature);
};
if (isLoading) return Loading...
;
return Execute Program ;
}
```
Generate your client from your Anchor IDL: `npx codama idl path/to/idl.json -o src/generated`
## Next Steps
# Get Solana Transaction Status
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/get-transaction-status
import { Card } from '/snippets/v3/components/ui/card.mdx';
Monitor transaction confirmation status and wait for finality on Solana. This guide covers checking transaction results and understanding commitment levels.
```typescript
import { useParaSolanaSigner } from '@getpara/react-sdk';
import { createSolanaRpc } from '@solana/kit';
import { Signature, Commitment } from '@solana/web3.js';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function TransactionStatus() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const checkTransactionStatus = async (signature: Signature) => {
try {
const { value: statuses } = await rpc
.getSignatureStatuses([signature], { searchTransactionHistory: true })
.send();
const status = statuses[0];
if (!status) {
console.log("Transaction not found");
return null;
}
console.log("Confirmations:", status.confirmations || "Max (32+)");
console.log("Confirmation status:", status.confirmationStatus);
console.log("Slot:", status.slot);
if (status.err) {
console.error("Transaction failed:", status.err);
return false;
}
return status;
} catch (error) {
console.error("Error checking status:", error);
throw error;
}
};
const waitForConfirmation = async (
signature: Signature,
commitment: Commitment = 'confirmed'
) => {
try {
const startTime = Date.now();
let confirmed = false;
while (!confirmed) {
const { value: statuses } = await rpc
.getSignatureStatuses([signature], { searchTransactionHistory: true })
.send();
const status = statuses[0];
if (status?.confirmationStatus === commitment ||
status?.confirmationStatus === 'finalized') {
confirmed = true;
if (status.err) {
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
}
}
// Wait before next poll
if (!confirmed) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
// Timeout after 30 seconds
if (Date.now() - startTime > 30000) {
throw new Error('Transaction confirmation timeout');
}
}
const elapsed = Date.now() - startTime;
console.log(`Transaction confirmed in ${elapsed}ms`);
// Get transaction details
const { value: txInfo } = await rpc
.getTransaction(signature, {
commitment,
maxSupportedTransactionVersion: 0,
})
.send();
if (txInfo) {
console.log("Block time:", new Date(txInfo.blockTime * 1000));
console.log("Fee:", txInfo.meta.fee, "lamports");
console.log("Compute units:", txInfo.meta.computeUnitsConsumed);
}
return txInfo;
} catch (error) {
console.error("Confirmation error:", error);
throw error;
}
};
return (
checkTransactionStatus("YOUR_SIGNATURE" as Signature)}>
Check Status
waitForConfirmation("YOUR_SIGNATURE" as Signature, "finalized")}>
Wait for Finalization
);
}
```
```typescript
import { useParaSolana } from './hooks/useParaSolana';
import { TransactionSignature, Commitment } from '@solana/web3.js';
function TransactionStatus() {
const { connection } = useParaSolana();
const checkTransactionStatus = async (signature: string) => {
try {
// Get signature status
const status = await connection.getSignatureStatus(signature);
if (status.value === null) {
console.log("Transaction not found");
return null;
}
console.log("Confirmations:", status.value.confirmations || "Max (32+)");
console.log("Confirmation status:", status.value.confirmationStatus);
console.log("Slot:", status.value.slot);
if (status.value.err) {
console.error("Transaction failed:", status.value.err);
return false;
}
return status.value.confirmationStatus;
} catch (error) {
console.error("Error checking status:", error);
throw error;
}
};
const waitForConfirmation = async (
signature: string,
commitment: Commitment = 'confirmed'
) => {
try {
const startTime = Date.now();
// Wait for confirmation with timeout
const confirmation = await connection.confirmTransaction(
signature,
commitment
);
const elapsed = Date.now() - startTime;
console.log(`Transaction confirmed in ${elapsed}ms`);
if (confirmation.value.err) {
throw new Error(`Transaction failed: ${confirmation.value.err}`);
}
// Get detailed transaction info
const txInfo = await connection.getTransaction(signature, {
commitment,
maxSupportedTransactionVersion: 0,
});
if (txInfo) {
console.log("Block time:", new Date(txInfo.blockTime! * 1000));
console.log("Fee:", txInfo.meta?.fee, "lamports");
console.log("Compute units consumed:", txInfo.meta?.computeUnitsConsumed);
}
return txInfo;
} catch (error) {
console.error("Confirmation error:", error);
throw error;
}
};
return (
checkTransactionStatus("YOUR_SIGNATURE")}>
Check Status
waitForConfirmation("YOUR_SIGNATURE", "finalized")}>
Wait for Finalization
);
}
```
Check transaction status using the RPC client:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
function TransactionStatus() {
const { solanaSigner } = useParaSolanaSigner({ rpc });
const checkStatus = async (signature: string) => {
const { value: statuses } = await rpc.getSignatureStatuses([signature as any]).send();
const status = statuses[0];
if (!status) {
console.log("Transaction not found");
return;
}
console.log("Confirmation status:", status.confirmationStatus);
console.log("Slot:", status.slot);
if (status.err) {
console.error("Transaction failed:", status.err);
}
};
return checkStatus("your-signature...")}>Check Status ;
}
```
## Next Steps
# Interact with Solana Programs
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/interact-with-programs
import { Card } from '/snippets/v3/components/ui/card.mdx';
Interact with Solana programs by calling instructions and working with Anchor IDLs. This guide covers manual instruction creation and Anchor's type-safe program interaction.
```typescript
import { useParaSolanaSigner, useParaSolanaSignAndSend } from '@getpara/react-sdk';
import { PublicKey, SystemProgram, TransactionInstruction } from '@solana/web3.js';
import {
createSolanaRpc,
createTransactionMessage,
setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstructions,
pipe,
} from '@solana/kit';
import { Buffer } from 'buffer';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function InteractWithProgram() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const { signAndSendAsync, isPending } = useParaSolanaSignAndSend(solanaSigner);
const callProgram = async () => {
if (!solanaSigner) {
console.error("No signer available. Connect wallet first.");
return;
}
const programId = new PublicKey("YOUR_PROGRAM_ID");
// Example: Initialize account with custom data
const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("seed"), solanaSigner.publicKey.toBuffer()],
programId
);
// Create instruction data (program-specific)
const instructionData = Buffer.alloc(9);
instructionData.writeUInt8(0, 0); // Instruction index
instructionData.writeBigUInt64LE(BigInt(1000), 1); // Example parameter
const instruction = new TransactionInstruction({
keys: [
{ pubkey: solanaSigner.publicKey, isSigner: true, isWritable: true },
{ pubkey: pda, isSigner: false, isWritable: true },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
],
programId,
data: instructionData,
});
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transaction = pipe(
createTransactionMessage({ version: 0 }),
(tx) => setTransactionMessageFeePayerSigner(solanaSigner.publicKey, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
(tx) => appendTransactionMessageInstructions([instruction], tx)
);
const result = await signAndSendAsync({ transactions: [transaction] });
console.log("Program call signature:", result);
console.log("Transaction confirmed");
};
return Call Program ;
}
```
```typescript
import { useParaSolana } from './hooks/useParaSolana';
import { Transaction, TransactionInstruction, PublicKey, SystemProgram } from '@solana/web3.js';
import { Buffer } from 'buffer';
function InteractWithProgram() {
const { connection, signer } = useParaSolana();
const callProgram = async () => {
const programId = new PublicKey("YOUR_PROGRAM_ID");
// Example: Initialize account with custom data
const [pda] = PublicKey.findProgramAddressSync(
[Buffer.from("seed"), signer.sender.toBuffer()],
programId
);
// Create instruction data (program-specific)
const instructionData = Buffer.alloc(9);
instructionData.writeUInt8(0, 0); // Instruction index
instructionData.writeBigUInt64LE(BigInt(1000), 1); // Example parameter
const instruction = new TransactionInstruction({
keys: [
{ pubkey: signer.sender, isSigner: true, isWritable: true },
{ pubkey: pda, isSigner: false, isWritable: true },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
],
programId,
data: instructionData,
});
const transaction = new Transaction().add(instruction);
try {
const signature = await signer.sendTransaction(transaction);
console.log("Program call signature:", signature);
const confirmation = await connection.confirmTransaction(signature, "confirmed");
console.log("Transaction confirmed:", confirmation);
} catch (error) {
console.error("Program call failed:", error);
}
};
return Call Program ;
}
```
Use Codama-generated typed instructions for type-safe program interaction:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstruction,
signAndSendTransactionMessageWithSigners } from "@solana/kit";
// Generated from your Anchor IDL
import { getInitializeInstruction, getUpdateInstruction } from "./generated/instructions";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
function InteractWithProgram() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const callProgram = async () => {
if (!solanaSigner) return;
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
// Codama generates typed instruction builders from your IDL
const instruction = getInitializeInstruction({
user: solanaSigner,
data: { value: 1000 },
});
const tx = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(solanaSigner, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstruction(instruction, tx),
);
const signature = await signAndSendTransactionMessageWithSigners(tx);
console.log("Transaction:", signature);
};
if (isLoading) return Loading...
;
return Call Program ;
}
```
Generate your client: `npx codama idl path/to/idl.json -o src/generated`
## Next Steps
# Manage SPL Token Accounts
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/manage-token-accounts
import { Card } from '/snippets/v3/components/ui/card.mdx';
Create and manage SPL token accounts for holding tokens on Solana. This includes creating Associated Token Accounts (ATAs) and closing empty accounts to reclaim SOL.
```typescript
import { useParaSolanaSigner, useParaSolanaSignAndSend } from '@getpara/react-sdk';
import {
createSolanaRpc,
pipe,
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
address
} from '@solana/kit';
import {
getCreateAssociatedTokenAccountInstruction,
findAssociatedTokenPda,
getCloseAccountInstruction
} from '@solana-program/token';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function ManageTokenAccounts() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const { signAndSendAsync, isPending } = useParaSolanaSignAndSend(solanaSigner);
const createTokenAccount = async (mint: string, owner?: string) => {
if (!solanaSigner) return;
const mintAddress = address(mint);
const ownerAddress = owner ? address(owner) : solanaSigner.address;
const [ata] = await findAssociatedTokenPda({
mint: mintAddress,
owner: ownerAddress,
});
const accountInfo = await rpc.getAccountInfo(ata).send();
if (accountInfo.value) {
console.log("Token account already exists:", ata);
return ata;
}
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transaction = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayer(solanaSigner.address, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstruction(
getCreateAssociatedTokenAccountInstruction({
ata,
mint: mintAddress,
owner: ownerAddress,
payer: solanaSigner,
}),
tx
)
);
try {
const result = await signAndSendAsync({ transactions: [transaction] });
console.log("Created token account:", ata);
console.log("Transaction signature:", result);
return ata;
} catch (error) {
console.error("Failed to create token account:", error);
throw error;
}
};
const closeTokenAccount = async (tokenAccount: string) => {
if (!solanaSigner) return;
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const transaction = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayer(solanaSigner.address, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstruction(
getCloseAccountInstruction({
account: address(tokenAccount),
destination: solanaSigner.address,
authority: solanaSigner,
}),
tx
)
);
try {
const result = await signAndSendAsync({ transactions: [transaction] });
console.log("Closed token account:", tokenAccount);
console.log("Transaction signature:", result);
return result;
} catch (error) {
console.error("Failed to close token account:", error);
throw error;
}
};
return (
createTokenAccount("USDC_MINT_ADDRESS")}>
Create USDC Account
closeTokenAccount("TOKEN_ACCOUNT_ADDRESS")}>
Close Token Account
);
}
```
```typescript
import { useParaSolana } from './hooks/useParaSolana';
import { Transaction, PublicKey } from '@solana/web3.js';
import {
createAssociatedTokenAccountInstruction,
getAssociatedTokenAddress,
createCloseAccountInstruction,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
} from '@solana/spl-token';
function ManageTokenAccounts() {
const { connection, signer } = useParaSolana();
const createTokenAccount = async (mint: string, owner?: string) => {
const mintPubkey = new PublicKey(mint);
const ownerPubkey = owner ? new PublicKey(owner) : signer.sender;
const ata = await getAssociatedTokenAddress(mintPubkey, ownerPubkey);
const accountInfo = await connection.getAccountInfo(ata);
if (accountInfo) {
console.log("Token account already exists:", ata.toString());
return ata;
}
const transaction = new Transaction().add(
createAssociatedTokenAccountInstruction(
signer.sender,
ata,
ownerPubkey,
mintPubkey,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID
)
);
try {
const signature = await signer.sendTransaction(transaction);
console.log("Created token account:", ata.toString());
console.log("Transaction signature:", signature);
return ata;
} catch (error) {
console.error("Failed to create token account:", error);
throw error;
}
};
const closeTokenAccount = async (tokenAccount: string) => {
const tokenAccountPubkey = new PublicKey(tokenAccount);
const transaction = new Transaction().add(
createCloseAccountInstruction(
tokenAccountPubkey,
signer.sender,
signer.sender,
[],
TOKEN_PROGRAM_ID
)
);
try {
const signature = await signer.sendTransaction(transaction);
console.log("Closed token account:", tokenAccount);
console.log("Transaction signature:", signature);
return signature;
} catch (error) {
console.error("Failed to close token account:", error);
throw error;
}
};
return (
createTokenAccount("USDC_MINT_ADDRESS")}>
Create USDC Account
closeTokenAccount("TOKEN_ACCOUNT_ADDRESS")}>
Close Token Account
);
}
```
Token account management uses `@solana-program/token` with Codama-generated helpers:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstruction,
signAndSendTransactionMessageWithSigners } from "@solana/kit";
import { getCreateAssociatedTokenInstruction, findAssociatedTokenPda } from "@solana-program/token";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
function CreateTokenAccount() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const createAccount = async (mintAddress: string) => {
if (!solanaSigner) return;
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const [ata] = await findAssociatedTokenPda({
mint: mintAddress as any,
owner: solanaSigner.address as any,
});
const instruction = getCreateAssociatedTokenInstruction({
payer: solanaSigner,
owner: solanaSigner.address as any,
mint: mintAddress as any,
ata,
});
const tx = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(solanaSigner, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstruction(instruction, tx),
);
const signature = await signAndSendTransactionMessageWithSigners(tx);
console.log("Token account created:", signature);
};
if (isLoading) return Loading...
;
return createAccount("mint-address...")}>Create Token Account ;
}
```
## Next Steps
# Query Wallet Balances with Solana Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/query-balances
import { Card } from '/snippets/v3/components/ui/card.mdx';
Query SOL and SPL token balances for Para wallets using Solana libraries. This guide covers checking native SOL balances and SPL token balances.
```typescript
import { useParaSolanaSigner } from '@getpara/react-sdk';
import { createSolanaRpc, address } from '@solana/kit';
import { getAssociatedTokenAddress } from '@solana/spl-token';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function WalletBalance() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const [solBalance, setSolBalance] = useState(0);
const [tokenBalance, setTokenBalance] = useState(0);
const fetchBalances = async () => {
if (!solanaSigner) return;
try {
// Get SOL balance
const { value } = await rpc.getBalance(solanaSigner.address).send();
setSolBalance(Number(value) / 1e9);
// Get SPL token balance
const usdcMint = address("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const tokenAccount = await getAssociatedTokenAddress(
usdcMint,
solanaSigner.address
);
const { value: accountData } = await rpc.getAccountInfo(tokenAccount).send();
if (accountData && accountData.data) {
const parsed = JSON.parse(accountData.data);
setTokenBalance(Number(parsed.parsed.info.tokenAmount.amount) / 1e6);
} else {
setTokenBalance(0);
}
} catch (error) {
console.error("Error fetching balances:", error);
}
};
return (
Refresh Balances
SOL Balance: {solBalance}
USDC Balance: {tokenBalance}
);
}
```
```typescript
import { useParaSolana } from './hooks/useParaSolana';
import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID, getAccount, getAssociatedTokenAddress } from '@solana/spl-token';
function WalletBalance() {
const { connection, signer } = useParaSolana();
const [solBalance, setSolBalance] = useState(0);
const [tokenBalance, setTokenBalance] = useState(0);
const fetchBalances = async () => {
try {
// Get SOL balance
const balance = await connection.getBalance(signer.sender);
setSolBalance(balance / LAMPORTS_PER_SOL);
// Get SPL token balance (example: USDC)
const usdcMint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
const tokenAccount = await getAssociatedTokenAddress(
usdcMint,
signer.sender
);
try {
const accountInfo = await getAccount(connection, tokenAccount);
setTokenBalance(Number(accountInfo.amount) / 1e6); // USDC has 6 decimals
} catch (error) {
console.log("Token account not found");
setTokenBalance(0);
}
} catch (error) {
console.error("Error fetching balances:", error);
}
};
return (
Refresh Balances
SOL Balance: {solBalance}
USDC Balance: {tokenBalance}
);
}
```
Query balances using the RPC client directly:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc, lamports } from "@solana/kit";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
function QueryBalances() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const getBalance = async () => {
if (!solanaSigner) return;
const { value: balance } = await rpc.getBalance(solanaSigner.address as any).send();
console.log("SOL Balance:", Number(balance) / 1e9);
// Query token accounts
const { value: tokenAccounts } = await rpc.getTokenAccountsByOwner(
solanaSigner.address as any,
{ programId: "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" as any },
{ encoding: "jsonParsed" },
).send();
for (const account of tokenAccounts) {
const parsed = account.account.data.parsed.info;
console.log(`Token: ${parsed.mint}, Balance: ${parsed.tokenAmount.uiAmountString}`);
}
};
if (isLoading) return Loading...
;
return Query Balances ;
}
```
## Next Steps
# Send Tokens with Solana Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/send-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
Transfer SOL tokens between wallets using Para's integrated signers with different Solana libraries.
```typescript
import { useParaSolanaSigner, useParaSolanaSignAndSend } from '@getpara/react-sdk';
import { Address } from '@solana/addresses';
import {
createSolanaRpc,
createTransactionMessage,
setTransactionMessageFeePayer,
setTransactionMessageLifetimeUsingBlockhash,
appendTransactionMessageInstruction,
lamports,
pipe,
} from '@solana/kit';
import { getTransferSolInstruction } from '@solana-program/system';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const LAMPORTS_PER_SOL = BigInt(1000000000);
function SendTokens() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const { signAndSendAsync, isPending } = useParaSolanaSignAndSend(solanaSigner);
const sendSOL = async (recipient: string, amount: number) => {
if (!solanaSigner) return;
const response = await rpc.getLatestBlockhash().send();
const { blockhash, lastValidBlockHeight } = response.value;
const transferInstruction = getTransferSolInstruction({
source: solanaSigner,
destination: recipient as Address,
amount: lamports(BigInt(Math.floor(amount * Number(LAMPORTS_PER_SOL)))),
});
const transaction = pipe(
createTransactionMessage({ version: "legacy" }),
(tx) => setTransactionMessageFeePayer(solanaSigner.address, tx),
(tx) => setTransactionMessageLifetimeUsingBlockhash({ blockhash, lastValidBlockHeight }, tx),
(tx) => appendTransactionMessageInstruction(transferInstruction, tx)
);
const result = await signAndSendAsync({ transactions: [transaction] });
return result;
};
return (
sendSOL("RECIPIENT_ADDRESS", 0.1)}>
Send 0.1 SOL
);
}
```
```typescript
import { useParaSolana } from './hooks/useParaSolana';
import { Transaction, SystemProgram, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
function SendTokens() {
const { connection, signer } = useParaSolana();
const sendSOL = async (recipient: string, amount: number) => {
if (!signer) {
console.error("No signer available. Connect wallet first.");
return;
}
const transaction = new Transaction().add(
SystemProgram.transfer({
fromPubkey: signer.sender,
toPubkey: new PublicKey(recipient),
lamports: LAMPORTS_PER_SOL * amount,
})
);
const signature = await signer.sendTransaction(transaction);
console.log("Transaction signature:", signature);
await connection.confirmTransaction(signature, "confirmed");
console.log("Transaction confirmed");
return signature;
};
return (
sendSOL("RECIPIENT_ADDRESS", 0.1)}>
Send 0.1 SOL
);
}
```
Send SOL and SPL tokens using `@solana-program/system` and `@solana-program/token`:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstruction,
signAndSendTransactionMessageWithSigners } from "@solana/kit";
import { getTransferSolInstruction, lamports } from "@solana-program/system";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
function SendTokens() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const sendSol = async (to: string, amountSol: number) => {
if (!solanaSigner) return;
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const instruction = getTransferSolInstruction({
source: solanaSigner,
destination: to as any,
amount: lamports(BigInt(amountSol * 1e9)),
});
const tx = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(solanaSigner, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstruction(instruction, tx),
);
const signature = await signAndSendTransactionMessageWithSigners(tx);
console.log("Sent SOL:", signature);
};
if (isLoading) return Loading...
;
return sendSol("recipient...", 0.1)}>Send 0.1 SOL ;
}
```
## Next Steps
# Setup Solana Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/setup-libraries
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para supports multiple Solana libraries. Choose your library below.
## Prerequisites
The modern Solana library with the `@solana/signers` interface. Recommended for new projects.
## Install
```bash
npm install @getpara/react-sdk @solana/kit
```
`@getpara/react-sdk` bundles `@getpara/solana-signers-v2-integration` — no separate integration package needed.
## Usage
Use the hook to create a Solana signer for your user's Para embedded wallet or external wallet. The hook wraps transaction signing in a React Query mutation.
```tsx
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function SolanaExample() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
if (isLoading) return Loading...
;
return Address: {solanaSigner?.address}
;
}
```
### Wallet Resolution
When no `address` or `walletId` is passed, the hook resolves the wallet in this order:
1. **Selected wallet** — if the user selected a Solana wallet in the UI. If there is only one Solana wallet in the session, it is already selected by default
2. **First Solana wallet** — the first available Solana wallet on the account
To target a specific wallet:
```tsx
const { solanaSigner } = useParaSolanaSigner({ rpc, address: "SoLaNa..." });
```
Use `createParaSolanaSigner` to create a signer directly.
```typescript
import { createParaSolanaSigner } from "@getpara/solana-signers-v2-integration";
import { createSolanaRpc } from "@solana/kit";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const signer = createParaSolanaSigner({ para, rpc });
console.log("Address:", signer.address);
```
### Wallet Resolution
When no `address` or `walletId` is passed, the factory picks the first available Solana wallet.
The legacy Solana Web3.js library. Use for compatibility with existing projects.
## Install
```bash
npm install @getpara/solana-web3.js-v1-integration @solana/web3.js
```
There is no built-in hook for `@solana/web3.js` — use the constructor directly or create a custom hook.
## Usage
```typescript
import { ParaSolanaWeb3Signer } from "@getpara/solana-web3.js-v1-integration";
import { Connection, clusterApiUrl } from "@solana/web3.js";
const connection = new Connection(clusterApiUrl("mainnet-beta"));
const signer = new ParaSolanaWeb3Signer(para, connection);
```
For interacting with Anchor programs using modern Codama-generated clients. Uses the same `useParaSolanaSigner` hook — no separate Anchor integration needed.
[Codama](https://github.com/solana-program/codama) generates type-safe TypeScript clients from Anchor IDLs that work natively with `@solana/kit` and `@solana/signers`. Your Para signer is already compatible.
## Install
```bash
npm install @getpara/react-sdk @solana/kit
```
Then generate your program client with Codama:
```bash
npx codama idl path/to/your_program.json -o src/generated
```
## Usage
Use `useParaSolanaSigner` — the returned signer works directly with Codama-generated instructions. Wallet resolution is the same as the @solana/kit tab.
```tsx
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
import { pipe, createTransactionMessage, setTransactionMessageFeePayerSigner,
setTransactionMessageLifetimeUsingBlockhash, appendTransactionMessageInstruction } from "@solana/transactions";
import { getInitializeInstruction } from "./generated";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function AnchorProgramInteraction() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const callProgram = async () => {
if (!solanaSigner) return;
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
const instruction = getInitializeInstruction({
user: solanaSigner,
});
const transactionMessage = pipe(
createTransactionMessage({ version: 0 }),
tx => setTransactionMessageFeePayerSigner(solanaSigner, tx),
tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
tx => appendTransactionMessageInstruction(instruction, tx),
);
// Sign and send using the signer
};
if (isLoading) return Loading...
;
return Call Program ;
}
```
```typescript
import { createParaSolanaSigner } from "@getpara/solana-signers-v2-integration";
import { createSolanaRpc } from "@solana/kit";
import { getInitializeInstruction } from "./generated";
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
const signer = createParaSolanaSigner({ para, rpc });
const instruction = getInitializeInstruction({
user: signer,
});
```
## Next Steps
# SWIG Account Abstraction
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/setup-swig
import { Card } from '/snippets/v3/components/ui/card.mdx';
SWIG provides account abstraction on Solana, enabling seamless embedded wallet experiences. This guide walks you through integrating SWIG with Para for embedded wallet creation.
## Prerequisites
Before starting this integration:
- Set up a Para account on their developer portal
- Generate an API Key
- Enable Solana as a supported network
- Basic understanding of React, TypeScript, and Solana
## Installation
Install the required dependencies for SWIG integration:
```bash
yarn add @getpara/react-sdk @tanstack/react-query @getpara/solana-web3.js-v1-integration @solana/web3.js @swig-wallet/classic @swig-wallet/coder
```
Install Vite polyfills for development:
```bash
yarn add vite-plugin-node-polyfills -D
```
## Setup
### Vite Configuration
Update your `vite.config.ts` for Solana compatibility:
```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { nodePolyfills } from "vite-plugin-node-polyfills";
import path from "path";
export default defineConfig({
plugins: [react(), nodePolyfills()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
```
### Environment Variables
Create a `.env.local` file in your project root:
```env
VITE_PARA_API_KEY=your_para_api_key
```
### Para Provider Setup
Create `providers.tsx` in your `src` directory:
```typescript
import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { OAuthMethod, ParaProvider } from "@getpara/react-sdk";
import "@getpara/react-sdk/styles.css";
const queryClient = new QueryClient();
interface ProvidersProps {
children: React.ReactNode;
}
export function Providers({ children }: ProvidersProps) {
return (
{children}
);
}
```
## Authentication
Create a login button component with Para modal integration:
```tsx
import React from "react";
import { useModal, useAccount, useLogout } from "@getpara/react-sdk";
export const LoginButton: React.FC = () => {
const { openModal } = useModal();
const { isConnected } = useAccount();
const { logoutAsync } = useLogout();
const handleClick = () => {
if (isConnected) {
logoutAsync();
} else {
openModal();
}
};
return (
<>
{isConnected ? "Sign out" : "Sign in with Para"}
>
);
};
```
## SWIG Account Creation
Create a SWIG account using Para's Solana Web3.js integration:
```tsx
import React, { useState, useEffect } from "react";
import { Connection, PublicKey, Transaction, LAMPORTS_PER_SOL } from "@solana/web3.js";
import { useAccount, useWallet, useClient } from "@getpara/react-sdk";
import { ParaSolanaWeb3Signer } from "@getpara/solana-web3.js-v1-integration";
import { Actions, Swig, findSwigPda, createEd25519AuthorityInfo } from "@swig-wallet/classic";
export const SwigAccountCreation: React.FC = () => {
const { isConnected } = useAccount();
const { data: wallet } = useWallet();
const [swigAddress, setSwigAddress] = useState(null);
const [solBalance, setSolBalance] = useState(null);
const [isCreatingSwig, setIsCreatingSwig] = useState(false);
const para = useClient();
const connection = new Connection("https://api.devnet.solana.com");
const createSwigAccount = async () => {
if (!wallet?.address || !para) return;
setIsCreatingSwig(true);
try {
const id = new Uint8Array(32);
crypto.getRandomValues(id);
const paraPubkey = new PublicKey(wallet.address);
const [swigPdaAddress] = findSwigPda(id);
const signer = new ParaSolanaWeb3Signer(para, connection, wallet.id);
const rootAuthorityInfo = createEd25519AuthorityInfo(paraPubkey);
const rootActions = Actions.set().all().get();
const createSwigInstruction = Swig.create({
authorityInfo: rootAuthorityInfo,
id,
payer: paraPubkey,
actions: rootActions,
});
const transaction = new Transaction();
transaction.add(createSwigInstruction);
transaction.feePayer = paraPubkey;
const { blockhash } = await connection.getLatestBlockhash();
transaction.recentBlockhash = blockhash;
const signedTransaction = await signer.signTransaction(transaction);
const signature = await connection.sendRawTransaction(signedTransaction.serialize());
await connection.confirmTransaction({
signature,
blockhash,
lastValidBlockHeight: (await connection.getLatestBlockhash()).lastValidBlockHeight,
});
setSwigAddress(swigPdaAddress.toBase58());
setIsCreatingSwig(false);
console.log("Swig account created successfully!");
console.log("Swig address:", swigPdaAddress.toBase58());
console.log("Transaction signature:", signature);
} catch (error) {
console.error("Error in transaction signing:", error);
setIsCreatingSwig(false);
}
};
useEffect(() => {
const fetchBalance = async () => {
if (wallet?.address) {
try {
const balance = await connection.getBalance(new PublicKey(wallet.address));
setSolBalance(balance / LAMPORTS_PER_SOL);
} catch (e) {
setSolBalance(null);
}
}
};
fetchBalance();
}, [wallet?.address]);
if (!isConnected || !wallet) {
return Please connect your wallet first
;
}
return (
You are signed in!
Wallet address: {wallet.address}
{solBalance !== null && (
Balance: {solBalance} SOL
)}
{swigAddress && (
Swig Account Created!
Address: {swigAddress}
)}
{!swigAddress && (
{isCreatingSwig ? "Creating Swig..." : "Create Swig Account"}
)}
);
};
```
## Complete Application
Here's the complete App component that brings everything together:
```tsx
import React from "react";
import { Providers } from "./providers";
import { LoginButton } from "./LoginButton";
import { SwigAccountCreation } from "./SwigAccountCreation";
export default function App() {
return (
SWIG + Para Integration
Using Para's Solana Web3.js integration for SWIG account creation
);
}
```
## Key Implementation Details
### SWIG Account Creation Process
The `createSwigAccount` function performs these critical steps:
1. **Generate Random ID**: Creates a unique 32-byte identifier for the SWIG account
2. **Create Para Signer**: Initializes `ParaSolanaWeb3Signer` with Para client and wallet
3. **Set Authority**: Establishes the Para-generated wallet as root authority
4. **Configure Actions**: Grants full permissions using `Actions.set().all().get()`
5. **Build Transaction**: Creates and configures the Solana transaction
6. **Sign & Send**: Uses Para's signer to sign and broadcast the transaction
### Important Notes
- **Devnet SOL Required**: You need devnet SOL in your Para-generated wallet before creating a SWIG account
- **Root Authority**: The Para-generated wallet becomes the root authority for the SWIG account
- **Error Handling**: The implementation includes proper error handling and loading states
- **Balance Display**: Shows wallet SOL balance and SWIG address after creation
## Testing Your Integration
1. Start your development server:
```bash
yarn dev
```
2. Open your browser to the displayed URL
3. Click "Sign in with Para" to authenticate
4. Send devnet SOL to your wallet address
5. Click "Create Swig Account" to create your SWIG account
## Best Practices
- Never commit API keys to version control
- Use environment variables for sensitive configuration
- Test thoroughly on Solana devnet before mainnet
- Implement proper error handling throughout the flow
- Store SWIG account information securely
## Resources
## Next Steps
Now that you have SWIG working with Para, you can:
- Implement additional SWIG functionality (transfers, token management)
- Customize the Para modal appearance and OAuth providers
- Add more complex transaction flows
- Implement additional security features like multi-signature support
- Integrate with other Solana protocols and applications
# Sign Messages with Solana Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/sign-messages
import { Card } from '/snippets/v3/components/ui/card.mdx';
Sign messages to prove ownership of a Solana address without submitting a transaction. This is commonly used for authentication and verification purposes.
```typescript
import { useParaSolanaSigner } from '@getpara/react-sdk';
import { createSolanaRpc } from '@solana/kit';
import { getUtf8Encoder } from '@solana/codecs-strings';
import bs58 from 'bs58';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function SignMessage() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const signMessage = async () => {
if (!solanaSigner) return;
const message = "Hello, Solana!";
const messageBytes = new Uint8Array(getUtf8Encoder().encode(message));
const signatureResult = await solanaSigner.signMessages([
{ content: messageBytes, signatures: {} }
]);
const signatureBytes = signatureResult[0][solanaSigner.address];
const signatureBase58 = bs58.encode(signatureBytes);
return { message, signature: signatureBase58, signer: solanaSigner.address };
};
return Sign Message ;
}
```
```typescript
import { useParaSolana } from './hooks/useParaSolana';
import bs58 from 'bs58';
function SignMessage() {
const { signer } = useParaSolana();
const signMessage = async () => {
if (!signer) {
console.error("No signer available. Connect wallet first.");
return;
}
const message = "Hello, Solana!";
const messageBytes = new TextEncoder().encode(message);
const signature = await signer.signBytes(Buffer.from(messageBytes));
const signatureBase58 = bs58.encode(signature);
console.log("Message:", message);
console.log("Signature:", signatureBase58);
console.log("Signer:", signer.address);
};
return Sign Message ;
}
```
Signing messages with Anchor programs uses the same `useParaSolanaSigner` — the signer implements `MessagePartialSigner`:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
import { getUtf8Encoder } from "@solana/codecs";
import bs58 from "bs58";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
function SignMessage() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const handleSign = async () => {
if (!solanaSigner) return;
const message = getUtf8Encoder().encode("Hello from Anchor + Para!");
const [signatures] = await solanaSigner.signMessages([
{ content: message, signatures: {} },
]);
const signatureBytes = signatures[solanaSigner.address];
console.log("Signature:", bs58.encode(signatureBytes));
console.log("Signer:", solanaSigner.address);
};
if (isLoading) return Loading...
;
return Sign Message ;
}
```
## Next Steps
# Verify Signatures with Solana Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/solana/verify-signatures
import { Card } from '/snippets/v3/components/ui/card.mdx';
Verify Ed25519 signatures to confirm that a message was signed by a specific Solana address. Essential for authentication and ensuring data integrity.
## Verify Signatures
```typescript
import { useParaSolanaSigner } from '@getpara/react-sdk';
import { createSolanaRpc } from '@solana/kit';
import { getUtf8Encoder } from '@solana/codecs-strings';
import nacl from 'tweetnacl';
import bs58 from 'bs58';
const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
function VerifySignature() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const verifyMessage = async () => {
if (!solanaSigner) return false;
const message = "Hello, Solana!";
const messageBytes = new Uint8Array(getUtf8Encoder().encode(message));
const signatureResult = await solanaSigner.signMessages([
{ content: messageBytes, signatures: {} }
]);
const signatureBytes = signatureResult[0][solanaSigner.address];
const publicKeyBuffer = solanaSigner.sender;
const isValid = nacl.sign.detached.verify(
messageBytes,
signatureBytes,
publicKeyBuffer
);
return isValid;
};
return Sign & Verify Message ;
}
```
```typescript
import { useParaSolana } from './hooks/useParaSolana';
import { PublicKey } from '@solana/web3.js';
import nacl from 'tweetnacl';
import bs58 from 'bs58';
function VerifySignature() {
const { signer } = useParaSolana();
const verifyMessage = async () => {
if (!signer) {
console.error("No signer available. Connect wallet first.");
return;
}
const message = "Hello, Solana!";
const messageBytes = new TextEncoder().encode(message);
// Sign the message first
const signature = await signer.signBytes(Buffer.from(messageBytes));
const signatureBase58 = bs58.encode(signature);
// Verify the signature
try {
const publicKeyBytes = new PublicKey(signer.address).toBytes();
const isValid = nacl.sign.detached.verify(
messageBytes,
signature,
publicKeyBytes
);
console.log("Message:", message);
console.log("Signature:", signatureBase58);
console.log("Signature valid:", isValid);
console.log("Signer:", signer.address);
return isValid;
} catch (error) {
console.error("Verification failed:", error);
return false;
}
};
return Sign & Verify Message ;
}
```
Verify signatures using the same approach as @solana/kit — the signer address is the public key:
```typescript
import { useParaSolanaSigner } from "@getpara/react-sdk";
import { createSolanaRpc } from "@solana/kit";
import { getUtf8Encoder } from "@solana/codecs";
import nacl from "tweetnacl";
import bs58 from "bs58";
const rpc = createSolanaRpc("https://api.devnet.solana.com");
function SignAndVerify() {
const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
const handleSignAndVerify = async () => {
if (!solanaSigner) return;
const message = getUtf8Encoder().encode("Verify me!");
const [signatures] = await solanaSigner.signMessages([
{ content: message, signatures: {} },
]);
const signatureBytes = signatures[solanaSigner.address];
const publicKeyBytes = bs58.decode(solanaSigner.address);
const isValid = nacl.sign.detached.verify(message, signatureBytes, publicKeyBytes);
console.log("Signature valid:", isValid);
};
if (isLoading) return Loading...
;
return Sign & Verify ;
}
```
## Next Steps
# Configure Horizon Endpoints with Stellar Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/stellar/configure-rpc
import { Card } from '/snippets/v3/components/ui/card.mdx';
Configure Horizon API endpoints for Stellar to connect to different networks, use custom nodes, or optimize performance. Stellar uses [Horizon](https://developers.stellar.org/docs/data/horizon) as its HTTP API layer instead of traditional RPC endpoints.
## Configure Horizon Server
```typescript
import { Horizon, Networks } from "@stellar/stellar-sdk";
// Public Horizon endpoints
const mainnetServer = new Horizon.Server("https://horizon.stellar.org");
const testnetServer = new Horizon.Server("https://horizon-testnet.stellar.org");
// Custom Horizon endpoint (e.g., self-hosted or third-party provider)
const customServer = new Horizon.Server("https://your-custom-horizon.example.com", {
allowHttp: false, // set to true only for local development
});
```
## Switch Networks
When switching between mainnet and testnet, update both the Horizon URL and the network passphrase used for signing:
```typescript
import { useStellarSigner } from "@getpara/react-sdk/stellar";
import { Horizon, Networks } from "@stellar/stellar-sdk";
type StellarNetwork = "mainnet" | "testnet";
const NETWORK_CONFIG = {
mainnet: {
horizonUrl: "https://horizon.stellar.org",
networkPassphrase: Networks.PUBLIC,
},
testnet: {
horizonUrl: "https://horizon-testnet.stellar.org",
networkPassphrase: Networks.TESTNET,
},
} as const;
function useStellarNetwork(network: StellarNetwork) {
const config = NETWORK_CONFIG[network];
const { stellarSigner, isLoading } = useStellarSigner({
networkPassphrase: config.networkPassphrase,
});
const server = new Horizon.Server(config.horizonUrl);
return { server, signer: stellarSigner, isLoading, networkPassphrase: config.networkPassphrase };
}
```
## Check Server Health
```typescript
import { Horizon } from "@stellar/stellar-sdk";
async function checkHorizonHealth() {
const server = new Horizon.Server("https://horizon.stellar.org");
// Get ledger info
const ledger = await server.ledgers().order("desc").limit(1).call();
const latestLedger = ledger.records[0];
console.log("Latest ledger:", latestLedger.sequence);
console.log("Closed at:", latestLedger.closed_at);
// Get fee stats
const feeStats = await server.feeStats();
console.log("Base fee:", feeStats.last_ledger_base_fee);
console.log("Fee charged (p50):", feeStats.fee_charged.p50);
}
```
## Next Steps
# Execute Transactions with Stellar Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/stellar/execute-transactions
import { Card } from '/snippets/v3/components/ui/card.mdx';
Build and sign complex Stellar transactions using Para's integrated signers. This includes multi-operation transactions, fee bumps, XDR signing, and Soroban smart contract authorization.
Combine multiple operations into a single atomic transaction:
```typescript
import { useParaStellar } from "./hooks/useParaStellar";
import { TransactionBuilder, Operation, Asset, BASE_FEE, Networks } from "@stellar/stellar-sdk";
function MultiOpTransaction() {
const { server, signer } = useParaStellar();
const execute = async () => {
if (!signer) return;
const sourceAccount = await server.loadAccount(signer.address);
const transaction = new TransactionBuilder(sourceAccount, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.payment({
destination: "GRECIPI...",
asset: Asset.native(),
amount: "10",
})
)
.addOperation(
Operation.manageData({
name: "memo",
value: "payment-ref-123",
})
)
.setTimeout(180)
.build();
const { signedTxXdr } = await signer.signTransaction(transaction.toXDR());
const tx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Transaction hash:", result.hash);
};
return Execute Multi-Op ;
}
```
Wrap an existing transaction with a higher fee to prioritize it during network congestion:
```typescript
import { useParaStellar } from "./hooks/useParaStellar";
import { TransactionBuilder, Operation, Asset, Networks } from "@stellar/stellar-sdk";
function FeeBumpTransaction() {
const { server, signer } = useParaStellar();
const execute = async () => {
if (!signer) return;
const sourceAccount = await server.loadAccount(signer.address);
// Build the inner transaction with a low base fee
const innerTx = new TransactionBuilder(sourceAccount, {
fee: "100",
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.payment({
destination: "GRECIPI...",
asset: Asset.native(),
amount: "10",
})
)
.setTimeout(180)
.build();
// Sign the inner transaction
const { signedTxXdr: signedInnerXdr } = await signer.signTransaction(innerTx.toXDR());
const signedInner = TransactionBuilder.fromXDR(signedInnerXdr, Networks.PUBLIC);
// Wrap with a fee bump
const feeBumpTx = TransactionBuilder.buildFeeBumpTransaction(
signer.address,
"500", // higher fee
signedInner,
Networks.PUBLIC
);
// Sign the fee bump transaction
const { signedTxXdr } = await signer.signTransaction(feeBumpTx.toXDR());
const tx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Fee bump transaction hash:", result.hash);
};
return Submit Fee Bump ;
}
```
Sign a pre-built transaction provided as an XDR string (e.g., from a server or dApp):
```typescript
import { ParaStellarSigner } from "@getpara/stellar-sdk-v14-integration";
import { TransactionBuilder, Networks, Horizon } from "@stellar/stellar-sdk";
import { useClient } from "@getpara/react-sdk";
function SignXDR() {
const para = useClient();
const signAndSubmit = async (xdrString: string) => {
if (!para) return;
const signer = new ParaStellarSigner(para, Networks.PUBLIC);
// Sign the XDR directly
const signedXdr = await signer.signTransactionXDR(
xdrString,
Networks.PUBLIC
);
// Submit to the network
const server = new Horizon.Server("https://horizon.stellar.org");
const tx = TransactionBuilder.fromXDR(signedXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Transaction hash:", result.hash);
};
return signAndSubmit("AAAA...")}>Sign XDR ;
}
```
Create a trustline to hold a custom asset (required before receiving non-XLM tokens):
```typescript
import { useParaStellar } from "./hooks/useParaStellar";
import { TransactionBuilder, Operation, Asset, BASE_FEE, Networks } from "@stellar/stellar-sdk";
function ChangeTrust() {
const { server, signer } = useParaStellar();
const addTrustline = async (assetCode: string, issuer: string) => {
if (!signer) return;
const sourceAccount = await server.loadAccount(signer.address);
const asset = new Asset(assetCode, issuer);
const transaction = new TransactionBuilder(sourceAccount, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(Operation.changeTrust({ asset }))
.setTimeout(180)
.build();
const { signedTxXdr } = await signer.signTransaction(transaction.toXDR());
const tx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Trustline added:", result.hash);
};
return (
addTrustline(
"USDC",
"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
)
}
>
Add USDC Trustline
);
}
```
Sign authorization entries for Soroban smart contract interactions:
```typescript
import { useStellarSigner } from "@getpara/react-sdk/stellar";
import { Networks } from "@stellar/stellar-sdk";
function SorobanAuth() {
const { stellarSigner } = useStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const signAuth = async (authEntryXdr: string) => {
if (!stellarSigner) return;
// Sign the authorization entry
const { signedAuthEntry, signerAddress } =
await stellarSigner.signAuthEntry(authEntryXdr);
console.log("Signed auth entry:", signedAuthEntry);
console.log("Signer:", signerAddress);
return signedAuthEntry;
};
return (
signAuth("AAAA...")}>
Authorize Contract Call
);
}
```
The `signAuthEntry` method is compatible with Stellar SDK's `contract.Client`, allowing Para wallets to authorize Soroban smart contract invocations. The auth entry XDR is typically provided by the contract client during simulation.
## Next Steps
# Query Wallet Balances with Stellar Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/stellar/query-balances
import { Card } from '/snippets/v3/components/ui/card.mdx';
Query XLM and token balances for your connected Para wallet or any Stellar address using the Horizon API.
## Query Balances
```typescript
import { useState } from "react";
import { useParaStellar } from "./hooks/useParaStellar";
function BalanceDisplay() {
const { server, signer, isLoading } = useParaStellar();
const [balances, setBalances] = useState<{ asset: string; balance: string }[]>([]);
const queryBalances = async () => {
if (!signer) return;
const account = await server.loadAccount(signer.address);
const parsed = account.balances.map((b) => {
if (b.asset_type === "native") {
return { asset: "XLM", balance: b.balance };
}
return { asset: `${b.asset_code}:${b.asset_issuer}`, balance: b.balance };
});
setBalances(parsed);
console.log("Balances:", parsed);
};
if (isLoading) return Loading...
;
return (
Address: {signer?.address}
Query Balances
{balances.map((b) => (
{b.balance} {b.asset}
))}
);
}
```
## Query a Specific Asset
```typescript
import { Horizon } from "@stellar/stellar-sdk";
async function getAssetBalance(address: string, assetCode: string, assetIssuer: string) {
const server = new Horizon.Server("https://horizon.stellar.org");
const account = await server.loadAccount(address);
const match = account.balances.find(
(b) => b.asset_type !== "native" && b.asset_code === assetCode && b.asset_issuer === assetIssuer
);
return match ? match.balance : "0";
}
// Example: Check USDC balance
const usdcBalance = await getAssetBalance(
"GABCD...",
"USDC",
"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
);
```
A balance of `"0"` for a custom asset means the account has a trustline but no tokens. If `find` returns `undefined`, the account has no trustline for that asset and cannot receive it until one is created. See [Execute Transactions](/v3/react/guides/web3-operations/stellar/execute-transactions) for how to add trustlines.
## Next Steps
# Send Tokens with Stellar Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/stellar/send-tokens
import { Card } from '/snippets/v3/components/ui/card.mdx';
Transfer XLM tokens between wallets using Para's Stellar signer with the Stellar SDK.
## Send XLM
```typescript
import { useParaStellar } from "./hooks/useParaStellar";
import { TransactionBuilder, Operation, Asset, BASE_FEE, Networks } from "@stellar/stellar-sdk";
function SendXLM() {
const { server, signer, isLoading } = useParaStellar();
const sendPayment = async (recipient: string, amount: string) => {
if (!signer) {
console.error("No signer available. Connect wallet first.");
return;
}
// Load the sender's account from the network
const sourceAccount = await server.loadAccount(signer.address);
// Build the payment transaction
const transaction = new TransactionBuilder(sourceAccount, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.payment({
destination: recipient,
asset: Asset.native(),
amount, // e.g. "10" for 10 XLM
})
)
.setTimeout(180)
.build();
// Sign the transaction with Para
const { signedTxXdr } = await signer.signTransaction(transaction.toXDR());
// Submit to the network
const tx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(tx);
console.log("Transaction hash:", result.hash);
return result;
};
if (isLoading) return Loading...
;
return (
Address: {signer?.address}
sendPayment("GRECIPI...", "10")}>
Send 10 XLM
);
}
```
## Send Custom Assets
To send a custom asset (like USDC on Stellar), replace `Asset.native()` with the specific asset:
```typescript
import { Asset } from "@stellar/stellar-sdk";
// Example: USDC on Stellar
const usdc = new Asset(
"USDC",
"GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN"
);
// Use in your payment operation
Operation.payment({
destination: recipient,
asset: usdc,
amount: "100", // 100 USDC
});
```
The recipient must have a trustline for the custom asset before they can receive it. See [Execute Transactions](/v3/react/guides/web3-operations/stellar/execute-transactions) for how to create trustlines.
## Next Steps
# Setup Stellar Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/stellar/setup-libraries
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para supports Stellar blockchain interactions through the . Use our integration package to sign Stellar transactions with Para's MPC wallets.
## Installation
If using `@stellar/stellar-sdk` and the `@getpara/react-sdk` you can use our hook to access the Stellar signer without any additional setup.
```bash npm
npm install @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --save-exact
```
```bash yarn
yarn add @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --exact
```
```bash pnpm
pnpm add @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --save-exact
```
```bash bun
bun add @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --exact
```
## Library Setup
```typescript hooks/useParaStellar.ts
import { useStellarSigner } from "@getpara/react-sdk/stellar";
import { Horizon, Networks } from "@stellar/stellar-sdk";
const HORIZON_URL = "https://horizon.stellar.org";
export function useParaStellar() {
const { stellarSigner, isLoading } = useStellarSigner({
networkPassphrase: Networks.PUBLIC,
});
const server = new Horizon.Server(HORIZON_URL);
return { server, signer: stellarSigner, isLoading };
}
```
```typescript
import { createParaStellarSigner } from "@getpara/stellar-sdk-v14-integration";
import { Horizon, Networks } from "@stellar/stellar-sdk";
import { para } from "./para";
const server = new Horizon.Server("https://horizon.stellar.org");
const signer = createParaStellarSigner({
para,
networkPassphrase: Networks.PUBLIC,
});
console.log("Stellar address:", signer.address);
```
To use the **Stellar testnet**, replace `Networks.PUBLIC` with `Networks.TESTNET` and use the testnet Horizon URL: `https://horizon-testnet.stellar.org`. You can fund testnet accounts using Stellar's [Friendbot](https://friendbot.stellar.org).
## Next Steps
# Sign Messages with Stellar Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/stellar/sign-messages
import { Card } from '/snippets/v3/components/ui/card.mdx';
Sign arbitrary bytes to prove ownership of a Stellar address without submitting a transaction. This is useful for authentication and off-chain verification.
## Sign Bytes
Use the `ParaStellarSigner` class API to sign arbitrary bytes directly:
```typescript
import { ParaStellarSigner } from "@getpara/stellar-sdk-v14-integration";
import { Networks } from "@stellar/stellar-sdk";
import { useClient } from "@getpara/react-sdk";
function SignMessage() {
const para = useClient();
const signMessage = async () => {
if (!para) return;
const signer = new ParaStellarSigner(para, Networks.PUBLIC);
const message = "Hello, Stellar!";
const messageBytes = Buffer.from(new TextEncoder().encode(message));
const signature = await signer.signBytes(messageBytes);
console.log("Message:", message);
console.log("Signature:", signature.toString("hex"));
console.log("Signer:", signer.address);
};
return Sign Message ;
}
```
Stellar does not have a standardized message signing format like EIP-191 on Ethereum. The `signBytes` method signs raw bytes using Ed25519. Both parties must agree on how to encode and hash the message before signing.
## Next Steps
# Verify Signatures with Stellar Libraries
Source: https://docs.getpara.com/v3/react/guides/web3-operations/stellar/verify-signatures
import { Card } from '/snippets/v3/components/ui/card.mdx';
Verify Ed25519 signatures to confirm that a message was signed by a specific Stellar address. Essential for authentication and ensuring data integrity.
## Verify Signatures
```typescript
import { ParaStellarSigner } from "@getpara/stellar-sdk-v14-integration";
import { Networks, StrKey } from "@stellar/stellar-sdk";
import { useClient } from "@getpara/react-sdk";
import nacl from "tweetnacl";
function VerifySignature() {
const para = useClient();
const verifyMessage = async () => {
if (!para) return;
const signer = new ParaStellarSigner(para, Networks.PUBLIC);
const message = "Hello, Stellar!";
const messageBytes = Buffer.from(new TextEncoder().encode(message));
// Sign the message
const signature = await signer.signBytes(messageBytes);
// Extract the raw Ed25519 public key from the Stellar G-address
const publicKeyBytes = StrKey.decodeEd25519PublicKey(signer.address);
// Verify the signature using tweetnacl
const isValid = nacl.sign.detached.verify(
new Uint8Array(messageBytes),
new Uint8Array(signature),
publicKeyBytes
);
console.log("Message:", message);
console.log("Signature:", signature.toString("hex"));
console.log("Signature valid:", isValid);
console.log("Signer:", signer.address);
return isValid;
};
return Sign & Verify Message ;
}
```
Install `tweetnacl` for Ed25519 signature verification: `npm install tweetnacl`. The `StrKey.decodeEd25519PublicKey` method from `@stellar/stellar-sdk` extracts the raw 32-byte public key from a Stellar G-address.
## Next Steps
# Switch Active Wallet
Source: https://docs.getpara.com/v3/react/guides/web3-operations/switch-wallet
import { MethodDocs } from '/snippets/v3/components/method-doc.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
import UseWalletState from '/snippets/v3/definitions/hooks/useWalletState.mdx';
To switch wallets, use the `useWalletState` hook. This hook provides methods to manage the currently selected wallet and allows you to switch between different wallets.
### useWalletState
```tsx
import { useWalletState } from '@getpara/react-sdk';
const { selectedWallet, setSelectedWallet, updateSelectedWallet } = useWalletState();
// Switch to a specific wallet by ID
setSelectedWallet({ id: 'wallet_123' });
// Refresh selection from Para client state
updateSelectedWallet();
```
## Next Steps
# React SDK Overview
Source: https://docs.getpara.com/v3/react/overview
import {Card} from '/snippets/v3/components/ui/card.mdx';
import {DemoCallout} from '/snippets/v3/components/ui/demo-callout.mdx';
Para's React SDK provides a comprehensive solution for Web3 authentication and wallet management in React applications. Navigate through our documentation to find the right integration path for your project.
## React Frameworks
Get started with Para in your preferred React framework. Each guide provides framework-specific setup instructions and best practices.
## Getting Started
Begin your Para integration journey with these essential resources designed to get you up and running quickly.
## Sign Transactions & Messages
Learn how to sign transactions and messages using Para's wallet infrastructure and integrate with popular Web3 libraries.
### Core Signing Operations
### Blockchain Libraries
### Smart Accounts
## Manage Users & Authentication
Implement secure authentication flows and manage user sessions with Para's comprehensive auth system.
### Wallet Connection Options
### Advanced Authentication
## Customize Your Integration
Tailor Para's appearance and behavior to match your brand and user experience requirements.
### Developer Portal Configuration
### UI Customization
## Advanced Features
Explore advanced capabilities and integration patterns for complex use cases.
### Resources & Support
# React SDK Quickstart
Source: https://docs.getpara.com/v3/react/quickstart
import {FilterButton} from '/snippets/v3/components/quick-start/filter-button.mdx';
import {FilterSection} from '/snippets/v3/components/quick-start/filter-section.mdx';
import {CustomCodeBlock} from '/snippets/v3/components/quick-start/code-block.mdx';
import {NetworkSelector} from '/snippets/v3/components/quick-start/network-selector.mdx';
import {PackageManagerSelector} from '/snippets/v3/components/quick-start/package-manager-selector.mdx';
import {SDKQuickstartNetworks} from '/snippets/v3/components/quick-start/sdk-quickstart-networks.mdx';
import {SDKSnippetsReact} from '/snippets/v3/components/quick-start/snippets/react.mdx';
import {SDKQuickstart} from '/snippets/v3/components/quick-start/sdk-quickstart.mdx';
import {Link} from '/snippets/v3/components/ui/link.mdx';
import {DemoCallout} from '/snippets/v3/components/ui/demo-callout.mdx';
## Interactive Setup
**Need an API Key?** Head to the to create your account and get your API key. If you prefer the terminal, use the to create, inspect, and configure keys from your local project.
Using Claude, Codex, or another coding agent? The shows how to let an agent inspect your Para context, scaffold a starter, and run `para doctor` safely.
Want a pre-configured starter instead? Run `npx @getpara/cli create` to scaffold a new project with the SDK, auth, and network settings ready to go. See the for details.
## Next Steps
Once you've integrated the SDK, explore these guides to enhance your application:
Learn how to sign transactions and messages with Para wallets
Configure your app settings, branding, and security in the Developer Portal
Manage user sessions, JWTs, and authentication flows
Explore all available React hooks for Para integration
# Para with Next.js
Source: https://docs.getpara.com/v3/react/setup/nextjs
import EnvironmentInfo from "/snippets/v3/quick-start-environment-info.mdx";
import { Card } from "/snippets/v3/components/ui/card.mdx";
import { Link } from "/snippets/v3/components/ui/link.mdx";
import { DemoCallout } from "/snippets/v3/components/ui/demo-callout.mdx";
This guide will walk you through integrating Para SDK into your **Next.js** application, providing seamless user
authentication and wallet management.
This guide uses the **Next.js App Router**. For Pages Router, refer to the for setup instructions.
## Prerequisites
Before starting, you'll need a Para API key which you can obtain from the Para Developer Portal. You can learn to create your account and get
your API key from the Developer Portal.
Prefer to manage setup from your terminal? Install the to create API keys, configure allowed origins, and run diagnostics. The shows a Claude and Codex friendly workflow.
## Installation
Install the Para React SDK and React Query:
```bash npm
npm install @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --save-exact
```
```bash yarn
yarn add @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --exact
```
````bash pnpm
pnpm add @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --save-exact
```bash bun
bun add @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --exact
````
Using wagmi v3? See [additional dependencies](/v3/react/troubleshooting/nextjs#wagmi-v3-module-resolution-errors) required.
## Configure Providers
Create a providers component and wrap your application with it. Note the `"use client"` directive at the top - this is
required for Next.js App Router:
The `import "@getpara/react-sdk/styles.css"` is **required** for the Para modal to display correctly. Without this import, the modal will not be visible.
```jsx providers.tsx
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ParaProvider } from "@getpara/react-sdk";
import "@getpara/react-sdk/styles.css";
const queryClient = new QueryClient();
export function Providers({
children,
}: Readonly<{
children: React.ReactNode,
}>) {
return (
{children}
);
}
```
If you're using a legacy API key (one without an environment prefix) you must provide a value to the
`paraClientConfig.environment`. You can retrieve your updated API key from the Para Developer Portal at
https://developer.getpara.com/
## Wrap Your App with Providers
Update your root layout to wrap your application with the Providers component:
```jsx app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
You can learn more about the `ParaProvider` and its definition in the .
The `ParaProvider` utilizes libraries like Wagmi, Graz, and Solana Wallet Adapter, to power wallet connections. Meaning you can use any of the hooks provided by these libraries when within the `ParaProvider` context. You can learn more about using external wallets in the .
## Create a Connect Button
Now you can create a component that uses Para hooks to manage wallet connection:
```jsx connect-button.tsx
"use client";
import { useModal, useAccount, useWallet } from "@getpara/react-sdk";
export function ConnectButton() {
const { openModal } = useModal();
const { data: wallet } = useWallet();
const { isConnected } = useAccount();
return (
openModal()}>
{isConnected ? `Connected: ${wallet?.address?.slice(0, 6)}...${wallet?.address?.slice(-4)}` : "Connect Wallet"}
);
}
```
Learn more about the hooks used in this example:
- - Control the Para modal programmatically
- - Access the current wallet data
- - Get account connection status and details
**Testing?** Use `BETA` testing credentials for fast development. Check out the to learn about test emails and phone numbers.
## Example
## Next Steps
Success you've set up Para with Next.js! Now you can expand your application with wallet connections, account
management, and more.
# Para with TanStack Start
Source: https://docs.getpara.com/v3/react/setup/tanstack-start
import EnvironmentInfo from "/snippets/v3/quick-start-environment-info.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { DemoCallout } from '/snippets/v3/components/ui/demo-callout.mdx';
This guide will walk you through integrating Para SDK with **TanStack Start** while preserving server-side rendering (SSR) capabilities.
TanStack Start is a full-stack React framework powered by TanStack Router. Para SDK uses styled-components internally which requires client-side only loading to work correctly with SSR.
## Prerequisites
Before starting, you'll need a Para API key which you can obtain from the Para Developer Portal. You can learn to create your account and get your API key from the Developer Portal.
## Installation
Install the Para React SDK and React Query:
```bash npm
npm install @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --save-exact
```
```bash yarn
yarn add @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --exact
```
````bash pnpm
pnpm add @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --save-exact
```bash bun
bun add @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --exact
````
Using wagmi v3? See [additional dependencies](/v3/react/troubleshooting/tanstack-start#wagmi-v3-module-resolution-errors) required.
## Setting Up Polyfills with TanStack Start
Since TanStack Start uses Vite under the hood, we need to set up polyfills for modules like crypto, buffer, etc. that Para SDK relies on. We only want to run these polyfills on the client, not on the server.
1. Install the Vite Node Polyfills plugin:
```bash npm
npm install vite-plugin-node-polyfills --save-dev
```
```bash yarn
yarn add vite-plugin-node-polyfills -D
```
```bash pnpm
pnpm add vite-plugin-node-polyfills -D
```
```bash bun
bun add -d vite-plugin-node-polyfills
```
2. Configure the polyfills in your `app.config.ts`:
```ts app.config.ts
import { defineConfig } from "@tanstack/react-start/config";
import tsConfigPaths from "vite-tsconfig-paths";
import { nodePolyfills } from "vite-plugin-node-polyfills";
export default defineConfig({
tsr: { appDirectory: "src" },
// Base configuration (applied to both client and server)
vite: {
plugins: [tsConfigPaths({ projects: ["./tsconfig.json"] })],
define: {
// This helps modules determine the execution environment
"process.browser": true,
},
},
// Client-specific configuration
routers: {
client: {
vite: {
// Apply node polyfills only on the client side
plugins: [nodePolyfills()],
},
},
},
});
```
## Setup Postinstall Script
Add the Para setup script to your `package.json`:
```json package.json
{
"scripts": {
"postinstall": "npx setup-para"
}
}
```
## Create a Client-Only Providers Component
Create a providers component using React.lazy and ClientOnly to ensure Para SDK only loads on the client:
```tsx src/components/Providers.tsx
import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ClientOnly } from "@tanstack/react-router";
const queryClient = new QueryClient();
// Lazy load ParaProvider to avoid SSR issues
const LazyParaProvider = React.lazy(() =>
import("@getpara/react-sdk").then((mod) => ({
default: mod.ParaProvider
}))
);
export default function Providers({ children }: React.PropsWithChildren) {
return (
{children}
);
}
```
If you're using a legacy API key (one without an environment prefix) you must provide a value to the `paraClientConfig.environment`. You can retrieve your updated API key from the Para Developer Portal at https://developer.getpara.com/
## Wrap Your App with Providers
Update your root component to wrap your application with the Providers component:
```tsx src/routes/__root.tsx
import Providers from "~/components/Providers";
import { Outlet } from "@tanstack/react-router";
export function RootComponent() {
return (
);
}
```
You can learn more about the `ParaProvider` and its definition in the .
The `ParaProvider` utilizes libraries like Wagmi, Graz, and Solana Wallet Adapter, to power wallet connections. Meaning you can use any of the hooks provided by these libraries when within the `ParaProvider` context. You can learn more about using external wallets in the .
## Create a Client-Only Connect Component
Create a component that uses Para SDK hooks, ensuring it's only rendered on the client:
```tsx src/components/ParaContainer.tsx
import { useModal, useAccount } from "@getpara/react-sdk";
export function ParaContainer() {
const { openConnectModal, openWalletModal } = useModal();
const account = useAccount();
return (
{account.isConnected && account.embedded.wallets?.length ? (
Connected: {account.embedded.wallets[0].address}
Manage Wallet
) : (
Connect Wallet
)}
);
}
```
Learn more about the hooks used in this example:
- - Control the Para modal programmatically (openConnectModal, openWalletModal)
- - Get account connection status and wallet details
Use it in your pages with ClientOnly wrapper:
The `import "@getpara/react-sdk/styles.css"` is **required** for the Para modal to display correctly. Without this import, the modal will not be visible. You can import it in your root route or any page that uses Para components.
```tsx src/routes/index.tsx
import React from "react";
import { createFileRoute, ClientOnly } from "@tanstack/react-router";
import "@getpara/react-sdk/styles.css";
// Lazy load Para container component
const LazyParaContainer = React.lazy(() =>
import("~/components/ParaContainer").then((mod) => ({
default: mod.ParaContainer,
}))
);
function Home() {
return (
Para Modal Example
Loading Para components...}>
);
}
export const Route = createFileRoute("/")({
component: Home,
});
```
The Para SDK uses styled-components internally which can cause issues during server-side rendering. By using `React.lazy` and `ClientOnly`, we ensure Para components are only evaluated in the browser environment where styled-components works correctly.
## Example
## Next Steps
Success you've set up Para with TanStack Start! Now you can expand your application with wallet connections, account management, and more.
# Para with React + Vite
Source: https://docs.getpara.com/v3/react/setup/vite
import EnvironmentInfo from "/snippets/v3/quick-start-environment-info.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { DemoCallout } from '/snippets/v3/components/ui/demo-callout.mdx';
This guide will walk you through integrating Para SDK into your **Vite**-powered React application, providing seamless user authentication and wallet management.
## Prerequisites
Before starting, you'll need a Para API key which you can obtain from the Para Developer Portal. You can learn to create your account and get your API key from the Developer Portal.
## Installation
Install the Para React SDK, React Query, and required polyfills for Vite:
```bash npm
npm install @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --save-exact && npm install vite-plugin-node-polyfills --save-dev
```
```bash yarn
yarn add @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --exact && yarn add vite-plugin-node-polyfills -D
```
```bash pnpm
pnpm add @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --save-exact && pnpm add vite-plugin-node-polyfills -D
```
```bash bun
bun add @getpara/react-sdk @tanstack/react-query graz @cosmjs/cosmwasm-stargate @cosmjs/launchpad @cosmjs/proto-signing @cosmjs/stargate @cosmjs/tendermint-rpc @leapwallet/cosmos-social-login-capsule-provider long starknet wagmi@^2 viem @farcaster/mini-app-solana @farcaster/miniapp-sdk @farcaster/miniapp-wagmi-connector @solana-mobile/wallet-adapter-mobile @solana/wallet-adapter-base @solana/wallet-adapter-react @solana/wallet-adapter-walletconnect @solana/web3.js @stellar/stellar-sdk --exact && bun add -d vite-plugin-node-polyfills
```
Using wagmi v3? See [additional dependencies](/v3/react/troubleshooting/react-vite#wagmi-v3-module-resolution-errors) required.
Then add the polyfill plugin to your `vite.config.js`:
```js vite.config.js
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { nodePolyfills } from "vite-plugin-node-polyfills";
export default defineConfig({
plugins: [react(), nodePolyfills()],
});
```
## Configure Providers
Create a providers component and wrap your application with it:
The `import "@getpara/react-sdk/styles.css"` is **required** for the Para modal to display correctly. Without this import, the modal will not be visible.
```jsx src/providers.jsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ParaProvider } from "@getpara/react-sdk";
import "@getpara/react-sdk/styles.css";
const queryClient = new QueryClient();
export function Providers({ children }) {
return (
{children}
);
}
```
```jsx src/App.jsx
import { Providers } from './providers';
function App() {
return (
{/* Your app content */}
);
}
export default App;
```
If you're using a legacy API key (one without an environment prefix) you must provide a value to the `paraClientConfig.environment`. You can retrieve your updated API key from the Para Developer Portal at https://developer.getpara.com/
You can learn more about the `ParaProvider` and its definition in the .
The `ParaProvider` utilizes libraries like Wagmi, Graz, and Solana Wallet Adapter, to power wallet connections. Meaning you can use any of the hooks provided by these libraries when within the `ParaProvider` context. You can learn more about using external wallets in the .
## Create a Connect Button
Now create a component that uses Para hooks to manage wallet connection:
```jsx ConnectButton.jsx
import { useModal, useAccount, useWallet } from "@getpara/react-sdk";
export function ConnectButton() {
const { openModal } = useModal();
const { data: wallet } = useWallet();
const { isConnected } = useAccount();
return (
openModal()}>
{isConnected
? `Connected: ${wallet?.address?.slice(0, 6)}...${wallet?.address?.slice(-4)}`
: "Connect Wallet"}
);
}
```
Learn more about the hooks used in this example:
- - Control the Para modal programmatically
- - Access the current wallet data
- - Get account connection status and details
**Testing?** Use `BETA` testing credentials for fast development. Check out the to learn about test emails and phone numbers.
## Example
## Next Steps
Success you've set up Para with Vite! Now you can expand your application with wallet connections, account management, and more.
# Testing Your Integration
Source: https://docs.getpara.com/v3/react/testing-guide
## Overview
Para provides test credentials for the `BETA` environment to help you develop and test your integration without creating real user accounts. This guide explains how to use test accounts and manage your testing workflow effectively.
## Test Credentials
### Email Testing
For email-based authentication testing in the `BETA` environment:
- Use any email ending in `@test.getpara.com`
- Examples: `dev@test.getpara.com`, `test1@test.getpara.com`, `user123@test.getpara.com`
- **Any OTP code will work** for verification (e.g., `123456`, `000000`, `111111`)
- Perfect for testing email-based authentication flows
### Phone Number Testing
For SMS-based authentication testing in the BETA environment:
- Use US phone numbers (+1) in format: `(area code)-555-xxxx`
- Examples: `(425)-555-1234`, `(206)-555-9876`, `(310)-555-0001`
- **Any OTP code will work** for verification
- Ideal for testing phone-based authentication flows
## Important Testing Notes
These test credentials **only work in the BETA Environment**. They will not work in production. Make sure your Para SDK is configured for the BETA environment when using these credentials.
### User Limits
- Beta accounts are limited to **50 users**
- If you reach the 50 user limit, you will need to delete users to continue testing
- Regular cleanup helps you stay within limits during development
### Managing Test Users
Navigate to your [Para Developer Portal](https://developer.getpara.com) and log in with your developer credentials
Click on "Users" in the left sidebar. You'll be taken to a list of users for your API key where you can see the identifier and login method for each user.
Click on any user to open a drawer with user details. Inside the drawer, click "Delete User" to remove that specific user.
If you need to clear all test users at once, use the "Delete All Users" button available on the users page.
**Important:** Deleting users is only possible while in the BETA environment. In production, wallets are permanent and cannot be deleted.
## Troubleshooting
- Verify you're using the BETA environment
- Check that the email ends exactly with `@test.getpara.com`
- Ensure phone numbers follow the `(xxx)-555-xxxx` format
- Access the Developer Portal to delete unused test users
- Consider using a naming convention to identify old test accounts
- Set up automated cleanup in your test suite
- Confirm you're in BETA environment (not production)
- Any numeric OTP should work (e.g., "123456")
- Check for typos in the test credentials
## Next Steps
Get started with Para integration
Configure your developer portal
Manage user sessions effectively
# Next.js
Source: https://docs.getpara.com/v3/react/troubleshooting/nextjs
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import DuplicateParaModalTroubleshooting from '/snippets/v3/duplicate-para-modal-troubleshooting.mdx';
Integrating Para with Next.js can present unique challenges. This guide outlines common issues you might encounter and
provides effective solutions and best practices to ensure a seamless integration.
Using an LLM (ChatGPT, Claude) or Coding Assistant (Cursor, Github Copilot)? Here are a few tips:
1) Include the for the most up-to-date help
2) Check out the for an interactive LLM using Para Examples Hub
## General Troubleshooting Steps
Before diving into specific issues, try these general troubleshooting steps:
```bash
rm -rf .next
rm -rf node_modules
npm cache clean --force
```
```bash npm install ```
```bash
npm update @getpara/web-sdk @getpara/react-sdk
```
## Common Issues and Solutions
**Problem**: Para is a client-side library and may cause issues with Server-Side Rendering (SSR) in Next.js.
**Solution**: Use dynamic imports to load Para components only on the client side.
```jsx
import dynamic from "next/dynamic";
const ParaComponent = dynamic(() => import("@getpara/react-sdk").then((mod) => mod.ParaComponent), {
ssr: false,
});
```
**Problem**: Next.js may not transpile Para packages by default, leading to build errors.
**Solution**: Add Para packages to the `transpilePackages` configuration in `next.config.js`:
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: [
"@getpara/web-sdk",
"@getpara/react-sdk",
// Add any other Para-related packages here
],
// ... other configurations
};
module.exports = nextConfig;
```
**Problem**: Para's CSS files may not load correctly in Next.js.
**Solution**: Import Para's CSS file in your `_app.js` or `_app.tsx` file:
```jsx
import "@getpara/react-sdk/styles.css";
function MyApp({ Component, pageProps }) {
return ;
}
export default MyApp;
```
**Problem**: Para API key not being recognized in the application.
**Solution**: Ensure you're setting the environment variable correctly in your `.env.local` file and prefixing it with `NEXT_PUBLIC_` for client-side access:
```
NEXT_PUBLIC_PARA_API_KEY=your_api_key_here
```
Then, use it in your code like this:
```javascript
const para = new Para(process.env.NEXT_PUBLIC_PARA_API_KEY);
```
**Problem**: Errors related to missing Node.js modules like `crypto` or `buffer`.
**Solution**: While not always necessary for Next.js, you may need to add polyfills. Update your `next.config.js`:
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
// ... other configs
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
buffer: require.resolve("buffer"),
};
}
return config;
},
};
module.exports = nextConfig;
```
**Problem**: After upgrading from wagmi v2 to v3, you encounter module resolution errors or missing dependency warnings.
**Solution**: Wagmi v3 requires additional peer dependencies that are not included in the default installation. Install them:
```bash
npm install porto @base-org/account @gemini-wallet/core @metamask/sdk @safe-global/safe-apps-provider @safe-global/safe-apps-sdk
```
The default Para SDK installation uses wagmi v2. These additional dependencies are only needed if you choose to upgrade to wagmi v3.
### Best Practices
1. **Use the Latest Versions**: Always use the latest versions of Next.js and Para SDK to ensure compatibility and
access to the latest features.
2. **Client-Side Rendering for Para Components**: Whenever possible, render Para components on the client-side to avoid
SSR-related issues.
3. **Error Boundary**: Implement error boundaries to gracefully handle any runtime errors related to Para integration.
4. **Environment-Specific Configurations**: Use Next.js environment configurations to manage different settings for
development and production environments.
By following these troubleshooting steps and best practices, you should be able to resolve most common issues when
integrating Para with your Next.js application.
# React with Vite
Source: https://docs.getpara.com/v3/react/troubleshooting/react-vite
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import DuplicateParaModalTroubleshooting from '/snippets/v3/duplicate-para-modal-troubleshooting.mdx';
Vite's approach to building React applications can introduce unique considerations when integrating Para. This guide
addresses frequent issues and offers tailored solutions to ensure a successful implementation.
Using an LLM (ChatGPT, Claude) or Coding Assistant (Cursor, Github Copilot)? Here are a few tips:
1) Include the for the most up-to-date help
2) Check out the for an interactive LLM using Para Examples Hub
## General Troubleshooting Steps
Before diving into specific issues, try these general troubleshooting steps:
```bash rm -rf node_modules npm cache clean --force ```
```bash npm install ```
```bash npm update @getpara/react-sdk ```
```bash npm run build ```
## Common Issues and Solutions
**Problem**: Vite doesn't include Node.js polyfills by default, which can cause issues with packages that depend on Node.js built-ins like `buffer` or `crypto`.
**Solution**: Add the necessary polyfills using the `vite-plugin-node-polyfills` plugin. Adjust the configuration as needed for your specific requirements:
1. Install the plugin:
```bash
npm install --save-dev vite-plugin-node-polyfills
```
2. Update your `vite.config.js`:
```javascript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { nodePolyfills } from "vite-plugin-node-polyfills";
export default defineConfig({
plugins: [
react(),
nodePolyfills({
include: ["buffer", "crypto", "stream", "util"],
}),
],
// ... other configurations
});
```
**Problem**: Environment variables not being recognized in your application.
**Solution**: Ensure you're prefixing your environment variables with `VITE_` and accessing them correctly:
1. In your `.env` file:
```
VITE_PARA_API_KEY=your_api_key_here
```
2. In your code:
```javascript
const para = new Para(import.meta.env.VITE_PARA_API_KEY);
```
**Problem**: Para's CSS files not loading correctly.
**Solution**: Import Para's CSS file in your main `App.jsx` or `index.jsx`:
```jsx
import "@getpara/react-sdk/styles.css";
function App() {
// Your app code
}
export default App;
```
**Problem**: After upgrading from wagmi v2 to v3, you encounter module resolution errors or missing dependency warnings.
**Solution**: Wagmi v3 requires additional peer dependencies that are not included in the default installation. Install them:
```bash
npm install porto @base-org/account @gemini-wallet/core @metamask/sdk @safe-global/safe-apps-provider @safe-global/safe-apps-sdk
```
The default Para SDK installation uses wagmi v2. These additional dependencies are only needed if you choose to upgrade to wagmi v3.
### Best Practices
1. **Use the Latest Versions**: Always use the latest versions of Vite, React, and Para SDK to ensure compatibility and
access to the latest features.
2. **Error Handling**: Implement error boundaries to gracefully handle any runtime errors related to Para integration.
3. **Development vs Production**: Use environment-specific configurations to manage different settings for development
and production builds. Para provides environment-specific API keys.
By following these troubleshooting steps and best practices, you should be able to resolve most common issues when
integrating Para with your React application using Vite.
# Svelte
Source: https://docs.getpara.com/v3/react/troubleshooting/svelte
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Using an LLM (ChatGPT, Claude) or Coding Assistant (Cursor, Github Copilot)? Here are a few tips:
1) Include the for the most up-to-date help
2) Check out the for an interactive LLM using Para Examples Hub
## General Troubleshooting Steps
Before diving into specific issues, try these general troubleshooting steps:
```bash rm -rf node_modules npm cache clean --force ```
```bash npm install ```
```bash npm update @getpara/react-sdk ```
```bash npm run build ```
Svelte applications require configuring some additional polyfills for svelte and react preprocessing. The following
example should cover these requirements.
## Adding Svelte and React + Vite Preprocessing
First, ensure `svelteKit` is installed and configured correctly within the project's `vite.config.ts` file.
```bash
npm i @sveltejs/kit
```
Then you'll need to add the appropriate preprocessing and adapter dependencies to the project's `svelte.config.ts` file.
```bash
npm i @sveltejs/vite-plugin-svelte @sveltejs/adapter-auto @svelte-preprocess @svelte-preprocess-react
```
Last, configure the `vite.config.js` and `svelte.config.js` project files to add preprocessing.
See the below code files for reference examples.
```javascript vite.config.js
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit()],
});
```
```javascript svelte.config.js
import adapter from "@sveltejs/adapter-auto";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
import preprocessReact from "svelte-preprocess-react/preprocessReact";
/**
* This will add autocompletion if you're working with SvelteKit
*
* @type {import('@sveltejs/kit').Config}
*/
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: [vitePreprocess(), preprocessReact()],
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter(),
},
};
export default config;
```
For more info, including CommonJS style configs, please see the
## Example
Explore our example implementation of SvelteKit and Svelte with Vite:
### Best Practices
1. **Use the Latest Versions**: Always use the latest versions of Svelte, React, and Para SDK to ensure compatibility
and access to the latest features.
2. **Error Handling**: Implement error boundaries to gracefully handle any runtime errors related to Para integration.
3. **Development vs Production**: Use environment-specific configurations to manage different settings for development
and production builds. Para provides environment-specific API keys.
By following these troubleshooting steps and best practices, you should be able to resolve most common issues when
integrating Para with your React application using Vite.
# TanStack Start Troubleshooting
Source: https://docs.getpara.com/v3/react/troubleshooting/tanstack-start
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import DuplicateParaModalTroubleshooting from '/snippets/v3/duplicate-para-modal-troubleshooting.mdx';
TanStack Start's server-side rendering (SSR) capabilities can introduce unique challenges when integrating Para SDK. This guide addresses frequent issues and offers tailored solutions to ensure a successful implementation.
Using an LLM (ChatGPT, Claude) or Coding Assistant (Cursor, Github Copilot)? Here are a few tips:
1) Include the for the most up-to-date help
2) Check out the for an interactive LLM using Para Examples Hub
## General Troubleshooting Steps
Before diving into specific issues, try these general troubleshooting steps:
```bash
rm -rf node_modules
npm cache clean --force
```
```bash
npm install
```
```bash
npm update @getpara/react-sdk
```
```bash
npm run build
```
## Common Issues and Solutions
**Problem**: Para API key environment variables not being recognized in your application.
**Solution**: Ensure you're using the correct prefix for environment variables in TanStack Start and accessing them properly:
1. In your `.env` file:
```
VITE_PARA_API_KEY=your_api_key_here
VITE_PARA_ENVIRONMENT=BETA
```
2. In your constants file:
```javascript
// src/constants.ts
export const API_KEY = import.meta.env.VITE_PARA_API_KEY || "";
export const ENVIRONMENT = import.meta.env.VITE_PARA_ENVIRONMENT || "BETA";
```
3. When using these in your ParaProvider:
```jsx
{children}
```
**Problem**: Missing or improperly configured Node.js polyfills causing errors like `crypto is not defined` or similar issues.
**Solution**: Configure the polyfills specifically for the client bundle only:
1. Install the plugin:
```bash
npm install --save-dev vite-plugin-node-polyfills
```
2. Update your `app.config.ts` to apply polyfills only to the client bundle:
```typescript
import { defineConfig } from "@tanstack/react-start/config";
import tsConfigPaths from "vite-tsconfig-paths";
import { nodePolyfills } from "vite-plugin-node-polyfills";
export default defineConfig({
tsr: { appDirectory: "src" },
// Base configuration (applied to both client and server)
vite: {
plugins: [tsConfigPaths({ projects: ["./tsconfig.json"] })],
define: {
// This helps modules determine the execution environment
"process.browser": true,
},
},
// Client-specific configuration
routers: {
client: {
vite: {
// Apply node polyfills ONLY on the client side
plugins: [nodePolyfills()],
},
},
},
});
```
**Important**: Do NOT configure nodePolyfills in the top-level vite plugins array, as this will apply the polyfills to the server bundle and can cause conflicts.
**Problem**: Browser-specific modules not being resolved correctly, leading to missing APIs or incorrect environment detection.
**Solution**: Add the `process.browser` definition to help modules determine the execution environment:
```typescript
// app.config.ts
export default defineConfig({
vite: {
define: {
"process.browser": true,
},
// other configurations...
},
// other configurations...
});
```
This setting is critical for ensuring that browser-specific code paths are correctly resolved during bundling.
**Problem**: Errors like `styled.div is not a function` or `Cannot read properties of undefined (reading 'div')` during server rendering.
**Solution**: Ensure Para components are only rendered on the client side by using both `React.lazy` for dynamic imports and `ClientOnly` from TanStack Router:
```jsx
import React from "react";
import { ClientOnly } from "@tanstack/react-router";
// Lazy load Para component to prevent server-side evaluation
const LazyParaProvider = React.lazy(() =>
import("@getpara/react-sdk").then((mod) => ({
default: mod.ParaProvider
}))
);
export function Providers({ children }) {
return (
{children}
);
}
```
The combination of `React.lazy` and `ClientOnly` ensures that Para components are not only rendered on the client side but also that the modules are not evaluated on the server.
**Problem**: Even with `ClientOnly`, you're still seeing styled-components errors during server rendering.
**Solution**: Module-level evaluation can still happen on the server even if the component isn't rendered. Make sure all Para imports are lazy loaded:
1. Don't import directly from Para SDK at the module level:
```jsx
// AVOID this at the top level:
import { useModal } from "@getpara/react-sdk";
```
2. Instead, create wrapper components for all Para components:
```jsx
// Create a component file like ParaContainer.tsx
export function ParaContainer() {
// Import and use Para components here
const { openModal } = useModal();
return (
<>
openModal()}>Open Para Modal
>
);
}
```
3. Then lazy load these wrapper components:
```jsx
const LazyParaContainer = React.lazy(() =>
import("~/components/ParaContainer").then((mod) => ({
default: mod.ParaContainer,
}))
);
function HomePage() {
return (
Loading...}>
);
}
```
**Problem**: The Para modal appears transparent or without proper styling.
**Solution**: Import Para's CSS file in your component:
```jsx
// In your main component or route file:
import "@getpara/react-sdk/styles.css";
// Then use Para components as usual
```
Ensure this import is included in the component that uses Para components or in a parent component that wraps them.
**Problem**: After upgrading from wagmi v2 to v3, you encounter module resolution errors or missing dependency warnings.
**Solution**: Wagmi v3 requires additional peer dependencies that are not included in the default installation. Install them:
```bash
npm install porto @base-org/account @gemini-wallet/core @metamask/sdk @safe-global/safe-apps-provider @safe-global/safe-apps-sdk
```
The default Para SDK installation uses wagmi v2. These additional dependencies are only needed if you choose to upgrade to wagmi v3.
## Best Practices for TanStack Start Integration
1. **Client-Side Only Rendering**: Always use both `React.lazy` and `ClientOnly` for Para components to avoid SSR issues.
2. **Polyfill Strategy**: Configure node polyfills only for the client bundle using the `routers.client.vite.plugins` config.
3. **Environment Awareness**: Set `process.browser` to true to help modules determine the execution environment.
4. **Component Boundaries**: Clearly define boundaries between server and client components to prevent hydration mismatches.
5. **Error Handling**: Implement error boundaries to gracefully handle any runtime errors related to Para integration.
6. **Development vs Production**: Use environment-specific configurations to manage different settings for development and production builds.
By following these troubleshooting steps and best practices, you should be able to resolve most common issues when integrating Para with your TanStack Start application.
# Vue.js Troubleshooting
Source: https://docs.getpara.com/v3/react/troubleshooting/vue
import { Link } from '/snippets/v3/components/ui/link.mdx';
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
Integrating Para SDK with Vue.js applications can present unique challenges. This guide addresses common issues and provides effective solutions to ensure smooth integration.
Using an LLM (ChatGPT, Claude) or Coding Assistant (Cursor, Github Copilot)? Here are a few tips:
1) Include the for the most up-to-date help
2) Check out the for an interactive LLM using Para Examples Hub
## General Troubleshooting Steps
Before diving into specific issues, try these general troubleshooting steps:
```bash
rm -rf node_modules
npm cache clean --force
```
```bash
npm install
```
```bash
npm update @getpara/react-sdk
```
```bash
npm run build
```
## Common Issues and Solutions
**Problem**: Para SDK is built with React, which requires special configuration to work in Vue applications.
**Solution**: Use a React-in-Vue wrapper library to integrate Para components:
1. Install the necessary packages:
```bash
npm install veaury react react-dom
```
2. Create a wrapper component for Para:
```javascript
// ParaWrapper.js
import { applyPureReactInVue } from 'veaury';
import { ParaProvider } from '@getpara/react-sdk';
export const VueParaProvider = applyPureReactInVue(ParaProvider);
```
3. Use in your Vue component:
```vue
```
`ParaProvider` includes its own modal. Do not also wrap and render `ParaModal` unless you set `disableEmbeddedModal: true` in the provider config.
**Problem**: Environment variables not being recognized in your Vue application.
**Solution**: Ensure you're using the correct prefix based on your build tool:
For Vite-based Vue projects:
```
VITE_PARA_API_KEY=your_api_key_here
```
Access in your code:
```javascript
const apiKey = import.meta.env.VITE_PARA_API_KEY;
```
For Vue CLI projects:
```
VUE_APP_PARA_API_KEY=your_api_key_here
```
Access in your code:
```javascript
const apiKey = process.env.VUE_APP_PARA_API_KEY;
```
**Problem**: Para's CSS styles not loading correctly.
**Solution**: Import Para's CSS file in your main entry point:
```javascript
// main.js or main.ts
import '@getpara/react-sdk/styles.css';
import { createApp } from 'vue';
import App from './App.vue';
createApp(App).mount('#app');
```
**Problem**: Build errors due to missing polyfills or module resolution issues.
**Solution**: Configure your build tool appropriately:
For Vite:
```javascript
// vite.config.js
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
export default defineConfig({
plugins: [
vue(),
nodePolyfills({
include: ['buffer', 'crypto', 'stream', 'util']
})
],
optimizeDeps: {
include: ['@getpara/react-sdk', 'react', 'react-dom']
}
});
```
## Best Practices
1. **Component Isolation**: Keep Para components isolated in wrapper components to manage the React-Vue boundary effectively.
2. **State Management**: Consider using a shared state management solution if you need to sync data between Vue and Para components.
3. **Error Boundaries**: Implement error handling to gracefully manage any runtime errors from the React components.
4. **Performance**: Lazy load Para components to reduce initial bundle size and improve application startup time.
By following these troubleshooting steps and best practices, you should be able to successfully integrate Para SDK with your Vue.js application.
# addCredential
Source: https://docs.getpara.com/v3/references/core/addcredential
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AddCredential from '/snippets/v3/definitions/core/addCredential.mdx';
# claimPregenWallets
Source: https://docs.getpara.com/v3/references/core/claimpregenwallets
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ClaimPregenWallets from '/snippets/v3/definitions/core/claimPregenWallets.mdx';
# clearStorage
Source: https://docs.getpara.com/v3/references/core/clearstorage
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ClearStorage from '/snippets/v3/definitions/core/clearStorage.mdx';
# createGuestWallets
Source: https://docs.getpara.com/v3/references/core/createguestwallets
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import CreateGuestWallets from '/snippets/v3/definitions/core/createGuestWallets.mdx';
# createPregenWallet
Source: https://docs.getpara.com/v3/references/core/createpregenwallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import CreatePregenWallet from '/snippets/v3/definitions/core/createPregenWallet.mdx';
# createPregenWalletPerType
Source: https://docs.getpara.com/v3/references/core/createpregenwalletpertype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import CreatePregenWalletPerType from '/snippets/v3/definitions/core/createPregenWalletPerType.mdx';
# createWallet
Source: https://docs.getpara.com/v3/references/core/createwallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import CreateWallet from '/snippets/v3/definitions/core/createWallet.mdx';
# createWalletPerType
Source: https://docs.getpara.com/v3/references/core/createwalletpertype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import CreateWalletPerType from '/snippets/v3/definitions/core/createWalletPerType.mdx';
# distributeNewWalletShare
Source: https://docs.getpara.com/v3/references/core/distributenewwalletshare
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import DistributeNewWalletShare from '/snippets/v3/definitions/core/distributeNewWalletShare.mdx';
# enable2fa
Source: https://docs.getpara.com/v3/references/core/enable2fa
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Enable2fa from '/snippets/v3/definitions/core/enable2fa.mdx';
# exportSession
Source: https://docs.getpara.com/v3/references/core/exportsession
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ExportSession from '/snippets/v3/definitions/core/exportSession.mdx';
# fetchWallets
Source: https://docs.getpara.com/v3/references/core/fetchwallets
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import FetchWallets from '/snippets/v3/definitions/core/fetchWallets.mdx';
# getAuthInfo
Source: https://docs.getpara.com/v3/references/core/getauthinfo
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetAuthInfo from '/snippets/v3/definitions/core/getAuthInfo.mdx';
# getFarcasterConnectUri
Source: https://docs.getpara.com/v3/references/core/getfarcasterconnecturi
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetFarcasterConnectUri from '/snippets/v3/definitions/core/getFarcasterConnectUri.mdx';
# getLinkedAccounts
Source: https://docs.getpara.com/v3/references/core/getlinkedaccounts
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetLinkedAccounts from '/snippets/v3/definitions/core/getLinkedAccounts.mdx';
# getOAuthUrl
Source: https://docs.getpara.com/v3/references/core/getoauthurl
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetOAuthUrl from '/snippets/v3/definitions/core/getOAuthUrl.mdx';
# getPregenWallets
Source: https://docs.getpara.com/v3/references/core/getpregenwallets
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetPregenWallets from '/snippets/v3/definitions/core/getPregenWallets.mdx';
# getUserShare
Source: https://docs.getpara.com/v3/references/core/getusershare
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetUserShare from '/snippets/v3/definitions/core/getUserShare.mdx';
# getVerificationToken
Source: https://docs.getpara.com/v3/references/core/getverificationtoken
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetVerificationToken from '/snippets/v3/definitions/core/getVerificationToken.mdx';
# getWalletBalance
Source: https://docs.getpara.com/v3/references/core/getwalletbalance
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetWalletBalance from '/snippets/v3/definitions/core/getWalletBalance.mdx';
# getWallets
Source: https://docs.getpara.com/v3/references/core/getwallets
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetWallets from '/snippets/v3/definitions/core/getWallets.mdx';
# getWalletsByType
Source: https://docs.getpara.com/v3/references/core/getwalletsbytype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetWalletsByType from '/snippets/v3/definitions/core/getWalletsByType.mdx';
# hasPregenWallet
Source: https://docs.getpara.com/v3/references/core/haspregenwallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import HasPregenWallet from '/snippets/v3/definitions/core/hasPregenWallet.mdx';
# importSession
Source: https://docs.getpara.com/v3/references/core/importsession
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ImportSession from '/snippets/v3/definitions/core/importSession.mdx';
# initiateOnRampTransaction
Source: https://docs.getpara.com/v3/references/core/initiateonramptransaction
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import InitiateOnRampTransaction from '/snippets/v3/definitions/core/initiateOnRampTransaction.mdx';
# isFullyLoggedIn
Source: https://docs.getpara.com/v3/references/core/isfullyloggedin
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import IsFullyLoggedIn from '/snippets/v3/definitions/core/isFullyLoggedIn.mdx';
# isSessionActive
Source: https://docs.getpara.com/v3/references/core/issessionactive
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import IsSessionActive from '/snippets/v3/definitions/core/isSessionActive.mdx';
# issueJwt
Source: https://docs.getpara.com/v3/references/core/issuejwt
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import IssueJwt from '/snippets/v3/definitions/core/issueJwt.mdx';
# keepSessionAlive
Source: https://docs.getpara.com/v3/references/core/keepsessionalive
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import KeepSessionAlive from '/snippets/v3/definitions/core/keepSessionAlive.mdx';
# loginExternalWallet
Source: https://docs.getpara.com/v3/references/core/loginexternalwallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import LoginExternalWallet from '/snippets/v3/definitions/core/loginExternalWallet.mdx';
# logout
Source: https://docs.getpara.com/v3/references/core/logout
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Logout from '/snippets/v3/definitions/core/logout.mdx';
# refreshSession
Source: https://docs.getpara.com/v3/references/core/refreshsession
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import RefreshSession from '/snippets/v3/definitions/core/refreshSession.mdx';
# refreshShare
Source: https://docs.getpara.com/v3/references/core/refreshshare
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import RefreshShare from '/snippets/v3/definitions/core/refreshShare.mdx';
# resendVerificationCode
Source: https://docs.getpara.com/v3/references/core/resendverificationcode
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ResendVerificationCode from '/snippets/v3/definitions/core/resendVerificationCode.mdx';
# setup2fa
Source: https://docs.getpara.com/v3/references/core/setup2fa
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Setup2fa from '/snippets/v3/definitions/core/setup2fa.mdx';
# setUserShare
Source: https://docs.getpara.com/v3/references/core/setusershare
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import SetUserShare from '/snippets/v3/definitions/core/setUserShare.mdx';
# signMessage
Source: https://docs.getpara.com/v3/references/core/signmessage
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import SignMessage from '/snippets/v3/definitions/core/signMessage.mdx';
# signTransaction
Source: https://docs.getpara.com/v3/references/core/signtransaction
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import SignTransaction from '/snippets/v3/definitions/core/signTransaction.mdx';
# signUpOrLogIn
Source: https://docs.getpara.com/v3/references/core/signuporlogin
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import SignUpOrLogIn from '/snippets/v3/definitions/core/signUpOrLogIn.mdx';
# updatePregenWalletIdentifier
Source: https://docs.getpara.com/v3/references/core/updatepregenwalletidentifier
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UpdatePregenWalletIdentifier from '/snippets/v3/definitions/core/updatePregenWalletIdentifier.mdx';
# verify2fa
Source: https://docs.getpara.com/v3/references/core/verify2fa
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Verify2fa from '/snippets/v3/definitions/core/verify2fa.mdx';
# verifyExternalWallet
Source: https://docs.getpara.com/v3/references/core/verifyexternalwallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import VerifyExternalWallet from '/snippets/v3/definitions/core/verifyExternalWallet.mdx';
# verifyFarcaster
Source: https://docs.getpara.com/v3/references/core/verifyfarcaster
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import VerifyFarcaster from '/snippets/v3/definitions/core/verifyFarcaster.mdx';
# verifyNewAccount
Source: https://docs.getpara.com/v3/references/core/verifynewaccount
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import VerifyNewAccount from '/snippets/v3/definitions/core/verifyNewAccount.mdx';
# verifyOAuth
Source: https://docs.getpara.com/v3/references/core/verifyoauth
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import VerifyOAuth from '/snippets/v3/definitions/core/verifyOAuth.mdx';
# verifyTelegram
Source: https://docs.getpara.com/v3/references/core/verifytelegram
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import VerifyTelegram from '/snippets/v3/definitions/core/verifyTelegram.mdx';
# waitForLogin
Source: https://docs.getpara.com/v3/references/core/waitforlogin
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import WaitForLogin from '/snippets/v3/definitions/core/waitForLogin.mdx';
# waitForSignup
Source: https://docs.getpara.com/v3/references/core/waitforsignup
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import WaitForSignup from '/snippets/v3/definitions/core/waitForSignup.mdx';
# waitForWalletCreation
Source: https://docs.getpara.com/v3/references/core/waitforwalletcreation
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import WaitForWalletCreation from '/snippets/v3/definitions/core/waitForWalletCreation.mdx';
# ParaProvider
Source: https://docs.getpara.com/v3/references/hooks/ParaProvider
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ParaProvider from '/snippets/v3/definitions/hooks/ParaProvider.mdx';
# useAccount
Source: https://docs.getpara.com/v3/references/hooks/useAccount
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseAccount from '/snippets/v3/definitions/hooks/useAccount.mdx';
# useAccountLinkInProgress
Source: https://docs.getpara.com/v3/references/hooks/useAccountLinkInProgress
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseAccountLinkInProgress from '/snippets/v3/definitions/hooks/useAccountLinkInProgress.mdx';
# useAddAuthMethod
Source: https://docs.getpara.com/v3/references/hooks/useAddAuthMethod
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseAddAuthMethod from '/snippets/v3/definitions/hooks/useAddAuthMethod.mdx';
# useClaimPregenWallets
Source: https://docs.getpara.com/v3/references/hooks/useClaimPregenWallets
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseClaimPregenWallets from '/snippets/v3/definitions/hooks/useClaimPregenWallets.mdx';
# useClient
Source: https://docs.getpara.com/v3/references/hooks/useClient
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseClient from '/snippets/v3/definitions/hooks/useClient.mdx';
# useCreateGuestWallets
Source: https://docs.getpara.com/v3/references/hooks/useCreateGuestWallets
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseCreateGuestWallets from '/snippets/v3/definitions/hooks/useCreateGuestWallets.mdx';
# useCreatePregenWallet
Source: https://docs.getpara.com/v3/references/hooks/useCreatePregenWallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseCreatePregenWallet from '/snippets/v3/definitions/hooks/useCreatePregenWallet.mdx';
# useCreatePregenWalletPerType
Source: https://docs.getpara.com/v3/references/hooks/useCreatePregenWalletPerType
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseCreatePregenWalletPerType from '/snippets/v3/definitions/hooks/useCreatePregenWalletPerType.mdx';
# useCreateWallet
Source: https://docs.getpara.com/v3/references/hooks/useCreateWallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseCreateWallet from '/snippets/v3/definitions/hooks/useCreateWallet.mdx';
# useCreateWalletPerType
Source: https://docs.getpara.com/v3/references/hooks/useCreateWalletPerType
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseCreateWalletPerType from '/snippets/v3/definitions/hooks/useCreateWalletPerType.mdx';
# useEnable2fa
Source: https://docs.getpara.com/v3/references/hooks/useEnable2fa
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseEnable2fa from '/snippets/v3/definitions/hooks/useEnable2fa.mdx';
# useHasPregenWallet
Source: https://docs.getpara.com/v3/references/hooks/useHasPregenWallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseHasPregenWallet from '/snippets/v3/definitions/hooks/useHasPregenWallet.mdx';
# useIsFullyLoggedIn
Source: https://docs.getpara.com/v3/references/hooks/useIsFullyLoggedIn
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseIsFullyLoggedIn from '/snippets/v3/definitions/hooks/useIsFullyLoggedIn.mdx';
# useIssueJwt
Source: https://docs.getpara.com/v3/references/hooks/useIssueJwt
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseIssueJwt from '/snippets/v3/definitions/hooks/useIssueJwt.mdx';
# useKeepSessionAlive
Source: https://docs.getpara.com/v3/references/hooks/useKeepSessionAlive
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseKeepSessionAlive from '/snippets/v3/definitions/hooks/useKeepSessionAlive.mdx';
# useLinkAccount
Source: https://docs.getpara.com/v3/references/hooks/useLinkAccount
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseLinkAccount from '/snippets/v3/definitions/hooks/useLinkAccount.mdx';
# useLinkedAccounts
Source: https://docs.getpara.com/v3/references/hooks/useLinkedAccounts
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseLinkedAccounts from '/snippets/v3/definitions/hooks/useLinkedAccounts.mdx';
# useLoginExternalWallet
Source: https://docs.getpara.com/v3/references/hooks/useLoginExternalWallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseLoginExternalWallet from '/snippets/v3/definitions/hooks/useLoginExternalWallet.mdx';
# useLogout
Source: https://docs.getpara.com/v3/references/hooks/useLogout
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseLogout from '/snippets/v3/definitions/hooks/useLogout.mdx';
# useModal
Source: https://docs.getpara.com/v3/references/hooks/useModal
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseModal from '/snippets/v3/definitions/hooks/useModal.mdx';
# useParaCosmjsAminoSigner
Source: https://docs.getpara.com/v3/references/hooks/useParaCosmjsAminoSigner
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseParaCosmjsAminoSigner from '/snippets/v3/definitions/hooks/useParaCosmjsAminoSigner.mdx';
# useParaCosmjsProtoSigner
Source: https://docs.getpara.com/v3/references/hooks/useParaCosmjsProtoSigner
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseParaCosmjsProtoSigner from '/snippets/v3/definitions/hooks/useParaCosmjsProtoSigner.mdx';
# useParaEthersSigner
Source: https://docs.getpara.com/v3/references/hooks/useParaEthersSigner
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseParaEthersSigner from '/snippets/v3/definitions/hooks/useParaEthersSigner.mdx';
# useParaSolanaSigner
Source: https://docs.getpara.com/v3/references/hooks/useParaSolanaSigner
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseParaSolanaSigner from '/snippets/v3/definitions/hooks/useParaSolanaSigner.mdx';
# useParaStatus
Source: https://docs.getpara.com/v3/references/hooks/useParaStatus
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseParaStatus from '/snippets/v3/definitions/hooks/useParaStatus.mdx';
# useParaStellarSigner
Source: https://docs.getpara.com/v3/references/hooks/useParaStellarSigner
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseParaStellarSigner from '/snippets/v3/definitions/hooks/useParaStellarSigner.mdx';
# useParaViemAccount
Source: https://docs.getpara.com/v3/references/hooks/useParaViemAccount
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseParaViemAccount from '/snippets/v3/definitions/hooks/useParaViemAccount.mdx';
# useParaViemClient
Source: https://docs.getpara.com/v3/references/hooks/useParaViemClient
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseParaViemClient from '/snippets/v3/definitions/hooks/useParaViemClient.mdx';
# useResendVerificationCode
Source: https://docs.getpara.com/v3/references/hooks/useResendVerificationCode
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseResendVerificationCode from '/snippets/v3/definitions/hooks/useResendVerificationCode.mdx';
# useSetup2fa
Source: https://docs.getpara.com/v3/references/hooks/useSetup2fa
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseSetup2fa from '/snippets/v3/definitions/hooks/useSetup2fa.mdx';
# useSignMessage
Source: https://docs.getpara.com/v3/references/hooks/useSignMessage
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseSignMessage from '/snippets/v3/definitions/hooks/useSignMessage.mdx';
# useSignTransaction
Source: https://docs.getpara.com/v3/references/hooks/useSignTransaction
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseSignTransaction from '/snippets/v3/definitions/hooks/useSignTransaction.mdx';
# useSignUpOrLogIn
Source: https://docs.getpara.com/v3/references/hooks/useSignUpOrLogIn
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseSignUpOrLogIn from '/snippets/v3/definitions/hooks/useSignUpOrLogIn.mdx';
# useStellarSigner
Source: https://docs.getpara.com/v3/references/hooks/useStellarSigner
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseStellarSigner from '/snippets/v3/definitions/hooks/useStellarSigner.mdx';
# useUpdatePregenWalletIdentifier
Source: https://docs.getpara.com/v3/references/hooks/useUpdatePregenWalletIdentifier
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseUpdatePregenWalletIdentifier from '/snippets/v3/definitions/hooks/useUpdatePregenWalletIdentifier.mdx';
# useVerify2fa
Source: https://docs.getpara.com/v3/references/hooks/useVerify2fa
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseVerify2fa from '/snippets/v3/definitions/hooks/useVerify2fa.mdx';
# useVerifyExternalWallet
Source: https://docs.getpara.com/v3/references/hooks/useVerifyExternalWallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseVerifyExternalWallet from '/snippets/v3/definitions/hooks/useVerifyExternalWallet.mdx';
# useVerifyFarcaster
Source: https://docs.getpara.com/v3/references/hooks/useVerifyFarcaster
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseVerifyFarcaster from '/snippets/v3/definitions/hooks/useVerifyFarcaster.mdx';
# useVerifyNewAccount
Source: https://docs.getpara.com/v3/references/hooks/useVerifyNewAccount
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseVerifyNewAccount from '/snippets/v3/definitions/hooks/useVerifyNewAccount.mdx';
# useVerifyOAuth
Source: https://docs.getpara.com/v3/references/hooks/useVerifyOAuth
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseVerifyOAuth from '/snippets/v3/definitions/hooks/useVerifyOAuth.mdx';
# useVerifyTelegram
Source: https://docs.getpara.com/v3/references/hooks/useVerifyTelegram
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseVerifyTelegram from '/snippets/v3/definitions/hooks/useVerifyTelegram.mdx';
# useWaitForLogin
Source: https://docs.getpara.com/v3/references/hooks/useWaitForLogin
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseWaitForLogin from '/snippets/v3/definitions/hooks/useWaitForLogin.mdx';
# useWaitForSignup
Source: https://docs.getpara.com/v3/references/hooks/useWaitForSignup
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseWaitForSignup from '/snippets/v3/definitions/hooks/useWaitForSignup.mdx';
# useWaitForWalletCreation
Source: https://docs.getpara.com/v3/references/hooks/useWaitForWalletCreation
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseWaitForWalletCreation from '/snippets/v3/definitions/hooks/useWaitForWalletCreation.mdx';
# useWallet
Source: https://docs.getpara.com/v3/references/hooks/useWallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseWallet from '/snippets/v3/definitions/hooks/useWallet.mdx';
# useWalletBalance
Source: https://docs.getpara.com/v3/references/hooks/useWalletBalance
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseWalletBalance from '/snippets/v3/definitions/hooks/useWalletBalance.mdx';
# useWalletState
Source: https://docs.getpara.com/v3/references/hooks/useWalletState
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import UseWalletState from '/snippets/v3/definitions/hooks/useWalletState.mdx';
# API Reference
Source: https://docs.getpara.com/v3/references/overview
## What You'll Find Here
This section contains comprehensive API reference documentation for the Para SDK. It's organized into three main categories to help you quickly find what you need:
### Core Methods
Direct SDK methods for authentication, wallet management, and blockchain operations. These are the fundamental building blocks for integrating Para into any JavaScript/TypeScript application.
### React Hooks
React-specific hooks that provide a declarative interface for Para functionality. These hooks handle state management, caching, and lifecycle concerns automatically, making it easy to build React applications with Para.
### Types & Interfaces
TypeScript type definitions and interfaces used throughout the SDK. Understanding these types will help you write type-safe code and better understand the data structures returned by Para methods.
## Quick Navigation Tips
- All items within each category are organized alphabetically for easy discovery
- Each reference includes detailed parameter descriptions, return types, and usage examples
- Type definitions link to their corresponding documentation for deeper understanding
- Hook documentation shows both TypeScript interfaces and practical implementation patterns
## Need Help?
If you're just getting started, we recommend checking out the [quickstart](/v3/react/quickstart) guide first to understand the basics before diving into the API reference.
For implementation examples and best practices, visit our [Examples](/v3/walkthroughs/overview) section.
# AccountLinkError
Source: https://docs.getpara.com/v3/references/types/accountlinkerror
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AccountLinkError from '/snippets/v3/definitions/types/AccountLinkError.mdx';
# AccountLinkInProgress
Source: https://docs.getpara.com/v3/references/types/accountlinkinprogress
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AccountLinkInProgress from '/snippets/v3/definitions/types/AccountLinkInProgress.mdx';
# AuthMethod
Source: https://docs.getpara.com/v3/references/types/authmethod
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AuthMethod from '/snippets/v3/definitions/types/AuthMethod.mdx';
# AuthState
Source: https://docs.getpara.com/v3/references/types/authstate
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AuthState from '/snippets/v3/definitions/types/AuthState.mdx';
# AuthStateLogin
Source: https://docs.getpara.com/v3/references/types/authstatelogin
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AuthStateLogin from '/snippets/v3/definitions/types/AuthStateLogin.mdx';
# AuthStateSignup
Source: https://docs.getpara.com/v3/references/types/authstatesignup
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AuthStateSignup from '/snippets/v3/definitions/types/AuthStateSignup.mdx';
# AuthStateSignupOrLogin
Source: https://docs.getpara.com/v3/references/types/authstatesignuporlogin
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AuthStateSignupOrLogin from '/snippets/v3/definitions/types/AuthStateSignupOrLogin.mdx';
# AuthStateVerify
Source: https://docs.getpara.com/v3/references/types/authstateverify
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AuthStateVerify from '/snippets/v3/definitions/types/AuthStateVerify.mdx';
# AuthStateVerifyOrLogin
Source: https://docs.getpara.com/v3/references/types/authstateverifyorlogin
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AuthStateVerifyOrLogin from '/snippets/v3/definitions/types/AuthStateVerifyOrLogin.mdx';
# AuthType
Source: https://docs.getpara.com/v3/references/types/authtype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import AuthType from '/snippets/v3/definitions/types/AuthType.mdx';
# BackupKitEmailProps
Source: https://docs.getpara.com/v3/references/types/backupkitemailprops
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import BackupKitEmailProps from '/snippets/v3/definitions/types/BackupKitEmailProps.mdx';
# BorderRadius
Source: https://docs.getpara.com/v3/references/types/borderradius
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import BorderRadius from '/snippets/v3/definitions/types/BorderRadius.mdx';
# Callbacks
Source: https://docs.getpara.com/v3/references/types/callbacks
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Callbacks from '/snippets/v3/definitions/types/Callbacks.mdx';
# ConstructorOpts
Source: https://docs.getpara.com/v3/references/types/constructoropts
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ConstructorOpts from '/snippets/v3/definitions/types/ConstructorOpts.mdx';
# CoreAuthInfo
Source: https://docs.getpara.com/v3/references/types/coreauthinfo
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import CoreAuthInfo from '/snippets/v3/definitions/types/CoreAuthInfo.mdx';
# Ctx
Source: https://docs.getpara.com/v3/references/types/ctx
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Ctx from '/snippets/v3/definitions/types/Ctx.mdx';
# CurrentWalletIds
Source: https://docs.getpara.com/v3/references/types/currentwalletids
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import CurrentWalletIds from '/snippets/v3/definitions/types/CurrentWalletIds.mdx';
# EmailTheme
Source: https://docs.getpara.com/v3/references/types/emailtheme
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import EmailTheme from '/snippets/v3/definitions/types/EmailTheme.mdx';
# EmbeddedWalletType
Source: https://docs.getpara.com/v3/references/types/embeddedwallettype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import EmbeddedWalletType from '/snippets/v3/definitions/types/EmbeddedWalletType.mdx';
# EnabledFlow
Source: https://docs.getpara.com/v3/references/types/enabledflow
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import EnabledFlow from '/snippets/v3/definitions/types/EnabledFlow.mdx';
# Environment
Source: https://docs.getpara.com/v3/references/types/environment
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Environment from '/snippets/v3/definitions/types/Environment.mdx';
# ExternalWalletConfig
Source: https://docs.getpara.com/v3/references/types/externalwalletconfig
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ExternalWalletConfig from '/snippets/v3/definitions/types/ExternalWalletConfig.mdx';
# ExternalWalletInfo
Source: https://docs.getpara.com/v3/references/types/externalwalletinfo
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ExternalWalletInfo from '/snippets/v3/definitions/types/ExternalWalletInfo.mdx';
# ExternalWalletType
Source: https://docs.getpara.com/v3/references/types/externalwallettype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ExternalWalletType from '/snippets/v3/definitions/types/ExternalWalletType.mdx';
# FullSignatureRes
Source: https://docs.getpara.com/v3/references/types/fullsignatureres
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import FullSignatureRes from '/snippets/v3/definitions/types/FullSignatureRes.mdx';
# GetWalletBalanceResponse
Source: https://docs.getpara.com/v3/references/types/getwalletbalanceresponse
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import GetWalletBalanceResponse from '/snippets/v3/definitions/types/GetWalletBalanceResponse.mdx';
# IssueJwtResponse
Source: https://docs.getpara.com/v3/references/types/issuejwtresponse
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import IssueJwtResponse from '/snippets/v3/definitions/types/IssueJwtResponse.mdx';
# LinkedAccounts
Source: https://docs.getpara.com/v3/references/types/linkedaccounts
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import LinkedAccounts from '/snippets/v3/definitions/types/LinkedAccounts.mdx';
# Network
Source: https://docs.getpara.com/v3/references/types/network
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Network from '/snippets/v3/definitions/types/Network.mdx';
# OAuthResponse
Source: https://docs.getpara.com/v3/references/types/oauthresponse
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import OAuthResponse from '/snippets/v3/definitions/types/OAuthResponse.mdx';
# OnRampAsset
Source: https://docs.getpara.com/v3/references/types/onrampasset
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import OnRampAsset from '/snippets/v3/definitions/types/OnRampAsset.mdx';
# OnRampProvider
Source: https://docs.getpara.com/v3/references/types/onrampprovider
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import OnRampProvider from '/snippets/v3/definitions/types/OnRampProvider.mdx';
# OnRampPurchase
Source: https://docs.getpara.com/v3/references/types/onramppurchase
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import OnRampPurchase from '/snippets/v3/definitions/types/OnRampPurchase.mdx';
# OnRampPurchaseCreateParams
Source: https://docs.getpara.com/v3/references/types/onramppurchasecreateparams
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import OnRampPurchaseCreateParams from '/snippets/v3/definitions/types/OnRampPurchaseCreateParams.mdx';
# OnRampPurchaseStatus
Source: https://docs.getpara.com/v3/references/types/onramppurchasestatus
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import OnRampPurchaseStatus from '/snippets/v3/definitions/types/OnRampPurchaseStatus.mdx';
# OnRampPurchaseType
Source: https://docs.getpara.com/v3/references/types/onramppurchasetype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import OnRampPurchaseType from '/snippets/v3/definitions/types/OnRampPurchaseType.mdx';
# ParaEvent
Source: https://docs.getpara.com/v3/references/types/paraevent
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ParaEvent from '/snippets/v3/definitions/types/ParaEvent.mdx';
# ParaModalProps
Source: https://docs.getpara.com/v3/references/types/paramodalprops
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ParaModalProps from '/snippets/v3/definitions/types/ParaModalProps.mdx';
# ParaProviderConfig
Source: https://docs.getpara.com/v3/references/types/paraproviderconfig
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import ParaProviderConfig from '/snippets/v3/definitions/types/ParaProviderConfig.mdx';
# PartnerEntity
Source: https://docs.getpara.com/v3/references/types/partnerentity
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import PartnerEntity from '/snippets/v3/definitions/types/PartnerEntity.mdx';
# PollParams
Source: https://docs.getpara.com/v3/references/types/pollparams
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import PollParams from '/snippets/v3/definitions/types/PollParams.mdx';
# PregenAuth
Source: https://docs.getpara.com/v3/references/types/pregenauth
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import PregenAuth from '/snippets/v3/definitions/types/PregenAuth.mdx';
# Setup2faResponse
Source: https://docs.getpara.com/v3/references/types/setup2faresponse
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Setup2faResponse from '/snippets/v3/definitions/types/Setup2faResponse.mdx';
# StorageType
Source: https://docs.getpara.com/v3/references/types/storagetype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import StorageType from '/snippets/v3/definitions/types/StorageType.mdx';
# SuccessfulSignatureRes
Source: https://docs.getpara.com/v3/references/types/successfulsignatureres
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import SuccessfulSignatureRes from '/snippets/v3/definitions/types/SuccessfulSignatureRes.mdx';
# SupportedAccountLinks
Source: https://docs.getpara.com/v3/references/types/supportedaccountlinks
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import SupportedAccountLinks from '/snippets/v3/definitions/types/SupportedAccountLinks.mdx';
# SupportedWalletTypes
Source: https://docs.getpara.com/v3/references/types/supportedwallettypes
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import SupportedWalletTypes from '/snippets/v3/definitions/types/SupportedWalletTypes.mdx';
# TelegramAuthResponse
Source: https://docs.getpara.com/v3/references/types/telegramauthresponse
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import TelegramAuthResponse from '/snippets/v3/definitions/types/TelegramAuthResponse.mdx';
# TExternalWallet
Source: https://docs.getpara.com/v3/references/types/texternalwallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import TExternalWallet from '/snippets/v3/definitions/types/TExternalWallet.mdx';
# Theme
Source: https://docs.getpara.com/v3/references/types/theme
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Theme from '/snippets/v3/definitions/types/Theme.mdx';
# TLinkedAccountType
Source: https://docs.getpara.com/v3/references/types/tlinkedaccounttype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import TLinkedAccountType from '/snippets/v3/definitions/types/TLinkedAccountType.mdx';
# TOAuthMethod
Source: https://docs.getpara.com/v3/references/types/toauthmethod
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import TOAuthMethod from '/snippets/v3/definitions/types/TOAuthMethod.mdx';
# TPregenIdentifierType
Source: https://docs.getpara.com/v3/references/types/tpregenidentifiertype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import TPregenIdentifierType from '/snippets/v3/definitions/types/TPregenIdentifierType.mdx';
# TWalletScheme
Source: https://docs.getpara.com/v3/references/types/twalletscheme
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import TWalletScheme from '/snippets/v3/definitions/types/TWalletScheme.mdx';
# TWalletType
Source: https://docs.getpara.com/v3/references/types/twallettype
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import TWalletType from '/snippets/v3/definitions/types/TWalletType.mdx';
# VerifiedAuth
Source: https://docs.getpara.com/v3/references/types/verifiedauth
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import VerifiedAuth from '/snippets/v3/definitions/types/VerifiedAuth.mdx';
# Verify2faResponse
Source: https://docs.getpara.com/v3/references/types/verify2faresponse
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Verify2faResponse from '/snippets/v3/definitions/types/Verify2faResponse.mdx';
# Wallet
Source: https://docs.getpara.com/v3/references/types/wallet
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import Wallet from '/snippets/v3/definitions/types/Wallet.mdx';
# WalletEntity
Source: https://docs.getpara.com/v3/references/types/walletentity
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import WalletEntity from '/snippets/v3/definitions/types/WalletEntity.mdx';
# WalletFilters
Source: https://docs.getpara.com/v3/references/types/walletfilters
import {MethodDocs} from '/snippets/v3/components/method-doc.mdx';
import WalletFilters from '/snippets/v3/definitions/types/WalletFilters.mdx';
# Bring your own auth
Source: https://docs.getpara.com/v3/rest/byo-auth
Para REST API authenticates your backend with `X-API-Key`. It does not authenticate end users, mint user JWTs, or run OTP, passkey, or OAuth flows for clients.
Use this pattern when your app already has its own login system and you want your backend to create and sign with Para wallets on behalf of those users.
Want users to **sign in through Para** with your own identity provider (OIDC / SSO) instead? That's [Custom OIDC](/v3/general/developer-portal-custom-oidc) — a client-side login method, distinct from this server-side pattern.
```text
[Mobile or web client] -> [Your auth and API server] -> [Para REST API]
|
| X-API-Key: sk_...
v
Create wallets
Sign transactions
Read transaction status
```
Keep your Para API key on your server. Do not put it in a mobile app, web app, desktop app, or client-side config.
This is a partner-custodial signing pattern. Your backend decides when to ask Para to sign. The end user does not have a Para session in REST API v1.
## When to use this
| Use REST with BYO auth when | Use a Para SDK when |
| --- | --- |
| You already authenticate users in your own backend. | You want Para to run the user auth flow. |
| You need to support a client platform without a Para SDK. | You need SDK wallet objects, adapters, or client-side signing libraries. |
| Your server should create wallets and submit signing requests. | Users should control signing from their own Para session. |
| You can model access control in your backend. | You need client-side private-key export or SDK share handling. |
## Walkthrough
### 1. Authenticate the user in your app
Use your existing auth system. Your backend should map the authenticated user to a stable internal ID, such as `user_123`.
The client never talks to Para directly in this pattern.
### 2. Create a REST wallet for that user
Create the wallet from your backend after signup or when the user first needs a wallet. Use your internal user ID as a `CUSTOM_ID`.
```bash create-wallet.sh
curl -X POST "https://api.beta.getpara.com/v1/wallets" \
-H "X-API-Key: $PARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "EVM",
"userIdentifier": "user_123",
"userIdentifierType": "CUSTOM_ID"
}'
```
Store the returned `id` as the Para wallet ID for your user.
### 3. Look up the wallet by identifier
If you need to recover the mapping, query wallets by the same identifier.
```bash lookup-wallet.sh
curl "https://api.beta.getpara.com/v1/wallets?userIdentifier=user_123&userIdentifierType=CUSTOM_ID" \
-H "X-API-Key: $PARA_API_KEY"
```
### 4. Sign from your backend
Your client asks your backend to perform an action. Your backend checks the user's session and authorization, then calls Para. The `requireUser` and `db` calls below stand in for your app's auth and storage.
```typescript server.ts
app.post("/api/wallets/:walletId/sign-message", requireUser, async (req, res) => {
const wallet = await db.wallets.findById(req.params.walletId);
if (!wallet || wallet.userId !== req.user.id) {
return res.status(404).json({ error: "Wallet not found" });
}
const paraRes = await fetch(
`https://api.beta.getpara.com/v1/wallets/${wallet.paraWalletId}/sign-message`,
{
method: "POST",
headers: {
"X-API-Key": process.env.PARA_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ message: req.body.message }),
}
);
if (!paraRes.ok) {
return res.status(paraRes.status).json(await paraRes.json());
}
res.json(await paraRes.json());
});
```
For transaction broadcasts, store the returned `transactionId`. Your backend can poll transaction history or receive REST transaction webhooks and expose the latest status to the client.
## Claiming and ownership
REST-created wallets are pre-created wallets. They start project-controlled and can be claimed later by a Para user if your app supports a client-side claim flow.
Before claim, REST signing endpoints work with the wallet ID and API key. After claim, REST signing endpoints reject the wallet because it is user-owned. At that point, use client SDK flows for user-owned wallet actions.
If you created a wallet with `CUSTOM_ID` but later want the user to claim it with an email, phone, or OAuth identifier, update the unclaimed wallet first:
```bash update-identifier.sh
curl -X PATCH "https://api.beta.getpara.com/v1/wallets/$WALLET_ID" \
-H "X-API-Key: $PARA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"userIdentifier": "user@example.com",
"userIdentifierType": "EMAIL"
}'
```
The `wallet.pregen_claimed` webhook can help your backend reconcile ownership after claim.
## Limits
- REST API v1 has no end-user Para session, user JWT, or user-scoped REST token.
- REST API v1 does not run OTP, passkey, OAuth, or Para-branded auth on the client.
- REST API v1 does not provide a private-key export endpoint.
- REST signing stops after the wallet is claimed by a user.
- Client-side export and SDK share-management flows require the relevant Para SDK path.
# Migrate SDK pregen wallets to REST API
Source: https://docs.getpara.com/v3/rest/migrate-from-sdk-pregen
import { Card } from '/snippets/v3/components/ui/card.mdx';
Wallets created with `createWalletPreGen()` require the stored `userShare` and an MPC ceremony every time you sign. After migration, signing only needs a wallet ID and your API key.
## SDK pregen vs REST API wallets
Both wallet types use the same database and MPC infrastructure. The difference is where the user's key share lives.
| | SDK pregen wallet | REST API wallet |
|---|---|---|
| Created with | `createWalletPreGen()` (SDK) | `POST /v1/wallets` (REST) |
| User share stored by | Your application | Para's enclave |
| Signing requires | `userShare` + MPC ceremony | Wallet ID + API key |
| In `GET /v1/wallets?status=ready` | No (until migrated) | Yes |
## What happens during migration
1. The SDK encrypts your `userShare` with the enclave's P-256 public key (ECIES). The plaintext never leaves your server.
2. The encrypted payload is sent to Para's backend, which forwards it to the hardware-isolated enclave for decryption and storage.
3. The wallet becomes eligible for REST API signing and appears in `GET /v1/wallets?status=ready` queries.
Migration is additive — you're adding REST API access, not replacing SDK signing. After verifying migration, you can optionally delete the stored `userShare` values from your database if you no longer need SDK-based signing.
Only unclaimed pregen wallets can be migrated. Once a user signs up with the wallet's pregen identifier, migration returns `403`.
## Migrate with the SDK (recommended)
The server SDK handles encryption for you.
```typescript
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
const para = new ParaServer(Environment.BETA, 'your-api-key'); // regular API key
const secretApiKey = 'sk_beta_...'; // secret key (sk_ prefix)
// Migrate a single wallet
await para.migrateWalletShare({ walletId, userShare, secretApiKey });
// Solana or Stellar wallets need the scheme specified
await para.migrateWalletShare({ walletId, userShare, secretApiKey, walletScheme: 'ED25519' });
```
This method fetches the enclave's public key, encrypts the share with ECIES-P256-AES256-SHA256, and sends the encrypted payload to `POST /v1/wallets/{walletId}/migrate-share`.
EVM and Cosmos wallets use the `DKLS` scheme (the default). Pass `'ED25519'` for Solana or Stellar wallets.
## Migrate with the REST API directly
If you're not using the SDK, you'll need to encrypt the share yourself before calling the endpoint.
### Fetch the enclave public key
```bash
curl "https://api.beta.getpara.com/v1/enclave/public-key" \
-H "X-API-Key: sk_..."
```
Response:
```json
{
"publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYH...base64...\n-----END PUBLIC KEY-----",
"keyFingerprint": "SHA256:abc123...",
"generatedAt": "2025-01-15T00:00:00.000Z"
}
```
### Encrypt the share
Implement ECIES encryption with the P-256 curve:
1. Generate an ephemeral P-256 key pair
2. Run ECDH with the enclave's public key to derive a shared secret
3. SHA-256 hash the shared secret to produce the AES key
4. Encrypt the share data with AES-256-GCM using a random 12-byte IV
5. Prepend the IV to the ciphertext (which includes the GCM auth tag), then base64-encode the result into `encryptedData`
The plaintext you encrypt is a JSON string:
```json
{
"shares": [{
"userId": "your-wallet-id",
"walletId": "your-wallet-id",
"walletScheme": "DKLS",
"signer": "raw-signer-extracted-from-user-share"
}]
}
```
The `signer` field is the raw signer secret extracted from the `userShare` string. The `userShare` is a series of base64-encoded JSON segments joined by `-`. Parse each segment, find the one whose `id` matches your wallet ID, and use its `signer` field.
Set `userId` to the wallet ID. Pregen wallets don't have a user ID, but the enclave schema requires a non-empty value — the wallet ID is used as a placeholder and is ignored during signing.
Getting ECIES-P256 right is tricky. Use the SDK method unless you have a specific reason not to.
### Send the encrypted payload
The `encryptedPayload` value is a **JSON string**, not a nested object. Stringify your ECIES envelope before embedding it in the request body.
```bash
curl -X POST "https://api.beta.getpara.com/v1/wallets/WALLET_ID/migrate-share" \
-H "X-API-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{ "encryptedPayload": "{\"encryptedData\":\"...\",\"keyId\":\"\",\"ephemeral\":\"...\",\"algorithm\":\"ECIES-P256-AES256-SHA256\"}" }'
```
Maximum payload size is 64KB. Returns the updated wallet object on success.
| Status | Meaning |
|--------|---------|
| `200` | Wallet migrated |
| `400` | Missing or invalid `encryptedPayload`, key generation incomplete, or payload targets wrong wallet |
| `403` | Wallet already claimed by a user — not migratable |
| `404` | Wallet not found or doesn't belong to your project |
| `409` | Wallet already migrated |
## Step-by-step migration
For new wallets, switch to the REST SDK or REST API. Replace `createWalletPreGen()` calls with
`para.createWallet()` from [`@getpara/rest-sdk`](/v3/rest/sdk), or call `POST /v1/wallets` directly. Wallets
created via REST are already ready for signing — no migration needed.
```typescript
import { ParaRestClient } from "@getpara/rest-sdk";
const para = new ParaRestClient({
apiKey: process.env.PARA_API_KEY!,
env: "BETA",
});
await para.createWallet({
type: "EVM",
userIdentifier: "user@test.getpara.com",
userIdentifierType: "EMAIL",
});
```
```bash
curl -X POST "https://api.beta.getpara.com/v1/wallets" \
-H "X-API-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"type": "EVM",
"userIdentifier": "user@test.getpara.com",
"userIdentifierType": "EMAIL"
}'
```
Gather the `userShare` values you stored when you created each SDK pregen wallet. You need the `walletId` and its corresponding `userShare` for every wallet you want to migrate.
```typescript
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
const para = new ParaServer(Environment.BETA, 'your-api-key');
const secretApiKey = 'sk_beta_...';
for (const { walletId, userShare } of walletsToMigrate) {
try {
await para.migrateWalletShare({ walletId, userShare, secretApiKey });
console.log(`Migrated ${walletId}`);
} catch (err) {
if (err.status === 409) {
console.log(`${walletId} already migrated, skipping`);
continue;
}
if (err.status === 403) {
console.log(`${walletId} already claimed, skipping`);
continue;
}
console.error(`Failed to migrate ${walletId}:`, err);
}
}
```
Migrated wallets appear in `?status=ready` results:
```bash
curl "https://api.beta.getpara.com/v1/wallets?status=ready&userIdentifier=user@example.com&userIdentifierType=EMAIL" \
-H "X-API-Key: sk_..."
```
No `userShare` or MPC ceremony needed:
```typescript
const { signature } = await para.signMessage("WALLET_ID", {
message: "Hello from REST API",
});
```
```bash
curl -X POST "https://api.beta.getpara.com/v1/wallets/WALLET_ID/sign-message" \
-H "X-API-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{ "message": "Hello from REST API" }'
```
## SDK method to REST endpoint mapping
| SDK method | REST API endpoint | Notes |
|---|---|---|
| `createWalletPreGen()` | `POST /v1/wallets` | REST API persists shares server-side |
| `getUserShare()` + partner signing | `POST /v1/wallets/{id}/sign-raw` | No `userShare` needed after migration |
| — | `POST /v1/wallets/{id}/sign-transaction` | EVM, Solana, and Stellar |
| — | `POST /v1/wallets/{id}/sign-message` | EVM, Solana, Cosmos, and Stellar |
| — | `POST /v1/wallets/{id}/sign-typed-data` | EVM only (EIP-712) |
| — | `POST /v1/wallets/{id}/transfer` | EVM and Solana only |
| `getWallets()` | `GET /v1/wallets` | Use `?status=ready` to filter to migrated wallets |
## FAQ
Yes. The wallet works with both the SDK and the REST API after migration.
No. Once the share is persisted to the enclave, it can't be removed. The wallet remains usable through both the SDK and REST API.
No. The endpoint returns `409 Conflict`. The migration loop above handles this by catching 409s and skipping.
No. Migration only works on unclaimed pregen wallets — it returns `403` once a user has claimed the wallet.
Standard rate limits apply. See [Setup - Rate limits](/v3/rest/setup#rate-limits) for details.
Only if you're not using the SDK. `para.migrateWalletShare()` handles ECIES encryption for you.
## Next steps
# Multiple Wallets per User
Source: https://docs.getpara.com/v3/rest/multi-wallet
Para's REST API enforces a uniqueness constraint: each combination of `type` + `scheme` + `userIdentifier` can only have one wallet. Attempting to create a duplicate returns `409 Conflict`.
To create multiple wallets per user, use `CUSTOM_ID` with unique identifiers that you manage.
## Solution
Instead of using `EMAIL` or `PHONE`, use `CUSTOM_ID` with a unique identifier for each wallet:
| Pattern | Example | Use Case |
|---------|---------|----------|
| `{userId}-{purpose}` | `user_123-savings` | Named wallet purposes |
| `{userId}-{index}` | `user_123-0`, `user_123-1` | Sequential wallets |
| `{userId}-{uuid}` | `user_123-a1b2c3d4` | Unlimited unique wallets |
## Example
Create multiple EVM wallets for the same user:
```bash
# Savings wallet
curl -X POST https://api.beta.getpara.com/v1/wallets \
-H "X-API-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"type": "EVM",
"userIdentifier": "user_123-savings",
"userIdentifierType": "CUSTOM_ID"
}'
# Checking wallet
curl -X POST https://api.beta.getpara.com/v1/wallets \
-H "X-API-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"type": "EVM",
"userIdentifier": "user_123-checking",
"userIdentifierType": "CUSTOM_ID"
}'
```
Both requests succeed because each has a unique `userIdentifier`.
## Common Use Cases
- **Fintech**: Savings, checking, and emergency fund accounts
- **Payments**: Category-based wallets (groceries, entertainment, subscriptions)
- **Multi-chain**: Same user with wallets on EVM, Solana, Cosmos, and Stellar
## Best Practices
1. **Store the mapping**: Keep a database record linking your user ID, custom identifier, and Para wallet ID
2. **Use descriptive identifiers**: Choose clear names like `savings` or `trading` rather than `wallet1`
3. **Add randomness if needed**: If you get `409 Conflict`, append a timestamp or UUID to guarantee uniqueness
4. **Use wallet ID for operations**: When signing transactions, use the Para wallet ID (not your custom identifier)
## Looking Up Wallets
You can retrieve wallets by identifier instead of storing wallet IDs:
```bash
curl "https://api.beta.getpara.com/v1/wallets?userIdentifier=user_123-savings&userIdentifierType=CUSTOM_ID" \
-H "X-API-Key: sk_..."
```
This returns a paginated response:
```json
{
"data": [
{ "id": "0a1b...", "type": "EVM", "scheme": "DKLS", "status": "ready", "address": "0x742d...", "createdAt": "2026-01-15T09:30:00.000Z" }
],
"pagination": { "cursor": null, "hasMore": false, "limit": 50 }
}
```
You can also list all wallets without filters, or filter by `type`, `status`, or `address`. See the [OpenAPI spec](/openapi.yaml) for all query parameters.
# REST API
Source: https://docs.getpara.com/v3/rest/overview
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para's REST API lets your backend create wallets and sign over HTTP. Two interfaces sit on top of the same API. Pick the one that fits your stack:
## Prerequisites
To use Para REST, you need an API key. It authenticates every request through the `X-API-Key` header.
Don't have an API key yet? Request access to the [Developer Portal](https://developer.getpara.com) to create API keys, manage billing, teams, and more.
You can restrict Para REST to specific IPs by adding CIDR entries in the [Developer Portal](https://developer.getpara.com). Once you add them, requests from other IPs return `401 Unauthorized`.
**Protect your API key.** REST API wallets are permanently scoped to the project that created them. If you lose access
to your API key, you can rotate it safely. Deleting the project or creating a new one means losing wallet access.
See [Setup → Authentication](/v3/rest/setup#authentication) for details.
## When to Use
- **Programmatic wallets:** the default path for new server-created or pre-created wallet integrations.
- **Any secp256k1 or ed25519 chain:** the `sign-raw` endpoint is chain-agnostic. Create an EVM wallet for secp256k1 signing (works with Bitcoin, etc.) or a Solana/Stellar wallet for ed25519 signing, then hash and serialize transactions on your side.
- Your backend needs wallets and you prefer REST semantics over share-backed SDK ceremonies.
- You already rely on secret-key auth + IP allowlists.
- You prefer cURL or language-native HTTP clients over SDKs.
## Wallet lifecycle
REST-created wallets are pre-created wallets. The difference is the integration surface: REST creates and signs through HTTP, while SDK pregen creates wallets through SDK share-management flows.
| Phase | REST behavior |
| --- | --- |
| Create | Your backend calls `POST /v1/wallets` with `type`, `userIdentifier`, and `userIdentifierType`. |
| Sign before claim | Your backend signs with REST while the wallet is still project-controlled. |
| Track broadcasts | `transfer` and `sign-transaction` with `broadcast: true` return `transactionId` for polling or webhooks. |
| Claim | A user can claim the wallet through a client SDK flow if the identifier matches. |
| After claim | REST signing stops because the wallet is now user-owned. Use client SDK flows instead. |
## Endpoint coverage
| Endpoint | EVM | Solana | Cosmos | Stellar | Notes |
| --- | --- | --- | --- | --- | --- |
| `POST /v1/wallets` | Yes | Yes | Yes | Yes | Creates a project-controlled wallet. |
| `sign-raw` | Yes | Yes | Yes | Yes | Signs raw hex bytes. |
| `sign-message` | Yes | Yes | Yes | Yes | EVM uses EIP-191. Other wallet types sign UTF-8 bytes. |
| `sign-transaction` | Yes | Yes | No | Yes | EVM and Solana can broadcast with `broadcast: true`. Stellar is sign-only. |
| `sign-typed-data` | Yes | No | No | No | EIP-712 only. |
| `sign-authorization` | Yes | No | No | No | EIP-7702 only. |
| `transfer` and `estimate-fee` | Yes | Yes | No | No | Builds transfer transactions for supported chains. |
## Wallet Claiming
When a user signs up through your app with the same identifier used to create the wallet (e.g., the same email), the wallet is automatically claimed and transferred to their account. No server-side action is needed. For details on the client-side flow, see the [Web Pregeneration guide](/v3/react/guides/pregen#claiming-a-pregenerated-wallet).
## Next Steps
# Permissions
Source: https://docs.getpara.com/v3/rest/permissions
Permissions let you define guardrails on your REST API wallets. Set up a policy through the [Para dashboard](https://developer.getpara.com) that specifies what transactions are allowed, and Para enforces it on every signing request. If a transaction violates the policy, the request is denied before anything is signed.
Permissions are **opt-in**. If you don't create a policy, all signing requests are allowed (existing behavior). Once a policy is active, it becomes **deny-by-default** — transactions must match an ALLOW rule.
## Key Concepts
- **One active policy per API key.** Each policy contains one or more scopes, and each scope contains permission rules.
- **Deny-by-default.** When a policy exists, any transaction that doesn't match an ALLOW rule is denied.
- **DENY always wins.** If a transaction matches both an ALLOW and a DENY rule, it's denied.
- **Windowed spend limits.** Direct native transfers and direct ERC-20 transfers can be limited by cumulative amount per fixed time window.
- **EVM only.** Permissions currently evaluate EVM transactions. Solana and Cosmos are not yet supported.
## How Enforcement Works
When you call any signing endpoint (`sign-raw`, `sign-message`, `sign-typed-data`, `sign-transaction`, `transfer`), Para checks whether a policy exists for your API key. If one does, the transaction is evaluated against every scope and permission in the policy.
If the transaction is denied, you get a `403` response with details about which rule blocked it:
```json
{
"code": "POLICY_DENIED",
"message": "Transaction denied by policy",
"deniedBy": {
"scopeName": "Token Transfers",
"permissionType": "TRANSFER",
"condition": {
"resource": "TO_ADDRESS",
"comparator": "CONTAINED_IN",
"reference": ["0xabc...", "0xdef..."]
}
}
}
```
The `deniedBy` field tells you exactly which scope, permission type, and condition caused the denial, making it straightforward to debug.
Windowed spend denials use the same `403` `POLICY_DENIED` response. Para denies the request before signing, so no transaction is broadcast and no pending user review is created for REST API wallets.
## Permission Types
Each permission targets a specific kind of signing operation:
| Type | Applies To |
|------|-----------|
| `SIGN_MESSAGE` | `sign-message`, `sign-typed-data`, and `sign-raw` endpoints |
| `TRANSFER` | `transfer` endpoint (native token sends) |
| `CALL_CONTRACT` | `sign-transaction` when calling a contract function |
| `DEPLOY_CONTRACT` | `sign-transaction` when deploying a contract |
## Condition Reference
Conditions narrow when a permission applies. Every condition has a **resource** (what to check), a **comparator** (how to check), and a **reference** (expected value).
### Resources
| Resource | Description | Used With |
|----------|-------------|-----------|
| `VALUE` | Transaction value in wei | `TRANSFER`, `CALL_CONTRACT` |
| `TO_ADDRESS` | Destination address | `TRANSFER`, `CALL_CONTRACT` |
| `MESSAGE` | The message being signed | `SIGN_MESSAGE` |
| `ARGUMENTS` | Contract function arguments (e.g., `ARGUMENTS[0]`) | `CALL_CONTRACT` |
### Comparators
| Comparator | Description | Reference Type |
|------------|-------------|----------------|
| `EQUALS` | Exact match | Single value |
| `NOT_EQUALS` | Must not match | Single value |
| `GREATER_THAN` | Numeric greater than | Number string (wei) |
| `LESS_THAN` | Numeric less than | Number string (wei) |
| `CONTAINED_IN` | Must be one of the listed values | Array of values |
| `NOT_CONTAINED_IN` | Must not be any of the listed values | Array of values |
### Windowed Spend Limits
Use a `WINDOWED_SPEND_LIMIT` condition when a REST API wallet should automatically sign only up to a cumulative amount in a fixed time window. The reference object must include:
| Field | Description |
|-------|-------------|
| `assetType` | `NATIVE` for native token transfers, or `ERC20` for direct ERC-20 transfers |
| `tokenAddress` | ERC-20 token contract address. Required for `ERC20`, omitted for `NATIVE` |
| `limitBaseUnits` | Maximum amount in the asset's smallest unit, as a decimal string |
| `windowMs` | Fixed window length in milliseconds |
Windowed spend limits count transaction value only. They do not include gas or fees, and each limit is scoped by API key, wallet, chain, asset, and window length.
## Limitations
- **EVM only.** Solana and Cosmos transactions are not evaluated against policies.
- **Direct transfers only.** Windowed spend limits cover native transfers and ERC-20 `transfer(address,uint256)`. They do not cover approvals, `transferFrom`, router calls, multicalls, non-transfer contract calls, USD-normalized limits, or cross-asset aggregate limits.
# REST SDK
Source: https://docs.getpara.com/v3/rest/sdk
`@getpara/rest-sdk` is Para's typed SDK for REST API wallets. Use it from trusted backend code when your server owns
API-key-backed wallet creation, lookup, signing, transfers, and transaction history.
This SDK sends your partner API key as `X-API-Key`. Never import it from browser, React, mobile, wagmi, RainbowKit,
or any user-controlled runtime.
## Install
```bash
yarn add @getpara/rest-sdk
```
The core client works in Node.js 18+ or any server runtime where you inject `fetch`.
Install adapter peers only when you use those subpaths:
```bash
yarn add ethers
yarn add viem
yarn add @solana/addresses @solana/keys @solana/signers @solana/transactions
```
## Choose the Right SDK
| Use case | Package |
| --- | --- |
| API-key-backed programmatic wallets, REST pregen wallets, typed REST signing | `@getpara/rest-sdk` |
| Share-backed server flows, imported user sessions, `migrateWalletShare()` encryption | `@getpara/server-sdk` |
| User authentication, wallet UI, browser/mobile signing | Web, React, React Native, Swift, or Flutter SDKs |
## Core Client
```ts
import { ParaRestClient, ParaRestError } from '@getpara/rest-sdk';
const para = new ParaRestClient({
apiKey: process.env.PARA_API_KEY!,
env: 'BETA',
});
const wallet = await para.createWallet(
{
type: 'EVM',
userIdentifier: 'user@test.getpara.com',
userIdentifierType: 'EMAIL',
},
{ idempotencyKey: crypto.randomUUID() },
);
const signature = await para.signMessage(wallet.id, { message: 'hello' });
```
`env` accepts `PROD`, `BETA`, or `{ baseUrl }`. Each request sends `X-API-Key` and `X-Request-Id`.
`Idempotency-Key` is caller-supplied; the SDK does not retry requests or generate idempotency keys internally.
Pass `AbortSignal` per call when your server needs a request deadline:
```ts
await para.getWallet(wallet.id, {
signal: AbortSignal.timeout(10_000),
});
```
## Adapters
```ts
import { createParaRestEthersSigner } from '@getpara/rest-sdk/ethers';
import { createParaRestViemAccount } from '@getpara/rest-sdk/viem';
import { createParaRestSolanaSigner } from '@getpara/rest-sdk/solana';
const ethersSigner = createParaRestEthersSigner({ client: para, walletId, address });
const viemAccount = createParaRestViemAccount({ client: para, walletId, address: address as `0x${string}` });
const solanaSigner = createParaRestSolanaSigner({ client: para, walletId: solanaWalletId, address: solanaAddress });
```
EVM message and typed-data signing send unhashed payloads to REST. Para hashes EIP-191 and EIP-712 payloads
server-side. EVM transaction adapters support ordinary legacy and EIP-1559 sends; they reject contract deployments,
access lists, blob transactions, authorization-list transactions, and custom viem serializers before calling REST.
With viem, string messages are treated as text. Pass `{ raw: bytes }` when a `0x...` value should be signed as bytes.
The Solana adapter implements Solana v2 signer traits by signing message bytes through `sign-raw`. For broadcasted
transactions, call `para.signTransaction(walletId, { transaction, broadcast: true })` or `para.transfer(...)`.
## Errors
`ParaRestValidationError` is thrown before `fetch` for missing local required fields or unsupported adapter inputs.
`ParaRestSerializationError` means the request body cannot be JSON serialized. HTTP errors become `ParaRestError` with
`status`, `code`, `requestId`, and parsed `body`.
```ts
try {
await para.getWallet(wallet.id);
} catch (error) {
if (error instanceof ParaRestError) {
console.error(error.status, error.code, error.requestId, error.body);
}
}
```
## Example
The typed SDK example creates REST wallets and demonstrates core client, ethers, viem, and Solana adapters:
```bash
cd examples-hub/server/rest-with-typed-sdk
cp .env.example .env
yarn install
yarn dev
```
Use `examples-hub/server/rest-with-node` when you want to inspect the raw HTTP baseline.
## Trust Boundary
REST signing uses the wallet ID and your partner API key. Your backend is responsible for deciding which end user or
job is allowed to request a wallet operation before calling Para. The SDK validates local required fields and normalizes
adapter inputs, but it does not independently reconstruct or audit every signed transaction returned by REST.
## Non-goals
The REST SDK does not include browser connectors, React hooks, wagmi or RainbowKit integration, mobile auth, hosted UI,
client-side sessions, automatic retries, or user-share encryption helpers. Use `@getpara/server-sdk` for
`migrateWalletShare()` and share-backed server flows.
# Setup
Source: https://docs.getpara.com/v3/rest/setup
import Prerequisites from '/snippets/v3/quick-start-prerequisites.mdx';
## Environments
| Environment | Base URL |
| --- | --- |
| Beta | `https://api.beta.getpara.com` |
| Production | `https://api.getpara.com` |
All endpoints are versioned under `/v1`.
## Authentication
Include your API key in every request:
```bash
curl https://api.beta.getpara.com/v1/wallets/WALLET_ID \
-H "X-API-Key: sk_..."
```
| Header | Required | Description |
|--------|----------|-------------|
| `X-API-Key` | Yes | Your partner secret key (server-side only) |
| `X-Request-Id` | No | UUID for request tracing. Para returns one if omitted. |
Never expose your API key in client-side code. Use it only from your backend.
TypeScript backends can use [`@getpara/rest-sdk`](/v3/rest/sdk) instead of hand-written REST calls. It handles
`X-API-Key`, `X-Request-Id`, optional `Idempotency-Key`, abort signals, validation errors, serialization errors, and
HTTP error mapping.
**Your project controls wallet access.** Wallets created via the REST API are permanently scoped to the project that
created them. Rotating your secret key is safe — the new key still accesses the same wallets. However, if you delete
your project or create a new one, you lose signing access to those wallets — even though the wallets and funds remain
on-chain.
If you need to migrate wallets between projects, contact [Para support](https://join.slack.com/t/para-community/shared_invite/zt-304keeulc-Oqs4eusCUAJEpE9DBwAqrg).
## IP Allowlisting
Restrict API access to specific IPs via the [Developer Portal](https://developer.getpara.com/) (Security → Allowlist). Once configured, requests from other IPs return `401 Unauthorized`.
## Error Handling
All errors return JSON with a `code` field for programmatic handling and a human-readable `message`. Some include extra context (e.g. `walletId` on `409`):
```json
{ "code": "WALLET_ALREADY_EXISTS", "message": "a wallet for this identifier and type already exists", "walletId": "0a1b..." }
```
| Status | Meaning | Action |
|--------|---------|--------|
| `400` | Invalid request body | Check required fields and types |
| `401` | API key not provided | Include `X-API-Key` header |
| `403` | Invalid API key | Verify your secret key is correct |
| `404` | Wallet not found | Confirm wallet ID exists |
| `409` | Duplicate wallet | Same `type` + `scheme` + `userIdentifier` already exists (see below) |
| `429` | Rate limit exceeded | Wait for `Retry-After` seconds then retry |
| `500` | Server error | Retry with backoff |
### Handling 409 Conflict (Duplicate Wallet)
When creating a wallet that already exists, the API returns `409 Conflict` with the existing `walletId` in the
response body:
```json
{ "message": "a wallet for this identifier and type already exists", "walletId": "0a1b...", "code": "WALLET_ALREADY_EXISTS" }
```
If you need to look up wallets by other means, you can also:
Query wallets by the original identifier:
```bash
curl "https://api.beta.getpara.com/v1/wallets?userIdentifier=user@example.com&userIdentifierType=EMAIL" \
-H "X-API-Key: sk_..."
```
As a final fallback, list all wallets and filter client-side:
```bash
curl "https://api.beta.getpara.com/v1/wallets" \
-H "X-API-Key: sk_..."
```
## Rate Limits
All `/v1` endpoints are rate-limited per API key. When a limit is exceeded the API returns `429 Too Many Requests` with a `Retry-After` header indicating how long to wait.
Rate limits vary by plan:
| Plan | REST API Limit |
|------|---------------|
| Free | 30 req/min |
| Growth | 1,000 req/min |
| Scale | Custom |
Need higher limits? Scale plan customers can request custom rate limits — contact the Para team to discuss your use case.
## Signing
The REST API supports several signing methods. All signing endpoints require an EVM or Solana wallet (created via `POST /v1/wallets`).
### Sign Typed Data (EIP-712)
Signs [EIP-712](https://eips.ethereum.org/EIPS/eip-712) structured data. EVM wallets only. Para computes the EIP-712 hash server-side — just pass the structured data directly.
```bash
curl -X POST "https://api.beta.getpara.com/v1/wallets/WALLET_ID/sign-typed-data" \
-H "X-API-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"typedData": {
"domain": {
"name": "MyDApp",
"version": "1",
"chainId": 11155111,
"verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
},
"types": {
"Mail": [
{ "name": "from", "type": "address" },
{ "name": "to", "type": "address" },
{ "name": "contents", "type": "string" }
]
},
"primaryType": "Mail",
"message": {
"from": "0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B",
"to": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
"contents": "Hello, world!"
}
}
}'
```
Returns `{ "signature": "a1b2c3..." }` — a hex-encoded signature without `0x` prefix.
Don't include `EIP712Domain` in the `types` object. Para handles it automatically from the `domain` fields.
### Sign Authorization (EIP-7702)
Signs an [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) authorization for account delegation. EVM wallets only. This enables account abstraction providers (ZeroDev, Alchemy, Pimlico, etc.) to delegate an EOA to a smart contract for gas sponsorship or batched calls — without migrating to a new wallet address.
```bash
curl -X POST "https://api.beta.getpara.com/v1/wallets/WALLET_ID/sign-authorization" \
-H "X-API-Key: sk_..." \
-H "Content-Type: application/json" \
-d '{
"authorization": {
"address": "0x1234567890abcdef1234567890abcdef12345678",
"chainId": 1,
"nonce": 0
}
}'
```
Returns the signed authorization with decomposed signature fields:
```json
{
"address": "0x1234567890abcdef1234567890abcdef12345678",
"chainId": 1,
"nonce": 0,
"r": "0xa1b2c3...",
"s": "0xd4e5f6...",
"yParity": 0,
"signature": "a1b2c3d4e5f6..."
}
```
Unlike other signing endpoints that return `v` (27/28), this endpoint returns `yParity` (0/1) per the EIP-7702 spec. The `address` field also accepts `contractAddress` as an alias, matching viem's API.
### Other Signing Methods
| Endpoint | Use Case |
|----------|----------|
| `POST /v1/wallets/{id}/sign-raw` | Sign arbitrary hex bytes (chain-agnostic) |
| `POST /v1/wallets/{id}/sign-message` | Sign a human-readable message (EIP-191 for EVM) |
| `POST /v1/wallets/{id}/sign-transaction` | Sign an EVM, Solana, or Stellar transaction; EVM and Solana can also broadcast with `broadcast: true` |
| `POST /v1/wallets/{id}/transfer` | Build, sign, and broadcast a transfer in one call |
| `POST /v1/wallets/{id}/estimate-fee` | Preview transfer fees without signing or broadcasting |
`sign-transaction` accepts both legacy `Transaction` and v0 `VersionedTransaction` (Address Lookup Tables) Solana formats; the endpoint auto-detects which one was sent. This means output from `VersionedTransaction.serialize()` (what Jupiter's swap API returns) can be passed in directly.
`sign-transaction` is sign-only by default. Set top-level `broadcast: true` for EVM or Solana to broadcast the signed bytes and receive `txHash`, `transactionId`, and an `x-transaction-id` response header. Stellar remains sign-only and rejects `broadcast: true`.
See the [API reference](/v3/rest/overview) for full request/response schemas.
## Timeouts
Recommended client timeouts:
| Operation | Timeout |
|-----------|---------|
| Create wallet | 30s |
| Get wallet | 10s |
| Sign | 30s |
# Developer Portal Email Branding
Source: https://docs.getpara.com/v3/rest/setup/developer-portal-email-branding
import DeveloperPortalEmailBranding from '/snippets/v3/developer-portal/email-branding.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Payment Integration
Source: https://docs.getpara.com/v3/rest/setup/developer-portal-payments
import DeveloperPortalPayments from '/snippets/v3/developer-portal/payments.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Security Settings
Source: https://docs.getpara.com/v3/rest/setup/developer-portal-security
import DeveloperPortalSecurity from '/snippets/v3/developer-portal/security.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Get Your API Key
Source: https://docs.getpara.com/v3/rest/setup/developer-portal-setup
import DeveloperPortalSetup from '/snippets/v3/developer-portal/setup.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Transaction History
Source: https://docs.getpara.com/v3/rest/transaction-history
REST broadcasts create persisted transaction records that Para tracks to terminal status on-chain. Records are queryable, auditable, and emit webhooks when they confirm or fail.
Rows are written for `POST /v1/wallets/{walletId}/transfer` broadcasts and for `POST /v1/wallets/{walletId}/sign-transaction` when `broadcast: true`. Sign-only calls do not write rows.
## Status Lifecycle
```
pending → submitted → confirmed
→ reverted
↘ failed
```
| Status | Meaning |
|--------|---------|
| `pending` | Record inserted; broadcast not yet attempted. |
| `submitted` | RPC accepted the signed bytes. Monitor is running. Not terminal. |
| `confirmed` | Included in a block, not reverted. `blockNumber` and `blockHash` are populated. |
| `reverted` | Included on-chain but execution failed (EVM status=0 or Solana signature error). Terminal. |
| `failed` | Never broadcast, or broadcast was rejected by the RPC. `failureStage` identifies where. Terminal. |
Partner code must treat unknown status values as non-terminal. Para may add new statuses (`finalized`, `replaced`) in a later release without bumping the API version.
## Response Shape
Calls that broadcast return a `transactionId` field and set an `x-transaction-id` response header. Use either to look up the record afterward.
```json
{
"signedTransaction": "0x02f8...",
"txHash": "0x1234abcd...",
"transactionId": "550e8400-e29b-41d4-a716-446655440000"
}
```
For `sign-transaction`, omitted `broadcast` is also sign-only. No record is written and no `transactionId` is returned.
If a broadcast request fails after Para creates the transaction row, the error response still sets `x-transaction-id` and includes `transactionId` in the JSON body. Failures after signing also include `signedTransaction`. Use the transaction history record's `failureStage` and `failureCode` fields for persisted failure details.
## Reading Transaction History
### List transactions for a wallet
```bash
curl -H "X-API-Key: $PARA_API_KEY" \
"https://api.beta.getpara.com/v1/wallets/$WALLET_ID/transactions?status=confirmed&limit=20"
```
Filter by status via `?status=`. Filter by source operation via `?intentKind=transfer` or `?intentKind=sign_transaction`. Paginate via `?cursor=` using the opaque cursor returned in each response. Results are ordered by `createdAt` DESC.
### Look up a single transaction
```bash
curl -H "X-API-Key: $PARA_API_KEY" \
"https://api.beta.getpara.com/v1/wallets/$WALLET_ID/transactions/$TX_ID"
```
Records that belong to a different partner return `404` with an identical body to missing records.
## Record Fields
Each record includes `intentKind`, either `transfer` or `sign_transaction`.
For EVM `sign_transaction` records, Para stores `chainId`, `to`, and `value` when `value` was present in the request. `tokenAddress` is absent. Para does not decode calldata.
For Solana `sign_transaction` records, `to`, `value`, and `tokenAddress` are absent because Para does not decode arbitrary Solana instructions.
## Polling Recipe
When webhooks aren't practical, poll the record until it leaves the `submitted` state.
```typescript
async function waitForConfirmation(walletId: string, transactionId: string) {
const start = Date.now();
let delayMs = 2000;
const timeoutMs = 10 * 60 * 1000;
while (Date.now() - start < timeoutMs) {
const res = await fetch(
`https://api.beta.getpara.com/v1/wallets/${walletId}/transactions/${transactionId}`,
{ headers: { 'X-API-Key': process.env.PARA_API_KEY! } },
);
const record = await res.json();
if (record.status === 'confirmed' || record.status === 'reverted' || record.status === 'failed') {
return record;
}
await new Promise((r) => setTimeout(r, delayMs));
delayMs = Math.min(delayMs * 1.5, 15000);
}
throw new Error('timed out waiting for confirmation');
}
```
## Webhooks
Subscribe to two event types from the [Developer Portal](https://developer.getpara.com) to receive terminal-state notifications automatically:
| Event | When it fires |
|-------|---------------|
| `rest.transaction.confirmed` | Transaction included in a block without reverting. |
| `rest.transaction.failed` | Transaction reverted on-chain, or monitor detected a terminal RPC failure. |
Webhook bodies use the standard envelope described in the [webhooks guide](/v3/general/webhooks). The `data.transactionId` field matches the `transactionId` returned by `transfer` or `sign-transaction` with `broadcast: true`.
Payload `data` example for `rest.transaction.confirmed`:
```json
{
"transactionId": "550e8400-e29b-41d4-a716-446655440000",
"walletId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"partnerId": "11111111-2222-3333-4444-555555555555",
"intentKind": "transfer",
"type": "EVM",
"hash": "0x1234abcd...",
"blockNumber": "5127103",
"blockHash": "0xdeadbeef...",
"resolvedAt": "2026-04-23T12:00:00.000Z"
}
```
`rest.transaction.failed` mirrors the shape and adds `status` (`reverted` or `failed`), plus optional `failureStage`, `failureCode`, and `failureMessage` fields.
### Backend receiver pattern
Mobile and web clients do not receive REST transaction webhooks directly. Your backend receives the webhook, verifies it with the secret from the Developer Portal, updates its transaction row, and exposes the latest status to the client.
In this example, `verifyParaWebhook` is the HMAC check described in the [webhooks guide](/v3/general/webhooks#verify-webhook-signatures).
```typescript webhook-route.ts
app.post("/webhooks/para", express.raw({ type: "application/json" }), async (req, res) => {
if (!verifyParaWebhook(req.body, req.headers, process.env.PARA_WEBHOOK_SECRET)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body.toString("utf8"));
if (event.type === "rest.transaction.confirmed") {
await db.transactions.update(event.data.transactionId, {
status: "confirmed",
txHash: event.data.hash,
blockNumber: event.data.blockNumber,
resolvedAt: event.data.resolvedAt,
});
}
if (event.type === "rest.transaction.failed") {
await db.transactions.update(event.data.transactionId, {
status: event.data.status,
txHash: event.data.hash,
failureStage: event.data.failureStage,
failureCode: event.data.failureCode,
failureMessage: event.data.failureMessage,
resolvedAt: event.data.resolvedAt,
});
}
res.sendStatus(204);
});
```
Store the `transactionId` when you submit the REST broadcast. In the webhook, use `event.data.transactionId` to update that same row.
Sign-only calls and transactions you broadcast yourself do not emit REST transaction webhooks. Para only sends these webhooks for broadcasts it can monitor.
## Failure Fields
When `status = failed`, `failureStage` describes where the pipeline broke:
| Stage | Meaning |
|-------|---------|
| `mpc_sign` | The MPC signing ceremony errored before a signature was produced. |
| `signature_apply` | The signature could not be attached to the transaction. |
| `signer_verify` | The recovered signer address did not match the wallet's public address (EVM only). |
| `broadcast` | The RPC node rejected the signed bytes (e.g. `INSUFFICIENT_NATIVE_BALANCE`, `EXECUTION_FAILED`). |
| `monitor_timeout` | The signed transaction was broadcast but didn't reach a terminal on-chain state within Para's 30-minute monitor window. The transaction may still confirm later. Query the chain directly to verify. |
When `status = reverted`, the transaction reached the chain and executed with a revert. `blockNumber` and `blockHash` are populated, `failureStage` is absent, and `failureMessage` contains any error detail surfaced by the monitor.
## Broadcast Error Messages
REST `/transfer` now sources broadcast-failure messages from Para's shared broadcast helper rather than passing through raw ethers.js strings. If code parses the previous raw string format, update it to read `failureCode` (e.g. `INSUFFICIENT_NATIVE_BALANCE`, `EXECUTION_FAILED`) from the transaction record or failure webhook instead.
## Retention
Both `/transfer` broadcasts and `sign-transaction` calls with `broadcast: true` write rows. Retention remains deferred until the `rest_transfers` table reaches an agreed large-row or disk threshold.
# Server Examples
Source: https://docs.getpara.com/v3/server/examples
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para offers a collection of server-side examples to help you integrate our technology across different JavaScript runtime environments. Our examples-hub repository includes server implementations that showcase how to use Para in Node.js, Bun, and Deno environments.
Each example demonstrates how to perform blockchain operations server-side using both pregenerated wallets and imported client sessions. These standalone examples focus on specific Para SDKs or features, providing minimal implementation requirements that you can easily adapt to your specific application needs.
## Runtime Environments
Browse our server examples showcasing Para integration across different JavaScript runtimes:
## Need Something Specific?
Don't see an example for your server-side use case? Para's team is eager to create new examples to help you integrate with different libraries, runtime environments, or blockchain ecosystems. Reach out to our support team for assistance.
# Account Abstraction
Source: https://docs.getpara.com/v3/server/guides/account-abstraction
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
import SmartAccountOverview from '/snippets/v3/aa/smart-account-overview.mdx';
## Provider Guides
Server-side AA uses the same `createXxxSmartAccount` action functions as the React SDK — no hooks needed. Select a provider below for installation and usage instructions.
You'll need a Para Server SDK session before calling any `createXxxSmartAccount` function. See the for details on initializing Para and importing sessions.
provides modular smart accounts with built-in gas sponsorship via Alchemy's Gas Manager. Create and manage smart accounts, submit gasless transactions, and execute batched UserOperations. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create an and obtain your **API key** and **Gas Policy ID** from the Alchemy dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-alchemy viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-alchemy viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-alchemy viem
```
### Usage
```typescript alchemy-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createAlchemySmartAccount } from "@getpara/aa-alchemy";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
const smartAccount = await createAlchemySmartAccount({
para,
apiKey: process.env.ALCHEMY_API_KEY!,
chain: sepolia,
gasPolicyId: process.env.ALCHEMY_GAS_POLICY_ID, // optional: for gas sponsorship
mode: "4337", // or "7702"
});
// Get the smart wallet address (different from Para EOA in 4337 mode)
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
// Send a single transaction
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
// Send batched transactions atomically
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
Alchemy requires chains from `@account-kit/infra` (e.g. `sepolia`, `baseSepolia`). Plain viem chains are automatically mapped if an Alchemy equivalent exists. For gasless transactions, set up a Gas Manager Policy in your and pass the policy ID as `gasPolicyId`.
is an embedded AA wallet powering many smart accounts across EVM chains. Known for its extensive feature set including gas sponsorship, session keys, recovery, multisig, and . Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **Project ID** from the ZeroDev dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-zerodev viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-zerodev viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-zerodev viem
```
### Usage
```typescript zerodev-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createZeroDevSmartAccount } from "@getpara/aa-zerodev";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
const smartAccount = await createZeroDevSmartAccount({
para,
projectId: process.env.ZERODEV_PROJECT_ID!,
chain: sepolia,
mode: "4337", // or "7702"
});
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
You can optionally pass `bundlerUrl` and `paymasterUrl` to use custom infrastructure instead of ZeroDev's defaults. For more on managing your ZeroDev project and RPC endpoints, see the .
provides account abstraction infrastructure via , a TypeScript library built on viem with no extra dependencies and a small bundle size. Supports multiple account implementations. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **API key** from the Pimlico dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-pimlico viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-pimlico viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-pimlico viem
```
### Usage
```typescript pimlico-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createPimlicoSmartAccount } from "@getpara/aa-pimlico";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
const smartAccount = await createPimlicoSmartAccount({
para,
apiKey: process.env.PIMLICO_API_KEY!,
chain: sepolia,
mode: "4337", // or "7702"
});
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
The Pimlico bundler/paymaster URL is automatically constructed from your API key and chain name. You can override it with a custom `rpcUrl`. Pimlico's permissionless.js also supports other account types (Safe, Kernel, Biconomy, SimpleAccount) — see the .
is a full-stack AA toolkit built on ERC-4337 that provides smart accounts, paymasters, and bundlers. Its Multi-chain Execution Environment (MEE) enables cross-chain orchestration of transactions. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **API key** from the Biconomy dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-biconomy viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-biconomy viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-biconomy viem
```
### Usage
```typescript biconomy-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createBiconomySmartAccount } from "@getpara/aa-biconomy";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
const smartAccount = await createBiconomySmartAccount({
para,
apiKey: process.env.BICONOMY_API_KEY!,
chain: sepolia,
mode: "4337", // or "7702"
});
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
Biconomy transactions are executed via the MEE (Multi-chain Execution Environment). You can optionally pass a custom `meeUrl` to use your own MEE node.
provides smart wallets with built-in gas sponsorship. Supports both EIP-4337 and EIP-7702 modes.
### Setup
1. Create a and obtain your **Client ID** from the Thirdweb dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-thirdweb viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-thirdweb viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-thirdweb viem
```
### Usage
```typescript thirdweb-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createThirdwebSmartAccount } from "@getpara/aa-thirdweb";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
const smartAccount = await createThirdwebSmartAccount({
para,
clientId: process.env.THIRDWEB_CLIENT_ID!,
chain: sepolia,
sponsorGas: true, // enable gas sponsorship (default)
mode: "4337", // or "7702"
});
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
Set `sponsorGas: false` to disable gas sponsorship. In EIP-4337 mode, you can optionally provide `factoryAddress` and `accountAddress` for custom smart wallet deployments.
provides native EIP-7702 smart accounts with built-in gas sponsorship via relay infrastructure. Gelato only supports EIP-7702 mode.
### Setup
1. Create a and obtain your **API key** from the Gelato dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-gelato viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-gelato viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-gelato viem
```
### Usage
```typescript gelato-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createGelatoSmartAccount } from "@getpara/aa-gelato";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
const smartAccount = await createGelatoSmartAccount({
para,
apiKey: process.env.GELATO_API_KEY!,
chain: sepolia,
});
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
Gelato only supports EIP-7702 mode. Gas sponsorship is built into Gelato's relay infrastructure — no separate paymaster configuration is needed.
provides 7702-native smart accounts with a built-in relay — no bundler or paymaster needed. Porto only supports EIP-7702 mode.
### Setup
1. No API key or third-party account is needed. Porto uses its own relay RPC.
2. **Gas sponsorship:** On testnets, Porto's relay sponsors gas by default — no setup needed.
For **mainnet**, you must set up a to cover gas fees for your users. Run `pnpx porto onboard --admin-key` to create a merchant
account, deploy a server-side merchant route using `porto/server`, and pass your endpoint URL
as `merchantUrl` in the config below.
3. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-porto viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-porto viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-porto viem
```
### Usage
```typescript porto-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createPortoSmartAccount } from "@getpara/aa-porto";
import { parseEther } from "viem";
import { base } from "viem/chains";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
// Testnets: gas is sponsored by default, no merchantUrl needed.
// Mainnet: merchantUrl is required for gas sponsorship.
const smartAccount = await createPortoSmartAccount({
para,
chain: base,
merchantUrl: process.env.PORTO_MERCHANT_URL, // required for mainnet
});
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
Porto only supports EIP-7702 mode. The Porto relay supports including Base, Optimism, Arbitrum, Ethereum, and several testnets. On testnets, gas fees are sponsored by default. For mainnet, a is required — pass `merchantUrl` to enable gas sponsorship.
(formerly Gnosis Safe) provides multi-signature smart contract wallets with ERC-4337 compatibility. The user's Para EOA serves as the signer, while the Safe smart contract wallet holds assets and submits transactions. Powered by Pimlico's bundler and paymaster infrastructure. Safe only supports EIP-4337 mode.
### Setup
1. Create a and obtain your **API key** — Safe uses Pimlico for bundler and paymaster services.
2. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-safe viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-safe viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-safe viem
```
### Usage
```typescript safe-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createSafeSmartAccount } from "@getpara/aa-safe";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
const smartAccount = await createSafeSmartAccount({
para,
pimlicoApiKey: process.env.PIMLICO_API_KEY!,
chain: sepolia,
});
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
Safe only supports EIP-4337 mode. You can optionally pass `safeVersion` (default: `"1.4.1"`) and `saltNonce` for deterministic address generation.
provides cross-chain smart accounts with intent-based transactions, automatic bridging, and gas abstraction across multiple EVM chains. Transactions are routed through Rhinestone's orchestrator which handles cross-chain execution automatically. Rhinestone only supports EIP-4337 mode.
### Setup
1. Obtain your **Rhinestone API key** and optionally a **Pimlico API key** for bundler/paymaster infrastructure.
2. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-rhinestone viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-rhinestone viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-rhinestone viem
```
### Usage
```typescript rhinestone-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createRhinestoneSmartAccount } from "@getpara/aa-rhinestone";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
const smartAccount = await createRhinestoneSmartAccount({
para,
chain: sepolia,
rhinestoneApiKey: process.env.RHINESTONE_API_KEY!,
pimlicoApiKey: process.env.PIMLICO_API_KEY!, // optional but recommended
});
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
Rhinestone only supports EIP-4337 mode. Both `rhinestoneApiKey` and `pimlicoApiKey` are optional but recommended for production use. For advanced cross-chain use cases with automatic bridging, see the .
provides Coinbase smart accounts on Base. Uses viem's built-in `toCoinbaseSmartAccount` — no additional bundler packages required. CDP only supports EIP-4337 mode on Base and Base Sepolia.
### Setup
1. Create a and obtain your **RPC token** from the paymaster URL in the CDP dashboard.
2. Install the required dependencies:
```bash npm
npm install @getpara/server-sdk @getpara/aa-cdp viem
```
```bash yarn
yarn add @getpara/server-sdk @getpara/aa-cdp viem
```
```bash pnpm
pnpm add @getpara/server-sdk @getpara/aa-cdp viem
```
### Usage
```typescript cdp-server.ts
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { createCDPSmartAccount } from "@getpara/aa-cdp";
import { baseSepolia } from "viem/chains";
import { parseEther } from "viem";
const para = new ParaServer(Environment.BETA, process.env.PARA_API_KEY!);
await para.importSession(serializedSession);
const smartAccount = await createCDPSmartAccount({
para,
rpcToken: process.env.CDP_RPC_TOKEN!,
chain: baseSepolia,
});
console.log("Smart wallet address:", smartAccount.smartAccountAddress);
const receipt = await smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
console.log("Transaction hash:", receipt.transactionHash);
const batchReceipt = await smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xContractAddress", data: "0xencodedCallData" },
]);
console.log("Batch tx hash:", batchReceipt.transactionHash);
```
CDP only supports EIP-4337 mode and is limited to **Base** and **Base Sepolia** chains. No additional bundler packages are needed — CDP uses viem's built-in `toCoinbaseSmartAccount`.
In EIP-4337 mode, funds must be sent to `smartAccount.smartAccountAddress` — not to the Para EOA address. The smart wallet is the entity that holds funds and executes transactions on-chain.
## Troubleshooting
If transactions fail, verify that the Para session is valid and that the smart account address has sufficient funds (in 4337 mode). For gas-sponsored transactions, confirm your policy ID and that the transaction meets the policy requirements.
First-time use of an AA wallet requires contract deployment, which can be more expensive. Ensure you have sufficient funds in the Para EOA for this initial deployment.
If using gas sponsorship, verify your policy ID and settings in the AA provider's dashboard. Also check that the transaction meets the policy requirements (value limits, allowed functions, etc.).
## Examples
# Cosmos Integration
Source: https://docs.getpara.com/v3/server/guides/cosmos
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para Server SDK seamlessly integrates with Cosmos-based blockchains through the CosmJS library. Once you've set up and authenticated your Para Server client, the Cosmos integration works identically to the client-side implementation.
Before using this integration, ensure you've completed the server setup by importing a client session or creating a pregenerated wallet. See the for details.
## Installation
Install the required dependencies for Cosmos integration:
```bash
npm install @getpara/cosmjs-v0-integration @cosmjs/stargate @cosmjs/proto-signing @cosmjs/amino @cosmjs/encoding --save-exact
```
## Implementation
The Para integration with CosmJS provides a custom signer that works with Stargate Client:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
import { createParaProtoSigner } from "@getpara/cosmjs-v0-integration";
import { SigningStargateClient } from "@cosmjs/stargate";
// Para server client (already authenticated)
const paraServer = new ParaServer("YOUR_API_KEY");
// Create the Para Cosmos Signer
const signer = createParaProtoSigner({ para: paraServer, prefix: "cosmos" });
// Connect to the Cosmos network
const rpcUrl = "https://rpc.cosmos.network"; // Replace with your preferred RPC endpoint
const client = await SigningStargateClient.connectWithSigner(rpcUrl, signer);
// Get the wallet address
const address = await signer.getAddress();
console.log(`Wallet address: ${address}`);
// Get account balance
const balance = await client.getBalance(address, "uatom");
console.log(`Balance: ${balance.amount} ${balance.denom}`);
// Send tokens
const recipient = "cosmos1recipient";
const amount = {
denom: "uatom",
amount: "100000", // 0.1 ATOM (uatom is microatom, 1 ATOM = 1,000,000 uatom)
};
const result = await client.sendTokens(
address,
recipient,
[amount],
{
amount: [{ denom: "uatom", amount: "5000" }],
gas: "200000",
}
);
console.log(`Transaction hash: ${result.transactionHash}`);
```
## Chain Support
The Para Cosmos integration supports various Cosmos-based chains. You can specify the chain when creating the signer:
```typescript
// For Cosmos Hub
const cosmosSigner = createParaProtoSigner({ para: paraServer, prefix: "cosmos" });
// For Osmosis
const osmosisSigner = createParaProtoSigner({ para: paraServer, prefix: "osmosis" });
// For other supported chains
const otherChainSigner = createParaProtoSigner({ para: paraServer, prefix: "chainName" });
```
## Best Practices
- Use appropriate gas settings for different types of transactions
- Implement proper error handling for network failures
- Consider retry logic for RPC endpoints
- Always verify transaction details before sending
## Learn More
For detailed examples of using Para with Cosmos, including chain-specific operations and advanced transaction types, refer to our web documentation:
## Examples
Explore our server-side Cosmos integration examples:
# EVM Integration
Source: https://docs.getpara.com/v3/server/guides/evm
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para Server SDK provides seamless integration with Ethereum Virtual Machine (EVM) compatible chains through popular libraries like Ethers.js and Viem. Once you've set up and authenticated your Para Server client, the EVM integration works identically to the client-side implementation.
Before using these integrations, ensure you've completed the server setup by importing a client session or creating a pregenerated wallet. See the for details.
Sepolia examples need testnet ETH before sending transactions. Server SDK and REST SDK flows can call `requestFaucet` with `{ walletId, chain: "ETHEREUM_SEPOLIA" }` after resolving the wallet ID. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react/guides/web3-operations/evm/fund-testnet-wallet) for the same request shape in a UI flow.
## Installation
Install the required dependencies for your preferred EVM library:
```bash Ethers.js
npm install @getpara/ethers-v6-integration ethers --save-exact
```
```bash Viem
npm install @getpara/viem-integration viem --save-exact
```
## Implementation
### Ethers.js Integration
The Para Ethers Signer acts as a drop-in replacement for standard Ethers signers:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
import { createParaEthersSigner } from "@getpara/ethers-v6-integration";
import { ethers } from "ethers";
// Para server client (already authenticated)
const paraServer = new ParaServer("YOUR_API_KEY");
// Set up the provider with your RPC URL
const provider = new ethers.JsonRpcProvider("YOUR_RPC_URL");
// Create the Para Ethers Signer
const signer = createParaEthersSigner({ para: paraServer, provider: provider });
// Now you can use the signer with any Ethers.js operations
const balance = await provider.getBalance(await signer.getAddress());
console.log(`Balance: ${ethers.formatEther(balance)} ETH`);
// Sign a message
const signature = await signer.signMessage("Hello from Para Server!");
// Send a transaction
const tx = await signer.sendTransaction({
to: "0xRecipientAddress",
value: ethers.parseEther("0.001")
});
```
### Viem Integration
Para's Viem integration provides a custom Viem client compatible with all Viem operations:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
import { createParaViemAccount, createParaViemClient } from "@getpara/viem-integration";
import { http } from "viem";
import { sepolia } from "viem/chains";
// Para server client (already authenticated)
const paraServer = new ParaServer("YOUR_API_KEY");
// Create a Para Account
const account = await createParaViemAccount(paraServer);
// Create the Para Viem WalletClient
const walletClient = createParaViemClient(paraServer, {
account: account,
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
});
// Now you can use the walletClient with any Viem operations
const hash = await walletClient.sendTransaction({
to: "0xRecipientAddress",
value: 1000000000000000n // 0.001 ETH
});
```
## Best Practices
- Use environment variables for API keys and RPC URLs
- Implement proper error handling for network failures
- Consider gas price management for production applications
- Cache network calls where appropriate to reduce RPC usage
## Learn More
For detailed examples of using Para with EVM chains, including smart contract interactions, ERC-20 transfers, and more, refer to our web documentation:
## Examples
Explore our server-side EVM integration examples:
# Wallet Pregeneration
Source: https://docs.getpara.com/v3/server/guides/pregen
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
**For new server-side wallet creation, use the [REST API](/v3/rest/overview) instead.** It covers the same use cases
(pre-created user wallets, agent wallets, server-side signing) without requiring you to store, encrypt, and restore
user shares or run an MPC ceremony per signature. This guide covers SDK-based pregeneration for existing integrations
and for flows that need direct user-share control. To move an existing integration, see
[Migrate SDK pregen wallets to REST API](/v3/rest/migrate-from-sdk-pregen).
Pregenerated wallets let your server create wallets before a user authenticates with Para. The flow works by creating a wallet associated with an identifier of your choice (email, phone, username, or custom ID). Para then provides you with the user share of the 2/2 MPC key, which you must securely store on your server until it's either claimed by a user or used for signing operations.
Because your server holds the user share, SDK pregeneration is the right choice only when you need that control directly — for example, signing with SDK ecosystem integrations (Ethers, Viem, Solana, CosmJS) before the wallet is claimed, or exporting key material through client flows.
## Creating a Pregenerated Wallet
```typescript Node.js
import { Para as ParaServer } from "@getpara/server-sdk";
const paraServer = new ParaServer("YOUR_API_KEY");
const pregenId = { email: "user@example.com" };
const hasWallet = await paraServer.hasPregenWallet({
pregenId,
});
if (!hasWallet) {
await paraServer.createPregenWallet({
type: "EVM",
pregenId,
});
}
```
```typescript Bun
import { Para as ParaServer } from "@getpara/server-sdk";
const paraServer = new ParaServer("YOUR_API_KEY", {
disableWebSockets: true
});
const pregenId = { email: "user@example.com" };
const hasWallet = await paraServer.hasPregenWallet({
pregenId,
});
if (!hasWallet) {
await paraServer.createPregenWallet({
type: "EVM",
pregenId,
});
}
```
```typescript Deno
import { Para as ParaServer, WalletType } from "@getpara/server-sdk";
const paraServer = new ParaServer("YOUR_API_KEY", {
disableWebSockets: true
});
const pregenId = { email: "user@example.com" };
const hasWallet = await paraServer.hasPregenWallet({
pregenId,
});
if (!hasWallet) {
await paraServer.createPregenWallet({
type: WalletType.EVM,
pregenId,
});
}
```
### Method Parameters
The type of wallet to create (`'EVM'`, `'SOLANA'`, `'COSMOS'`, or `'STELLAR'`).
The identifier for the new wallet. The `pregenId` must be an object of the form:
```typescript
| { email: string; }
| { phone: `+${number}`; }
| { farcasterUsername: string; }
| { telegramUserId: string; }
| { discordUsername: string; }
| { xUsername: string; }
| { customId: string; }
```
The identifier can be an email or phone number, a third-party user ID (for Farcaster, Telegram, Discord, or X), or a custom ID relevant to your application. Choose an identifier that works best for your application architecture.
## Securing the User Share
After creating a pregenerated wallet, you must securely store the user share. This component is critical for the wallet's operation and security.
```typescript
const userShare = await paraServer.getUserShare();
const encryptedUserShare = await encryptUserShare(userShare);
await database.pregenWallets.save({
walletId,
encryptedUserShare,
});
```
You must securely store this user share in your backend, associated with the user's identifier. If this share is lost, the wallet becomes permanently inaccessible.
### Secure Storage Best Practices
We strongly recommend implementing robust encryption for user shares both in transit and at rest. Consider using a high-entropy encryption key with AES-GCM encryption. Do not store encryption keys in the same database as the encrypted data.
Para offers pre-launch security reviews for teams in the Growth tier or above. Reach out to the Para team for assistance with your implementation!
### Storage Security Recommendations
- **Encrypt** user shares in-transit and at-rest
- Implement **access controls** for your share database
- Maintain regular **database backups** to prevent data loss
- Create **disaster recovery** processes for compromise scenarios
- Have a **key rotation** plan in case of security incidents
- Complete **security fire drills** before launching
## Using a Pregenerated Wallet
To use a pregenerated wallet for blockchain operations, you need to:
1. Retrieve the encrypted user share from your database
2. Decrypt the user share
3. Load it into your Para client instance
```typescript
const encryptedUserShare = await database.getUserShare(walletId);
const userShare = decryptUserShare(encryptedUserShare);
await paraServer.setUserShare(userShare);
```
When implementing `setUserShare` in API routes or serverless functions, it's critical to create a new Para client instance for each request. This prevents different users' shares from conflicting with each other. Creating a new Para client has minimal overhead and won't impact request performance.
### Signing Operations
Once the user share is loaded, you can test the wallet functionality by signing a message. However, for production use, we recommend using the blockchain library integrations described in the next section.
```typescript
const messageBase64 = Buffer.from("Hello, World!").toString('base64');
const signature = await paraServer.signMessage({
walletId,
messageBase64,
});
```
### Using with Blockchain Libraries
Pregenerated wallets work seamlessly with all blockchain integration libraries. Here are some examples:
## Wallet Claiming Flow
Claiming pregenerated wallets must be done client-side with the Para Client SDK. The Server SDK does not support the key rotation operations required for wallet claiming.
For applications that need to transfer ownership of pregenerated wallets to users, implement a client-side claiming flow.
Your backend still prepares the stored user share for the authenticating identifier before the client receives it.
1. Look up the stored pregenerated wallet from the authenticating identifier
2. Decrypt and load the stored user share into a fresh server-side Para client
3. Update the pregenerated wallet identifier if it was created with a custom ID
4. Return the updated user share to the client
5. Let the client SDK load the share and claim the wallet
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
export async function preparePregenClaim(email: string) {
const wallet = await getPregenWalletByEmail(email);
const para = new ParaServer(process.env.PARA_API_KEY!);
const userShare = await decryptUserShare(wallet.encryptedUserShare);
await para.setUserShare(userShare);
await para.updatePregenWalletIdentifier({
walletId: wallet.walletId,
newPregenId: { email },
});
return {
userShare: await para.getUserShare(),
};
}
```
Calling `setUserShare` before `updatePregenWalletIdentifier` lets the server SDK update the wallet metadata in the
loaded share. Return that updated share to the client so the loaded wallet identifier matches the user's auth
identifier during claim.
For a comprehensive guide on implementing the claiming flow, refer to our web documentation:
## Core Pregeneration Methods
Create a new pregenerated wallet for an identifier
Check if a pregenerated wallet exists for an identifier
Retrieve pregenerated wallets for a given identifier
Get the user share that must be securely stored
Load a previously stored user share
Update the identifier of a pregenerated wallet
## Best Practices
- **Choose appropriate identifiers** that align with your application architecture
- **Implement robust encryption** for user share storage
- **Create backup systems** to prevent data loss
- **Use a separate database** for user share storage with enhanced security
- **Monitor for suspicious activity** in your pregenerated wallet systems
- **Implement rate-limiting** to prevent abuse of wallet creation
- **Document your recovery procedures** for security incidents
- **Consider multi-region replication** for high-availability systems
## Example Implementation
Here's a reference example demonstrating the creation and secure storage of pregenerated wallets:
## Community Showcase
Check out some novel use cases of pregenerated wallets created by our community:
# Session Management
Source: https://docs.getpara.com/v3/server/guides/sessions
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para's Server SDK can import a session that a user created in your client application. This lets your backend perform authenticated operations for that user, including signing, without asking the user to re-authenticate for the server request. Session validity and duration are enforced by the Para API and the API key's session length configuration.
## Importing Client Sessions
Export the authenticated client session, send it to your backend over HTTPS, then import it into a fresh `ParaServer` instance for the request.
Create a new `ParaServer` instance for each imported session. A server SDK instance holds one active session at a time, so reusing one instance across users can mix request state.
### Client-Side Session Export
Use `waitAndExportSession()` after the user authenticates:
```typescript
const serializedSession = await para.waitAndExportSession();
await fetch("/api/sign-message", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
session: serializedSession,
message: "Hello from Para",
}),
});
```
`waitAndExportSession()` waits until the SDK has reached an authenticated state before reading session data. Use it for handoff flows immediately after login.
If your backend needs to sign for the user, do not export with `{ excludeSigners: true }`. That option removes the wallet signer data required for server-side signing.
If your backend only needs to validate the user session and will not sign, you can export without signer data:
```typescript
const sessionWithoutSigners = await para.waitAndExportSession({
excludeSigners: true,
});
```
### Server-Side Session Import
Import the serialized session before running user-authenticated operations:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
import express from "express";
const app = express();
app.use(express.json());
const paraApiKey = process.env.PARA_API_KEY;
if (!paraApiKey) {
throw new Error("PARA_API_KEY is required");
}
app.post("/api/sign-message", async (req, res) => {
const { session, message } = req.body;
const para = new ParaServer(paraApiKey);
await para.importSession(session);
if (!(await para.isSessionActive())) {
return res.status(401).json({ error: "Session expired" });
}
const sessionExtended = await para.keepSessionAlive();
if (!sessionExtended) {
return res.status(401).json({ error: "Session expired" });
}
const walletId = para.findWalletId();
const result = await para.signMessage({
walletId,
messageBase64: Buffer.from(message).toString("base64"),
});
return res.status(200).json({
walletId,
signature: result.signature,
});
});
```
`keepSessionAlive()` extends the active imported session according to the API key's configured session length. It returns `false` if the session cannot be extended.
### Handling Multiple User Sessions
Each exported session belongs to one authenticated user. Import each session into its own `ParaServer` instance when your backend handles multiple users or parallel requests:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
async function signForImportedSession(session: string, message: string) {
const para = new ParaServer(process.env.PARA_API_KEY!);
await para.importSession(session);
const walletId = para.findWalletId();
return para.signMessage({
walletId,
messageBase64: Buffer.from(message).toString("base64"),
});
}
const [firstSignature, secondSignature] = await Promise.all([
signForImportedSession(firstUserSession, "Message for first user"),
signForImportedSession(secondUserSession, "Message for second user"),
]);
```
## Session Validation
You can validate sessions on the server side to ensure they're still active before performing operations.
### Using the Para Client
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
app.post("/api/authenticated-action", async (req, res) => {
const paraServer = new ParaServer("YOUR_API_KEY");
await paraServer.importSession(req.body.session);
if (!(await paraServer.isSessionActive())) {
return res.status(401).json({ error: "Session expired" });
}
return res.status(200).json({ success: true });
});
```
### Using JWT Authentication
Once a user is signed in, you can request a Para JWT token. This token will provide attestations for the user's ID, their identity, and any wallets they have provisioned via your application.
To request a token, use the `issueJwt` method. The method returns the token itself as well as the JWKS key ID (`kid`) for the keypair that signed it.
```typescript
const paraServer = new ParaServer('your-api-key');
const { token, keyId } = await paraServer.issueJwt();
```
The token's expiry will be determined by your customized session length, or else will default to 30 minutes. Issuing a token, like most authenticated API operations, will also renew and extend the session for that duration.
The token's `aud` field will be set to your API key's unique ID, linking it specifically to your application.
Depending on the user in question, a decoded token payload might resemble the following:
```json Email
{
"data": {
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"wallets": [
{
"id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"type": "EVM",
"address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
"publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
},
{
"id": "d70f64e4-266a-457e-9cea-eeb42341a975",
"type": "SOLANA",
"address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
"publicKey": ""
}
],
"email": "email@example.com",
"authType": "email",
"identifier": "email@example.com",
"oAuthMethod": "google" // or: undefined | "x" | "discord" | "facebook" | "apple"
},
"iat": 1745877709,
"exp": 1745879509,
"aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
"sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
}
```
```json Phone
{
"data": {
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"wallets": [
{
"id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"type": "EVM",
"address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
"publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
},
{
"id": "d70f64e4-266a-457e-9cea-eeb42341a975",
"type": "SOLANA",
"address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
"publicKey": ""
}
],
"phone": "+13105551234",
"authType": "phone",
"identifier": "+13105551234"
},
"iat": 1745877709,
"exp": 1745879509,
"aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
"sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
}
```
```json Telegram
{
"data": {
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"wallets": [
{
"id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"type": "EVM",
"address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
"publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
},
{
"id": "d70f64e4-266a-457e-9cea-eeb42341a975",
"type": "SOLANA",
"address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
"publicKey": ""
}
],
"telegramUserId": "1234567890",
"authType": "telegram",
"identifier": "1234567890"
},
"iat": 1745877709,
"exp": 1745879509,
"aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
"sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
}
```
```json Farcaster
{
"data": {
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"wallets": [
{
"id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"type": "EVM",
"address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
"publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
},
{
"id": "d70f64e4-266a-457e-9cea-eeb42341a975",
"type": "SOLANA",
"address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
"publicKey": ""
}
],
"farcasterUsername": "FarcasterUsername",
"authType": "farcaster",
"identifier": "FarcasterUsername"
},
"iat": 1745877709,
"exp": 1745879509,
"aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
"sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
}
```
```json External Wallet
{
"data": {
"userId": "d5358219-38d3-4650-91a8-e338131d1c5e",
"wallets": [
{
"id": "de4034f1-6b0f-4a98-87a5-e459db4d3a03",
"type": "EVM",
"address": "0x9dd3824f045c77bc369485e8f1dd6b452b6be617",
"publicKey": "0x0465434f76c8321f386856c44e735fd365a09d42c1da03489184b651c2052ea1c7b19c54722ed828458c1d271cc590b0818d8c7df423f71e92683f9e819095a8c6"
},
{
"id": "d70f64e4-266a-457e-9cea-eeb42341a975",
"type": "SOLANA",
"address": "EEp7DbBu5yvgf7Pr9W17cATPjCqUxY8K8R3dFbg53a3W",
"publicKey": ""
}
],
"externalWalletAddress": "0xaD6b78193b78e23F9aBBB675734f4a2B3559598D",
"authType": "externalWallet",
"identifier": "0xaD6b78193b78e23F9aBBB675734f4a2B3559598D",
"externalWallet": {
"address": "0xaD6b78193b78e23F9aBBB675734f4a2B3559598D",
"type": "EVM",
"provider": "MetaMask"
}
},
"iat": 1745877709,
"exp": 1745879509,
"aud": "a31b8f2e-7c6d-4e5a-9f8b-1d2c3a4b5e6f",
"sub": "d5358219-38d3-4650-91a8-e338131d1c5e"
}
```
Para's JSON Web Keys Set (JWKS) file(s) are available at the following URLs:
| Environment | JWKS URL |
| ----------- | -------- |
| BETA | `https://api.beta.getpara.com/.well-known/jwks.json` |
| PROD | `https://api.getpara.com/.well-known/jwks.json` |
### Using Verification Tokens
For non-Node.js servers or scenarios where you only need to validate a session without importing it, Para provides dedicated verification endpoints:
```typescript
// Client-side: Get a verification token
const verificationToken = await para.getVerificationToken();
// Send to your server
await fetch("/api/verify-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ verificationToken }),
});
```
On your server, verify the token against Para's API.
Use your Secret API Key from the Developer Portal to authenticate requests to the verification endpoints. This key is different from the public-facing API Key used in the Para client.
```typescript Node.js
// Server-side verification
app.post("/api/verify-session", async (req, res) => {
const { verificationToken } = req.body;
if (!verificationToken) {
return res.status(400).json({ error: "Missing verification token" });
}
// Set the correct URL based on your environment
const verifyUrl = "https://api.beta.getpara.com/sessions/verify";
const response = await fetch(verifyUrl, {
method: "POST",
headers: {
"content-type": "application/json",
"x-external-api-key": "YOUR_SECRET_API_KEY"
},
body: JSON.stringify({ verificationToken }),
});
if (response.status === 403) {
return res.status(403).json({ error: "Session expired" });
}
const userData = await response.json();
return res.status(200).json({ userData });
});
```
```python Python
from flask import Flask, request, jsonify
import requests
app = Flask(__name__)
@app.route('/api/verify-session', methods=['POST'])
def verify_session():
data = request.get_json()
verification_token = data.get("verificationToken")
if not verification_token:
return jsonify({"error": "Missing verification token"}), 400
# Set the correct URL based on your environment
verify_url = "https://api.beta.getpara.com/sessions/verify"
response = requests.post(
url=verify_url,
json={"verificationToken": verification_token},
headers={
"content-type": "application/json",
"x-external-api-key": "YOUR_SECRET_API_KEY"
}
)
if response.status_code == 403:
return jsonify({"error": "Session expired"}), 403
user_data = response.json()
# user_data contains { authType, identifier }
# Proceed with authenticated operations
return jsonify({"userData": user_data})
if __name__ == '__main__':
app.run(debug=True)
```
The verification endpoints are environment-specific:
| Environment | Verification URL |
| ----------- | ---------------- |
| BETA | `https://api.beta.getpara.com/sessions/verify` |
| PROD | `https://api.getpara.com/sessions/verify` |
The verification response will contain the authentication type, identifier, and optionally the OAuth method used:
```typescript
{
authType: "email" | "phone" | "farcaster" | "telegram" | "externalWallet";
identifier: string;
oAuthMethod?: "google" | "x" | "discord" | "facebook" | "apple";
}
```
## Session Management
### Maintaining Session Validity
To extend the validity of the session imported into the current `ParaServer` instance, call `keepSessionAlive()`:
```typescript
const success = await paraServer.keepSessionAlive();
if (!success) {
throw new Error("Session expired");
}
```
Session length is configured per API key in the or CLI. The Para API enforces that duration, and `keepSessionAlive()` extends the active session according to the configured value.
`refreshSession()` starts a login or refresh flow and returns a login URL. For imported server sessions, use `keepSessionAlive()` to extend the active session.
### Best Practices
1. **Create a fresh server instance per session**: Initialize a new Para Server SDK instance for each imported user session or request.
2. **Secure session transport**: Always use HTTPS when transferring sessions between client and server. Do not log serialized sessions.
3. **Export signer data only when needed**: Use `{ excludeSigners: true }` only when the server does not need to sign.
4. **Validate before operations**: Check that the imported session is active before performing authenticated operations.
5. **Handle expiration explicitly**: If the session is expired or cannot be extended, ask the client to authenticate again and export a new session.
6. **Use verification tokens for auth-only checks**: When you only need to verify who the user is, use verification tokens instead of importing a full session.
7. **Configure session length intentionally**: Set the API key's session length in the Developer Portal or CLI based on your application's security model.
## Verifying Wallet Ownership
To verify that a wallet address matches one of your users' embedded wallets, you can send a request to one of the following endpoints:
| Environment | URL |
| ----------- | --- |
| BETA | `https://api.beta.getpara.com/wallets/verify` |
| PROD | `https://api.getpara.com/wallets/verify` |
Use your Secret API Key from the Developer Portal to authenticate requests to this endpoint. This key is different from the public-facing API Key used in the Para client.
Pass the address for the wallet in the POST request body:
```typescript Node.js
// Server-side verification
app.post("/api/verify-wallet", async (req, res) => {
const { address } = req.body;
if (!address) {
return res.status(400).json({ error: "Missing address" });
}
// Set the correct URL based on your environment
const verifyUrl = "https://api.beta.getpara.com/wallets/verify";
const response = await fetch(verifyUrl, {
method: "POST",
headers: {
"content-type": "application/json",
"x-external-api-key": "YOUR_SECRET_API_KEY"
},
body: JSON.stringify({ address }),
});
if (response.status === 404) {
return res.status(404).json({ error: `Wallet not found with address: ${address}` });
}
const { walletId } = await response.json();
return res.status(200).json({ walletId });
});
```
## Learn More
For more information about client-side session management and authentication, refer to our web documentation:
## Examples
To learn more about using sessions on the server, check out this example. Each example route will have both pregen and session based routes for you to test with.
# Solana Integration
Source: https://docs.getpara.com/v3/server/guides/solana
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para Server SDK provides seamless integration with Solana blockchain through both Solana Web3.js and Anchor frameworks. Once you've set up and authenticated your Para Server client, the Solana integration works identically to the client-side implementation.
Before using these integrations, ensure you've completed the server setup by importing a client session or creating a pregenerated wallet. See the for details.
## Installation
Install the required dependencies for your preferred Solana library:
```bash Solana Web3.js
npm install @getpara/solana-web3.js-v1-integration @solana/web3.js --save-exact
```
```bash Anchor
npm install @getpara/solana-web3.js-v1-integration @solana/web3.js @coral-xyz/anchor --save-exact
```
## Implementation
### Solana Web3.js Integration
The Para Solana Web3 Signer works seamlessly with the Solana Web3.js library:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
import { ParaSolanaWeb3Signer } from "@getpara/solana-web3.js-v1-integration";
import { Connection, clusterApiUrl, SystemProgram, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
// Para server client (already authenticated)
const paraServer = new ParaServer("YOUR_API_KEY");
// Set up Solana connection
const solanaConnection = new Connection(clusterApiUrl("testnet"));
// Create the Para Solana Signer
const solanaSigner = new ParaSolanaWeb3Signer(paraServer, solanaConnection);
// Get the wallet address
const walletAddress = solanaSigner.sender.toBase58();
console.log(`Wallet address: ${walletAddress}`);
// Create and send a transaction
const transaction = await solanaSigner.createTransaction({
instructions: [
SystemProgram.transfer({
fromPubkey: solanaSigner.sender,
toPubkey: new PublicKey("RecipientPublicKeyHere"),
lamports: 0.01 * LAMPORTS_PER_SOL,
}),
],
});
const signature = await solanaSigner.sendTransaction(transaction);
console.log(`Transaction signature: ${signature}`);
```
### Anchor Integration
Para can be used with Anchor by creating an Anchor-compatible wallet wrapper:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
import { ParaSolanaWeb3Signer } from "@getpara/solana-web3.js-v1-integration";
import { Connection, clusterApiUrl, Transaction, VersionedTransaction } from "@solana/web3.js";
import * as anchor from "@coral-xyz/anchor";
// Para server client (already authenticated)
const paraServer = new ParaServer("YOUR_API_KEY");
// Set up Solana connection
const solanaConnection = new Connection(clusterApiUrl("testnet"));
const solanaSigner = new ParaSolanaWeb3Signer(paraServer, solanaConnection);
// Create an Anchor-compatible wallet
const anchorWallet = {
publicKey: solanaSigner.sender,
signTransaction: async (tx: T): Promise => {
return await solanaSigner.signTransaction(tx);
},
signAllTransactions: async (txs: T[]): Promise => {
return await Promise.all(txs.map((tx) => solanaSigner.signTransaction(tx)));
},
};
// Create the Anchor provider
const provider = new anchor.AnchorProvider(
solanaConnection,
anchorWallet,
{ commitment: "confirmed" }
);
// Now you can use this provider with any Anchor program
const program = new anchor.Program(
YOUR_IDL,
"PROGRAM_ID_HERE",
provider
);
// Interact with your program
await program.methods
.yourProgramMethod()
.accounts({
// Your accounts here
})
.rpc();
```
## Best Practices
- Use higher commitment levels (`confirmed` or `finalized`) for critical transactions
- Implement proper error handling for network failures
- Consider retry logic for Solana RPC endpoints, which can occasionally be unreliable
- Cache account data where appropriate to reduce RPC usage
## Learn More
For detailed examples of using Para with Solana, including SPL token transfers, NFT interactions, and more, refer to our web documentation:
## Examples
Explore our server-side Solana integration examples:
# Stellar Integration
Source: https://docs.getpara.com/v3/server/guides/stellar
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para Server SDK provides seamless integration with the Stellar blockchain. Once you've set up and authenticated your Para Server client, the Stellar integration works identically to the client-side implementation.
Before using these integrations, ensure you've completed the server setup by importing a client session or creating a pregenerated wallet. See the for details.
## Installation
Install the required dependencies:
```bash npm
npm install @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --save-exact
```
```bash yarn
yarn add @getpara/stellar-sdk-v14-integration @stellar/stellar-sdk --exact
```
## Implementation
### Functional API
The `createParaStellarSigner` function returns a signer compatible with Stellar SDK's `contract.SignTransaction` and `contract.SignAuthEntry` interfaces:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
import { createParaStellarSigner } from "@getpara/stellar-sdk-v14-integration";
import { Horizon, Networks, TransactionBuilder, Operation, Asset, BASE_FEE } from "@stellar/stellar-sdk";
// Para server client (already authenticated)
const paraServer = new ParaServer("YOUR_API_KEY");
// Set up Horizon connection
const server = new Horizon.Server("https://horizon.stellar.org");
// Create the Stellar signer
const signer = createParaStellarSigner({
para: paraServer,
networkPassphrase: Networks.PUBLIC,
});
console.log(`Stellar address: ${signer.address}`);
// Load the source account
const sourceAccount = await server.loadAccount(signer.address);
// Build a payment transaction
const transaction = new TransactionBuilder(sourceAccount, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.payment({
destination: "GRECIPI...",
asset: Asset.native(),
amount: "10",
})
)
.setTimeout(180)
.build();
// Sign and submit
const { signedTxXdr } = await signer.signTransaction(transaction.toXDR());
const signedTx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(signedTx);
console.log(`Transaction hash: ${result.hash}`);
```
### Class API
The `ParaStellarSigner` class can also be instantiated directly. Pass `networkPassphrase` to the constructor so `signTransaction` uses it as the default:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
import { ParaStellarSigner } from "@getpara/stellar-sdk-v14-integration";
import { Horizon, Networks, TransactionBuilder, Operation, Asset, BASE_FEE } from "@stellar/stellar-sdk";
const paraServer = new ParaServer("YOUR_API_KEY");
const server = new Horizon.Server("https://horizon.stellar.org");
// Create the signer
const signer = new ParaStellarSigner(paraServer, Networks.PUBLIC);
console.log(`Stellar address: ${signer.address}`);
// Load account and build transaction
const sourceAccount = await server.loadAccount(signer.address);
const transaction = new TransactionBuilder(sourceAccount, {
fee: BASE_FEE,
networkPassphrase: Networks.PUBLIC,
})
.addOperation(
Operation.payment({
destination: "GRECIPI...",
asset: Asset.native(),
amount: "10",
})
)
.setTimeout(180)
.build();
// Sign and submit
const { signedTxXdr } = await signer.signTransaction(transaction.toXDR());
const signedTx = TransactionBuilder.fromXDR(signedTxXdr, Networks.PUBLIC);
const result = await server.submitTransaction(signedTx);
console.log(`Transaction hash: ${result.hash}`);
```
## Best Practices
- Always load the source account from Horizon before building transactions to get the correct sequence number
- Use `setTimeout()` on transactions to prevent them from being valid indefinitely
- Consider fee bump transactions during network congestion
- Use higher fees during high-traffic periods to ensure transaction inclusion
## Learn More
For detailed examples including multi-operation transactions, fee bumps, and Soroban auth, refer to our web documentation:
# Para Server SDK
Source: https://docs.getpara.com/v3/server/overview
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para's Server SDK enables you to perform blockchain operations on the server-side across Node.js, Bun, Deno, and Cloudflare Workers environments. The server SDK shares the same core functionality as the web SDK, with authentication being the primary difference.
The Server SDK only works with Embedded Wallets. For connecting to and signing with external wallets such as MetaMask and Phantom, you
will need to integrate one of Para's Client SDKs.
**Non-Node.js runtimes** (Bun, Deno, Cloudflare Workers) require `disableWebSockets: true` in the constructor.
See the [setup guide](/v3/server/setup#runtime-compatibility) for details.
## Getting Started
## Blockchain Ecosystem Support
Para works seamlessly with major blockchain ecosystems on the server-side, allowing you to leverage Para's authentication alongside chain-specific libraries:
## Authentication Options
Creating new wallets from your server? The [REST API](/v3/rest/overview) is the recommended path — Para holds the key
material, so there is no user share to manage. Use the Server SDK when importing client sessions or when an existing
integration relies on SDK pregen.
Para offers two authentication methods for server-side applications:
## Resources
# Server Setup
Source: https://docs.getpara.com/v3/server/setup
import { Card } from '/snippets/v3/components/ui/card.mdx';
The Para Server SDK enables secure server-side blockchain operations across different JavaScript runtime environments.
With nearly identical functionality to client-side implementations, the server SDK allows you to perform signing
operations server-side by either importing client-side sessions or using pregenerated wallets. If you're creating new
wallets from your server rather than importing sessions, start with the [REST API](/v3/rest/overview) instead.
## Installation
Install the Para Server SDK in your preferred JavaScript runtime environment:
```bash npm
npm install @getpara/server-sdk --save-exact
```
```bash yarn
yarn add @getpara/server-sdk --exact
```
```bash pnpm
pnpm add @getpara/server-sdk --save-exact
```
```bash bun
bun add @getpara/server-sdk --exact
```
```bash deno
deno install npm:@getpara/server-sdk
```
**Why `--save-exact`?** The Para Server SDK may publish breaking changes in minor versions during active development.
Pinning the exact version prevents unexpected breakage when you install or update dependencies.
## Initialization
Initialize the Para Server SDK with your API key. The initialization process varies slightly depending on your runtime
environment:
```typescript Node.js
import { Para as ParaServer } from "@getpara/server-sdk";
// Standard initialization for Node.js
const paraServer = new ParaServer("YOUR_API_KEY");
```
```typescript Bun
import { Para as ParaServer } from "@getpara/server-sdk";
// Bun requires disabling WebSockets
const paraServer = new ParaServer("YOUR_API_KEY", {
disableWebSockets: true
});
```
```typescript Deno
import { Para as ParaServer } from "@getpara/server-sdk";
// Deno requires disabling WebSockets
const paraServer = new ParaServer("YOUR_API_KEY", {
disableWebSockets: true,
});
```
### Runtime Compatibility
The Server SDK works across multiple JavaScript runtimes. Some runtimes require additional constructor options:
| Runtime | `disableWebSockets` | Notes |
|---------|:-------------------:|-------|
| Node.js | Not needed | Full support out of the box |
| Bun | **Required** | Bun's WebSocket implementation is incompatible with the SDK's internal transport |
| Deno | **Required** | Same as Bun — the SDK falls back to HTTP polling |
| Cloudflare Workers | **Required** | Workers runtime doesn't support persistent WebSocket connections |
If you omit `disableWebSockets: true` in Bun, Deno, or Cloudflare Workers, the SDK may fail silently or hang
during signing operations. Always set this option for non-Node.js runtimes.
**Signing performance tip:** Para's signing infrastructure runs in `us-east-1`. For the lowest latency on server-side signing operations, deploy your application servers in or near that region.
If you're using a legacy API key (one without an environment prefix) you must provide the `Environment` as the first argument to the `ParaServer` constructor. You can retrieve your updated API key from the Para Developer Portal at https://developer.getpara.com/
**Pre-warm the MPC signer:** The first signing operation loads the MPC WASM module and spawns a worker thread, which
adds latency. Call `initializeWorker()` after setup to pre-warm the signer so subsequent signing calls are fast:
```typescript
const paraServer = new ParaServer("YOUR_API_KEY");
await paraServer.initializeWorker();
```
## Authentication Methods
After initializing the Para Server SDK, you need to authenticate it before performing any operations. The server SDK
supports two authentication methods:
### Option 1: Importing Client Sessions
Import an active session from your client application when your backend needs to act for an authenticated user.
In your client application, export the authenticated session and send it to your backend:
```typescript
const serializedSession = await para.waitAndExportSession();
await fetch("/api/import-session", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session: serializedSession }),
});
```
`waitAndExportSession()` waits for the SDK to reach an authenticated state before exporting. Use it for client-to-server handoff flows immediately after login.
If the server does not need to sign for the user, export without signer data:
```typescript
const sessionWithoutSigners = await para.waitAndExportSession({
excludeSigners: true,
});
```
Do not use `{ excludeSigners: true }` when the server needs to sign messages or transactions for the user.
On your server, import the serialized session into a new `ParaServer` instance:
```typescript
import { Para as ParaServer } from "@getpara/server-sdk";
app.post("/api/import-session", async (req, res) => {
const paraServer = new ParaServer("YOUR_API_KEY");
await paraServer.importSession(req.body.session);
const isActive = await paraServer.isSessionActive();
return res.status(200).json({ isActive });
});
```
With a session loaded into the server instance, you can perform the operations that the Para Server SDK supports.
Create a new `ParaServer` instance for each imported user session. A server SDK instance holds one active session at a time.
### Option 2: Using Pregenerated Wallets
Generate and use deterministic wallets server-side without requiring client-side authentication. This pregen wallet will load the para server instance with a wallet that can be used for all operations that the Para Server SDK supports.
```typescript
import { WalletType } from "@getpara/server-sdk";
await paraServer.createPregenWallet({
type: 'EVM', // or 'SOLANA', 'COSMOS', 'STELLAR'
pregenId: { email: "user@example.com" },
});
```
When using pregenerated wallets, you'll need to manage the user share of the wallet and store it securely.
Learn more about pregen wallets and their usage on the server in our pregen wallets guide.
## Examples
Explore our example implementations of the Para Server SDK across different runtime environments:
## Troubleshooting
If you encounter issues while setting up the Para Server SDK, check out our troubleshooting guide:
## Next Steps
Once your Para Server SDK is set up and authenticated, you can start performing blockchain operations. The server SDK
provides the same functionality as the client SDK, allowing you to:
# Developer Portal Email Branding
Source: https://docs.getpara.com/v3/server/setup/developer-portal-email-branding
import DeveloperPortalEmailBranding from '/snippets/v3/developer-portal/email-branding.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Payment Integration
Source: https://docs.getpara.com/v3/server/setup/developer-portal-payments
import DeveloperPortalPayments from '/snippets/v3/developer-portal/payments.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Security Settings
Source: https://docs.getpara.com/v3/server/setup/developer-portal-security
import DeveloperPortalSecurity from '/snippets/v3/developer-portal/security.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Get Your API Key
Source: https://docs.getpara.com/v3/server/setup/developer-portal-setup
import DeveloperPortalSetup from '/snippets/v3/developer-portal/setup.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Para with SvelteKit
Source: https://docs.getpara.com/v3/svelte/setup/svelte-kit
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import { Link } from "/snippets/v3/components/ui/link.mdx";
Para does not ship a prebuilt SvelteKit UI. Use the framework-agnostic `@getpara/web-sdk` and build the authentication, wallet, and signing screens inside your SvelteKit routes and components.
## Install the Web SDK
Install the Para Web SDK using your preferred package manager:
```bash npm
npm install @getpara/web-sdk --save-exact
```
```bash yarn
yarn add @getpara/web-sdk --exact
```
```bash pnpm
pnpm add @getpara/web-sdk --save-exact
```
```bash bun
bun add @getpara/web-sdk --exact
```
## Create a Para Client
Create a browser-only client module for your SvelteKit app:
```ts src/lib/para.ts
import { PUBLIC_PARA_API_KEY } from "$env/static/public";
import { ParaWeb } from "@getpara/web-sdk";
export const para = new ParaWeb(PUBLIC_PARA_API_KEY);
```
Create your API key in the , then expose it to SvelteKit as `PUBLIC_PARA_API_KEY`.
Para authentication flows depend on browser APIs. Call Web SDK methods from client-side Svelte components or browser-only modules, not from server load functions.
## Build the Custom UI
Use Para's Web SDK methods from your SvelteKit components and render the screens your product needs.
Use `authenticateWithEmailOrPhone`, `authenticateWithOAuth`, and `verifyNewAccount` to drive sign-up and login. Listen to `para.onStatePhaseChange()` so your UI can open the verification, passkey, password, or PIN URLs that Para returns.
Render wallet creation, wallet selection, address display, loading states, and recovery prompts in your own Svelte components.
After the user is authenticated and has a wallet, call Para signing methods from the Web SDK and show signing status in your own UI.
Sepolia examples need testnet ETH before sending transactions or writing contracts. After resolving the Para EVM wallet ID, call `para.requestFaucet({ walletId, chain: "ETHEREUM_SEPOLIA" })` from the Web SDK. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react/guides/web3-operations/evm/fund-testnet-wallet) for the same request shape in React.
## Next Docs
Build framework-agnostic auth screens with Svelte examples.
Review the custom UI path and where it hands off into platform docs.
Configure login methods, external wallets, guest mode, and 2FA.
Verify the integration by signing a message or transaction.
# Para with Svelte + Vite
Source: https://docs.getpara.com/v3/svelte/setup/vite
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import { Link } from "/snippets/v3/components/ui/link.mdx";
Para does not ship a prebuilt Svelte UI. Use the framework-agnostic `@getpara/web-sdk` and build the authentication, wallet, and signing screens inside your Svelte app.
## Install the Web SDK
Install the Para Web SDK using your preferred package manager:
```bash npm
npm install @getpara/web-sdk --save-exact
```
```bash yarn
yarn add @getpara/web-sdk --exact
```
```bash pnpm
pnpm add @getpara/web-sdk --save-exact
```
```bash bun
bun add @getpara/web-sdk --exact
```
## Create a Para Client
Create a shared client module for your Svelte app:
```ts client/para.ts
import { ParaWeb } from "@getpara/web-sdk";
const PARA_API_KEY = import.meta.env.VITE_PARA_API_KEY;
export const para = new ParaWeb(PARA_API_KEY);
```
Create your API key in the , then expose it to Vite as `VITE_PARA_API_KEY`.
## Build the Custom UI
Use Para's Web SDK methods from your Svelte components and render the screens your product needs.
Use `authenticateWithEmailOrPhone`, `authenticateWithOAuth`, and `verifyNewAccount` to drive sign-up and login. Listen to `para.onStatePhaseChange()` so your UI can open the verification, passkey, password, or PIN URLs that Para returns.
Render wallet creation, wallet selection, address display, loading states, and recovery prompts in your own Svelte components.
After the user is authenticated and has a wallet, call Para signing methods from the Web SDK and show signing status in your own UI.
Sepolia examples need testnet ETH before sending transactions or writing contracts. After resolving the Para EVM wallet ID, call `para.requestFaucet({ walletId, chain: "ETHEREUM_SEPOLIA" })` from the Web SDK. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react/guides/web3-operations/evm/fund-testnet-wallet) for the same request shape in React.
## Next Docs
Build framework-agnostic auth screens with Svelte examples.
Review the custom UI path and where it hands off into platform docs.
Configure login methods, external wallets, guest mode, and 2FA.
Verify the integration by signing a message or transaction.
# Swift SDK API Reference
Source: https://docs.getpara.com/v3/swift/api/sdk
The Para Swift SDK provides a comprehensive toolkit for integrating non-custodial wallet functionality, passkey-based authentication, and blockchain interactions into your native iOS applications.
## ParaManager Class
The `ParaManager` class is the central component for managing user authentication, wallet operations, and session state.
### Properties
An array of `Wallet` objects currently associated with the authenticated user. This list is updated after operations like `fetchWallets()` or `createWallet()`.
The current session state of the Para SDK (e.g., `.unknown`, `.inactive`, `.active`, `.activeLoggedIn`).
The environment configuration for the Para SDK (e.g., `.beta`, `.prod`).
The API key used for authenticating with Para services.
Indicates if the `ParaManager` and its underlying WebView are initialized and ready to process requests.
### Initialization
Initializes a new `ParaManager` instance.
The Para environment to use (e.g., `.beta`, `.prod`).
Your Para API key.
Optional. Your app's custom URL scheme (e.g., "yourapp://"). Defaults to the app's bundle identifier. Used for OAuth and other redirection flows.
### Authentication Methods
Starts the authentication (signup or login) process for a user with the specified email or phone number. When a default `WebAuthenticationSession` is registered via `setDefaultWebAuthenticationSession(_:)`, hosted Para One Click flows are handled automatically and the returned state's `stage` will be `.done`.
The authentication identifier: `.email(String)` for email or `.phone(String)` for a full phone number (e.g., "+15551234567").
An `AuthState` object indicating the next step in the flow (e.g., `.done` when hosted auth completed, `.verify` for new users, `.login` for existing users).
Stores a `WebAuthenticationSession` that the SDK reuses for hosted login, signup, and OAuth flows.
The shared session to reuse. Pass `nil` to clear the default and fall back to per-call overrides.
Submits the verification code (OTP) received by the user via email or SMS.
The verification code entered by the user.
An `AuthState` object, typically with `stage == .signup`, indicating the user is verified and needs to choose a signup method (passkey or password).
Requests a new verification code to be sent to the user's email or phone.
Checks if a specific signup method (passkey or password) is available based on the current `AuthState`.
The signup method to check (e.g., `.passkey`).
The current `AuthState` (should have `stage == .signup`).
`true` if the method is available, `false` otherwise.
Completes the signup process for a new, verified user using the chosen method (passkey or password). This typically includes creating a passkey/password and the first wallet.
The current `AuthState` (must have `stage == .signup`).
The chosen signup method (`.passkey` or `.password`).
An `ASAuthorizationController` instance for handling Passkey UI operations.
Optional override for web-based authentication (e.g., password setup). Defaults to the session registered via `setDefaultWebAuthenticationSession(_:)`.
Checks if a specific login method (passkey or password) is available for an existing user.
The login method to check (e.g., `.passkey`).
The current `AuthState` (should have `stage == .login`).
`true` if the method is available, `false` otherwise.
Logs in an existing user, automatically determining and using the preferred available method (passkey or password).
The current `AuthState` (must have `stage == .login`).
An `ASAuthorizationController` instance for Passkey UI.
Optional override for web-based login (e.g., password). Defaults to the session registered via `setDefaultWebAuthenticationSession(_:)`.
Logs in a user directly using their passkey.
An `ASAuthorizationController` instance for Passkey UI.
Optional. The user's email if known, to help filter passkeys.
Optional. The user's full phone number if known, to help filter passkeys. If both email and phone are nil, the system prompts for any available passkey.
Generates and registers a new passkey for the user. Typically called during signup when `authState.stage == .signup` and `SignupMethod.passkey` is chosen.
The user's identifier (email or full phone number).
The `passkeyId` obtained from `AuthState.passkeyId` when `authState.stage == .signup`.
An `ASAuthorizationController` instance for Passkey UI.
Presents a web URL (typically for password setup/login) using a web authentication session.
The URL to present (e.g., `authState.passwordUrl`).
Optional override. Defaults to the session registered via `setDefaultWebAuthenticationSession(_:)`.
The callback URL if authentication was successful via direct callback, or nil on failure/cancellation.
Initiates and handles the entire OAuth flow with the specified provider (e.g., Google, Apple, Discord).
This includes user authentication with the provider, Para account lookup/creation, and passkey setup if it's a new Para user.
The OAuth provider to use (e.g., `.google`).
Optional override for handling the OAuth web flow. Defaults to the session registered via `setDefaultWebAuthenticationSession(_:)`.
An `ASAuthorizationController` for potential passkey setup.
Returns nothing on success. Throws ParaError or other authentication-related errors on failure.
Logs into Para using an externally managed wallet (e.g., MetaMask).
Information about the external wallet, including its address and type.
### Session Management Methods
Checks if there is an active session and the user is fully authenticated (e.g., passkey/password set up).
`true` if the user is fully logged in, `false` otherwise.
Checks if there is any active session with Para, even if the user is not fully logged in (e.g., pending verification).
`true` if there is any active session, `false` otherwise.
Exports the current session for backup or transfer purposes.
The session data as a string that can be saved and restored later.
Logs out the current user and clears all session data.
Gets the current user's persisted authentication details.
The current user's authentication state, or nil if no user is authenticated. The 'stage' will be set to 'login' for active sessions.
Manually checks and updates the current session state. This method waits for the Para WebView to be ready and updates the session state accordingly.
### Wallet Management Methods
Retrieves all wallets associated with the current user.
An array of `Wallet` objects associated with the user.
Creates a new wallet for the authenticated user. This method initiates wallet creation and refreshes the internal `wallets` list. Observe the `paraManager.wallets` property for the new wallet.
The type of wallet to create (e.g., `.evm`, `.solana`, `.cosmos`).
Whether to skip the distributable key generation step.
Retrieves the email of the currently authenticated user, if available and the session was initiated with an email.
The user's email address.
(Advanced) Distributes a new share for an existing wallet.
The ID of the wallet.
The user share to distribute.
Gets the balance for any wallet type. This unified method works with all wallet types (EVM, Solana, Cosmos).
The ID of the wallet.
Optional token identifier (contract address for EVM, mint address for Solana, etc.).
Optional RPC URL (recommended for Solana and Cosmos to avoid 403/CORS issues).
Optional bech32 prefix for Cosmos (e.g., "juno", "stars").
Optional denom for Cosmos balances (e.g., "ujuno", "ustars").
The balance as a string (format depends on the chain).
Synchronizes required wallets by creating any missing non-optional wallet types. This should be called after successful authentication for new users.
Array of newly created wallets, if any.
### Transfer Methods
Transfers tokens using an EVM wallet. This method builds, signs, and broadcasts the transaction in one call.
The ID of the EVM wallet to use for the transfer.
The recipient address.
The amount to send in wei (smallest unit).
Optional. The chain ID for the EVM network. If not provided, uses the wallet's default chain.
Optional. Custom RPC URL for the transaction. If not provided, uses the default RPC for the chain.
A TransferResult object containing transaction details.
This method is only available for EVM wallets. Solana and Cosmos wallets must use signing methods only.
### Signing Methods
Signs an arbitrary message using the specified wallet.
The ID of the wallet to use for signing.
The raw message string to sign. The SDK will Base64-encode this string before signing.
Optional. Timeout for the signing operation in milliseconds.
A SignatureResult object containing the signature and metadata.
Signs a transaction object using the specified wallet. The method accepts any Encodable transaction type (EVMTransaction, SolanaTransaction, CosmosTransaction) and handles chain-specific formatting internally.
The ID of the wallet to use for signing.
The transaction object to sign. Can be EVMTransaction, SolanaTransaction, or CosmosTransaction.
Optional. Timeout for the signing operation in milliseconds.
A SignatureResult containing the signed transaction. For EVM, this includes the complete RLP-encoded transaction in `signedTransaction`. For Solana/Cosmos, if only a signature is available (e.g., pre-serialized transactions), it's provided in `signedTransaction`. Use `result.transactionData` (computed property) to get the value for broadcasting.
For EVM transactions, the returned `SignatureResult` contains the complete RLP-encoded transaction ready for broadcasting via `eth_sendRawTransaction`.
Signs an RLP-encoded EVM transaction string using the private key of the specified wallet. This method is maintained for backward compatibility.
The ID of the EVM wallet to use for signing.
The RLP-encoded transaction as a hex string. The SDK will Base64-encode this string.
The chain ID of the EVM network.
Optional. Timeout for the signing operation in milliseconds.
The resulting transaction signature as a hex string.
### Two-Factor Authentication (2FA) Methods
Initiates the setup process for 2FA.
Returns `.alreadySetup` if 2FA is already configured, or `.needsSetup(uri: String)` containing the URI to be displayed in an authenticator app (e.g., as a QR code).
Enables 2FA for the user after they have scanned the URI and entered the code from their authenticator app.
The 2FA code from the user's authenticator app.
## MetaMaskConnector Class
The `MetaMaskConnector` class enables integration with MetaMask wallet for external wallet operations and authentication.
### Properties
Indicates whether the connector is currently connected to MetaMask.
Array of connected MetaMask account addresses.
The current chain ID from MetaMask (e.g., "0x1" for Ethereum mainnet, "0xaa36a7" for Sepolia).
### Initialization
Initializes a new `MetaMaskConnector` instance.
The ParaManager instance.
Your app's URL scheme for deep link callbacks.
Configuration object containing app metadata.
### Methods
Processes incoming deep link URLs from MetaMask responses.
The incoming URL to process.
Initiates connection to MetaMask and requests account access. This method is `async throws`. Upon successful connection, the `accounts`, `chainId`, and `isConnected` properties of the `MetaMaskConnector` instance are updated. `ParaManager.loginExternalWallet` is also called internally.
Requests MetaMask to sign a message with the specified account.
The message string to be signed.
The account address to use for signing.
The resulting signature from MetaMask.
Sends a transaction through MetaMask using the specified account.
The transaction object or dictionary containing transaction parameters.
The account address to use for the transaction.
The transaction hash returned by MetaMask.
## Error Types
The main error type for Para SDK operations.
An error occurred while executing JavaScript bridge code.
The JavaScript bridge did not respond in time.
A general error occurred.
Feature not implemented yet.
Errors specific to MetaMask connector operations.
Another MetaMask operation is already in progress.
Failed to construct a valid URL for MetaMask communication.
Received an invalid or unexpected response from MetaMask.
An error reported by the MetaMask app itself (e.g., user rejection code 4001).
MetaMask app is not installed on the device.
Errors specific to CosmosTransaction operations.
Invalid address format.
Invalid amount format.
Invalid denomination.
Invalid chain prefix.
Transaction encoding failed.
Errors related to authorization handling in external wallet operations.
Invalid response received from authorization process.
Authorization was cancelled by the user.
### CosmosChain
Supported Cosmos chains with testnet configurations.
Cosmos Hub network with chain ID "provider", prefix "cosmos" and default denom "uatom" (testnet).
Osmosis network with chain ID "osmo-test-5", prefix "osmo" and default denom "uosmo" (testnet).
Juno network with chain ID "uni-6", prefix "juno" and default denom "ujuno" (testnet).
Stargaze network with chain ID "elgafar-1", prefix "stars" and default denom "ustars" (testnet).
The chain ID for this network.
The bech32 prefix for this chain.
Default denomination for this chain.
Default RPC URL for this chain's testnet.
### CosmosSigningMethod
Signing method for Cosmos transactions.
Legacy Amino JSON signing method for backward compatibility.
Modern Proto/Direct binary signing method (recommended - more efficient).
### CosmosTransaction
A struct representing a Cosmos transaction with messages, fees, and metadata.
The transaction messages.
Transaction fee.
Optional memo.
Preferred signing method (defaults to proto).
Convenience initializer for creating token transfer transactions.
### CosmosSignResponse
Response from a Cosmos transaction signing operation.
The signature information.
The signed document.
### CosmosCoin
Represents a coin amount in Cosmos with denomination and amount.
The token denomination (e.g., "uatom").
The amount as a string in the smallest unit.
Creates a new coin with string amount.
Creates a new coin with UInt64 amount.
### CosmosFee
Represents transaction fees in Cosmos.
Array of fee coins.
Gas limit as a string.
Creates a fee with array of coins.
Convenience initializer for simple fee.
## Type Definitions
### SignatureResult
Result of a message or transaction signing operation.
For transaction signing: Contains the complete signed transaction ready for broadcasting (RLP-encoded for EVM, serialized for other chains).
For message signing: Contains just the signature.
Note: The bridge returns `signature` for messages and `signedTransaction` for transactions, but Swift SDK normalizes both to `signedTransaction` for consistency.
Computed property that returns the signed transaction data for broadcasting.
The ID of the wallet that performed the signing.
The wallet type that signed (e.g., "evm", "solana", "cosmos").
### AuthState
Authentication state returned by authentication operations.
The stage of authentication (`.verify`, `.signup`, `.login`).
The Para userId for the currently authenticating user.
Email address if using email auth.
Phone number if using phone auth.
Display name for the authenticating user.
Profile picture URL for the authenticating user.
Username for the authenticating user.
URL for passkey authentication.
ID for the passkey.
URL for passkey authentication on a known device.
URL for password authentication.
Biometric hints for the user's devices.
### TransferResult
Result returned from a successful EVM transfer operation.
The transaction hash on the blockchain.
The sender address.
The recipient address.
The amount transferred in wei (smallest unit).
The chain ID where the transaction was broadcast.
### Auth
Authentication type for initiateAuthFlow method.
Email-based authentication.
Phone-based authentication with full phone number including country code.
### OAuthProvider
Supported OAuth provider types for authentication.
Google authentication provider.
Discord authentication provider.
Apple authentication provider.
### SignupMethod & LoginMethod
The possible methods for signup when the stage is .signup.
Passkey-based signup.
Password-based signup.
The possible methods for login when the stage is .login.
Passkey-based login.
Password-based login.
Passkey login on a known device.
### Wallet
Represents a wallet associated with a Para user account.
Unique identifier for the wallet.
The type of wallet (e.g., `.evm`, `.solana`, `.cosmos`).
Primary public address of the wallet.
Secondary address (e.g., Cosmos bech32 address for Cosmos wallets).
Public key of the wallet.
Scheme used by the wallet.
Timestamp when the wallet was created.
### WalletType
Supported wallet types for Para wallets.
Ethereum Virtual Machine compatible wallet (Ethereum, Polygon, etc.).
Solana blockchain wallet.
Cosmos ecosystem wallet supporting bech32 addresses.
### MetaMaskConfig
Configuration object for MetaMask connector initialization.
The name of your application as displayed in MetaMask.
Your application's identifier (typically the bundle identifier).
The API version to use (e.g., "1.0").
# Mobile Examples
Source: https://docs.getpara.com/v3/swift/examples
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
Para offers a comprehensive Swift example to help you integrate our technology into your iOS applications. Our example repository demonstrates minimal implementation requirements with clean, focused code that you can easily adapt to your specific application needs.
## Para Swift Example
Explore our Swift example showcasing Para integration with various features:
The Swift example demonstrates:
- Native iOS passkey authentication
- Face ID and Touch ID integration
- Wallet creation and management
- Transaction signing across multiple chains
- Session management
- Integration with Apple's Secure Enclave
- iCloud Sharechain support
## Need Something Specific?
Don't see an example for your use case? Para's team is eager to create new examples to help you integrate with different libraries, third-party features, or providers.
# Account Abstraction
Source: https://docs.getpara.com/v3/swift/guides/account-abstraction
import { Card } from '/snippets/v3/components/ui/card.mdx';
The Swift SDK can spin up an Alchemy smart account for any EVM wallet your user holds. The Para wallet signs; the smart account is what shows up on-chain. You get gas sponsorship through Alchemy's Gas Manager and atomic batched calls through the Account Kit.
Only Alchemy (EIP-4337) is wired through the native bridge today. ZeroDev, Pimlico, and the other providers already available on web and React Native are on the roadmap for iOS.
## Setup
1. Create an [Alchemy account](https://dashboard.alchemy.com/signup), grab your **API key**, and create a Gas Manager **policy** for the chain you're targeting. The policy ID is what tells the paymaster to sponsor gas.
2. Stash the values wherever you keep other credentials. A plain `Config` struct works:
```swift
enum AlchemyConfig {
static let apiKey = "YOUR_ALCHEMY_API_KEY"
static let gasPolicyId = "YOUR_GAS_POLICY_ID"
static let sepoliaChainId = 11155111
}
```
You don't need to add a package. `ParaSwift` already knows how to reach `@getpara/aa-alchemy` through the bridge.
## Usage
Create the smart account, then send a gasless transaction with it:
```swift
import ParaSwift
// `paraManager` is your authenticated ParaManager instance
let info = try await paraManager.createSmartAccount(
apiKey: AlchemyConfig.apiKey,
chainId: AlchemyConfig.sepoliaChainId,
gasPolicyId: AlchemyConfig.gasPolicyId
)
print("Smart account:", info.smartAccountAddress)
// Zero-value tx to the burn address, paid for by the paymaster.
let receipt = try await paraManager.sendSmartAccountTransaction(
smartAccountAddress: info.smartAccountAddress,
chainId: AlchemyConfig.sepoliaChainId,
to: "0x000000000000000000000000000000000000dEaD"
)
print("tx:", receipt.transactionHash, "status:", receipt.status)
```
Heads up on timing: UserOp inclusion on a public chain usually lands in 30 to 90 seconds, and the call blocks until it does. `ParaWebView` defaults its request timeout to 120s for this reason. Pass a shorter value to `ParaWebView.init` if you'd rather bail earlier.
### Batched transactions
One UserOp, multiple calls, one receipt. Use it for approve + swap, approve + transfer, or anything that should succeed or fail together:
```swift
let batchReceipt = try await paraManager.sendSmartAccountBatchTransaction(
smartAccountAddress: info.smartAccountAddress,
chainId: AlchemyConfig.sepoliaChainId,
calls: [
SmartAccountCall(to: "0xRecipientA", value: "10000000000000000"), // 0.01 ETH
SmartAccountCall(to: "0xRecipientB", data: "0xEncodedCallData")
]
)
```
### Error handling
Provider failures come back with a `SmartAccountErrorCode` from `@getpara/core-sdk`. The ones you're most likely to see:
- `PROVIDER_RATE_LIMITED`: back off and retry.
- `SPONSORSHIP_DENIED`: the gas policy rejected this UserOp. Check the Alchemy dashboard.
- `TRANSACTION_REVERTED`: the target contract reverted at execution time.
- `MISSING_ACCOUNT_ADDRESS`: you called `sendSmartAccountTransaction` before `createSmartAccount` for this chain + address.
Errors surface as `ParaError.bridgeError` and keep the provider's original message. If a permissions policy blocks the tx, the call throws `ParaError.transactionDenied(pendingTransactionId:transactionReviewUrl:)` and fires `setTransactionReviewHandler` if you've registered one. Same pattern as `signTransaction`.
## What gets returned
`createSmartAccount` returns a `SmartAccountInfo`:
```swift
struct SmartAccountInfo {
let smartAccountAddress: String
let mode: String // "4337"
let provider: String // "ALCHEMY"
let chainId: Int
}
```
`sendSmartAccountTransaction` and `sendSmartAccountBatchTransaction` return an `AATransactionReceipt`:
```swift
struct AATransactionReceipt {
let transactionHash: String
let blockHash: String
let blockNumber: String // uint256, decimal string
let from: String
let to: String?
let status: String // "success" or "reverted"
let gasUsed: String
let effectiveGasPrice: String
}
```
BigInt fields arrive as decimal strings so you don't lose `uint256` precision across the bridge.
## Reference
Full working implementation: [`examples-hub/mobile/with-swift/example/SmartAccount/SmartAccountView.swift`](https://github.com/getpara/examples-hub/tree/3.0.0/mobile/with-swift/example/SmartAccount).
# Cosmos Integration
Source: https://docs.getpara.com/v3/swift/guides/cosmos
import { Card } from '/snippets/v3/components/ui/card.mdx';
## Quick Start
```swift
import ParaSwift
// Sign a Cosmos transaction
let paraManager = ParaManager(apiKey: "your-api-key")
// Get an existing Cosmos wallet or create one
let wallets = try await paraManager.fetchWallets()
let wallet: Wallet
if let existing = wallets.first(where: { $0.type == .cosmos }) {
wallet = existing
} else {
wallet = try await paraManager.createWallet(type: .cosmos, skipDistributable: false)
}
let 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"
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction,
chainId: "theta-testnet-001"
)
print("Transaction signed: \(result.signedTransaction)")
```
## Common Operations
### Sign Transactions for Different Chains
```swift
// Sign ATOM transaction on Cosmos Hub testnet
let atomTx = CosmosTransaction(
to: "cosmos1recipient...",
amount: "1000000", // 1 ATOM
denom: "uatom",
chainId: "theta-testnet-001", // Testnet
format: "proto"
)
// Sign OSMO transaction on Osmosis testnet
let osmoTx = CosmosTransaction(
to: "osmo1recipient...",
amount: "1000000", // 1 OSMO
denom: "uosmo",
chainId: "osmo-test-5", // Testnet
format: "proto"
)
// Sign JUNO transaction on Juno mainnet
let junoTx = CosmosTransaction(
to: "juno1recipient...",
amount: "1000000", // 1 JUNO
denom: "ujuno",
chainId: "juno-1",
format: "proto"
)
```
### Sign Transaction
```swift
let 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
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction,
chainId: "theta-testnet-001",
rpcUrl: "https://rpc.sentry-01.theta-testnet.polypore.xyz"
)
// Cosmos returns: { signBytes, signDoc, format }
print("Signed: \(result.signedTransaction)")
```
### Get Wallet Address
```swift
// Get the Cosmos wallet address for a specific chain
let wallet = (try await paraManager.fetchWallets()).first { $0.type == .cosmos }!
let address = wallet.address // Returns the primary address
print("Cosmos address: \(address ?? "No address")")
```
### Check Balance
```swift
// Native token balance
let balance = try await paraManager.getBalance(
walletId: wallet.id,
rpcUrl: "https://cosmos-rpc.publicnode.com",
chainPrefix: "cosmos" // Important: specify chain prefix for address derivation
)
print("Balance: \(balance) uatom")
// Different chain
let osmoBalance = try await paraManager.getBalance(
walletId: wallet.id,
rpcUrl: "https://osmosis-rpc.publicnode.com",
chainPrefix: "osmo" // Different prefix for Osmosis
)
print("Balance: \(osmoBalance) uosmo")
```
### Sign Message
```swift
let message = "Hello, Cosmos!"
let result = try await paraManager.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
```swift
import SwiftUI
import ParaSwift
struct CosmosWalletView: View {
@EnvironmentObject var paraManager: ParaManager
@State private var selectedChain = "theta-testnet-001"
@State private var isLoading = false
@State private var result: String?
let wallet: Wallet
let chains = [
"theta-testnet-001": ("Cosmos Hub Testnet", "https://rpc.sentry-01.theta-testnet.polypore.xyz", "uatom", "cosmos"),
"osmo-test-5": ("Osmosis Testnet", "https://rpc.osmotest5.osmosis.zone", "uosmo", "osmo")
]
var body: some View {
VStack(spacing: 20) {
Picker("Chain", selection: $selectedChain) {
ForEach(chains.keys.sorted(), id: \.self) { chainId in
Text(chains[chainId]!.0).tag(chainId)
}
}
.pickerStyle(.segmented)
Text(wallet.address ?? "No address")
.font(.system(.caption, design: .monospaced))
Button(action: signTransaction) {
Text(isLoading ? "Signing..." : "Sign Transaction for \(chains[selectedChain]!.0)")
}
.disabled(isLoading)
if let result = result {
Text(result)
.font(.caption)
}
}
.padding()
}
private func signTransaction() {
isLoading = true
Task {
do {
let chainInfo = chains[selectedChain]!
let transaction = CosmosTransaction(
to: "\(chainInfo.3)1recipient...", // Uses chain prefix
amount: "1000000", // 1 token in micro-units
denom: chainInfo.2,
memo: "Test transaction from Swift",
chainId: selectedChain,
format: "proto"
)
let txResult = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction,
chainId: selectedChain,
rpcUrl: chainInfo.1
)
result = "Signed! Signature: \(txResult.signedTransaction)"
} catch {
result = "Error: \(error)"
}
isLoading = false
}
}
}
```
## Proto vs Amino Formats
```swift
// Modern Proto format (recommended)
let protoTx = CosmosTransaction(
to: "cosmos1recipient...",
amount: "1000000",
denom: "uatom",
format: "proto",
chainId: "theta-testnet-001" // Testnet
)
// Legacy Amino format (compatibility)
let aminoTx = CosmosTransaction(
to: "cosmos1recipient...",
amount: "1000000",
denom: "uatom",
format: "amino",
chainId: "theta-testnet-001" // Testnet
)
// Use convenience constructor
let simpleTx = CosmosTransaction(
to: "cosmos1recipient...",
amount: "1000000",
denom: "uatom",
chainId: "theta-testnet-001"
)
```
# Email & Phone Login
Source: https://docs.getpara.com/v3/swift/guides/email-phone-login
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import { Link } from '/snippets/v3/components/ui/link.mdx';
import BetaCredentialsFull from "/snippets/v3/beta-credentials/beta-credentials-full.mdx";
## Overview
Para One Click Login is the default experience—Para hosts the entire flow for existing users and new signups. If you turn on passkey or password requirements in the Developer Portal, the optional sections below show how to handle them.
Need to install the SDK? Start with the .
Para ships with One Click enabled. You can add requirements like Passkey-only signup or password fallback from the . Adjust the optional sections below only if you turn on those features.
## Start the Authentication Flow
Inject the Para manager and authentication helpers, then launch the flow with whichever identifier your UI collects (email or phone). Para One Click handles the rest unless you enable extra verification.
```swift AuthenticationView.swift
import AuthenticationServices
import ParaSwift
struct AuthenticationView: View {
@EnvironmentObject var paraManager: ParaManager
@Environment(\.authorizationController) private var authorizationController
@Environment(\.webAuthenticationSession) private var webAuthenticationSession
@State private var pendingState: AuthState? // Only used if you enable OTP/passkey requirements
var body: some View {
VStack {
// Render your auth UI and call startAuth(identifier:)
}
.task {
paraManager.setDefaultWebAuthenticationSession(webAuthenticationSession)
}
}
private func startAuth(identifier: String) {
let auth: Auth = identifier.contains("@")
? .email(identifier.lowercased())
: .phone(identifier) // Expect E.164 format
Task {
do {
let state = try await paraManager.initiateAuthFlow(auth: auth)
switch state.stage {
case .done:
completeLogin()
case .verify:
// Store state to finish OTP/passkey signup later if you enabled it
pendingState = state
case .login:
try await paraManager.handleLogin(
authState: state,
authorizationController: authorizationController
)
completeLogin()
case .signup:
// Signup completes after OTP + passkey if required
break
}
} catch {
// Handle errors according to your UI needs
}
}
}
}
```
The `completeLogin()` helper can mark your app as authenticated and transition to the signed-in experience:
```swift AuthenticationView.swift
private func completeLogin() {
// Mark the user as authenticated in your app
}
```
## Handle OTP Signup (Optional)
Only implement this step if you turn on OTP / passkey signup in the Developer Portal. Collect the OTP, exchange it for a verified state, and finish signup:
```swift AuthenticationView.swift
private func submitOtp(_ code: String) {
guard let pendingState else { return }
Task {
do {
let verifiedState = try await paraManager.handleVerificationCode(
verificationCode: code
)
try await paraManager.handleSignup(
authState: verifiedState,
method: .passkey,
authorizationController: authorizationController
)
completeLogin()
} catch {
// Handle errors according to your UI needs
}
}
}
```
If you skip `setDefaultWebAuthenticationSession(_:)`, `initiateAuthFlow` returns a `loginUrl` you can present manually using `presentAuthUrl(_:context:webAuthenticationSession:)`. The automatic `.done` stage shown above relies on the default session being set.
## Support Direct Passkey Login
If you already know a user has passkeys registered, you can offer a shortcut that skips email/phone input:
```swift AuthenticationView.swift
private func loginWithPasskey(email: String?) {
Task {
do {
try await paraManager.loginWithPasskey(
authorizationController: authorizationController,
email: email,
phone: nil
)
completeLogin()
} catch {
// Handle errors according to your UI needs
}
}
}
```
## Next Steps
- Add social providers alongside this flow in
- Manage existing sessions with
# EVM Integration
Source: https://docs.getpara.com/v3/swift/guides/evm
import { Card } from '/snippets/v3/components/ui/card.mdx';
## Quick Start
### Transfer
Para handles signing and broadcasting in one call:
```swift
import ParaSwift
let paraManager = ParaManager(apiKey: "your-api-key")
// Get an existing EVM wallet or create one
let wallets = try await paraManager.fetchWallets()
let wallet: Wallet
if let existing = wallets.first(where: { $0.type == .evm }) {
wallet = existing
} else {
wallet = try await paraManager.createWallet(type: .evm, skipDistributable: false)
}
// Send ETH - Para signs and broadcasts
let result = try await paraManager.transfer(
walletId: wallet.id,
to: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
amount: "1000000000000000", // 0.001 ETH in wei
chainId: "11155111", // Optional: Sepolia testnet (defaults to wallet's chain)
rpcUrl: nil // 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:
```swift
import ParaSwift
import BigInt
// Step 1: Sign transaction with Para
let transaction = EVMTransaction(
to: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
value: BigUInt("1000000000000000")!,
gasLimit: BigUInt("21000")!
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction,
chainId: "11155111" // Sepolia testnet
)
// Step 2: Broadcast using your preferred method (e.g., web3.swift)
// The transactionData property provides the complete signed transaction
// let txHash = try await broadcastWithWeb3Swift(result.transactionData)
```
## Common Operations
### Send ETH
```swift
// Para handles everything - signing and broadcasting
let result = try await paraManager.transfer(
walletId: wallet.id,
to: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
amount: "1000000000000000", // 0.001 ETH in wei
chainId: "11155111", // Optional: Sepolia testnet
rpcUrl: nil // Optional: custom RPC URL
)
print("Transaction hash: \(result.hash)")
print("From: \(result.from), To: \(result.to), Chain: \(result.chainId)")
```
### Sign Transaction
```swift
import BigInt
let transaction = EVMTransaction(
to: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
value: BigUInt("1000000000000000")!,
gasLimit: BigUInt("21000")!,
maxPriorityFeePerGas: BigUInt("1000000000")!,
maxFeePerGas: BigUInt("3000000000")!,
nonce: BigUInt("0")!,
chainId: BigUInt("11155111")!, // Sepolia
type: 2
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction,
chainId: "11155111"
)
// The transactionData property 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
```swift
// Native ETH balance
let ethBalance = try await paraManager.getBalance(walletId: wallet.id)
// ERC-20 token balance
let tokenBalance = try await paraManager.getBalance(
walletId: wallet.id,
token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // USDC
)
```
### Sign Message
```swift
let message = "Hello, Ethereum!"
let result = try await paraManager.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
```swift
import SwiftUI
import ParaSwift
import BigInt
struct EVMWalletView: View {
@EnvironmentObject var paraManager: ParaManager
@State private var isLoading = false
@State private var txHash: String?
let wallet: Wallet
var body: some View {
VStack(spacing: 20) {
Text(wallet.address ?? "No address")
.font(.system(.caption, design: .monospaced))
Button(action: sendETH) {
Text(isLoading ? "Sending..." : "Send 0.001 ETH")
}
.disabled(isLoading)
if let txHash = txHash {
Text("Sent: \(txHash)")
.font(.caption)
}
}
.padding()
}
private func sendETH() {
isLoading = true
Task {
do {
let result = try await paraManager.transfer(
walletId: wallet.id,
to: "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
amount: "1000000000000000",
chainId: "11155111", // Optional: Sepolia testnet
rpcUrl: nil // Optional: custom RPC
)
txHash = result.hash
} catch {
print("Error: \(error)")
}
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.
```swift
// Call a contract function
let contractTransaction = EVMTransaction(
to: "0x123abc...", // Contract address
value: BigUInt(0),
gasLimit: BigUInt("150000")!,
maxPriorityFeePerGas: BigUInt("1000000000")!,
maxFeePerGas: BigUInt("3000000000")!,
nonce: BigUInt("0")!,
chainId: BigUInt("11155111")!, // Sepolia testnet
smartContractAbi: """
[{
"inputs": [{"name":"num","type":"uint256"}],
"name": "store",
"type": "function"
}]
""",
smartContractFunctionName: "store",
smartContractFunctionArgs: ["42"],
type: 2
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: contractTransaction,
chainId: "11155111" // Sepolia testnet
)
// Use result.transactionData to get the complete signed transaction
print("Signed transaction: \(result.transactionData)")
```
### ERC20 Token Transfer
```swift
// Transfer USDC on Sepolia testnet
let usdcTransaction = EVMTransaction(
to: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238", // USDC contract on Sepolia
value: BigUInt(0), // No ETH value for token transfer
gasLimit: BigUInt("100000")!, // Higher gas limit for token transfers
maxPriorityFeePerGas: BigUInt("1000000000")!,
maxFeePerGas: BigUInt("3000000000")!,
nonce: BigUInt("0")!,
chainId: BigUInt("11155111")!, // Sepolia testnet
smartContractAbi: """
[{
"inputs": [
{"name": "recipient", "type": "address"},
{"name": "amount", "type": "uint256"}
],
"name": "transfer",
"outputs": [{"name": "", "type": "bool"}],
"type": "function"
}]
""",
smartContractFunctionName: "transfer",
smartContractFunctionArgs: [
"0xcb53FD7529d257D40618992993c5F863f5d86572", // Recipient address
"100000" // 0.1 USDC (6 decimals, so 100000 = 0.1)
],
type: 2
)
// Sign the transaction - Para bridge handles ABI encoding
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: usdcTransaction,
chainId: "11155111"
)
// The signed transaction is ready to broadcast
// Para encodes the function call data using the provided ABI
print("Signed ERC20 transfer: \(result.transactionData)")
// You can broadcast using your preferred method
// The transaction will transfer 0.1 USDC to the recipient
```
# External Wallets
Source: https://docs.getpara.com/v3/swift/guides/external-wallets
import EnvironmentInfo from "/snippets/v3/quick-start-environment-info.mdx";
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import BetaCredentialsFull from "/snippets/v3/beta-credentials/beta-credentials-full.mdx";
import { Link } from '/snippets/v3/components/ui/link.mdx';
## Overview
This guide outlines how to integrate and use external cryptocurrency wallets with the ParaSwift SDK. With the unified wallet architecture, the Swift SDK has removed all blockchain-specific dependencies and now provides a streamlined approach to external wallet connectivity.
The SDK supports external wallets like MetaMask through deep-linking protocols and provides built-in authentication and transaction signing capabilities. You can also use external wallet addresses to authenticate with Para.
**Unified Wallet Architecture:** The Swift SDK uses only BigInt for handling blockchain values and avoids blockchain-specific package dependencies such as web3swift or solana-swift. This provides a cleaner, more maintainable architecture while still supporting all necessary external wallet operations.
The SDK now has minimal dependencies:
- **BigInt**: For handling large blockchain numbers (Wei, gas values, etc.)
- **PhoneNumberKit**: For phone number validation in authentication flows
## Deep Linking
The ParaSwift SDK communicates with other apps via deep linking. To enable deep linking for your application, you'll need to configure your app appropriately.
### Configure URL Schemes
First, you need to configure your app to handle custom URL schemes:
1. Open your app's Info.plist file
2. Add a new entry for `LSApplicationQueriesSchemes` as an array
3. Add the URL schemes of supported wallets as strings to this array
```xml
LSApplicationQueriesSchemes
metamask
```
### Configure URL Types
Next, you need to set up URL Types to handle callbacks from external wallets:
1. In Xcode, select your app target
2. Go to "Info" tab
3. Expand "URL Types"
4. Click the "+" button to add a new URL Type
5. Set the "Identifier" to your app's bundle identifier
6. Set the "URL Schemes" to a unique identifier for your app
**Important:** You must use a unique URL scheme to avoid conflicts with other apps. We recommend using reverse-domain notation like `com.yourcompany.yourapp`.
In our examples we use `paraswift`, but you **must replace this** with your own unique scheme:
```xml
CFBundleURLTypes
CFBundleURLSchemes
com.yourcompany.yourapp
```
## MetaMask Connector
### Setup
Create and initialize the MetaMask connector with your app's configuration:
```swift
import ParaSwift
// Create MetaMask configuration
let appScheme = "com.yourcompany.yourapp" // Replace with your unique scheme
let metaMaskConfig = MetaMaskConfig(
appName: "Your App Name",
appId: appScheme,
apiVersion: "1.0"
)
// Initialize the connector
let metaMaskConnector = MetaMaskConnector(
para: paraManager,
appUrl: "https://\(appScheme)",
config: metaMaskConfig
)
```
### Handle Deep Links
Handle incoming deep links from MetaMask by adding a URL handler in your SwiftUI app:
```swift
@main
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// Handle MetaMask deep links with URL validation
if url.scheme == "yourapp", url.host == "mmsdk" {
MetaMaskConnector.handleDeepLink(url)
}
}
}
}
}
```
The deep link handling has been simplified to use a static method. You no longer need to maintain a MetaMaskConnector instance at the app level just for handling deep links.
### Connecting
To connect to MetaMask and retrieve the user's accounts:
```swift
do {
try await metaMaskConnector.connect()
// MetaMask will automatically attempt Para authentication after connection
// Access connected accounts via metaMaskConnector.accounts
} catch {
// Handle connection error
}
```
**Automatic Authentication:** The MetaMask connector automatically attempts to authenticate with Para after a successful connection. This streamlines the user experience by reducing the number of manual steps required.
### Sign Message
To request a signature for a message from MetaMask:
```swift
guard let account = metaMaskConnector.accounts.first else { return }
do {
let signature = try await metaMaskConnector.signMessage(
"Message to sign! Hello World",
account: account
)
// Use the signature
} catch {
// Handle signing error
}
```
### Send Transaction
To send a transaction through MetaMask using the EVMTransaction model:
```swift
import BigInt
guard let account = metaMaskConnector.accounts.first else { return }
do {
// Convert 0.001 ETH to wei (1 ETH = 10^18 wei)
let valueInWei = BigUInt("1000000000000000")! // 0.001 ETH in wei
let gasLimit = BigUInt(100000)
let transaction = EVMTransaction(
to: "0x13158486860B81Dee9e43Dd0391e61c2F82B577F",
value: valueInWei,
gasLimit: gasLimit
)
let txHash = try await metaMaskConnector.sendTransaction(transaction, account: account)
// Transaction sent successfully, use txHash
} catch {
// Handle transaction error
}
```
### Alternative: Raw Transaction Format
You can also use a raw transaction dictionary format:
```swift
guard let account = metaMaskConnector.accounts.first else { return }
let transaction: [String: String] = [
"from": account,
"to": "0x13158486860B81Dee9e43Dd0391e61c2F82B577F",
"value": "0x38D7EA4C68000", // 0.001 ETH in wei (hex)
"gasLimit": "0x186A0" // 100000 in hex
]
do {
let txHash = try await metaMaskConnector.sendTransaction(
transaction,
account: account
)
// Transaction sent successfully
} catch {
// Handle transaction error
}
```
### Properties
The MetaMask connector provides several useful properties:
```swift
// Check if connected to MetaMask
let isConnected = metaMaskConnector.isConnected
// Get list of connected accounts
let accounts = metaMaskConnector.accounts
// Get current chain ID (e.g., "0x1" for Ethereum mainnet)
let chainId = metaMaskConnector.chainId
```
### Supported Networks
The MetaMask connector works with any EVM-compatible network that MetaMask supports. Common networks include:
| Network | Chain ID | Description |
|---------|----------|-------------|
| Ethereum Mainnet | `0x1` | Main Ethereum network |
| Sepolia Testnet | `0xaa36a7` | Ethereum test network |
| Polygon | `0x89` | Polygon mainnet |
| Arbitrum One | `0xa4b1` | Arbitrum Layer 2 |
| Base | `0x2105` | Base Layer 2 |
## Working with BigUInt Values
Since the Swift SDK now uses BigInt for handling blockchain values, here are some utility patterns for working with Ether and Wei conversions:
```swift
import BigInt
// Convert Ether to Wei (multiply by 10^18)
func etherToWei(_ ether: String) -> BigUInt? {
guard let etherDecimal = Decimal(string: ether) else { return nil }
let weiDecimal = etherDecimal * pow(10, 18)
return BigUInt(weiDecimal.description.components(separatedBy: ".").first ?? "")
}
// Convert Wei to Ether (divide by 10^18)
func weiToEther(_ wei: BigUInt) -> String {
let weiDecimal = Decimal(string: wei.description) ?? 0
let etherDecimal = weiDecimal / pow(10, 18)
return etherDecimal.description
}
// Usage examples
let oneEthInWei = etherToWei("1.0")! // 1000000000000000000
let halfEthInWei = etherToWei("0.5")! // 500000000000000000
let pointZeroOneEthInWei = etherToWei("0.01")! // 10000000000000000
// Convert back to Ether
let ethAmount = weiToEther(BigUInt("1000000000000000000")!) // "1"
```
These utility functions help you convert between human-readable Ether amounts and the Wei values required by the blockchain. Always validate decimal inputs to prevent runtime crashes.
## Advanced Configuration
### Customizing MetaMask Connection
You can customize various aspects of the MetaMask connection by modifying the MetaMaskConfig:
```swift
let metaMaskConfig = MetaMaskConfig(
appName: "Your App Name",
appId: bundleId,
apiVersion: "1.0"
)
```
### Working with Different Networks
MetaMask supports multiple networks. You can check the current network and adjust your app's behavior accordingly:
```swift
switch metaMaskConnector.chainId {
case "0x1":
// Ethereum Mainnet
case "0x5":
// Goerli Testnet
case "0x89":
// Polygon
default:
// Other network
}
```
## External Wallet Authentication with Para
Para supports authentication using external wallets like MetaMask. This allows users to log into Para using their existing wallet credentials.
### Using External Wallet for Para Login
After connecting to MetaMask, you can use the wallet address to authenticate with Para:
```swift
// First, connect to MetaMask
try await metaMaskConnector.connect()
// Get the first connected account
guard let account = metaMaskConnector.accounts.first else {
throw ParaError.error("No MetaMask accounts found")
}
// Create external wallet info for Para authentication
let externalWallet = ExternalWalletInfo(
address: account,
type: .evm,
provider: "metamask",
isConnectionOnly: false // Set to false for full authentication
)
// Login to Para using the external wallet
try await paraManager.loginExternalWallet(wallet: externalWallet)
// User is now logged into Para with their MetaMask wallet
```
### Complete External Wallet Login Example
Here's a complete example showing external wallet authentication:
```swift
import SwiftUI
import ParaSwift
struct ExternalWalletLoginView: View {
@EnvironmentObject var paraManager: ParaManager
@EnvironmentObject var appRootManager: AppRootManager
@StateObject private var metaMaskConnector: MetaMaskConnector
@State private var isConnecting = false
@State private var errorMessage: String?
init(paraManager: ParaManager) {
let config = MetaMaskConfig(
appName: "Your App",
appId: "com.yourcompany.yourapp", // Use your unique app scheme
apiVersion: "1.0"
)
_metaMaskConnector = StateObject(wrappedValue: MetaMaskConnector(
para: paraManager,
appUrl: "https://com.yourcompany.yourapp", // Use your unique app scheme
config: config
))
}
var body: some View {
VStack(spacing: 20) {
Image("metamask")
.resizable()
.frame(width: 80, height: 80)
Text("Connect with MetaMask")
.font(.title2)
.fontWeight(.semibold)
Text("Use your existing MetaMask wallet to log into Para")
.font(.body)
.foregroundColor(.secondary)
.multilineTextAlignment(.center)
if let errorMessage = errorMessage {
Text(errorMessage)
.foregroundColor(.red)
.font(.caption)
.padding()
.background(Color.red.opacity(0.1))
.cornerRadius(8)
}
Button {
connectAndLogin()
} label: {
if isConnecting {
ProgressView()
.tint(.white)
} else {
Text("Connect MetaMask")
.fontWeight(.semibold)
}
}
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
.disabled(isConnecting)
}
.padding()
.navigationTitle("External Wallet")
}
private func connectAndLogin() {
isConnecting = true
errorMessage = nil
Task {
do {
// Connect to MetaMask
try await metaMaskConnector.connect()
// Get the first account
guard let account = metaMaskConnector.accounts.first else {
throw ParaError.error("No MetaMask accounts found")
}
// Create external wallet info
let externalWallet = ExternalWalletInfo(
address: account,
type: .evm,
provider: "metamask",
isConnectionOnly: false // Set to false for full authentication
)
// Login to Para using external wallet
try await paraManager.loginExternalWallet(wallet: externalWallet)
// Navigate to home on success
appRootManager.currentRoot = .home
} catch {
errorMessage = error.localizedDescription
}
isConnecting = false
}
}
}
```
# Wallet Pregeneration & Claiming
Source: https://docs.getpara.com/v3/swift/guides/pregen
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
**Coming Soon**: Wallet pregeneration and claiming functionality for Swift applications is currently in development and will be available soon. Reach out to us at if you'd like to get early access.
Para will soon provide support for wallet pregeneration and claiming in Swift applications, allowing you to create blockchain wallets for users before they complete authentication.
# Swift Session Management
Source: https://docs.getpara.com/v3/swift/guides/sessions
import { Link } from '/snippets/v3/components/ui/link.mdx';
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para Swift SDK provides robust session management, automatically tracking authentication states and ensuring secure, seamless interactions. Effective session management is critical for security, usability, and reliability of your application.
## Session States
Para manages sessions using the `ParaSessionState` enum:
- `unknown`: Initial state before the status is determined.
- `inactive`: SDK initialized but no active session.
- `active`: Session is active but user not fully logged in.
- `activeLoggedIn`: User is fully logged in with an active session.
### Observing Session States
Utilize SwiftUI's Combine or other state management tools to observe changes:
```swift
@StateObject private var paraManager: ParaManager
.onChange(of: paraManager.sessionState) { newState in
switch newState {
case .activeLoggedIn:
print("User authenticated")
case .inactive:
print("Session inactive")
default:
print("Session state: \(newState)")
}
}
```
## Session Duration
Para session length is configured per API key and can be set up to 30 days through the Configuration section of the or CLI. The Para API enforces the configured duration. Signing a message or transaction, or calling the session keep-alive method, can extend an active session according to that configuration.
## Key Session Methods
- `isSessionActive() async throws -> Bool`: Checks if the session is currently valid before performing authenticated operations.
- `isFullyLoggedIn() async throws -> Bool`: Checks if the user is fully logged in with an active session.
- `exportSession() async throws -> String`: Exports session state as a string that can be used for advanced integration scenarios.
- `logout() async throws`: Clears the current session, removes all website data from the WebView, and resets the session state to inactive.
## Maintaining Active Sessions
For long-running applications, check session status before performing sensitive operations:
```swift
func performSensitiveOperation() {
Task {
do {
if try await paraManager.isSessionActive() {
// Proceed with sensitive operation
try await signTransaction(...)
} else {
// Handle session expiration - redirect to login
navigateToLogin()
}
} catch {
await MainActor.run {
isLoggedIn = false
}
}
}
}
```
## Refreshing Expired Sessions
When a session has expired, Para recommends initiating a full authentication flow rather than trying to refresh the session.
For Swift applications, always call `logout()` before reinitiating authentication when a session has expired to ensure all stored data is properly cleared.
```swift
import ParaSwift
func handleSessionExpiration() async {
do {
// When session expires, first clear storage
try await paraManager.logout()
// Then redirect to authentication screen
await MainActor.run {
// Navigate to authentication screen
}
} catch {
// Handle error
}
}
```
## Background Security
Clear sensitive data when app goes to background by logging out:
```swift
class AppDelegate: NSObject, UIApplicationDelegate {
func applicationDidEnterBackground(_ application: UIApplication) {
Task {
try? await paraManager.logout()
}
}
}
```
## Exporting Sessions to Your Server
In some advanced scenarios, you may need to export the session state:
```swift
func sendSessionToServer() async throws {
do {
// Export session without signing capabilities
let sessionString = try await paraManager.exportSession()
// Create URL request
var request = URLRequest(url: URL(string: "https://your-api.com/sessions")!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
// Create request body
let body: [String: Any] = ["session": sessionString]
request.httpBody = try JSONSerialization.data(withJSONObject: body)
// Send request
let (_, response) = try await URLSession.shared.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
// Handle success
} catch {
// Handle error
throw error
}
}
```
## Best Practices
### Check Sessions on App Launch
Verify session status when your app starts to determine if users need to reauthenticate:
```swift
import SwiftUI
import ParaSwift
class AppStartupManager: ObservableObject {
@Published var isLoggedIn = false
let paraManager: ParaManager
init(paraManager: ParaManager) {
self.paraManager = paraManager
checkSessionOnLaunch()
}
func checkSessionOnLaunch() {
Task {
do {
let isActive = try await paraManager.isSessionActive()
await MainActor.run {
if isActive {
// User is logged in
isLoggedIn = true
} else {
// Session not active, clear any lingering data
Task {
try? await paraManager.logout()
}
isLoggedIn = false
}
}
} catch {
await MainActor.run {
isLoggedIn = false
}
}
}
}
}
```
### Handle App Lifecycle Changes
Swift apps can be backgrounded and foregrounded, which may affect session status:
```swift
import SwiftUI
import ParaSwift
class LifecycleManager: ObservableObject {
let paraManager: ParaManager
init(paraManager: ParaManager) {
self.paraManager = paraManager
// Register for foreground notifications
NotificationCenter.default.addObserver(
self,
selector: #selector(appMovedToForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil
)
}
@objc func appMovedToForeground() {
// App came to foreground, check session
checkSession()
}
func checkSession() {
Task {
let isActive = try? await paraManager.isSessionActive()
if isActive != true {
try? await paraManager.logout()
await MainActor.run {
// Navigate to login screen
}
}
}
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
```
## Next Steps
Explore more advanced features and integrations with Para in Swift:
# Social Login
Source: https://docs.getpara.com/v3/swift/guides/social-login
import EnvironmentInfo from "/snippets/v3/quick-start-environment-info.mdx";
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import IntegrationSupport from "/snippets/v3/integration-support.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import BetaCredentialsFull from "/snippets/v3/beta-credentials/beta-credentials-full.mdx";
import { Link } from '/snippets/v3/components/ui/link.mdx';
## Overview
Social login (OAuth) is integrated directly into Para's unified authentication experience. This guide covers how to implement social login alongside email and phone authentication in a single, streamlined interface. Para supports Google, Apple, and Discord as OAuth providers.
Have your own OpenID Connect provider? Once you [set up Custom OIDC](/v3/general/developer-portal-custom-oidc), use `CUSTOM_OIDC` as the OAuth method — it works like any other provider.
## Prerequisites
You must have the ParaSwift SDK installed and configured in your project. If you haven't done this yet, please refer to our .
## Unified Authentication Approach
Para's recommended approach is to integrate social login directly into your main authentication view alongside email and phone options. This provides users with all authentication methods in one place.
### Environment Setup
Ensure your authentication view can access the system authentication helpers and register the default web session once:
```swift
@Environment(\.webAuthenticationSession) private var webAuthenticationSession
@Environment(\.authorizationController) private var authorizationController
var body: some View {
VStack { /* auth UI */ }
.task {
paraManager.setDefaultWebAuthenticationSession(webAuthenticationSession)
}
}
```
## Implementing Social Login
### Integration with Unified Auth View
Social login should be integrated alongside email and phone authentication in your main authentication view. Here's a basic example:
```swift
import SwiftUI
import ParaSwift
import AuthenticationServices
struct AuthView: View {
@EnvironmentObject var paraManager: ParaManager
@Environment(\.webAuthenticationSession) private var webAuthenticationSession
@Environment(\.authorizationController) private var authorizationController
@State private var emailOrPhone = ""
var body: some View {
VStack(spacing: 20) {
// Email/Phone input
TextField("Email or phone number", text: $emailOrPhone)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button("Continue") {
handleEmailPhoneAuth()
}
.buttonStyle(.borderedProminent)
// Divider
Text("or")
.foregroundColor(.gray)
// Social login buttons
Button("Continue with Google") {
handleSocialLogin(.google)
}
Button("Continue with Apple") {
handleSocialLogin(.apple)
}
Button("Continue with Discord") {
handleSocialLogin(.discord)
}
}
.padding()
.task {
paraManager.setDefaultWebAuthenticationSession(webAuthenticationSession)
}
}
}
```
### Handling Social Login
Implement the social login handler that manages the OAuth flow:
```swift
private func handleSocialLogin(_ provider: OAuthProvider) {
Task {
do {
try await paraManager.handleOAuth(
provider: provider,
authorizationController: authorizationController
)
// OAuth flow completed successfully
// User is now logged in and wallets are available
print("User authenticated successfully")
// Navigate to authenticated area of your app
} catch {
// Handle OAuth error
print("OAuth error: \(error.localizedDescription)")
}
}
}
```
The `handleOAuth` method:
- Authenticates the user with the OAuth provider
- Checks if a Para account exists for the user
- For new users: creates a Para account and sets up a passkey automatically
- For existing users: logs them in directly
- Returns nothing on success, throws errors on failure
- Uses the default `WebAuthenticationSession` you registered; pass a custom one only if you need to override it for a specific call.
### Creating Social Login Buttons
Create buttons for each OAuth provider:
```swift
// Google Login
Button("Continue with Google") {
handleSocialLogin(.google)
}
// Apple Login
Button("Continue with Apple") {
handleSocialLogin(.apple)
}
// Discord Login
Button("Continue with Discord") {
handleSocialLogin(.discord)
}
```
## Available OAuth Providers
The ParaSwift SDK supports the following OAuth providers:
- Google (`.google`)
- Apple (`.apple`)
- Discord (`.discord`)
## Key Points
- Social login is handled through the `handleOAuth` method
- The method manages the complete OAuth flow including user authentication, Para account creation/lookup, and passkey setup for new users
- For existing users, it logs them in directly
- The SDK supports Google, Apple, and Discord as OAuth providers
- Social login should be integrated with email/phone authentication for a unified experience
## Next Steps
After implementing social login, you might want to:
1. for new users
2. for session management
3. as an additional security layer
# Solana Integration
Source: https://docs.getpara.com/v3/swift/guides/solana
import { Card } from '/snippets/v3/components/ui/card.mdx';
## Quick Start
```swift
import ParaSwift
// Sign a Solana transaction
let paraManager = ParaManager(apiKey: "your-api-key")
// Get an existing Solana wallet or create one
let wallets = try await paraManager.fetchWallets()
let wallet: Wallet
if let existing = wallets.first(where: { $0.type == .solana }) {
wallet = existing
} else {
wallet = try await paraManager.createWallet(type: .solana, skipDistributable: false)
}
let transaction = try SolanaTransaction(
to: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
lamports: UInt64(1_000_000), // 0.001 SOL
feePayer: nil, // Uses wallet as fee payer
recentBlockhash: nil // Fetched automatically with RPC URL
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction,
chainId: nil, // Not needed for Solana
rpcUrl: "https://api.devnet.solana.com"
)
print("Transaction signed: \(result.signedTransaction)")
```
## Common Operations
### Sign Transaction
```swift
let transaction = try SolanaTransaction(
to: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
lamports: UInt64(1_000_000), // 0.001 SOL
feePayer: nil, // Uses wallet as fee payer
recentBlockhash: nil // Fetched automatically with RPC URL
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction,
chainId: nil, // Not needed for Solana
rpcUrl: "https://api.devnet.solana.com"
)
print("Signed: \(result.signedTransaction)")
```
### Check Balance
```swift
let balance = try await paraManager.getBalance(
walletId: wallet.id,
token: nil, // Native SOL
rpcUrl: "https://api.devnet.solana.com"
)
// Convert lamports to SOL
let lamports = Double(balance) ?? 0
let sol = lamports / 1_000_000_000
print("Balance: \(String(format: "%.4f", sol)) SOL")
```
### Sign Message
```swift
let message = "Hello, Solana!"
let result = try await paraManager.signMessage(
walletId: wallet.id,
message: message
)
print("Signature: \(result.signedTransaction)")
```
## Networks
### Testnets
| Network | RPC URL | Native Token |
|---------|---------|--------------|
| **Devnet** | `https://api.devnet.solana.com` | SOL |
| **Testnet** | `https://api.testnet.solana.com` | SOL |
### Mainnets
| Network | RPC URL | Native Token | Network Type |
|---------|---------|--------------|--------------|
| **Mainnet** | `https://api.mainnet-beta.solana.com` | SOL | Production |
| **Alchemy** | `https://solana-mainnet.g.alchemy.com/v2/YOUR_KEY` | SOL | Production |
## Complete Example
```swift
import SwiftUI
import ParaSwift
struct SolanaWalletView: View {
@EnvironmentObject var paraManager: ParaManager
@State private var isLoading = false
@State private var signature: String?
let wallet: Wallet
private let rpcUrl = "https://api.devnet.solana.com"
var body: some View {
VStack(spacing: 20) {
Text(wallet.address ?? "No address")
.font(.system(.caption, design: .monospaced))
Button(action: signTransaction) {
Text(isLoading ? "Signing..." : "Sign Transaction")
}
.disabled(isLoading)
if let signature = signature {
Text("Signed: \(signature)")
.font(.caption)
}
}
.padding()
}
private func signTransaction() {
isLoading = true
Task {
do {
let transaction = try SolanaTransaction(
to: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
lamports: 1_000_000, // 0.001 SOL
feePayer: nil,
recentBlockhash: nil
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction,
chainId: nil, // Not needed for Solana
rpcUrl: rpcUrl
)
signature = result.signedTransaction
} catch {
print("Error: \(error)")
}
isLoading = false
}
}
}
```
## Advanced Transaction Options
```swift
// Transaction with compute budget
let transaction = try SolanaTransaction(
to: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
lamports: UInt64(1_000_000),
computeUnitLimit: UInt32(200_000), // Set compute budget
computeUnitPrice: UInt64(1_000) // Set priority fee
)
// Transaction with custom blockhash
let transaction = try SolanaTransaction(
to: "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
lamports: UInt64(1_000_000),
recentBlockhash: "custom_blockhash",
feePayer: wallet.address
)
```
## Pre-Serialized Transactions
Sign base64-encoded Solana transactions from dApps or backend services.
```swift
let base64Transaction = "AQABA8GlkLb8bd/L6i5/YftGpxyig/iBvof2eNEF9WPF2o0Z..."
let result = try await paraManager.signSolanaSerializedTransaction(
walletId: wallet.id,
base64Tx: base64Transaction
)
```
### Use Cases
```swift
// From dApp
let serializedTx = dApp.getSerializedTransaction()
let signature = try await paraManager.signSolanaSerializedTransaction(
walletId: wallet.id,
base64Tx: serializedTx
)
// From backend
let preparedTx = await backend.prepareTransaction()
let result = try await paraManager.signSolanaSerializedTransaction(
walletId: wallet.id,
base64Tx: preparedTx.serialized
)
```
Accepts base64-encoded bytes from `transaction.serializeMessage()` or compatible Solana SDKs.
# Stellar Integration
Source: https://docs.getpara.com/v3/swift/guides/stellar
Use `WalletType.stellar` to create or load a Stellar wallet, then pass a `StellarTransaction` value to `signTransaction`.
## Quick start
```swift StellarSigning.swift
import ParaSwift
let paraManager = ParaManager(apiKey: "your-api-key")
enum StellarWalletError: Error {
case notFound
}
func getOrCreateStellarWallet(_ paraManager: ParaManager) async throws -> Wallet {
var wallets = try await paraManager.fetchWallets()
if let wallet = wallets.first(where: { $0.type == .stellar }) {
return wallet
}
try await paraManager.createWallet(type: .stellar, skipDistributable: false)
wallets = try await paraManager.fetchWallets()
guard let wallet = wallets.first(where: { $0.type == .stellar }) else {
throw StellarWalletError.notFound
}
return wallet
}
let wallet = try await getOrCreateStellarWallet(paraManager)
let transaction = StellarTransaction(
to: "GRECIPIENT_STELLAR_ADDRESS",
amount: "10",
memo: .text("hello"),
networkPassphrase: StellarNetwork.testnetPassphrase
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction
)
print("Signed transaction: \(result.signedTransaction)")
```
## Display the address
A Stellar wallet uses the same `Wallet` model as the other wallet types. Use `wallet.stellarAddress` to derive the Stellar G-address from the wallet public key when needed.
```swift StellarAddress.swift
let address = wallet.stellarAddress ?? wallet.address
print("Stellar address: \(address ?? "No address")")
```
You can also call the address helpers directly:
```swift StellarAddressHelpers.swift
let address = try StellarAddress.fromPublicKey(publicKeyHex)
let addressFromSolana = try StellarAddress.fromSolanaAddress(solanaAddress)
```
## Sign a payment
`StellarTransaction` supports native XLM payments and issued assets. The `networkPassphrase` must match the network the transaction will be submitted to.
```swift StellarPayment.swift
let xlmPayment = StellarTransaction(
to: "GRECIPIENT_STELLAR_ADDRESS",
amount: "10",
networkPassphrase: StellarNetwork.testnetPassphrase,
fee: "100",
timeout: 60
)
let signed = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: xlmPayment
)
```
For issued assets, include the asset code and issuer:
```swift StellarAssetPayment.swift
let usdcPayment = StellarTransaction(
to: "GRECIPIENT_STELLAR_ADDRESS",
amount: "5",
asset: StellarAsset(
code: "USDC",
issuer: "GISSUER_STELLAR_ADDRESS"
),
networkPassphrase: StellarNetwork.publicPassphrase
)
```
## Sign serialized XDR
If your backend or Stellar SDK code already builds the transaction, pass the serialized XDR directly.
```swift StellarXDR.swift
let transaction = StellarTransaction(
serializedXDR: "AAAAAgAAA...",
networkPassphrase: StellarNetwork.publicPassphrase
)
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction
)
```
## Network passphrases
| Network | Constant |
|---------|----------|
| Stellar Testnet | `StellarNetwork.testnetPassphrase` |
| Stellar Public Network | `StellarNetwork.publicPassphrase` |
# Swift SDK Overview
Source: https://docs.getpara.com/v3/swift/overview
import { Card } from '/snippets/v3/components/ui/card.mdx';
Para Swift SDK eliminates the complexity of blockchain integration by providing a unified interface for wallet creation, authentication, and multi-chain transactions in iOS applications.
## Quick Start
```swift AuthView.swift
import SwiftUI
import ParaSwift
struct AuthView: View {
@EnvironmentObject var paraManager: ParaManager
@Environment(\.webAuthenticationSession) private var webAuthenticationSession
@Environment(\.authorizationController) private var authorizationController
var body: some View {
VStack {
Button("Authenticate") {
startAuth()
}
}
.task {
// Reuse the system WebAuthenticationSession for hosted auth flows
paraManager.setDefaultWebAuthenticationSession(webAuthenticationSession)
}
}
private func startAuth() {
Task {
do {
let authState = try await paraManager.initiateAuthFlow(auth: .email("user@example.com"))
switch authState.stage {
case .done:
// One Click hosted auth finished automatically
handleAuthenticatedUser()
case .verify:
// Show OTP or passkey enrollment UI
presentVerification(authState)
case .signup:
// Let the user pick passkey vs password enrollment
presentSignupOptions(authState)
case .login:
// Existing user — Para chooses passkey/password automatically
try await paraManager.handleLogin(
authState: authState,
authorizationController: authorizationController
)
handleAuthenticatedUser()
default:
// Handle other states as needed (e.g., show error UI)
break
}
} catch {
// Handle errors according to your UI needs
}
}
}
}
```
Implement lightweight helpers like `handleAuthenticatedUser()`, `presentVerification(_:)`, or `presentSignupOptions(_:)` to align with your navigation flow.
Create the `ParaManager` once at your app entry point (for example with `@StateObject` in `App`) and inject it into views with `.environmentObject(paraManager)`.
## Sign Transactions
```swift TransactionHandler.swift
// Get wallet
let wallets = try await paraManager.fetchWallets()
let evmWallet = wallets.first { $0.type == .evm }!
let solanaWallet = wallets.first { $0.type == .solana }!
// EVM transaction
let transaction = EVMTransaction(
to: "0x742d35Cc6634C0532925a3b844Bc9e7595f6E2c0",
value: BigUInt("1000000000000000")!, // 0.001 ETH in wei
gasLimit: BigUInt("21000")!
)
let result = try await paraManager.signTransaction(
walletId: evmWallet.id,
transaction: transaction,
chainId: "11155111", // Sepolia
rpcUrl: "https://sepolia.infura.io/v3/YOUR_API_KEY"
)
// Solana message signing
let signature = try await paraManager.signMessage(
walletId: solanaWallet.id,
message: "Hello, Solana!"
)
```
## Next Steps
# Setup
Source: https://docs.getpara.com/v3/swift/setup
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import { Card } from '/snippets/v3/components/ui/card.mdx';
import { Link } from '/snippets/v3/components/ui/link.mdx';
The Para Swift SDK enables you to integrate secure wallet features including creation, passkey-based authentication, and transaction signing into your iOS applications. This guide covers all necessary steps from installation to implementing authentication flows.
## Install the SDK
1. In your Xcode project, go to **File > Add Packages** or (in your target) **Frameworks, Libraries, and Embedded Content** and click **+**.
2. Enter `https://github.com/getpara/swift-sdk`
3. Select **Up to Next Major Version** and enter `3.0.0`
4. Add the package to your app target and click **Add Package**.
The Para Swift SDK automatically includes the following dependencies:
- **BigInt**: For handling large numbers in blockchain operations
- **PhoneNumberKit**: For phone number validation and formatting
These dependencies will be automatically resolved by Swift Package Manager.
To enable passkeys on iOS, you need to configure Associated Domains:
1. In Xcode, go to **Signing & Capabilities** for your app target
2. Click **+ Capability** and add **Associated Domains**
3. Add the following entries:
```
webcredentials:app.usecapsule.com
webcredentials:app.beta.usecapsule.com
```
4. Register your Team ID + Bundle ID with Para via the
Without properly registering your Team ID and Bundle ID with Para, passkey authentication flows will fail. Contact Para support if you encounter issues with passkey registration.
Heading to App Review? Check for Sign in with Apple, reviewer, and deletion tips.
## Configure URL Scheme
Before initializing Para, you need to configure your app's URL scheme for deep linking. This is required for the `appScheme` parameter and enables OAuth authentication flows.
1. In Xcode, select your project in the navigator
2. Select your app target
3. Go to the **Info** tab
4. Scroll down to **URL Types** and click **+** to add a new URL type
5. Fill in the fields:
- **URL Schemes**: Enter your scheme name (e.g., `paraswift`, `yourapp`)
- **Role**: Select **Editor**
## Initialize Para
To use Para's features, you'll need to initialize a Para manager that can be accessed throughout your app. This manager handles all interactions with Para's services, including authentication, wallet management, and transaction signing.
Below is an example of initializing the SDK in a SwiftUI application:
```swift App.swift
import SwiftUI
import ParaSwift
@main
struct ExampleApp: App {
@StateObject private var paraManager: ParaManager
init() {
// Initialize Para manager
_paraManager = StateObject(wrappedValue: ParaManager(
environment: .beta, // Use .prod for production
apiKey: "YOUR_API_KEY_HERE", // Get from: https://developer.getpara.com
appScheme: "yourapp" // Your app's URL scheme for deep linking
))
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(paraManager)
}
}
}
```
The `appScheme` parameter must match the URL scheme you configured in your Info.plist. This enables deep linking for external wallet integrations like MetaMask and OAuth authentication flows.
## Continue with Authentication
Once Para is initialized, implement your auth experience using the Authentication & Users guides:
-
-
For wallet creation and transaction signing examples, jump into the blockchain guides:
-
-
-
-
## Example
For a complete implementation example, check out our Swift SDK example app:
## Next Steps
After setup, use these guides for authentication and wallet signing flows.
# Developer Portal Email Branding
Source: https://docs.getpara.com/v3/swift/setup/developer-portal-email-branding
import DeveloperPortalEmailBranding from '/snippets/v3/developer-portal/email-branding.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Payment Integration
Source: https://docs.getpara.com/v3/swift/setup/developer-portal-payments
import DeveloperPortalPayments from '/snippets/v3/developer-portal/payments.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Developer Portal Security Settings
Source: https://docs.getpara.com/v3/swift/setup/developer-portal-security
import DeveloperPortalSecurity from '/snippets/v3/developer-portal/security.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Get Your API Key
Source: https://docs.getpara.com/v3/swift/setup/developer-portal-setup
import DeveloperPortalSetup from '/snippets/v3/developer-portal/setup.mdx';
import { DeveloperPortalNextSteps } from '/snippets/v3/developer-portal/next-steps.mdx';
# Troubleshooting
Source: https://docs.getpara.com/v3/swift/troubleshooting
import { Link } from '/snippets/v3/components/ui/link.mdx';
This guide helps you identify and resolve common issues encountered while integrating the Para Swift SDK into your iOS application.
Using an LLM (ChatGPT, Claude) or Coding Assistant (Cursor, Github Copilot)? Here are a few tips:
1) Include the for the most up-to-date help
2) Check out the for an interactive LLM using Para Examples Hub
## General Troubleshooting Steps
Before diving into specific issues, try these basic troubleshooting steps:
In Xcode, go to **Product → Clean Build Folder** (Option + Shift + Command + K).
For Swift Package Manager: **File → Packages → Update to Latest Package Versions**
For CocoaPods: Run `pod update` in your terminal.
Ensure you are using the latest Para SDK compatible with your minimum deployment target (iOS 13.0+).
Confirm your API key, Associated Domains, custom URL scheme, Team ID, and Bundle ID are correctly configured.
## Common Issues and Solutions
### Authentication Issues
**Error:** `ParaError.bridgeError` or system authentication errors
**Solution:** Verify Associated Domains, Team ID, Bundle ID, and domain setup.
```swift
do {
try await paraManager.generatePasskey(
identifier: email,
biometricsId: biometricsId,
authorizationController: authorizationController
)
} catch let error as ParaError {
print("Para error occurred: \(error.description)")
} catch let error as ASAuthorizationError {
print("Authentication error: \(error.localizedDescription)")
}
```
**Solution:** Confirm biometric setup on the device and check permissions in app settings.
Make sure your app includes the necessary privacy descriptions in Info.plist:
- `NSFaceIDUsageDescription` for Face ID
- Proper permission handling for biometric authentication
**Error:** `ASAuthorizationError.canceled` or similar system errors
**Solution:** Catch authentication cancellation errors and offer a retry option to the user. This error occurs when the user cancels a biometric prompt.
```swift
do {
try await paraManager.loginWithPasskey(authorizationController: authorizationController)
} catch let error as ASAuthorizationError where error.code == .canceled {
print("User canceled authentication")
}
```
**Solution:** Verify:
- Correct environment settings (BETA/prod)
- Proper user input (valid email/phone format)
- Active network connection
- You haven't hit rate limits for verification attempts
### Transaction Signing Issues
**Error:** `ParaError.bridgeError` or `ParaError.error`
**Solution:** Verify transaction parameters, proper encoding (Base64), and integration with web3 libraries.
```swift
do {
let result = try await paraManager.signTransaction(
walletId: walletId,
transaction: transaction
)
} catch let error as ParaError {
print("Transaction signing failed: \(error.description)")
}
```
**Error:** Transaction signing failures
**Solution:** Handle signing errors appropriately:
```swift
do {
let result = try await paraManager.signTransaction(
walletId: wallet.id,
transaction: transaction
)
print("Signed transaction: \(result.signedTransaction)")
} catch ParaError.bridgeError(let message) {
print("Signing failed: \(message)")
} catch {
print("Unexpected error: \(error)")
}
```
**Solution:**
- Ensure wallet balance covers gas fees
- Verify correct gas parameters
- Use reliable web3 libraries for estimates
- Consider implementing fallback gas values
### Network Issues
**Error:** `ParaError.bridgeError` or `ParaError.bridgeTimeoutError`
**Solution:** Check network connectivity, implement retry logic, and use network monitoring tools.
```swift
do {
try await paraManager.createWallet(type: .evm, skipDistributable: false)
} catch ParaError.bridgeTimeoutError {
print("Bridge operation timed out. Check connectivity and try again.")
} catch ParaError.bridgeError(let message) {
print("Bridge error occurred: \(message)")
}
```
**Error:** `ParaError.bridgeTimeoutError`
**Solution:**
- Implement timeout handling using Swift concurrency features
- Provide retry options for users
- Display loading indicators during network operations
- Consider implementing exponential backoff for retries
### External Wallet Issues
**Error:** `MetaMaskError` or `ParaError.bridgeError`
**Solution:** Check MetaMask installation and connection flow.
```swift
do {
try await metaMaskConnector.connect()
} catch let error as MetaMaskError {
print("MetaMask error: \(error.localizedDescription)")
} catch let error as ParaError {
print("Para error during MetaMask connection: \(error.description)")
}
```
**Solution:** Confirm wallet address and type are correct for external wallet login.
```swift
do {
let walletInfo = ExternalWalletInfo(
address: walletAddress,
type: .evm,
provider: "metamask"
)
try await paraManager.loginExternalWallet(wallet: walletInfo)
} catch let error as ParaError {
print("External wallet login failed: \(error.description)")
}
```
**Solution:**
- Verify URL schemes in `Info.plist`
- Confirm deep-link handling in AppDelegate or SceneDelegate
- Test with simple deep links to isolate the issue
- Check if the external wallet app is properly installed
## Best Practices
## Development Tools
Enable debug mode in the Para SDK (if available) to get more detailed logging information.
Utilize network debugging tools like Charles Proxy or Xcode's network debugger to inspect API calls.
Leverage Xcode's built-in debugging features:
- Set breakpoints at critical points
- Inspect variables and state
- Use the console for logging
### Setup and Integration Issues
If you're having trouble initializing the Para SDK:
- Ensure you're providing the required `appScheme` parameter
- Verify that you're using the correct API key and environment
- Check that all necessary dependencies are installed properly
- Look for any Swift compiler errors in your Xcode console
- Verify that your minimum deployment target is iOS 13.0 or higher
If passkey creation, retrieval, or usage isn't working:
- Verify that you've set up Associated Domains correctly in your Xcode project
- Make sure you've registered your Team ID + Bundle ID with Para via the Developer Portal
- Ensure that biometric authentication is enabled on the test device
- Check that `AuthorizationController` is properly configured
- Verify Face ID/Touch ID permissions are properly set in Info.plist
- Check for `ASAuthorizationError.canceled` when users cancel the biometric prompt
If you're experiencing authentication issues:
- Double-check that your API key is correct and properly set in your Para manager initialization
- Verify you're using the correct environment (`beta` or `prod`) that matches your API key
- Ensure your account has the necessary permissions for the operations you're attempting
- Check that your URL scheme matches what's configured in your Info.plist
- Verify the authentication flow is being followed correctly (verify → signup/login)
If OAuth callbacks or deep links aren't functioning:
- Verify your URL scheme is correctly configured in Info.plist
- Check that `onOpenURL` modifier is properly implemented
- Ensure the `appScheme` parameter matches your URL scheme exactly
- Test with a simple deep link first to isolate the issue
- Make sure your app is handling the URL in the main app file
## Getting Help
If you're still experiencing issues after trying the solutions above, you can get additional help:
-
- Contact Para Support via or [Slack](https://join.slack.com/t/para-community/shared_invite/zt-304keeulc-Oqs4eusCUAJEpE9DBwAqrg)
- When reporting issues, include:
- Detailed error messages
- Steps to reproduce the issue
- Device and iOS version details
- Para SDK version
# Para with Nuxt 3
Source: https://docs.getpara.com/v3/vue/setup/nuxt
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import { Link } from "/snippets/v3/components/ui/link.mdx";
Para does not ship a prebuilt Nuxt or Vue UI. Use the framework-agnostic `@getpara/web-sdk` and build the authentication, wallet, and signing screens inside your Nuxt app.
## Install the Web SDK
Install the Para Web SDK using your preferred package manager:
```bash npm
npm install @getpara/web-sdk --save-exact
```
```bash yarn
yarn add @getpara/web-sdk --exact
```
```bash pnpm
pnpm add @getpara/web-sdk --save-exact
```
```bash bun
bun add @getpara/web-sdk --exact
```
## Create a Para Client
Create a client-only module for your Nuxt app:
```ts utils/para.client.ts
import { ParaWeb } from "@getpara/web-sdk";
const config = useRuntimeConfig();
export const para = new ParaWeb(config.public.paraApiKey);
```
Create your API key in the , then expose it to Nuxt through `runtimeConfig.public.paraApiKey`.
Para authentication flows depend on browser APIs. Call Web SDK methods from client components or client-only modules, not during server rendering.
## Build the Custom UI
Use Para's Web SDK methods from your Nuxt components and render the screens your product needs.
Use `authenticateWithEmailOrPhone`, `authenticateWithOAuth`, and `verifyNewAccount` to drive sign-up and login. Listen to `para.onStatePhaseChange()` so your UI can open the verification, passkey, password, or PIN URLs that Para returns.
Render wallet creation, wallet selection, address display, loading states, and recovery prompts in your own Vue components.
After the user is authenticated and has a wallet, call Para signing methods from the Web SDK and show signing status in your own UI.
Sepolia examples need testnet ETH before sending transactions or writing contracts. After resolving the Para EVM wallet ID, call `para.requestFaucet({ walletId, chain: "ETHEREUM_SEPOLIA" })` from the Web SDK. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react/guides/web3-operations/evm/fund-testnet-wallet) for the same request shape in React.
## Next Docs
Build framework-agnostic auth screens with Vue examples.
Review the custom UI path and where it hands off into platform docs.
Configure login methods, external wallets, guest mode, and 2FA.
Verify the integration by signing a message or transaction.
# Para with Vue + Vite
Source: https://docs.getpara.com/v3/vue/setup/vite
import Prerequisites from "/snippets/v3/quick-start-prerequisites.mdx";
import { Link } from "/snippets/v3/components/ui/link.mdx";
Para does not ship a prebuilt Vue UI. Use the framework-agnostic `@getpara/web-sdk` and build the authentication, wallet, and signing screens inside your Vue app.
## Install the Web SDK
Install the Para Web SDK using your preferred package manager:
```bash npm
npm install @getpara/web-sdk --save-exact
```
```bash yarn
yarn add @getpara/web-sdk --exact
```
```bash pnpm
pnpm add @getpara/web-sdk --save-exact
```
```bash bun
bun add @getpara/web-sdk --exact
```
## Create a Para Client
Create a shared client module for your Vue app:
```ts client/para.ts
import { ParaWeb } from "@getpara/web-sdk";
const PARA_API_KEY = import.meta.env.VITE_PARA_API_KEY;
export const para = new ParaWeb(PARA_API_KEY);
```
Create your API key in the , then expose it to Vite as `VITE_PARA_API_KEY`.
## Build the Custom UI
Use Para's Web SDK methods from your Vue components and render the screens your product needs.
Use `authenticateWithEmailOrPhone`, `authenticateWithOAuth`, and `verifyNewAccount` to drive sign-up and login. Listen to `para.onStatePhaseChange()` so your UI can open the verification, passkey, password, or PIN URLs that Para returns.
Render wallet creation, wallet selection, address display, loading states, and recovery prompts in your own Vue components.
After the user is authenticated and has a wallet, call Para signing methods from the Web SDK and show signing status in your own UI.
Sepolia examples need testnet ETH before sending transactions or writing contracts. After resolving the Para EVM wallet ID, call `para.requestFaucet({ walletId, chain: "ETHEREUM_SEPOLIA" })` from the Web SDK. If `chain` is omitted, the faucet defaults to `ETHEREUM_SEPOLIA`. See [Fund Testnet Wallet](/v3/react/guides/web3-operations/evm/fund-testnet-wallet) for the same request shape in React.
## Next Docs
Build framework-agnostic auth screens with Vue examples.
Review the custom UI path and where it hands off into platform docs.
Configure login methods, external wallets, guest mode, and 2FA.
Verify the integration by signing a message or transaction.
# Integrate Para Wallets with Eliza OS
Source: https://docs.getpara.com/v3/walkthroughs/Eliza
Build wallet-enabled AI agents by integrating Para's wallet infrastructure with Eliza OS. Your agent will create and manage EVM wallets and send transactions across Ethereum-compatible chains.
## Prerequisites
You need these components before starting:
- Node.js 18+ and npm/pnpm/yarn/bun installed
- An active [Eliza OS](https://docs.elizaos.ai/) project
- Para API credentials (get them from the [Para Developer Portal](https://developer.getpara.com/))
## Installation and Setup
Choose your preferred package manager to install the [Para plugin](https://github.com/aipop-fun/plugin-para):
```bash
# npm
npm install @elizaos/plugin-para
# pnpm
pnpm add @elizaos/plugin-para
# yarn
yarn add @elizaos/plugin-para
# bun
bun add @elizaos/plugin-para
```
Create or update your `.env` file with Para credentials:
```env
# Para Configuration
PARA_API_KEY=your-para-api-key
PARA_ENV=production
# Optional: Chain-specific RPC URLs
ETH_RPC_URL=https://mainnet.infura.io/v3/your-key
POLYGON_RPC_URL=https://polygon-rpc.com
```
Register the Para plugin in your Eliza character configuration:
```typescript
// character.config.ts
import { paraPlugin } from '@elizaos/plugin-para';
export const characterConfig = {
name: "ParaAgent",
description: "An AI agent with wallet management capabilities",
plugins: [paraPlugin],
settings: {
secrets: {
PARA_API_KEY: process.env.PARA_API_KEY,
PARA_ENV: process.env.PARA_ENV || 'production'
}
}
};
```
Create your agent with Para capabilities:
```typescript
// index.ts
import { ElizaOS } from '@elizaos/core';
import { characterConfig } from './character.config';
async function main() {
const agent = new ElizaOS({
character: characterConfig,
runtime: {
// Additional runtime configuration
logLevel: 'info',
persistState: true
}
});
await agent.start();
console.log('🤖 Para-enabled agent is running!');
}
main().catch(console.error);
```
## Next Steps
Add more features to your Eliza + Para integration
Learn more about building AI agents with Eliza
## Related Walkthroughs
Intermediate · 15 min · AI-powered migration tooling
Advanced · 40 min · Smart accounts and gas sponsorship
Advanced · 45 min · Browser extensions with Para wallets
# Integrating Para with Rhinestone
Source: https://docs.getpara.com/v3/walkthroughs/Rhinestone
Build cross-chain smart accounts using Para's embedded wallets and [Rhinestone](https://www.rhinestone.dev/) — enabling intent-based transactions, automatic bridging, and gas abstraction across EVM chains. The `useRhinestoneSmartAccount` hook handles account creation, orchestrator setup, and Pimlico bundler configuration automatically.
## Prerequisites
- Para API key from the [Para Developer Portal](https://developer.getpara.com/)
- Rhinestone API key from the Rhinestone team
- Optionally a Pimlico API key for bundler/paymaster infrastructure
- Node.js 18+ and Next.js development environment
## Installation
```bash npm
npm install @getpara/react-sdk viem @tanstack/react-query
```
```bash yarn
yarn add @getpara/react-sdk viem @tanstack/react-query
```
```bash pnpm
pnpm add @getpara/react-sdk viem @tanstack/react-query
```
## Setup Para Provider
Configure the Para provider:
```tsx filename="app/providers.tsx"
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ParaProvider } from "@getpara/react-sdk";
const queryClient = new QueryClient();
export default function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
## Usage
Use the `useRhinestoneSmartAccount` hook to create and manage a Rhinestone smart account. The hook handles Para signer creation, Rhinestone account setup, and orchestrator configuration internally.
```tsx filename="components/RhinestoneDemo.tsx"
"use client";
import { useRhinestoneSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const RHINESTONE_API_KEY = process.env.NEXT_PUBLIC_RHINESTONE_API_KEY!;
const PIMLICO_API_KEY = process.env.NEXT_PUBLIC_PIMLICO_API_KEY!;
export function RhinestoneDemo() {
const { smartAccount, isLoading, error } = useRhinestoneSmartAccount({
chain: sepolia,
rhinestoneApiKey: RHINESTONE_API_KEY,
pimlicoApiKey: PIMLICO_API_KEY,
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
Rhinestone Smart Account
Address: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
{sendError.message}
}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
{batchError.message}
}
);
}
```
Rhinestone only supports EIP-4337 mode. Both `rhinestoneApiKey` and `pimlicoApiKey` are optional but recommended for production use. For advanced cross-chain use cases with automatic bridging, see the [Rhinestone documentation](https://docs.rhinestone.dev).
## Key Features
- **Cross-chain smart accounts**: Single account across multiple EVM chains
- **Intent-based transactions**: Automatic execution of cross-chain operations
- **Gas abstraction**: Sponsored transactions without users managing gas fees
- **Automatic bridging**: Seamless asset transfers between chains
- **Para wallet management**: Embedded wallet infrastructure with MPC security
## Complete Example
View the complete working example in Para's Examples Hub
## Next Steps
Explore Rhinestone's features and advanced use cases
See all supported AA providers and the unified SmartAccount interface
## Related Walkthroughs
Intermediate · 30 min · EIP-7702 smart accounts with session keys
Advanced · 40 min · Smart accounts and gas sponsorship
Advanced · 45 min · Cross-chain USDC bridges
# Integrating Para with Squid
Source: https://docs.getpara.com/v3/walkthroughs/Squid
Build a cross-chain USDC bridge using Squid Router API with Para SDK for seamless wallet management across Ethereum, Base, and Solana networks.
**The key integration pattern:** Squid Router provides the cross-chain route and signable transaction data, while Para SDK handles the wallet management and transaction signing across multiple networks.
## Para Setup Requirements
Before integrating with Squid Router, ensure your Para configuration supports all required networks:
- **Para API key** with **Ethereum, Base, and Solana networks enabled**
- **Squid Router integrator ID** from [Squid Router](https://squidrouter.com)
- Each enabled network will create wallets for your users on that chain
**Important:** Your Para API key must have all three networks (Ethereum, Base, Solana) enabled in your [Developer Portal](https://developer.getpara.com) for the multi-network signers to work properly.
## Prerequisites
Before integrating Para with Squid Router, ensure you have:
- Para API key with **all three networks enabled**: Ethereum, Base, and Solana
- Squid Router integrator ID from [Squid Router](https://squidrouter.com)
- Node.js 18+ and Next.js development environment
## Installation
Install the required dependencies:
```bash
npm install @getpara/react-sdk @0xsquid/sdk
npm install @getpara/ethers-v6-integration @getpara/solana-web3.js-v1-integration
npm install @tanstack/react-query ethers@^6 @solana/web3.js lucide-react
```
## Environment Variables
Configure your environment variables:
```bash
# .env.local
NEXT_PUBLIC_PARA_API_KEY=your_para_api_key
NEXT_PUBLIC_PARA_ENVIRONMENT=BETA
NEXT_PUBLIC_SQUID_INTEGRATOR_ID=your_squid_integrator_id
```
## Configuration Setup
Create your constants file with network configurations and validation:
```typescript
// src/constants.ts
import { Environment } from "@getpara/react-sdk";
export const PARA_API_KEY = process.env.NEXT_PUBLIC_PARA_API_KEY ?? "";
export const PARA_ENVIRONMENT = (process.env.NEXT_PUBLIC_PARA_ENVIRONMENT as Environment) || Environment.BETA;
if (!PARA_API_KEY) {
throw new Error("API key is not defined. Please set NEXT_PUBLIC_PARA_API_KEY in your environment variables.");
}
export const SQUID_INTEGRATOR_ID = process.env.NEXT_PUBLIC_SQUID_INTEGRATOR_ID ?? "";
if (!SQUID_INTEGRATOR_ID) {
throw new Error(
"Squid integrator ID is not defined. Please set NEXT_PUBLIC_SQUID_INTEGRATOR_ID in your environment variables."
);
}
export const SUPPORTED_NETWORKS = ["ethereum", "base", "solana"] as const;
export type SupportedNetwork = (typeof SUPPORTED_NETWORKS)[number];
type NetworkConfig = {
name: string;
icon: string;
chainId: number | string;
usdcContractAddress: string;
rpcUrl: string;
networkType: "mainnet" | "testnet" | "devnet";
networkCategory: "evm" | "svm";
};
export const NETWORK_CONFIG: Record = {
ethereum: {
name: "Ethereum",
icon: "/ethereum.png",
chainId: 1,
usdcContractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
rpcUrl: process.env.NEXT_PUBLIC_ETHEREUM_RPC_URL ?? "https://ethereum-rpc.publicnode.com",
networkType: "mainnet",
networkCategory: "evm",
},
base: {
name: "Base",
icon: "/base.png",
chainId: 8453,
usdcContractAddress: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
rpcUrl: process.env.NEXT_PUBLIC_BASE_RPC_URL ?? "https://base-rpc.publicnode.com",
networkType: "mainnet",
networkCategory: "evm",
},
solana: {
name: "Solana",
icon: "/solana.png",
chainId: "solana-mainnet-beta",
usdcContractAddress: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
rpcUrl: process.env.NEXT_PUBLIC_SOLANA_RPC_URL ?? "https://solana-rpc.publicnode.com",
networkType: "mainnet",
networkCategory: "svm",
},
};
export const SUPPORTED_ASSETS = ["usdc"] as const;
export type SupportedAsset = (typeof SUPPORTED_ASSETS)[number];
export const ASSET_DETAILS: Record = {
usdc: {
id: "usdc",
name: "USD Coin",
symbol: "USDC",
icon: "/usdc.png",
},
};
```
## Squid Client Hook
Initialize the Squid Router SDK:
```typescript
// src/hooks/useSquidClient.tsx
import { useEffect, useState } from "react";
import { Squid } from "@0xsquid/sdk";
import { SQUID_INTEGRATOR_ID } from "@/constants";
let squidInstance: Squid | null = null;
export function useSquidClient() {
const [client, setClient] = useState(squidInstance);
useEffect(() => {
if (squidInstance) {
setClient(squidInstance);
return;
}
const initSquid = async () => {
const squid = new Squid({
baseUrl: "https://apiplus.squidrouter.com",
integratorId: SQUID_INTEGRATOR_ID,
});
await squid.init();
squidInstance = squid;
setClient(squid);
};
initSquid();
}, []);
return client;
}
```
## Multi-Network Signers
Para creates separate signers for each network, allowing you to sign transactions on Ethereum, Base, and Solana:
```typescript
// src/hooks/useSigners.tsx
import { useQuery } from "@tanstack/react-query";
import { useAccount, useClient } from "@getpara/react-sdk";
import { createParaEthersSigner } from "@getpara/ethers-v6-integration";
import { ParaSolanaWeb3Signer } from "@getpara/solana-web3.js-v1-integration";
import { ethers } from "ethers";
import { Connection } from "@solana/web3.js";
import { NETWORK_CONFIG } from "@/constants";
async function initializeSigners(para, account) {
// Initialize Ethereum signer
const ethereumProvider = new ethers.JsonRpcProvider(NETWORK_CONFIG.ethereum.rpcUrl);
const ethereumSigner = createParaEthersSigner({ para: para, provider: ethereumProvider });
// Initialize Base signer
const baseProvider = new ethers.JsonRpcProvider(NETWORK_CONFIG.base.rpcUrl);
const baseSigner = createParaEthersSigner({ para: para, provider: baseProvider });
// Initialize Solana signer
const solanaConnection = new Connection(NETWORK_CONFIG.solana.rpcUrl);
const solanaSigner = new ParaSolanaWeb3Signer(para, solanaConnection);
return {
ethereumEthers: { provider: ethereumProvider, signer: ethereumSigner, address: "...", isInitialized: true },
baseEthers: { provider: baseProvider, signer: baseSigner, address: "...", isInitialized: true },
solanaSvm: { signer: solanaSigner, connection: solanaConnection, address: "...", isInitialized: true },
};
}
export function useSigners() {
const para = useClient();
const { data: account } = useAccount();
const { data: signers } = useQuery({
queryKey: ["globalSigners", account?.isConnected],
queryFn: () => initializeSigners(para, account),
enabled: !!para && !!account?.isConnected,
staleTime: Infinity,
});
// Returns initialized signers for all three networks
return signers || defaultSignerState;
}
```
**Key Point:** Para automatically creates wallets for each enabled network in your API key configuration. Each signer can then sign transactions for its respective blockchain.
*See the [full implementation](https://github.com/getpara/examples-hub/blob/3.0.0/defi-integrations/with-squid-router-api/src/hooks/useSigners.tsx) for complete error handling and initialization logic.*
## Bridge Operations Hook
The integration follows a clear pattern: **Squid Router provides the route and signable transaction data, Para SDK handles the signing**:
```typescript
// src/hooks/useSquidBridge.tsx
import { useQuery, useMutation } from "@tanstack/react-query";
import { useSquidClient } from "./useSquidClient";
import { useSigners } from "./useSigners";
export function useSquidBridge() {
const squid = useSquidClient();
const { ethereumEthers, baseEthers, solanaSvm } = useSigners();
const useQuote = (params: QuoteParams) => {
return useQuery({
queryKey: ["squidQuote", params],
queryFn: () => fetchQuote(params), // Squid generates route and transaction data
enabled: !!(squid && params.originNetwork && params.destNetwork && params.amount),
staleTime: 20000,
refetchInterval: 20000,
});
};
const executeMutation = useMutation({
mutationFn: async ({ quote, originNetwork, onProgress }) => {
// 1. Squid provides the signable route object
const route = quote.route;
// 2. Para signer signs the transaction for the appropriate network
const signer = originNetwork === "ethereum" ? ethereumEthers.signer :
originNetwork === "base" ? baseEthers.signer :
solanaSvm.signer;
// 3. Execute with Squid client + Para signer
const tx = await squid.executeRoute({ signer, route });
// 4. Monitor transaction status
return tx;
},
});
return {
useQuote,
executeBridge: executeMutation.mutate,
isExecuting: executeMutation.isPending,
};
}
```
*See the [full implementation](https://github.com/getpara/examples-hub/blob/3.0.0/defi-integrations/with-squid-router-api/src/hooks/useSquidBridge.tsx) for complete quote fetching, transaction execution, and status monitoring logic.*
## Main Application Component
Create the core bridge interface structure:
```typescript
// src/app/page.tsx
"use client";
import { useState } from "react";
import { useAccount, useModal } from "@getpara/react-sdk";
import { useSquidBridge } from "@/hooks/useSquidBridge";
import { useSigners } from "@/hooks/useSigners";
export default function Home() {
const { openModal } = useModal();
const { data: account } = useAccount();
const { useQuote, executeBridge, isExecuting } = useSquidBridge();
const { ethereumEthers, baseEthers, solanaSvm } = useSigners();
const [originNetwork, setOriginNetwork] = useState(null);
const [destNetwork, setDestNetwork] = useState(null);
const [amount, setAmount] = useState("");
const { data: quote } = useQuote({
originNetwork,
destNetwork,
amount,
originAddress: getNetworkAddress(originNetwork),
destAddress: getNetworkAddress(destNetwork),
});
const handleBridge = () => {
executeBridge({
quote,
originNetwork,
onProgress: (progressData) => {
// Handle transaction progress updates
},
});
};
return (
{/* Network selection, amount input, bridge button */}
);
}
```
*See the [full implementation](https://github.com/getpara/examples-hub/blob/3.0.0/defi-integrations/with-squid-router-api/src/app/page.tsx) for complete UI components and transaction processing logic.*
## Para Provider Setup
Wrap your application with the Para and QueryClient providers:
```typescript
// src/providers/providers.tsx
"use client";
import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ParaProvider } from "@getpara/react-sdk";
import { PARA_API_KEY, PARA_ENVIRONMENT } from "@/constants";
const queryClient = new QueryClient();
export function Providers({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{children}
);
}
```
## Key Features
This integration provides:
- **Multi-network support**: Seamless bridging between Ethereum, Base, and Solana
- **Unified wallet management**: Single Para authentication for all networks
- **Real-time quotes**: Automatic quote updates with optimal routing
- **Transaction monitoring**: Status tracking with error handling and recovery options
- **Production-ready**: Comprehensive error handling and user feedback
## Next Steps
Learn more about Para's React SDK features and configuration options
Explore how Para integrates with Account Abstraction systems
## Related Walkthroughs
Advanced · 40 min · Cross-chain smart accounts with gas abstraction
Advanced · 45 min · Lending, borrowing, and yield strategies
Intermediate · 20 min · Send ETH with Ethers and Viem
# Integrating Para with thirdweb
Source: https://docs.getpara.com/v3/walkthroughs/Thirdweb
Integrate Para's embedded wallets with thirdweb for smart accounts, gas sponsorship, and in-app payments. The `useThirdwebSmartAccount` hook handles Para signer creation, smart wallet setup, and gas sponsorship configuration automatically — supporting both EIP-4337 and EIP-7702 modes.
## Prerequisites
- Para API key from the [Para Developer Portal](https://developer.getpara.com/)
- thirdweb Client ID from the [thirdweb Dashboard](https://thirdweb.com/dashboard)
- Node.js 18+ and React/Next.js development environment
## Installation
```bash npm
npm install @getpara/react-sdk viem @tanstack/react-query
```
```bash yarn
yarn add @getpara/react-sdk viem @tanstack/react-query
```
```bash pnpm
pnpm add @getpara/react-sdk viem @tanstack/react-query
```
### Add your keys to .env.local
```bash
NEXT_PUBLIC_THIRDWEB_CLIENT_ID=your_thirdweb_client_id
NEXT_PUBLIC_PARA_API_KEY=your_para_api_key
```
## Setup Para Provider
```tsx filename="app/providers.tsx"
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ParaProvider } from "@getpara/react-sdk";
const queryClient = new QueryClient();
export default function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
## Usage
Use the `useThirdwebSmartAccount` hook to create and manage a thirdweb smart account. The hook handles Para signer creation, smart wallet setup, and gas sponsorship configuration internally.
```tsx filename="components/ThirdwebDemo.tsx"
"use client";
import { useThirdwebSmartAccount } from "@getpara/react-sdk";
import { useMutation } from "@tanstack/react-query";
import { sepolia } from "viem/chains";
import { parseEther } from "viem";
const THIRDWEB_CLIENT_ID = process.env.NEXT_PUBLIC_THIRDWEB_CLIENT_ID!;
export function ThirdwebDemo() {
const { smartAccount, isLoading, error } = useThirdwebSmartAccount({
clientId: THIRDWEB_CLIENT_ID,
chain: sepolia,
sponsorGas: true, // enable gas sponsorship (default)
mode: "4337", // or "7702"
});
const {
mutate: sendTransaction,
isPending: isSending,
error: sendError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendTransaction({
to: "0xRecipient",
value: parseEther("0.01"),
});
},
onSuccess: (receipt) => {
console.log("Transaction hash:", receipt.transactionHash);
},
});
const {
mutate: sendBatch,
isPending: isBatching,
error: batchError,
} = useMutation({
mutationFn: async () => {
if (!smartAccount) throw new Error("Smart account not ready");
return smartAccount.sendBatchTransaction([
{ to: "0xRecipientA", value: parseEther("0.01") },
{ to: "0xRecipientB", data: "0xencodedCallData" },
]);
},
onSuccess: (receipt) => {
console.log("Batch tx hash:", receipt.transactionHash);
},
});
if (isLoading) return Setting up smart account...
;
if (error) return Error: {error.message}
;
if (!smartAccount) return null;
return (
thirdweb Smart Account
Address: {smartAccount.smartAccountAddress}
Mode: {smartAccount.mode}
sendTransaction()} disabled={isSending}>
{isSending ? "Sending..." : "Send Transaction"}
{sendError &&
{sendError.message}
}
sendBatch()} disabled={isBatching}>
{isBatching ? "Batching..." : "Send Batch"}
{batchError &&
{batchError.message}
}
);
}
```
## Key Features
- **Dual mode support**: Choose between EIP-4337 (smart wallets via bundler) or EIP-7702 (EOA delegation)
- **Built-in gas sponsorship**: Enable `sponsorGas: true` for gasless transactions
- **Transaction batching**: Send multiple transactions atomically via `sendBatchTransaction`
- **Para wallet management**: Embedded wallet infrastructure with MPC security
## Next Steps
Explore thirdweb's smart wallet features and SDK documentation
See all supported AA providers and the unified SmartAccount interface
## Related Walkthroughs
Intermediate · 30 min · EIP-7702 smart accounts with session keys
Advanced · 40 min · Cross-chain smart accounts with gas abstraction
Advanced · 45 min · Lending, borrowing, and yield strategies
# Integrate x402 with Para
Source: https://docs.getpara.com/v3/walkthroughs/X402
Para provides a standard ViemAccount that works seamlessly with x402's HTTP payment protocol. This guide shows you how to integrate Para wallets with x402 for stablecoin payments.
## Prerequisites
- Para SDK configured with API credentials
- Node.js 18+ or modern browser environment
- Basic understanding of Viem accounts
- x402 facilitator URL (default: `https://x402.org/facilitator`)
## Installation
### Install packages
Install the required packages:
```bash
npm install @getpara/viem-v2-integration viem@^2 @coinbase/x402 x402-express --save-exact
```
## Setup
```typescript
import Para, { Environment } from '@getpara/web-sdk';
const para = new Para(Environment.BETA, YOUR_API_KEY);
```
Para provides a standard ViemAccount that works with any Viem-compatible library:
```typescript
import { createParaViemAccount } from '@getpara/viem-v2-integration';
const viemAccount = await createParaViemAccount(para);
```
## Usage
### Client-Side Payments
```typescript
import { wrapFetchWithPayment } from '@coinbase/x402';
// Wrap fetch with x402 payments using Para's ViemAccount
const paymentFetch = wrapFetchWithPayment(fetch, viemAccount);
// Make payment-enabled requests
const response = await paymentFetch('https://api.example.com/premium');
const data = await response.json();
```
### React Hook Integration
```typescript
import { useAccount } from '@getpara/react-sdk';
import { createParaViemAccount } from '@getpara/viem-v2-integration';
import { wrapFetchWithPayment } from '@coinbase/x402';
function PaymentComponent() {
const { para } = useAccount();
const handlePayment = async () => {
const viemAccount = await createParaViemAccount(para);
const paymentFetch = wrapFetchWithPayment(fetch, viemAccount);
const response = await paymentFetch('/api/endpoint');
const data = await response.json();
console.log('Payment complete:', data);
};
return Pay with Para ;
}
```
### Server-Side Setup
Set up your Express server with x402 payment middleware:
```typescript
import express from 'express';
import { paymentMiddleware } from 'x402-express';
import { facilitator } from '@coinbase/x402';
const app = express();
```
Configure the middleware with your wallet address and pricing:
```typescript
app.use('/api/premium', paymentMiddleware(
'0xYourWalletAddress', // Your receiving wallet
{
'GET /api/premium': {
price: '$0.01',
network: 'base'
}
},
facilitator // or { url: 'https://x402.org/facilitator' } for testnet
));
```
Add your protected endpoint and start the server:
```typescript
app.get('/api/premium', (req, res) => {
res.json({ data: 'Premium content' });
});
app.listen(3000);
```
## Examples
### Autonomous Agent
Build an agent that makes autonomous payments:
```typescript
import Para, { Environment } from '@getpara/server-sdk';
import { createParaViemAccount } from '@getpara/viem-v2-integration';
import { wrapFetchWithPayment } from '@coinbase/x402';
class PaymentAgent {
private para: Para;
private viemAccount: any;
async initialize() {
this.para = new Para(Environment.BETA, process.env.PARA_API_KEY);
this.viemAccount = await createParaViemAccount(this.para);
}
async payForService(url: string, maxAmount: string) {
const paymentFetch = wrapFetchWithPayment(fetch, this.viemAccount, {
maxAmount
});
return await paymentFetch(url);
}
}
// Usage
const agent = new PaymentAgent();
await agent.initialize();
const response = await agent.payForService('https://api.example.com/premium', '0.10');
```
### Multi-Chain Payments
Use Para's multi-chain support with x402:
```typescript
import { createParaViemAccount } from '@getpara/viem-v2-integration';
import { http } from 'viem';
import { base, ethereum, polygon } from 'viem/chains';
// Create Para Viem client with multi-chain support
const paraClient = createParaViemAccount(para, {
chain: base, // Default chain
transport: http()
});
// Use with x402 - automatically handles chain switching
const paymentFetch = wrapFetchWithPayment(fetch, paraClient.account);
```
## Next Steps
Learn more about the x402 payment protocol
Explore Para's Viem integration in detail
## Related Walkthroughs
Intermediate · 25 min · Large-scale wallet generation
Intermediate · 25 min · Zero-knowledge virtual machine
Advanced · 45 min · Browser extensions with Para wallets
# Integrate Aave v3 with Para
Source: https://docs.getpara.com/v3/walkthroughs/aave
## Integrating Aave V3 with Para's Viem Accounts
Combine Para's viem account infrastructure with [Aave v3](https://docs-aave-git-feat-api-sdk-documentation-avaraxyz.vercel.app/docs/developers/aave-v3/overview) to enable seamless borrowing, lending, and yield strategies, all from a Para-powered wallet.
## What You Need
You need these components to integrate Aave V3 with Para:
- **Para SDK** for creating and managing wallets
- **Aave V3 SDK / ABI** for interacting with the protocol
- **EVM-compatible chain** such as Polygon, Optimism, or Arbitrum
## Step-by-Step Integration
1. Initialize Para (complete authentication before signing)
```
import { supply } from '@aave/client/actions';
import { sendWith } from '@aave/client/viem';
import { createParaViemClient, createParaViemAccount } from "@getpara/viem-v2-integration";
import { http } from 'viem';
import { mainnet } from 'viem/chains';
import {ParaWeb } from "@getpara/react-sdk";
// Initialize Para (complete authentication before signing)
const para = new ParaWeb('YOUR_API_KEY_HERE');
```
2. Create Para account
```
const account = await createParaViemAccount(para);
```
3. Create Para Viem wallet client for transactions
```
const wallet = createParaViemClient(para, {
account,
chain: mainnet,
transport: http(), // Or specify RPC URL: http('https://your-rpc-url')
});
const result = await supply(client, {
market: evmAddress('0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4e2'), // Aave V3 Pool address
amount: {
erc20: {
currency: evmAddress('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'), // USDC on Mainnet
value: '1000' // 1000 USDC
}
},
supplier: evmAddress('0xYourUserAddressHere'),
chainId: chainId(1), // Mainnet
})
.andThen(sendWith(wallet)) // Signs and sends via Para Viem wallet client
.andThen(client.waitForTransaction); // Waits for confirmation
```
## Why Combine Para and Aave
| Feature | Benefit |
|---------------|----------------------------------------------|
| Para wallets | Instant onboarding, MPC-secure, white-label |
| Aave V3 | Yield, borrow, leverage strategies |
Together, you can enable [fintech-grade](https://www.getpara.com/fintech) defi UX in your app without friction.
## Example Use Cases
Integrating Para and Aave enable these example use cases:
**1. Fintech App Integration**: Embed borrow and lend flows directly in your fintech or stablecoin application
**2. Stablecoin Vaults**: Run automated stablecoin vaults using Aave yield generation
**3. Treasury Management**: Automate treasury strategies from a Para wallet with programmable rules
## Related Walkthroughs
Beginner · 20 min · Programmable stablecoin infrastructure
Advanced · 45 min · Cross-chain USDC bridges
Beginner · 20 min · Stablecoin issuance API
# Stablecoin Yield via Paxos Amplify
Source: https://docs.getpara.com/v3/walkthroughs/amplify
Combine Para's wallet infrastructure with [Amplify](https://www.paxoslabs.com) to enable stablecoin yield deposits and withdrawals, all from a Para-powered wallet.
This walkthrough covers two integration paths: the **Amplify SDK** for a higher-level API, and **direct smart contract** calls for environments where the SDK isn't available.
## Why Combine Para and Paxos Amplify
| Para Wallets | Paxos Amplify |
|---|---|
| Instant onboarding, MPC-secure, white-label | Stablecoin yield across multiple strategies |
| Embedded + external wallet support | Multi-chain support, unified deposit API |
| Email/social login — no seed phrase or browser extension | Audited smart contracts |
## Example Use Cases
1. **Fintech App Integration** — Embed stablecoin deposit and yield flows directly in your fintech application with Para's white-label wallet onboarding.
2. **Stablecoin Yield Product** — Build a consumer-facing earn product where users deposit stablecoins into Amplify vaults through a Para-powered wallet — no browser extension required.
3. **Treasury Management** — Automate treasury yield strategies from Para wallets with programmable deposit and withdrawal rules.
4. **Multi-Language Backend** — Call Amplify contracts directly from Python, Go, Swift, or any language with an Ethereum JSON-RPC library — no JavaScript SDK required.
## What You Need
- **Para SDK** for creating and managing wallets
- **EVM-compatible chain** such as Ethereum Mainnet
- **Amplify API Credentials** (`pxl_your_api_key`) from [support@paxoslabs.com](mailto:support@paxoslabs.com)
- **Amplify SDK** (SDK path only) — `@paxoslabs/amplify-sdk`
## Step-by-Step Integration
### Step 1: Install Dependencies
```bash
npm install @getpara/viem-v2-integration @getpara/react-sdk @paxoslabs/amplify-sdk viem
```
### Step 2: Initialize Para and Amplify
Complete authentication before signing any transactions.
```typescript
import { createParaViemClient, createParaAccount } from "@getpara/viem-v2-integration";
import { ParaWeb } from "@getpara/react-sdk";
import { http, createPublicClient } from "viem";
import { mainnet } from "viem/chains";
import { initAmplifySDK } from "@paxoslabs/amplify-sdk";
// Initialize Para
const para = new ParaWeb("YOUR_PARA_API_KEY");
// Initialize Amplify SDK
await initAmplifySDK("pxl_your_api_key", {
rpcUrls: { [mainnet.id]: "https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY" },
});
```
### Step 3: Create Para Account and Clients
```typescript
const account = await createParaAccount(para);
const walletClient = createParaViemClient(para, {
account,
chain: mainnet,
transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"),
});
const publicClient = createPublicClient({
chain: mainnet,
transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"),
});
```
### Step 4: Discover a Vault and Deposit
The SDK auto-detects the optimal authorization method (gasless permit, standard approval, or existing allowance) and handles each path for you.
```typescript
import {
getVaultsByConfig,
prepareDepositAuthorization,
prepareDeposit,
isPermitAuth,
isApprovalAuth,
isAlreadyApprovedAuth,
YieldType,
} from "@paxoslabs/amplify-sdk";
// Find a vault by yield strategy, chain, and asset
const [vault] = await getVaultsByConfig({
yieldType: YieldType.CORE, // "CORE", "TREASURY", or "FRONTIER"
chainId: mainnet.id,
depositAssetAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
});
const params = {
vaultName: vault.name,
depositAsset: vault.vault.baseTokenAddress,
depositAmount: "1000", // 1000 USDC
to: account.address,
chainId: mainnet.id,
};
// Auto-detect optimal authorization method
const auth = await prepareDepositAuthorization(params);
if (isPermitAuth(auth)) {
// Para signs the EIP-712 permit off-chain (gasless)
const signature = await walletClient.signTypedData({
account,
...auth.permitData,
});
const prepared = await prepareDeposit({
...params,
signature,
deadline: auth.permitData.message.deadline,
});
const hash = await walletClient.writeContract({ ...prepared.txData, account });
await publicClient.waitForTransactionReceipt({ hash });
} else if (isApprovalAuth(auth)) {
// Standard ERC-20 approve followed by deposit
const approvalHash = await walletClient.writeContract({ ...auth.txData, account });
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
const prepared = await prepareDeposit(params);
const depositHash = await walletClient.writeContract({ ...prepared.txData, account });
await publicClient.waitForTransactionReceipt({ hash: depositHash });
} else if (isAlreadyApprovedAuth(auth)) {
// Sufficient allowance — deposit directly
const prepared = await prepareDeposit(params);
const hash = await walletClient.writeContract({ ...prepared.txData, account });
await publicClient.waitForTransactionReceipt({ hash });
}
```
### Step 5: Withdraw from a Vault
```typescript
import {
prepareWithdrawalAuthorization,
prepareWithdrawal,
isWithdrawApprovalAuth,
} from "@paxoslabs/amplify-sdk";
const withdrawParams = {
vaultName: vault.name,
wantAsset: vault.vault.baseTokenAddress,
withdrawAmount: "500", // 500 Shares
userAddress: account.address,
chainId: mainnet.id,
};
const withdrawAuth = await prepareWithdrawalAuthorization(withdrawParams);
if (isWithdrawApprovalAuth(withdrawAuth)) {
const approvalHash = await walletClient.writeContract({ ...withdrawAuth.txData, account });
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
}
const withdrawTxData = await prepareWithdrawal(withdrawParams);
const withdrawHash = await walletClient.writeContract({ ...withdrawTxData, account });
await publicClient.waitForTransactionReceipt({ hash: withdrawHash });
```
### Step 1: Install Dependencies
```bash
npm install @getpara/viem-v2-integration @getpara/react-sdk viem
```
### Step 2: Initialize Para
Complete authentication before signing any transactions.
```typescript
import { createParaViemClient, createParaAccount } from "@getpara/viem-v2-integration";
import { ParaWeb } from "@getpara/react-sdk";
import { http, createPublicClient } from "viem";
import { mainnet } from "viem/chains";
const para = new ParaWeb("YOUR_PARA_API_KEY");
```
### Step 3: Create Para Account and Clients
```typescript
const account = await createParaAccount(para);
const walletClient = createParaViemClient(para, {
account,
chain: mainnet,
transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"),
});
const publicClient = createPublicClient({
chain: mainnet,
transport: http("https://eth-mainnet.g.alchemy.com/v2/YOUR_KEY"),
});
```
### Step 4: Discover Contract Addresses
Query the Amplify GraphQL API to get vault and module addresses for your target chain and yield type.
```typescript
const response = await fetch("https://api.paxoslabs.com/graphql", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "YOUR_AMPLIFY_API_KEY",
},
body: JSON.stringify({
query: `query {
amplifySdkConfigs(chainId: 1, yieldType: CORE) {
vault {
boringVaultAddress
communityCodeDepositorModuleId
withdrawQueueModuleId
accountantModuleId
supportedAssets { address symbol decimals depositable withdrawable }
}
}
}`,
}),
});
const { data } = await response.json();
const vault = data.amplifySdkConfigs[0].vault;
const depositorAddress = vault.communityCodeDepositorModuleId;
const withdrawQueueAddress = vault.withdrawQueueModuleId;
const accountantAddress = vault.accountantModuleId;
const boringVaultAddress = vault.boringVaultAddress;
const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
```
### Step 5: Approve and Deposit
```typescript
const erc20Abi = [
{
inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" },
],
name: "approve",
outputs: [{ name: "", type: "bool" }],
stateMutability: "nonpayable",
type: "function",
},
];
const depositorAbi = [
{
inputs: [
{ name: "depositAsset", type: "address" },
{ name: "depositAmount", type: "uint256" },
{ name: "minimumMint", type: "uint256" },
{ name: "to", type: "address" },
{ name: "distributorCode", type: "bytes" },
{
components: [
{ name: "uuid", type: "string" },
{ name: "expiration", type: "uint256" },
{ name: "attester", type: "address" },
{ name: "signature", type: "bytes" },
],
name: "_attestation",
type: "tuple",
},
],
name: "deposit",
outputs: [{ name: "shares", type: "uint256" }],
stateMutability: "nonpayable",
type: "function",
},
];
const depositAmount = 1000000000n; // 1,000 USDC (6 decimals)
// Approve the depositor to spend your USDC
const approvalHash = await walletClient.writeContract({
address: usdcAddress,
abi: erc20Abi,
functionName: "approve",
args: [depositorAddress, depositAmount],
account,
});
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
// Deposit into the vault
const depositHash = await walletClient.writeContract({
address: depositorAddress,
abi: depositorAbi,
functionName: "deposit",
args: [
usdcAddress,
depositAmount,
0n, // minimumMint — see production note below
account.address,
"0x", // distributorCode
{
uuid: "",
expiration: 0n,
attester: "0x0000000000000000000000000000000000000000",
signature: "0x",
},
],
account,
});
await publicClient.waitForTransactionReceipt({ hash: depositHash });
```
In production, query `Accountant.getRateInQuoteSafe(usdcAddress)` to calculate a safe `minimumMint` with slippage protection instead of passing `0n`. See the slippage calculation below.
**Production slippage calculation:**
The intermediate arithmetic uses WAD (1e18) precision, then scales to the vault share token's on-chain decimals.
```typescript
const WAD = 10n ** 18n;
const SLIPPAGE_BPS = 50n; // 0.5%
const accountantAbi = [
{
inputs: [{ name: "quote", type: "address" }],
name: "getRateInQuoteSafe",
outputs: [{ name: "rateInQuote", type: "uint256" }],
stateMutability: "view",
type: "function",
},
] as const;
const rateInQuote = await publicClient.readContract({
address: accountantAddress,
abi: accountantAbi,
functionName: "getRateInQuoteSafe",
args: [usdcAddress],
});
const vaultTokenDecimals = await publicClient.readContract({
address: boringVaultAddress,
abi: [{ inputs: [], name: "decimals", outputs: [{ type: "uint8" }], stateMutability: "view", type: "function" }],
functionName: "decimals",
});
const idealMint = (depositAmount * WAD) / rateInQuote;
const slippageWad = (SLIPPAGE_BPS * WAD) / 10_000n;
const slippageAmount = (idealMint * slippageWad) / WAD;
const minimumMint = vaultTokenDecimals > 18
? (idealMint - slippageAmount) * 10n ** (BigInt(vaultTokenDecimals) - 18n)
: (idealMint - slippageAmount) / 10n ** (18n - BigInt(vaultTokenDecimals));
```
### Step 6: Submit a Withdrawal Order
Withdrawal orders are fulfilled by the Amplify account operator, typically within 24 hours.
```typescript
const withdrawQueueAbi = [
{
inputs: [
{
components: [
{ name: "amountOffer", type: "uint256" },
{ name: "wantAsset", type: "address" },
{ name: "intendedDepositor", type: "address" },
{ name: "receiver", type: "address" },
{ name: "refundReceiver", type: "address" },
{
components: [
{ name: "approvalMethod", type: "uint8" },
{ name: "approvalV", type: "uint8" },
{ name: "approvalR", type: "bytes32" },
{ name: "approvalS", type: "bytes32" },
{ name: "submitWithSignature", type: "bool" },
{ name: "deadline", type: "uint256" },
{ name: "eip2612Signature", type: "bytes" },
],
name: "signatureParams",
type: "tuple",
},
],
name: "params",
type: "tuple",
},
],
name: "submitOrder",
outputs: [{ name: "orderIndex", type: "uint256" }],
stateMutability: "nonpayable",
type: "function",
},
];
const sharesToWithdraw = 500000000000000000000n; // example share amount
// Approve vault shares to the WithdrawQueue
const shareApprovalHash = await walletClient.writeContract({
address: boringVaultAddress,
abi: erc20Abi,
functionName: "approve",
args: [withdrawQueueAddress, sharesToWithdraw],
account,
});
await publicClient.waitForTransactionReceipt({ hash: shareApprovalHash });
// Submit the withdrawal order
const withdrawHash = await walletClient.writeContract({
address: withdrawQueueAddress,
abi: withdrawQueueAbi,
functionName: "submitOrder",
args: [
{
amountOffer: sharesToWithdraw,
wantAsset: usdcAddress,
intendedDepositor: account.address,
receiver: account.address,
refundReceiver: account.address,
signatureParams: {
approvalMethod: 0,
approvalV: 0,
approvalR: "0x0000000000000000000000000000000000000000000000000000000000000000",
approvalS: "0x0000000000000000000000000000000000000000000000000000000000000000",
submitWithSignature: false,
deadline: 0n,
eip2612Signature: "0x",
},
},
],
account,
});
await publicClient.waitForTransactionReceipt({ hash: withdrawHash });
```
Use `WithdrawQueue.getOrderStatus(orderIndex)` to poll for withdrawal completion.
## Related Resources
Stablecoin yield protocol documentation and API reference
Set up Para wallets with Viem for EVM transaction signing
Sign structured data using the EIP-712 standard with Para wallets
# Integrate Arc Chain with Para
Source: https://docs.getpara.com/v3/walkthroughs/arc
## Prerequisites
Before integrating Para with Arc Chain, ensure you have:
- A Para API key from the Para Developer Portal
- Node.js 18+ and Next.js development environment
- Basic familiarity with React and TypeScript
## Installation
``` npm install @getpara/react-sdk ```
Please make sure to be on version 2.2.0
## Setup Para Provider
Create a ParaSDKProvider that communicates with Arc Chain.
```import { ParaSDKProvider } from "@para/sdk-react";
const ARC_TESTNET = {
name: "Arc Testnet",
evmChainId: "5042002" as const,
nativeTokenSymbol: "USDC",
logoUrl:
"", // Replace with your Arc logo URL
rpcUrl: "https://rpc.testnet.arc.network",
explorer: {
name: "ArcScan Testnet Explorer",
url: "https://testnet.arcscan.app",
txUrlFormat:
"https://testnet.arcscan.app/tx/{HASH}",
},
isTestnet: true,
};
export function ParaProvider({ children }: { children: React.ReactNode }) {
return (
", // Replace with your Arc logo URL
implementations: [
{
network: ARC_TESTNET,
},
],
},
],
},
disableEmailLogin: false,
disablePhoneLogin: false,
authLayout: ["AUTH:FULL", "EXTERNAL:FULL"],
oAuthMethods: ["GOOGLE"],
onRampTestMode: true,
theme: {
foregroundColor: "#222222",
backgroundColor: "#FFFFFF",
mode: "light",
borderRadius: "none",
font: "Inter",
},
logo: "/para.svg",
recoverySecretStepEnabled: true,
twoFactorAuthEnabled: false,
}}
>
{children}
);
}
```
## Key Features
This integration provides a way to use Para Wallet with Arc Chain, an EVM-compatible L1 blockchain that uses USDC as its native gas token for predictable fiat-based transaction fees. Arc features sub-second deterministic finality and is designed for programmable money, lending, capital markets, FX, and payments.
## Related Walkthroughs
Beginner · 20 min · Bitcoin sidechain with EVM compatibility
Intermediate · 20 min · Send ETH with Ethers and Viem
Beginner · 20 min · Programmable stablecoin infrastructure
# Integrate Brale with Para
Source: https://docs.getpara.com/v3/walkthroughs/brale
## Integrating Brale with Para Wallets
[Brale](https://docs.brale.xyz) is a compliant stablecoin issuance and orchestration platform built for fintechs and onchain ecosystems. Combine Para's wallet infrastructure with [Brale](https://docs.brale.xyz)'s financial rails to mint, redeem, and manage stablecoins across 20+ chains, all from a Para-powered wallet.
## What You Need
You need these components to integrate Brale with Para:
- **Para SDK** for creating and managing wallets
- **Brale API credentials** (`client_id` and `client_secret`) from the [Brale dashboard](https://app.brale.xyz/)
- **EVM-compatible chain** such as Base, Polygon, or Ethereum
## Step-by-Step Integration
1. Initialize Para and create a viem wallet client
```typescript
import { createParaViemClient, createParaViemAccount } from "@getpara/viem-v2-integration";
import { http } from "viem";
import { base } from "viem/chains";
import { ParaWeb } from "@getpara/react-sdk";
// Initialize Para (complete authentication before signing)
const para = new ParaWeb("YOUR_PARA_API_KEY");
const account = await createParaViemAccount(para);
const wallet = createParaViemClient(para, {
account,
chain: base,
transport: http(),
});
```
2. Obtain a Brale bearer token via OAuth 2.0 client credentials
```typescript
const tokenResponse = await fetch("https://auth.brale.xyz/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
grant_type: "client_credentials",
client_id: BRALE_CLIENT_ID,
client_secret: BRALE_CLIENT_SECRET,
}),
});
const { access_token } = await tokenResponse.json();
```
3. Retrieve your Brale account ID
```typescript
const accountsResponse = await fetch("https://api.brale.xyz/accounts", {
headers: { Authorization: `Bearer ${access_token}` },
});
const accounts = await accountsResponse.json();
const accountId = accounts[0].id;
```
4. Register the Para wallet address with Brale
```typescript
const walletAddress = account.address;
const registerResponse = await fetch(
`https://api.brale.xyz/accounts/${accountId}/addresses/external`,
{
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "Non-Custodial (External) Wallet",
transfer_types: ["ethereum", "base"],
wallet_address: walletAddress,
}),
}
);
const addressData = await registerResponse.json();
const addressId = addressData.id;
```
5. Mint stablecoins (fiat-to-stablecoin) to the Para wallet
These examples use [SBC (Stablecoin)](https://stablecoin.xyz/) — Brale's native stablecoin. When you mint via Brale today, funds arrive as SBC. From there you can swap SBC to USDC or any other supported stablecoin through Brale's platform.
```typescript
const mintResponse = await fetch(
`https://api.brale.xyz/accounts/${accountId}/transfers`,
{
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: { value: "1000", currency: "USD" },
source: { value_type: "USD", transfer_type: "wire" },
destination: { address_id: addressId, value_type: "SBC", transfer_type: "base" },
}),
}
);
const mint = await mintResponse.json();
```
6. Redeem stablecoins (stablecoin-to-fiat) from the Para wallet
```typescript
const redeemResponse = await fetch(
`https://api.brale.xyz/accounts/${accountId}/transfers`,
{
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: { value: "500", currency: "USD" },
source: { address_id: addressId, value_type: "SBC", transfer_type: "base" },
destination: { value_type: "USD", transfer_type: "wire" },
}),
}
);
const redeem = await redeemResponse.json();
```
## Why Combine Para and Brale
[Para](https://www.getpara.com) and [Brale](https://docs.brale.xyz) are purpose-built for the same audience — fintechs, neobanks, and onchain ecosystems that need to move real money. Para provides the wallet infrastructure: MPC-secured wallets, instant user onboarding, and white-label UX across web, mobile, server, and REST API. Brale provides the financial rails: compliant stablecoin issuance, fiat on-ramps and off-ramps, and orchestration across 20+ chains.
Together, Para+Brale closes the full loop from user identity to settled funds onchain.
| Layer | Provider | What it does |
|---|---|---|
| Wallet infrastructure | Para | MPC-secured wallets, instant onboarding, white-label UX across web, mobile, server, and REST API |
| Stablecoin issuance | Brale | Mint and redeem compliant stablecoins, fiat on/off-ramps across 20+ chains |
| Settlement | Para + Brale | Sign and broadcast transactions from Para wallets funded via Brale |
**Why this combination wins for fintechs:**
- **Compliance-ready from day one** — Brale handles stablecoin licensing and reserve management; Para handles wallet security and key custody
- **No fragmented vendors** — replace a patchwork of banking APIs, custody providers, and blockchain nodes with two focused SDKs
- **User experience that converts** — Para's embedded wallet UX means users never leave your app to fund or move stablecoins
- **Multi-chain by default** — issue on Base, Ethereum, Polygon, and more without rewriting your integration
## Example Use Cases
Integrating Para and Brale enables these example use cases:
**1. Stablecoin Payments App**: Build a payments experience with instant mint and redeem flows powered by Para wallets
**2. Treasury Management**: Automate stablecoin operations for treasury workflows from a Para wallet with programmable rules
**3. Cross-Chain Stablecoin Orchestration**: Manage stablecoin issuance and redemption across multiple chains from a single Para wallet
## Related Walkthroughs
Beginner · 20 min · Programmable stablecoin infrastructure
Advanced · 45 min · Lending, borrowing, and yield strategies
Intermediate · 20 min · Send ETH with Ethers and Viem
# Bulk Wallet Pregeneration for Twitter
Source: https://docs.getpara.com/v3/walkthroughs/bulk-pregeneration
For new bulk wallet creation, consider the [REST API](/v3/rest/overview) — loop `POST /v1/wallets` with a `TWITTER`
identifier and Para stores the key material for you, with the same automatic claiming when users sign in. This
walkthrough uses the Server SDK, which requires storing user shares yourself.
Bulk pregeneration allows you to create Para wallets for multiple Twitter users ahead of time. Users can later claim these wallets by authenticating with their Twitter accounts, creating a seamless onboarding experience for airdrops, token distributions, or whitelist rewards.
## How Bulk Pregeneration Works
When you bulk pregenerate wallets:
1. **Generate wallets** for Twitter usernames using Para's server SDK
2. **Store user shares** securely on your backend
3. **Fund wallets** with tokens or NFTs for airdrops (optional)
4. **Users claim wallets** later by signing in with Twitter
5. **Para matches** the Twitter username to the pregenerated wallet
## Prerequisites
You need a Para API key and basic knowledge of Node.js/TypeScript development.
## Getting Twitter Usernames
You can obtain Twitter usernames for bulk pregeneration from various sources:
- **Database of pre-registered users** - Users who joined your whitelist or waitlist
- **Twitter API** - Programmatically fetch followers, mentions, or community members
- **CSV files** - Export from existing user databases or CRM systems
- **Contest participants** - Users who engaged with your Twitter campaigns
For this tutorial, we'll use a local CSV file as an example, but the core logic applies regardless of your data source.
## Server-Side Implementation
### Setup Para Server Client
First, create a Para server client to handle wallet generation:
```typescript lib/para-server.ts
import { Para } from "@getpara/server-sdk";
export function getParaServerClient() {
const apiKey = process.env.PARA_API_KEY;
if (!apiKey) {
throw new Error("PARA_API_KEY is required");
}
return new Para(apiKey);
}
```
### Create Bulk Generation API
Create an API endpoint to generate wallets for Twitter usernames:
```typescript api/wallet/generate/route.ts
import { getParaServerClient } from "@/lib/para-server";
import { NextResponse } from "next/server";
interface GenerateWalletRequest {
handle: string;
type: "TWITTER";
}
export async function POST(request: Request) {
try {
const { handle, type }: GenerateWalletRequest = await request.json();
if (!handle || type !== "TWITTER") {
return NextResponse.json({ error: "Invalid handle or type" }, { status: 400 });
}
const para = getParaServerClient();
const wallet = await para.createPregenWallet({
type: "EVM",
pregenId: { xUsername: handle.trim() }
});
const userShare = await para.getUserShare();
if (!wallet || !userShare) {
throw new Error("Failed to generate wallet");
}
await storeWalletData(handle.trim(), wallet, userShare);
return NextResponse.json({
success: true,
handle: handle.trim(),
address: wallet.address
});
} catch (error) {
console.error("Wallet generation error:", error);
return NextResponse.json({ error: "Generation failed" }, { status: 500 });
}
}
async function storeWalletData(handle: string, wallet: any, userShare: any) {
// Store wallet and user share in your database
// This is critical for users to claim their wallets later
}
```
### Batch Processing Implementation
For processing multiple handles efficiently:
```typescript hooks/use-batch-processor.ts
import { useState } from "react";
interface BatchResult {
handle: string;
success: boolean;
address?: string;
error?: string;
}
export function useBatchProcessor() {
const [processing, setProcessing] = useState(false);
const [results, setResults] = useState([]);
const [progress, setProgress] = useState(0);
const processBatch = async (handles: string[]) => {
setProcessing(true);
setResults([]);
setProgress(0);
const batchSize = 10;
const batches = [];
for (let i = 0; i < handles.length; i += batchSize) {
batches.push(handles.slice(i, i + batchSize));
}
const allResults: BatchResult[] = [];
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
const batchResults = await Promise.all(
batch.map(async (handle) => {
try {
const response = await fetch("/api/wallet/generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ handle, type: "TWITTER" })
});
const data = await response.json();
return {
handle,
success: data.success,
address: data.address,
error: data.error
};
} catch (error) {
return {
handle,
success: false,
error: "Network error"
};
}
})
);
allResults.push(...batchResults);
setResults([...allResults]);
setProgress(((i + 1) / batches.length) * 100);
// Rate limiting delay
if (i < batches.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
setProcessing(false);
};
return {
processing,
results,
progress,
processBatch
};
}
```
## Alternative Data Sources
### From Database
You can fetch usernames directly from your database:
```typescript
// Example: Fetch whitelist users from database
async function getWhitelistUsers() {
const users = await db.users.findMany({
where: { whitelisted: true },
select: { twitterUsername: true }
});
return users.map(user => user.twitterUsername).filter(Boolean);
}
```
### From Twitter API
Programmatically fetch Twitter usernames:
```typescript
// Example: Get followers using Twitter API
async function getFollowers(userId: string) {
const response = await fetch(`https://api.twitter.com/2/users/${userId}/followers`, {
headers: {
'Authorization': `Bearer ${process.env.TWITTER_BEARER_TOKEN}`
}
});
const data = await response.json();
return data.data?.map((user: any) => user.username) || [];
}
```
### Example: CSV Processing
For this tutorial, we'll demonstrate with a CSV file containing Twitter handles:
```csv
handle,type
@username1,twitter
@username2,twitter
username3,twitter
```
The `@` symbol is optional and will be automatically handled. Headers are also optional.
## Frontend Considerations
While the UI implementation depends on your application's design system, consider these patterns:
- **Progress tracking** - Show real-time batch processing status
- **Error handling** - Display failed generations with retry options
- **Results export** - Allow downloading generation results
- **Batch size control** - Let users adjust processing batch sizes
The core logic remains the same regardless of your UI framework choice.
## Important Considerations
### Rate Limiting
Para's API has rate limits. Process handles in batches with delays:
```typescript
// Process 10 handles at a time with 1-second delays
const batchSize = 10;
const delay = 1000; // 1 second between batches
```
### Error Handling
Always implement retry logic for failed generations:
```typescript
const retryFailed = async (failedResults: BatchResult[]) => {
const failedHandles = failedResults
.filter(result => !result.success)
.map(result => result.handle);
// Retry processing
await processBatch(failedHandles);
};
```
### Data Storage
Store wallet data securely in your database:
- **Wallet addresses** for reference
- **User shares** for wallet claiming
- **Handle mappings** for Twitter username lookup
- **Generation timestamps** for tracking
## Testing Your Implementation
1. **Start with small batches** (5-10 handles)
2. **Use test Twitter handles** that you control
3. **Verify wallet generation** in Para Developer Portal
4. **Test claiming flow** with actual Twitter authentication
## Next Steps
Learn how users claim pregenerated wallets
Deep dive into Para's pregeneration system
## Related Walkthroughs
Intermediate · 25 min · Micropayments over HTTP
Advanced · 45 min · Browser extensions with Para wallets
Intermediate · 15 min · AI-powered migration tooling
# Canton Network External Party Onboarding with Para
Source: https://docs.getpara.com/v3/walkthroughs/canton-network
Canton Network uses Ed25519 keypairs for **external party** identities — wallets that participate in the ledger without running a Canton node. Because Para-managed Solana wallets are native Ed25519 keys, they work directly with Canton's external party API.
This walkthrough shows how to connect through ParaModal, prove key ownership to Canton, and receive a `partyId` on the ledger — entirely from a React app.
## How it works
User authenticates through ParaModal and gets an embedded Solana wallet.
Server sends the Solana public key to Canton's `generateExternalParty`, which returns a `multiHash` challenge.
Client signs the `multiHash` with `useSignMessage`.
Server submits the signature to Canton's `allocateExternalParty`, which returns a `partyId`.
## Prerequisites
- A Para API key from the [Para Developer Portal](https://developer.getpara.com)
- Access to a Canton ledger and validator — either a hosted deployment or a local [Splice LocalNet](https://docs.dev.sync.global/app_dev/testing/localnet.html) stack via docker-compose
- Node.js 18+ with Next.js (for the server-side Canton SDK calls)
- Basic familiarity with React and TypeScript
## Installation
```bash
npm install @getpara/react-sdk @canton-network/wallet-sdk @tanstack/react-query bs58 pino server-only
```
The Canton SDK (`@canton-network/wallet-sdk`) must run on the server. Using `server-only` ensures it never bundles into the browser.
## Project structure
The Canton SDK holds credentials for your validator. Keep it in Next.js API routes so those credentials never reach the client.
```
src/
├── app/page.tsx # React UI — ParaModal + sign button
├── app/api/canton/generate/route.ts # Server: generateExternalParty
├── app/api/canton/allocate/route.ts # Server: allocateExternalParty
├── components/ParaProvider.tsx # Para SDK + Solana config
├── hooks/useCantonOnboarding.ts # generate → sign → allocate
└── lib/canton.ts # Server-only Canton SDK setup
```
## Setup
### 1. Configure the Para Provider
Set up `ParaProvider` with embedded-wallet signups enabled. Para automatically provisions a Solana (Ed25519) wallet for each user, which is what Canton needs.
```typescript
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import ParaWeb, { Environment, ParaProvider as ParaSDKProvider } from "@getpara/react-sdk";
const para = new ParaWeb(Environment.BETA, process.env.NEXT_PUBLIC_PARA_API_KEY!);
const queryClient = new QueryClient();
export function ParaProvider({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
No `externalWalletConfig` is set — Canton external parties are backed by Para's own MPC-managed Solana key, not a user-supplied wallet like Phantom or MetaMask.
### 2. Initialize the Canton SDK (server-only)
Create `src/lib/canton.ts`. This mirrors Canton's own `localNetAuthDefault` pattern and is memoized so the SDK connects once per server process.
```typescript
import "server-only";
import { WalletSDKImpl, localNetAuthDefault, LedgerController } from "@canton-network/wallet-sdk";
import { pino } from "pino";
const logger = pino({ name: "canton", level: "info" });
let sdkPromise: Promise | null = null;
export function getSdk(): Promise {
if (sdkPromise) return sdkPromise;
const ledgerApiUrl = process.env.LEDGER_API_URL!;
const validatorApiUrl = process.env.VALIDATOR_API_URL!;
const validatorAudience = process.env.VALIDATOR_AUDIENCE!;
const unsafeSecret = process.env.AUTH_UNSAFE_SECRET!;
const userId = process.env.AUTH_USER_ID ?? "ledger-api-user";
const authFactory = () => {
const auth = localNetAuthDefault(logger as any);
auth.userId = userId;
(auth as any).audience = validatorAudience;
(auth as any).unsafeSecret = unsafeSecret;
return auth;
};
sdkPromise = (async () => {
const sdk = new WalletSDKImpl().configure({
logger,
authFactory,
ledgerFactory: (uid, auth, isAdmin) =>
new LedgerController(uid, new URL(ledgerApiUrl), undefined, isAdmin, auth),
});
await sdk.connect();
await sdk.connectAdmin();
await sdk.connectTopology(new URL(validatorApiUrl));
return sdk;
})().catch((err) => { sdkPromise = null; throw err; });
return sdkPromise;
}
```
For production Canton deployments, replace `localNetAuthDefault` with your validator's authentication method (typically OAuth/JWT). Update `VALIDATOR_AUDIENCE` to match your validator's expected audience.
### 3. Create the API routes
**`/api/canton/generate`** — decodes the Solana address and calls Canton to prepare the external party challenge.
```typescript
// src/app/api/canton/generate/route.ts
import { NextResponse } from "next/server";
import bs58 from "bs58";
import { getSdk } from "@/lib/canton";
export const runtime = "nodejs";
export async function POST(request: Request) {
const { solanaAddress, partyHint } = await request.json();
// Solana address (base58) → raw 32-byte Ed25519 public key → base64
const rawPubkey = bs58.decode(solanaAddress);
const publicKeyBase64 = Buffer.from(rawPubkey).toString("base64");
const sdk = await getSdk();
const generatedParty = await sdk.userLedger?.generateExternalParty(
publicKeyBase64,
partyHint
);
return NextResponse.json({
multiHash: generatedParty!.multiHash,
generatedParty,
});
}
```
**`/api/canton/allocate`** — submits the Para-produced Ed25519 signature to finalize the party.
```typescript
// src/app/api/canton/allocate/route.ts
import { NextResponse } from "next/server";
import { getSdk } from "@/lib/canton";
export const runtime = "nodejs";
export async function POST(request: Request) {
const { signatureBase64, generatedParty } = await request.json();
const sdk = await getSdk();
const allocatedParty = await sdk.userLedger?.allocateExternalParty(
signatureBase64,
generatedParty
);
return NextResponse.json({ partyId: allocatedParty!.partyId });
}
```
### 4. Build the onboarding hook
`useCantonOnboarding` does three things on the client:
1. Makes sure the user's Para **Solana** wallet is the active one (embedded accounts default to EVM).
2. Fetches the Canton challenge, signs it with `useSignMessage`, and submits the signature.
`useSignMessage` goes straight to Para's MPC `signMessage` endpoint, so there's no Solana RPC client to configure.
```typescript
"use client";
import { useCallback, useEffect, useState } from "react";
import { useAccount, useSignMessage, useWallet, useWalletState } from "@getpara/react-sdk";
export function useCantonOnboarding() {
const account = useAccount();
const { data: wallet } = useWallet();
const { setSelectedWallet } = useWalletState();
const { signMessageAsync } = useSignMessage();
// Para accounts can hold multiple embedded wallets (EVM, Solana, …).
// Force-select the Solana one so `wallet` / `wallet.address` is Ed25519.
useEffect(() => {
if (account?.isConnected && wallet?.type !== "SOLANA") {
const solanaWallet = account.embedded.wallets?.find((w) => w.type === "SOLANA");
if (solanaWallet) {
setSelectedWallet({ id: solanaWallet.id, type: "SOLANA" });
}
}
}, [account, wallet, setSelectedWallet]);
const [isPending, setIsPending] = useState(false);
const [error, setError] = useState(null);
const [multiHash, setMultiHash] = useState();
const [partyId, setPartyId] = useState();
const isSolanaWallet = wallet?.type === "SOLANA";
const address = isSolanaWallet ? wallet?.address : undefined;
const walletId = isSolanaWallet ? wallet?.id : undefined;
const onboard = useCallback(async () => {
if (!address || !walletId) return;
setIsPending(true);
setError(null);
try {
// Step 1: Register public key with Canton, receive multiHash challenge
const genRes = await fetch("/api/canton/generate", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ solanaAddress: address, partyHint: "my-app-party" }),
});
const { multiHash: mh, generatedParty } = await genRes.json();
setMultiHash(mh);
// Step 2: Sign the multiHash with Para's MPC-managed Ed25519 key
const signRes = await signMessageAsync({ walletId, messageBase64: mh });
if (!("signature" in signRes) || !signRes.signature) {
throw new Error("Para signing was denied or returned no signature");
}
const signatureBase64 = signRes.signature;
// Step 3: Submit signature to Canton to allocate the party
const allocRes = await fetch("/api/canton/allocate", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ signatureBase64, generatedParty }),
});
const { partyId: pid } = await allocRes.json();
setPartyId(pid);
} catch (err) {
setError(err instanceof Error ? err : new Error("Onboarding failed"));
} finally {
setIsPending(false);
}
}, [address, walletId, signMessageAsync]);
return { onboard, address, partyId, multiHash, isPending, error };
}
```
### 5. Wire up the UI
```typescript
"use client";
import { useModal, useAccount } from "@getpara/react-sdk";
import { useCantonOnboarding } from "@/hooks/useCantonOnboarding";
export default function Home() {
const { openModal } = useModal();
const { isConnected } = useAccount();
const { onboard, address, partyId, multiHash, isPending, error } = useCantonOnboarding();
if (!isConnected) {
return Connect with Para ;
}
return (
Solana address: {address ?? "Selecting Solana wallet…"}
{isPending ? "Onboarding…" : partyId ? "Onboarded" : "Onboard as Canton external party"}
{error &&
{error.message}
}
{multiHash &&
Signed multiHash: {multiHash}
}
{partyId &&
Canton partyId: {partyId}
}
);
}
```
## Environment variables
```bash
# Client
NEXT_PUBLIC_PARA_API_KEY=your_para_api_key
NEXT_PUBLIC_PARA_ENVIRONMENT=BETA
# Server-only (never prefix with NEXT_PUBLIC_)
LEDGER_API_URL=http://localhost:2975
VALIDATOR_API_URL=http://localhost:2903/api/validator
VALIDATOR_AUDIENCE=https://canton.network.global
AUTH_USER_ID=ledger-api-user
AUTH_UNSAFE_SECRET=unsafe
```
These defaults target the app-user node of a [Splice LocalNet](https://docs.dev.sync.global/app_dev/testing/localnet.html) docker-compose stack. For a hosted Canton deployment, update the URLs, swap `localNetAuthDefault` for the appropriate auth factory, and set `VALIDATOR_AUDIENCE` to whatever your validator expects.
## Complete example
A fully working Next.js app with this flow, including styled UI and error handling, is available in the Para Examples Hub:
Next.js + ParaModal + Canton Network — generate, sign, and allocate an external party on the Canton ledger
## Related walkthroughs
Intermediate · 25 min · Send SOL with Web3.js, Signers v2, and Anchor
Intermediate · 20 min · Send ETH with Ethers and Viem
Intermediate · 20 min · Structured data signing
# Chrome Extension Integration with Para
Source: https://docs.getpara.com/v3/walkthroughs/chrome-extension-integration
Building Chrome extensions with Para requires special considerations for state persistence and user experience. This walkthrough covers how to implement Chrome storage overrides, background workers, and seamless authentication flows.
## Chrome Extension Challenges
Chrome extensions present unique challenges for web applications:
- **State resets** - Clicking outside a popup can close and reset the application state
- **Limited popup space** - Small popup windows aren't ideal for complex authentication flows
- **Background execution** - Background workers need access to authentication state
- **Storage limitations** - Standard localStorage/sessionStorage APIs work differently in extensions
## Solution Overview
Para addresses these challenges through:
1. **Storage overrides** - Custom storage implementations using Chrome extension APIs
2. **Singleton promise pattern** - Shared Para instance across popup and background
3. **Smart routing** - Background worker decides between popup vs tab based on auth state
4. **State persistence** - Authentication state survives popup closures
## Setup and Configuration
### Install Dependencies
```bash
npm install @getpara/react-sdk
```
### Create Chrome Storage Overrides
Create a storage implementation that uses Chrome extension storage APIs:
```typescript lib/chrome-storage.ts
// Chrome local storage overrides
export const localStorageGetItemOverride = async (key: string): Promise => {
try {
// Handle special cases
if (key === "guestWalletIds" || key === "pregenIds") {
return JSON.stringify({});
}
const result = await chrome.storage.local.get([key]);
return result[key] || null;
} catch (error) {
console.error("Local storage get error:", error);
return null;
}
};
export const localStorageSetItemOverride = async (key: string, value: string): Promise => {
try {
await chrome.storage.local.set({ [key]: value });
} catch (error) {
console.error("Local storage set error:", error);
}
};
export const localStorageRemoveItemOverride = async (key: string): Promise => {
try {
await chrome.storage.local.remove([key]);
} catch (error) {
console.error("Local storage remove error:", error);
}
};
// Chrome session storage overrides
export const sessionStorageGetItemOverride = async (key: string): Promise => {
try {
if (key === "guestWalletIds" || key === "pregenIds") {
return JSON.stringify({});
}
const result = await chrome.storage.session.get([key]);
return result[key] || null;
} catch (error) {
console.error("Session storage get error:", error);
return null;
}
};
export const sessionStorageSetItemOverride = async (key: string, value: string): Promise => {
try {
await chrome.storage.session.set({ [key]: value });
} catch (error) {
console.error("Session storage set error:", error);
}
};
export const sessionStorageRemoveItemOverride = async (key: string): Promise => {
try {
await chrome.storage.session.remove([key]);
} catch (error) {
console.error("Session storage remove error:", error);
}
};
// Clear storage with Para prefix
export const clearStorageOverride = async (): Promise => {
try {
// Get all keys from both storages
const [localKeys, sessionKeys] = await Promise.all([
chrome.storage.local.get(),
chrome.storage.session.get()
]);
// Filter keys with Para prefix
const paraLocalKeys = Object.keys(localKeys).filter(key => key.startsWith("@CAPSULE/"));
const paraSessionKeys = Object.keys(sessionKeys).filter(key => key.startsWith("@CAPSULE/"));
// Remove Para keys
await Promise.all([
paraLocalKeys.length > 0 ? chrome.storage.local.remove(paraLocalKeys) : Promise.resolve(),
paraSessionKeys.length > 0 ? chrome.storage.session.remove(paraSessionKeys) : Promise.resolve()
]);
} catch (error) {
console.error("Clear storage error:", error);
}
};
// Export all overrides as a single object
export const chromeStorageOverrides = {
localStorageGetItemOverride,
localStorageSetItemOverride,
localStorageRemoveItemOverride,
sessionStorageGetItemOverride,
sessionStorageSetItemOverride,
sessionStorageRemoveItemOverride,
clearStorageOverride
};
```
### Configure Para Client
Create a singleton Para client with Chrome storage overrides:
```typescript lib/para/client.ts
import { ParaWeb } from "@getpara/react-sdk";
import { chromeStorageOverrides } from "../chrome-storage";
const PARA_API_KEY = process.env.NEXT_PUBLIC_PARA_API_KEY || "your-api-key";
// Create Para instance with Chrome storage overrides
export const para = new ParaWeb(PARA_API_KEY, {
...chromeStorageOverrides,
useStorageOverrides: true,
});
// Export shared promise for initialization
export const paraReady = para.init();
```
### Background Worker Implementation
Create a background worker that manages authentication flows:
```typescript background.ts
import { para, paraReady } from "@/lib/para/client";
// Handle extension icon clicks
chrome.action.onClicked.addListener(async () => {
try {
// Wait for Para to be ready
await paraReady;
// Check authentication status
const isLoggedIn = await para.isFullyLoggedIn();
console.log("User authentication status:", isLoggedIn);
if (!isLoggedIn) {
// User not authenticated - open full tab for login
chrome.tabs.create({
url: chrome.runtime.getURL("index.html")
});
} else {
// User authenticated - open popup for quick actions
await chrome.action.setPopup({ popup: "index.html" });
await chrome.action.openPopup();
// Reset popup after opening (allows clicking icon again)
await chrome.action.setPopup({ popup: "" });
}
} catch (error) {
console.error("Authentication check failed:", error);
// Fallback to tab on error
chrome.tabs.create({
url: chrome.runtime.getURL("index.html")
});
}
});
// Optional: Handle installation
chrome.runtime.onInstalled.addListener(async () => {
console.log("Extension installed");
// Initialize Para on installation
try {
await paraReady;
console.log("Para initialized successfully");
} catch (error) {
console.error("Para initialization failed:", error);
}
});
```
## Manifest Configuration
Create a `manifest.json` file for your Chrome extension:
```json manifest.json
{
"manifest_version": 3,
"name": "Para Chrome Extension",
"version": "1.0",
"description": "Chrome extension with Para authentication",
"permissions": [
"storage",
"activeTab"
],
"background": {
"service_worker": "background.js",
"type": "module"
},
"action": {
"default_title": "Para Extension"
},
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
},
"web_accessible_resources": [
{
"resources": ["index.html"],
"matches": [""]
}
]
}
```
## Application Setup
### Main Application Component
```typescript components/App.tsx
import { useEffect, useState } from "react";
import { para, paraReady } from "@/lib/para/client";
import { useAccount } from "@getpara/react-sdk";
export function App() {
const [isReady, setIsReady] = useState(false);
const { data: account } = useAccount();
useEffect(() => {
// Wait for Para initialization
paraReady.then(() => {
setIsReady(true);
}).catch((error) => {
console.error("Para initialization failed:", error);
setIsReady(true); // Show UI even on error
});
}, []);
if (!isReady) {
return (
);
}
return (
{account?.isConnected ? (
) : (
)}
);
}
function AuthenticatedView() {
const { data: account } = useAccount();
return (
Welcome Back!
Address: {account?.address}
para.logout()}
className="mt-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
Logout
);
}
function LoginView() {
const handleLogin = async () => {
try {
await para.signUpOrLogin();
} catch (error) {
console.error("Login failed:", error);
}
};
return (
Para Chrome Extension
Sign in to get started
Sign In with Para
);
}
```
### Para Provider Setup
```typescript components/ParaProvider.tsx
import { ParaProvider as BaseParaProvider } from "@getpara/react-sdk";
import { para } from "@/lib/para/client";
interface ParaProviderProps {
children: React.ReactNode;
}
export function ParaProvider({ children }: ParaProviderProps) {
return (
{children}
);
}
```
## User Experience Patterns
### Smart Authentication Flow
The background worker implements smart routing based on authentication state:
```typescript
// Pseudocode for authentication flow decision
if (userNotAuthenticated) {
// Open full tab - more space for authentication
openTab("index.html");
} else {
// Open popup - quick access for authenticated users
openPopup("index.html");
}
```
### State Persistence
Para state persists across popup sessions:
```typescript
// User clicks extension icon
// -> Popup opens with preserved authentication state
// -> User interacts with popup
// -> User clicks outside, popup closes
// -> User clicks extension icon again
// -> Popup reopens with same state (no re-authentication needed)
```
### Error Handling
Implement robust error handling for extension-specific scenarios:
```typescript
const handleExtensionError = (error: Error) => {
console.error("Extension error:", error);
// Always fallback to tab on critical errors
chrome.tabs.create({
url: chrome.runtime.getURL("index.html")
});
};
```
## Building and Deployment
### Build Configuration
Configure your build tool (Webpack, Vite, etc.) for Chrome extension:
```javascript webpack.config.js
module.exports = {
entry: {
background: './src/background.ts',
content: './src/index.tsx'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
// ... other webpack configuration
};
```
### Development Testing
1. **Build your extension**:
```bash
npm run build
```
2. **Load in Chrome**:
- Open `chrome://extensions/`
- Enable "Developer mode"
- Click "Load unpacked"
- Select your `dist` folder
3. **Test authentication flows**:
- Click extension icon when logged out (should open tab)
- Complete authentication
- Click extension icon when logged in (should open popup)
### Production Considerations
- **Permissions**: Only request necessary permissions in manifest
- **CSP**: Configure Content Security Policy for WASM and external resources
- **Error reporting**: Implement error tracking for production
- **Performance**: Optimize background worker to minimize resource usage
## Advanced Features
### Tab Communication
Communicate between popup and tabs:
```typescript
// In popup
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id!, {
type: "PARA_AUTH_STATUS",
isAuthenticated: true
});
});
// In content script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === "PARA_AUTH_STATUS") {
// Handle authentication status update
updateUIBasedOnAuth(message.isAuthenticated);
}
});
```
### Context Menus
Add context menu integration:
```typescript
// In background.ts
chrome.contextMenus.create({
id: "para-action",
title: "Sign with Para",
contexts: ["selection"]
});
chrome.contextMenus.onClicked.addListener(async (info, tab) => {
if (info.menuItemId === "para-action") {
// Handle context menu action
await paraReady;
const isLoggedIn = await para.isFullyLoggedIn();
if (isLoggedIn) {
// Perform action with selected text
console.log("Selected text:", info.selectionText);
}
}
});
```
## Troubleshooting
### Common Issues
**Storage not persisting**:
- Ensure `useStorageOverrides: true` is set
- Check Chrome storage permissions in manifest
- Verify storage override functions are async
**Background worker not receiving state**:
- Confirm `paraReady` promise is properly awaited
- Check console for initialization errors
- Verify manifest background configuration
**Popup closing unexpectedly**:
- This is expected Chrome behavior
- Use tabs for complex flows
- Implement state persistence with storage overrides
## Next Steps
Learn more about Para's React integration
Understand Para's authentication system
## Related Walkthroughs
Intermediate · 25 min · Large-scale wallet generation
Intermediate · 20 min · Send ETH with Ethers and Viem
Intermediate · 20 min · Structured data signing
# Aggregated Yield via Compass
Source: https://docs.getpara.com/v3/walkthroughs/compass
For AI-friendly documentation, see [Para llms.txt](https://docs.getpara.com/llms.txt) and [Compass llms.txt](https://docs.compasslabs.ai/llms-full.txt).
If you're using Para for embedded wallets, you can now add yield, lending, borrowing, and portfolio management to your app using the Compass API. Your users get a full DeFi experience without ever managing approvals or interacting with protocols directly.
## How It Works
Compass provides a non-custodial DeFi API — you call an endpoint, get back an unsigned payload, and the user signs it via their Para wallet. Users earn yield, borrow against crypto, or rebalance portfolios without touching a DeFi protocol.
This walkthrough covers three products: **Earn** (deposit into Aave, Morpho, Pendle vaults), **Credit** (borrow stablecoins against collateral), and **Portfolio Manager** (atomic rebalancing across venues).
## Why Combine Para and Compass
| Para Wallets | Compass API |
|---|---|
| Email/social login — no seed phrase or browser extension | Non-custodial DeFi across Aave, Morpho, Pendle |
| EIP-712 signing — users see structured data, not hex | Gas sponsorship — users never hold or pay ETH |
| Embedded + external wallet support | Atomic transaction bundling — 50-70% gas savings |
| Multi-chain (Ethereum, Base, Arbitrum) | Embedded fees — monetize from day one |
## Example Use Cases
1. **Yield Savings Account** — Users deposit USDC and earn 4-8% APY across vaults. You embed a 10% performance fee on yield. Users see one balance, one APY.
2. **Crypto-Backed Credit Line** — Users borrow USDC against ETH collateral. Display health factor and liquidation risk. Users never interact with Aave directly.
3. **Auto-Rebalancing Portfolio** — Monitor rates and rebalance atomically when better opportunities emerge. One signature moves \$50K from Aave (4.8%) to Morpho (6.2%).
**Want to see what you'd be building?** Try [Compass Studio](https://studio.compasslabs.ai) — a visual interface for Earn, Credit, and Portfolio products. Explore vaults, simulate transactions, and copy working configurations into your app.
## What You Need
- A [Para SDK setup](https://docs.getpara.com/v3/react/setup/nextjs) with embedded wallets configured
- A **Compass API key** — get one free at [compasslabs.ai](https://compasslabs.ai)
- **Compass SDKs**: [TypeScript](https://www.npmjs.com/package/@compass-labs/api-sdk) · [Python](https://pypi.org/project/compass_api_sdk/) — both fully typed with all endpoints
### Gas Sponsorship (Recommended)
With gas sponsorship, your users never need ETH. You provide a **gas sponsor wallet** — a server-side private key funded with ETH on your target chain (Base, Ethereum, or Arbitrum). Your backend uses this wallet to pay gas and relay transactions on behalf of users. Compass returns EIP-712 typed data, the user signs it off-chain, and your sponsor wallet submits the transaction.
Without gas sponsorship, Compass returns a standard unsigned transaction that the user signs and broadcasts directly from their Para wallet. The user pays their own gas, which requires them to hold ETH.
This walkthrough uses gas sponsorship for all examples. See [Compass Gas Sponsorship docs](https://docs.compasslabs.ai/v2/Products/gas-sponsorship) for more details.
You can also sponsor gas through [Account Abstraction integrations](/v3/react/guides/web3-operations/evm/account-abstraction) like Alchemy, Pimlico, or ZeroDev instead of running your own sponsor wallet.
## Step-by-Step Integration
### Step 1: Install the Compass SDK
Add the Compass TypeScript SDK alongside your existing Para setup. Para's `ParaProvider` already includes wagmi — the `useSignTypedData` hook used for EIP-712 signing is available out of the box.
```bash
npm install @compass-labs/api-sdk
```
Add these server-side environment variables:
```bash
# Server-side only — never expose to the client
COMPASS_API_KEY=your_compass_api_key
GAS_SPONSOR_PK=0x_your_sponsor_wallet_private_key
```
### Step 2: Create a Product Account
Compass uses isolated on-chain accounts ([Product Accounts](https://docs.compasslabs.ai/v2/Products/Accounts)) per product. Create a separate account for each product you plan to use — Earn, Credit, or both. Each account is a smart account controlled by the user's wallet. Compass never holds custody — it orchestrates creation and transaction routing.
Each account is deployed once per user. Calling the create endpoint again safely returns the existing address without a new transaction.
Your gas sponsor deploys these accounts so the user never needs ETH.
```typescript
// app/api/earn-account/create/route.ts
import { CompassApiSDK } from "@compass-labs/api-sdk";
import { createWalletClient, createPublicClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base, mainnet, arbitrum, type Chain } from "viem/chains";
const viemChains: Record = { base, ethereum: mainnet, arbitrum };
export async function POST(request: Request) {
const { owner, chain } = await request.json();
const sdk = new CompassApiSDK({
apiKeyAuth: process.env.COMPASS_API_KEY,
});
const sponsorAccount = privateKeyToAccount(
process.env.GAS_SPONSOR_PK as `0x${string}`
);
const response = await sdk.earn.earnCreateAccount({
chain,
owner, // User's Para wallet address
sender: sponsorAccount.address, // Your sponsor pays for deployment
estimateGas: true,
});
// If account already exists, no transaction is returned
if (!response.transaction) {
return Response.json({ earnAccountAddress: response.earnAccountAddress });
}
const sponsorWallet = createWalletClient({
account: sponsorAccount, chain: viemChains[chain], transport: http(),
});
const tx = response.transaction as any;
const txHash = await sponsorWallet.sendTransaction({
to: tx.to, data: tx.data,
value: BigInt(tx.value || "0"),
gas: tx.gas ? BigInt(tx.gas) : undefined,
});
await createPublicClient({ chain: viemChains[chain], transport: http() })
.waitForTransactionReceipt({ hash: txHash });
return Response.json({
earnAccountAddress: response.earnAccountAddress,
txHash,
});
}
```
```typescript
// app/api/credit-account/create/route.ts
import { CompassApiSDK } from "@compass-labs/api-sdk";
import { createWalletClient, createPublicClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base, mainnet, arbitrum, type Chain } from "viem/chains";
const viemChains: Record = { base, ethereum: mainnet, arbitrum };
export async function POST(request: Request) {
const { owner, chain } = await request.json();
const sdk = new CompassApiSDK({
apiKeyAuth: process.env.COMPASS_API_KEY,
});
const sponsorAccount = privateKeyToAccount(
process.env.GAS_SPONSOR_PK as `0x${string}`
);
const response = await sdk.credit.creditCreateAccount({
chain,
owner, // User's Para wallet address
sender: sponsorAccount.address, // Your sponsor pays for deployment
estimateGas: true,
});
if (!response.transaction) {
return Response.json({ creditAccountAddress: response.creditAccountAddress });
}
const sponsorWallet = createWalletClient({
account: sponsorAccount, chain: viemChains[chain], transport: http(),
});
const tx = response.transaction as any;
const txHash = await sponsorWallet.sendTransaction({
to: tx.to, data: tx.data,
value: BigInt(tx.value || "0"),
gas: tx.gas ? BigInt(tx.gas) : undefined,
});
await createPublicClient({ chain: viemChains[chain], transport: http() })
.waitForTransactionReceipt({ hash: txHash });
return Response.json({
creditAccountAddress: response.creditAccountAddress,
txHash,
});
}
```
### Step 3: Fund Your Account
Before depositing into a vault or borrowing, move tokens from the user's wallet into their Product Account. This step teaches the **three-step signing pattern** that every gas-sponsored Compass operation follows.
Your backend requests EIP-712 typed data from Compass (**prepare**), the Para wallet signs it via wagmi's `useSignTypedData` (**sign**), and your backend submits the signed payload through your gas sponsor (**execute**).
All amounts in the Compass API are in **human-readable units** (e.g., `"100"` means 100 USDC), not in base units or wei.
```typescript
// app/api/transfer/prepare/route.ts
import { CompassApiSDK } from "@compass-labs/api-sdk";
import { privateKeyToAccount } from "viem/accounts";
export async function POST(request: Request) {
const { owner, chain, token, amount } = await request.json();
const sdk = new CompassApiSDK({
apiKeyAuth: process.env.COMPASS_API_KEY,
});
const sponsorAccount = privateKeyToAccount(
process.env.GAS_SPONSOR_PK as `0x${string}`
);
// Request EIP-712 typed data with gas sponsorship
const transfer = await sdk.earn.earnTransfer({
owner, chain,
token,
amount,
action: "DEPOSIT",
spender: sponsorAccount.address,
gasSponsorship: true,
});
// For Credit accounts, use the same pattern:
// const transfer = await sdk.credit.creditTransfer({
// owner, chain, token, amount,
// action: "DEPOSIT", spender: sponsorAccount.address,
// gasSponsorship: true,
// });
const eip712 = transfer.eip712!;
// Gas-sponsored transfers use Permit2 — returns PermitTransferFrom types.
// Product operations (earnManage, creditBorrow) return SafeTx types instead.
// Normalize camelCase keys to PascalCase for wagmi.
const normalizedTypes = {
EIP712Domain: (eip712.types as any).eip712Domain,
PermitTransferFrom: (eip712.types as any).permitTransferFrom,
TokenPermissions: (eip712.types as any).tokenPermissions,
};
return Response.json({
eip712, normalizedTypes,
primaryType: eip712.primaryType,
domain: eip712.domain,
message: eip712.message,
});
}
```
```typescript
// app/api/execute/route.ts — reused by ALL gas-sponsored operations
import { CompassApiSDK } from "@compass-labs/api-sdk";
import { createWalletClient, createPublicClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base, mainnet, arbitrum, type Chain } from "viem/chains";
const viemChains: Record = { base, ethereum: mainnet, arbitrum };
export async function POST(request: Request) {
const { owner, eip712, signature, chain } = await request.json();
const sdk = new CompassApiSDK({
apiKeyAuth: process.env.COMPASS_API_KEY,
});
// Your gas sponsor wallet
const sponsorAccount = privateKeyToAccount(
process.env.GAS_SPONSOR_PK as `0x${string}`
);
// Compass wraps the user's EIP-712 signature into
// a transaction that your sponsor can submit
const sponsored = await sdk.gasSponsorship.gasSponsorshipPrepare({
owner, chain,
eip712: eip712 as any,
signature,
sender: sponsorAccount.address,
});
// Your sponsor signs and broadcasts — pays the gas
const tx = sponsored.transaction as any;
const sponsorWallet = createWalletClient({
account: sponsorAccount, chain: viemChains[chain], transport: http(),
});
const hash = await sponsorWallet.sendTransaction({
to: tx.to, data: tx.data,
value: BigInt(tx.value || "0"),
gas: tx.gas ? BigInt(tx.gas) : undefined,
maxFeePerGas: BigInt(tx.maxFeePerGas),
maxPriorityFeePerGas: BigInt(tx.maxPriorityFeePerGas),
});
await createPublicClient({ chain: viemChains[chain], transport: http() })
.waitForTransactionReceipt({ hash });
return Response.json({ success: true, txHash: hash });
}
```
```typescript
// components/FundAccount.tsx
import { useWallet } from "@getpara/react-sdk";
import { useSignTypedData, useSwitchChain } from "wagmi";
export function FundAccount() {
const { data: wallet } = useWallet();
const { signTypedDataAsync } = useSignTypedData();
const { switchChainAsync } = useSwitchChain();
async function fund(token: string, amount: string) {
await switchChainAsync({ chainId: 8453 }); // Base
// 1. Get EIP-712 typed data from your backend
const prepareRes = await fetch("/api/transfer/prepare", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
owner: wallet?.address, chain: "base",
token, amount,
}),
});
const { eip712, normalizedTypes, primaryType, domain, message } =
await prepareRes.json();
// 2. Para wallet signs (user sees structured data, not hex)
const signature = await signTypedDataAsync({
domain, types: normalizedTypes,
primaryType,
message,
});
// 3. Your backend submits via gas sponsor
await fetch("/api/execute", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
owner: wallet?.address,
eip712, signature, chain: "base",
}),
});
}
return (
fund("USDC", "100")}>
Fund Earn Account with 100 USDC
);
}
```
Every Compass operation — deposit, withdraw, swap, borrow, repay, bundle — follows this same three-step pattern. Only the SDK call in the **Prepare** step changes. The **Execute** route is reused by all operations.
**Before your first gas-sponsored transfer**, each token needs a one-time [Permit2 approval](https://docs.compasslabs.ai/v2/api-reference/gas-sponsorship/approve-token-transfer). Without it, `earnTransfer` and `creditTransfer` will return an "Insufficient allowance" error.
```typescript
// app/api/approve-transfer/route.ts
import { CompassApiSDK } from "@compass-labs/api-sdk";
export async function POST(request: Request) {
const { owner, chain, token } = await request.json();
const sdk = new CompassApiSDK({
apiKeyAuth: process.env.COMPASS_API_KEY,
});
const approval = await sdk.gasSponsorship.gasSponsorshipApproveTransfer({
owner, chain, token,
gasSponsorship: true,
});
const eip712 = approval.eip712;
// If no EIP-712 data, the token is already approved
if (!eip712) {
return Response.json({ approved: true });
}
const normalizedTypes = {
EIP712Domain: (eip712.types as any).eip712Domain,
Permit: (eip712.types as any).permit,
};
return Response.json({
eip712, normalizedTypes,
primaryType: eip712.primaryType,
domain: eip712.domain,
message: eip712.message,
});
}
```
The frontend signing and execution flow is identical — use the same `signTypedDataAsync` and `/api/execute` calls shown above.
Some tokens like USDT and WETH don't support EIP-2612 permits. For those, set `gasSponsorship: false` in the approve call — the user signs and pays gas for the one-time approval directly.
## Product Operations
The signing pattern is identical across all products. Only the SDK call in the **Prepare** step changes — swap it in and use the same **Execute** route and frontend signing flow from Step 3. Product operations return `SafeTx` types (normalize with `EIP712Domain` + `SafeTx`), unlike transfers which use `PermitTransferFrom`. The backend returns `primaryType` so the frontend handles both automatically.
### Deposit into a Vault
```typescript
const deposit = await sdk.earn.earnManage({
owner, chain,
venue: { type: "VAULT", vaultAddress },
action: "DEPOSIT",
amount: "100",
gasSponsorship: true,
});
// → returns eip712 → sign → execute
```
### Withdraw with Fee
```typescript
// You collect fees atomically — no separate payment flow
const withdraw = await sdk.earn.earnManage({
owner, chain,
venue: { type: "VAULT", vaultAddress },
action: "WITHDRAW",
amount: "ALL",
gasSponsorship: true,
fee: {
recipient: "0xYOUR_FEE_ADDRESS",
amount: "10",
denomination: "PERFORMANCE", // 10% of realized profit
},
});
// → returns eip712 → sign → execute
```
### Check Positions
```typescript
// Read positions — no signing required
const positions = await sdk.earn.earnPositions({
owner: walletAddress,
chain: "base",
});
// Returns: balance, PnL, yield earned, fee history
```
### Borrow
```typescript
// Borrow USDC against deposited collateral via Aave V3
const borrow = await sdk.credit.creditBorrow({
owner, chain,
borrowToken: "USDC",
borrowAmount: "1000",
gasSponsorship: true,
});
// → returns eip712 → sign → execute
```
### Repay Debt
```typescript
const repay = await sdk.credit.creditRepay({
owner, chain,
repayToken: "USDC",
repayAmount: "1000",
gasSponsorship: true,
});
// → returns eip712 → sign → execute
```
### Supply Collateral
```typescript
// Supply WETH as collateral on Aave via Credit Account
const supply = await sdk.credit.creditBundle({
owner, chain,
gasSponsorship: true,
actions: [
{
body: {
actionType: "CREDIT_SUPPLY",
token: "WETH",
amount: "1.0",
},
},
],
});
// → returns eip712 → sign → execute
```
### Withdraw Collateral
```typescript
// Withdraw WETH collateral from Aave
const withdraw = await sdk.credit.creditBundle({
owner, chain,
gasSponsorship: true,
actions: [
{
body: {
actionType: "CREDIT_WITHDRAW",
token: "WETH",
amount: "0.5",
},
},
],
});
// → returns eip712 → sign → execute
```
### Check Positions
```typescript
// Read positions — no signing required
const creditHealth = await sdk.credit.creditPositions({
owner: walletAddress,
chain: "base",
});
// Returns: healthFactor, collateral, debt, LTV
```
### Rebalance Across Vaults
```typescript
// Atomic: withdraw → swap → deposit in one signature
const bundle = await sdk.earn.earnBundle({
owner, chain,
gasSponsorship: true,
actions: [
{
body: {
actionType: "V2_MANAGE",
venue: { type: "VAULT", vaultAddress: "0xVaultA..." },
action: "WITHDRAW", amount: "ALL",
},
},
{
body: {
actionType: "V2_SWAP",
tokenIn: "USDC", tokenOut: "WETH",
amountIn: "5000", slippage: 0.5,
},
},
{
body: {
actionType: "V2_MANAGE",
venue: { type: "VAULT", vaultAddress: "0xVaultB..." },
action: "DEPOSIT", amount: "ALL",
},
},
],
});
// → returns eip712 → sign → execute
```
Bundle any combination of withdrawals, swaps, and deposits into a single atomic transaction. If any step fails, the entire bundle reverts — no partial state.
## Querying Data
All read endpoints are free, require no signing, and return instantly. Use these to populate your UI with available markets and rates.
```typescript
const sdk = new CompassApiSDK({ apiKeyAuth: process.env.COMPASS_API_KEY });
// List top vaults by TVL on Base
const vaults = await sdk.earn.earnVaults({
orderBy: "tvl_usd", direction: "desc",
chain: "base", assetSymbol: "USDC", limit: 10,
});
// Returns: vault address, APY, TVL, underlying asset, protocol
// List Aave lending markets with supply/borrow APYs
const aaveMarkets = await sdk.earn.earnAaveMarkets({ chain: "base" });
// List Pendle markets with implied APY and expiry
const pendleMarkets = await sdk.earn.earnPendleMarkets({
orderBy: "tvl_usd", direction: "desc",
chain: "base", limit: 10,
});
```
See the full [Compass API reference](https://docs.compasslabs.ai/v2/api-reference) for all available endpoints.
## Related Resources
Vaults, APYs, and yield strategies across Morpho, Aave, and Pendle
Gasless transactions with EIP-712 signing and sponsor wallets
Sign structured data using the EIP-712 standard with Para wallets
# EIP-712 Typed Data Signing with Para
Source: https://docs.getpara.com/v3/walkthroughs/eip712-typed-data-signing
EIP-712 enables signing complex, structured data in a standardized way, providing better security and user experience compared to simple message signing. This walkthrough shows how to implement EIP-712 typed data signing using Para's Ethers integration.
## What is EIP-712?
EIP-712 is a standard for signing typed structured data, offering several advantages:
- **Structured data signing** - Sign complex objects with multiple fields and nested structures
- **Domain separation** - Prevents signature replay attacks across different applications or chains
- **Human-readable format** - Users can see exactly what they're signing in wallet interfaces
- **Type safety** - Ensures data conforms to expected structure before signing
## Common Use Cases
EIP-712 is commonly used for:
- **Meta-transactions** - Gasless transactions with relayer support
- **Permit signatures** - Token approvals without on-chain transactions
- **Attestations** - Cryptographically signed claims or certificates
- **Voting systems** - Off-chain voting with on-chain verification
- **Order signing** - DEX orders and marketplace listings
## Setup Requirements
You need an authenticated Para client and Ethers provider to implement EIP-712 signing.
### Install Dependencies
```bash
npm install @getpara/react-sdk @getpara/ethers-v6-integration ethers
```
### Create Ethers Provider Hook
```typescript hooks/useEthersProvider.ts
import { useMemo } from "react";
import { ethers } from "ethers";
const RPC_URL = process.env.NEXT_PUBLIC_RPC_URL ||
"https://ethereum-holesky-rpc.publicnode.com";
export function useEthersProvider() {
const provider = useMemo(() => {
return new ethers.JsonRpcProvider(RPC_URL);
}, []);
return { provider };
}
```
### Create Para Signer Hook
```typescript hooks/useParaSigner.ts
import { useState, useEffect } from "react";
import { createParaEthersSigner } from "@getpara/ethers-v6-integration";
import { useAccount, useClient } from "@getpara/react-sdk";
import { useEthersProvider } from "./useEthersProvider";
export function useParaSigner() {
const { data: account } = useAccount();
const client = useClient();
const { provider } = useEthersProvider();
const [signer, setSigner] = useState(null);
useEffect(() => {
if (account?.isConnected && provider && client) {
try {
const newSigner = createParaEthersSigner({ para: client, provider: provider });
setSigner(newSigner);
} catch (error) {
console.error("Failed to initialize Para signer:", error);
setSigner(null);
}
} else {
setSigner(null);
}
}, [account?.isConnected, provider, client]);
return { signer, provider };
}
```
## EIP-712 Implementation
### Define Domain and Types
The domain separator provides context and prevents replay attacks:
```typescript
// Domain definition - provides context for the signature
const domain = {
name: "MyDApp", // Application name
version: "1", // Version of signing domain
chainId: 17000, // Network chain ID (Holesky testnet)
verifyingContract: "0x..." as Address // Contract that will verify signatures
};
// Types definition - structure of the data being signed
const types = {
TokenAttestation: [
{ name: "holder", type: "address" },
{ name: "balance", type: "string" },
{ name: "purpose", type: "string" },
{ name: "timestamp", type: "uint256" },
{ name: "nonce", type: "uint256" }
]
};
```
### Create Typed Data Structure
```typescript
interface TokenAttestation {
holder: string;
balance: string;
purpose: string;
timestamp: number;
nonce: number;
}
// Create the data object to sign
const attestation: TokenAttestation = {
holder: "0x742d35Cc6634C0532925a3b8D756e3C98d8a3a1B",
balance: "1000.5",
purpose: "Identity verification",
timestamp: Math.floor(Date.now() / 1000),
nonce: 1
};
```
### Sign Typed Data
```typescript
import { useParaSigner } from "./hooks/useParaSigner";
export function TypedDataSigning() {
const { signer } = useParaSigner();
const [signature, setSignature] = useState("");
const [isLoading, setIsLoading] = useState(false);
const signAttestation = async () => {
if (!signer) {
throw new Error("Signer not available");
}
setIsLoading(true);
try {
// Sign the typed data
const signature = await signer.signTypedData(domain, types, attestation);
setSignature(signature);
console.log("Signed attestation:", signature);
} catch (error) {
console.error("Signing failed:", error);
} finally {
setIsLoading(false);
}
};
return (
{isLoading ? "Signing..." : "Sign Attestation"}
{signature && (
Signature:
{signature}
)}
);
}
```
## Advanced Examples
### Permit Signature for Token Approvals
```typescript
// EIP-2612 Permit signature
const permitTypes = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
};
const permitData = {
owner: userAddress,
spender: spenderAddress,
value: ethers.parseUnits("100", 18),
nonce: await token.nonces(userAddress),
deadline: Math.floor(Date.now() / 1000) + 3600 // 1 hour
};
const permitSignature = await signer.signTypedData(
{
name: "MyToken",
version: "1",
chainId: 1,
verifyingContract: tokenAddress
},
permitTypes,
permitData
);
```
### Voting Signature
```typescript
const voteTypes = {
Vote: [
{ name: "proposalId", type: "uint256" },
{ name: "support", type: "bool" },
{ name: "voter", type: "address" },
{ name: "reason", type: "string" },
{ name: "timestamp", type: "uint256" }
]
};
const voteData = {
proposalId: 42,
support: true,
voter: userAddress,
reason: "I support this proposal",
timestamp: Math.floor(Date.now() / 1000)
};
const voteSignature = await signer.signTypedData(domain, voteTypes, voteData);
```
### Marketplace Order Signature
```typescript
const orderTypes = {
Order: [
{ name: "seller", type: "address" },
{ name: "buyer", type: "address" },
{ name: "tokenContract", type: "address" },
{ name: "tokenId", type: "uint256" },
{ name: "price", type: "uint256" },
{ name: "deadline", type: "uint256" },
{ name: "nonce", type: "uint256" }
]
};
const orderData = {
seller: userAddress,
buyer: "0x0000000000000000000000000000000000000000", // Any buyer
tokenContract: nftContractAddress,
tokenId: 123,
price: ethers.parseEther("1.5"),
deadline: Math.floor(Date.now() / 1000) + 86400, // 24 hours
nonce: 1
};
const orderSignature = await signer.signTypedData(domain, orderTypes, orderData);
```
## Signature Verification
### Client-Side Verification
```typescript
import { ethers } from "ethers";
async function verifySignature(
domain: any,
types: any,
data: any,
signature: string,
expectedSigner: string
) {
try {
const recoveredSigner = ethers.verifyTypedData(domain, types, data, signature);
return recoveredSigner.toLowerCase() === expectedSigner.toLowerCase();
} catch (error) {
console.error("Verification failed:", error);
return false;
}
}
// Usage
const isValid = await verifySignature(
domain,
types,
attestation,
signature,
userAddress
);
console.log("Signature valid:", isValid);
```
### Smart Contract Verification
```solidity
// Solidity contract for verifying EIP-712 signatures
contract AttestationVerifier {
bytes32 private constant ATTESTATION_TYPEHASH = keccak256(
"TokenAttestation(address holder,string balance,string purpose,uint256 timestamp,uint256 nonce)"
);
bytes32 private immutable DOMAIN_SEPARATOR;
constructor() {
DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes("MyDApp")),
keccak256(bytes("1")),
block.chainid,
address(this)
)
);
}
function verifyAttestation(
TokenAttestation memory attestation,
uint8 v,
bytes32 r,
bytes32 s
) public view returns (address) {
bytes32 structHash = keccak256(
abi.encode(
ATTESTATION_TYPEHASH,
attestation.holder,
keccak256(bytes(attestation.balance)),
keccak256(bytes(attestation.purpose)),
attestation.timestamp,
attestation.nonce
)
);
bytes32 digest = keccak256(
abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, structHash)
);
return ecrecover(digest, v, r, s);
}
}
```
## Error Handling
### Common Issues and Solutions
```typescript
const signWithErrorHandling = async () => {
try {
const signature = await signer.signTypedData(domain, types, data);
return signature;
} catch (error) {
if (error.code === 'ACTION_REJECTED') {
console.log("User rejected the signing request");
} else if (error.message.includes('invalid domain')) {
console.error("Domain configuration error:", error);
} else if (error.message.includes('invalid types')) {
console.error("Types definition error:", error);
} else {
console.error("Unexpected signing error:", error);
}
throw error;
}
};
```
### Validation Before Signing
```typescript
function validateTypedData(domain: any, types: any, data: any) {
// Validate domain
if (!domain.name || !domain.version || !domain.chainId) {
throw new Error("Invalid domain: missing required fields");
}
// Validate types
if (!types || Object.keys(types).length === 0) {
throw new Error("Invalid types: empty or undefined");
}
// Validate data matches types
const primaryType = Object.keys(types)[0];
const typeFields = types[primaryType];
for (const field of typeFields) {
if (!(field.name in data)) {
throw new Error(`Missing required field: ${field.name}`);
}
}
return true;
}
```
## Best Practices
### Security Considerations
- **Validate all inputs** before signing
- **Use proper domain separation** to prevent replay attacks
- **Include nonces** to prevent signature reuse
- **Set reasonable deadlines** for time-sensitive signatures
- **Verify contract addresses** in domain separator
### Type Definition Guidelines
- **Use specific types** (uint256 instead of uint)
- **Order fields consistently** across your application
- **Document field purposes** for maintainability
- **Version your types** when making changes
### User Experience
- **Provide clear descriptions** of what users are signing
- **Show human-readable summaries** before signing
- **Handle rejection gracefully** with meaningful error messages
- **Cache signatures** when appropriate to avoid re-signing
## Next Steps
Learn more about Para's Ethers integration
Understand Para's authentication system
## Related Walkthroughs
Intermediate · 20 min · Send ETH with Ethers and Viem
Intermediate · 30 min · EIP-7702 smart accounts with session keys
Advanced · 45 min · Lending, borrowing, and yield strategies
# Ethereum Transfers with Para
Source: https://docs.getpara.com/v3/walkthroughs/ethereum-transfers
This walkthrough covers sending basic Ethereum transfers using Para with both Ethers v6 and Viem v2. You'll learn transaction construction, gas estimation, and signing patterns for each library.
## Prerequisites
You need an authenticated Para client and basic knowledge of Ethereum transactions.
## Core Dependencies
```bash
npm install @getpara/react-sdk @getpara/ethers-v6-integration ethers
```
```bash
npm install @getpara/react-sdk @getpara/viem viem
```
## Provider Setup
### RPC Configuration
```typescript
import { ethers } from "ethers";
const RPC_URL = process.env.NEXT_PUBLIC_HOLESKY_RPC_URL ||
"https://ethereum-holesky-rpc.publicnode.com";
// Create JSON RPC provider
const provider = new ethers.JsonRpcProvider(RPC_URL);
```
```typescript
import { createPublicClient, http } from "viem";
import { holesky } from "viem/chains";
const RPC_URL = process.env.NEXT_PUBLIC_HOLESKY_RPC_URL ||
"https://ethereum-holesky-rpc.publicnode.com";
// Create public client for read operations
const publicClient = createPublicClient({
chain: holesky,
transport: http(RPC_URL)
});
```
### Para Signer Setup
```typescript
import { createParaEthersSigner } from "@getpara/ethers-v6-integration";
import { useAccount, useClient } from "@getpara/react-sdk";
// Get Para client and account
const client = useClient();
const { data: account } = useAccount();
// Create Para Ethers signer
let signer: ParaEthersSigner | null = null;
if (account?.isConnected && provider && client) {
signer = createParaEthersSigner({ para: client, provider: provider });
}
```
```typescript
import { createParaViemAccount, createParaViemClient } from "@getpara/viem";
import { useAccount, useClient } from "@getpara/react-sdk";
// Get Para client and account
const client = useClient();
const { data: account } = useAccount();
// Create Para Viem account and wallet client
let walletClient: any = null;
if (account?.isConnected && client) {
const viemAccount = createParaViemAccount(client);
walletClient = createParaViemClient(client, {
account: viemAccount,
chain: holesky,
transport: http(RPC_URL)
});
}
```
## Transaction Construction
### Basic Transaction Parameters
```typescript
import { parseEther, toBigInt } from "ethers";
const constructTransaction = async (
toAddress: string,
ethAmount: string,
userAddress: string
) => {
// Get transaction parameters
const nonce = await provider.getTransactionCount(userAddress);
const feeData = await provider.getFeeData();
const gasLimit = toBigInt(21000); // Standard ETH transfer gas
const value = parseEther(ethAmount);
// Construct transaction object
const transaction = {
to: toAddress,
value: value,
nonce: nonce,
gasLimit: gasLimit,
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
chainId: 17000, // Holesky chain ID
};
return transaction;
};
```
```typescript
import { parseEther, parseGwei } from "viem";
const constructTransaction = (
toAddress: `0x${string}`,
ethAmount: string,
userAddress: `0x${string}`
) => {
// Construct transaction object
const transaction = {
account: userAddress,
to: toAddress,
value: parseEther(ethAmount),
chain: holesky,
maxFeePerGas: parseGwei("100"), // Custom max fee
maxPriorityFeePerGas: parseGwei("3") // Custom priority fee
};
return transaction;
};
```
## Gas Estimation
### Estimate Transaction Cost
```typescript
import { formatEther } from "ethers";
const estimateTransactionCost = async (
userAddress: string,
ethAmount: string
) => {
// Get current balance
const balanceWei = await provider.getBalance(userAddress);
// Get fee data
const feeData = await provider.getFeeData();
const gasLimit = toBigInt(21000);
// Calculate max gas cost
const maxGasFee = gasLimit * (feeData.maxFeePerGas ?? toBigInt(0));
const amountWei = parseEther(ethAmount);
const totalCost = amountWei + maxGasFee;
return {
balance: formatEther(balanceWei),
amount: formatEther(amountWei),
estimatedGas: formatEther(maxGasFee),
totalCost: formatEther(totalCost),
hasSufficientBalance: totalCost <= balanceWei
};
};
```
```typescript
import { formatEther, parseEther } from "viem";
const estimateTransactionCost = async (
userAddress: `0x${string}`,
toAddress: `0x${string}`,
ethAmount: string
) => {
// Get current balance
const balance = await publicClient.getBalance({ address: userAddress });
const amountWei = parseEther(ethAmount);
// Estimate gas for the specific transaction
const estimatedGas = await publicClient.estimateGas({
account: userAddress,
to: toAddress,
value: amountWei
});
// Get current gas price
const gasPrice = await publicClient.getGasPrice();
const estimatedGasCost = estimatedGas * gasPrice;
const totalCost = amountWei + estimatedGasCost;
return {
balance: formatEther(balance),
amount: formatEther(amountWei),
estimatedGas: formatEther(estimatedGasCost),
totalCost: formatEther(totalCost),
hasSufficientBalance: totalCost <= balance
};
};
```
## Transaction Validation
### Balance and Parameter Checks
```typescript
const validateTransaction = async (
userAddress: string,
toAddress: string,
ethAmount: string
) => {
// Basic parameter validation
if (!ethers.isAddress(toAddress)) {
throw new Error("Invalid recipient address");
}
if (parseFloat(ethAmount) <= 0) {
throw new Error("Amount must be greater than 0");
}
// Check balance and gas
const estimation = await estimateTransactionCost(userAddress, ethAmount);
if (!estimation.hasSufficientBalance) {
throw new Error(
`Insufficient balance. Need ${estimation.totalCost} ETH, have ${estimation.balance} ETH`
);
}
return true;
};
```
```typescript
import { isAddress } from "viem";
const validateTransaction = async (
userAddress: `0x${string}`,
toAddress: string,
ethAmount: string
) => {
// Basic parameter validation
if (!isAddress(toAddress)) {
throw new Error("Invalid recipient address");
}
if (parseFloat(ethAmount) <= 0) {
throw new Error("Amount must be greater than 0");
}
// Check balance and gas
const estimation = await estimateTransactionCost(
userAddress,
toAddress as `0x${string}`,
ethAmount
);
if (!estimation.hasSufficientBalance) {
throw new Error(
`Insufficient balance. Need ${estimation.totalCost} ETH, have ${estimation.balance} ETH`
);
}
return true;
};
```
## Sending Transactions
### Execute Transfer
```typescript
const sendEthTransfer = async (
toAddress: string,
ethAmount: string,
userAddress: string
) => {
// Validate transaction
await validateTransaction(userAddress, toAddress, ethAmount);
// Construct transaction
const transaction = await constructTransaction(toAddress, ethAmount, userAddress);
// Send transaction using Para signer
const txResponse = await signer.sendTransaction(transaction);
console.log("Transaction sent:", txResponse.hash);
// Wait for confirmation
const receipt = await txResponse.wait();
console.log("Transaction confirmed:", receipt.hash);
console.log("Block number:", receipt.blockNumber);
console.log("Gas used:", receipt.gasUsed.toString());
return {
hash: receipt.hash,
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed.toString(),
status: receipt.status === 1 ? "success" : "failed"
};
};
```
```typescript
const sendEthTransfer = async (
toAddress: `0x${string}`,
ethAmount: string,
userAddress: `0x${string}`
) => {
// Validate transaction
await validateTransaction(userAddress, toAddress, ethAmount);
// Send transaction using Para wallet client
const hash = await walletClient.sendTransaction({
account: userAddress,
to: toAddress,
value: parseEther(ethAmount),
chain: holesky,
maxFeePerGas: parseGwei("100"),
maxPriorityFeePerGas: parseGwei("3")
});
console.log("Transaction sent:", hash);
// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log("Transaction confirmed:", receipt.transactionHash);
console.log("Block number:", receipt.blockNumber);
console.log("Gas used:", receipt.gasUsed.toString());
return {
hash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed.toString(),
status: receipt.status === "success" ? "success" : "failed"
};
};
```
## Complete Implementation Example
### Full Transfer Function
```typescript
import { ethers, parseEther, formatEther, toBigInt } from "ethers";
import { createParaEthersSigner } from "@getpara/ethers-v6-integration";
class EthersTransferService {
private provider: ethers.JsonRpcProvider;
private signer: ParaEthersSigner;
constructor(rpcUrl: string, paraClient: any) {
this.provider = new ethers.JsonRpcProvider(rpcUrl);
this.signer = createParaEthersSigner({ para: paraClient, provider: this.provider });
}
async transfer(toAddress: string, ethAmount: string, fromAddress: string) {
try {
// Validate inputs
if (!ethers.isAddress(toAddress)) {
throw new Error("Invalid recipient address");
}
// Check balance
const balance = await this.provider.getBalance(fromAddress);
const amount = parseEther(ethAmount);
const feeData = await this.provider.getFeeData();
const gasLimit = toBigInt(21000);
const maxGasFee = gasLimit * (feeData.maxFeePerGas ?? toBigInt(0));
if (balance < amount + maxGasFee) {
throw new Error("Insufficient balance for transfer and gas");
}
// Construct and send transaction
const nonce = await this.provider.getTransactionCount(fromAddress);
const transaction = {
to: toAddress,
value: amount,
nonce: nonce,
gasLimit: gasLimit,
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
chainId: 17000
};
const txResponse = await this.signer.sendTransaction(transaction);
const receipt = await txResponse.wait();
return {
success: true,
hash: receipt.hash,
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed.toString()
};
} catch (error) {
console.error("Transfer failed:", error);
throw error;
}
}
}
```
```typescript
import {
createPublicClient,
createParaViemClient,
createParaViemAccount,
http,
parseEther,
formatEther,
parseGwei,
isAddress
} from "viem";
import { holesky } from "viem/chains";
class ViemTransferService {
private publicClient: any;
private walletClient: any;
constructor(rpcUrl: string, paraClient: any) {
this.publicClient = createPublicClient({
chain: holesky,
transport: http(rpcUrl)
});
const account = createParaViemAccount(paraClient);
this.walletClient = createParaViemClient(paraClient, {
account,
chain: holesky,
transport: http(rpcUrl)
});
}
async transfer(
toAddress: `0x${string}`,
ethAmount: string,
fromAddress: `0x${string}`
) {
try {
// Validate inputs
if (!isAddress(toAddress)) {
throw new Error("Invalid recipient address");
}
// Check balance and estimate gas
const balance = await this.publicClient.getBalance({ address: fromAddress });
const amount = parseEther(ethAmount);
const estimatedGas = await this.publicClient.estimateGas({
account: fromAddress,
to: toAddress,
value: amount
});
const gasPrice = await this.publicClient.getGasPrice();
const estimatedGasCost = estimatedGas * gasPrice;
if (balance < amount + estimatedGasCost) {
throw new Error("Insufficient balance for transfer and gas");
}
// Send transaction
const hash = await this.walletClient.sendTransaction({
account: fromAddress,
to: toAddress,
value: amount,
chain: holesky,
maxFeePerGas: parseGwei("100"),
maxPriorityFeePerGas: parseGwei("3")
});
const receipt = await this.publicClient.waitForTransactionReceipt({ hash });
return {
success: true,
hash: receipt.transactionHash,
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed.toString()
};
} catch (error) {
console.error("Transfer failed:", error);
throw error;
}
}
}
```
## Key Differences
### Library Comparison
| Feature | Ethers v6 | Viem v2 |
|---------|-----------|---------|
| **Provider Setup** | `JsonRpcProvider` | `createPublicClient` |
| **Wallet Client** | `ParaEthersSigner` | `createParaViemClient` |
| **Unit Parsing** | `parseEther()` | `parseEther()` |
| **Gas Estimation** | `getFeeData()` | `estimateGas()` + `getGasPrice()` |
| **Transaction Send** | `signer.sendTransaction()` | `walletClient.sendTransaction()` |
| **Receipt Waiting** | `txResponse.wait()` | `publicClient.waitForTransactionReceipt()` |
| **Address Validation** | `ethers.isAddress()` | `isAddress()` |
### Gas Strategy Differences
```typescript
// Ethers uses provider fee data
const feeData = await provider.getFeeData();
const transaction = {
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas
};
```
```typescript
// Viem allows manual gas price setting
const transaction = {
maxFeePerGas: parseGwei("100"),
maxPriorityFeePerGas: parseGwei("3")
};
```
## Best Practices
### Transaction Safety
- **Always validate recipient addresses** before sending
- **Check balance including gas costs** before transaction construction
- **Use appropriate gas limits** (21000 for basic ETH transfers)
- **Handle network errors gracefully** with proper try/catch blocks
- **Wait for receipt confirmation** before considering transaction complete
### Gas Optimization
- **Monitor network conditions** for optimal gas pricing
- **Use EIP-1559 gas parameters** for better fee prediction
- **Estimate gas dynamically** rather than using static values
- **Consider gas limit buffers** for complex operations
### Error Handling
Common errors to handle:
- Invalid recipient addresses
- Insufficient balance
- Network connectivity issues
- Transaction reversion
- Nonce management conflicts
## Next Steps
Learn to transfer ERC-20 tokens
Track transaction status and confirmations
## Related Walkthroughs
Intermediate · 20 min · SOL transfers and Solana programs
Intermediate · 20 min · Structured data signing
Advanced · 45 min · Lending, borrowing, and yield strategies
# Inco Integration
Source: https://docs.getpara.com/v3/walkthroughs/inco
This guide demonstrates how to create a seamless private payment flow using Para SDK with Inco's confidential wrapper. The app allows users to deposit, check balances, send confidential transfers, and withdraw with encrypted amounts.
Only transaction amounts are encrypted. Sender and receiver addresses are not.
## Overview
Inco offers confidential transaction capabilities where token amounts are encrypted, providing privacy for:
- **Confidential Transfers:** Token amounts are encrypted. The sender, recipient addresses, and asset are visible, but the transfer amount remains private.
- **Private Balance:** User balances are stored encrypted and can only be decrypted by the account owner through cryptographic attestation.
## Prerequisites
- Node.js 20+
- Next.js project
- pnpm package manager
- A Para API key from the [Para Developer Portal](https://developer.getpara.com)
- WalletConnect project ID
## Environment Setup
Create a `.env` file in the project root:
```bash
# Contract Addresses (Base Sepolia)
NEXT_PUBLIC_ERC20_CONTRACT_ADDRESS=0x2a7f20a455b35ea3cff416f71ddb30e0edf5c9fe
NEXT_PUBLIC_ENCRYPTED_ERC20_CONTRACT_ADDRESS=0x3b9cabcf20eda599c5df823f46eb2eabe99bda40
# Inco Configuration
NEXT_PUBLIC_INCO_ENV=demonet
# Para configuration
NEXT_PUBLIC_PARA_API_KEY=your-para-api-key
NEXT_PUBLIC_PARA_ENVIRONMENT=BETA
# WalletConnect (used by Para for external wallets)
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=your-walletconnect-project-id
```
To obtain values of `NEXT_PUBLIC_PARA_API_KEY` and `NEXT_PUBLIC_PARA_ENVIRONMENT` visit [Para](https://www.getpara.com/).
## Installation
```bash
pnpm install @getpara/react-sdk@^2.6.0 @inco/js@^0.7.7 wagmi@^2.19.5 viem@^2.44.4
```
## Para Wallet Integration
### 1. Configuration Setup
```tsx
// src/config/constants.ts
import { Environment } from "@getpara/react-sdk";
export const API_KEY = process.env.NEXT_PUBLIC_PARA_API_KEY ?? "";
export const ENVIRONMENT = (process.env.NEXT_PUBLIC_PARA_ENVIRONMENT as Environment) || Environment.BETA;
if (!API_KEY) {
throw new Error("Missing NEXT_PUBLIC_PARA_API_KEY environment variable");
}
```
### 2. Para Provider Setup
```tsx
// src/context/para-provider.tsx
"use client";
import { useState } from "react";
import { ParaProvider as Provider } from "@getpara/react-sdk";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { cookieStorage, createStorage, http } from "wagmi";
import { baseSepolia as chain } from "wagmi/chains";
import { createPublicClient } from "viem";
import { API_KEY, ENVIRONMENT } from "@/config/constants";
const BASE_SEPOLIA_RPC_URL = "your-base-sepolia-rpc-url";
export const baseSepolia = {
...chain,
rpcUrls: {
default: {
http: [BASE_SEPOLIA_RPC_URL],
},
},
};
export const publicClient = createPublicClient({
chain: baseSepolia,
transport: http(BASE_SEPOLIA_RPC_URL),
});
export function ParaProvider({
children,
}: {
children: React.ReactNode;
}) {
const [queryClient] = useState(() => new QueryClient());
return (
{children}
);
}
```
### 3. Root Layout Integration
```tsx
// src/app/layout.tsx
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ParaProvider } from "@/context/para-provider";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
{children}
);
}
```
### 4. Connect Wallet Component
```tsx
// src/components/connect-wallet.tsx
"use client";
import { useModal } from "@getpara/react-sdk";
import { useAccount } from "wagmi";
export default function ConnectWallet() {
const { openModal } = useModal();
const { address, isConnected } = useAccount();
return (
{isConnected ? (
Connected to {address}
) : (
openModal()}>Connect Wallet
)}
);
}
```
## Core Features
### 1. Minting ERC20 Tokens
```tsx
// src/components/mint-erc20.tsx
import React from "react";
import { parseUnits } from "viem";
import { useAccount, useWriteContract } from "wagmi";
import { publicClient } from "@/context/para-provider";
const MintERC20 = () => {
const { writeContractAsync } = useWriteContract();
const { address } = useAccount();
const amountWithDecimals = parseUnits("1000", 18);
const mintERC20 = async () => {
const txHash = await writeContractAsync({
address: process.env
.NEXT_PUBLIC_ERC20_CONTRACT_ADDRESS as `0x${string}`,
abi: [
{
inputs: [
{
internalType: "address",
name: "userAddress",
type: "address",
},
{
internalType: "uint256",
name: "amount",
type: "uint256",
},
],
name: "mint",
outputs: [],
stateMutability: "nonpayable",
type: "function",
},
],
functionName: "mint",
args: [address, amountWithDecimals],
});
await publicClient?.waitForTransactionReceipt({ hash: txHash });
};
return (
Mint ERC20
Mint 1000 ERC20
);
};
export default MintERC20;
```
### 2. Balance Checking
```tsx
// src/components/encrypted-balance.tsx
import React, { useState } from 'react';
import { type Address } from 'viem';
import { useAccount, useBalance, useWalletClient } from 'wagmi';
import { getEncryptedBalance } from '@/lib/balance-utils';
import { publicClient } from '@/context/para-provider';
const BalanceDisplay = () => {
const { data: walletClient } = useWalletClient();
const { address } = useAccount();
const [encryptedBalance, setEncryptedBalance] = useState('0');
const ERC20Balance = useBalance({
address: address as Address,
token: process.env.NEXT_PUBLIC_ERC20_CONTRACT_ADDRESS as Address,
});
const handleFetchBalance = async () => {
if (address && publicClient && walletClient) {
const result = await getEncryptedBalance({
address,
publicClient,
walletClient,
encryptedERC20Address: process.env.NEXT_PUBLIC_ENCRYPTED_ERC20_CONTRACT_ADDRESS as Address,
incoEnv: process.env.NEXT_PUBLIC_INCO_ENV as 'testnet' | 'devnet',
});
if (result) setEncryptedBalance(result);
}
};
return (
ERC20 Balance
{ERC20Balance?.data?.formatted} {ERC20Balance?.data?.symbol}
Encrypted Balance
{encryptedBalance} Tokens
Fetch Encrypted Balance
);
};
export default BalanceDisplay;
```
### 3. Wrapping Tokens (Deposit)
```tsx
// src/components/wrap.tsx
'use client';
import { useAccount, useWalletClient } from 'wagmi';
import { type Address } from 'viem';
import { approveTokens, wrapTokens } from '@/lib/wrap-utils';
import { publicClient } from '@/context/para-provider';
export default function WrapTokens() {
const { data: walletClient } = useWalletClient();
const { address } = useAccount();
const handleWrap = async () => {
if (!walletClient || !address) return;
const amount = '0.01';
// Step 1: Approve tokens
const approveTx = await approveTokens({
walletClient,
address,
amount,
erc20Address: process.env.NEXT_PUBLIC_ERC20_CONTRACT_ADDRESS as Address,
encryptedERC20Address: process.env.NEXT_PUBLIC_ENCRYPTED_ERC20_CONTRACT_ADDRESS as Address,
});
await publicClient?.waitForTransactionReceipt({ hash: approveTx });
// Step 2: Wrap tokens
const wrapTx = await wrapTokens({
walletClient,
address,
amount,
encryptedERC20Address: process.env.NEXT_PUBLIC_ENCRYPTED_ERC20_CONTRACT_ADDRESS as Address,
});
await publicClient?.waitForTransactionReceipt({ hash: wrapTx });
};
return (
Wrap Tokens
Wrap 0.01 Tokens
);
}
```
### 4. Confidential Transfer
```tsx
// src/components/confidential-transfer.tsx
import React, { useState } from "react";
import { Address } from "viem";
import { useAccount, useWalletClient } from "wagmi";
import { confidentialTransfer } from "@/lib/confidential-send";
import { publicClient } from "@/context/para-provider";
const ConfidentialTransfer = () => {
const [recipient, setRecipient] = useState("0x");
const { address } = useAccount();
const { data: walletClient } = useWalletClient();
const transferConfidentialERC20 = async () => {
const txHash = await confidentialTransfer({
amount: "0.01",
address: address,
recipient: recipient,
walletClient: walletClient,
publicClient: publicClient,
encryptedERC20Address:
process.env.NEXT_PUBLIC_ENCRYPTED_ERC20_CONTRACT_ADDRESS as Address,
incoEnv: "testnet",
});
await publicClient?.waitForTransactionReceipt({ hash: txHash });
};
return (
setRecipient(e.target.value as Address)}
placeholder="Recipient address"
/>
Transfer 0.01 confidential ERC20 to {recipient}
);
};
export default ConfidentialTransfer;
```
### 5. Unwrapping Tokens (Withdraw)
```tsx
// src/components/unwrap.tsx
import { unwrapTokens } from "@/lib/unwrap-utils";
import React from "react";
import { useAccount, useWalletClient } from "wagmi";
import { publicClient } from "@/context/para-provider";
const Unwrap = () => {
const { data: walletClient } = useWalletClient();
const { address } = useAccount();
const handleUnwrap = async () => {
if (!walletClient || !address) return;
const unwrapTx = await unwrapTokens({
publicClient,
walletClient,
address,
amount: "0.01",
encryptedERC20Address:
process.env.NEXT_PUBLIC_ENCRYPTED_ERC20_CONTRACT_ADDRESS,
incoEnv: process.env.NEXT_PUBLIC_INCO_ENV,
});
};
return (
Unwrap
Unwrap 0.01 Tokens
);
};
export default Unwrap;
```
## Utility Functions
### Inco Lite Utilities
```tsx
// src/lib/inco-lite.ts
import { Lightning, AttestedComputeSupportedOps } from '@inco/js/lite';
import { handleTypes } from '@inco/js';
import { baseSepolia } from 'viem/chains';
import { type PublicClient, type WalletClient, bytesToHex, pad, toHex } from 'viem';
type IncoEnv = 'testnet' | 'devnet';
async function getConfig(env: IncoEnv) {
return await Lightning.latest(env, baseSepolia.id);
}
export async function encryptValue({
value,
address,
contractAddress,
env,
}: {
value: bigint;
address: `0x${string}`;
contractAddress: `0x${string}`;
env: IncoEnv;
}): Promise<`0x${string}`> {
const inco = await getConfig(env);
const encrypted = await inco.encrypt(value, {
accountAddress: address,
dappAddress: contractAddress,
handleType: handleTypes.euint256,
});
return encrypted as `0x${string}`;
}
export async function decryptValue({
walletClient,
handle,
env,
}: {
walletClient: WalletClient;
handle: string;
env: IncoEnv;
}): Promise {
const inco = await getConfig(env);
const decrypted = await inco.attestedDecrypt(walletClient, [handle]);
return decrypted[0].plaintext.value;
}
export async function attestedCompute({
walletClient,
lhsHandle,
op,
rhsPlaintext,
env,
}: {
walletClient: WalletClient;
lhsHandle: `0x${string}`;
op: AttestedComputeSupportedOps;
rhsPlaintext: bigint;
env: IncoEnv;
}) {
const inco = await getConfig(env);
const result = await inco.attestedCompute(
walletClient,
lhsHandle,
op,
rhsPlaintext
);
const plaintext = result.plaintext;
const attestation = {
handle: result.attestation.handle,
value: encodePlaintext(plaintext),
};
const signatures = result.attestation.covalidatorSignatures.map((sig) =>
bytesToHex(sig)
);
return { plaintext, attestation, signatures };
}
function encodePlaintext(value: unknown): `0x${string}` {
if (typeof value === 'boolean') {
return value ? `0x${'0'.repeat(63)}1` : `0x${'0'.repeat(64)}`;
}
if (typeof value === 'bigint') {
return pad(toHex(value), { size: 32 });
}
throw new Error('Unsupported plaintext type');
}
export async function getFee(env: IncoEnv, publicClient: PublicClient): Promise {
const inco = await getConfig(env);
const fee = await publicClient.readContract({
address: inco.executorAddress,
abi: [
{
type: "function",
inputs: [],
name: "getFee",
outputs: [{ name: "", internalType: "uint256", type: "uint256" }],
stateMutability: "pure",
},
],
functionName: "getFee",
});
return fee;
}
```
### Balance Utilities
```tsx
// src/lib/balance-utils.ts
import { formatEther, type Address, type PublicClient, type WalletClient } from 'viem';
import { decryptValue } from './inco-lite';
import { baseSepolia } from '@/context/para-provider';
export const getEncryptedBalance = async ({
address,
publicClient,
walletClient,
encryptedERC20Address,
incoEnv,
}: {
address: Address;
publicClient: PublicClient;
walletClient: WalletClient;
encryptedERC20Address: Address;
incoEnv: 'testnet' | 'devnet';
}) => {
try {
if (!publicClient || !walletClient || !address) {
return '0';
}
const encryptedHandle = await publicClient.readContract({
address: encryptedERC20Address,
abi: [
{
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
],
functionName: 'balanceOf',
args: [address],
});
if (encryptedHandle === '0x' + '0'.repeat(64)) {
return '0';
}
const decrypted = await decryptValue({
walletClient: { ...walletClient, chain: baseSepolia },
handle: encryptedHandle,
env: incoEnv,
});
return formatEther(decrypted);
} catch (error) {
console.error('Failed to fetch encrypted balance:', error);
return '0';
}
};
```
### Wrap Utilities
```tsx
// src/lib/wrap-utils.ts
import { parseEther, type Address, type WalletClient } from 'viem';
import { baseSepolia } from '@/context/para-provider';
export const approveTokens = async ({
walletClient,
address,
amount,
erc20Address,
encryptedERC20Address,
}: {
walletClient: WalletClient;
address: Address;
amount: string;
erc20Address: Address;
encryptedERC20Address: Address;
}) => {
const amountInWei = parseEther(amount);
return walletClient.writeContract({
address: erc20Address,
account: address,
chain: baseSepolia,
abi: [
{
inputs: [
{ internalType: 'address', name: 'spender', type: 'address' },
{ internalType: 'uint256', name: 'value', type: 'uint256' },
],
name: 'approve',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
stateMutability: 'nonpayable',
type: 'function',
},
] as const,
functionName: 'approve',
args: [encryptedERC20Address, amountInWei],
});
};
export const wrapTokens = async ({
walletClient,
address,
amount,
encryptedERC20Address,
}: {
walletClient: WalletClient;
address: Address;
amount: string;
encryptedERC20Address: Address;
}) => {
const amountInWei = parseEther(amount);
return walletClient.writeContract({
address: encryptedERC20Address,
account: address,
chain: baseSepolia,
abi: [
{
inputs: [{ internalType: 'uint256', name: 'amount', type: 'uint256' }],
name: 'wrap',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
] as const,
functionName: 'wrap',
args: [amountInWei],
});
};
```
### Confidential Transfer Utilities
```tsx
// src/lib/confidential-send.ts
import {
type Address,
type PublicClient,
type WalletClient,
parseEther,
} from 'viem';
import { encryptValue, getFee } from './inco-lite';
import { baseSepolia } from '@/context/para-provider';
export const confidentialTransfer = async ({
amount,
address,
recipient,
walletClient,
publicClient,
encryptedERC20Address,
incoEnv,
}: {
amount: string;
address: Address;
recipient: Address;
publicClient: PublicClient;
walletClient: WalletClient;
encryptedERC20Address: Address;
incoEnv: 'testnet' | 'devnet';
}) => {
try {
const amountInWei = parseEther(amount);
const encryptedAmount = await encryptValue({
value: amountInWei,
address,
contractAddress: encryptedERC20Address,
env: incoEnv,
});
const fee = await getFee(incoEnv, publicClient);
return walletClient.writeContract({
address: encryptedERC20Address,
chain: baseSepolia,
abi: [
{
inputs: [
{ internalType: 'address', name: 'to', type: 'address' },
{ internalType: 'bytes', name: 'encryptedAmount', type: 'bytes' },
],
name: 'transfer',
outputs: [{ internalType: 'bool', name: '', type: 'bool' }],
stateMutability: 'payable',
type: 'function',
},
],
functionName: 'transfer',
args: [recipient, encryptedAmount],
account: address,
value: fee,
});
} catch (error) {
console.error('Error in confidential transfer:', error);
throw error;
}
};
```
### Unwrap Utilities
```tsx
// src/lib/unwrap-utils.ts
import {
type Address,
type PublicClient,
type WalletClient,
parseEther,
} from 'viem';
import { AttestedComputeSupportedOps } from '@inco/js/lite';
import { attestedCompute } from './inco-lite';
import { baseSepolia } from '@/context/para-provider';
export const unwrapTokens = async ({
publicClient,
walletClient,
address,
amount,
encryptedERC20Address,
incoEnv,
}: {
publicClient: PublicClient;
walletClient: WalletClient;
address: Address;
amount: string;
encryptedERC20Address: Address;
incoEnv: 'testnet' | 'devnet';
}) => {
try {
const amountInWei = parseEther(amount);
const balanceHandle = await publicClient.readContract({
address: encryptedERC20Address,
abi: [
{
inputs: [{ internalType: 'address', name: 'account', type: 'address' }],
name: 'balanceOf',
outputs: [{ internalType: 'bytes32', name: '', type: 'bytes32' }],
stateMutability: 'view',
type: 'function',
},
],
functionName: 'balanceOf',
args: [address],
});
const { attestation, signatures } = await attestedCompute({
walletClient: {...walletClient, chain: baseSepolia},
lhsHandle: balanceHandle as `0x${string}`,
op: AttestedComputeSupportedOps.Ge,
rhsPlaintext: amountInWei,
env: incoEnv,
});
const unwrapTx = await walletClient.writeContract({
address: encryptedERC20Address,
chain: baseSepolia,
abi: [
{
inputs: [
{ internalType: 'uint256', name: 'amount', type: 'uint256' },
{
internalType: 'tuple',
name: 'enoughBalanceDecryptionAttestation',
components: [
{ internalType: 'bytes32', name: 'handle', type: 'bytes32' },
{ internalType: 'bytes32', name: 'value', type: 'bytes32' },
],
type: 'tuple',
},
{ internalType: 'bytes[]', name: 'signature', type: 'bytes[]' },
],
name: 'unwrap',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
],
functionName: 'unwrap',
args: [amountInWei, attestation, signatures],
account: address,
});
await publicClient.waitForTransactionReceipt({
hash: unwrapTx,
confirmations: 5,
});
return unwrapTx;
} catch (error) {
console.error('Unwrap error:', error);
throw error;
}
};
```
## Conclusion
With Para, setting up secure, user-friendly access to Inco Confidential Wrapper contracts is fast and easy.
## Related Walkthroughs
Intermediate · 25 min · Zero-knowledge virtual machine
Advanced · 45 min · Cross-chain USDC bridges
Intermediate · 20 min · Send ETH with Ethers and Viem
# Para + M0 Integration
Source: https://docs.getpara.com/v3/walkthroughs/m0
Para's embedded wallets [provide seamless access to M0's programmable stablecoin infrastructure](https://blog.getpara.com/money-movement/). Build next-generation financial applications with custom digital dollars.
Learn about M0's stablecoin protocol
## Prerequisites
- Para API key configured
- Node.js 18+ environment
- Understanding of stablecoin mechanics
- [M0 contract addresses](https://docs.m0.org/home/overview/) for your network
## Installation
Install the required packages:
```bash
npm install @getpara/server-sdk @getpara/viem-v2-integration viem@^2
```
## Setup
Import the required Para SDK and Viem components:
```typescript
import { ParaServer } from "@getpara/server-sdk";
import { createParaViemClient, createParaViemAccount } from "@getpara/viem-integration";
import { http } from 'viem';
import { mainnet } from 'viem/chains';
```
Set up Para server instance:
```typescript
const para = new ParaServer(process.env.PARA_API_KEY);
```
Check for existing wallet or create new pregenerated wallet:
```typescript
const hasWallet = await para.hasPregenWallet({
pregenId: { email: 'user@example.com' },
});
let pregenWallet;
if (!hasWallet) {
pregenWallet = await para.createPregenWallet({
type: 'EVM',
pregenId: { email: "user@example.com" },
});
}
```
Set up the account and Viem client for M0 interactions:
```typescript
const account = await createParaViemAccount(para);
const client = createParaViemClient(para, {
account,
chain: mainnet,
transport: http(),
});
```
## Contract Configuration
### M0 Contract Addresses
```typescript
const M_ADDRESS = '0x866A2BF4E572CbcF37D5071A7a58503Bfb36be1b';
const WM_ADDRESS = '0x437cc33344a0B27A429f795ff6B469C72698B291';
```
### Contract ABIs
```typescript
const ERC20_ABI = [
{ name: 'approve', type: 'function', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
{ name: 'transfer', type: 'function', inputs: [{ name: 'recipient', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] }
];
const WM_ABI = [
{ name: 'wrap', type: 'function', inputs: [{ name: 'recipient', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'uint240' }] },
{ name: 'unwrap', type: 'function', inputs: [{ name: 'recipient', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'uint240' }] }
];
```
## Usage
### Wrapping \$M to \$wM
```typescript
async function wrapMToWM(amount) {
const approveHash = await client.writeContract({
address: M_ADDRESS,
abi: ERC20_ABI,
functionName: 'approve',
args: [WM_ADDRESS, amount]
});
console.log('Approve hash:', approveHash);
```
```typescript
const wrapHash = await client.writeContract({
address: WM_ADDRESS,
abi: WM_ABI,
functionName: 'wrap',
args: [account.address, amount]
});
console.log('Wrap hash:', wrapHash);
}
```
### Unwrapping \$wM to \$M
```typescript
async function unwrapWMToM(amount) {
const unwrapHash = await client.writeContract({
address: WM_ADDRESS,
abi: WM_ABI,
functionName: 'unwrap',
args: [account.address, amount]
});
console.log('Unwrap hash:', unwrapHash);
}
```
### Token Transfers
```typescript
async function transferToken(tokenAddress, recipient, amount) {
const transferHash = await client.writeContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: 'transfer',
args: [recipient, amount]
});
console.log('Transfer hash:', transferHash);
}
```
## Examples
### Basic Operations
```typescript
await wrapMToWM(1000000n);
await unwrapWMToM(1000000n);
await transferToken(M_ADDRESS, '0xRecipientAddress', 1000000n);
```
## Next Steps
Learn about M0's stablecoin protocol
Deep dive into wallet pregeneration
## Related Walkthroughs
Advanced · 45 min · Lending, borrowing, and yield strategies
Beginner · 20 min · Stablecoin issuance API
Advanced · 45 min · Cross-chain USDC bridges
# Mellow Core Vaults
Source: https://docs.getpara.com/v3/walkthroughs/mellow-core-vaults
This guide covers the full lifecycle for integrating deposits and redemptions with **Mellow Core Vaults**.
## 1. Architecture Overview
A **Mellow Core Vault** is a programmable, modular asset management contract. It serves as the central hub for capital management, risk control, and composable logic. **Depositors** provide capital; **Curators** manage that capital within guardrails set by the vault configuration.
All deposit and redemption flows are time-buffered through an off-chain oracle - protecting depositors against flash-loan attacks and front-running by design.
### Core Components
| Component | Role |
|-----------|------|
| **Vault** | Central contract. Orchestrates ACLModule (access control), ShareModule (queues & shares), VaultModule (subvault management), and BaseModule (reentrancy). |
| **DepositQueue** | Accepts token deposits, stores them as timestamped checkpoints, and mints vault shares after oracle pricing. |
| **RedeemQueue** | Accepts share redemptions, locks shares immediately, and releases assets after oracle pricing and liquidity settlement. |
| **ShareManager** | ERC20-compatible contract managing share supply, whitelisting, global lockups, and compliance controls. |
| **Oracle** | Trusted off-chain price reporter. Submits `handleReport()` with a price and timestamp. |
| **Curator** | Manages capital allocation across subvaults; calls `handleBatches()` to settle redemption liquidity. |
### Deposit Lifecycle
#### Async queue
Time-buffered. Oracle prices the batch; Curator settles liquidity. Claim is a separate transaction after processing.
```
Step 1 - User calls deposit(assets, referral, merkleProof)
+--> Request stored as a timestamped checkpoint in DepositQueue
Step 2 - Handle Report submitted handleReport(priceD18, depositTimestamp)
+--> Processes all requests older than the configured depositInterval
+--> Shares are allocated lazily using a Fenwick tree (computed at claim time)
Step 3 - User calls DepositQueue.claim(account)
+--> Share amount computed, deposit fee deducted, shares transferred to user
```
#### Sync queue
Shares issued or assets returned in the same transaction. No separate claim step.
```
Step 1 - User calls deposit(assets, referral, merkleProof)
+--> Share amount computed, deposit fee deducted, shares transferred to user
```
### Redemption Lifecycle
```
Step 1 - User calls RedeemQueue.redeem(shares)
+--> Shares locked immediately from the user's wallet
Step 2 - Handle Report submitted handleReport(priceD18, redeemTimestamp)
+--> Prices all requests older than the configured redeemInterval
Step 3 - Curator calls RedeemQueue.handleBatches(n)
+--> Pulls required liquidity from vault/subvaults into RedeemQueue
+--> RedeemRequestsHandled event emitted; isClaimable becomes true
Step 4 - User calls RedeemQueue.claim(receiver, timestamps[])
+--> Underlying assets transferred to receiver
```
---
## 2. Supported Networks
| Chain ID | Network |
|----------|---------|
| 1 | Ethereum |
| 8453 | Base |
| 42161 | Arbitrum |
| 17000 | Holesky (testnet) |
| 560048 | Hoodi (testnet) |
| 143 | Monad (testnet) |
| 9745 | Plasma |
| 999 | HyperEVM |
| 31612 | Mezo |
---
## 3. Vault Discovery
Fetch the list of all vaults from the Mellow REST API. No authentication is required.
```
GET https://api.mellow.finance/v1/vaults
-> VaultData[]
```
```ts
async function fetchVaults(): Promise {
const response = await fetch('https://api.mellow.finance/v1/vaults');
if (!response.ok) {
throw new Error(`Failed to fetch vaults: ${response.status} ${response.statusText}`)
}
return response.json() as Promise
}
```
---
## 4. TypeScript Interfaces & Constants
```ts
import type { Address } from 'viem'
// -- Token ---------------------------------------------------------------------
interface Token {
address: Address
symbol: string
decimals: number
}
// -- Queue ---------------------------------------------------------------------
interface Queue {
/** The queue contract address */
queue: Address
/** The token this queue accepts (for deposits) or pays out (for redemptions) */
asset: Address
/** When true, new submissions are rejected */
is_paused: boolean
/** Async queues require a separate claim step after oracle processing */
type: 'async' | 'sync'
}
// -- VaultData -----------------------------------------------------------------
interface VaultData {
id: string
chain_id: number
address: Address
symbol: string
/** Decimals used for vault shares - use this when parsing redeem amounts */
decimals: number
name: string
base_token: Token
deposit_tokens: Token[]
withdraw_tokens: Token[]
collector: Address
deposit_queues: Queue[]
redeem_queues: Queue[]
}
// -- RedeemRequest -------------------------------------------------------------
interface RedeemRequest {
/** uint32 unix timestamp identifying this request */
timestamp: bigint
/** Shares submitted for this request */
shares: bigint
/** True when the oracle has processed this batch and assets can be claimed */
isClaimable: boolean
/** Assets available to claim (0 until isClaimable is true) */
assets: bigint
}
// -- Constants -----------------------------------------------------------------
/** Sentinel address representing native ETH in the Mellow protocol */
const NATIVE_ETH_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE' as const
/** Maximum value for Solidity uint224 - the deposit() assets parameter type */
const UINT224_MAX = (1n << 224n) - 1n
function isNativeEth(address: string): boolean {
return address.toLowerCase() === NATIVE_ETH_ADDRESS.toLowerCase()
}
```
---
## 5. ABIs
Only the functions and events relevant to integrations are shown here.
### 5.1 Deposit Queue ABI
```ts
const DEPOSIT_QUEUE_ABI = [
// -- View functions ----------------------------------------------------------
{
type: 'function',
name: 'asset',
inputs: [],
outputs: [{ name: '', type: 'address' }],
stateMutability: 'view',
},
{
type: 'function',
name: 'requestOf',
inputs: [{ name: 'account', type: 'address' }],
outputs: [
{ name: 'timestamp', type: 'uint256' },
{ name: 'assets', type: 'uint256' },
],
stateMutability: 'view',
},
{
type: 'function',
name: 'claimableOf',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: 'shares', type: 'uint256' }], // returns claimable shares, not assets
stateMutability: 'view',
},
// -- Write functions ---------------------------------------------------------
{
type: 'function',
name: 'deposit',
inputs: [
{ name: 'assets', type: 'uint224' },
{ name: 'referral', type: 'address' },
{ name: 'merkleProof', type: 'bytes32[]' },
],
outputs: [],
stateMutability: 'payable',
},
{
type: 'function',
name: 'claim',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'nonpayable',
},
{
type: 'function',
name: 'cancelDepositRequest',
inputs: [],
outputs: [],
stateMutability: 'nonpayable',
},
// -- Events ------------------------------------------------------------------
{
type: 'event',
name: 'DepositRequested',
inputs: [
{ name: 'account', type: 'address', indexed: true },
{ name: 'referral', type: 'address', indexed: true },
{ name: 'assets', type: 'uint224', indexed: false },
{ name: 'timestamp', type: 'uint32', indexed: false },
],
},
{
type: 'event',
name: 'DepositRequestClaimed',
inputs: [
{ name: 'account', type: 'address', indexed: true },
{ name: 'shares', type: 'uint256', indexed: false },
{ name: 'timestamp', type: 'uint32', indexed: false },
],
},
{
type: 'event',
name: 'DepositRequestCanceled',
inputs: [
{ name: 'account', type: 'address', indexed: true },
{ name: 'assets', type: 'uint256', indexed: false },
{ name: 'timestamp', type: 'uint32', indexed: false },
],
},
// -- Errors ------------------------------------------------------------------
{ type: 'error', name: 'PendingRequestExists', inputs: [] },
{ type: 'error', name: 'ClaimableRequestExists', inputs: [] },
{ type: 'error', name: 'NoPendingRequest', inputs: [] },
{ type: 'error', name: 'QueuePaused', inputs: [] },
{ type: 'error', name: 'DepositNotAllowed', inputs: [] },
{ type: 'error', name: 'ZeroValue', inputs: [] },
{
type: 'error',
name: 'InsufficientBalance',
inputs: [
{ name: 'balance', type: 'uint256' },
{ name: 'needed', type: 'uint256' },
],
},
] as const
```
### 5.2 Redeem Queue ABI
```ts
const REDEEM_QUEUE_ABI = [
// -- View functions ----------------------------------------------------------
{
type: 'function',
name: 'asset',
inputs: [],
outputs: [{ name: '', type: 'address' }],
stateMutability: 'view',
},
{
type: 'function',
name: 'requestsOf',
inputs: [
{ name: 'account', type: 'address' },
{ name: 'offset', type: 'uint256' },
{ name: 'limit', type: 'uint256' },
],
outputs: [
{
name: 'requests',
type: 'tuple[]',
components: [
{ name: 'timestamp', type: 'uint256' },
{ name: 'shares', type: 'uint256' },
{ name: 'isClaimable', type: 'bool' },
{ name: 'assets', type: 'uint256' },
],
},
],
stateMutability: 'view',
},
// -- Write functions ---------------------------------------------------------
{
type: 'function',
name: 'redeem',
inputs: [{ name: 'shares', type: 'uint256' }],
outputs: [],
stateMutability: 'nonpayable',
},
{
type: 'function',
name: 'claim',
inputs: [
{ name: 'receiver', type: 'address' },
{ name: 'timestamps', type: 'uint32[]' },
],
outputs: [{ name: 'assets', type: 'uint256' }],
stateMutability: 'nonpayable',
},
// -- Events ------------------------------------------------------------------
{
type: 'event',
name: 'RedeemRequested',
inputs: [
{ name: 'account', type: 'address', indexed: true },
{ name: 'shares', type: 'uint256', indexed: false },
{ name: 'timestamp', type: 'uint256', indexed: false },
],
},
{
type: 'event',
name: 'RedeemRequestClaimed',
inputs: [
{ name: 'account', type: 'address', indexed: true },
{ name: 'receiver', type: 'address', indexed: true },
{ name: 'assets', type: 'uint256', indexed: false },
{ name: 'timestamp', type: 'uint32', indexed: false },
],
},
// -- Errors ------------------------------------------------------------------
{ type: 'error', name: 'QueuePaused', inputs: [] },
{ type: 'error', name: 'ZeroValue', inputs: [] },
{
type: 'error',
name: 'InsufficientBalance',
inputs: [
{ name: 'balance', type: 'uint256' },
{ name: 'needed', type: 'uint256' },
],
},
] as const
```
### 5.3 ERC20 ABI (subset - for approval)
```ts
const ERC20_ABI = [
{
type: 'function',
name: 'allowance',
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
],
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
},
{
type: 'function',
name: 'approve',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' },
],
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'nonpayable',
},
{
type: 'function',
name: 'balanceOf',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
},
] as const
```
### 5.4 Vault ABI (subset - for share balance lookup)
```ts
const VAULT_ABI = [
{
type: 'function',
name: 'shareManager',
inputs: [],
outputs: [{ name: '', type: 'address' }],
stateMutability: 'view',
},
] as const
```
The `shareManager` address is itself an ERC20-compatible contract. Use `ERC20_ABI` with `balanceOf`, or use the richer `SHARE_MANAGER_ABI` below for more precise balance reads.
### 5.5 ShareManager ABI (subset)
```ts
const SHARE_MANAGER_ABI = [
// -- Balance reads (prefer these over raw balanceOf) -------------------------
{
type: 'function',
name: 'sharesOf',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
},
{
type: 'function',
name: 'activeSharesOf',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
// Returns only the shares that are not locked in a redeem queue.
// Use this to check the redeemable balance.
},
{
type: 'function',
name: 'claimableSharesOf',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
// Shares processed by the oracle and awaiting DepositQueue.claim().
},
// -- Whitelist / permissions --------------------------------------------------
{
type: 'function',
name: 'flags',
inputs: [],
outputs: [
{
name: '',
type: 'tuple',
components: [
{ name: 'hasMintPause', type: 'bool' },
{ name: 'hasBurnPause', type: 'bool' },
{ name: 'hasTransferPause', type: 'bool' },
{ name: 'hasWhitelist', type: 'bool' }, // deposit whitelist active
{ name: 'hasTransferWhitelist', type: 'bool' },
{ name: 'globalLockup', type: 'uint32' }, // seconds all shares are locked after mint
],
},
],
stateMutability: 'view',
},
{
type: 'function',
name: 'isDepositorWhitelisted',
inputs: [
{ name: 'account', type: 'address' },
{ name: 'merkleProof', type: 'bytes32[]' },
],
outputs: [{ name: '', type: 'bool' }],
stateMutability: 'view',
},
] as const
interface ShareManagerFlags {
hasMintPause: boolean
hasBurnPause: boolean
hasTransferPause: boolean
hasWhitelist: boolean // when true, deposits require a valid Merkle proof
hasTransferWhitelist: boolean
globalLockup: number // seconds before newly minted shares become transferable
}
```
---
## 6. Deposit
### Overview
Depositing submits tokens to a `DepositQueue` contract. For **async** queues the shares are not immediately available - an oracle processes the batch and sets a price, after which the user calls `claim` to receive their shares.
```
Step 1 - approve token spend (ERC20 only)
Step 2 - call deposit()
v
[oracle processes batch]
v
Step 3 - call claim() to receive vault shares
```
### Steps
1. Pick a deposit queue from `vault.deposit_queues`. You can match by `queue.asset` address and `queue.type` deposit type ("sync" | "async").
2. Check `queue.is_paused === false`. Throw early if paused.
3. **Whitelist check (permissioned vaults only):** Read `shareManager.flags()`. If `flags.hasWhitelist === true`, call `shareManager.isDepositorWhitelisted(userAddress, merkleProof)`. If it returns `false`, the deposit will revert with `DepositNotAllowed`. For public vaults (`hasWhitelist === false`), pass `[]` as the proof.
4. Find the matching `Token` in `vault.deposit_tokens` for `queue.asset`.
5. Parse the human-readable amount: `parseUnits(amount, token.decimals)`.
6. Validate `parsedAmount > 0n` and `parsedAmount <= UINT224_MAX`.
7. Only one pending deposit request per user is allowed per queue. For async queues, call `requestOf(userAddress)` and check `timestamp === 0n` before depositing.
8. **If the asset is native ETH** (`queue.asset === NATIVE_ETH_ADDRESS`): check the user's ETH balance, then call `deposit()` with `value = parsedAmount`.
9. **If the asset is an ERC20**:
- Read `allowance(userAddress, queueAddress)`.
- If `currentAllowance > 0n`, send `approve(queueAddress, 0n)` first. This is required for tokens like USDT that revert if you set a non-zero allowance on top of an existing one.
- Send `approve(queueAddress, parsedAmount)`.
- Then call `deposit(parsedAmount, zeroAddress, merkleProof)` with `value = 0n`.
10. For **async** queues: do not expect shares immediately. Poll `claimableOf` or listen for `DepositRequestClaimed` events, then call `claim`.
### Code Example
```ts
import { createParaViemAccount, createParaViemClient } from "@getpara/viem-v2-integration";
import {
createPublicClient,
http,
parseUnits,
zeroAddress,
} from 'viem';
import { mainnet } from 'viem/chains';
import Para from "@getpara/web-sdk";
const para = new Para("YOUR_API_KEY");
// Authenticate first...
const account = createParaViemAccount({ para });
const publicClient = createPublicClient({ chain: mainnet, transport: http() });
const walletClient = createParaViemClient({ para, walletClientConfig: {
account,
chain: mainnet,
transport: http(),
}});
async function deposit(
vault: VaultData,
queueAddress: string,
humanAmount: string,
// Pass [] for public vaults. For whitelisted vaults, obtain proof from the Mellow API.
merkleProof: `0x${string}`[] = [],
) {
const userAddress = account.address;
// 1. Find the queue and validate it is open
const queue = vault.deposit_queues.find(q => q.queue.toLowerCase() === queueAddress.toLowerCase());
if (!queue) throw new Error('Queue not found');
if (queue.is_paused) throw new Error('Queue is paused');
// 2. Whitelist check - read shareManager.flags() to see if this vault requires a proof
const shareManagerAddress = await publicClient.readContract({
address: vault.address,
abi: VAULT_ABI,
functionName: 'shareManager',
});
const flags = await publicClient.readContract({
address: shareManagerAddress,
abi: SHARE_MANAGER_ABI,
functionName: 'flags',
}) as ShareManagerFlags;
if (flags.hasWhitelist) {
const allowed = await publicClient.readContract({
address: shareManagerAddress,
abi: SHARE_MANAGER_ABI,
functionName: 'isDepositorWhitelisted',
args: [userAddress, merkleProof],
});
if (!allowed) throw new Error('Address is not whitelisted for this vault');
}
// 3. Resolve token metadata
const token = vault.deposit_tokens.find(t => t.address.toLowerCase() === queue.asset.toLowerCase());
if (!token) throw new Error('Token not found');
// 4. Parse and validate amount
const parsedAmount = parseUnits(humanAmount, token.decimals);
if (parsedAmount <= 0n) throw new Error('Amount must be greater than zero');
if (parsedAmount > UINT224_MAX) throw new Error('Amount exceeds maximum (uint224)');
const native = isNativeEth(queue.asset);
if (native) {
// -- Native ETH path --------------------------------------------------------
const balance = await publicClient.getBalance({ address: userAddress });
if (parsedAmount > balance) throw new Error('Insufficient ETH balance');
const hash = await walletClient.writeContract({
address: queue.queue as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
functionName: 'deposit',
args: [parsedAmount, zeroAddress, merkleProof],
value: parsedAmount,
});
return publicClient.waitForTransactionReceipt({ hash });
}
else {
// -- ERC20 path -------------------------------------------------------------
// Check balance
const balance = await publicClient.readContract({
address: queue.asset as `0x${string}`,
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [userAddress],
});
if (parsedAmount > balance) throw new Error('Insufficient token balance');
// Check for pending request - only one pending request per user per queue is allowed
if (queue.type === 'async') {
const [timestamp] = await publicClient.readContract({
address: queue.queue as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
functionName: 'requestOf',
args: [userAddress],
});
if (timestamp > 0n) {
throw new Error('Pending deposit request already exists - cancel or claim it first');
}
}
// Read allowance BEFORE sending any transactions
const currentAllowance = await publicClient.readContract({
address: queue.asset as `0x${string}`,
abi: ERC20_ABI,
functionName: 'allowance',
args: [userAddress, queue.queue as `0x${string}`],
})
// Reset allowance if needed (required for tokens like USDT)
if (currentAllowance > 0n) {
const resetHash = await walletClient.writeContract({
address: queue.asset as `0x${string}`,
abi: ERC20_ABI,
functionName: 'approve',
args: [queue.queue as `0x${string}`, 0n],
});
await publicClient.waitForTransactionReceipt({ hash: resetHash });
}
// Approve exact amount
const approveHash = await walletClient.writeContract({
address: queue.asset as `0x${string}`,
abi: ERC20_ABI,
functionName: 'approve',
args: [queue.queue as `0x${string}`, parsedAmount],
});
await publicClient.waitForTransactionReceipt({ hash: approveHash });
// Deposit
const depositHash = await walletClient.writeContract({
address: queue.queue as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
functionName: 'deposit',
args: [parsedAmount, zeroAddress, merkleProof],
value: 0n,
});
return publicClient.waitForTransactionReceipt({ hash: depositHash });
}
}
```
**Referral address:** Pass `zeroAddress` (`0x0000000000000000000000000000000000000000`) unless you have been issued a referral address by the Mellow team.
**Merkle proof and whitelisting:** Pass `[]` for public vaults. When `shareManager.flags().hasWhitelist === true`, the vault is permissioned. `deposit()` will revert with `DepositNotAllowed` if the proof is invalid or empty. Contact the Mellow team or query the Mellow API to obtain a valid proof for whitelisted addresses.
---
## 7. Cancel Deposit
### Overview
A pending async deposit request can be cancelled before the oracle processes it. Cancelling returns the deposited tokens to the user.
You **cannot** cancel once the request is claimable - call `claim` instead.
### Steps
1. Call `requestOf(userAddress)` on the deposit queue. Check `timestamp > 0n` - if zero, there is no pending request.
2. Call `claimableOf(userAddress)`. If `> 0n`, the oracle has already processed the request - you must claim it, not cancel it.
3. Call `cancelDepositRequest()`. This function takes no arguments - it cancels the caller's own request.
### Code Example
```ts
async function cancelDeposit(queueAddress: string) {
const userAddress = account.address;
// 1. Check for a pending request
const [timestamp] = await publicClient.readContract({
address: queueAddress as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
functionName: 'requestOf',
args: [userAddress],
});
if (timestamp === 0n) throw new Error('No pending deposit request to cancel');
// 2. Check whether it is already claimable
const claimable = await publicClient.readContract({
address: queueAddress as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
functionName: 'claimableOf',
args: [userAddress],
});
if (claimable > 0n) {
throw new Error('Request already processed - call claim() instead of cancel');
}
// 3. Cancel
const hash = await walletClient.writeContract({
address: queueAddress as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
functionName: 'cancelDepositRequest',
args: [],
});
return publicClient.waitForTransactionReceipt({ hash });
}
```
---
## 8. Claim Deposit (async)
### Overview
After the oracle processes an async deposit request, vault shares are held in the queue contract ready for collection. Call `claim` to transfer them to the user.
### Steps
1. Call `claimableOf(userAddress)`. If `> 0n`, shares are ready to claim.
2. If `claimableOf` returns `0n`, call `requestOf(userAddress)`. If `timestamp > 0n`, the oracle has not yet processed the request - wait and retry later.
3. Call `claim(userAddress)`. Returns `true` when shares are successfully transferred.
### Code Example
```ts
async function claimDeposit(queueAddress: string) {
const userAddress = account.address;
// 1. Check if there is anything to claim
const claimable = await publicClient.readContract({
address: queueAddress as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
functionName: 'claimableOf',
args: [userAddress],
});
if (claimable === 0n) {
// Check if still pending
const [timestamp] = await publicClient.readContract({
address: queueAddress as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
functionName: 'requestOf',
args: [userAddress],
});
if (timestamp > 0n) {
throw new Error('Deposit is pending oracle processing - try again later');
}
throw new Error('No claimable deposit found');
}
// 2. Claim shares
const hash = await walletClient.writeContract({
address: queueAddress as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
functionName: 'claim',
args: [userAddress],
});
return publicClient.waitForTransactionReceipt({ hash });
}
```
---
## 9. Redeem
### Overview
Redemption burns vault shares and, after oracle processing, returns the underlying asset to the user. Redeem queues are **always async** - there is always a separate claim step.
```
Step 1 - call redeem(shares)
v
[oracle processes batch]
v
Step 2 - call claim(receiver, timestamps)
```
### Steps
1. Pick a redeem queue from `vault.redeem_queues`.
2. Check `queue.is_paused === false`.
3. Fetch the user's redeemable share balance:
- Call `vault.shareManager()` to get the share manager address.
- Call `shareManager.activeSharesOf(userAddress)` - this returns only shares that are **not** currently locked in a pending redeem request. Use `sharesOf` if you want the total including locked shares.
4. Parse the share amount using **vault decimals** (`vault.decimals`), **not** the token's decimals. This is a common mistake - the share token uses the vault's decimal precision.
5. Validate `parsedShares > 0n` and `parsedShares <= activeShareBalance`.
6. Call `redeem(parsedShares)`. No ETH value, no ERC20 approval - the vault contract locks shares directly from the caller.
7. Wait for oracle processing and `handleBatches()`, then call `claimRedeem` (see Section 10).
**Multiple requests are allowed.** Unlike deposits, a user can have many concurrent redemption requests. Each `redeem()` call creates a new request with its own timestamp.
**Requests cannot be cancelled.** Once submitted, a redemption request is permanent. This is intentional. Cancellable redemptions would allow yield-griefing by requesting redemption to force liquidity pulls from external protocols, then withdrawing the request. The locked shares remain locked until claimed.
**Settlement flow:** After `redeem()`, two off-chain steps must happen before you can claim: (1) the oracle calls `handleReport()` to price the batch, then (2) the Curator calls `handleBatches()` on the RedeemQueue to pull the required liquidity from subvaults. Listen for the `RedeemRequestsHandled` event. It fires when `handleBatches()` settles one or more batches and requests become claimable. The timing depends on vault configuration (`redeemInterval`) and curator activity, typically ranging from minutes to hours.
### Code Example
```ts
async function redeem(vault: VaultData, queueAddress: string, humanShares: string) {
const userAddress = account.address;
// 1. Find the queue
const queue = vault.redeem_queues.find(q => q.queue.toLowerCase() === queueAddress.toLowerCase());
if (!queue) throw new Error('Redeem queue not found');
if (queue.is_paused) throw new Error('Redeem queue is paused');
// 2. Get redeemable share balance via vault's shareManager
// activeSharesOf excludes shares already locked in pending redeem requests
const shareManagerAddress = await publicClient.readContract({
address: vault.address,
abi: VAULT_ABI,
functionName: 'shareManager',
});
const activeShareBalance = await publicClient.readContract({
address: shareManagerAddress,
abi: SHARE_MANAGER_ABI,
functionName: 'activeSharesOf',
args: [userAddress],
});
// 3. Parse share amount using vault decimals (NOT the output token's decimals)
const parsedShares = parseUnits(humanShares, vault.decimals);
if (parsedShares <= 0n) throw new Error('Redeem amount must be greater than zero');
if (parsedShares > activeShareBalance) {
throw new Error(`Insufficient redeemable share balance: have ${activeShareBalance}, need ${parsedShares}`);
}
// 4. Submit redeem - no approval required, vault locks shares from caller
const hash = await walletClient.writeContract({
address: queue.queue as `0x${string}`,
abi: REDEEM_QUEUE_ABI,
functionName: 'redeem',
args: [parsedShares],
});
return publicClient.waitForTransactionReceipt({ hash });
}
```
**No approval needed:** Unlike deposits, redemptions do not require an ERC20 `approve`. The vault contract has the authority to lock and burn shares on behalf of the caller.
---
## 10. Claim Redeem
### Overview
A user may accumulate multiple redemption requests over time. The `requestsOf` function returns all requests paginated. Once the oracle marks a request `isClaimable`, the user can batch-claim them by passing the corresponding timestamps to `claim`.
### Steps
1. Paginate `requestsOf(userAddress, offset, 100)`:
- Start with `offset = 0`.
- Increment by `100` each iteration.
- Stop when a page returns fewer than `100` items.
2. Filter to requests where `isClaimable === true`.
3. Extract the `timestamp` field from each claimable request. Cast to `number` - timestamps are `uint32` values, safely representable as JavaScript numbers until year 2106.
4. Call `claim(userAddress, timestamps)`. Returns the total `assets` transferred.
**`claim()` is idempotent.** Non-claimable or already-claimed timestamps are silently skipped. The contract does not revert. You may safely pass all known timestamps and let the contract filter them.
### Code Example
```ts
async function claimRedeem(vault: VaultData, queueAddress: string) {
const userAddress = account.address;
const PAGE_SIZE = 100;
// 1. Collect all requests via pagination
const allRequests: RedeemRequest[] = [];
let offset = 0;
while (true) {
const page = await publicClient.readContract({
address: queueAddress as `0x${string}`,
abi: REDEEM_QUEUE_ABI,
functionName: 'requestsOf',
args: [userAddress, BigInt(offset), BigInt(PAGE_SIZE)],
}) as RedeemRequest[];
allRequests.push(...page);
if (page.length < PAGE_SIZE) break;
offset += PAGE_SIZE;
}
if (allRequests.length === 0) {
throw new Error('No redemption requests found');
}
// 2. Filter to claimable requests
const claimable = allRequests.filter(r => r.isClaimable);
if (claimable.length === 0) {
throw new Error(
`${allRequests.length} redemption request(s) are pending oracle processing - try again later`,
);
}
// 3. Extract timestamps as number[] (uint32 - safe as JS number)
const timestamps = claimable.map(r => Number(r.timestamp));
console.log(`Claiming ${claimable.length} of ${allRequests.length} redemption request(s)...`);
// 4. Batch claim
const hash = await walletClient.writeContract({
address: queueAddress as `0x${string}`,
abi: REDEEM_QUEUE_ABI,
functionName: 'claim',
args: [userAddress, timestamps],
});
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const pending = allRequests.length - claimable.length;
if (pending > 0) {
console.log(`${pending} request(s) are still pending and will need to be claimed later.`);
}
return receipt;
}
```
---
## 11. Fees
Fees in Mellow Core Vaults are paid in **vault shares**, not in underlying assets. The `FeeManager` contract calculates and deducts fees automatically during oracle report handling - integrators do not call fee functions directly.
| Fee Type | When Applied | Effect on Integrator |
|----------|-------------|----------------------|
| **Deposit fee** | At `DepositQueue.claim()` time | User receives fewer shares than the raw price implies. Calculated as `shares * depositFeeD6 / 1e6`. |
| **Redeem fee** | At `RedeemQueue.redeem()` time | A portion of shares is deducted before the redemption amount is finalized. |
| **Performance fee** | Oracle report trigger | Accrued to the vault as yield is generated; does not directly affect per-request calculations. |
| **Protocol fee** | Continuous accrual | Time-based, deducted from share supply; transparent to depositors but reduces NAV per share over time. |
Fees are vault-specific and set by Curators. Check the vault configuration or the Mellow API for exact fee parameters before displaying estimated returns to users.
---
## 12. Error Reference
| Error | Contract | When it occurs | What to do |
|-------|----------|----------------|------------|
| `PendingRequestExists` | DepositQueue | `deposit()` called when a pending request already exists | Call `cancelDepositRequest()` first, or wait for oracle and then `claim()` |
| `ClaimableRequestExists` | DepositQueue | `cancelDepositRequest()` called when request is already claimable | The oracle processed the request - call `claim()` instead |
| `NoPendingRequest` | DepositQueue | `cancelDepositRequest()` called with no pending request | Nothing to cancel |
| `QueuePaused` | Both | Queue is temporarily suspended | Check `queue.is_paused` before submitting; wait for it to re-open |
| `DepositNotAllowed` | DepositQueue | Deposit rejected - queue paused, or vault has a whitelist and address is not included | Check `flags.hasWhitelist`; if true, obtain a valid Merkle proof via the Mellow API |
| `ZeroValue` | RedeemQueue | `redeem()` called with `shares = 0` | Validate amount > 0 before calling |
| `InsufficientBalance` | Both | Token or share balance too low | Validate balance on-chain before submitting |
| `Forbidden` | Both | Caller does not have the required role for the called function | This is a contract-operator error; user-facing code should not hit this |
---
## 13. Events Reference
Listen for these events to drive UI state or index on-chain activity.
| Event | Contract | Emitted when |
|-------|----------|--------------|
| `DepositRequested(account, referral, assets, timestamp)` | DepositQueue | `deposit()` succeeds |
| `DepositRequestClaimed(account, shares, timestamp)` | DepositQueue | `claim()` succeeds - user received vault shares |
| `DepositRequestCanceled(account, assets, timestamp)` | DepositQueue | `cancelDepositRequest()` succeeds - tokens returned |
| `RedeemRequested(account, shares, timestamp)` | RedeemQueue | `redeem()` succeeds |
| `RedeemRequestsHandled(counter, demand)` | RedeemQueue | Curator called `handleBatches()` and settled one or more batches - **listen for this to know when claims become available** |
| `RedeemRequestClaimed(account, receiver, assets, timestamp)` | RedeemQueue | `claim()` succeeds - user received underlying assets |
### Example: watching for claim events with viem
```ts
const unwatch = publicClient.watchContractEvent({
address: depositQueueAddress as `0x${string}`,
abi: DEPOSIT_QUEUE_ABI,
eventName: 'DepositRequestClaimed',
args: { account: userAddress },
onLogs: (logs) => {
for (const log of logs) {
console.log(`Shares received: ${log.args.shares}, timestamp: ${log.args.timestamp}`)
}
},
});
// Stop watching when done
unwatch();
```
# Miden Integration
Source: https://docs.getpara.com/v3/walkthroughs/miden
## What is Miden?
[Miden](https://miden.xyz/) is a zero-knowledge virtual machine (zkVM) built by Polygon. It enables developers to build high-throughput, privacy-preserving applications with:
- **Client-side proving**: Users generate proofs locally, enabling private transactions
- **Parallel transaction execution**: Transactions can be processed concurrently for high throughput
- **Programmable accounts**: Flexible account logic using Miden Assembly or higher-level languages
- **Privacy by default**: Transaction details remain private while maintaining verifiability
## What the Para Integration Enables
Integrating Para with Miden gives your users:
- **Seamless onboarding**: Users can create Miden accounts using familiar authentication methods (email, social login, passkeys) without managing seed phrases
- **Embedded wallet experience**: Para's distributed MPC infrastructure secures user keys while maintaining a smooth UX
- **Cross-platform support**: Build web and React applications that connect to Miden with consistent wallet functionality
To integrate Para on Miden, please visit [`miden-para`](https://github.com/0xMiden/miden-para).
## Prerequisites
Before getting started, you'll need:
- A Para API key from the [Para Developer Portal](https://developer.getpara.com)
- Node.js installed in your development environment
- Yarn 1.22.22 or later (required by miden-para)
## Installation
Install `miden-para` along with its peer dependencies:
```bash
yarn add miden-para @demox-labs/miden-sdk@^0.12.5 @getpara/web-sdk@2.0.0-alpha.73
```
The `miden-para` package requires specific versions of peer dependencies to avoid duplicate copies. Make sure to install the exact versions shown above.
## Basic Setup
```typescript
import { MidenPara } from "miden-para";
const midenPara = new MidenPara({
paraApiKey: process.env.PARA_API_KEY,
// Additional configuration options
});
```
### Private Storage Mode
When using `storageMode: "private"`, you must provide an `accountSeed` parameter to ensure private accounts remain recoverable:
```typescript
const midenPara = new MidenPara({
paraApiKey: process.env.PARA_API_KEY,
storageMode: "private",
accountSeed: "your-account-seed", // Required for private mode
});
```
Never hardcode your account seed in production code. Use secure environment variables or a secrets manager.
## React Integration
For React applications, the Miden team provides dedicated hooks via the `use-miden-para-react` package:
```bash
yarn add use-miden-para-react
```
## Scaffolding a New Project
To quickly bootstrap a new project with Miden and Para integration, use the scaffolding tool:
```bash
npx create-miden-para-react my-miden-app
cd my-miden-app
yarn install
yarn dev
```
This creates a Vite-based React TypeScript project with Para and Miden pre-configured.
## Features In Progress
Miden is currently on testnet. The following features are being developed:
### On-Ramps
The Miden team is working on on-ramp solutions to allow users to fund their Miden accounts directly. This feature will be available as Miden progresses toward mainnet.
## Resources
- [Miden Website](https://miden.xyz/)
- [Miden Documentation](https://docs.polygon.technology/miden/)
- [miden-para GitHub Repository](https://github.com/0xMiden/miden-para)
## Next Steps
Learn more about Para's Web SDK features and configuration options
Explore Para's React SDK for building web applications
## Related Walkthroughs
Intermediate · 30 min · Confidential payment flows
Intermediate · 25 min · Micropayments over HTTP
Intermediate · 25 min · Large-scale wallet generation
# Automated Migration to Para with MCP
Source: https://docs.getpara.com/v3/walkthroughs/migration-mcp
The [Para Migration MCP Server](https://github.com/getpara/Para-Migration-MCP) is an AI-powered Model Context Protocol (MCP) server that automates wallet provider migrations to Para. Instead of manually rewriting providers, hooks, imports, and configuration, you can run the migration through your AI coding assistant and have it handle the entire process atomically — all changes succeed together or roll back automatically.
It works with Claude Code, Cursor, and Claude Desktop, and supports migrations from Privy, Reown (AppKit), Web3Modal, and WalletConnect.
## Supported Migration Paths
| Source Provider | Strategy | Description |
|---|---|---|
| Privy | `privy-to-para` | Replaces `PrivyProvider`, `usePrivy`, `useWallets`, and related hooks |
| Reown / AppKit | `reown-to-para` | Replaces `AppKit`, `useAppKit`, `useAppKitAccount`, and related hooks |
| Web3Modal | `web3modal-to-para` | Replaces `Web3Modal`, `useWeb3Modal`, `useWeb3ModalAccount`, and related hooks |
| WalletConnect | `walletconnect-to-para` | Replaces WalletConnect provider and connector configuration |
## Prerequisites
Before starting, make sure you have:
- A Para API key from the [Developer Portal](https://developer.getpara.com/)
- Node.js 18+ installed
- An existing project using one of the supported wallet providers above
- An AI tool that supports MCP: [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://www.cursor.com/), or [Claude Desktop](https://claude.ai/download)
## Installation
Add the Migration MCP server to your AI tool of choice:
Run the following command in your terminal:
```bash
claude mcp add para-migration -- npx -y para-migration-mcp
```
Restart Claude Code to pick up the new server.
Add the following to your `.cursor/mcp.json` file (create it if it doesn't exist):
```json .cursor/mcp.json
{
"mcpServers": {
"para-migration": {
"command": "npx",
"args": ["-y", "para-migration-mcp"]
}
}
}
```
Restart Cursor to pick up the new server.
Add the following to your Claude Desktop configuration file:
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
```json claude_desktop_config.json
{
"mcpServers": {
"para-migration": {
"command": "npx",
"args": ["-y", "para-migration-mcp"]
}
}
}
```
Restart Claude Desktop to pick up the new server.
## Step-by-Step Migration
Once the MCP server is connected, walk through these steps in your AI tool's chat interface. Each step corresponds to an MCP tool that runs automatically behind the scenes.
Start by asking your AI assistant to analyze your project:
```text
Analyze my project at ./my-app for wallet provider usage
```
This runs the `analyze_project` tool, which scans your project's dependencies, imports, provider components, hooks, and styles. It returns:
- The detected wallet provider (Privy, Reown, Web3Modal, or WalletConnect)
- The recommended migration strategy
- A summary of all provider-related code found (hooks, providers, imports)
If your project uses a non-standard structure, you can also specify the path to your `package.json` directly.
Para maintains full [Wagmi](https://wagmi.sh/) compatibility, so your existing Wagmi hooks continue to work after migration. Verify this by asking:
```text
Check Wagmi hook compatibility for my project at ./my-app
```
This runs `check_compatibility` and reports which Wagmi hooks are in use and whether they're compatible with Para's provider.
Before making any changes, preview what the migration will do:
```text
Run a dry run migration from Privy to Para for my project at ./my-app
```
This runs `execute_atomic_migration` with `dryRun: true`. It returns a detailed list of every operation that **would** be performed — dependency removals, import replacements, provider swaps, hook updates, and CSS additions — without actually modifying any files.
Review the output to make sure you're comfortable with the changes.
When you're ready, execute the migration:
```text
Execute the atomic migration from Privy to Para for my project at ./my-app
```
This runs `execute_atomic_migration` with `dryRun: false`. The migration is **atomic** — either all operations succeed together, or everything rolls back to the original state. You'll never be left with a half-migrated project.
The tool will:
1. Remove old wallet provider imports
2. Add Para SDK imports
3. Replace provider components with `ParaProvider`
4. Update hook usages (e.g., `usePrivy` becomes `useAccount`)
5. Add Para CSS imports to your entry point
Make sure your project is committed to version control before running the migration, so you can easily review the diff afterward.
After the migration completes, run validation to catch the issues that cause 90% of migration failures:
```text
Validate my Para migration at ./my-app
```
This runs `validate_para_migration` and checks for five critical issues:
1. **Modal ownership** — use the modal embedded in ``, or disable it before rendering your own `