Documentation Index
Fetch the complete documentation index at: https://docs.getpara.com/llms.txt
Use this file to discover all available pages before exploring further.
Para
Non-custodial embedded wallet infrastructure using MPC for building embedded wallet integrations across web, mobile, and server platforms supporting EVM, Solana, and Cosmos chains.
Para SDK Implementation Supplement
Concrete build-time details: packages, initialization, authentication, signing, connectors, account abstraction, and configuration for building Para integrations.
1. SDK Package Matrix
All @getpara/* packages must use the same version as each other. Always pin every @getpara/* dependency to the same release to avoid mismatches.
Core SDKs
| Package | Use When |
|---|
@getpara/react-sdk | React/Next.js apps with ParaProvider + hooks + ParaModal |
@getpara/react-sdk-lite | React apps using Para only through a connector (Wagmi/Graz) - lighter bundle, exports ParaWeb |
@getpara/web-sdk | Non-React web frameworks (Vue, Svelte) - exports ParaWeb imperative class |
@getpara/server-sdk | Node.js/Bun/Deno server-side - exports Para (aliased as ParaServer) |
@getpara/react-native-wallet | React Native / Expo mobile apps |
Signing Integration Packages
| Package | Web3 Library | Companion Deps |
|---|
@getpara/viem-v2-integration | Viem 2.x | viem@^2.27.0 |
@getpara/ethers-v6-integration | Ethers 6.x | ethers@^6.13.0 |
@getpara/ethers-v5-integration | Ethers 5.x | ethers@^5.8.0 |
@getpara/solana-web3.js-v1-integration | Solana Web3.js 1.x | @solana/web3.js@^1.98.0 |
@getpara/solana-signers-v2-integration | Solana Signers 2.x | @solana/web3.js@^1.98.0 |
@getpara/cosmjs-v0-integration | CosmJS 0.34+ | @cosmjs/stargate@^0.34.0, @cosmjs/proto-signing@^0.34.0 |
Connector Packages
| Package | Connector Ecosystem | Companion Deps |
|---|
@getpara/wagmi-v2-integration | Wagmi 2.x / 3.x | wagmi@^2.15.0, viem@^2.27.0, @tanstack/react-query@^5.0.0 |
@getpara/rainbowkit-wallet | RainbowKit | @rainbow-me/rainbowkit@^2.0.0, wagmi, viem |
@getpara/graz-integration | Graz (Cosmos) | graz@^0.4.1 |
Wallet Connector Packages (for bulk pregen)
| Package | Purpose |
|---|
@getpara/evm-wallet-connectors | EVM external wallet connections |
@getpara/solana-wallet-connectors | Solana external wallet connections |
@getpara/cosmos-wallet-connectors | Cosmos external wallet connections |
Account Abstraction Companion Deps
| AA Provider | Key Packages |
|---|
| Alchemy (ERC-4337) | @aa-sdk/core, @account-kit/infra, @account-kit/smart-contracts |
| Alchemy (EIP-7702) | Same as above, uses createModularAccountV2Client with mode: "7702" |
| ZeroDev (ERC-4337) | @zerodev/sdk, @zerodev/ecdsa-validator |
| ZeroDev (EIP-7702) | Same packages, uses create7702KernelAccount + KERNEL_V3_3 |
| Gelato | @gelatonetwork/smartwallet |
| Thirdweb | thirdweb |
| Rhinestone | @rhinestone/sdk |
| Porto | porto |
Required Companion Dependency
All React examples require:
@tanstack/react-query@^5.0.0
2. Initialization Patterns
React / Next.js (ParaProvider)
// src/components/ParaProvider.tsx
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Environment, ParaProvider as ParaSDKProvider } from "@getpara/react-sdk";
const API_KEY = process.env.NEXT_PUBLIC_PARA_API_KEY ?? "";
const ENVIRONMENT = (process.env.NEXT_PUBLIC_PARA_ENVIRONMENT as Environment) || Environment.BETA;
if (!API_KEY) {
throw new Error("API key is not defined. Please set NEXT_PUBLIC_PARA_API_KEY in your environment variables.");
}
const queryClient = new QueryClient();
export function ParaProvider({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<ParaSDKProvider
paraClientConfig={{
apiKey: API_KEY,
env: ENVIRONMENT,
}}
config={{ appName: "My App" }}
paraModalConfig={{
disableEmailLogin: false,
disablePhoneLogin: false,
authLayout: ["AUTH:FULL", "EXTERNAL:FULL"],
oAuthMethods: ["APPLE", "DISCORD", "FACEBOOK", "FARCASTER", "GOOGLE", "TWITTER"],
onRampTestMode: true,
theme: {
foregroundColor: "#222222",
backgroundColor: "#FFFFFF",
accentColor: "#888888",
darkForegroundColor: "#EEEEEE",
darkBackgroundColor: "#111111",
darkAccentColor: "#AAAAAA",
mode: "light",
borderRadius: "none",
font: "Inter",
},
logo: "/logo.svg",
recoverySecretStepEnabled: true,
twoFactorAuthEnabled: false,
}}>
{children}
</ParaSDKProvider>
</QueryClientProvider>
);
}
The config prop accepts:
interface ParaProviderConfig {
appName: string; // required
disableAutoSessionKeepAlive?: boolean; // disable automatic session refresh
disableEmbeddedModal?: boolean; // set true only when rendering your own ParaModal
rpcUrl?: string;
farcasterMiniAppConfig?: FarcasterMiniAppConfig;
}
Wrap your app root (e.g., layout.tsx):
import { ParaProvider } from "@/components/ParaProvider";
export default function RootLayout({ children }) {
return <html><body><ParaProvider>{children}</ParaProvider></body></html>;
}
ParaProvider also accepts an optional callbacks prop for event handling:
type Callbacks = {
onLogout?: (event) => void;
onLogin?: (event) => void;
onAccountSetup?: (event) => void;
onAccountCreation?: (event) => void;
onSignMessage?: (event) => void;
onSignTransaction?: (event) => void;
onWalletsChange?: (event) => void;
onWalletCreated?: (event) => void;
onPregenWalletClaimed?: (event) => void;
};
Vue / Svelte (ParaWeb - imperative)
// src/lib/para.ts
import { Environment, ParaWeb } from "@getpara/web-sdk";
const API_KEY = import.meta.env.VITE_PARA_API_KEY;
const ENVIRONMENT = (import.meta.env.VITE_PARA_ENVIRONMENT as Environment) || Environment.BETA;
if (!API_KEY) {
throw new Error("API key is not defined. Please set VITE_PARA_API_KEY in your environment variables.");
}
export const para = new ParaWeb(ENVIRONMENT, API_KEY);
Server (ParaServer)
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
const PARA_API_KEY = process.env.PARA_API_KEY;
const PARA_ENVIRONMENT = (process.env.PARA_ENVIRONMENT as Environment) || Environment.BETA;
const para = new ParaServer(PARA_ENVIRONMENT, PARA_API_KEY);
React Native / Expo (ParaMobile)
import ParaMobile, { Environment } from "@getpara/react-native-wallet";
const para = new ParaMobile(
Environment.BETA,
process.env.EXPO_PUBLIC_PARA_API_KEY,
undefined,
{ disableWorkers: true }
);
The { disableWorkers: true } option is required for React Native because Web Workers are not available.
Chrome Extension (storage overrides)
import { ParaWeb } from "@getpara/react-sdk";
import { chromeStorageOverrides } from "../chrome-storage";
export const para = new ParaWeb(ENVIRONMENT, API_KEY, {
...chromeStorageOverrides,
useStorageOverrides: true,
});
// Shared init promise - await in any script that needs Para
export const paraReady = para.init();
The chromeStorageOverrides object maps chrome.storage.local / chrome.storage.session to replace localStorage / sessionStorage which are not available in extension service workers. See examples-hub/web/with-chrome-extension/src/lib/chrome-storage.ts for the full implementation.
3. Environment Variables
Naming Conventions by Framework
| 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
# Required - Para
PARA_API_KEY=your_api_key
PARA_ENVIRONMENT=BETA # or PRODUCTION
# Client-side variants (pick one based on framework)
NEXT_PUBLIC_PARA_API_KEY=your_api_key
NEXT_PUBLIC_PARA_ENVIRONMENT=BETA
VITE_PARA_API_KEY=your_api_key
VITE_PARA_ENVIRONMENT=BETA
EXPO_PUBLIC_PARA_API_KEY=your_api_key
# Server-side pregen encryption
ENCRYPTION_KEY=a_32_byte_string_exactly_this_l # Must be exactly 32 bytes
# Account Abstraction
ALCHEMY_API_KEY=your_alchemy_key
ALCHEMY_GAS_POLICY_ID=your_policy_id
ALCHEMY_RPC_URL=https://arb-sepolia.g.alchemy.com/v2/your_key
ZERODEV_PROJECT_ID=your_zerodev_project_id
ZERODEV_BUNDLER_RPC=https://rpc.zerodev.app/api/v2/bundler/...
ZERODEV_PAYMASTER_RPC=https://rpc.zerodev.app/api/v2/paymaster/...
ZERODEV_ARBITRUM_SEPOLIA_RPC=https://rpc.zerodev.app/api/v2/rpc/...
# REST API
PARA_REST_BASE_URL=https://api.beta.getpara.com # or https://api.getpara.com
4. Authentication Flows
Email Auth (React hooks pattern)
import {
useSignUpOrLogIn,
useWaitForLogin,
useWaitForWalletCreation,
type AuthStateVerify,
} from "@getpara/react-sdk";
// In your hook/component:
const { signUpOrLogIn, isPending: isSigningUp } = useSignUpOrLogIn();
const { waitForLogin, isPending: isWaitingForLogin } = useWaitForLogin();
const { waitForWalletCreation, isPending: isWaitingForWallet } = useWaitForWalletCreation();
const shouldCancel = useRef(false);
// Step 1: Submit email
signUpOrLogIn(
{ auth: { email } },
{
onSuccess: (authState) => {
if (authState?.stage === "verify") {
const verifyState = authState as AuthStateVerify;
// verifyState.loginUrl -> open in iframe or popup for passkey verification
// verifyState.nextStage -> "signup" (new user) or "login" (returning user)
const isNewUser = verifyState.nextStage === "signup";
handleAuthComplete(isNewUser);
}
},
onError: (err) => { /* handle error */ },
}
);
// Step 2: Wait for auth completion
function handleAuthComplete(isNewUser: boolean) {
if (isNewUser) {
waitForWalletCreation(
{ isCanceled: () => shouldCancel.current },
{ onSuccess: () => { /* user is logged in with wallet */ } }
);
} else {
waitForLogin(
{ isCanceled: () => shouldCancel.current },
{ onSuccess: () => { /* user is logged in */ } }
);
}
}
The loginUrl from Step 1 must be displayed to the user in an iframe or popup - it’s the passkey verification page hosted by Para.
Email Auth (Imperative / Svelte / Vue)
import { para } from "@/lib/para";
// Step 1: Submit email
const authState = await para.signUpOrLogIn({ auth: { email } });
if (authState.stage === "verify" && authState.loginUrl) {
const isNewUser = authState.nextStage === "signup";
// Show authState.loginUrl to user in iframe/popup
// Step 2: Wait for completion
if (isNewUser) {
await para.waitForWalletCreation({ isCanceled: () => shouldCancel });
} else {
const result = await para.waitForLogin({ isCanceled: () => shouldCancel });
if (result.needsWallet) {
await para.waitForWalletCreation({ isCanceled: () => shouldCancel });
}
}
}
OAuth Auth (React hooks)
import {
useVerifyOAuth,
useVerifyFarcaster,
useWaitForLogin,
useWaitForWalletCreation,
type TOAuthMethod,
} from "@getpara/react-sdk";
const { verifyOAuth } = useVerifyOAuth();
const { verifyFarcaster } = useVerifyFarcaster();
// Standard OAuth (Google, Apple, Discord, Facebook, Twitter)
verifyOAuth(
{
method: "GOOGLE", // TOAuthMethod excluding "TELEGRAM" | "FARCASTER"
onOAuthUrl: (url) => {
window.open(url, "oauth", "popup=true");
},
isCanceled: () => shouldCancel.current || !!popupWindow.current?.closed,
},
{
onSuccess: (authState) => {
if (authState.stage === "done") {
handleAuthComplete(authState.isNewUser);
}
},
}
);
// Farcaster
verifyFarcaster(
{
onConnectUri: (uri) => {
window.open(uri, "farcaster", "popup=true");
},
isCanceled: () => shouldCancel.current || !!popupWindow.current?.closed,
},
{
onSuccess: (authState) => {
if (authState.stage === "done") {
handleAuthComplete(authState.isNewUser);
}
},
}
);
Verify iframe listener
When showing the loginUrl in an iframe, listen for the close message:
useEffect(() => {
const portalBase = "https://app.beta.getpara.com"; // or https://app.getpara.com
const handleMessage = (event: MessageEvent) => {
if (!event.origin.startsWith(portalBase)) return;
if (event.data?.type === "CLOSE_WINDOW" && event.data.success) {
setVerifyUrl(null);
}
};
window.addEventListener("message", handleMessage);
return () => window.removeEventListener("message", handleMessage);
}, []);
5. Signing Integration Code
Viem v2
import { createParaAccount, createParaViemClient } from "@getpara/viem-v2-integration";
import { http, parseEther, parseGwei } from "viem";
import { sepolia } from "viem/chains";
// From client (using useClient hook):
import { useClient } from "@getpara/react-sdk";
const client = useClient(); // returns the Para client instance
// Create account + client
const viemParaAccount = createParaAccount(para); // para = ParaServer or client
const viemClient = createParaViemClient(para, {
account: viemParaAccount,
chain: sepolia,
transport: http("https://ethereum-sepolia-rpc.publicnode.com"),
});
// Sign transaction
const request = await viemClient.prepareTransactionRequest({
account: viemParaAccount,
to: "0x...",
value: parseEther("0.0001"),
gas: BigInt(21000),
maxFeePerGas: parseGwei("20"),
maxPriorityFeePerGas: parseGwei("3"),
chain: sepolia,
});
const signedTx = await viemClient.signTransaction(request);
Ethers v6
import { ParaEthersSigner } from "@getpara/ethers-v6-integration";
import { ethers } from "ethers";
const provider = new ethers.JsonRpcProvider("https://ethereum-sepolia-rpc.publicnode.com");
const signer = new ParaEthersSigner(para, provider as ethers.Provider);
const address = await signer.getAddress();
const tx = {
to: address,
value: ethers.parseEther("0.0001"),
nonce: await provider.getTransactionCount(address),
gasLimit: 21000,
gasPrice: (await provider.getFeeData()).gasPrice,
};
await signer.signTransaction(tx);
Ethers v5
import { ParaEthersV5Signer } from "@getpara/ethers-v5-integration";
import { ethers } from "ethers";
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const signer = new ParaEthersV5Signer(client, provider); // client from useClient()
await signer.signMessage("Hello, Para!");
await signer.sendTransaction(tx);
Solana Web3.js v1
import { ParaSolanaWeb3Signer } from "@getpara/solana-web3.js-v1-integration";
import { Connection, Transaction, SystemProgram, LAMPORTS_PER_SOL } from "@solana/web3.js";
const connection = new Connection("https://api.testnet.solana.com");
const signer = new ParaSolanaWeb3Signer(para, connection);
const { blockhash } = await connection.getLatestBlockhash();
const tx = new Transaction();
tx.recentBlockhash = blockhash;
tx.feePayer = signer.sender; // PublicKey from Para wallet
tx.add(
SystemProgram.transfer({
fromPubkey: signer.sender,
toPubkey: signer.sender,
lamports: LAMPORTS_PER_SOL / 1000,
})
);
await signer.signTransaction(tx);
CosmJS
import { ParaProtoSigner } from "@getpara/cosmjs-v0-integration";
import { SigningStargateClient } from "@cosmjs/stargate";
const signer = new ParaProtoSigner(para, "cosmos"); // second arg is bech32 prefix
const stargateClient = await SigningStargateClient.connectWithSigner(
"https://rpc-rs.cosmos.nodestake.top/",
signer
);
const fee = { amount: [{ denom: "uatom", amount: "500" }], gas: "200000" };
await stargateClient.sign(
signer.address,
[{ typeUrl: "/cosmos.bank.v1beta1.MsgSend", value: { fromAddress: signer.address, toAddress: "cosmos1...", amount: [{ denom: "uatom", amount: "1000" }] } }],
fee,
"Signed with Para"
);
6. Wallet Pre-generation (Server-Side)
Create pre-generated wallet
import { Para as ParaServer, Environment } from "@getpara/server-sdk";
import { encrypt } from "./encryption-utils";
import { setKeyShareInDB } from "./keySharesDB";
const para = new ParaServer(PARA_ENVIRONMENT, PARA_API_KEY);
// Check if wallet already exists
const walletExists = await para.hasPregenWallet({ pregenId: { email } });
// Create wallets for all chain types
const wallets = await para.createPregenWalletPerType({
types: ["EVM", "SOLANA", "COSMOS"],
pregenId: { email },
});
// Get and encrypt the user share
const keyShare = para.getUserShare();
const encryptedKeyShare = await encrypt(keyShare);
await setKeyShareInDB(email, encryptedKeyShare);
Restore user share for signing
const keyShare = await getKeyShareInDB(email);
const decryptedKeyShare = await decrypt(keyShare);
await para.setUserShare(decryptedKeyShare);
// Now para is ready for signing with any integration
AES-GCM Encryption Pattern
// encryption-utils.ts
const ALGORITHM = "AES-GCM";
const IV_LENGTH = 12;
// ENCRYPTION_KEY env var must be exactly 32 bytes
async function importSecretKey(keyString: string): Promise<CryptoKey> {
const keyBuffer = Buffer.from(keyString, "utf-8");
return await crypto.subtle.importKey("raw", keyBuffer, { name: ALGORITHM }, false, ["encrypt", "decrypt"]);
}
export async function encrypt(text: string): Promise<string> {
const cryptoKey = await importSecretKey(process.env.ENCRYPTION_KEY);
const iv = crypto.getRandomValues(new Uint8Array(IV_LENGTH));
const encoded = new TextEncoder().encode(text);
const encrypted = await crypto.subtle.encrypt({ name: ALGORITHM, iv }, cryptoKey, encoded);
return `${Buffer.from(iv).toString("base64")}:${Buffer.from(encrypted).toString("base64")}`;
}
export async function decrypt(encryptedText: string): Promise<string> {
const [ivBase64, dataBase64] = encryptedText.split(":");
const iv = new Uint8Array(Buffer.from(ivBase64, "base64"));
const data = Buffer.from(dataBase64, "base64");
const cryptoKey = await importSecretKey(process.env.ENCRYPTION_KEY);
const decrypted = await crypto.subtle.decrypt({ name: ALGORITHM, iv }, cryptoKey, data);
return new TextDecoder().decode(decrypted);
}
SQLite Storage Schema
// keySharesDB.ts - uses `sqlite` + `sqlite3` packages
import { open } from "sqlite";
import sqlite3 from "sqlite3";
const db = await open({ filename: "keyShares.db", driver: sqlite3.Database });
await db.exec(`
CREATE TABLE IF NOT EXISTS keyShares (
email TEXT PRIMARY KEY NOT NULL,
keyShare TEXT NOT NULL
)
`);
// Get
const row = await db.get("SELECT keyShare FROM keyShares WHERE email = ?", [email]);
// Upsert
await db.run(
"INSERT INTO keyShares (email, keyShare) VALUES (?, ?) ON CONFLICT(email) DO UPDATE SET keyShare = excluded.keyShare",
[email, encryptedKeyShare]
);
7. Account Abstraction Patterns
Alchemy ERC-4337
import { alchemy, arbitrumSepolia } from "@account-kit/infra";
import { WalletClientSigner } from "@aa-sdk/core";
import { createModularAccountAlchemyClient } from "@account-kit/smart-contracts";
import { createParaAccount, createParaViemClient } from "@getpara/viem-v2-integration";
const viemParaAccount = createParaAccount(para);
const viemClient = createParaViemClient(para, {
account: viemParaAccount,
chain: arbitrumSepolia,
transport: http(ALCHEMY_RPC_URL),
});
const walletClientSigner = new WalletClientSigner(viemClient, "para");
const alchemyClient = await createModularAccountAlchemyClient({
transport: alchemy({ rpcUrl: ALCHEMY_RPC_URL }),
chain: arbitrumSepolia,
signer: walletClientSigner,
policyId: ALCHEMY_GAS_POLICY_ID,
});
const result = await alchemyClient.sendUserOperation({ uo: batchCalls });
await alchemyClient.waitForUserOperationTransaction(result);
Alchemy EIP-7702
import { createModularAccountV2Client } from "@account-kit/smart-contracts";
import { createParaAccount } from "@getpara/viem-v2-integration";
import type { SmartAccountSigner, LocalAccount } from "@aa-sdk/core";
const viemParaAccount = createParaAccount(para);
// Wrap as SmartAccountSigner
const paraSigner: SmartAccountSigner<LocalAccount> = {
signerType: "para",
inner: viemParaAccount,
getAddress: async () => viemParaAccount.address,
signMessage: async (message) => viemParaAccount.signMessage({ message }),
signTypedData: async (typedData) => viemParaAccount.signTypedData(typedData),
signAuthorization: async (auth) => viemParaAccount.signAuthorization(auth),
};
const alchemyClient = await createModularAccountV2Client({
mode: "7702",
transport: alchemy({ rpcUrl: ALCHEMY_RPC_URL }),
chain: arbitrumSepolia,
signer: paraSigner,
policyId: ALCHEMY_GAS_POLICY_ID,
});
ZeroDev ERC-4337
import { signerToEcdsaValidator } from "@zerodev/ecdsa-validator";
import { createKernelAccount, createKernelAccountClient, createZeroDevPaymasterClient } from "@zerodev/sdk";
import { getEntryPoint, KERNEL_V3_1 } from "@zerodev/sdk/constants";
const viemParaAccount = createParaAccount(para);
const viemClient = createParaViemClient(para, { account: viemParaAccount, chain: arbitrumSepolia, transport: http(ZERODEV_RPC_URL) });
const publicClient = createPublicClient({ chain: arbitrumSepolia, transport: http(ZERODEV_RPC_URL) });
const entryPoint = getEntryPoint("0.7");
const ecdsaValidator = await signerToEcdsaValidator(viemClient, {
signer: viemParaAccount,
entryPoint,
kernelVersion: KERNEL_V3_1,
});
const kernelAccount = await createKernelAccount(publicClient, {
plugins: { sudo: ecdsaValidator },
entryPoint,
kernelVersion: KERNEL_V3_1,
});
const kernelClient = createKernelAccountClient({
account: kernelAccount,
chain: arbitrumSepolia,
bundlerTransport: http(ZERODEV_BUNDLER_RPC),
paymaster: {
getPaymasterData: (userOp) =>
createZeroDevPaymasterClient({ chain: arbitrumSepolia, transport: http(ZERODEV_PAYMASTER_RPC) })
.sponsorUserOperation({ userOperation: userOp }),
},
});
const hash = await kernelClient.sendUserOperation({ callData: await kernelClient.account.encodeCalls(calls) });
await kernelClient.waitForUserOperationReceipt({ hash, timeout: 30000 });
ZeroDev EIP-7702
import { create7702KernelAccount, create7702KernelAccountClient } from "@zerodev/ecdsa-validator";
import { KERNEL_V3_3 } from "@zerodev/sdk/constants";
const kernelAccount = await create7702KernelAccount(publicClient, {
signer: viemParaAccount,
entryPoint: getEntryPoint("0.7"),
kernelVersion: KERNEL_V3_3,
});
const kernelClient = create7702KernelAccountClient({
account: kernelAccount,
chain: arbitrumSepolia,
bundlerTransport: http(ZERODEV_BUNDLER_RPC),
paymaster: createZeroDevPaymasterClient({ chain: arbitrumSepolia, transport: http(ZERODEV_PAYMASTER_RPC) }),
client: publicClient,
});
const hash = await kernelClient.sendUserOperation({ calls });
Gelato ERC-4337
import { accounts, createGelatoSmartWalletClient } from "@gelatonetwork/smartwallet";
const viemParaAccount = createParaAccount(para);
const viemClient = createParaViemClient(para, { account: viemParaAccount, chain: sepolia, transport: http() });
const account = await accounts.kernel({ eip7702: false, signer: viemClient });
const gelatoClient = createGelatoSmartWalletClient({ account, apiKey: GELATO_API_KEY });
const result = await gelatoClient.execute({
payment: { type: "sponsored" },
calls: [{ to: "0x...", value: 0n, data: "0x" }],
});
await result.wait();
Gelato EIP-7702
import { gelato } from "@gelatonetwork/smartwallet/accounts";
import { createGelatoSmartWalletClient } from "@gelatonetwork/smartwallet";
const account = await gelato({ signer: viemClient }); // Different subpath from 4337's accounts.kernel()
const gelatoClient = createGelatoSmartWalletClient({ account, apiKey: GELATO_API_KEY });
// Same execute pattern as 4337
Thirdweb ERC-4337
import { createThirdwebClient, sendTransaction, prepareTransaction } from "thirdweb";
import { viemAdapter, smartWallet } from "thirdweb/wallets";
import { sepolia } from "thirdweb/chains";
const thirdwebClient = createThirdwebClient({ clientId: THIRDWEB_CLIENT_ID });
const personalAccount = viemAdapter.walletClient.fromViem({ walletClient: viemClient });
const wallet = smartWallet({ chain: sepolia, sponsorGas: true });
const smartAccount = await wallet.connect({ client: thirdwebClient, personalAccount });
const tx = prepareTransaction({ to: "0x...", value: 0n, chain: sepolia, client: thirdwebClient });
await sendTransaction({ account: smartAccount, transaction: tx });
Porto EIP-7702
Porto operates on Base Sepolia with a unique EOA-to-smart-account upgrade flow. See examples-hub/web/with-react-nextjs/aa-porto-7702/ for the full implementation using Account.from(), Key.createSecp256k1(), and RelayActions.prepareUpgradeAccount().
Rhinestone ERC-4337
Rhinestone provides cross-chain account abstraction using @rhinestone/sdk. See examples-hub/web/with-react-nextjs/aa-rhinestone-4337/ for cross-chain USDC transfers (Arbitrum->Base) with sponsored transactions.
8. Connector Integrations
Wagmi Connector
// src/config/wagmi.ts
import { paraConnector } from "@getpara/wagmi-v2-integration";
import { ParaWeb } from "@getpara/react-sdk-lite"; // or @getpara/react-sdk
import { createConfig, http, cookieStorage, createStorage } from "wagmi";
import { sepolia } from "wagmi/chains";
// Initialize ParaWeb (not ParaProvider - connector manages its own UI)
const para = new ParaWeb(Environment.BETA, API_KEY);
const connector = paraConnector({
para,
appName: "My App",
chains: [sepolia],
queryClient,
authLayout: ["AUTH:FULL", "EXTERNAL:FULL"],
oAuthMethods: ["APPLE", "DISCORD", "FACEBOOK", "FARCASTER", "GOOGLE", "TWITTER"],
disableEmailLogin: false,
disablePhoneLogin: false,
logo: "/logo.svg",
onRampTestMode: true,
recoverySecretStepEnabled: true,
twoFactorAuthEnabled: false,
theme: {
foregroundColor: "#222222",
backgroundColor: "#FFFFFF",
accentColor: "#888888",
mode: "light",
borderRadius: "none",
font: "Inter",
},
});
export const wagmiConfig = createConfig({
chains: [sepolia],
connectors: [connector, injected(), metaMask(), coinbaseWallet()],
ssr: true,
storage: createStorage({ storage: cookieStorage }),
transports: { [sepolia.id]: http(RPC_URL) },
});
Important: The Wagmi connector uses @getpara/react-sdk-lite (not the full @getpara/react-sdk) to avoid bundle bloat, since the connector provides its own modal.
RainbowKit
// src/client/wagmi.ts
import { getParaWallet } from "@getpara/rainbowkit-wallet";
import { connectorsForWallets } from "@rainbow-me/rainbowkit";
import { createConfig, http } from "wagmi";
import { sepolia } from "wagmi/chains";
const paraWallet = getParaWallet({
para, // ParaWeb instance
chains: [sepolia],
appName: "My App",
// Same modal config options as paraConnector: oAuthMethods, theme, etc.
});
const connectors = connectorsForWallets(
[{ groupName: "Para", wallets: [paraWallet] }],
{ appName: "My App", projectId: WALLETCONNECT_PROJECT_ID }
);
export const wagmiConfig = createConfig({
chains: [sepolia],
connectors,
transports: { [sepolia.id]: http(RPC_URL) },
});
// Providers: WagmiProvider + QueryClientProvider + RainbowKitProvider
// Then use wagmi hooks: useSignMessage(), useSendTransaction(), etc.
Graz (Cosmos)
// src/context/Provider.tsx
import { ParaGrazConnector } from "@getpara/graz-integration";
import { ParaWeb, Environment } from "@getpara/react-sdk-lite";
import { GrazProvider, defineChainInfo, type ParaGrazConfig } from "graz";
const para = new ParaWeb(Environment.BETA, API_KEY);
const cosmosTestnet = defineChainInfo({
chainId: "provider",
chainName: "Cosmos ICS Provider Testnet",
rpc: "https://rpc.provider-sentry-01.ics-testnet.polypore.xyz",
rest: "https://rest.provider-sentry-01.ics-testnet.polypore.xyz",
bip44: { coinType: 118 },
bech32Config: { bech32PrefixAccAddr: "cosmos", /* ... other prefixes */ },
stakeCurrency: { coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6 },
currencies: [{ coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6 }],
feeCurrencies: [{ coinDenom: "ATOM", coinMinimalDenom: "uatom", coinDecimals: 6 }],
});
const paraConfig: ParaGrazConfig = {
paraWeb: para as ParaGrazConfig["paraWeb"],
connectorClass: ParaGrazConnector, // Note: connectorClass, not connector instance
modalProps: { appName: "My App" },
queryClient: queryClient,
};
// Wrap your app:
<GrazProvider
grazOptions={{
chains: [cosmosTestnet],
paraConfig,
}}>
{children}
</GrazProvider>
Note: defineChainInfo and ParaGrazConfig type are imported from graz, not from @getpara/graz-integration.
9. Session Management
Check and keep alive
// Client-side (React hooks)
import { useAccount, useKeepSessionAlive } from "@getpara/react-sdk";
const { isConnected } = useAccount();
// Imperative
const isActive = await para.isSessionActive();
if (isActive) {
await para.keepSessionAlive();
}
Export / Import session (server transfer)
// Client: export session string
const sessionString = await para.exportSession();
// Send sessionString to server via API call
// Server: import session
const serverPara = new ParaServer(PARA_ENVIRONMENT, PARA_API_KEY);
await serverPara.importSession(sessionString);
// Server can now sign on behalf of the user
const isActive = await serverPara.isSessionActive();
Issue JWT
// Client-side
import { useIssueJwt } from "@getpara/react-sdk";
const { issueJwt } = useIssueJwt();
const { token, keyId } = await issueJwt();
// Send `token` to your server for verification
// Imperative
const { token, keyId } = await para.issueJWT();
10. ParaModal Configuration
The paraModalConfig prop on <ParaSDKProvider> (or the config object for paraConnector) accepts:
interface ParaModalConfig {
// Authentication layout
authLayout?: ("AUTH:FULL" | "AUTH:CONDENSED" | "EXTERNAL:FULL" | "EXTERNAL:CONDENSED")[];
// Email / Phone login toggles
disableEmailLogin?: boolean;
disablePhoneLogin?: boolean;
defaultAuthIdentifier?: string; // Pre-fill email/phone
// OAuth providers
oAuthMethods?: TOAuthMethod[];
// TOAuthMethod = "APPLE" | "DISCORD" | "FACEBOOK" | "FARCASTER" | "GOOGLE" | "TWITTER" | "TELEGRAM"
// Branding
logo?: string; // recommended 372x160px
// Theme
theme?: {
foregroundColor?: string;
backgroundColor?: string;
accentColor?: string;
darkForegroundColor?: string;
darkBackgroundColor?: string;
darkAccentColor?: string;
overlayBackground?: string;
mode?: "light" | "dark";
borderRadius?: "none" | "xs" | "sm" | "md" | "lg" | "xl" | "full";
font?: string;
oAuthLogoVariant?: "dark" | "light" | "default";
};
// Features
onRampTestMode?: boolean;
recoverySecretStepEnabled?: boolean;
twoFactorAuthEnabled?: boolean;
hideWallets?: boolean; // Hide wallet terminology
isGuestModeEnabled?: boolean; // Enable guest login
bareModal?: boolean; // No overlay backdrop
embeddedModal?: boolean; // Embedded styling
className?: string; // Custom CSS class
// Account linking
supportedAccountLinks?: ("EMAIL" | "PHONE" | "GOOGLE" | "EXTERNAL_WALLET")[];
// Step override
currentStepOverride?: ModalStepProp;
// Steps: AUTH_MAIN, AUTH_MORE, AWAITING_OAUTH, VERIFICATIONS,
// BIOMETRIC_CREATION, PASSWORD_CREATION, SECRET, AWAITING_WALLET_CREATION,
// ACCOUNT_MAIN, ACCOUNT_PROFILE, CHAIN_SWITCH,
// EX_WALLET_MORE, EX_WALLET_SELECTED,
// ADD_FUNDS_BUY, ADD_FUNDS_RECEIVE, ADD_FUNDS_WITHDRAW,
// SETUP_2FA, VERIFY_2FA
// Callbacks
onModalStepChange?: (value: any) => void;
onClose?: () => void;
// Advanced overrides
loginTransitionOverride?: (para: ParaWeb) => Promise<void>;
createWalletOverride?: (para: ParaWeb) => Promise<{ recoverySecret?: string; walletIds: any }>;
}
Note: Password and PIN screens render in an iframe and use the Developer Portal theme settings, not paraModalConfig.theme.
11. Mobile Setup Requirements
Required Native Modules (Expo)
{
"@craftzdog/react-native-buffer": "^6.1.0",
"@getpara/react-native-wallet": "<same version as other @getpara/* packages>",
"@peculiar/webcrypto": "^1.5.0",
"@react-native-async-storage/async-storage": "^2.2.0",
"react-native-keychain": "^10.0.0",
"react-native-modpow": "^1.1.0",
"react-native-passkey": "^3.3.2",
"react-native-quick-base64": "^2.2.0",
"react-native-quick-crypto": "^0.7.14",
"react-native-worklets": "^0.5.1",
"readable-stream": "^4.5.2"
}
Metro Config (polyfills)
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);
config.resolver.extraNodeModules = {
crypto: require.resolve('react-native-quick-crypto'),
buffer: require.resolve('@craftzdog/react-native-buffer'),
};
module.exports = config;
Babel Config
// babel.config.js
module.exports = function (api) {
api.cache(true);
return {
presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'],
plugins: ['react-native-worklets/plugin'],
};
};
iOS Passkey Setup
For passkey support on iOS, you must configure your app’s Associated Domains capability with a webcredentials: entry pointing to your domain. The domain must serve an apple-app-site-association file. See examples-hub/mobile/with-expo-one-click-login/ios/ for an example Xcode project configuration.
Key Init Option
Always pass { disableWorkers: true } when initializing ParaMobile - Web Workers are not available in React Native.
12. Vite Polyfill Configuration
Vue and Svelte projects using Vite need vite-plugin-node-polyfills because the Para SDK uses Node.js built-in modules (buffer, crypto, stream).
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue"; // or svelte()
import { nodePolyfills } from "vite-plugin-node-polyfills";
export default defineConfig({
plugins: [vue(), nodePolyfills()],
});
Install:
npm install -D vite-plugin-node-polyfills
This is required for @getpara/web-sdk. The @getpara/react-sdk with Next.js does not need this (Next.js handles polyfills via npx setup-para postinstall script).
13. REST API Direct Usage
For server-to-server integrations without the SDK, use the REST API directly.
Base URLs
| Environment | URL |
|---|
| Beta | https://api.beta.getpara.com |
| Production | https://api.getpara.com |
X-API-Key: your_api_key
X-Request-Id: <uuid> # optional, for request tracing
Content-Type: application/json
Endpoints
POST /v1/wallets - Create wallet
GET /v1/wallets/{walletId} - Get wallet details
POST /v1/wallets/{walletId}/sign-raw - Sign raw bytes (0x-prefixed hex)
Create Wallet Request
{
"type": "EVM",
"userIdentifier": "alice@example.com",
"userIdentifierType": "EMAIL",
"scheme": "DKLS"
}
type: "EVM" | "SOLANA" | "COSMOS"
userIdentifierType: "EMAIL" | "PHONE" | "CUSTOM_ID" | "GUEST_ID" | "TELEGRAM" | "DISCORD" | "TWITTER"
scheme: "DKLS" | "CGGMP" | "ED25519" (optional, defaults based on wallet type)
cosmosPrefix: string (optional, for Cosmos wallets)
Sign Raw Request
{
"data": "0xdeadbeef..."
}
Error Codes
| 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
const PARA_API_KEY = process.env.PARA_API_KEY;
const BASE_URL = process.env.PARA_REST_BASE_URL ?? "https://api.beta.getpara.com";
async function callPara<T>(path: string, options: { method?: string; body?: unknown } = {}): Promise<T> {
const { method = "GET", body } = options;
const headers: Record<string, string> = {
"X-API-Key": PARA_API_KEY,
"X-Request-Id": crypto.randomUUID(),
};
if (body) headers["Content-Type"] = "application/json";
const res = await fetch(`${BASE_URL}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
const data = await res.json();
if (!res.ok) throw new Error(`Para ${res.status}: ${JSON.stringify(data)}`);
return data as T;
}
14. React Hook Reference
CSS Import
ParaModal requires its stylesheet:
import "@getpara/react-sdk/styles.css";
Add this in your root layout or ParaProvider component.
ParaProvider renders an embedded ParaModal by default. Do not render a separate <ParaModal /> 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 (requires @getpara/viem-v2-integration as a dependency):
useViemClient({ address, walletClientConfig }) -> { viemClient } - returns a Viem WalletClient pre-configured with the Para signer
import { useAccount } from "@getpara/react-sdk";
import { useViemClient } from "@getpara/react-sdk/evm";
import { http } from "viem";
import { sepolia } from "viem/chains";
const { embedded } = useAccount();
const address = embedded?.wallets?.[0]?.address as `0x${string}`;
const { viemClient } = useViemClient({
address,
walletClientConfig: { chain: sepolia, transport: http() },
});
// viemClient.sendTransaction, viemClient.signMessage, etc.
This is the preferred way to use Viem in React - no need to import createParaAccount / createParaViemClient directly.
From @getpara/react-sdk/viem
useViemAccount({ address? }) -> { viemAccount: Account | null, isLoading } - lower-level access to the viem Account without creating a full WalletClient
From @getpara/react-sdk/solana
useSolanaSigner({ rpc, walletId? }) -> { solanaSigner: SignerWalletAdapter | null, isLoading } - for Solana Signers v2 integration
From @getpara/react-sdk/cosmos
useCosmjsProtoSigner({ prefix?, walletId?, messageSigningTimeoutMs? }) -> { protoSigner: OfflineDirectSigner | null, isLoading } - for CosmJS proto signing
useCosmjsAminoSigner({ prefix?, walletId?, messageSigningTimeoutMs? }) -> { aminoSigner: OfflineAminoSigner | null, isLoading } - for CosmJS amino signing
// CosmJS example using hooks
import { useCosmjsProtoSigner } from "@getpara/react-sdk/cosmos";
import { SigningStargateClient } from "@cosmjs/stargate";
const { protoSigner } = useCosmjsProtoSigner({ prefix: "cosmos" });
const stargateClient = await SigningStargateClient.connectWithSigner(RPC_URL, protoSigner);
15. Testing Credentials
Beta Environment
- Environment:
Environment.BETA / "BETA"
- Portal base URL:
https://app.beta.getpara.com
- API base URL:
https://api.beta.getpara.com
Test Email Pattern
Use any email ending in @test.getpara.com (e.g., dev@test.getpara.com, test1@test.getpara.com). Any OTP code works (e.g., 123456, 000000).
Test Phone Numbers
Use US numbers (+1) with format (area code)-555-xxxx (e.g., (425)-555-1234, (206)-555-9876). Any OTP code works.
User Limits
50 users per beta account. Delete test users via Developer Portal -> Users section. Users can only be deleted in BETA; in production, wallets are permanent.
Post-Install Setup
Many Next.js examples include a postinstall script:
"postinstall": "npx setup-para"
This configures Next.js webpack to handle Para SDK’s WASM and worker files. Always run it after npm install.
16. Examples Hub Directory Map
Web - Framework Setup
| 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 |