# Veridex Protocol — Full Documentation > This file contains the complete Veridex Protocol documentation in plain text, > optimized for AI/LLM consumption. Generated from the MDX source files. > > Docs site: https://docs.veridex.network > GitHub: https://github.com/veridex-protocol/ > llms.txt index: https://docs.veridex.network/llms.txt > Generated: 2026-04-16T19:35:33.902Z > Pages: 65 ## Table of Contents ### Home - Veridex Protocol (/home/index) ### Getting Started - Quick Start (/getting-started/quick-start) - Installation (/getting-started/installation) - Core Concepts (/getting-started/concepts) - FAQ (/getting-started/faq) ### Guides - Create Wallet (/guides/create-wallet) - Send Tokens (/guides/send-tokens) - Wallet Backup & Recovery (/guides/wallet-backup-recovery) - Cross-Domain Passkey Integration (/guides/cross-domain-passkeys) - Gasless Transactions (/guides/gasless-transactions) - Session Keys (/guides/session-keys) - Multi-Session Management (/guides/multi-session) - Spending Limits (/guides/spending-limits) - Balance Watching (/guides/balance-watching) - Cross-Chain Transfers (/guides/cross-chain) - Error Handling Patterns (/guides/error-handling) - React Hooks (/guides/react-hooks) - Enterprise Manager (/guides/enterprise-manager) - Agent Payments (/guides/agent-payments) - Veridex Agents — Guide (/guides/veridex-agents) - Agent Identity & Reputation (/guides/agent-identity) - Agent Integrity & Response Seals (/guides/agent-integrity) - Data Sovereignty (/guides/data-sovereignty) - Examples (/guides/examples) - Multisig Wallet (/guides/multisig-wallet) - Advanced Patterns (/guides/advanced-patterns) ### Chains - EVM Chains (/chains/evm) - Ethereum Sepolia (/chains/ethereum-sepolia) - Solana (/chains/solana) - Aptos (/chains/aptos) - Sui (/chains/sui) - Starknet (/chains/starknet) - Stacks (/chains/stacks) ### Integrations - Next.js Integration (/integrations/nextjs) - React Integration (/integrations/react) - Sera Protocol (/integrations/sera) - DeFi Integration (/integrations/defi) - Payment Gateway (/integrations/payment-gateway) - Gaming Integration (/integrations/gaming) - NFT Marketplace (/integrations/nft-marketplace) ### Security - Security Audits (/security/audits) - Security Best Practices (/security/best-practices) - Account Recovery (/security/recovery) - Agent Security Gateway (/security/agent-security) - Response Integrity (/security/response-integrity) - Data Sovereignty (/security/data-sovereignty) ### Api Reference - SDK Reference (/api-reference/sdk) - React SDK API Reference (/api-reference/react) - Agent SDK Reference (/api-reference/agent-sdk) - Agent Security API Reference (/api-reference/agent-security) - Agents Framework API Reference (/api-reference/agents-framework) - Agents Adapters API Reference (/api-reference/agents-adapters) - Agents OpenClaw API Reference (/api-reference/agents-openclaw) - Agents Control Plane API Reference (/api-reference/agents-control-plane) - Relayer API (/api-reference/relayer) - TypeScript Types (/api-reference/types) - Error Handling (/api-reference/errors) - Agents React API Reference (/api-reference/agents-react) ### Resources - Architecture Decisions (/resources/architecture-decisions) - Contributing (/resources/contributing) - AI & LLM Access (/resources/llm-access) - Glossary (/resources/glossary) - Changelog (/resources/changelog) - Troubleshooting (/resources/troubleshooting) - Migration Guide (/resources/migration) --- ================================================================================ URL: / ================================================================================ Open-source protocol Bounded authority.For apps & agents. API keys identify applications. Session keys delegate bounded authority to software. Operators decide how much autonomy is acceptable. Developers decide how to implement it. Developer Quickstart Operator Quickstart npm install @veridex/sdk How it works 1. API Keys Register your app and get an API key. This identifies your application to the platform. 2. Session Keys Create bounded session keys with limits, chains, expiry, and permissions. Multiple concurrent sessions per identity. 3. Governed Execution Every action runs through policy evaluation. Spending limits, chain restrictions, approval thresholds, and counterparty rules. 4. Audit Evidence Every execution produces a trace with reasoning, policy verdicts, signatures, and an exportable evidence bundle. 🚀 Developer Quickstart Register an app, create a session, execute your first governed request in under 10 minutes. 🛡️ Operator Quickstart Review posture, configure policies, approve actions, inspect traces and evidence bundles. ⚡ SDKs Core SDK for wallets and sessions. Agent SDK for autonomous payments, identity, and MCP tools. 🎛️ Session Studio Create, list, select, rotate, and revoke sessions. App sessions, agent sessions, multi-session inventory. 📋 Governance Approvals, policies, incidents, traces, and evidence exports for operator oversight. 🔗 Chains Base, Optimism, Arbitrum, Solana, Aptos, Sui, Starknet, and Stacks. Packages @veridex/sdk Passkey wallets, multi-session keys, cross-chain transfers, spending limits, and ERC-8004 utilities. npm install @veridex/sdk View SDK docs → @veridex/agentic-payments Autonomous payments, on-chain identity, session inventory, trust gates, and MCP tools for AI agents. npm install @veridex/agentic-payments View Agent SDK docs → @veridex/agents General-purpose TypeScript agent runtime with tools, hooks, memory, checkpoints, and policy-aware execution. npm install @veridex/agents zod View framework docs → ## Bounded Authority in 30 seconds ```typescript const sdk = createSDK('base', { network: 'testnet' }); // Register a passkey const credential = await sdk.passkey.register('alice', 'My Wallet'); // Create a bounded session (one of many) const session = await sdk.sessions.createSession({ permissions: ['transfer'], chainScopes: [8453], spendingLimit: { amount: 100_000_000n, token: 'USDC' }, duration: 3600, label: 'payment-agent-001', }); // List all active sessions const sessions = await sdk.sessions.listSessions(); // Execute a governed transfer with the active session const prepared = await sdk.prepareTransfer({ targetChain: 8453, token: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', amount: 1000000n, }); ``` Explore the docs Multi-Session Management → Session Keys → Agent Payments → Veridex Agents → Agent Identity (ERC-8004) → Cross-Chain Transfers → Spending Limits → React Hooks → Approval Inbox → Trace Explorer → Evidence Export → Enterprise Manager → Architecture Decisions → Glossary → Community GitHub Discord Telegram Twitter ================================================================================ URL: /getting-started/quick-start ================================================================================ # Quick Start Build a passkey wallet and send your first transaction in under 5 minutes. > **Note:** **What is a passkey wallet?** Instead of seed phrases or private keys, your wallet is secured by your device's biometrics (FaceID, fingerprint, Windows Hello). No passwords to remember, no seed phrases to write down. Your biometric = your wallet key. > **Note:** **Frontend Required:** Passkey operations (`register`, `authenticate`) use WebAuthn browser APIs and must run in a **client-side browser context** (HTTPS or localhost). They cannot run in Node.js, server components, or terminal scripts. All code examples below must execute in a browser — either in a React/Next.js client component, a plain HTML page with a `` tag, or any other browser environment. ## 1. Initialize the SDK First, tell the SDK which blockchain you want to use. We'll use Base Sepolia (a free test network) so you won't spend any real money while learning. ```typescript // Initialize with testnet (free test network — no real money involved) const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); // That's it! The SDK is ready to use. ``` ## 2. Register a Passkey This is the "sign up" step. The user's browser will show a biometric prompt (FaceID, fingerprint, etc.). After they verify, a passkey is created and a wallet address is generated automatically. ```typescript // This pops up a biometric prompt (FaceID / fingerprint / PIN) const credential = await sdk.passkey.register( 'user@example.com', // Username shown in the passkey prompt 'My Veridex Wallet' // A friendly name for this passkey ); // Done! The user now has a wallet. console.log('Credential ID:', credential.credentialId); console.log('Key Hash:', credential.keyHash); console.log('Vault Address:', sdk.getVaultAddress()); ``` > **Note:** The vault address is **deterministic** — the same passkey always produces the same address on every EVM chain. Your user gets one address that works everywhere. ## 3. Fund the Vault Your wallet needs some test tokens before it can send transactions. "Faucets" are free token dispensers for test networks — just paste your vault address and click a button. For testnet, use these faucets: | Chain | Faucet | |-------|--------| | Base Sepolia | [base.org/faucet](https://www.base.org/faucet) | | Optimism Sepolia | [app.optimism.io/faucet](https://app.optimism.io/faucet) | | Arbitrum Sepolia | [faucet.quicknode.com/arbitrum](https://faucet.quicknode.com/arbitrum) | | Stacks Testnet | [explorer.hiro.so/sandbox/faucet](https://explorer.hiro.so/sandbox/faucet?chain=testnet) | ## 4. Prepare and Execute a Transfer Sending tokens is a two-step process: **prepare** (preview the cost) then **execute** (sign and send). This lets you show users exactly what will happen before they confirm. ```typescript // Step A: Prepare — calculates gas cost, doesn't send anything yet const prepared = await sdk.prepareTransfer({ targetChain: 10004, // Base Sepolia (Wormhole chain ID) token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // USDC on Base Sepolia recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', amount: 1000000n, // 1 USDC (USDC has 6 decimals, so 1_000_000 = 1.0 USDC) }); // Show the user a human-readable summary const summary = await sdk.getTransactionSummary(prepared); console.log(summary.title); // "Transfer" console.log(summary.description); // "Send 1.0 USDC to 0x742d...5A234" console.log('Gas cost:', prepared.formattedCost); // Step B: Execute — triggers biometric prompt, signs, and sends const result = await sdk.executeTransfer(prepared, signer); console.log('Transaction hash:', result.transactionHash); // Done! The tokens are on their way. ``` ## 5. Check Spending Limits ```typescript // Check current limits const limits = await sdk.getSpendingLimits(); console.log('Daily remaining:', limits.dailyRemaining); // Check if a specific amount is allowed const check = await sdk.checkSpendingLimit(ethers.parseEther('1.0')); if (check.allowed) { console.log('Transfer allowed!'); } else { console.log('Blocked:', check.message); } // Get formatted limits for UI const formatted = await sdk.getFormattedSpendingLimits(); console.log(`${formatted.dailyUsedPercentage}% of daily limit used`); ``` ## 6. Check Balances ```typescript // Get all token balances on the current chain const portfolio = await sdk.getVaultBalances(); for (const entry of portfolio.tokens) { console.log(`${entry.token.symbol}: ${entry.formatted}`); } console.log('Total USD:', portfolio.totalUsdValue); ``` ## 7. Back Up Your Wallet (Optional but Recommended) If you lose your device, you can recover your wallet using an encrypted backup. This takes 30 seconds: ```typescript const backup = new WalletBackupManager({ passkey: sdk.passkey, relayerUrl: 'https://relayer.veridex.network/api/v1', }); // Encrypt and upload your credentials (triggers biometric prompt) const cred = sdk.passkey.getCredential(); const result = await backup.backupCredentials(cred.keyHash); console.log('Backup complete!', result.credentialCount, 'credentials saved'); ``` > **Note:** Your backup is encrypted with a key only your passkey can produce — the server cannot read it. See the full [Wallet Backup & Recovery](/guides/wallet-backup-recovery) guide for recovery steps. ## Complete Example ```typescript async function main() { // 1. Initialize const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); // 2. Check if user has existing passkeys const storedCredentials = sdk.passkey.getAllStoredCredentials(); if (storedCredentials.length === 0) { // 3. Register new passkey (prompts biometric) console.log('Registering passkey...'); await sdk.passkey.register('user@example.com', 'My Wallet'); } else { // 4. Authenticate with existing passkey (shows passkey picker) console.log('Logging in...'); await sdk.passkey.authenticate(); } // 5. Get vault info const vault = sdk.getVaultAddress(); console.log('Vault:', vault); // 6. Check spending limits const limits = await sdk.getSpendingLimits(); console.log('Daily limit remaining:', limits.dailyRemaining); // 7. Prepare a transfer const prepared = await sdk.prepareTransfer({ targetChain: 10004, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', amount: 100000n, // 0.1 USDC }); // 8. Show summary to user const summary = await sdk.getTransactionSummary(prepared); console.log(`${summary.title}: ${summary.description}`); console.log('Gas cost:', prepared.formattedCost); if (summary.risks.length > 0) { console.warn('Risks:', summary.risks.map(r => r.message)); } } main().catch(console.error); ``` ## Multi-Chain Initialization (Advanced) For apps that need multi-chain support, use the `VeridexSDK` class directly with an `EVMClient`: ```typescript // Create the EVM chain client (hub chain) const evmClient = new EVMClient({ chainId: 84532, wormholeChainId: 10004, rpcUrl: 'https://sepolia.base.org', hubContractAddress: '0x66D87dE68327f48A099c5B9bE97020Feab9a7c82', wormholeCoreBridge: '0x79A1027a6A159502049F10906D333EC57E95F083', name: 'Base Sepolia', explorerUrl: 'https://sepolia.basescan.org', vaultFactory: '0xCFaEb5652aa2Ee60b2229dC8895B4159749C7e53', vaultImplementation: '0x0d13367C16c6f0B24eD275CC67C7D9f42878285c', }); const sdk = new VeridexSDK({ chain: evmClient, persistWallet: true, testnet: true, relayerUrl: '/api/relayer', relayerApiKey: process.env.NEXT_PUBLIC_RELAYER_API_KEY, }); ``` > **Note:** The `createSDK` shorthand is great for getting started. For production apps with multi-chain vaults, use `VeridexSDK` + `EVMClient` directly — see the [React Integration](/integrations/react) guide. ## Gasless Transfer (via Relayer) Send tokens without the user needing gas: ```typescript // Transfer via relayer — user pays no gas const result = await sdk.transferViaRelayer({ targetChain: 10004, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', amount: 1000000n, // 1 USDC }); console.log('Transaction hash:', result.transactionHash); ``` ## Agent SDK Quick Start For AI agents that need to make autonomous payments: ```typescript async function agentQuickStart() { // 1. Create agent with spending limits and MCP tools const agent = await createAgentWallet({ masterCredential: { credentialId: process.env.CREDENTIAL_ID!, publicKeyX: BigInt(process.env.PUBLIC_KEY_X!), publicKeyY: BigInt(process.env.PUBLIC_KEY_Y!), keyHash: process.env.KEY_HASH!, }, session: { dailyLimitUSD: 50, perTransactionLimitUSD: 10, expiryHours: 24, allowedChains: [10004], // Base Sepolia }, mcp: { enabled: true }, // Enable MCP tools for AI model integration relayerUrl: 'https://relay.veridex.network', }); // 2. Get MCP tools (for Gemini, Claude, GPT function calling) const tools = agent.getMCPTools(); console.log('Available tools:', tools.map(t => t.name)); // 3. Fetch paid data (x402 handled automatically) const response = await agent.fetch('https://paid-api.example.com/data'); const data = await response.json(); // 4. Direct payment const receipt = await agent.pay({ chain: 10004, token: 'USDC', amount: '1000000', recipient: '0x...', }); // 5. Check spending const status = agent.getSessionStatus(); console.log(`Spent: $${status.totalSpentUSD} / $${status.limits!.dailyLimitUSD}`); // 6. Clean up await agent.revokeSession(); } ``` ## Next.js SSR-Safe Pattern In Next.js, use dynamic imports to avoid SSR issues: ```typescript // lib/veridex-client.ts let sdkInstance: any = null; export async function getVeridexSDK() { if (!sdkInstance) { const { createSDK } = await import('@veridex/sdk'); sdkInstance = createSDK('base', { network: 'testnet', relayerUrl: process.env.NEXT_PUBLIC_RELAYER_URL, }); } return sdkInstance; } ``` > **Note:** The `await import('@veridex/sdk')` ensures the SDK is only loaded in the browser, never during server-side rendering. See the [Next.js Integration](/integrations/nextjs) guide for the full pattern. ## React Integration ```tsx 'use client'; const sdk = createSDK('base', { network: 'testnet' }); export function WalletButton() { const [vault, setVault] = useState(null); const [loading, setLoading] = useState(false); const handleConnect = async () => { setLoading(true); try { // Check for existing passkey const hasStored = sdk.passkey.hasStoredCredential(); if (hasStored) { await sdk.passkey.authenticate(); } else { await sdk.passkey.register('user@example.com', 'My Wallet'); sdk.passkey.saveToLocalStorage(); } setVault(sdk.getVaultAddress()); } catch (error) { console.error('Failed to connect:', error); } setLoading(false); }; if (vault) { return ( Connected: {vault.slice(0, 6)}...{vault.slice(-4)} ); } return ( {loading ? 'Connecting...' : 'Connect with Passkey'} ); } ``` ## Next Steps Full multi-chain context provider with VeridexSDK SSR-safe patterns with dynamic imports Build autonomous AI agent payments with MCP tools Configure spending limits for safety ================================================================================ URL: /getting-started/installation ================================================================================ # Installation Veridex now provides a protocol layer and a runtime layer: - **`@veridex/sdk`** — Core SDK for passkey wallets, vaults, sessions, cross-chain transfers, and **wallet backup/recovery** - **`@veridex/agentic-payments`** — Agent SDK for autonomous AI agent payments, x402 protocol, and MCP tools - **`@veridex/agents`** — General-purpose TypeScript agent runtime - **`@veridex/agents-react`** — React hooks and provider for the runtime - **`@veridex/agents-adapters`** — Adapters and live bridges for external runtimes - **`@veridex/agents-openclaw`** — OpenClaw / Pi compatibility bridge - **`@veridex/agents-control-plane`** — Control-plane service and client for approvals, traces, policy, and audit ## Prerequisites - Node.js 18+ or Bun - A **browser frontend** for passkey operations (see note below) - A WebAuthn-compatible browser (Chrome, Safari, Firefox, Edge) - TypeScript 5.0+ (recommended) - `ethers` v6 (peer dependency for the core SDK) > **Note:** **Frontend Required for Passkeys:** The core SDK uses WebAuthn browser APIs for passkey registration and authentication. These operations (`sdk.passkey.register()`, `sdk.passkey.authenticate()`) must run in a **client-side browser context** (HTTPS or localhost) — they cannot run in Node.js, server components, or CLI scripts. You need a frontend (React, Next.js, plain HTML, etc.) to create and authenticate passkeys. Server-side code can handle balance queries, transaction verification, and relayer calls, but the initial passkey creation must happen in the browser. > **Note:** **Agent SDK Exception:** The Agent SDK (`@veridex/agentic-payments`) runs server-side in Node.js, but it requires a `masterCredential` that was originally created via a browser frontend. The typical flow is: human creates passkey in browser → configures spending limits → agent backend receives credentials and operates autonomously within those limits. ## Install the Core SDK For building passkey-authenticated wallets and dApps: ```bash npm install @veridex/sdk ethers ``` ```bash yarn add @veridex/sdk ethers ``` ```bash pnpm add @veridex/sdk ethers ``` ```bash bun add @veridex/sdk ethers ``` ## Install the Agent SDK For building autonomous AI agents that can make payments: ```bash npm install @veridex/agentic-payments ``` ```bash yarn add @veridex/agentic-payments ``` ```bash pnpm add @veridex/agentic-payments ``` ```bash bun add @veridex/agentic-payments ``` > **Note:** The Agent SDK (`@veridex/agentic-payments`) includes `@veridex/sdk` as a dependency and re-exports core functions like `createSDK` and `generateSecp256k1KeyPair` for convenience. ## Install the Agents Framework For general-purpose agents, workflow-style orchestration, interop, and enterprise control: ```bash npm install @veridex/agents zod npm install @veridex/agents-react @veridex/agents-adapters @veridex/agents-openclaw @veridex/agents-control-plane ``` ```bash yarn add @veridex/agents zod yarn add @veridex/agents-react @veridex/agents-adapters @veridex/agents-openclaw @veridex/agents-control-plane ``` ```bash pnpm add @veridex/agents zod pnpm add @veridex/agents-react @veridex/agents-adapters @veridex/agents-openclaw @veridex/agents-control-plane ``` ```bash bun add @veridex/agents zod bun add @veridex/agents-react @veridex/agents-adapters @veridex/agents-openclaw @veridex/agents-control-plane ``` > **Note:** The new framework packages are implemented and usable, but still evolving. Some APIs may still change and parts of the docs may still be catching up. We welcome fixes and feedback. ## Package Comparison | Feature | `@veridex/sdk` | `@veridex/agentic-payments` | |---------|---------------|---------------------------| | Passkey registration & login | ✅ | ✅ (via re-export) | | Vault management | ✅ | ✅ | | Session key management | ✅ `SessionManager` | ✅ `SessionKeyManager` (enhanced) | | Spending limits | ✅ `SpendingLimitsManager` | ✅ `SpendingTracker` (USD-aware) | | Cross-chain transfers | ✅ | ✅ `CrossChainRouter` | | ERC-8004 low-level utilities | ✅ `erc8004/` module | ✅ (via re-export) | | ERC-8004 identity clients | — | ✅ `IdentityClient`, `ReputationClient` | | Registration file management | — | ✅ `RegistrationFileManager` | | Trust gates & reputation | — | ✅ `TrustGate`, `ReputationPipeline` | | Agent discovery | — | ✅ `AgentDiscovery` | | Protocol detection (x402/UCP/ACP/AP2) | — | ✅ `ProtocolDetector` | | AI agent orchestration | — | ✅ `AgentWallet` | | MCP tools (Claude/Cursor) | — | ✅ `MCPServer` | | UCP credentials | — | ✅ `UCPCredentialProvider` | | Audit logging & compliance | — | ✅ `AuditLogger`, `ComplianceExporter` | | Multi-chain clients | EVM, Solana, Aptos, Sui, Starknet, Stacks, Monad | All + `MonadChainClient`, `StacksChainClient` | | Pyth price oracle | — | ✅ `PythOracle` | ## Agent Fabric Package Comparison | Package | Main job | Best for | |---------|----------|----------| | `@veridex/agents` | Runtime core | Tools, hooks, memory, checkpoints, transports, policy-aware runs | | `@veridex/agents-react` | UI integration | React chat, approvals, trace views, budget and identity dashboards | | `@veridex/agents-adapters` | Import/export and bridges | LangGraph, OpenAI Agents, PydanticAI, OpenAPI migration | | `@veridex/agents-openclaw` | OpenClaw / Pi interop | Skill import, context-file import, ACP collaboration | | `@veridex/agents-control-plane` | Governance plane | Tenants, approvals, policy packs, traces, audit export, remote service | ## TypeScript Configuration For TypeScript projects, ensure your `tsconfig.json` includes: ```json { "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "esModuleInterop": true, "strict": true } } ``` ## Browser Support Veridex uses WebAuthn (Passkeys) which requires: | Browser | Minimum Version | |---------|-----------------| | Chrome | 67+ | | Safari | 14+ | | Firefox | 60+ | | Edge | 79+ | > **Note:** Passkeys require a secure context (HTTPS) in production. For local development, `localhost` is allowed. ## Framework Integration ### React / Next.js (Core SDK) ```typescript // lib/veridex.ts export const sdk = createSDK('base', { network: 'testnet', relayerUrl: process.env.NEXT_PUBLIC_RELAYER_URL, }); ``` ### Node.js / Backend (Agent SDK) ```typescript // agent.ts const agent = await createAgentWallet({ masterCredential: { credentialId: process.env.CREDENTIAL_ID!, publicKeyX: BigInt(process.env.PUBLIC_KEY_X!), publicKeyY: BigInt(process.env.PUBLIC_KEY_Y!), keyHash: process.env.KEY_HASH!, }, session: { dailyLimitUSD: 100, perTransactionLimitUSD: 25, expiryHours: 24, allowedChains: [10004], }, }); ``` ### General-purpose runtime (`@veridex/agents`) ```typescript const searchDocs = tool({ name: 'search_docs', description: 'Search internal documentation by keyword', input: z.object({ query: z.string() }), safetyClass: 'read', async execute({ input }) { return { success: true, llmOutput: `Would search docs for ${input.query}`, }; }, }); const agent = createAgent({ id: 'docs-agent', name: 'Docs Agent', model: { provider: 'openai', model: 'gpt-4o-mini' }, instructions: 'Help users find accurate documentation quickly.', tools: [searchDocs], }); ``` ### Stacks Integration ```typescript // For Stacks-specific operations const sdk = createSDK('stacks', { network: 'testnet' }); ``` ## Verify Installation ### Core SDK ```typescript // test-sdk.ts const sdk = createSDK('base', { network: 'testnet' }); const chains = getSupportedChains('testnet'); console.log('SDK initialized successfully!'); console.log('Supported chains:', chains); console.log('Base config:', getChainConfig('base', 'testnet')); ``` ### Agent SDK ```typescript // test-agent.ts // Test Pyth oracle const oracle = new PythOracle(); const ethPrice = await oracle.getPrice('ETH'); const stxPrice = await oracle.getPrice('STX'); console.log('Agent SDK loaded successfully!'); console.log('ETH price:', ethPrice); console.log('STX price:', stxPrice); ``` Run with: ```bash npx tsx test-sdk.ts ``` ## Next Steps Build your first passkey wallet Understand how Veridex works Build autonomous agent payments Build general-purpose and payment-capable agents ================================================================================ URL: /getting-started/concepts ================================================================================ # Core Concepts Understanding the fundamental concepts of Veridex will help you build better integrations. ## Passkeys (WebAuthn) Passkeys are a modern authentication standard that replaces passwords with biometrics (FaceID, TouchID) or hardware security keys. ### How Passkeys Work ``` User taps "Connect" → Device prompts for biometric → Passkey signs challenge → Signature verified on-chain ``` ### Key Properties - **Phishing-resistant**: Passkeys are bound to domains - **Cross-device**: Sync via iCloud Keychain, Google Password Manager - **Non-exportable**: Private key never leaves the device ### Cryptography Veridex uses the **P-256 (secp256r1)** curve, supported natively by: - WebAuthn standard - RIP-7212 precompile on Base/Optimism/Arbitrum - All modern secure enclaves (TPM, Secure Enclave) ## Cross-Domain Passkeys By default, WebAuthn passkeys are bound to a single domain. A passkey created on `app-a.com` cannot be used on `app-b.com`. Veridex solves this with **Related Origin Requests (ROR)** — a WebAuthn Level 3 standard that allows passkeys to be shared across approved domains. ### How It Works 1. All Veridex-integrated apps use `veridex.network` as the canonical **Relying Party ID (rpId)**. 2. A `.well-known/webauthn` file at `veridex.network` lists all approved origins. 3. When a user visits a third-party app, the browser checks the whitelist and allows the passkey if the origin is listed. 4. For browsers without ROR support, the SDK falls back to an **Auth Portal** at `auth.veridex.network` (popup or redirect). ### Key Points for Developers - **Register your origin** at [developers.veridex.network](https://developers.veridex.network) before integrating. - **Use `createCrossOriginAuth()`** from the SDK — it handles ROR detection and fallback automatically. - **Create server-side session tokens** via the relayer for backend-verifiable sessions. - **HTTPS is required** — WebAuthn does not work on insecure origins (except `localhost`). - **Chrome limits** `.well-known/webauthn` to 5 unique eTLD+1 labels. Origins beyond this limit use the Auth Portal fallback seamlessly. See the [Cross-Domain Passkeys Guide](/guides/cross-domain-passkeys) for full integration instructions. ## Hub and Spoke Architecture Veridex uses a hub-and-spoke model for cross-chain operations. ```mermaid flowchart TD Hub["Base (Hub + Spoke)\nPasskey Registry + Vault"] Hub -- "Wormhole" --> Eth["Ethereum\nVault"] Hub -- "Wormhole" --> Op["Optimism\nVault"] Hub -- "Wormhole" --> Arb["Arbitrum\nVault"] Hub -- "Wormhole" --> Sei["Sei\nVault"] Hub -- "Wormhole" --> Apt["Aptos\nVault"] Hub -- "Wormhole" --> Sui["Sui\nVault"] Hub -- "Wormhole" --> Sol["Solana\nVault"] Hub -- "Wormhole" --> Stark["Starknet\nVault"] Hub -- "Direct Relay" --> Stx["Stacks\nVault"] ``` ### Hub (Base) - Stores passkey credentials (public key, credential ID) - Verifies WebAuthn signatures (P-256) - Emits cross-chain authorization messages - Single source of truth for identity - **Also functions as a Spoke** — can hold funds and execute transactions directly (no cross-chain hop needed for Base operations) ### Spokes (All Chains) - Hold user assets in deterministic vaults - Execute actions upon receiving Hub authorization via Wormhole (or direct relay for Stacks) - Support native token + ERC20/SPL/SIP-010 transfers ## Deterministic Vaults Each passkey generates a **deterministic vault address** that's the same across all EVM chains. ### How It Works ```typescript vaultAddress = CREATE2( factory, keccak256(publicKeyX, publicKeyY), vaultBytecode ) ``` ### Benefits - **Single address**: Share one address for all EVM chains - **No deployment needed**: Address is known before vault exists - **Counterfactual**: Can receive funds before vault is deployed ### Non-EVM Chains For Solana, Aptos, Sui, Starknet, and Stacks, vault addresses are derived using chain-specific methods but still deterministic from the passkey. **Stacks** is unique because Clarity natively supports `secp256r1-verify` for passkey signatures — no ZK proofs or precompiles needed. ## Session Keys Session keys enable smooth UX by delegating limited authority to a temporary key. ### Why Session Keys? Without session keys: ``` Action 1 → Biometric prompt Action 2 → Biometric prompt Action 3 → Biometric prompt ``` With session keys: ``` Create session → Biometric prompt (once) Action 1 → Auto-signed Action 2 → Auto-signed Action 3 → Auto-signed ``` ### Session Constraints Sessions can be limited by: | Constraint | Description | |------------|-------------| | `duration` | Time limit (e.g., 1 hour) | | `maxValue` | Total value limit | | `allowedTokens` | Whitelist of tokens | | `allowedRecipients` | Whitelist of addresses | ### Core SDK Session Example ```typescript // Generate a session key pair const keyPair = generateSecp256k1KeyPair(); const sessionHash = computeSessionKeyHash(keyPair.publicKey); // SessionManager handles lifecycle const sessionManager = new SessionManager({ /* hub client config */ }); ``` ### Agent SDK Session Example The Agent SDK provides a higher-level `SessionKeyManager` with USD-denominated spending limits: ```typescript const agent = await createAgentWallet({ masterCredential: { /* passkey credential */ }, session: { dailyLimitUSD: 50, // $50/day budget perTransactionLimitUSD: 10, // $10 max per tx expiryHours: 24, // 24-hour session allowedChains: [10004], // Base Sepolia only }, }); // Agent uses session key automatically for payments await agent.pay({ chain: 10004, token: 'USDC', amount: '1000000', recipient: '0x...' }); ``` ## Cross-Chain Messaging Veridex uses Wormhole for secure cross-chain communication. ### Message Flow 1. **Hub signs action** → WebAuthn signature verified 2. **Wormhole guardians attest** → 13/19 signatures required 3. **Spoke verifies VAA** → Executes authorized action ### Verification Paths | Path | Speed | Use Case | |------|-------|----------| | **Query (CCQ)** | ~5-7 seconds | Balance checks, low-value transfers | | **VAA** | ~60 seconds | High-value transfers, critical actions | The relayer automatically chooses the optimal path based on transaction value. ## Wallet Backup & Recovery Passkeys live in your device's secure enclave. If you lose all your devices, you need a way to recover access. Veridex provides encrypted credential backup via the relayer. ### How Backup Works ``` ┌─────────────┐ ┌─────────────┐ │ Your Device │ │ Relayer │ │ │ │ (Cloud) │ │ Credentials ├── PRF encrypt ───▶│ Encrypted │ │ (keyHash, │ │ Blob │ │ credId) │ │ (can't read │ │ │ │ contents) │ └─────────────┘ └─────────────┘ ``` 1. Your passkey generates a **PRF seed** — a deterministic secret only your authenticator can produce. 2. The SDK derives an AES-256-GCM encryption key from that seed. 3. All credential metadata is encrypted and uploaded to the Veridex relayer. 4. On recovery, the same passkey (or a synced copy) regenerates the PRF seed and decrypts the archive. ### PRF (Pseudo-Random Function) PRF is a WebAuthn extension that lets an authenticator produce a deterministic secret for a given input. Key properties: - **Deterministic**: Same passkey + same salt = same secret, every time - **Non-exportable**: The secret is generated inside the secure enclave - **Domain-bound**: Different RP IDs produce different outputs PRF is supported in Chrome 116+, Safari 17+, and Edge 116+. On browsers without PRF, the SDK falls back to a credential-ID-based key — functional but less secure. ### Recovery Scenarios | Scenario | What Happens | |----------|-------------| | **Lost one device, passkey synced** | Use another device — passkey already there | | **Lost device, have relayer backup** | Authenticate on synced device → download + decrypt backup | | **Lost all devices, passkey synced via iCloud/Google** | Set up new device with same account → passkey restores → decrypt backup | | **Lost everything, passkey NOT synced** | Social recovery via guardians (Phase 2) | ### Key Points - The relayer **cannot read** your backup — it only stores an encrypted blob. - Passkeys typically sync within your platform ecosystem (Apple → Apple, Google → Google). - For cross-ecosystem backup (iPhone → Windows PC), use the **Add Device** flow. - Back up after adding new devices or registering new passkeys. See the [Wallet Backup & Recovery Guide](/guides/wallet-backup-recovery) for step-by-step instructions. ## Gasless Transactions Users never need to hold gas tokens — the relayer pays gas fees. ### How It Works ```mermaid flowchart TD A["User signs payload with passkey"] --> B["Relayer receives signed payload"] B --> C["Relayer submits transaction (pays gas)"] C --> D["Contract verifies signature, executes action"] ``` ### Fee Model - **User pays**: Token amount only - **Relayer pays**: Gas fees - **Relayer recovers**: Via protocol fee or sponsorship ## Security Model ### Trust Assumptions | Component | Trust Level | |-----------|-------------| | User's device | Fully trusted (holds passkey) | | Hub contract | Trustless (on-chain verification) | | Wormhole guardians | Honest majority (13/19) | | Relayer | Untrusted (can't forge signatures) | ### Attack Resistance - **Replay attacks**: Nonces prevent replay - **Cross-chain replay**: Chain ID in signature - **Signature malleability**: Canonical encoding enforced - **Phishing**: Passkeys are domain-bound ## Agent SDK Concepts The Agent SDK (`@veridex/agentic-payments`) extends the core SDK for autonomous AI agent operations, including payments, on-chain identity, reputation, and multi-protocol support. ### AgentWallet The central orchestration class that manages session keys, payments, on-chain identity (ERC-8004), reputation, trust gates, agent discovery, and audit logging. ```typescript const agent = await createAgentWallet({ masterCredential: { /* passkey credential */ }, session: { dailyLimitUSD: 50, perTransactionLimitUSD: 10, expiryHours: 24 }, erc8004: { enabled: true, testnet: true, minReputationScore: 30 }, }); ``` ### Protocol Abstraction Layer The SDK supports multiple payment protocols with automatic detection. When `agent.fetch()` encounters a paywall, it auto-detects the protocol and handles payment: | Protocol | Creator | Priority | Use Case | |----------|---------|----------|----------| | **UCP** | Google/Shopify | 100 | Commerce/search integration | | **ACP** | OpenAI/Stripe | 90 | ChatGPT ecosystem payments | | **AP2** | Google | 80 | A2A mandate delegation | | **x402** | Coinbase/Cloudflare | 70 | HTTP micropayments (402 Payment Required) | ```mermaid sequenceDiagram participant Agent participant Server Agent->>Server: HTTP request Server-->>Agent: 402 Payment Required Agent->>Agent: ProtocolDetector identifies protocol Agent->>Agent: Check merchant reputation (TrustGate) Agent->>Agent: Check spending limits Agent->>Agent: Sign payment with session key Agent->>Server: Retry with payment proof Server-->>Agent: Returns data Agent->>Agent: Auto-submit reputation feedback ``` ### ERC-8004 On-Chain Identity The [ERC-8004](https://www.8004.org/learn) standard provides on-chain identity for AI agents via three singleton registries deployed on every EVM chain: | Registry | Purpose | Canonical Mainnet Address | |----------|---------|--------------------------| | **Identity** | Agent registration (ERC-721 NFT), metadata URI, wallet linkage | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` | | **Reputation** | Feedback submission, revocation, scoring, summaries | `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` | | **Validation** | Third-party attestations (spec in progress) | TBD | When an agent registers, it receives an **ERC-721 NFT** that represents its on-chain identity. The NFT's `tokenURI` points to a registration file containing the agent's name, description, services, and capabilities. ```typescript // Register an agent on-chain const { agentId, agentURI } = await agent.register({ name: 'Sentiment Analyzer', description: 'NLP-powered sentiment analysis', services: [{ name: 'analyze', endpoint: 'https://my-agent.com/analyze' }], }); ``` ### Reputation & Trust Gates After every payment, the SDK can automatically submit reputation feedback to the ERC-8004 Reputation Registry. Before paying a merchant, the SDK can check their reputation score: ```typescript // Check merchant reputation before paying const trust = await agent.checkMerchantTrust('https://data-provider.com'); // trust.score: 0-100, trust.trusted: boolean // Get detailed reputation const summary = await agent.getReputation(agentId); // summary.feedbackCount, summary.normalizedScore, summary.tags ``` The **TrustGate** enforces minimum reputation thresholds: - If `minReputationScore` is set, `agent.fetch()` will reject merchants below that score - `trustedReviewers` can filter which feedback counts toward the score ### Agent Discovery Agents can discover other agents on-chain by capability, chain, or reputation: ```typescript // Find sentiment analysis agents const agents = await agent.discover({ category: 'sentiment' }); // Resolve an agent from a URL, UAI, or address const resolved = await agent.resolveAgent('https://my-agent.com'); ``` The **AgentDiscovery** client supports resolution from: - **UAI** (Universal Agent Identifier) — CAIP-2 extended format - **Endpoint URL** — via `/.well-known/agent-registration.json` - **Chain address** — reverse lookup on Identity Registry - **Search** — by name, category, or tags ### Registration File Management The **RegistrationFileManager** builds, validates, and publishes ERC-8004 registration files: ```typescript // Build a registration file const file = RegistrationFileManager.buildRegistrationFile({ name: 'My Agent', description: 'Does cool things', services: [{ name: 'api', endpoint: 'https://my-agent.com/api' }], }); // Validate against ERC-8004 schema const { valid, errors } = RegistrationFileManager.validate(file); // Publish to IPFS or encode as data URI const dataURI = RegistrationFileManager.buildDataURI(file); ``` ### Modular Identity Clients The identity module is composed of independent, chain-agnostic clients: | Client | Purpose | |--------|---------| | **IdentityClient** | ERC-8004 Identity Registry — register, query, update agents | | **ReputationClient** | ERC-8004 Reputation Registry — feedback, scoring, summaries | | **RegistrationFileManager** | Build, validate, publish registration files (IPFS/data URI) | | **TrustGate** | Pre-payment reputation checks | | **AgentDiscovery** | Resolve agents from UAI, URL, address, or search | | **ServiceDirectoryClient** | Veridex-specific on-chain marketplace (not ERC-8004) | | **ERC8004Client** | Backward-compatible facade composing all of the above | ### Spending Limits (Three Layers) Veridex enforces spending limits at three levels: | Layer | Enforcement | Description | |-------|------------|-------------| | **SDK** | Off-chain | `SpendingTracker` checks USD limits before signing | | **Contract** | On-chain | Smart contract enforces daily/per-tx limits | | **Post-Conditions** | Protocol (Stacks only) | Stacks native guarantee on max transfer amounts | ### MCP Tools The Agent SDK provides Model Context Protocol (MCP) tools for integration with AI assistants like Claude and Cursor: ```typescript const agent = await createAgentWallet({ // ... mcp: { enabled: true }, }); const tools = agent.getMCPTools(); // Tools: check_balance, send_payment, get_session_status, discover_agents, get_reputation, etc. ``` ### Pyth Oracle Real-time price feeds for USD-denominated spending limits: ```typescript const oracle = new PythOracle(); const ethPrice = await oracle.getPrice('ETH'); const stxPrice = await oracle.getPrice('STX'); ``` ## Veridex Agent Fabric Concepts The newer framework layer extends Veridex beyond commerce-specific agents into broader agentic software. ### Core idea `@veridex/agents` is designed to work for: - ordinary assistants - workflow automation - multi-agent systems - agents that later need payments, approvals, or stronger enterprise controls Payments are optional. Governance-ready runtime primitives are not. ### Core runtime primitives The framework is organized around: - `AgentDefinition` - `Run` - `Turn` - `ToolContract` - `RuntimeHook` - `MemoryStore` - `ContextPlan` - `CheckpointStore` - transports for `MCP`, `ACP`, and `A2A` ### Companion packages | Package | Purpose | |---|---| | `@veridex/agents` | runtime core | | `@veridex/agents-react` | React hooks and provider | | `@veridex/agents-adapters` | adapters and live runtime bridges | | `@veridex/agents-openclaw` | OpenClaw / Pi compatibility | | `@veridex/agents-control-plane` | tenants, approvals, traces, and audit | ### Why it matters This lets teams start with a normal agent and then add: - passkey identity - payment capabilities - approvals - policy packs - audit-ready trace retention - external runtime interop without changing framework families later. > **Note:** These framework surfaces are still maturing. Some APIs may still move and some integrations may still have rough edges. We are documenting the direction honestly and we welcome contributions. ## Next Steps Step-by-step wallet creation guide ERC-8004 identity, reputation, and discovery Build autonomous AI agent payments Explore the framework package family Explore the SDK API ================================================================================ URL: /getting-started/faq ================================================================================ # Frequently Asked Questions ## General ### What is Veridex? Veridex is a cross-chain passkey authentication protocol. It lets users control assets on any blockchain using FaceID/TouchID — no seed phrases, no wallet apps, no chain switching. ### Is Veridex self-custodial? **Yes, 100%.** Your passkey's private key never leaves your device. Veridex contracts verify signatures on-chain without ever seeing your private key. ### How is this different from Privy/Dynamic/Web3Auth? | Feature | Privy/Dynamic | Veridex | |---------|--------------|---------| | Custody | MPC (they hold shards) | Fully self-custodial | | Trust | Trust their servers | Trustless (on-chain) | | Cross-chain | Limited | Native multi-chain | | Open source | No | Yes | ### What chains are supported? - **EVM**: Base (hub), Optimism, Arbitrum, Polygon, Ethereum - **Non-EVM**: Solana, Aptos, Sui, Starknet, Stacks ### Is it production ready? Veridex is currently on **testnet**. Mainnet launch is planned for Q2 2026 after security audits. ## Technical ### What happens if I lose my device? Veridex supports **on-chain social recovery**. You can designate guardians who can collectively recover your account. See [Recovery Guide](/guides/recovery). ### Can I use the same passkey on multiple devices? Yes! Passkeys sync via: - iCloud Keychain (Apple devices) - Google Password Manager (Android/Chrome) - Windows Hello (Windows devices) ### Why Base as the hub chain? Base was chosen because: 1. **RIP-7212 support**: Native P-256 verification (~3,450 gas) 2. **Low fees**: Sub-cent transactions 3. **Coinbase ecosystem**: Large user base 4. **EVM compatibility**: Easy spoke deployment ### How fast are cross-chain transfers? | Path | Time | Use Case | |------|------|----------| | Query (CCQ) | ~5-7 seconds | Low-value transfers | | VAA | ~60 seconds | High-value transfers | The SDK/relayer automatically chooses the optimal path. ### What's the difference between Query and VAA paths? - **Query (CCQ)**: Wormhole Cross-Chain Queries. Faster but relies on guardian attestation of current state. - **VAA**: Verified Action Approval. Full 13/19 guardian consensus. More secure for high-value operations. ### Do users need to hold gas tokens? **No.** The relayer pays gas fees. Users only need the tokens they want to transfer. ### How does the deterministic vault address work? Your vault address is derived from your passkey's public key using CREATE2: ``` vaultAddress = CREATE2(factory, hash(publicKey), bytecode) ``` This means: - Same passkey → same address on all EVM chains - Address is known before vault exists - You can receive funds before deploying ## Security ### Is my passkey secure? Yes. Passkeys use: - **Hardware security**: TPM, Secure Enclave, or similar - **Biometric protection**: FaceID, TouchID, fingerprint - **Non-exportable keys**: Private key never leaves the device ### What if Veridex gets hacked? Even if Veridex infrastructure is compromised: - Your private key is on your device, not our servers - Contracts are immutable after deployment - Guardians can recover via social recovery ### Can the relayer steal my funds? **No.** The relayer only submits transactions — it cannot forge your passkey signature. All signatures are verified on-chain. ### What about Wormhole security? Wormhole uses a 13/19 guardian quorum. Veridex verifies: - Guardian signatures - Message origin (hub emitter) - Replay protection (nonces) ## Integration ### How do I integrate Veridex? ```typescript const sdk = createSDK('base', { network: 'testnet' }); await sdk.passkey.register('user@example.com', 'My App'); const vault = sdk.getVaultAddress(); ``` See the [Quick Start Guide](/getting-started/quick-start). ### What is the Agent SDK? The Agent SDK (`@veridex/agentic-payments`) is a separate package for building autonomous AI agents that can make payments. It provides: - **x402 HTTP payment protocol** — agents automatically pay for API access - **Session keys with spending limits** — agents operate within USD-denominated budgets - **Multi-chain support** — EVM, Solana, Stacks, and more - **Audit logging** — full compliance trail of all payments - **MCP tools** — integrate with Claude, Cursor, and other AI assistants ```typescript const agent = await createAgentWallet({ masterCredential: { /* passkey credential */ }, session: { dailyLimitUSD: 50, expiryHours: 24 }, }); // Agent handles 402 Payment Required automatically const response = await agent.fetch('https://paid-api.example.com/data'); ``` See the [Agent Payments Guide](/guides/agent-payments). ### How does x402 work? The x402 protocol is an HTTP-native payment standard: 1. Agent sends a request to a paid API 2. Server responds with `402 Payment Required` + payment details in headers 3. Agent SDK automatically parses requirements, signs payment, and retries 4. Server verifies payment on-chain and returns the data This enables AI agents to autonomously pay for data, compute, and services. ### Is there a React component library? Not yet, but planned. For now, use the SDK directly in your React components. ### Can I run my own relayer? Yes! The relayer is open source. See [Relayer Setup](/api-reference/relayer). ### What's the cost of integration? The SDK and protocol are **free and open source**. Costs are: - **Gas fees**: Paid by relayer (can be sponsored) - **Wormhole fees**: ~0.0001 ETH per cross-chain message ### Do you have a grant program? We're exploring partnerships with ecosystem partners. Contact us at [team@veridex.network](mailto:team@veridex.network). ## Troubleshooting ### "Passkey not supported" error Ensure you're using: - A supported browser (Chrome 67+, Safari 14+, Firefox 60+) - HTTPS (or localhost for development) - A device with biometric capability ### "Signature verification failed" error Common causes: 1. Credential was created on a different domain 2. Passkey was deleted from device 3. Wrong network (testnet vs mainnet) ### Transaction stuck or failing Check: 1. Vault has sufficient balance 2. Relayer is running and healthy (`/health` endpoint) 3. Destination chain is responding ### Session key expired Sessions have a time limit. For the core SDK, use `SessionManager` to create a new session. For the Agent SDK, the `AgentWallet` handles session lifecycle automatically — just re-initialize: ```typescript // Agent SDK — sessions are managed automatically const agent = await createAgentWallet({ masterCredential: { /* ... */ }, session: { dailyLimitUSD: 50, expiryHours: 24 }, }); ``` ## Contact - **GitHub**: [github.com/Veridex-Protocol](https://github.com/Veridex-Protocol) - **Discord**: [discord.gg/veridex](https://discord.gg/veridex) - **Twitter**: [@VeridexProtocol](https://twitter.com/VeridexProtocol) - **Email**: [team@veridex.network](mailto:team@veridex.network) ================================================================================ URL: /guides/create-wallet ================================================================================ # Create Wallet This guide walks through creating a passkey-authenticated wallet with Veridex. ## Prerequisites - Veridex SDK installed (`npm install @veridex/sdk`) - WebAuthn-compatible browser - Device with biometric capability (FaceID, TouchID, fingerprint) ## Basic Wallet Creation ### 1. Initialize the SDK ```typescript const sdk = createSDK('base', { network: 'testnet', // or 'mainnet' relayerUrl: 'https://relayer.veridex.network', }); ``` ### 2. Register a Passkey ```typescript const credential = await sdk.passkey.register( 'user@example.com', // Username (shown in passkey prompt) 'My Veridex Wallet' // Display name ); console.log('Credential ID:', credential.credentialId); console.log('Key Hash:', credential.keyHash); console.log('Public Key X:', credential.publicKeyX); console.log('Public Key Y:', credential.publicKeyY); ``` > **Note:** The browser will prompt the user for biometric authentication (FaceID, TouchID, etc.). ### 3. Get Your Vault Address ```typescript const vaultAddress = sdk.getVaultAddress(); console.log('Your vault address:', vaultAddress); // This address is the SAME on all EVM chains! // You can share it as your receiving address ``` ## Returning Users (Login) ### Check for Existing Credentials ```typescript // Check if user has stored credentials const storedCredentials = sdk.passkey.getAllStoredCredentials(); if (storedCredentials.length > 0) { // User has previously registered console.log(`Found ${storedCredentials.length} stored passkey(s)`); } else { // First time user console.log('No passkey found, will need to register'); } ``` ### Authenticate with Existing Passkey ```typescript // Authenticate — shows passkey picker with all available credentials const { credential, signature } = await sdk.passkey.authenticate(); // Now the SDK is authenticated and ready to use const vault = sdk.getVaultAddress(); console.log('Logged in as:', credential.keyHash); console.log('Vault:', vault); ``` ## Complete Flow Example ```typescript async function connectWallet() { const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); // Check for existing credentials const storedCredentials = sdk.passkey.getAllStoredCredentials(); if (storedCredentials.length > 0) { // Returning user — authenticate (shows passkey picker) console.log('Logging in with existing passkey...'); await sdk.passkey.authenticate(); } else { // New user — register console.log('Creating new passkey wallet...'); await sdk.passkey.register( 'user@example.com', 'My Veridex Wallet' ); } // Get vault info const vaultAddress = sdk.getVaultAddress(); const credential = sdk.passkey.getCredential(); return { vaultAddress, credentialId: credential?.credentialId, }; } // Usage connectWallet() .then(({ vaultAddress }) => { console.log('Connected! Vault:', vaultAddress); }) .catch((error) => { console.error('Failed to connect:', error); }); ``` ## React Component Example ```tsx const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); export function ConnectButton() { const [vault, setVault] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Check for existing credentials on mount useEffect(() => { const stored = sdk.passkey.getAllStoredCredentials(); if (stored.length > 0) { // Auto-login if credentials exist handleConnect(); } }, []); const handleConnect = async () => { setLoading(true); setError(null); try { const stored = sdk.passkey.getAllStoredCredentials(); if (stored.length > 0) { await sdk.passkey.authenticate(); } else { await sdk.passkey.register('user@example.com', 'My Wallet'); } setVault(sdk.getVaultAddress()); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to connect'); } finally { setLoading(false); } }; const handleDisconnect = () => { // Clear the active credential from memory sdk.passkey.clearCredential(); setVault(null); }; if (vault) { return ( {vault.slice(0, 6)}...{vault.slice(-4)} Disconnect ); } return ( {loading ? 'Connecting...' : 'Connect with Passkey'} {error && ( {error} )} ); } ``` ## Credential Storage By default, credentials are stored in `localStorage` under the `veridex_credentials` key. The `PasskeyManager` handles persistence automatically: ```typescript // Get all stored credentials const all = sdk.passkey.getAllStoredCredentials(); // Manually save credentials (e.g., after migration) sdk.passkey.saveCredentials(all); // Add a credential to storage sdk.passkey.addCredentialToStorage(credential); // Check if any credentials exist const hasCredentials = sdk.passkey.hasStoredCredentials(); ``` ## Error Handling ```typescript try { await sdk.passkey.register('user@example.com', 'My Wallet'); } catch (error) { if (error.name === 'NotAllowedError') { // User cancelled the passkey prompt console.log('User cancelled passkey creation'); } else if (error.name === 'NotSupportedError') { // WebAuthn not supported console.log('Passkeys not supported on this device'); } else if (error.name === 'SecurityError') { // Not in secure context (need HTTPS) console.log('HTTPS required for passkeys'); } else { console.error('Unexpected error:', error); } } ``` ## Agent Wallet Creation For AI agents that need autonomous payment capabilities: ```typescript // Use the credential from passkey registration const agent = await createAgentWallet({ masterCredential: { credentialId: credential.credentialId, publicKeyX: credential.publicKeyX, publicKeyY: credential.publicKeyY, keyHash: credential.keyHash, }, session: { dailyLimitUSD: 50, perTransactionLimitUSD: 10, expiryHours: 24, allowedChains: [10004], // Base Sepolia (Wormhole chain ID) }, }); const status = agent.getSessionStatus(); console.log('Session valid:', status.isValid); console.log('Agent wallet:', status.address); console.log('Daily limit:', `$${status.limits!.dailyLimitUSD}`); ``` ## Stacks Wallet Create a wallet on Stacks with native passkey support: ```typescript const stacksSdk = createSDK('stacks', { network: 'testnet' }); // Passkey verified natively via Clarity's secp256r1-verify const credential = await stacksSdk.passkey.register('alice', 'My Stacks Wallet'); const vault = stacksSdk.getVaultAddress(); console.log('Stacks vault:', vault); ``` ## Next Steps Transfer tokens from your wallet Build autonomous AI agent payments Enable smooth UX without repeated prompts Native passkey support on Stacks ================================================================================ URL: /guides/send-tokens ================================================================================ # Send Tokens Transfer tokens from your Veridex vault to any address. ## Prerequisites - Wallet connected (see [Create Wallet](/guides/create-wallet)) - Tokens in your vault (use testnet faucets) - SDK initialized ## Basic Transfer ### Prepare and Execute ```typescript const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); // Authenticate first (shows passkey picker) const { credential } = await sdk.passkey.authenticate(); // Use chain presets instead of hardcoded Wormhole IDs const baseConfig = getChainConfig('base', 'testnet'); // Step 1: Prepare the transfer (shows gas cost before signing) const prepared = await sdk.prepareTransfer({ targetChain: baseConfig.wormholeChainId, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', // USDC recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', amount: ethers.parseUnits('10', 6), // 10 USDC }); console.log('Gas cost:', prepared.formattedCost); console.log('Expires:', new Date(prepared.expiresAt)); // Step 2: Get human-readable summary const summary = await sdk.getTransactionSummary(prepared); console.log(summary.title); // "Transfer" console.log(summary.description); // "Send 10.0 USDC to 0x742d...5A234" // Step 3: Execute with a signer (signs with passkey, then dispatches) const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const result = await sdk.executeTransfer(prepared, signer); console.log('Transaction hash:', result.transactionHash); console.log('Sequence:', result.sequence); ``` ### Native Token Transfer ```typescript const baseConfig = getChainConfig('base', 'testnet'); const prepared = await sdk.prepareTransfer({ targetChain: baseConfig.wormholeChainId, token: 'native', recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', amount: ethers.parseEther('0.01'), // 0.01 ETH }); const result = await sdk.executeTransfer(prepared, signer); console.log('ETH transfer:', result.transactionHash); ``` ## Agent SDK Payments For AI agents, the Agent SDK provides a simpler payment interface: ```typescript const agent = await createAgentWallet({ masterCredential: { /* passkey credential */ }, session: { dailyLimitUSD: 50, perTransactionLimitUSD: 10, expiryHours: 24, allowedChains: [10004], // Base Sepolia }, }); // Direct payment — session key handles signing const receipt = await agent.pay({ chain: 10004, // Base Sepolia token: 'USDC', amount: '10000000', // 10 USDC (6 decimals) recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', }); console.log('Tx:', receipt.txHash); console.log('Status:', receipt.status); ``` ## Check Spending Limits Before Transfer ```typescript // Check if a transfer amount is within limits const check = await sdk.checkSpendingLimit(ethers.parseUnits('100', 6)); if (check.allowed) { console.log('Transfer allowed!'); } else { console.log('Blocked:', check.message); console.log('Suggestions:', check.suggestions); } // Get formatted limits for UI const formatted = await sdk.getFormattedSpendingLimits(); console.log(`${formatted.dailyUsedPercentage}% of daily limit used`); ``` ## Check Balance Before Transfer ```typescript // Get vault balances on the current chain const portfolio = await sdk.getVaultBalances(); for (const entry of portfolio.tokens) { console.log(`${entry.token.symbol}: ${entry.formatted}`); } console.log('Total USD:', portfolio.totalUsdValue); ``` ## Transaction Tracking Track transaction status after execution: ```typescript const result = await sdk.executeTransfer(prepared, signer); // Wait for confirmation const state = await sdk.waitForTransaction(result.transactionHash); console.log('Status:', state.status); console.log('Confirmations:', state.confirmations); console.log('Tx Hash:', result.transactionHash); console.log('Sequence:', result.sequence); ``` ## React Example ### Using `@veridex/react` Hooks (Recommended) ```tsx const USDC = '0x036CbD53842c5426634e7929541eC2318f3dCF7e'; export function SendForm() { const { balance } = useBalance(); const { transferViaRelayer, step, result, error, reset } = useTransfer(); const handleSend = (recipient: string, amount: string) => { transferViaRelayer({ targetChain: 'base', // Chain name — resolved automatically token: USDC, recipient, amount: parseUnits(amount, 6), }); }; if (step === 'confirmed') { return ( Sent! Tx: {result?.transactionHash} Send Another ); } return ( Balance: {balance?.tokens[0]?.formatted ?? '...'} handleSend('0x...', '10')} disabled={step === 'executing'} > {step === 'executing' ? 'Sending...' : 'Send 10 USDC'} {error && {error.message}} ); } ``` ### Using SDK Directly ```tsx const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); const USDC = '0x036CbD53842c5426634e7929541eC2318f3dCF7e'; export function SendForm() { const [recipient, setRecipient] = useState(''); const [amount, setAmount] = useState(''); const [loading, setLoading] = useState(false); const [txHash, setTxHash] = useState(null); const [summary, setSummary] = useState(null); const [error, setError] = useState(null); const handleSend = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setError(null); setTxHash(null); try { // Prepare transfer const prepared = await sdk.prepareTransfer({ targetChain: 10004, token: USDC, recipient, amount: ethers.parseUnits(amount, 6), }); // Show summary const txSummary = await sdk.getTransactionSummary(prepared); setSummary(`${txSummary.description} (Gas: ${prepared.formattedCost})`); // Execute const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const result = await sdk.executeTransfer(prepared, signer); setTxHash(result.transactionHash); } catch (err) { setError(err instanceof Error ? err.message : 'Transfer failed'); } finally { setLoading(false); } }; return ( Recipient setRecipient(e.target.value)} placeholder="0x..." className="mt-1 block w-full rounded-md border p-2" required /> Amount (USDC) setAmount(e.target.value)} placeholder="10.00" step="0.01" className="mt-1 block w-full rounded-md border p-2" required /> {loading ? 'Sending...' : 'Send USDC'} {summary && {summary}} {txHash && ( Success!{' '} View on Explorer )} {error && {error}} ); } ``` ## Common Token Addresses ### Base Sepolia (Testnet) | Token | Address | |-------|---------| | USDC | `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | | WETH | `0x4200000000000000000000000000000000000006` | ### Base Mainnet | Token | Address | |-------|---------| | USDC | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` | | WETH | `0x4200000000000000000000000000000000000006` | ## Error Handling ```typescript try { const prepared = await sdk.prepareTransfer({ /* ... */ }); const result = await sdk.executeTransfer(prepared, signer); } catch (error: any) { if (error.message?.includes('No credential set')) { console.log('Please register or login first'); } else if (error.message?.includes('expired')) { console.log('Prepared transfer expired, please prepare again'); } else { console.error('Transfer failed:', error); } } // For Agent SDK payments try { await agent.pay({ chain: 10004, token: 'USDC', amount: '1000000', recipient: '0x...' }); } catch (error) { if (error instanceof AgentPaymentError) { if (error.code === AgentPaymentErrorCode.INSUFFICIENT_BALANCE) { console.log('Not enough tokens:', error.suggestion); } else if (error.code === AgentPaymentErrorCode.LIMIT_EXCEEDED) { console.log('Spending limit exceeded'); } } } ``` ## Next Steps Send tokens between different blockchains Enable smooth UX for multiple transfers Set up safety limits ================================================================================ URL: /guides/wallet-backup-recovery ================================================================================ # Wallet Backup & Recovery Lost your phone? Switched devices? This guide shows you how to back up your Veridex wallet so you can recover it anywhere — even if your original device is gone. > **Note:** **What gets backed up?** Your passkey *credentials* (public keys, key hashes, credential IDs) — the information needed to identify your wallet. The actual passkey private keys live in your device's secure enclave and are never extracted. Recovery works because your passkey syncs via your platform (iCloud Keychain, Google Password Manager) or because you've added backup devices. --- ## How It Works (Plain English) Think of it like this: 1. **Your passkey** = the key to your house. It lives on your device (phone, laptop). 2. **Your credentials** = a list of all the locks that key can open (wallet addresses, key hashes). 3. **Backup** = you write down that list, put it in a locked box (encrypted), and store the box in the cloud (relayer). 4. **The lock on the box** = your passkey's PRF output (a secret your authenticator generates deterministically). Only your passkey can open it. 5. **Recovery** = you use your passkey on a new device to open the box and get the list back. ``` ┌─────────┐ encrypt ┌──────────┐ store ┌──────────┐ │ Device │ ──────────────→ │ Encrypted │ ────────────→ │ Relayer │ │ Creds │ (PRF key) │ Archive │ │ (cloud) │ └─────────┘ └──────────┘ └──────────┘ ┌──────────┐ fetch ┌──────────┐ decrypt │ New │ ←──────────── │ Relayer │ ←──────────── │ Device │ (PRF key) │ (cloud) │ restore! └──────────┘ └──────────┘ ``` --- ## Prerequisites - `@veridex/sdk` installed (`npm install @veridex/sdk ethers`) - An active passkey (you've already registered — see [Create Wallet](/guides/create-wallet)) - A browser that supports WebAuthn (Chrome, Safari, Firefox, Edge) --- ## Step 1: Check Platform Support Before backing up, check if your device supports PRF (the strongest encryption method): ```typescript const support = await WalletBackupManager.checkPlatformSupport(); if (!support.webauthnSupported) { console.log('WebAuthn not supported — backup not available'); } else if (support.prfSupported) { console.log('Full PRF support — your backup will be strongly encrypted'); } else { console.log('PRF not supported — backup will use a weaker fallback'); // Still works! Just less secure. } ``` > **Note:** **What is PRF?** PRF (Pseudo-Random Function) is a WebAuthn extension that lets your authenticator generate a deterministic secret. This secret becomes the encryption key for your backup. Without PRF, the SDK falls back to using your credential ID as the key source — this works but is less secure because credential IDs are not secret. ### Which Browsers Support PRF? | Browser | PRF Support | |---------|------------| | Chrome 116+ | Yes | | Safari 17+ | Yes | | Firefox | Not yet | | Edge 116+ | Yes | --- ## Step 2: Create a Backup ```typescript // Initialize const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); // Authenticate first (you need an active credential) const { credential } = await sdk.passkey.authenticate(); // Create the backup manager const backup = new WalletBackupManager({ passkey: sdk.passkey, relayerUrl: 'https://relayer.veridex.network/api/v1', }); // Back up your credentials const result = await backup.backupCredentials(credential.keyHash); console.log('Backup complete!'); console.log('Archive ID:', result.archiveId); console.log('Credentials backed up:', result.credentialCount); ``` **What happens when you press the button:** 1. Your browser asks for biometric verification (FaceID/TouchID) 2. The passkey generates a PRF seed (a deterministic secret) 3. The SDK derives an AES-256-GCM encryption key from that seed 4. All your stored credentials are encrypted 5. The encrypted blob is uploaded to the Veridex relayer > **Note:** The relayer **cannot read your credentials** — they're encrypted with a key only your passkey can produce. Even if the relayer is compromised, your data is safe. --- ## Step 3: Recover on a New Device Got a new phone or laptop? Here's how to get your wallet back: ```typescript const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); const backup = new WalletBackupManager({ passkey: sdk.passkey, relayerUrl: 'https://relayer.veridex.network/api/v1', }); // Recover — uses your passkey to decrypt the backup const recovered = await backup.recoverCredentials(keyHash); console.log('Recovered', recovered.credentials.length, 'credentials!'); // Your credentials are now in localStorage — you can use the SDK normally const vault = sdk.getVaultAddress(); console.log('Welcome back! Your vault:', vault); ``` > **Note:** **You need your passkey!** Recovery requires the same passkey (or a synced copy). If you've lost ALL devices with your passkey and it wasn't synced, you'll need social recovery via guardians (a future feature). --- ## Step 4: Check Backup Status See if a backup exists and how fresh it is: ```typescript const status = await backup.getBackupStatus(keyHash); if (status.hasArchive) { console.log('Backup exists'); console.log('Last updated:', new Date(status.archiveUpdatedAt)); console.log('PRF supported:', status.prfSupported); console.log('Guardians:', status.guardianCount); } else { console.log('No backup found — create one!'); } ``` --- ## Adding Extra Devices (Best Practice) The **best protection** is having your passkey on multiple devices. If your phone breaks, your laptop still has access. ### How to Add a Device 1. Go to your account page at `auth.veridex.network/account` 2. Click **"Add Another Device"** 3. Your browser will show a QR code — scan it with your other device 4. The new device creates a passkey linked to the same identity > **Note:** **Cross-ecosystem tip:** Passkeys sync automatically within the same ecosystem (Apple→Apple, Google→Google). To bridge across ecosystems (e.g., iPhone to Windows PC), use the "Add Another Device" flow — it uses QR/Bluetooth to set up the passkey on the other device. ### How Passkey Sync Works ``` ┌──────────────────────────────────────────────────────┐ │ Apple Ecosystem │ │ iPhone ←──→ iPad ←──→ MacBook │ │ (iCloud Keychain syncs passkeys) │ └──────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────┐ │ Google Ecosystem │ │ Android ←──→ Chrome (Windows) ←──→ Chromebook │ │ (Google Password Manager syncs passkeys) │ └──────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────┐ │ Cross-Ecosystem (via Add Device) │ │ iPhone ──QR/BLE──→ Windows PC │ │ Android ──QR/BLE──→ MacBook │ └──────────────────────────────────────────────────────┘ ``` --- ## Complete React Example ```tsx export function BackupManager() { const [status, setStatus] = useState<'idle' | 'backing-up' | 'recovering' | 'done'>('idle'); const [backupExists, setBackupExists] = useState(false); const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); const backup = new WalletBackupManager({ passkey: sdk.passkey, relayerUrl: 'https://relayer.veridex.network/api/v1', }); // Check backup status on mount useEffect(() => { const cred = sdk.passkey.getCredential(); if (cred?.keyHash) { backup.getBackupStatus(cred.keyHash).then((s) => { setBackupExists(s.hasArchive); }); } }, []); const handleBackup = async () => { setStatus('backing-up'); const cred = sdk.passkey.getCredential(); if (!cred) return; await backup.backupCredentials(cred.keyHash); setBackupExists(true); setStatus('done'); }; const handleRecover = async () => { setStatus('recovering'); const cred = sdk.passkey.getCredential(); if (!cred) return; await backup.recoverCredentials(cred.keyHash); setStatus('done'); }; return ( Wallet Backup {backupExists ? ( ✅ Backup exists on the Veridex relayer ) : ( ⚠️ No backup found )} {status === 'backing-up' ? 'Backing up...' : 'Create Backup'} {status === 'recovering' ? 'Recovering...' : 'Recover Wallet'} ); } ``` --- ## What Happens if I Lose Everything? | Scenario | Solution | |----------|----------| | Lost one device, but passkey synced to another | Use the other device — everything works | | Lost device, have backup on relayer | Use any synced passkey to recover | | Lost all devices, passkey synced via iCloud/Google | Sign in on a new device to restore passkey, then recover | | Lost all devices, passkey was NOT synced | Social recovery via guardians (coming soon) | --- ## FAQ No. Your backup is encrypted with a key derived from your passkey's PRF output. The relayer stores the encrypted blob but cannot decrypt it. Even if the relayer database is compromised, your credentials are safe. Back up whenever you add a new device or register a new passkey. The backup is a snapshot of your credential list at that moment. The SDK falls back to a credential-ID-based encryption key. This still works but is less secure since credential IDs are not secret. You'll see a warning in the account page. Consider adding a device that supports PRF (Chrome 116+, Safari 17+) for stronger encryption. No. Encrypted backups are stored on the Veridex relayer (a server), not on any blockchain. This is intentional — on-chain storage would be expensive and the encrypted blobs can be large. Guardian-based social recovery is a Phase 2 feature. The database models for guardians are already in place. When ready, you'll be able to designate trusted contacts who can help you recover your wallet even if you've lost all devices and your passkey wasn't synced. ================================================================================ URL: /guides/cross-domain-passkeys ================================================================================ # Cross-Domain Passkey Integration This guide explains how third-party applications can enable Veridex passkey authentication, allowing users to sign in with passkeys they've already created on `veridex.network`. ## Overview Veridex supports a "Google-style" authentication flow that works across all domains. Users carry their identity (and assets) to your application without creating new accounts. Two methods are supported, and the SDK handles both automatically: 1. **Related Origin Requests (ROR)** — The modern WebAuthn Level 3 standard. If the user's browser supports ROR and your origin is listed in the `.well-known/webauthn` file, the passkey dialog appears natively. No popup needed. 2. **Auth Portal Fallback** — A popup/redirect flow via `auth.veridex.network`. Works on all browsers. The user signs with their passkey at the portal, and a session is returned to your app. > **Note:** You don't need to choose between these methods. The SDK detects browser support and picks the best flow automatically. --- ## Quick Start ### Step 1: Register Your App Every third-party origin must be registered before it can use Veridex cross-domain passkeys. Registration is free and instant. **Option A: Developer Platform (UI)** 1. Go to the [Developer Platform](https://developers.veridex.network). 2. Enter your **Application Name**, **Origin** (e.g., `https://myapp.com`), and optionally a **Contact Email**. 3. You'll receive an **App ID** and **API Key**. Save the API key — it's shown only once. **Option B: API** ```bash curl -X POST https://relayer.veridex.network/api/v1/apps/register \ -H "Content-Type: application/json" \ -d '{ "name": "My DeFi App", "origin": "https://myapp.com", "contactEmail": "dev@myapp.com" }' ``` **Response:** ```json { "success": true, "app": { "id": "app_a1b2c3d4e5f6", "name": "My DeFi App", "origin": "https://myapp.com", "apiKey": "vdx_sk_...", "trustLevel": "registered", "status": "active" } } ``` > **Note:** **Save your API Key immediately.** It is only shown once during registration. You'll need it to manage your app or delete it later. ### Step 2: Install the SDK ```bash npm install @veridex/sdk ``` ### Step 3: Implement Authentication ```typescript const auth = createCrossOriginAuth(); async function signIn() { try { // The SDK automatically detects ROR support and falls back to Auth Portal if (await auth.supportsRelatedOrigins()) { // Seamless: native passkey dialog, no popup const result = await auth.authenticate(); console.log('Signed in (ROR):', result.credential.keyHash); return result; } // Fallback: popup to auth.veridex.network const session = await auth.connectWithVeridex(); console.log('Signed in (Portal):', session.address); return session; } catch (error) { console.error('Sign in failed:', error); } } ``` ### Step 4: Create a Server Session (Recommended) After authentication, create a **server-validated session token** via the relayer. This gives you a token that your backend can verify independently. The SDK handles the entire session flow in a **single popup** — no double-prompt. ```typescript const auth = createCrossOriginAuth(); async function signInWithServerSession() { // Full flow: authenticate + create server-validated session // If ROR is available: native passkey dialog (no popup at all) // If ROR is unavailable: single popup handles auth + session creation const { session, serverSession } = await auth.authenticateAndCreateSession({ permissions: ['read', 'transfer'], expiresInMs: 3600000, // 1 hour }); console.log('Vault address:', session.address); console.log('Server session ID:', serverSession.id); console.log('Expires at:', new Date(serverSession.expiresAt)); // Store the session ID for subsequent API calls localStorage.setItem('veridex_session_id', serverSession.id); } ``` > **Note:** **Single-popup flow:** When using `authenticateAndCreateSession()`, the auth portal handles session challenge creation internally. The user sees only one popup with one biometric prompt — not two separate ones. ``` You can validate this session from your backend at any time: ```bash curl https://relayer.veridex.network/api/v1/session/ses_abc123 ``` --- ## How It Works ### Architecture ```mermaid flowchart TD A["Your Application (https://myapp.com)"] --> B["@veridex/sdk"] B --> CROR Supported? C -- Yes --> D["Native Passkey Dialog"] C -- No --> E["Auth Portal Popup/Redirect"] D --> F["Server Session Token"] E --> F F -- "POST /session/create" --> G["Validated by Relayer"] ``` ### The User Experience 1. User clicks **"Connect with Veridex"** on your site. 2. **If ROR is supported**: The browser's native passkey dialog appears immediately. User verifies with biometrics. Done. 3. **If ROR is not supported**: - A popup opens to `auth.veridex.network`. - User sees a clean "Sign in to [Your App]" card (your app name from registration). - User verifies with their passkey. - Popup closes and returns the session to your app. ### What You Get Back After authentication, you receive a `CrossOriginSession` containing: | Field | Type | Description | |-------|------|-------------| | `address` | `string` | User's vault address (same on all EVM chains) | | `sessionPublicKey` | `string` | Session key for signing transactions | | `expiresAt` | `number` | Session expiry timestamp (ms) | | `signature` | `WebAuthnSignature` | Proof of passkey ownership | | `credential` | `PasskeyCredential` | The credential used (keyHash, publicKey) | | `serverSessionId` | `string?` | Server-validated session ID (if created) | --- ## Configuration Options ### `createCrossOriginAuth` ```typescript const auth = createCrossOriginAuth({ // The canonical Relying Party ID. Must be 'veridex.network' for cross-domain. rpId: 'veridex.network', // default // Auth Portal URL for popup/redirect flow. authPortalUrl: 'https://auth.veridex.network', // default // Relayer API URL for server-side session tokens. relayerUrl: 'https://relayer.veridex.network/api/v1', // default // Authentication mode: 'popup' (default) or 'redirect'. // Use 'redirect' for mobile browsers where popups might be blocked. mode: 'popup', // URL to redirect back to (only for mode: 'redirect'). redirectUri: 'https://myapp.com/callback', // Timeout for auth operations (ms). Default: 120000 (2 minutes). timeout: 120000, }); ``` ### Redirect Mode If using `mode: 'redirect'`, handle the callback on your `redirectUri` page: ```typescript // pages/callback.tsx export default function Callback() { useEffect(() => { const auth = createCrossOriginAuth(); const session = auth.completeRedirectAuth(); if (session) { localStorage.setItem('veridex_session', JSON.stringify(session)); window.location.href = '/dashboard'; } }, []); return Completing sign in...; } ``` > **Note:** Use `mode: 'redirect'` for mobile web apps. Many mobile browsers block popups by default. --- ## Server-Side Session Tokens Server session tokens let your backend verify a user's identity without trusting the client. ### Create a Session The easiest way is `authenticateAndCreateSession()` (shown above). If you need manual control: ```typescript // 1. Authenticate first const session = await auth.connectWithVeridex(); // 2. Create a server-validated token const serverSession = await auth.createServerSession(session); console.log('Session ID:', serverSession.id); ``` ### Validate a Session (from your backend) ```bash curl https://relayer.veridex.network/api/v1/session/ses_abc123 ``` ```json { "valid": true, "session": { "id": "ses_abc123", "keyHash": "0x1234...", "appOrigin": "https://myapp.com", "permissions": ["read", "transfer"], "expiresAt": 1738900000000 } } ``` ### Revoke a Session ```typescript await auth.revokeServerSession('ses_abc123'); ``` Or via API: ```bash curl -X DELETE https://relayer.veridex.network/api/v1/session/ses_abc123 ``` --- ## Managing Your App ### Check Registration Status Verify your origin is registered and active: ```bash curl "https://relayer.veridex.network/api/v1/origins/validate?origin=https://myapp.com" ``` ```json { "allowed": true, "reason": "registered", "origin": "https://myapp.com" } ``` ### Get App Details ```bash curl https://relayer.veridex.network/api/v1/apps/app_a1b2c3d4e5f6 \ -H "X-API-Key: vdx_sk_..." ``` ### Delete Your App ```bash curl -X DELETE https://relayer.veridex.network/api/v1/apps/app_a1b2c3d4e5f6 \ -H "X-API-Key: vdx_sk_..." ``` > **Note:** Deleting your app will immediately revoke all active user sessions for your origin. --- ## Hosting `.well-known/webauthn` If you want to enable the **seamless ROR flow** (no popup) on your domain, you can optionally host a `.well-known/webauthn` file. This is not required — the Auth Portal fallback works without it — but it provides the best user experience. ### File Contents Create `public/.well-known/webauthn` in your project: ```json { "origins": [ "https://veridex.network", "https://auth.veridex.network", "https://myapp.com" ] } ``` ### Serving Requirements The file **must** be served with these headers: | Header | Value | |--------|-------| | `Content-Type` | `application/json` | | `Access-Control-Allow-Origin` | `*` | **Netlify** (`netlify.toml`): ```toml [[headers]] for = "/.well-known/webauthn" [headers.values] Content-Type = "application/json" Access-Control-Allow-Origin = "*" ``` **Vercel** (`vercel.json`): ```json { "headers": [ { "source": "/.well-known/webauthn", "headers": [ { "key": "Content-Type", "value": "application/json" }, { "key": "Access-Control-Allow-Origin", "value": "*" } ] } ] } ``` **Next.js** (middleware or API route): ```typescript // app/.well-known/webauthn/route.ts export async function GET() { return Response.json( { origins: ['https://veridex.network', 'https://auth.veridex.network', 'https://myapp.com'] }, { headers: { 'Access-Control-Allow-Origin': '*' } } ); } ``` > **Note:** The canonical `.well-known/webauthn` file at `veridex.network` is managed by the Veridex team. Your origin is automatically added when you register via the Developer Platform. You only need to host this file on **your own domain** if you want ROR to work in both directions. --- ## Chrome eTLD+1 Limit Chrome currently limits `.well-known/webauthn` to **5 unique eTLD+1 labels**. This means only 5 different top-level domains can be listed (e.g., `example.com`, `myapp.io`, `game.xyz` = 3 labels). Subdomains of the same eTLD+1 share one label. ### What This Means for You - All `*.veridex.network` subdomains count as **1 label**. - If the canonical file at `veridex.network` already has 5 eTLD+1 labels, additional third-party domains will use the **Auth Portal fallback** instead of ROR. - The Auth Portal provides the same functionality — just with a popup instead of a native dialog. ### Mitigation Strategies | Strategy | Description | |----------|-------------| | **Auth Portal Fallback** | Automatic. Origins beyond the 5-label limit use the popup flow. | | **Subdomain Proxying** | High-value partners can be assigned `partner.veridex.network` subdomains (doesn't count against the limit). | | **Priority Rotation** | The `.well-known/webauthn` file prioritizes the most active eTLD+1 labels by usage. | > **Note:** This limit is a Chrome implementation detail, not a spec requirement. As the WebAuthn spec matures, this limit is expected to increase. The Auth Portal fallback ensures your integration works regardless. --- ## React Integration Example ```tsx const auth = createCrossOriginAuth(); export function ConnectVeridex() { const [session, setSession] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const handleConnect = async () => { setLoading(true); setError(null); try { const { session, serverSession } = await auth.authenticateAndCreateSession({ permissions: ['read', 'transfer'], }); setSession({ ...session, serverSessionId: serverSession.id }); } catch (err) { setError(err instanceof Error ? err.message : 'Connection failed'); } finally { setLoading(false); } }; const handleDisconnect = async () => { if (session?.serverSessionId) { await auth.revokeServerSession(session.serverSessionId); } setSession(null); }; if (session) { return ( Connected: {session.address.slice(0, 6)}...{session.address.slice(-4)} Disconnect ); } return ( {loading ? 'Connecting...' : 'Connect with Veridex'} {error && {error}} ); } ``` --- ## Security Best Practices 1. **Register your production domain.** Only registered origins can receive sessions from the Auth Portal. Unregistered origins are blocked. 2. **Use server-side session tokens.** Don't trust client-side session data alone. Create a server session and validate it from your backend. 3. **Always use HTTPS.** WebAuthn requires a secure context. The only exception is `localhost` for development. 4. **Validate sessions on your backend.** Call `GET /api/v1/session/:id` to verify a session is valid before granting access to sensitive operations. 5. **Revoke sessions on logout.** Call `DELETE /api/v1/session/:id` when the user logs out to prevent session reuse. 6. **Keep your API key secret.** Your API key (`vdx_sk_...`) is used to manage your app registration. Never expose it in client-side code. --- ## Troubleshooting | Issue | Cause | Solution | |-------|-------|----------| | "Origin not authorized" error | Your origin isn't registered | Register at [developers.veridex.network](https://developers.veridex.network) | | Popup blocked | Browser blocking popups | Use `mode: 'redirect'` instead, or ask users to allow popups | | ROR not working | Browser doesn't support Related Origin Requests | The SDK automatically falls back to Auth Portal. No action needed. | | CORS error on relayer | Origin not in CORS allowlist | Registered origins are automatically added. Check your registration status. | | Session expired | Server session token expired | Create a new session with `authenticateAndCreateSession()` | | "Credential not found" on session create | User hasn't registered a passkey yet | User must create a passkey at `veridex.network` first | --- ## API Reference See the full [Relayer API Reference](/api-reference/relayer) for all cross-domain endpoints: - `POST /api/v1/apps/register` — Register your app - `GET /api/v1/apps` — List registered apps - `GET /api/v1/origins/validate` — Check if an origin is allowed - `POST /api/v1/session/create` — Create a server session - `GET /api/v1/session/:id` — Validate a session - `DELETE /api/v1/session/:id` — Revoke a session ## Next Steps Transfer tokens from your wallet Enable smooth UX without repeated prompts Submit transactions without gas fees Full SDK API documentation ================================================================================ URL: /guides/gasless-transactions ================================================================================ # Gasless Transactions Veridex enables truly gasless transactions — users never need to hold ETH or other gas tokens. ## How It Works ```mermaid flowchart LR A["User\nSigns with passkey"] --> B["Relayer\nReceives and submits\n(pays gas)"] B --> C["Blockchain\nVerifies signature\non-chain"] ``` 1. User signs a payload with their passkey 2. Relayer receives the signed payload 3. Relayer submits the transaction and pays gas 4. Contract verifies the passkey signature on-chain ## Basic Usage ```typescript const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', // Veridex relayer }); const { credential } = await sdk.passkey.authenticate(); // Gasless cross-chain bridge via relayer const result = await sdk.bridgeViaRelayer( { sourceChain: 10004, // Base Sepolia destinationChain: 10005, // Optimism Sepolia token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: sdk.getVaultAddress(), amount: ethers.parseUnits('100', 6), }, (progress) => console.log(progress.message) ); console.log('Gasless bridge complete:', result.transactionHash); ``` ## Relayer Configuration ### Use Veridex Relayer ```typescript const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); ``` ### Run Your Own Relayer ```typescript // Point to your self-hosted relayer const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'http://localhost:3001', }); ``` See [Relayer Setup](/api-reference/relayer) for self-hosting instructions. ## Sponsored Vault Creation New vaults can be created gaslessly via the `GasSponsor`: ```typescript // User has no ETH, but can still create a vault const result = await sdk.sponsor.createVault(10005, credential); console.log('Vault created on Optimism:', result.vaultAddress); ``` For integrators who want to sponsor vault creation for their users, pass a `sponsorPrivateKey` or `integratorSponsorKey` in the SDK config: ```typescript const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', sponsorPrivateKey: process.env.SPONSOR_KEY, }); ``` ## Fee Model While users don't pay gas, the relayer does. Relayer economics: | Model | Description | |-------|-------------| | **Sponsored** | Relayer absorbs gas costs (promotional/enterprise) | | **Protocol Fee** | Small fee deducted from transfer amount | | **Subscription** | User/app pays monthly for gasless access | ### Check Bridge Fees ```typescript const fees = await sdk.getBridgeFees({ sourceChain: 10004, destinationChain: 10005, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: sdk.getVaultAddress(), amount: 100000000n, }); console.log('Source gas:', fees.sourceGas); console.log('Message fee:', fees.messageFee); console.log('Relayer fee:', fees.relayerFee); console.log('Total:', fees.formattedTotal, fees.currency); ``` ## Non-Relayer Fallback If you want direct on-chain transactions (user pays gas): ```typescript // Connect user's wallet for gas const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const sdk = createSDK('base', { network: 'testnet' }); // Prepare and execute directly (user pays gas) const prepared = await sdk.prepareTransfer({ targetChain: 10004, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: '0x...', amount: ethers.parseUnits('100', 6), }); const result = await sdk.executeTransfer(prepared, signer); ``` ## Stacks: Native Sponsored Transactions Stacks has **native sponsored transactions** — the relayer can pay STX gas fees at the protocol level: ```typescript const sdk = createSDK('stacks', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); // The relayer automatically sponsors Stacks transactions // Users never need to hold STX for gas ``` Stacks sponsorship limits: | Limit | Value | |-------|-------| | Per-identity rate | 50 tx/hour | | Per-tx fee cap | 0.5 STX | | Hourly budget | 50 STX | ## Agent SDK: Gasless Payments The Agent SDK handles gasless operations automatically: ```typescript const agent = await createAgentWallet({ masterCredential: { /* ... */ }, session: { dailyLimitUSD: 50, perTransactionLimitUSD: 10, expiryHours: 24, allowedChains: [10004], }, relayerUrl: 'https://relayer.veridex.network', }); // All agent payments are gasless via the relayer const receipt = await agent.pay({ chain: 10004, token: 'USDC', amount: '1000000', recipient: '0x...', }); // x402 payments are also gasless const response = await agent.fetch('https://paid-api.example.com/data'); ``` ## React Integration ```tsx const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); export function GaslessBridge() { const [targetChain, setTargetChain] = useState('10005'); const [amount, setAmount] = useState(''); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(null); const handleBridge = async () => { setLoading(true); try { const result = await sdk.bridgeViaRelayer( { sourceChain: 10004, destinationChain: parseInt(targetChain), token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: sdk.getVaultAddress(), amount: ethers.parseUnits(amount, 6), }, (p) => setProgress(`Step ${p.step}: ${p.message}`) ); setProgress(`Done! Tx: ${result.transactionHash}`); } catch (error: any) { setProgress(`Error: ${error.message}`); } setLoading(false); }; return ( Gasless Bridge You don't need ETH — the relayer pays gas fees setTargetChain(e.target.value)} className="block w-full rounded-md border p-2" > Optimism Sepolia Arbitrum Sepolia Stacks Testnet setAmount(e.target.value)} placeholder="Amount (USDC)" className="block w-full rounded-md border p-2" /> {loading ? 'Bridging...' : 'Bridge (Gasless)'} {progress && {progress}} ); } ``` ## Relayer Health Check Check relayer status via the REST API: ```typescript // Direct health check via fetch const response = await fetch('https://relayer.veridex.network/health'); const health = await response.json(); console.log('Status:', health.status); console.log('Chains:', health.chains); ``` See [Relayer API Reference](/api-reference/relayer) for all endpoints. ## Error Handling ```typescript try { await sdk.bridgeViaRelayer(params, onProgress); } catch (error: any) { if (error.message?.includes('relayer')) { // Fallback to direct transaction console.log('Relayer unavailable, user needs gas'); const prepared = await sdk.prepareBridge(params); const result = await sdk.executeBridge(prepared, signer); } else if (error.message?.includes('rate')) { console.log('Rate limited, try again later'); } else { console.error('Bridge failed:', error); } } ``` ## Benefits | Benefit | Description | |---------|-------------| | **Zero gas UX** | Users never see "insufficient ETH" errors | | **Lower friction** | No need to acquire gas tokens first | | **Cross-chain ready** | Gasless on all supported chains | | **Enterprise friendly** | Predictable costs for sponsors | ## Next Steps Gasless autonomous agent payments Self-host your own relayer Native sponsored transactions on Stacks ================================================================================ URL: /guides/session-keys ================================================================================ # Session Keys Session keys enable smooth UX by delegating limited authority to a temporary key, avoiding repeated biometric prompts. ## Why Session Keys? Without session keys, every action requires biometric authentication: ``` Transfer 1 → FaceID prompt Transfer 2 → FaceID prompt Transfer 3 → FaceID prompt ``` With session keys: ``` Create session → FaceID prompt (once) Transfer 1 → Auto-signed ✓ Transfer 2 → Auto-signed ✓ Transfer 3 → Auto-signed ✓ ``` ## Core SDK: Session Key Management The core SDK provides low-level session key utilities: ```typescript createSDK, SessionManager, generateSecp256k1KeyPair, computeSessionKeyHash, signWithSessionKey, hashAction, deriveEncryptionKey, encrypt, decrypt, MAX_SESSION_DURATION, DEFAULT_SESSION_DURATION, } from '@veridex/sdk'; const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); // Authenticate first (shows passkey picker) const { credential } = await sdk.passkey.authenticate(); // Generate a session key pair (secp256k1) const keyPair = generateSecp256k1KeyPair(); console.log('Session public key:', keyPair.publicKey); // Compute session key hash for on-chain registration const sessionHash = computeSessionKeyHash(keyPair.publicKey); console.log('Session hash:', sessionHash); // Sign an action with the session key const actionHash = hashAction('transfer', { /* params */ }); const signature = signWithSessionKey(keyPair.privateKey, actionHash); ``` ## Encrypted Session Storage Session keys can be encrypted and stored securely: ```typescript generateSecp256k1KeyPair, deriveEncryptionKey, encrypt, decrypt, createSessionStorage, IndexedDBSessionStorage, LocalStorageSessionStorage, } from '@veridex/sdk'; // Generate key pair const keyPair = generateSecp256k1KeyPair(); // Derive encryption key from master credential const encryptionKey = deriveEncryptionKey('master-key-hash'); // Encrypt the session private key const encrypted = encrypt(keyPair.privateKey, encryptionKey); // Store encrypted session const storage = createSessionStorage(); // Auto-selects best storage // Or explicitly: const indexedDB = new IndexedDBSessionStorage(); const localStorage = new LocalStorageSessionStorage(); // Later: decrypt to use const decrypted = decrypt(encrypted, encryptionKey); ``` ## Agent SDK: SessionKeyManager The Agent SDK provides a higher-level `SessionKeyManager` with USD-denominated spending limits: ```typescript const agent = await createAgentWallet({ masterCredential: { credentialId: 'abc123', publicKeyX: BigInt('0x...'), publicKeyY: BigInt('0x...'), keyHash: '0x...', }, session: { dailyLimitUSD: 50, // $50/day perTransactionLimitUSD: 10, // $10 max per tx expiryHours: 24, // 24-hour session allowedChains: [10004], // Base Sepolia only }, }); ``` ### Session Constraints (Agent SDK) | Constraint | Description | |------------|-------------| | `dailyLimitUSD` | Maximum USD spend per 24-hour period | | `perTransactionLimitUSD` | Maximum USD per single transaction | | `expiryHours` | Session duration in hours | | `allowedChains` | Whitelist of Wormhole chain IDs | ## Use Session for Payments (Agent SDK) Once an agent is initialized, payments use the session key automatically: ```typescript // Agent session is active — no biometric needed for payments await agent.pay({ chain: 10004, token: 'USDC', amount: '1000000', recipient: addr1 }); await agent.pay({ chain: 10004, token: 'USDC', amount: '2000000', recipient: addr2 }); await agent.pay({ chain: 10004, token: 'USDC', amount: '3000000', recipient: addr3 }); // All auto-signed with session key! ✓ // x402 payments also use the session key const response = await agent.fetch('https://paid-api.example.com/data'); ``` ## Check Session Status ```typescript // Agent SDK: check session status const status = agent.getSessionStatus(); console.log('Valid:', status.isValid); console.log('Wallet:', status.address); console.log('Expires:', new Date(status.expiry)); console.log('Daily limit:', `$${status.limits!.dailyLimitUSD}`); console.log('Spent today:', `$${status.totalSpentUSD.toFixed(2)}`); console.log('Remaining:', `$${status.remainingDailyLimitUSD.toFixed(2)}`); ``` ## Revoke Session ```typescript // Agent SDK: revoke session await agent.revokeSession(); console.log('Session revoked — agent can no longer make payments'); ``` ## Session Persistence The core SDK provides multiple storage backends for session keys: ```typescript // Auto-select best storage (IndexedDB > localStorage) const storage = createSessionStorage(); // Or use IndexedDB explicitly for better security const secureStorage = new IndexedDBSessionStorage(); ``` The Agent SDK's `SessionKeyManager` handles persistence automatically, including encrypted storage of session private keys. ## React Example (Agent SDK) ```tsx export function AgentSessionPanel() { const [agent, setAgent] = useState(null); const [status, setStatus] = useState(null); const [loading, setLoading] = useState(false); const initAgent = async () => { setLoading(true); try { const newAgent = await createAgentWallet({ masterCredential: { credentialId: 'stored-credential-id', publicKeyX: BigInt('0x...'), publicKeyY: BigInt('0x...'), keyHash: '0x...', }, session: { dailyLimitUSD: 50, perTransactionLimitUSD: 10, expiryHours: 8, allowedChains: [10004], }, }); setAgent(newAgent); setStatus(newAgent.getSessionStatus()); } catch (error) { console.error('Failed to create agent:', error); } setLoading(false); }; const revokeSession = async () => { if (agent) { await agent.revokeSession(); setAgent(null); setStatus(null); } }; if (status) { const expiresIn = Math.floor((status.expiry - Date.now()) / 1000 / 60); return ( Agent Session Active Expires in {expiresIn} minutes Budget: ${status.totalSpentUSD.toFixed(2)} / ${status.limits!.dailyLimitUSD} Wallet: {status.address.slice(0, 8)}...{status.address.slice(-6)} Revoke Session ); } return ( No Active Agent Session Initialize an agent with spending limits {loading ? 'Initializing...' : 'Create Agent Session'} ); } ``` ## Batch Payments (Agent SDK) Agent sessions are perfect for batch operations: ```typescript const agent = await createAgentWallet({ masterCredential: { /* ... */ }, session: { dailyLimitUSD: 100, perTransactionLimitUSD: 25, expiryHours: 1, allowedChains: [10004], }, }); // Batch multiple payments — all auto-signed const recipients = [ { address: '0xaaa...', amount: '10000000' }, // 10 USDC { address: '0xbbb...', amount: '20000000' }, // 20 USDC { address: '0xccc...', amount: '30000000' }, // 30 USDC ]; for (const { address, amount } of recipients) { const receipt = await agent.pay({ chain: 10004, token: 'USDC', amount, recipient: address, }); console.log(`Paid ${address}: ${receipt.txHash}`); } // Check remaining budget const status = agent.getSessionStatus(); console.log(`Spent: $${status.totalSpentUSD.toFixed(2)} / $${status.limits!.dailyLimitUSD}`); // Revoke when done await agent.revokeSession(); ``` ## Security Considerations > **Note:** Session keys have access to your vault within their constraints. Only create sessions when needed and revoke when done. Best practices: 1. **Short durations**: Use the minimum needed duration 2. **Tight limits**: Set value limits appropriate for the use case 3. **Token whitelist**: Only allow tokens that will be used 4. **Revoke when done**: Don't leave sessions open unnecessarily 5. **Trusted devices only**: Don't create sessions on shared devices ## Error Handling ```typescript try { await agent.pay({ chain: 10004, token: 'USDC', amount: '100000000', recipient: '0x...' }); } catch (error) { if (error instanceof AgentPaymentError) { switch (error.code) { case AgentPaymentErrorCode.LIMIT_EXCEEDED: console.log('Spending limit exceeded:', error.message); break; case AgentPaymentErrorCode.SESSION_EXPIRED: console.log('Session expired, re-initialize agent'); break; case AgentPaymentErrorCode.INSUFFICIENT_BALANCE: console.log('Not enough tokens:', error.suggestion); break; } } } ``` ## Next Steps Build autonomous AI agent payments Configure spending limits for safety Session keys on Stacks with native secp256k1 ================================================================================ URL: /guides/multi-session ================================================================================ # Multi-Session Management Veridex supports multiple concurrent sessions per app and per agent. This guide covers when to use multiple sessions, how to manage them with both the Core SDK and Agent SDK, and patterns for production use. ## When to Use Multiple Sessions | Scenario | Sessions | Example | |----------|----------|---------| | Single-purpose integration | 1 | A payment widget that processes one flow | | Multi-chain app | 1 per chain | Base session + Solana session | | Role-based agents | 1 per role | Treasury agent, swapper agent, monitor agent | | Time-windowed budgets | 1 per window | Daily budget session, refreshed each morning | | Isolated workloads | 1 per workload | Production session + staging session | ## Core SDK: Multiple Sessions ### Create Multiple Sessions ```typescript const client = new VeridexClient({ apiKey: process.env.VERIDEX_API_KEY, chain: 'base', rpcUrl: 'https://...', }); // Create sessions with distinct labels and limits const treasury = await client.createSession({ label: 'treasury-ops', spendingLimit: '10000', validUntil: Date.now() + 86400_000, }); const swaps = await client.createSession({ label: 'swap-engine', spendingLimit: '5000', validUntil: Date.now() + 3600_000, }); ``` ### List Active Sessions ```typescript const sessions = await client.listSessions(); for (const s of sessions) { console.log(`${s.label}: ${s.status} — ${s.remainingLimit} remaining`); } ``` ### Execute with a Specific Session ```typescript const result = await client.execute({ session: treasury.sessionKeyHash, action: { type: 'transfer', to: '0x...', amount: '500', token: 'USDC', }, }); ``` ### Revoke a Session ```typescript await client.revokeSession(swaps.sessionKeyHash); ``` ## Agent SDK: Agent Sessions The Agent SDK (`@veridex/agentic-payments`) manages sessions through the `SessionKeyManager`. ### Session Inventory ```typescript const manager = new SessionKeyManager(signer, contractAddress, { chain: 'base', rpcUrl: 'https://...', }); // Create labeled sessions const session1 = await manager.createSession({ label: 'defi-ops', spendingLimit: '5000', allowedTokens: ['USDC', 'ETH'], validUntil: Date.now() + 86400_000, }); const session2 = await manager.createSession({ label: 'monitoring', spendingLimit: '100', validUntil: Date.now() + 86400_000, }); ``` ### List and Filter Sessions ```typescript const all = manager.listSessions(); const active = manager.listSessions({ status: 'active' }); const defi = manager.listSessions({ label: 'defi-ops' }); ``` ### Session Restoration On restart, the agent can restore sessions from storage: ```typescript const restored = await manager.restoreSession(sessionKeyHash); console.log(`Restored: ${restored.label} — ${restored.status}`); ``` ### MCP Tool: Session Inventory If using the MCP server, agents can query sessions via the `check_session_inventory` tool: ```json { "tool": "check_session_inventory", "parameters": {} } ``` Returns all sessions with label, status, remaining limit, and expiry. ## Patterns ### Pattern: Daily Budget Refresh Create a new session each day with a fresh spending limit. Revoke the previous day's session. ```typescript async function refreshDailyBudget(client: VeridexClient, dailyLimit: string) { // Revoke yesterday's session const sessions = await client.listSessions(); const expired = sessions.filter(s => s.label === 'daily-budget' && s.status === 'active'); for (const s of expired) { await client.revokeSession(s.sessionKeyHash); } // Create today's session return client.createSession({ label: 'daily-budget', spendingLimit: dailyLimit, validUntil: Date.now() + 86400_000, }); } ``` ### Pattern: Chain-Specific Sessions Create one session per chain to isolate cross-chain risk. ```typescript const baseSession = await client.createSession({ label: 'base-ops', spendingLimit: '5000', chainId: '8453', validUntil: Date.now() + 86400_000, }); const ethSession = await client.createSession({ label: 'eth-ops', spendingLimit: '10000', chainId: '1', validUntil: Date.now() + 86400_000, }); ``` ### Pattern: Graceful Session Rotation Rotate sessions without downtime by creating the new session before revoking the old one. ```typescript async function rotateSession(client: VeridexClient, oldHash: string, config: SessionConfig) { const newSession = await client.createSession(config); // New session is active — switch traffic await client.revokeSession(oldHash); return newSession; } ``` ## Session Studio Integration The [Session Studio](/platform/session-studio) in the Developer Platform provides a visual interface for multi-session management: - View all active, expired, and revoked sessions - Filter by label, chain, or agent - Health indicators show remaining limits and expiry - Create new sessions with the provisioning wizard ================================================================================ URL: /guides/spending-limits ================================================================================ # Spending Limits Spending limits add an extra layer of security to your vault by capping how much can be transferred within a time period. ## Overview Spending limits protect against: - Compromised session keys - Stolen devices (before recovery) - Accidental large transfers ## Three-Layer Spending Safety Veridex enforces spending limits at three levels: | Layer | Enforcement | Description | |-------|------------|-------------| | **SDK** | Off-chain | `SpendingLimitsManager` / `SpendingTracker` checks before signing | | **Contract** | On-chain | Smart contract enforces daily/per-tx limits | | **Post-Conditions** | Protocol (Stacks) | Stacks native guarantee on max transfer amounts | ## Core SDK: Check Spending Limits ```typescript const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); const { credential } = await sdk.passkey.authenticate(); // Get current on-chain spending limits const limits = await sdk.getSpendingLimits(); console.log('Daily limit:', limits.dailyLimit); console.log('Daily used:', limits.dailyUsed); console.log('Daily remaining:', limits.dailyRemaining); console.log('Per-tx limit:', limits.perTransactionLimit); console.log('Resets at:', new Date(limits.resetsAt)); ``` ## Check Before Transfer ```typescript // Check if a specific amount is within limits const check = await sdk.checkSpendingLimit(ethers.parseUnits('500', 6)); if (check.allowed) { console.log('Transfer allowed!'); // Proceed with prepareTransfer + transfer } else { console.log('Blocked:', check.message); console.log('Suggestions:', check.suggestions); } ``` ## Formatted Limits for UI ```typescript const formatted = await sdk.getFormattedSpendingLimits(); console.log(`${formatted.dailyUsedPercentage}% of daily limit used`); console.log(`Resets in: ${formatted.timeUntilReset}`); console.log(`Daily: ${formatted.dailyUsed} / ${formatted.dailyLimit}`); ``` ## Update Spending Limits ```typescript // Prepare a transaction to update the daily limit const prepared = await sdk.prepareSetDailyLimit(ethers.parseEther('5.0')); // Execute (requires passkey signature) const result = await sdk.executeTransfer(prepared, signer); console.log('Limit updated:', result.transactionHash); ``` ## Emergency Pause ```typescript // Prepare an emergency pause transaction const prepared = await sdk.preparePauseVault(); const result = await sdk.executeTransfer(prepared, signer); console.log('Vault paused:', result.transactionHash); ``` ## Agent SDK: USD-Denominated Limits The Agent SDK provides USD-aware spending limits via `SpendingTracker`: ```typescript const agent = await createAgentWallet({ masterCredential: { /* ... */ }, session: { dailyLimitUSD: 100, // $100/day perTransactionLimitUSD: 25, // $25 max per tx expiryHours: 24, allowedChains: [10004], // Base Sepolia }, }); // Check session spending status const status = agent.getSessionStatus(); console.log('Daily limit:', `$${status.limits!.dailyLimitUSD}`); console.log('Spent today:', `$${status.totalSpentUSD.toFixed(2)}`); console.log('Remaining:', `$${status.remainingDailyLimitUSD.toFixed(2)}`); // Set up spending alerts agent.onSpendingAlert((alert) => { if (alert.type === 'approaching_limit') { console.warn('Approaching daily limit!'); } else if (alert.type === 'limit_exceeded') { console.error('Transaction blocked by limit'); } }); ``` ### Stacks: StacksSpendingTracker For Stacks, the Agent SDK provides a specialized tracker with Pyth pricing: ```typescript const tracker = new StacksSpendingTracker({ dailyLimitUSD: 100, network: 'testnet', }); // Convert microSTX to USD using real-time Pyth price const usdValue = await tracker.convertToUSD(1000000n, 'STX'); console.log('1 STX =', usdValue, 'USD'); // Check if a payment is within limits const allowed = await tracker.checkPayment(5000000n); // 5 STX console.log('Payment allowed:', allowed); ``` ## React Example ```tsx const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); export function SpendingLimitsPanel() { const [formatted, setFormatted] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { loadLimits(); }, []); const loadLimits = async () => { const data = await sdk.getFormattedSpendingLimits(); setFormatted(data); setLoading(false); }; if (loading) return Loading limits...; if (!formatted) return No limits configured; const percentage = formatted.dailyUsedPercentage; return ( Spending Limits Daily: {formatted.dailyUsed} / {formatted.dailyLimit} Per-tx max: {formatted.perTransactionLimit} Resets in: {formatted.timeUntilReset} 90 ? 'bg-red-500' : percentage > 70 ? 'bg-yellow-500' : 'bg-green-500' }`} style={{ width: `${Math.min(percentage, 100)}%` }} /> {percentage.toFixed(1)}% of daily limit used ); } ``` ## Limit Exceeded Behavior Always check limits before attempting a transfer: ```typescript const amount = ethers.parseUnits('10000', 6); const check = await sdk.checkSpendingLimit(amount); if (!check.allowed) { console.log('Transfer blocked:', check.message); console.log('Suggestions:', check.suggestions); // Suggestions might include: "Wait for daily reset" or "Reduce amount to X" } ``` For Agent SDK: ```typescript try { await agent.pay({ chain: 10004, token: 'USDC', amount: '10000000000', recipient: '0x...' }); } catch (error) { if (error instanceof AgentPaymentError && error.code === AgentPaymentErrorCode.LIMIT_EXCEEDED) { console.log('Agent spending limit exceeded:', error.message); } } ``` ## Security Considerations > **Note:** Spending limits are enforced on-chain and cannot be bypassed by the SDK or relayer. Changing limits requires passkey authentication. Best practices: 1. **Set conservative defaults**: Start low, increase as needed 2. **Use token-specific limits**: Stablecoins may need different limits 3. **Review periodically**: Adjust limits based on usage patterns 4. **Combine with session keys**: Sessions have their own limits too ## Stacks Post-Conditions On Stacks, spending limits have an additional protocol-level layer via Post-Conditions: ```typescript // Build post-conditions that guarantee max transfer amount const postConditions = buildStxWithdrawalPostConditions({ vaultAddress: 'ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-vault', amount: 1000000n, // 1 STX max }); // Validate before submitting const validation = validateStacksPostConditions(postConditions); if (!validation.valid) { console.warn('Post-condition issues:', validation.warnings); } ``` Post-Conditions are enforced at the Stacks protocol level and **cannot be bypassed** by any contract or SDK code. ## Next Steps USD-denominated agent spending limits Session-level spending constraints Three-layer spending safety on Stacks ================================================================================ URL: /guides/balance-watching ================================================================================ # Balance Watching The SDK provides a polling-based subscription API for vault balance changes. Subscribe to incoming transfers, outgoing withdrawals, or spending limit resets without implementing your own polling loop. ## Quick Start ### Subscribe to Balance Changes ```typescript const unsub = sdk.watchBalance( (event) => { console.log(`${event.changes.length} token(s) changed`); for (const change of event.changes) { const direction = change.delta > 0n ? 'received' : 'sent'; console.log(`${direction} ${change.token.symbol}: ${change.delta}`); } }, { intervalMs: 10_000, emitInitial: true }, ); ``` ### Handle Errors ```typescript const unsub = sdk.watchBalance( (event) => { /* handle changes */ }, { intervalMs: 10_000 }, (error) => { console.error('Balance poll failed:', error.message); // The watcher continues on error — it won't crash }, ); ``` ### Stop Watching ```typescript // Unsubscribe when done (e.g., component unmount) unsub(); ``` --- ## Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `intervalMs` | `number` | `15000` | Poll interval in milliseconds | | `minIntervalMs` | `number` | `5000` | Minimum allowed interval (floor) | | `emitInitial` | `boolean` | `false` | Emit current balances immediately on subscribe | > **Note:** The minimum poll interval is 5 seconds to protect against aggressive polling. Setting `intervalMs` below 5000 will be clamped to 5000. --- ## Balance Change Events Each callback receives a `BalanceChangeEvent`: ```typescript interface BalanceChangeEvent { wormholeChainId: number; // Chain ID address: string; // Vault address portfolio: PortfolioBalance; // Full current portfolio changes: TokenBalanceChange[]; // Only tokens that changed timestamp: number; // Poll timestamp } interface TokenBalanceChange { token: TokenInfo; previousBalance: bigint; currentBalance: bigint; delta: bigint; // positive = received, negative = sent } ``` ### Detecting Deposits vs Withdrawals ```typescript sdk.watchBalance((event) => { for (const change of event.changes) { if (change.delta > 0n) { showNotification(`Received ${formatUnits(change.delta, change.token.decimals)} ${change.token.symbol}`); } else { showNotification(`Sent ${formatUnits(-change.delta, change.token.decimals)} ${change.token.symbol}`); } } }); ``` ### Detecting New Tokens When a token appears in the portfolio for the first time, `previousBalance` is `0n`: ```typescript sdk.watchBalance((event) => { for (const change of event.changes) { if (change.previousBalance === 0n && change.currentBalance > 0n) { console.log(`New token detected: ${change.token.symbol}`); } } }); ``` --- ## React Usage With `@veridex/react`: ```tsx function VaultBalance() { const { portfolio, loading, error, refetch } = useBalance({ pollInterval: 10_000, }); if (loading) return ; if (error) return Error: {error.message}; if (!portfolio) return No credential set; return ( {portfolio.tokens.map(t => ( {t.token.symbol}: {t.formatted} ))} Refresh ); } ``` --- ## Enterprise Usage The `EnterpriseManager` provides balance watching for admin dashboards: ```typescript const enterprise = new EnterpriseManager({ sdk }); // Watch a specific user's vault const unsub = enterprise.watchVaultBalance( 10004, // chain ID '0xVaultAddr', // vault address (event) => { // Forward to webhook fetch('https://your-api.com/webhooks/balance', { method: 'POST', body: JSON.stringify(event), }); }, { intervalMs: 30_000 }, (error) => alertOps('Balance watch failed', error), ); ``` --- ## Advanced: Direct BalanceWatcher For full control, use `BalanceWatcher` directly: ```typescript const watcher = new BalanceWatcher( async (chainId, address) => { return sdk.balance.getPortfolioBalance(chainId, address, false); }, ); const unsub = watcher.watch( 10004, '0xVaultAddr', (event) => { /* ... */ }, { intervalMs: 10_000, emitInitial: true }, ); // Stop all watchers at once watcher.stopAll(); ``` ### Multiple Subscribers Multiple callbacks can subscribe to the same vault — they share one polling loop: ```typescript const unsub1 = watcher.watch(10004, '0xVault', onUIUpdate); const unsub2 = watcher.watch(10004, '0xVault', onAnalytics); // One poll, both callbacks invoked unsub1(); // UI unsubscribes; analytics continues ``` ================================================================================ URL: /guides/cross-chain ================================================================================ # Cross-Chain Transfers Send tokens from one blockchain to another using Veridex. ## Overview Veridex uses Wormhole for cross-chain messaging. When you transfer cross-chain: 1. Hub (Base) creates authorization message 2. Wormhole guardians attest the message 3. Destination spoke executes the transfer ## Basic Cross-Chain Transfer ```typescript const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); const { credential } = await sdk.passkey.authenticate(); // Use chain presets instead of hardcoded Wormhole IDs const base = getChainConfig('base', 'testnet'); const optimism = getChainConfig('optimism', 'testnet'); // Step 1: Estimate bridge fees const fees = await sdk.getBridgeFees({ sourceChain: base.wormholeChainId, destinationChain: optimism.wormholeChainId, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: sdk.getVaultAddress(), amount: 10000000n, }); console.log('Bridge fees:', fees.formattedTotal, fees.currency); console.log('Source gas:', fees.sourceGas); console.log('Message fee:', fees.messageFee); console.log('Relayer fee:', fees.relayerFee); // Step 2: Prepare the bridge const prepared = await sdk.prepareBridge({ sourceChain: base.wormholeChainId, destinationChain: optimism.wormholeChainId, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: sdk.getVaultAddress(), amount: 10000000n, }); // Step 3: Execute with progress tracking const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const result = await sdk.executeBridge(prepared, signer, (progress) => { console.log(`Step ${progress.step}/${progress.totalSteps}: ${progress.message}`); }); console.log('Bridge completed:', result.transactionHash); ``` ## Chain Configuration ### Get Chain Info ```typescript // Get all supported chains const chains = getSupportedChains('testnet'); console.log(chains); // ['base', 'optimism', 'arbitrum', 'solana', 'aptos', 'sui', 'starknet', 'stacks'] // Get specific chain config const optimism = getChainConfig('optimism', 'testnet'); console.log(optimism); // { // name: 'optimism', // chainId: 11155420, // wormholeChainId: 10005, // rpcUrl: 'https://sepolia.optimism.io', // contracts: { factory: '0x...', implementation: '0x...' } // } ``` ### Wormhole Chain IDs | Chain | Testnet | Mainnet | |-------|---------|---------| | Base | 10004 | 30 | | Optimism | 10005 | 24 | | Arbitrum | 10003 | 23 | | Solana | 1 | 1 | | Aptos | 22 | 22 | | Sui | 21 | 21 | | Starknet | 50001 | 50002 | | Stacks | 50003 | 2147483648 | ## EVM to EVM Transfer ```typescript // Base → Optimism const base = getChainConfig('base', 'testnet'); const optimism = getChainConfig('optimism', 'testnet'); const result = await sdk.bridgeWithTracking( { sourceChain: base.wormholeChainId, destinationChain: optimism.wormholeChainId, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: sdk.getVaultAddress(), // Same address! amount: 10000000n, }, signer, (p) => console.log(p.message) ); ``` ## EVM to Solana Transfer ```typescript // Base → Solana const solana = getChainConfig('solana', 'testnet'); const result = await sdk.bridgeWithTracking( { sourceChain: base.wormholeChainId, destinationChain: solana.wormholeChainId, token: USDC, recipient: 'DRpbCBMxVnDK7maPM5tGv6MvB3v1sRMC86PZ8okm21hy', // Solana address amount: 50000000n, // 50 USDC }, signer, (p) => console.log(p.message) ); ``` ## EVM to Aptos Transfer ```typescript // Base → Aptos const aptos = getChainConfig('aptos', 'testnet'); const result = await sdk.bridgeWithTracking( { sourceChain: base.wormholeChainId, destinationChain: aptos.wormholeChainId, token: USDC, recipient: '0x1a89da9e9f8f0bc90d8d492890bd55fb261c6277d2a95dfcac70c268d0c23dcc', amount: 25000000n, // 25 USDC }, signer, (p) => console.log(p.message) ); ``` ## Transfer Speed Veridex supports two verification paths: | Path | Speed | Use Case | |------|-------|----------| | **Query (CCQ)** | ~5-7 seconds | Balance checks, low-value transfers | | **VAA** | ~60 seconds | High-value transfers, critical actions | The relayer automatically selects the optimal path based on transaction value. ## Track Cross-Chain Status The `executeBridge` and `bridgeWithTracking` methods provide real-time progress via callbacks: ```typescript const result = await sdk.executeBridge(prepared, signer, (progress) => { switch (progress.step) { case 1: console.log('Signing transaction...'); break; case 2: console.log('Dispatching to source chain...'); break; case 3: console.log('Waiting for confirmations...'); break; case 4: console.log('Fetching Wormhole VAA...'); break; case 5: console.log('Relaying to destination chain...'); break; case 6: console.log('Bridge completed!'); break; } }); console.log('Source tx:', result.transactionHash); console.log('Sequence:', result.sequence); ``` ## React Example ```tsx const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); const USDC = '0x036CbD53842c5426634e7929541eC2318f3dCF7e'; export function BridgeForm() { const [targetChain, setTargetChain] = useState('10005'); const [amount, setAmount] = useState(''); const [progress, setProgress] = useState(null); const [fees, setFees] = useState(null); const [loading, setLoading] = useState(false); const estimateFees = async () => { const feeEstimate = await sdk.getBridgeFees({ sourceChain: 10004, destinationChain: parseInt(targetChain), token: USDC, recipient: sdk.getVaultAddress(), amount: ethers.parseUnits(amount || '0', 6), }); setFees(`Estimated fees: ${feeEstimate.formattedTotal} ${feeEstimate.currency}`); }; const handleBridge = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setProgress('Preparing bridge...'); try { const provider = new ethers.BrowserProvider(window.ethereum); const signer = await provider.getSigner(); const result = await sdk.bridgeWithTracking( { sourceChain: 10004, destinationChain: parseInt(targetChain), token: USDC, recipient: sdk.getVaultAddress(), amount: ethers.parseUnits(amount, 6), }, signer, (p) => setProgress(`Step ${p.step}/${p.totalSteps}: ${p.message}`) ); setProgress(`Bridge completed! Tx: ${result.transactionHash}`); } catch (error) { setProgress(`Error: ${error instanceof Error ? error.message : 'Bridge failed'}`); } finally { setLoading(false); } }; return ( Destination Chain setTargetChain(e.target.value)} className="mt-1 block w-full rounded-md border p-2" > Optimism Sepolia Arbitrum Sepolia Solana Aptos Stacks Testnet Amount (USDC) { setAmount(e.target.value); setFees(null); }} onBlur={estimateFees} placeholder="10.00" step="0.01" className="mt-1 block w-full rounded-md border p-2" required /> {fees && {fees}} {loading ? 'Bridging...' : 'Bridge USDC'} {progress && {progress}} ); } ``` ## Agent SDK: Cross-Chain Router The Agent SDK provides a `CrossChainRouter` for multi-chain agent operations: ```typescript const agent = await createAgentWallet({ masterCredential: { /* ... */ }, session: { dailyLimitUSD: 100, perTransactionLimitUSD: 25, expiryHours: 24, allowedChains: [10004, 10005, 50003], // Base, Optimism, Stacks }, }); // Agent can pay on any allowed chain await agent.pay({ chain: 10004, token: 'USDC', amount: '1000000', recipient: '0x...' }); await agent.pay({ chain: 10005, token: 'USDC', amount: '2000000', recipient: '0x...' }); // Multi-chain balance check const portfolio = await agent.getMultiChainBalance(); console.log('Total USD:', portfolio.totalUsdValue); ``` ## Fee Estimation ```typescript const fees = await sdk.getBridgeFees({ sourceChain: 10004, destinationChain: 10005, token: USDC, recipient: sdk.getVaultAddress(), amount: 10000000n, }); console.log('Bridge fees:', fees.formattedTotal, fees.currency); console.log('Source gas:', fees.sourceGas); console.log('Message fee:', fees.messageFee); console.log('Relayer fee:', fees.relayerFee); console.log('Estimated time:', fees.estimatedTimeSeconds); ``` ## Error Handling ```typescript try { const result = await sdk.bridgeWithTracking(params, signer, onProgress); } catch (error: any) { if (error.message?.includes('insufficient')) { console.log('Not enough tokens to bridge'); } else if (error.message?.includes('unsupported')) { console.log('Chain not supported'); } else if (error.message?.includes('VAA')) { console.log('Wormhole attestation failed, try again'); } else { console.error('Bridge error:', error); } } ``` ## Next Steps Smooth UX for multiple transfers Configure safety limits for bridges ================================================================================ URL: /guides/error-handling ================================================================================ # Error Handling Patterns The Veridex SDK normalizes errors from EVM, Solana, Starknet, and Stacks into a single `VeridexError` class. This guide covers practical patterns for handling errors in your app. ## Basic Error Handling ```typescript try { await sdk.transferViaRelayer(params); } catch (err) { if (err instanceof VeridexError) { console.log(err.code); // 'INSUFFICIENT_FUNDS' console.log(err.message); // Human-readable message console.log(err.chain); // 'base', 'solana', etc. console.log(err.retryable); // true for RPC_ERROR, TIMEOUT, RELAYER_ERROR console.log(err.cause); // Original chain error } } ``` > **Note:** The `retryable` flag is `true` for `RPC_ERROR`, `TIMEOUT`, and `RELAYER_ERROR`. All other codes are non-retryable by default. --- ## Common Patterns ### User-Facing Error Messages Map error codes to friendly messages: ```typescript function getErrorMessage(err: VeridexError): string { switch (err.code) { case VeridexErrorCode.NO_CREDENTIAL: return 'Please log in with your passkey first.'; case VeridexErrorCode.INSUFFICIENT_FUNDS: return 'Not enough tokens in your vault.'; case VeridexErrorCode.DAILY_LIMIT_EXCEEDED: return 'Daily spending limit reached. Try again after reset.'; case VeridexErrorCode.VAULT_NOT_FOUND: return 'Your vault hasn\'t been created yet.'; case VeridexErrorCode.SESSION_EXPIRED: return 'Your session has expired. Please sign in again.'; case VeridexErrorCode.UNSUPPORTED_FEATURE: return 'This feature is not available on the current chain.'; default: return err.retryable ? 'Something went wrong. Please try again.' : err.message; } } ``` ### Retry with Exponential Backoff ```typescript async function withRetry( fn: () => Promise, maxRetries = 3, ): Promise { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await fn(); } catch (err) { const isRetryable = err instanceof VeridexError && err.retryable; const isLastAttempt = attempt === maxRetries - 1; if (!isRetryable || isLastAttempt) throw err; const delay = 1000 * 2 ** attempt; // 1s, 2s, 4s await new Promise(r => setTimeout(r, delay)); } } throw new Error('unreachable'); } // Usage const result = await withRetry(() => sdk.transferViaRelayer(params)); ``` ### Chain-Aware Error Handling Use `err.chain` to provide chain-specific guidance: ```typescript try { await sdk.transferViaRelayer(params); } catch (err) { if (!(err instanceof VeridexError)) throw err; if (err.code === VeridexErrorCode.INSUFFICIENT_FUNDS) { switch (err.chain) { case 'solana': showToast('Fund your vault with SOL for rent + tokens'); break; case 'stacks': showToast('Fund your vault with STX + tokens'); break; default: showToast('Not enough tokens in your vault'); } } } ``` --- ## normalizeError for Chain Clients If you work directly with chain clients, use `normalizeError` to convert chain-specific errors: ```typescript try { await evmClient.dispatch(signature, pubX, pubY, payload, signer); } catch (err) { const error = normalizeError(err, 'base'); // error.code is now a VeridexErrorCode } ``` The normalizer detects: - **EVM:** ethers error codes, revert messages (`insufficient funds`, `execution reverted`, etc.) - **Solana:** Anchor program error codes (`6000`–`6013`) - **Starknet:** Cairo/felt error patterns - **Stacks:** Clarity error codes (`(err u100)` through `(err u202)`) See the [Error Code Reference](/api-reference/errors) for the complete mapping tables. --- ## React Integration ### Error Boundary ```tsx class VeridexErrorBoundary extends Component< { children: ReactNode; fallback: ReactNode }, { hasError: boolean; code?: VeridexErrorCode } > { state: { hasError: boolean; code?: VeridexErrorCode } = { hasError: false }; static getDerivedStateFromError(error: Error) { if (error instanceof VeridexError) { return { hasError: true, code: error.code }; } return { hasError: true }; } render() { if (!this.state.hasError) return this.props.children; if (this.state.code === VeridexErrorCode.NO_CREDENTIAL) { return ; } return this.props.fallback; } } ``` ### Hook Error State With `@veridex/react`: ```tsx function SendButton() { const { prepare, execute, error, step } = useTransfer(); if (error) { return {getErrorMessage(error)}; } return prepare(params)}>Send; } ``` ================================================================================ URL: /guides/react-hooks ================================================================================ # React Hooks (`@veridex/react`) `@veridex/react` provides pre-built hooks that handle loading states, error boundaries, and polling — so you don't write boilerplate for every SDK call. > **Note:** This package is a thin wrapper around `@veridex/sdk`. For manual integration without the hooks package, see [React Integration](/integrations/react). ## Installation ```bash npm install @veridex/react @veridex/sdk react ``` `@veridex/sdk` and `react` are peer dependencies. --- ## Quick Start ### Wrap Your App with VeridexProvider ```tsx 'use client'; const sdk = createSDK('base', { network: 'testnet', relayerUrl: process.env.NEXT_PUBLIC_RELAYER_URL, }); export default function App({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### Use Hooks in Components ```tsx 'use client'; export function Wallet() { const { credential, register, authenticate, loading } = usePasskey(); const { balance } = useBalance({ pollInterval: 10_000 }); const { prepare, execute, step, error } = useTransfer(); if (!credential) { return ( register('alice', 'Alice')} disabled={loading}> Create Passkey Sign In ); } return ( Balance {balance?.tokens.map(t => ( {t.token.symbol}: {t.formatted} ))} Send prepare({ targetChain: 'base', token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: '0x...', amount: 1_000_000n, })}> Prepare Transfer ); } ``` --- ## Hooks Reference ### usePasskey Handles passkey registration and authentication state. ```tsx const { credential, // PasskeyCredential | null loading, // boolean error, // Error | null register, // (username, displayName) => Promise authenticate, // () => Promise clear, // () => void } = usePasskey(); ``` **Example: Login / Register flow** ```tsx function AuthScreen() { const { credential, register, authenticate, loading, error } = usePasskey(); if (credential) return Logged in as {credential.credentialId.slice(0, 8)}...; return ( register('user', 'User')} disabled={loading}> Create Account Sign In {error && {error.message}} ); } ``` ### useNetwork Returns the current network type from the provider context. ```tsx const network = useNetwork(); // 'testnet' | 'mainnet' ``` Useful for displaying network indicators or conditionally rendering testnet-specific UI. ### useBalance Auto-polling vault balance with configurable interval. ```tsx const { balance, // PortfolioBalance | null loading, // boolean error, // Error | null refetch, // () => Promise } = useBalance({ pollInterval: 15_000, // ms (default: 15000) enabled: true, // disable polling (default: true) }); ``` **Example: Token list** ```tsx function TokenList() { const { balance, loading, refetch } = useBalance({ pollInterval: 10_000 }); if (loading) return ; if (!balance) return No wallet connected; return ( {balance.tokens.map(t => ( {t.token.symbol} {t.formatted} ))} Refresh ); } ``` ### useTransfer State machine for the prepare → review → execute transfer flow. Uses `TransferInput` with `ChainTarget` (chain name or numeric Wormhole chain ID) for `targetChain`. ```tsx const { step, // 'idle' | 'preparing' | 'prepared' | 'executing' | 'confirmed' | 'error' prepared, // PreparedTransfer | null result, // TransferResult | null error, // Error | null loading, // boolean prepare, // (params: TransferInput) => Promise execute, // (signer: any) => Promise transferViaRelayer,// (params: TransferInput) => Promise reset, // () => void } = useTransfer(); ``` The `TransferInput` type accepts a `ChainTarget` for `targetChain` — either a chain name string (`'base'`, `'optimism'`, `'arbitrum'`, `'solana'`, etc.) or a numeric Wormhole chain ID. The hook resolves chain names to numeric IDs internally using the provider's network context. ```tsx interface TransferInput { targetChain: ChainTarget; // 'base' | 'optimism' | 10004 | ... token: string; recipient: string; amount: bigint; } type ChainTarget = ChainName | number; ``` **Example: Gasless transfer** ```tsx function SendForm() { const { transferViaRelayer, step, result, error, reset } = useTransfer(); const handleSend = () => { transferViaRelayer({ targetChain: 'base', token: USDC_ADDRESS, recipient: recipientAddress, amount: parseUnits(amount, 6), }); }; if (step === 'confirmed') { return ( Sent! Tx: {result?.transactionHash} Send Another ); } return ( {step === 'executing' ? 'Sending...' : 'Send USDC'} {error && {error.message}} ); } ``` **Example: Prepare → Review → Execute** ```tsx function TransferWithReview() { const { prepare, execute, step, prepared, error } = useTransfer(); return ( {step === 'idle' && ( prepare(transferParams)}>Prepare )} {step === 'prepared' && prepared && ( Gas cost: {prepared.formattedCost} Expires: {new Date(prepared.expiresAt).toLocaleString()} execute(signer)}>Confirm & Send )} {step === 'executing' && Submitting transaction...} {step === 'confirmed' && Done!} {error && {error.message}} ); } ``` ### useMultiChainPortfolio Combined portfolio and addresses across multiple chains. Accepts optional `chains` parameter using `ChainTarget` (chain names or numeric IDs). ```tsx const { portfolio, // PortfolioBalance[] | null addresses, // Record | null (chainId → vault address) loading, // boolean error, // Error | null refetch, // () => Promise } = useMultiChainPortfolio({ chains: ['base', 'optimism', 'arbitrum'], // ChainTarget[] — optional pollInterval: 30_000, enabled: true, }); ``` **Example: Multi-chain dashboard** ```tsx function Dashboard() { const { portfolio, loading } = useMultiChainPortfolio({ chains: ['base', 'optimism', 'solana'], pollInterval: 30_000, }); if (loading || !portfolio) return ; return ( {portfolio.map(chain => ( {chain.chainName} {chain.tokens.map(t => ( {t.token.symbol}: {t.formatted} ))} ))} ); } ``` ### useSpendingLimits Formatted spending limits with amount checking. ```tsx const { limits, // { dailyLimit, dailySpent, dailyRemaining, dailyUsedPercentage, transactionLimit, timeUntilReset } | null loading, // boolean error, // Error | null checkAmount, // (amount: bigint) => Promise<{ allowed: boolean; message?: string }> refetch, // () => Promise } = useSpendingLimits(); ``` **Example: Spending limit bar** ```tsx function SpendingLimitBar() { const { limits, loading } = useSpendingLimits(); if (loading || !limits) return null; return ( {limits.dailyUsedPercentage}% used — resets in {limits.timeUntilReset} Remaining: {limits.dailyRemaining} ); } ``` --- ## Chain Name Resolution The React hooks support human-readable chain names via the `ChainTarget` type. Instead of remembering Wormhole chain IDs, use names like `'base'`, `'optimism'`, or `'solana'`. ```tsx // Resolve a single chain const baseId = resolveChainId('base', 'testnet'); // → 10004 const opId = resolveChainId('optimism', 'testnet'); // → 10005 // Resolve multiple chains const ids = resolveChainIds(['base', 'optimism', 'solana'], 'testnet'); // → [10004, 10005, 1] // Numeric IDs pass through unchanged const id = resolveChainId(10004, 'testnet'); // → 10004 ``` The hooks resolve chain names automatically using the `network` prop from `VeridexProvider`. You can also import and use the resolution utilities directly. --- ## Accessing the SDK Directly Use `useVeridexSDK()` for operations not covered by hooks: ```tsx function CustomComponent() { const sdk = useVeridexSDK(); const handleBridge = async () => { const result = await sdk.bridgeViaRelayer({ sourceChain: 10004, destinationChain: 10005, token: USDC, recipient: '0x...', amount: 1_000_000n, }); }; return Bridge USDC; } ``` > **Note:** When using `useVeridexSDK()` directly, you work with the raw SDK API which uses numeric Wormhole chain IDs. Chain name resolution is a feature of the React hooks layer only. --- ## Next.js Setup > **Note:** The SDK uses browser APIs (WebAuthn, localStorage). In Next.js App Router, add `'use client'` to any component that uses Veridex hooks. ```tsx // app/providers.tsx 'use client'; export function Providers({ children }: { children: React.ReactNode }) { const sdk = useMemo(() => createSDK('base', { network: 'testnet', relayerUrl: process.env.NEXT_PUBLIC_RELAYER_URL, }), []); return {children}; } ``` ```tsx // app/layout.tsx export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` --- ## Full Exports Everything exported from `@veridex/react`: ```tsx // Components // Hooks useVeridexSDK, useNetwork, usePasskey, useBalance, useTransfer, useMultiChainPortfolio, useSpendingLimits, } from '@veridex/react'; // Utilities // Types ``` --- ## Agent SDK Hooks The Agent SDK (`@veridex/agentic-payments`) provides additional hooks for agent dashboard UIs. These are separate from the core `@veridex/react` hooks and focus on agent-specific functionality like spending, payment, and protocol detection. ```tsx AgentWalletProvider, useAgentWallet, useAgentWalletContext, usePayment, useSessionStatus, useFetchWithPayment, useCostEstimate, useProtocolDetection, useMultiChainBalance, usePaymentHistory, useSpendingAlerts, useCanPay, } from '@veridex/agentic-payments'; ``` ### AgentWalletProvider Wraps your app to provide a shared `AgentWallet` instance via context: ```tsx ``` ### usePayment Make payments with loading and error state: ```tsx const { pay, isPaying, lastReceipt, error } = usePayment(wallet); await pay({ amount: '1000000', token: 'USDC', recipient: '0x...', chain: 10004 }); ``` ### useFetchWithPayment Make paid HTTP requests with automatic protocol detection: ```tsx const { fetchWithPayment, data, isPending, detectedProtocol, costEstimate } = useFetchWithPayment(wallet); await fetchWithPayment('https://api.example.com/premium', { onBeforePayment: async (estimate) => estimate.amountUSD < 10, }); ``` ### useCostEstimate Preview payment cost before executing: ```tsx const { estimate, isLoading } = useCostEstimate(wallet, 'https://api.example.com/data'); // estimate?.amountUSD, estimate?.scheme ``` ### useProtocolDetection Detect which payment protocol a URL uses: ```tsx const { protocol, isDetecting } = useProtocolDetection(wallet, url); // protocol: 'x402' | 'ucp' | 'acp' | 'ap2' | 'mpp' | null ``` ### useCanPay Check if the wallet can afford a payment: ```tsx const { canPay, reason } = useCanPay(wallet, 5.00); // canPay: boolean, reason: 'Exceeds daily limit' | null ``` ### useSessionStatus Get session status with auto-refresh (every 30s): ```tsx const { status, isExpired, refreshStatus } = useSessionStatus(wallet); // status.isValid, status.remainingDailyLimitUSD, status.expiry ``` ### useMultiChainBalance Fetch balances across all configured chains: ```tsx const { balances, totalUSD, isLoading } = useMultiChainBalance(wallet); ``` ### useSpendingAlerts Subscribe to real-time spending alerts: ```tsx const { alerts, latestAlert, clearAlerts } = useSpendingAlerts(wallet); ``` ================================================================================ URL: /guides/enterprise-manager ================================================================================ # Enterprise Manager The `EnterpriseManager` provides batch operations for enterprise back-ends that need to manage many vaults, execute bulk transfers, and monitor balances across multiple users. > **Note:** EnterpriseManager is an orchestration layer — it delegates to existing SDK primitives with concurrency control, lifecycle callbacks, and error isolation per item. ## Quick Start ### Create an SDK with Sponsor Key ```typescript const sdk = createSDK('base', { network: 'testnet', sponsorPrivateKey: process.env.SPONSOR_KEY, relayerUrl: 'https://relayer.veridex.network', relayerApiKey: process.env.RELAYER_API_KEY, }); ``` ### Initialize Enterprise Manager ```typescript const enterprise = new EnterpriseManager({ sdk, maxConcurrency: 5, // up to 5 parallel operations }); ``` ### Batch Create Vaults ```typescript const result = await enterprise.batchCreateVaults({ keyHashes: userKeyHashes, maxConcurrency: 3, }); console.log(`${result.succeeded}/${result.total} vaults created`); for (const r of result.results) { if (r.error) { console.error(`Failed for ${r.keyHash}: ${r.error}`); } } ``` --- ## Batch Operations ### Batch Vault Creation Create sponsored vaults for multiple users across all configured chains. Errors are captured per key hash — the batch continues on individual failures. ```typescript const result = await enterprise.batchCreateVaults( { keyHashes: ['0xabc...', '0xdef...', '0x123...'], maxConcurrency: 3, }, // Optional lifecycle callback (event) => { switch (event.type) { case 'started': console.log(`Creating vaults for ${event.total} users...`); break; case 'item_completed': const pct = Math.round(((event.index + 1) / event.total) * 100); console.log(`[${pct}%] User ${event.index + 1}: ${event.success ? 'OK' : event.error}`); break; case 'completed': console.log(`Done: ${event.succeeded} succeeded, ${event.failed} failed`); break; } }, ); ``` ### Batch Transfers Execute multiple transfers with concurrent preparation and execution: ```typescript const result = await enterprise.batchTransfer({ transfers: [ { targetChain: 10004, token: USDC, recipient: '0xAlice', amount: 1_000_000n }, { targetChain: 10004, token: USDC, recipient: '0xBob', amount: 2_000_000n }, { targetChain: 10005, token: USDC, recipient: '0xCarol', amount: 500_000n }, ], signer, maxConcurrency: 2, }, (event) => { if (event.type === 'item_completed' && !event.success) { alertOps(`Transfer ${event.index} failed: ${event.error}`); } }); console.log(`${result.succeeded}/${result.total} transfers completed`); ``` ### Batch Spending Limits Update daily spending limits. These execute sequentially since each requires a passkey signature: ```typescript const result = await enterprise.batchSetSpendingLimits({ updates: [ { newLimit: ethers.parseEther('5.0') }, { newLimit: ethers.parseEther('10.0') }, ], signer, }); ``` --- ## Admin Operations ### Check Vault Status ```typescript const status = await enterprise.checkVaults('0xUserKeyHash...'); // { 10004: { exists: true, address: '0x...' }, 10005: { exists: false, address: '0x...' } } ``` ### Inspect Spending Limits Read limits for any vault address without owning it — useful for admin dashboards: ```typescript const limits = await enterprise.getSpendingLimitsForVault('0xVaultAddr', 10004); console.log('Daily remaining:', limits.dailyRemaining); console.log('Paused:', limits.isPaused); ``` ### Monitor Vault Balances Subscribe to balance changes for webhook-style notifications: ```typescript const unsub = enterprise.watchVaultBalance( 10004, '0xVaultAddr', (event) => { for (const change of event.changes) { if (change.delta > 0n) { notifyUser(`Received ${change.token.symbol}`); } } }, { intervalMs: 15_000, emitInitial: true }, (error) => alertOps('Balance watch failed', error), ); // Later: stop monitoring unsub(); ``` --- ## Lifecycle Callbacks All batch methods accept an optional lifecycle callback that reports progress: ```typescript type BatchLifecycleEvent = | { type: 'started'; total: number } | { type: 'item_started'; index: number; total: number } | { type: 'item_completed'; index: number; total: number; success: boolean; error?: string } | { type: 'completed'; succeeded: number; failed: number; total: number }; ``` Use these to power progress bars, logging, or webhook notifications: ```typescript enterprise.batchCreateVaults(request, (event) => { if (event.type === 'item_completed') { updateProgressBar(event.index + 1, event.total); } if (event.type === 'completed') { sendSlackNotification( `Vault batch: ${event.succeeded}/${event.total} succeeded`, ); } }); ``` --- ## Concurrency Control The `maxConcurrency` parameter controls how many operations run in parallel. This is important for: - **Rate limiting** — avoid hitting RPC or relayer rate limits - **Resource management** — control memory and network usage - **Signing constraints** — spending limit updates must be sequential | Method | Default Concurrency | Parallel? | |--------|-------------------|-----------| | `batchCreateVaults` | 3 | Yes | | `batchTransfer` | 3 | Yes | | `batchSetSpendingLimits` | 1 (sequential) | No — requires passkey signature | Override per-call: ```typescript enterprise.batchCreateVaults({ keyHashes, maxConcurrency: 10 }); enterprise.batchTransfer({ transfers, signer, maxConcurrency: 1 }); // sequential ``` ================================================================================ URL: /guides/agent-payments ================================================================================ # Agent Payments Build autonomous AI agents that can make payments using the Veridex Agent SDK (`@veridex/agentic-payments`). This guide covers both a **beginner-friendly** walkthrough and references to a **production-grade** advanced example. > **Note:** **Passkey Wallet Required:** The Agent SDK (`@veridex/agentic-payments`) is built on top of `@veridex/sdk`, which requires a **passkey wallet created via a frontend**. Passkey operations (`register`, `authenticate`) use WebAuthn browser APIs and **cannot run in Node.js or server-side code**. Your app must have a browser-based frontend that creates the passkey, then passes the credential to the server-side agent. ## Try It — Working Examples Minimal Next.js app — passkey wallet + agent provisioning + simple payments. Best for beginners. Production-grade — Gemini AI chat, MCP tools, ERC-8004 identity, trust gates, multi-chain. ## Overview The Agent SDK enables AI agents to: - **Pay for APIs automatically** via multiple protocols (x402, UCP, ACP, AP2, MPP) with auto-detection - **Expose MCP tools** for integration with AI models (Gemini, Claude, GPT) with security hardening - **Manage spending limits** with daily and per-transaction caps - **Use session keys** so the master passkey isn't needed for every payment - **Register on-chain identity** via ERC-8004 and enforce trust gates - **Enforce policies** via the Policy Engine (8 built-in rules, allow/deny/escalate) - **Detect threats** via the Security Firewall (injection, tool poisoning, secret leaks, anomalies) - **Track evidence** via Trace & Evidence layer (8 storage backends, cryptographic audit trails) - **Escalate to humans** for high-risk actions with configurable timeouts - **Monitor spending** with real-time alerts and audit logs - **Operate across chains** including EVM, Solana, Stacks, Monad, and more ## Prerequisites ```bash bun add @veridex/agentic-payments @veridex/sdk ``` You'll also need: - A **browser-based frontend** to create the passkey wallet (WebAuthn) - Testnet tokens on your target chain (e.g., USDC on Base Sepolia via [Circle Faucet](https://faucet.circle.com)) ### Next.js Webpack Configuration If you're using Next.js, add this `next.config.mjs` to handle transitive dependency resolution from the SDK's multi-chain support: ```js /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, transpilePackages: ['@veridex/sdk', '@veridex/agentic-payments'], webpack: (config, { isServer }) => { config.resolve.fallback = { ...config.resolve.fallback, fs: false, net: false, tls: false, }; // Ignore problematic transitive deps from @aptos-labs/ts-sdk // that reference unexported paths in @noble/curves config.resolve.alias = { ...config.resolve.alias, '@aptos-labs/ts-sdk': false, }; return config; }, }; export default nextConfig; ``` > **Note:** **Without this config**, Next.js builds will fail with a `Module not found: Package path ./nist.js is not exported from package @noble/curves` error. This is caused by `@aptos-labs/ts-sdk` (a transitive dependency of `@veridex/sdk`) referencing unexported module paths. ## Architecture Every agent app has the same two-layer architecture: ``` ┌─────────────────────────────────────────┐ │ Browser (React / Next.js client) │ │ • @veridex/sdk │ │ • Passkey registration (WebAuthn) │ │ • Sends credential to backend │ └──────────┬──────────────────────────────┘ │ POST /api/agent ┌──────────▼──────────────────────────────┐ │ Server (Next.js API Route / Express) │ │ • @veridex/agentic-payments │ │ • createAgentWallet(credential) │ │ • agent.pay() / getBalance() / fetch() │ │ • MCP tools for AI models │ └─────────────────────────────────────────┘ ``` --- ## Basic Example: Step by Step This walkthrough matches the [agent-basic](https://github.com/Veridex-Protocol/examples/tree/main/agent-basic) example. ### Step 1: Create Passkey Wallet (Browser) The passkey **must** be created in the browser. Use `@veridex/sdk`: ```typescript // This runs in the browser (React component, 'use client') const sdk = createSDK('base'); // Register a new passkey — triggers WebAuthn biometric prompt const credential = await sdk.passkey.register('alice', 'alice'); // Persist to localStorage for page reloads sdk.passkey.saveToLocalStorage(); // These values are sent to the server to create the agent const { credentialId, publicKeyX, publicKeyY, keyHash } = credential; ``` > **Note:** The `credentialId`, `publicKeyX`, `publicKeyY`, and `keyHash` are the four values the server needs to create an `AgentWallet`. The passkey itself stays on the user's device — only the public key data is sent to the server. ### Step 2: Create Agent Wallet (Server) On the server, use `createAgentWallet` with the credential from the frontend: ```typescript // Next.js API Route: app/api/agent/route.ts let agent: AgentWallet | null = null; // Called when the frontend sends the passkey credential async function provision(credential: any, session: any) { agent = await createAgentWallet({ masterCredential: { credentialId: credential.credentialId, publicKeyX: BigInt(credential.publicKeyX), publicKeyY: BigInt(credential.publicKeyY), keyHash: credential.keyHash, }, session: { dailyLimitUSD: session.dailyLimitUSD ?? 50, perTransactionLimitUSD: session.perTransactionLimitUSD ?? 10, expiryHours: session.expiryHours ?? 24, allowedChains: [10004], // Base Sepolia }, }); return agent; } ``` ### Step 3: Check Balance ```typescript const tokens = await agent.getBalance(10004); // Base Sepolia console.log('Tokens:', tokens); ``` ### Step 4: Make a Payment ```typescript const receipt = await agent.pay({ chain: 10004, // Base Sepolia (Wormhole chain ID) token: 'USDC', amount: '5000000', // 5 USDC (6 decimals) recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', }); console.log('Tx hash:', receipt.txHash); console.log('Status:', receipt.status); // 'confirmed' ``` ### Step 5: Monitor Session ```typescript const status = agent.getSessionStatus(); console.log('Session valid:', status.isValid); console.log('Wallet address:', status.address); console.log('Daily limit:', `$${status.limits!.dailyLimitUSD}`); console.log('Spent today:', `$${status.totalSpentUSD.toFixed(2)}`); console.log('Remaining:', `$${status.remainingDailyLimitUSD.toFixed(2)}`); ``` ### Step 6: Revoke Session ```typescript await agent.revokeSession(); ``` > **Note:** **Run the full example:** Clone the [examples repo](https://github.com/Veridex-Protocol/examples) and run `cd agent-basic && bun install && bun run dev`. Open `http://localhost:3000` to try it. --- ## Advanced Example: AI Agent with Gemini The [agent-advanced](https://github.com/Veridex-Protocol/examples/tree/main/agent-advanced) example adds: - **Gemini AI chat** with function calling - **MCP tools** mapped to Gemini function declarations - **ERC-8004 identity** (optional) for on-chain agent registration - **Trust-gated payments** via reputation scoring - **Multi-chain support** (Base + Ethereum Sepolia) ### MCP Tools → AI Function Calling The Agent SDK exposes **MCP tools** that map directly to any AI model's function calling interface: ```typescript // Get MCP tools from the agent const mcpTools = agent.getMCPTools(); ``` | Tool Name | Description | |-----------|-------------| | `veridex_pay` | Execute a token payment (chain, token, amount, recipient) | | `veridex_check_balance` | Check wallet balance on a specific chain | | `veridex_create_session_key` | Create a new bounded session | | `veridex_revoke_session` | Revoke agent wallet access | | `veridex_get_payment_history` | Retrieve transaction history | ### Hardened MCP Server The `MCPServer` includes security layers for production AI integrations: ```typescript const server = new MCPServer(agent, { allowedTools: ['veridex_pay', 'veridex_check_balance'], toolSpendingLimits: { veridex_pay: 10 }, sanitizeDescriptions: true, // Strip hidden instructions from tool descriptions pinDescriptions: true, // Detect tool description rug-pulls scanInputs: true, // Injection detection on all inputs scanOutputs: true, // Secret scanning on all outputs }); ``` ### Mapping to Gemini ```typescript const tools = mcpTools.map(tool => ({ declaration: { name: tool.name, description: tool.description, parameters: { type: SchemaType.OBJECT, properties: tool.inputSchema.properties, required: tool.inputSchema.required || [], }, }, execute: async (args: any) => { const result = await tool.handler(args); // Serialize BigInts for JSON compatibility return JSON.parse(JSON.stringify(result, (_, v) => typeof v === 'bigint' ? v.toString() : v )); }, })); ``` > **Note:** **BigInt serialization**: The Agent SDK returns `bigint` values for token amounts. Always serialize them to strings before passing to AI model APIs (Gemini, OpenAI, etc.) which don't support BigInt in JSON. ### Automatic x402 Payments When the agent uses `agent.fetch()`, payment protocols are handled transparently: ```typescript // Auto-detects x402, UCP, ACP, AP2, or MPP and handles payment const response = await agent.fetch('https://paid-api.example.com/data', { onBeforePayment: async (estimate) => { console.log(`About to pay $${estimate.amountUSD}`); return estimate.amountUSD < 10; // auto-approve under $10 }, maxAutoApproveUSD: 5, }); if (response.ok) { const data = await response.json(); console.log('Got paid data:', data); } ``` ### ERC-8004 Identity (Optional) Enable on-chain agent identity for trust-gated payments: ```typescript const agent = await createAgentWallet({ masterCredential: { /* ... */ }, session: { /* ... */ }, erc8004: { enabled: true, testnet: true, minReputationScore: 20, // Reject merchants below 20/100 }, }); // Check merchant trust before paying const trust = await agent.checkMerchantTrust('https://data-provider.com'); if (trust.trusted) { console.log(`Trusted — score: ${trust.score}/100`); } ``` See the [Agent Identity guide](/guides/agent-identity) for full ERC-8004 documentation. > **Note:** **Run the advanced example:** Clone the [examples repo](https://github.com/Veridex-Protocol/examples), add your `GOOGLE_API_KEY` to `.env.local`, and run `cd agent-advanced && bun install && bun run dev`. Open `http://localhost:3001`. --- ## Error Handling ```typescript try { await agent.pay({ chain: 10004, token: 'USDC', amount: '100000000', recipient: '0x...' }); } catch (error) { if (error instanceof AgentPaymentError) { switch (error.code) { case AgentPaymentErrorCode.LIMIT_EXCEEDED: console.log('Over budget! Wait for daily reset.'); break; case AgentPaymentErrorCode.INSUFFICIENT_BALANCE: console.log('Need more tokens:', error.suggestion); break; case AgentPaymentErrorCode.SESSION_EXPIRED: // Re-provision from frontend break; default: console.error('Agent error:', error.message); } } } ``` ## Spending Alerts ```typescript agent.onSpendingAlert((alert) => { console.warn(`[ALERT] ${alert.type}: ${alert.message}`); }); ``` ## Next Steps ERC-8004 identity, reputation, and discovery Complete API documentation including Policy Engine, Security Firewall, Trace & Evidence Configure spending safety Build the frontend dashboard ================================================================================ URL: /guides/veridex-agents ================================================================================ # Veridex Agents Guide This guide walks you through the Veridex Agent Fabric from first install to production deployment. For detailed API signatures see the [API Reference](/api-reference/agents-framework). --- ## What is the Agent Fabric? The Veridex Agent Fabric is a modular TypeScript framework for building AI agents that are **safe**, **observable**, and **interoperable**. It is split into five packages so you only install what you need: | Package | Purpose | |---------|---------| | `@veridex/agents` | Core runtime — agent loop, tools, policy, memory, checkpoints, events, transports | | `@veridex/agents-react` | React hooks and provider for agent-powered UIs | | `@veridex/agents-adapters` | Import/export agents between Veridex, OpenAI, LangGraph, PydanticAI | | `@veridex/agents-openclaw` | Bridge for OpenClaw / Pi agent ecosystems — context files, skills, ACP | | `@veridex/agents-control-plane` | Enterprise governance — tenants, policy packs, trace storage, approval workflows | --- ## Architecture overview ``` ┌─────────────────────────────────────────────────────────────────┐ │ Your Application │ │ ┌──────────────┐ ┌──────────────────┐ ┌───────────────────┐ │ │ │ React UI │ │ REST / WebSocket│ │ CLI / Script │ │ │ │ (agents- │ │ Server │ │ │ │ │ │ react) │ │ │ │ │ │ │ └──────┬───────┘ └────────┬─────────┘ └────────┬──────────┘ │ │ │ │ │ │ │ └───────────────────┼──────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ AgentRuntime │ │ │ │ ┌─────────┐ ┌────────────┐ ┌────────────┐ ┌─────────┐ │ │ │ │ │ Tools │ │ Policy │ │ Approvals │ │ Memory │ │ │ │ │ │ Registry│ │ Engine │ │ Manager │ │ Manager │ │ │ │ │ └─────────┘ └────────────┘ └────────────┘ └─────────┘ │ │ │ │ ┌─────────┐ ┌────────────┐ ┌────────────┐ ┌─────────┐ │ │ │ │ │ Context │ │ Checkpoint │ │ Event │ │ Audit │ │ │ │ │ │Compiler │ │ Manager │ │ Bus │ │ Emitter │ │ │ │ │ └─────────┘ └────────────┘ └────────────┘ └─────────┘ │ │ │ │ ┌─────────┐ ┌────────────┐ ┌────────────┐ │ │ │ │ │ Hook │ │ Model │ │ Transports │ │ │ │ │ │Registry │ │ Registry │ │ MCP/ACP/A2A│ │ │ │ │ └─────────┘ └────────────┘ └────────────┘ │ │ │ └──────────────────────────────────────────────────────────┘ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────┐ │ │ │ Control Plane (optional) │ │ │ │ PolicyPacks │ Tenants │ TraceStore │ ApprovalWorkflows │ │ │ └──────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ### Runtime loop Every call to `agent.run(input)` executes a deterministic loop: 1. **Context compilation** — instructions, memory, conversation history are assembled within the token budget. 2. **Model call** — the compiled context is sent to the configured LLM provider. 3. **Proposal extraction** — the model response is parsed into one or more `ActionProposal` objects (tool calls, final answers, handoffs, memory writes). 4. **Policy evaluation** — every proposal is checked against registered policy rules. Verdicts: `allow`, `deny`, or `escalate`. 5. **Approval routing** — escalated proposals are routed to the appropriate approval mode (auto, human, dual, policy pack). 6. **Execution** — allowed proposals are executed (tool calls run, memory written, handoffs initiated). 7. **Event emission** — every step emits typed trace events on the `EventBus`. 8. **Turn boundary** — the loop repeats from step 1 until a final answer is produced, the max turn count is reached, or the run is suspended for approval. > **Note:** The runtime never executes a tool without first checking policy. There is no bypass. --- ## Getting started ### Prerequisites - Node.js 18+ or Bun 1.0+ - An OpenAI or Anthropic API key (or any compatible provider) ### Install ### Install the core package ```bash npm install @veridex/agents zod ``` ### (Optional) Install companion packages ```bash # React hooks npm install @veridex/agents-react # Framework adapters npm install @veridex/agents-adapters # OpenClaw bridge npm install @veridex/agents-openclaw # Enterprise control plane npm install @veridex/agents-control-plane ``` --- ## Tutorial: Your first agent This section builds a working agent step by step. Each step adds one capability so you can see how the pieces connect. ### Step 1 — Minimal agent The simplest possible agent: no tools, just a model and instructions. ```ts const agent = createAgent( { id: 'greeter', name: 'Greeter', model: { provider: 'openai', model: 'gpt-4o-mini' }, instructions: 'You are a friendly greeter. Say hello and ask how you can help.', }, { modelProviders: { openai: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }), }, }, ); const result = await agent.run('Hi there!'); console.log(result.output); // → "Hello! How can I help you today?" console.log(result.state); // → "completed" ``` **What happened:** - `createAgent` built an `AgentRuntime` with all subsystems initialized. - `agent.run()` compiled context (instructions only), called the model, got a final answer, and returned. - No tools were involved, so the loop completed in one turn. ### Step 2 — Add tools Tools give the agent the ability to take actions. Every tool has a Zod-validated input schema and a `safetyClass` that the policy engine uses. ```ts const lookupOrder = tool({ name: 'lookup_order', description: 'Look up an order by its ID.', input: z.object({ orderId: z.string().regex(/^ORD-\d+$/), }), safetyClass: 'read', async execute({ input }) { // In production, this would query your database return { success: true, llmOutput: `Order ${input.orderId}: 2x Widget, shipped, arriving Thursday.`, data: { orderId: input.orderId, status: 'shipped', eta: 'Thursday' }, }; }, }); const agent = createAgent( { id: 'order-agent', name: 'Order Agent', model: { provider: 'openai', model: 'gpt-4o-mini' }, instructions: 'Help customers check their order status.', tools: [lookupOrder], maxTurns: 5, }, { modelProviders: { openai: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }), }, }, ); const result = await agent.run('Where is my order ORD-1042?'); console.log(result.output); // → "Your order ORD-1042 contains 2x Widget. It has been shipped and is arriving Thursday." console.log(result.turns.length); // → 2 (first turn: tool call, second turn: final answer) ``` **What happened:** - The model decided to call `lookup_order` with `{ orderId: "ORD-1042" }`. - The runtime validated the input against the Zod schema. - Since `safetyClass` is `read`, the default policy auto-allowed execution. - The tool returned `llmOutput` which the model used to compose the final answer. ### Step 3 — Add policy rules Policy rules control what the agent can and cannot do. Rules are evaluated in priority order and produce verdicts. ```ts blockSafetyClasses, requireApprovalFor, maxRunSpendUSD, maxTokenBudget, } from '@veridex/agents'; // Block destructive and privileged tools entirely agent.policyEngine.register( blockSafetyClasses(['destructive', 'privileged'], agent.tools.list()) ); // Financial tools require human approval agent.policyEngine.register( requireApprovalFor(['financial'], agent.tools.list()) ); // Cap spend per run agent.policyEngine.register(maxRunSpendUSD(50)); // Cap token usage per run agent.policyEngine.register(maxTokenBudget(100_000)); ``` You can also write custom rules: ```ts agent.policyEngine.register({ id: 'business-hours-only', name: 'Business Hours Only', description: 'Block tool executions outside business hours.', priority: 5, evaluate(ctx) { const hour = new Date().getHours(); if (hour < 9 || hour >= 17) { return { verdict: 'deny', reason: 'Outside business hours (9am–5pm).' }; } return { verdict: 'allow' }; }, }); ``` ### Step 4 — Set up approvals When a policy rule returns `escalate`, the approval manager routes the request to the right handler. ```ts // Route escalated proposals to human approval with a 5-minute timeout agent.approvals.addRoute({ match: (proposal, policy) => policy.verdict === 'escalate', mode: 'human_required', timeoutMs: 300_000, }); // Register a handler that gets called when approval is needed agent.approvals.registerHandler('human_required', async (request) => { // In production: send to Slack, email, or a dashboard console.log(`Approval needed for: ${request.proposal.toolName}`); console.log(`Arguments: ${JSON.stringify(request.proposal.arguments)}`); // For this example, auto-approve return { approved: true, decidedBy: 'auto-test' }; }); ``` When the agent tries to call a `financial` tool, the run will suspend, emit an `approval_requested` event, and wait for `resolve()` or timeout. ### Step 5 — Use memory The memory manager stores context across turns and sessions. Four tiers serve different scopes: | Tier | Scope | Use case | |------|-------|----------| | `working` | Current run | Scratch notes the agent uses within a single run | | `episodic` | Session | Facts learned during a conversation session | | `semantic` | Global | Long-term knowledge that persists across sessions | | `procedural` | Global | Tagged routines and procedures the agent can recall | ```ts // Write a memory await agent.memory.write({ tier: 'episodic', content: 'Customer prefers email notifications over SMS.', scope: 'session', tags: ['preferences', 'notifications'], confidence: 0.95, ttlMs: 7 * 24 * 60 * 60 * 1000, // 7 days }); // Search memory const prefs = await agent.memory.search({ query: 'notification preference', tiers: ['episodic', 'semantic'], limit: 5, }); // The context compiler automatically includes relevant memory in the prompt ``` ### Step 6 — Listen to events The event bus emits typed events for every runtime action. Use it for logging, metrics, UI updates, or audit. ```ts // Specific event types agent.events.on('run_started', (e) => { console.log(`Run ${e.runId} started`); }); agent.events.on('tool_completed', (e) => { console.log(`Tool ${e.toolName} completed in ${e.durationMs}ms`); }); agent.events.on('policy_evaluated', (e) => { console.log(`Policy verdict: ${e.verdict} for ${e.toolName}`); }); // Or listen to everything agent.events.onAny((e) => { metrics.increment(`agent.event.${e.type}`); }); // Access the full immutable event log after a run const log = agent.eventLog; const toolEvents = log.getByType('tool_completed'); const allEvents = log.getAll(); ``` **Available event types:** `run_started`, `run_completed`, `run_failed`, `run_suspended`, `turn_started`, `turn_completed`, `tool_started`, `tool_completed`, `tool_failed`, `model_called`, `model_responded`, `policy_evaluated`, `approval_requested`, `approval_resolved`, `memory_written`, `checkpoint_saved`, `handoff_initiated`, `handoff_completed` ### Step 7 — Enable checkpoints Checkpoints let the agent suspend (e.g., while waiting for approval) and resume later without losing state. ```ts const agent = createAgent( { /* ... */ }, { modelProviders: { /* ... */ }, enableCheckpoints: true, }, ); // After a run suspends: const result = await agent.run('Transfer $500 to ACCT-B'); if (result.state === 'suspended') { // State was automatically checkpointed const checkpoint = await agent.checkpoints.loadLatest(result.runId); console.log(`Checkpoint saved at turn ${checkpoint.turnIndex}`); // Later, after approval is granted, resume from the checkpoint const state = agent.checkpoints.parseState(checkpoint); // ... restore and continue the run } ``` --- ## Using hooks Hooks run at specific phases of the runtime loop. They can annotate context, override execution flow, or inject side effects. Hooks are useful for cross-cutting concerns like logging, rate limiting, or feature flags. ```ts // Log every tool call agent.hooks.register({ name: 'log-tool-calls', phase: 'before_tool', priority: 10, async execute(context) { console.log(`Calling tool: ${context.toolName}`); console.log(`Arguments: ${JSON.stringify(context.arguments)}`); }, }); // Add metadata to every run agent.hooks.register({ name: 'add-request-id', phase: 'before_run', priority: 1, async execute(context) { return { annotations: { requestId: crypto.randomUUID() }, }; }, }); // Block runs during maintenance agent.hooks.register({ name: 'maintenance-window', phase: 'before_run', async execute() { if (isMaintenanceWindow()) { return { override: 'deny', reason: 'System is in maintenance mode.' }; } }, }); ``` **Hook phases** (in execution order): `before_run` → `before_turn` → `before_tool` → `after_tool` → `after_turn` → `after_run` / `on_error` **Override values:** - `continue` — proceed normally - `retry` — re-attempt the current step - `pause` — suspend the run (creates a checkpoint) - `deny` — abort the run with the given reason --- ## Using transports Transports expose the agent to external systems via standard protocols. ### MCP (Model Context Protocol) Expose your agent's tools to any MCP-compatible LLM host (Claude Desktop, Cursor, etc.). ```ts const mcp = new MCPServerTransport({ serverName: 'my-agent-mcp', serverVersion: '1.0.0', tools: agent.tools.list(), }); await mcp.connect(); // External LLM hosts can now discover and call your tools via MCP ``` ### ACP (Agent Communication Protocol) Enable agent-to-agent task delegation via ACP. ```ts const acp = new ACPTransport({ agentCard: { name: 'research-agent', description: 'Performs deep research on topics', capabilities: [ { name: 'research_topic', description: 'Research a topic', inputSchema: {} }, ], }, }); await acp.connect(); ``` ### A2A (Agent-to-Agent) Direct inter-agent communication with discovery via agent cards. ```ts const a2a = new A2ATransport({ agentCard: { name: 'coordinator', description: 'Coordinates specialist agents', capabilities: [], }, }); await a2a.connect(); ``` --- ## React integration `@veridex/agents-react` provides a context provider and hooks that wire your React UI to the agent runtime. ### Setup ```tsx function App() { return ( ); } ``` ### Build a chat UI with approvals ```tsx useAgent, useRun, useApprovals, useBudget, useTrace, useMemory, useToolRegistry, } from '@veridex/agents-react'; function ChatPanel() { const { definition } = useAgent(); const { run, result, state, isLoading, error } = useRun(); const [input, setInput] = React.useState(''); return ( {definition.name} { e.preventDefault(); run(input); }}> setInput(e.target.value)} /> Send {isLoading && Thinking...} {error && {error.message}} {result && {result.output}} State: {state} ); } function ApprovalInbox() { const { pending, resolve } = useApprovals(); if (pending.length === 0) return No pending approvals.; return ( {pending.map((req) => ( {req.proposal.toolName} {JSON.stringify(req.proposal.arguments, null, 2)} resolve(req.id, true)}>Approve resolve(req.id, false)}>Deny ))} ); } function BudgetDisplay() { const { snapshot } = useBudget(); return ( Spent: ${snapshot.spent.toFixed(2)} / ${snapshot.budget.toFixed(2)} ); } function EventLog() { const { events } = useTrace(); return ( {events.slice(-10).map((e, i) => ( {e.type} — {new Date(e.timestamp).toLocaleTimeString()} ))} ); } ``` ### Available hooks | Hook | Returns | Purpose | |------|---------|---------| | `useAgent()` | `{ definition, state }` | Agent config and current state | | `useRun()` | `{ run, state, result, error, isLoading }` | Start and observe runs | | `useTurnStream()` | `{ stream, cancel }` | Stream individual turns | | `useApprovals()` | `{ pending, resolve }` | Manage pending approvals | | `useTrace()` | `{ events, latest }` | Observe trace events in real time | | `useMemory()` | `{ entries, write, search, clear }` | Read/write agent memory | | `useBudget()` | `{ snapshot, estimateSpend }` | Track spend against budget | | `useContextPlan()` | `{ plan, recompile }` | Inspect context compilation | | `useToolRegistry()` | `{ tools, register, remove }` | Manage tools from React | | `usePayments()` | `{ balance, send, history }` | Agentic payments wallet | | `useAgentIdentity()` | `{ identity, isVerified }` | On-chain identity (ERC-8004) | | `useAgentReputation()` | `{ score, history }` | On-chain reputation | | `useOpenClawBridge()` | `{ importSkills, translateSession }` | OpenClaw/Pi bridge | --- ## Framework adapters `@veridex/agents-adapters` lets you import agents from other frameworks into Veridex, export Veridex agents to other formats, and bridge live runtimes. ### Import/export ```ts OpenAIAgentsAdapter, LangGraphAdapter, PydanticAIAdapter, } from '@veridex/agents-adapters'; // Import from OpenAI const openaiAdapter = new OpenAIAgentsAdapter(); const { definition, warnings } = openaiAdapter.importAgent({ name: 'Billing Agent', instructions: 'Help with billing questions.', model: 'gpt-4o', tools: [{ type: 'function', function: { name: 'lookup', description: 'Look up account', parameters: {} } }], }); // Export back to OpenAI format const { config } = openaiAdapter.exportAgent(definition); // Import from LangGraph const langAdapter = new LangGraphAdapter(); const { definition: lgDef } = langAdapter.importAgent(langGraphConfig); // Import from PydanticAI const pyAdapter = new PydanticAIAdapter(); const { definition: pyDef } = pyAdapter.importAgent(pydanticAIConfig); ``` ### OpenAPI tool import Convert any OpenAPI spec directly into Veridex tool contracts: ```ts const importer = new OpenAPIImporter(); const tools = importer.importTools(openAPISpec, { baseUrl: 'https://api.example.com', auth: { type: 'bearer', value: process.env.API_TOKEN }, }); // Each path+method becomes a validated ToolContract // Register them with your agent tools.forEach(t => agent.tools.register(t)); ``` ### Runtime bridges Invoke external agent runtimes live — wrap them as Veridex tools or handoff targets: ```ts const bridge = new OpenAIRuntimeBridge({ name: 'billing-assistant', endpoint: 'https://api.openai.com/v1/agents/asst_xxx', apiKey: process.env.OPENAI_API_KEY, }); // Use as a tool const billingTool = bridge.asTool({ name: 'ask_billing_agent' }); agent.tools.register(billingTool); // Or use as a handoff target const handoff = bridge.asHandoffHandler(); ``` --- ## OpenClaw bridge `@veridex/agents-openclaw` bridges the OpenClaw / Pi agent ecosystem into Veridex. ### Import context files OpenClaw workspaces use markdown context files (`AGENTS.md`, `IDENTITY.md`, `TOOLS.md`, etc.) to configure agents. The bridge parses these into structured Veridex context sections. ```ts parseWorkspaceContextFiles, compileContextSections, } from '@veridex/agents-openclaw'; const files = { 'AGENTS.md': fs.readFileSync('workspace/AGENTS.md', 'utf8'), 'IDENTITY.md': fs.readFileSync('workspace/IDENTITY.md', 'utf8'), 'TOOLS.md': fs.readFileSync('workspace/TOOLS.md', 'utf8'), }; const contextFiles = parseWorkspaceContextFiles(files); const compiled = compileContextSections(contextFiles); // compiled['AGENTS'] → text block for agent definitions // compiled['IDENTITY'] → text block for identity config // compiled['TOOLS'] → text block for tool definitions ``` ### Import skills OpenClaw skills are defined in `SKILL.md` documents with YAML frontmatter. The bridge converts them into Veridex `ToolContract` objects. ```ts parseSkillDocument, importSkillDocument, importSkillManifest, } from '@veridex/agents-openclaw'; // Parse a single skill const doc = parseSkillDocument('web-search/SKILL.md', skillContent); const { tool, warnings } = importSkillDocument(doc); // Import a full manifest of skills const { tools, warnings: allWarnings } = importSkillManifest(manifest); ``` ### ACP agent cards Export Veridex agents as ACP-compatible agent cards, import remote capabilities, and create tools that call remote ACP agents. ```ts toACPAgentCard, fromACPCapabilities, createRemoteAgentTool, } from '@veridex/agents-openclaw'; // Export your agent as an ACP card const card = toACPAgentCard(agent.definition); // Import another agent's capabilities as tools const remoteTools = fromACPCapabilities(remoteCard.capabilities); // Create a tool that calls a remote ACP agent const remoteTool = createRemoteAgentTool({ name: 'research-agent', description: 'Performs deep research', endpoint: 'https://agents.example.com/research', auth: { type: 'bearer', token: process.env.RESEARCH_TOKEN }, timeoutMs: 30_000, }); ``` ### Session translation Migrate from OpenClaw to Veridex by translating session histories into Veridex trace events. ```ts const result = translateSession(openClawSession); // result.events → Veridex trace events // result.warnings → any translation issues ``` --- ## Enterprise control plane `@veridex/agents-control-plane` provides multi-tenant agent governance for production deployments. ### Policy packs Policy packs are versioned collections of rules that can be applied to agents at the tenant or system level. ```ts const manager = new PolicyPackManager(); // Create a versioned policy pack const pack = manager.create({ name: 'production-safety', description: 'Standard safety rules for production agents', version: '1.0.0', rules: [ { type: 'block_safety_class', params: { classes: ['destructive', 'privileged'] }, enabled: true }, { type: 'max_spend', params: { limitUSD: 100 }, enabled: true }, { type: 'require_approval', params: { classes: ['financial'] }, enabled: true }, ], tags: ['production', 'safety'], }); // System packs are immutable manager.registerSystemPack({ name: 'core-safety', description: 'Core safety rules that cannot be modified', version: '1.0.0', rules: [ { type: 'block_safety_class', params: { classes: ['privileged'] }, enabled: true }, ], }); ``` ### Trace storage Ingest, query, and verify run traces with content hashing for tamper detection. ```ts const store = new TraceStore(new InMemoryTraceStore()); // Ingest a trace const record = await store.ingest({ runId: 'run-001', agentId: 'ops-agent', tenantId: 'tenant-acme', startedAt: Date.now() - 5000, completedAt: Date.now(), state: 'completed', turnCount: 3, totalTokens: 1500, totalCostUSD: 0.02, events: traceEvents, }); // Query traces const traces = await store.query({ agentId: 'ops-agent', state: 'completed', startAfter: Date.now() - 86_400_000, limit: 50, }); // Verify content hash for tamper detection const valid = await store.verify(record.id); ``` ### Approval workflows Multi-step approval workflows with escalation chains, timeouts, and auto-approval. ```ts const engine = new ApprovalWorkflowEngine(); engine.registerWorkflow({ id: 'high-risk', name: 'High Risk Approval', description: 'Two-step escalation for high-risk actions', escalationChain: [ { name: 'Team Lead', approvers: ['lead@company.com'], timeoutSeconds: 300, minApprovals: 1 }, { name: 'VP Engineering', approvers: ['vp@company.com'], timeoutSeconds: 600, minApprovals: 1 }, ], defaultMode: 'block', }); // Start an approval process const approval = engine.startApproval('high-risk', 'run-001', 'agent-x', 'Transfer $10K', 0.9); // Cast votes engine.vote(approval.id, 'lead@company.com', true, 'Amount within limits'); // Process timeouts periodically const timedOut = engine.processTimeouts(); ``` ### Multi-tenancy Isolate agents by tenant with per-tenant configuration. ```ts const tenants = new TenantManager(); const tenant = tenants.create({ name: 'Acme Corp', config: { maxConcurrentRuns: 10, dailySpendLimitUSD: 500, allowedProviders: ['openai'], auditEnabled: true, traceRetentionDays: 90, deploymentMode: 'managed', }, }); ``` ### Deploy as HTTP service ```ts const server = await startControlPlaneServer({ port: 4500, storageBackend: 'postgres', postgres: { connectionString: process.env.DATABASE_URL }, authRequired: true, bootstrapTokens: [ { token: process.env.ADMIN_TOKEN, role: 'admin' }, ], traceRetentionDays: 90, }); ``` Connect from agent runtimes: ```ts const client = new RemoteControlPlaneClient({ baseUrl: 'https://cp.example.com', token: process.env.CP_TOKEN, tenantId: 'tenant-acme', }); ``` ### Audit export Export traces for compliance in JSON, JSONL, or CSV format with tamper-evident hashing. ```ts const result = await exportTraces(traces, 'jsonl', { includeMetadata: true, startTime: Date.now() - 86_400_000, }); // result.data — exported string // result.contentHash — SHA-256 hash for verification const bundle = await generateEvidenceBundle(traceId, policyDecisions, approvalDecisions); // bundle.contentHash — combined hash for tamper detection ``` --- ## Testing agents The core package includes utilities for deterministic, offline testing. ### Replay provider Record model interactions during a live run and replay them in tests. ```ts // Record during a live run const recorder = new TraceRecorder(); // ... attach recorder to agent, run the agent ... const trace = recorder.getTrace(); // Replay in tests — no API calls needed const replay = new ReplayProvider(trace.interactions); const response = await replay.complete(messages); ``` ### Trace comparison Compare golden traces to detect regressions. ```ts compareTraces, serializeGoldenTrace, deserializeGoldenTrace, } from '@veridex/agents'; // Save a baseline const baseline = serializeGoldenTrace(trace); fs.writeFileSync('golden/order-lookup.json', baseline); // Compare against baseline const saved = deserializeGoldenTrace(fs.readFileSync('golden/order-lookup.json', 'utf8')); const result = compareTraces(saved, currentTrace); console.log(result.identical); // boolean console.log(result.similarityScore); // 0..1 console.log(result.diffs); // EventDiff[] ``` --- ## End-to-end example: Treasury Agent This complete example shows all packages working together — tools with policy, approval workflows, memory, events, checkpoints, and a React UI. ```ts // treasury-agent.ts createAgent, tool, OpenAIProvider, blockSafetyClasses, requireApprovalFor, maxRunSpendUSD, } from '@veridex/agents'; // --- Tools --- const getBalance = tool({ name: 'get_balance', description: 'Check account balance', input: z.object({ accountId: z.string() }), safetyClass: 'read', async execute({ input }) { return { success: true, llmOutput: `Account ${input.accountId}: $12,345.67` }; }, }); const transferFunds = tool({ name: 'transfer_funds', description: 'Transfer funds between accounts', input: z.object({ from: z.string(), to: z.string(), amount: z.number().positive(), }), safetyClass: 'financial', async execute({ input }) { return { success: true, llmOutput: `Transferred $${input.amount} from ${input.from} to ${input.to}` }; }, }); // --- Agent --- const agent = createAgent( { id: 'treasury-agent', name: 'Treasury Agent', model: { provider: 'openai', model: 'gpt-4o' }, instructions: 'Help the finance team manage treasury operations safely.', tools: [getBalance, transferFunds], maxTurns: 5, }, { modelProviders: { openai: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }), }, enableTracing: true, enableCheckpoints: true, }, ); // --- Policy --- agent.policyEngine.register(blockSafetyClasses(['destructive', 'privileged'], agent.tools.list())); agent.policyEngine.register(requireApprovalFor(['financial'], agent.tools.list())); agent.policyEngine.register(maxRunSpendUSD(100)); // --- Approvals --- agent.approvals.addRoute({ match: (proposal, policy) => policy.verdict === 'escalate', mode: 'human_required', timeoutMs: 300_000, }); // --- Memory --- await agent.memory.write({ tier: 'semantic', content: 'Company policy: transfers over $5,000 require VP approval.', scope: 'global', tags: ['policy', 'transfers'], confidence: 1.0, ttlMs: Infinity, }); // --- Events --- agent.events.on('tool_started', (e) => console.log(`→ ${e.toolName}`)); agent.events.on('approval_requested', (e) => console.log(`⏳ Approval: ${e.requestId}`)); // --- Run --- const result = await agent.run('Check balance for ACCT-MAIN, then transfer $500 to ACCT-OPS'); console.log(result.output); console.log(`State: ${result.state}`); console.log(`Turns: ${result.turns.length}`); ``` ```tsx // treasury-ui.tsx function TreasuryApp() { return ( ); } function Chat() { const { run, result, isLoading } = useRun(); const [input, setInput] = React.useState(''); return ( Treasury Agent { e.preventDefault(); run(input); setInput(''); }}> setInput(e.target.value)} placeholder="Ask the agent..." /> Send {isLoading && Processing...} {result && Agent: {result.output}} ); } function Sidebar() { return ( ); } function ApprovalInbox() { const { pending, resolve } = useApprovals(); return ( Approvals ({pending.length}) {pending.map((req) => ( {req.proposal.toolName} {JSON.stringify(req.proposal.arguments, null, 2)} resolve(req.id, true)}>✓ Approve resolve(req.id, false)}>✗ Deny ))} ); } function BudgetBar() { const { snapshot } = useBudget(); return ( Budget ${snapshot.spent.toFixed(2)} / ${snapshot.budget.toFixed(2)} ); } function EventLog() { const { events } = useTrace(); return ( Events {events.slice(-10).map((e, i) => ( {e.type} — {new Date(e.timestamp).toLocaleTimeString()} ))} ); } ``` --- ## Package selection guide | I want to... | Install | |--------------|---------| | Build an agent with tools and policy | `@veridex/agents` | | Add a React chat UI with approvals | `+ @veridex/agents-react` | | Import agents from OpenAI / LangGraph / PydanticAI | `+ @veridex/agents-adapters` | | Bridge OpenClaw / Pi agents into Veridex | `+ @veridex/agents-openclaw` | | Run multi-tenant governance in production | `+ @veridex/agents-control-plane` | | Just prototype quickly | `@veridex/agents` alone is enough | --- ## Common patterns ### Pattern: Rate-limited tool execution ```ts agent.hooks.register({ name: 'rate-limit', phase: 'before_tool', async execute(context) { const count = agent.eventLog.getByType('tool_started') .filter(e => e.toolName === context.toolName).length; if (count >= 10) { return { override: 'deny', reason: `Tool ${context.toolName} exceeded 10 calls.` }; } }, }); ``` ### Pattern: Conditional tool registration ```ts if (user.role === 'admin') { agent.tools.register(deleteAccountTool); } else { // Non-admins only get read tools agent.tools.register(lookupAccountTool); } ``` ### Pattern: Multi-agent coordination with adapters ```ts // Wrap an external OpenAI agent as a tool const researchBridge = new OpenAIRuntimeBridge({ name: 'research-agent', endpoint: 'https://api.openai.com/v1/agents/asst_xxx', apiKey: process.env.OPENAI_API_KEY, }); const coordinatorAgent = createAgent({ id: 'coordinator', name: 'Coordinator', model: { provider: 'openai', model: 'gpt-4o' }, instructions: 'Coordinate between specialist agents to answer complex questions.', tools: [ researchBridge.asTool({ name: 'ask_researcher' }), lookupOrder, ], }); ``` ### Pattern: Audit everything ```ts const traceStore = new TraceStore(new InMemoryTraceStore()); agent.events.onAny(async (event) => { // Collect and store all events per run }); // After each run, ingest the trace agent.events.on('run_completed', async (e) => { await traceStore.ingest({ runId: e.runId, agentId: agent.definition.id, startedAt: e.startedAt, completedAt: e.completedAt, state: 'completed', turnCount: e.turnCount, totalTokens: e.totalTokens, totalCostUSD: e.totalCostUSD, events: agent.eventLog.getAll(), }); }); // Export for compliance const allTraces = await traceStore.query({}); const exported = await exportTraces(allTraces, 'jsonl', { includeMetadata: true }); ``` --- ## Next steps Complete API signatures, types, and method tables Payment, identity, trust, and protocol-routing primitives ADRs explaining the design rationale Get the SDK running in 5 minutes ================================================================================ URL: /guides/agent-identity ================================================================================ # Agent Identity & Reputation This guide walks through using the ERC-8004 on-chain identity and reputation system in the Veridex Agent SDK. By the end, your agent will have an on-chain identity (ERC-721 NFT), can accumulate reputation, discover other agents, and enforce trust gates before payments. > **Note:** **Passkey Wallet Required:** The Agent SDK (`@veridex/agentic-payments`) is built on `@veridex/sdk`, which requires a **passkey wallet created via a browser frontend**. Passkey operations use WebAuthn APIs and cannot run server-side. See the [Agent Payments guide](/guides/agent-payments) for the full setup flow. ## Try It — Working Examples Production-grade Next.js app with ERC-8004 identity, trust gates, Gemini AI chat, and MCP tools. Start here if you're new — minimal passkey wallet + agent payments without identity features. ## Overview The [ERC-8004](https://www.8004.org/learn) standard provides three singleton registries deployed on every EVM chain: | Registry | Purpose | |----------|---------| | **Identity** | Agent registration as ERC-721 NFT, metadata URI, wallet linkage | | **Reputation** | Feedback submission, revocation, scoring, summaries | | **Validation** | Third-party attestations (spec in progress) | The SDK provides modular clients for each registry, plus higher-level abstractions for trust gating and agent discovery. ## Prerequisites ```bash bun add @veridex/agentic-payments ``` You'll need: - A funded wallet on an ERC-8004 supported chain (Base, Ethereum, Monad, etc.) - For IPFS publishing: a Pinata or web3.storage API key (optional — data URIs work without external deps) ### Next.js Webpack Configuration If you're using Next.js, you must configure `next.config.mjs` to handle transitive dependency resolution: ```js /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, transpilePackages: ['@veridex/sdk', '@veridex/agentic-payments'], webpack: (config, { isServer }) => { config.resolve.fallback = { ...config.resolve.fallback, fs: false, net: false, tls: false, }; // Ignore problematic transitive deps from @aptos-labs/ts-sdk // that reference unexported paths in @noble/curves config.resolve.alias = { ...config.resolve.alias, '@aptos-labs/ts-sdk': false, }; return config; }, }; export default nextConfig; ``` > **Note:** **Without this config**, Next.js builds will fail with a `Module not found: Package path ./nist.js is not exported from package @noble/curves` error. See the [Agent Payments guide](/guides/agent-payments#nextjs-webpack-configuration) for details. ## 1. Register an Agent Identity ### Via AgentWallet (Recommended) ```typescript const agent = await createAgentWallet({ masterCredential: { credentialId: process.env.CREDENTIAL_ID!, publicKeyX: BigInt(process.env.PUBLIC_KEY_X!), publicKeyY: BigInt(process.env.PUBLIC_KEY_Y!), keyHash: process.env.KEY_HASH!, }, session: { dailyLimitUSD: 50, perTransactionLimitUSD: 10, expiryHours: 24, allowedChains: [10004], // Base Sepolia }, erc8004: { enabled: true, testnet: true, }, }); // Register on-chain — mints an ERC-721 NFT const { agentId, agentURI } = await agent.register({ name: 'Market Data Analyst', description: 'Autonomous agent that analyzes crypto market data', services: [ { name: 'analyze', endpoint: 'https://my-agent.com/api/analyze' }, { name: 'report', endpoint: 'https://my-agent.com/api/report' }, ], x402Support: true, }); console.log(`Registered as agent #${agentId}`); console.log(`Agent URI: ${agentURI}`); ``` ### Via IdentityClient (Standalone) ```typescript const provider = new ethers.JsonRpcProvider('https://mainnet.base.org'); const signer = new ethers.Wallet(process.env.PRIVATE_KEY!, provider); const identity = new IdentityClient(provider, signer, { testnet: false }); const { agentId, agentURI } = await identity.registerWithFile({ name: 'Price Oracle', description: 'Real-time crypto prices via Pyth + CoinGecko', services: [{ name: 'price', endpoint: 'https://oracle.example.com/price' }], }); ``` > **Note:** Registration mints an ERC-721 NFT on the Identity Registry. The NFT's `tokenURI` points to a registration file containing the agent's metadata and service endpoints. The same canonical registry address works on every EVM chain. ## 2. Build & Publish Registration Files The **RegistrationFileManager** handles the creation, validation, and publishing of ERC-8004 registration files. ### Build a Registration File ```typescript const file = RegistrationFileManager.buildRegistrationFile({ name: 'My Agent', description: 'Does cool things', services: [ { name: 'api', endpoint: 'https://my-agent.com/api' }, { name: 'webhook', endpoint: 'https://my-agent.com/webhook' }, ], }); ``` ### Validate Against Schema ```typescript const { valid, errors } = RegistrationFileManager.validate(file); if (!valid) { console.error('Validation errors:', errors); } else { console.log('Registration file is valid'); } ``` ### Publish Options **Data URI** (no external dependencies): ```typescript const dataURI = RegistrationFileManager.buildDataURI(file); // data:application/json;base64,eyJuYW1lIjoiTXkgQWdlbnQiLC... ``` **IPFS** (requires Pinata or web3.storage): ```typescript const manager = new RegistrationFileManager({ ipfs: { gateway: 'https://api.pinata.cloud', apiKey: process.env.PINATA_API_KEY!, provider: 'pinata', }, }); const ipfsURI = await manager.publishToIPFS(file); // ipfs://QmXyz... ``` ### Manage Services ```typescript // Add a service const updated = RegistrationFileManager.addService(file, { name: 'new-endpoint', endpoint: 'https://my-agent.com/new', }); // Remove a service const trimmed = RegistrationFileManager.removeService(updated, 'new-endpoint'); ``` ### Well-Known File For zero-lookup agent resolution, serve a well-known file: ```typescript const wellKnown = RegistrationFileManager.buildWellKnownFile( Number(agentId), `eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432:${agentId}`, ipfsURI, ); // Serve at: https://my-agent.com/.well-known/agent-registration.json ``` Other agents can then resolve your agent just from your URL — no on-chain lookup needed. ## 3. Query & Submit Reputation ### Get Reputation Score ```typescript // Via AgentWallet const score = await agent.getReputationScore(targetAgentId); console.log(`Agent reputation: ${score}/100`); // Detailed summary const summary = await agent.getReputation(targetAgentId); console.log(`${summary.feedbackCount} reviews`); console.log(`Score: ${summary.normalizedScore}/100`); console.log(`Tags: ${summary.tags.join(', ')}`); ``` ### Submit Feedback ```typescript await agent.submitFeedback(targetAgentId, { score: 85, tags: ['fast', 'accurate', 'reliable'], comment: 'Excellent data quality and low latency', }); ``` ### Trusted Reviewers Filter reputation to only count feedback from specific addresses: ```typescript const score = await agent.getReputationScore(targetAgentId, [ '0xTrustedReviewer1...', '0xTrustedReviewer2...', ]); ``` ### Via ReputationClient (Standalone) ```typescript const reputation = new ReputationClient(provider, signer, { testnet: false }); await reputation.submitFeedback(agentId, { score: 90, tags: ['fast'] }); const summary = await reputation.getSummary(agentId, []); const score = await reputation.getReputationScore(agentId); // Revoke previous feedback await reputation.revokeFeedback(agentId); ``` ## 4. Trust-Gated Payments The **TrustGate** enforces minimum reputation before the agent pays a merchant. This is configured via the `erc8004.minReputationScore` option. ### Configure Trust Gate ```typescript const agent = await createAgentWallet({ masterCredential: { /* ... */ }, session: { dailyLimitUSD: 100, perTransactionLimitUSD: 10, expiryHours: 24, allowedChains: [10004], }, erc8004: { enabled: true, testnet: false, minReputationScore: 30, // Reject merchants below 30/100 trustedReviewers: [ // Only count these reviewers '0xDeFiLlama...', '0xMessari...', ], }, }); ``` ### How It Works When `agent.fetch()` encounters a paywall: 1. SDK detects the payment protocol (x402, UCP, ACP, AP2) 2. **TrustGate** resolves the merchant's agent identity from the endpoint URL 3. Queries the merchant's reputation score (filtered by trusted reviewers) 4. If score < `minReputationScore` → **rejects the payment** and throws an error 5. If score >= threshold → proceeds with payment 6. After successful payment → **auto-submits reputation feedback** ### Manual Trust Check ```typescript const trust = await agent.checkMerchantTrust('https://data-provider.com'); if (trust.trusted) { console.log(`Trusted — score: ${trust.score}/100`); } else { console.log(`Untrusted — reason: ${trust.reason}`); } ``` ## 5. Discover Agents ### By Category ```typescript const sentimentAgents = await agent.discover({ category: 'sentiment', minReputation: 30, limit: 10, }); for (const a of sentimentAgents) { const score = await agent.getReputationScore(a.agentId); console.log(`Agent #${a.agentId}: ${a.name} — reputation: ${score}/100`); } ``` ### Resolve from URL ```typescript const resolved = await agent.resolveAgent('https://oracle.example.com'); if (resolved) { console.log(`Found agent #${resolved.agentId}: ${resolved.name}`); console.log(`Reputation: ${resolved.reputationScore}/100`); console.log(`Resolved from: ${resolved.resolvedFrom}`); // 'url' } ``` ### Resolve from UAI The Universal Agent Identifier (UAI) extends CAIP-2 format: ``` eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432:42 │ │ │ │ │ │ │ └── Agent ID │ │ └── Identity Registry address │ └── Chain ID (Base mainnet) └── Namespace (EVM) ``` ```typescript const resolved = await agent.resolveAgent( 'eip155:8453:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432:42' ); ``` ### Via AgentDiscovery (Standalone) ```typescript const discovery = new AgentDiscovery(identityClient, reputationClient); // Resolve from any input type const fromUrl = await discovery.resolve('https://my-agent.com'); const fromUai = await discovery.resolve('eip155:8453:0x8004A169...:42'); const fromAddr = await discovery.resolve('0x742d35Cc...'); ``` ## 6. Complete Example: Coordinator Agent A coordinator agent that discovers specialist agents, checks their reputation, hires them, and submits feedback: ```typescript async function coordinatorAgent() { const agent = await createAgentWallet({ masterCredential: { credentialId: process.env.CREDENTIAL_ID!, publicKeyX: BigInt(process.env.PUBLIC_KEY_X!), publicKeyY: BigInt(process.env.PUBLIC_KEY_Y!), keyHash: process.env.KEY_HASH!, }, session: { dailyLimitUSD: 200, perTransactionLimitUSD: 5, expiryHours: 8, allowedChains: [10004], }, erc8004: { enabled: true, testnet: true, minReputationScore: 20, }, }); // 1. Register this coordinator const { agentId } = await agent.register({ name: 'Research Coordinator', description: 'Orchestrates multi-agent research tasks', services: [{ name: 'coordinate', endpoint: 'https://coordinator.example.com/api' }], }); console.log(`Registered as agent #${agentId}`); // 2. Discover sentiment analysis agents const sentimentAgents = await agent.discover({ category: 'sentiment' }); console.log(`Found ${sentimentAgents.length} sentiment agents`); // 3. Pick the highest-reputation agent let bestAgent = null; let bestScore = 0; for (const a of sentimentAgents) { const score = await agent.getReputationScore(a.agentId); if (score > bestScore) { bestScore = score; bestAgent = a; } } if (!bestAgent) { console.log('No suitable agents found'); return; } console.log(`Hiring agent #${bestAgent.agentId} (score: ${bestScore}/100)`); // 4. Call the agent's API (trust-gated fetch handles reputation + payment) const response = await agent.fetch(bestAgent.services?.[0]?.endpoint + '/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: 'Bitcoin just hit $100K!' }), }); const result = await response.json(); console.log('Sentiment result:', result); // 5. Feedback is auto-submitted after payment // Or submit manually with custom tags: await agent.submitFeedback(bestAgent.agentId, { score: 90, tags: ['fast', 'accurate'], }); } coordinatorAgent().catch(console.error); ``` ## Canonical Registry Addresses The same singleton addresses work on **every EVM chain** (deployed via CREATE2): | Registry | Mainnet | Testnet | |----------|---------|---------| | **Identity** | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` | `0x8004A818BFB912233c491871b3d84c89A494BD9e` | | **Reputation** | `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` | `0x8004B663056A597Dffe9eCcC1965A193B7388713` | Supported chains: Base, Ethereum, Polygon, Arbitrum, Optimism, Linea, MegaETH, Monad (+ all testnets). ## Next Steps Full working Next.js app with ERC-8004 identity, Gemini AI, and trust gates Payments, spending limits, and monitoring Complete API documentation Official ERC-8004 standard ================================================================================ URL: /guides/agent-integrity ================================================================================ # Agent Integrity & Response Seals Response seals provide cryptographic tamper-evidence for every LLM response in the Veridex agent runtime. They ensure that model outputs haven't been modified between the provider and your application. > **Note:** Response seals are created **automatically** by all built-in model providers. No configuration required. ## How It Works The model provider (OpenAI, Anthropic, Gemini, etc.) returns a raw response. The provider adapter captures the raw response bytes, derives a signing key via HKDF from the API key, and computes HMAC-SHA256. The seal is attached to the `ModelResponse._seal` field and forwarded through the response envelope. Downstream consumers (control plane, audit system, or client) can verify the seal using the same API key. ## Verifying Response Seals ```typescript // From a ResponseEnvelope const envelope = result.envelope; if (envelope.chainOfCustodySeal) { const isValid = verifyResponseSeal( envelope.chainOfCustodySeal, rawResponseBytes, apiKeyBytes, ); if (!isValid) { console.error('Response may have been tampered with!'); } } ``` ## ResponseEnvelope Structure Every agent run turn produces a `ResponseEnvelope`: ```typescript interface ResponseEnvelope { runId: string; turnIndex: number; model: string; provider: string; output: string; tokensUsed: { prompt: number; completion: number; }; chainOfCustodySeal?: { algorithm: 'HMAC-SHA256'; seal: string; // hex-encoded HMAC rawHash: string; // SHA-256 of raw response bytes timestamp: number; // Unix ms when seal was created }; } ``` ## Seal Cryptography | Component | Algorithm | Purpose | |-----------|-----------|---------| | Key Derivation | HKDF-SHA256 | Derive signing key from API key | | Signing | HMAC-SHA256 | Produce tamper-evident seal | | Raw Hash | SHA-256 | Fingerprint of raw response bytes | The signing key is derived using: - **IKM**: API key bytes - **Salt**: `"veridex-response-seal-v1"` - **Info**: `"hmac-signing-key"` ## Supported Providers All built-in model providers automatically create seals: | Provider | Seal Support | |----------|-------------| | `OpenAIProvider` | Automatic | | `AnthropicProvider` | Automatic | | `GeminiProvider` | Automatic | | `OpenAICompatibleProvider` | Automatic | | `GroqProvider` | Automatic | | `TogetherAIProvider` | Automatic | | `FireworksProvider` | Automatic | | `DeepSeekProvider` | Automatic | | `PerplexityProvider` | Automatic | | `MistralProvider` | Automatic | ## Agent Integrity Bindings Beyond response seals, agents can bind their identity to a specific code + config snapshot: ```typescript interface AgentIntegrityBinding { agentId: string; identityClaims: AgentIdentityClaims; codeHash: string; // SHA-256 of agent code configHash: string; // SHA-256 of agent configuration toolManifestHash: string; // SHA-256 of registered tools timestamp: number; } ``` This allows auditors to verify that an agent's behavior matches its declared capabilities at the time of execution. ## Integration with Audit Trail Response seals are automatically: - Logged by `RawResponseHashLogger` for offline verification - Included in trace records via the control plane - Available in evidence bundles via `generateEvidenceBundle()` ## Related - [Agents Framework](/sdks/agents-framework) — ResponseSeal API - [API Reference](/api-reference/agents-framework#response-integrity) — Full type signatures - [Governance: Traces](/governance/traces) — Audit trail - [Security: Response Integrity](/security/response-integrity) — Security implications ================================================================================ URL: /guides/data-sovereignty ================================================================================ # Data Sovereignty The Veridex agent framework detects PII in tool arguments and enforces jurisdictional transfer rules, ensuring compliance with GDPR, CCPA, PIPEDA, and other data protection regulations. ## Overview Data sovereignty enforcement works at two levels: 1. **Tool-level PII tagging** — Declare which PII categories each tool exposes via `ppiTags` on `ToolContract` 2. **Security gateway evaluation** — The `dataSovereigntyPack` in `@veridex/agent-security` detects PII in tool arguments and evaluates jurisdictional rules > **Note:** PII tagging on tools is advisory — it helps the security gateway focus its evaluation. The gateway also performs independent PII detection via regex patterns and heuristics. ## Tagging Tools with PII Categories ```typescript const userLookup = tool({ name: 'lookup_user', description: 'Look up user profile by ID', input: z.object({ userId: z.string() }), safetyClass: 'read', ppiTags: ['name', 'email', 'address', 'phone'], async execute({ input }) { return { success: true, llmOutput: `User ${input.userId}: ...` }; }, }); ``` ### Supported PII Categories | Category | Examples | |----------|---------| | `email` | Email addresses | | `phone` | Phone numbers | | `ssn` | Social security numbers, national IDs | | `name` | Full names, first/last names | | `address` | Physical addresses | | `dob` | Dates of birth | | `financial` | Bank accounts, card numbers | | `health` | Medical records, diagnoses | | `biometric` | Fingerprints, face data | ## Configuring Jurisdictional Rules ```typescript const sovereignty = dataSovereigntyPack({ defaultJurisdiction: 'US', piiCategories: ['email', 'phone', 'ssn', 'name', 'address', 'dob', 'financial'], jurisdictionRules: [ { from: 'EU', to: 'US', verdict: 'block', reason: 'GDPR prohibits PII transfer to US without adequacy decision', regulations: ['GDPR Art. 44-49'], }, { from: 'EU', to: 'UK', verdict: 'allow', reason: 'UK has GDPR adequacy decision', regulations: ['GDPR Art. 45'], }, { from: 'CA', to: '*', verdict: 'flag', reason: 'PIPEDA requires consent for cross-border PII transfer', regulations: ['PIPEDA Principle 4.1.3'], }, ], toolJurisdictions: { 'store_in_eu_db': 'EU', 'send_to_us_api': 'US', 'log_to_ca_service': 'CA', }, }); ``` ## Sovereignty Violation Flow Agent invokes a tool that handles PII (detected via `ppiTags` or pattern matching). The security gateway determines the source jurisdiction (tool tag or default) and destination jurisdiction (target tool or service). Jurisdictional rules are evaluated. If a cross-border transfer violates a rule, a violation is generated. Depending on the rule verdict (`block`, `flag`, or `allow`), the tool call is blocked, flagged for review, or allowed. Violations are recorded as `SovereigntyViolationSummary` events in the trace and forwarded to the relayer for audit. ## Sovereignty Violations in Runtime The agent runtime automatically detects sovereignty violations and emits them as trace events: ```typescript interface SovereigntyViolationSummary { runId: string; agentId: string; turnIndex: number; toolName: string; piiCategories: string[]; fromJurisdiction: string; toJurisdiction: string; regulation: string; timestamp: number; } ``` Listen for violations: ```typescript agent.events.on('sovereignty_violation', (violation) => { console.log(`PII violation: ${violation.piiCategories.join(', ')}`); console.log(`${violation.fromJurisdiction} → ${violation.toJurisdiction}`); console.log(`Regulation: ${violation.regulation}`); }); ``` ## Integration with Security Gateway For agents using the standalone security gateway: ```typescript const gateway = new SecurityGateway({ packs: [ dataSovereigntyPack({ /* config */ }), // ... other packs ], }); const result = await gateway.evaluate({ type: 'tool_call', toolName: 'send_to_us_api', arguments: { email: 'user@example.com', name: 'John Doe' }, agentId: 'data-agent', }); if (result.overall === 'block') { // PII transfer blocked by sovereignty rules } ``` ## Related - [Agent Security](/sdks/agent-security) — Security gateway - [Governance: Sovereignty Compliance](/governance/sovereignty) — Audit trail - [Security: Data Sovereignty](/security/data-sovereignty) — Policy reference - [API Reference](/api-reference/agent-security#datasovereigntypack-options) — Configuration types ================================================================================ URL: /guides/examples ================================================================================ # Examples Production-ready examples covering every major Veridex SDK feature — from basic wallet creation to full-stack multisig wallets and DeFi integrations. > **Note:** All examples live in the public **[Veridex Examples Repository](https://github.com/Veridex-Protocol/examples)**. Clone it and start building in minutes. ```bash git clone https://github.com/Veridex-Protocol/examples.git cd examples npm install ``` --- ## Basic Examples Learn the SDK fundamentals step-by-step. | Example | What you'll learn | Run command | |---------|-------------------|-------------| | **Create Wallet** | Passkey registration, vault address derivation | `npm run basic:wallet` | | **Get Balances** | Multi-chain balance queries | `npm run basic:balances` | | **Send Tokens** | Prepare and execute transfers, spending limits | `npm run basic:send` | | **Cross-Chain Bridge** | Bridge tokens between chains via Wormhole | `npm run basic:crosschain` | | **Gasless Transactions** | Relayer-sponsored transfers, vault creation | `npm run basic:gasless` | ### Quick taste — Create a wallet ```typescript const sdk = createSDK('base'); await sdk.passkey.register('user@example.com', 'My Wallet'); const vaultAddress = sdk.getVaultAddress(); // Same on every EVM chain console.log('Vault:', vaultAddress); ``` --- ## Session Key Examples Session keys let users approve once and execute many transactions without repeated biometric prompts — essential for gaming, trading, and automation. | Example | What you'll learn | Run command | |---------|-------------------|-------------| | **Create Session** | `SessionManager` setup, session creation, status checks | `npm run session:create` | | **Execute Batch** | Sign multiple actions with `signAction`, nonce management | `npm run session:execute` | | **Revoke Session** | Load, verify, and revoke sessions | `npm run session:revoke` | ### Quick taste — Create and use a session ```typescript const sdk = createSDK('base'); const provider = new JsonRpcProvider('https://sepolia.base.org'); const signer = new Wallet(PRIVATE_KEY, provider); const credential = sdk.getCredential(); const hubClient = new EVMHubClientAdapter( sdk.getChainClient() as any, signer as any ); const sessionManager = new SessionManager( credential, hubClient, (challenge) => sdk.passkey.sign(challenge), { duration: 3600, maxValue: parseEther('0.1') }, ); // One passkey prompt const session = await sessionManager.createSession(); // Then sign unlimited actions within bounds — no prompts! await sessionManager.signAction({ action: 'transfer', targetChain: sdk.getChainConfig().wormholeChainId, payload: getBytes(encodedData), nonce: Number(await sdk.getNonce()), value: parseEther('0.01'), }); ``` --- ## Advanced Examples Deep-dive into cross-chain security and session lifecycle management. | Example | What you'll learn | Run command | |---------|-------------------|-------------| | **VAA Verification** | Parse Wormhole VAAs, verify Guardian quorum, validate emitters | `npm run advanced:vaa` | | **Session Lifecycle** | Full create → use → refresh → revoke flow | `npm run advanced:session` | ### VAA verification highlights ```typescript const vaa = parseVAA(vaaBase64); // Verify Guardian quorum (13/19 required) const valid = hasQuorum(vaa, true); // true = testnet // Verify emitter matches your Hub contract const emitter = normalizeEmitterAddress(vaa.emitterAddress); const hub = normalizeEmitterAddress(hubContractAddress); console.log('Emitter match:', emitter === hub); ``` --- ## Integration Examples Full-stack patterns for real-world use cases. Production-grade 2-of-3 multisig with passkey auth, gasless execution, and on-chain contracts. List, buy, and trade NFTs with passkey authentication and gasless transactions. Session keys for frictionless gameplay — buy items, complete missions, join tournaments. Automated yield farming with session-key-powered rebalancing. Accept crypto payments with invoice creation, customer payment, and webhook verification. --- ## Running Examples > **Note:** WebAuthn/Passkey functionality requires a **browser environment with HTTPS**. When running examples in Node.js, passkey registration will fail — but you can see the full API usage patterns and error handling flows. ### Environment setup Create a `.env` file in the examples root: ```env # Optional: Custom RPC URLs BASE_RPC_URL=https://sepolia.base.org OPTIMISM_RPC_URL=https://sepolia.optimism.io # Optional: Relayer for gasless transactions RELAYER_URL=https://relayer.veridex.network RELAYER_API_KEY=your-api-key # For examples that require gas payment PRIVATE_KEY=your_deployer_key_here ``` ### Supported chains | Chain | Type | Wormhole ID | |-------|------|-------------| | Base | Hub (EVM) | 10004 | | Optimism | Spoke (EVM) | 10005 | | Arbitrum | Spoke (EVM) | 10003 | | Ethereum | Spoke (EVM) | 10002 | | Solana | Spoke | 1 | | Aptos | Spoke | 22 | | Sui | Spoke | 21 | --- ## Next Steps - **[Multisig Wallet Tutorial](/guides/multisig-wallet)** — Build a production multisig app - **[Advanced Patterns](/guides/advanced-patterns)** — VAA verification and session lifecycle deep-dive - **[Session Keys Guide](/guides/session-keys)** — Understand session key concepts - **[Cross-Chain Guide](/guides/cross-chain)** — Learn Wormhole bridging ================================================================================ URL: /guides/multisig-wallet ================================================================================ # Multisig Wallet Build a production-grade multisig wallet with passkey authentication, gasless execution, and on-chain smart contracts. > **Note:** **Full source code:** [github.com/Veridex-Protocol/examples/tree/main/multisig-wallet](https://github.com/Veridex-Protocol/examples/tree/main/multisig-wallet) ## Overview This example demonstrates **two approaches** to multi-signer transaction approval: | Feature | Off-Chain (Veridex SDK) | On-Chain (Smart Contract) | |---------|------------------------|--------------------------| | **Gas costs** | Gasless — relayer pays | Signers pay gas | | **Authentication** | Passkeys (WebAuthn) | EOA wallets (MetaMask) | | **Trust model** | Relayer + backend DB | Fully trustless on-chain | | **Best for** | Consumer wallets, UX-first | DAOs, treasuries, custody | --- ## Quick Start ### Clone and install ```bash git clone https://github.com/Veridex-Protocol/examples.git cd examples/multisig-wallet bun install ``` ### Configure environment ```bash cp .env.example .env.local ``` | Variable | Description | |----------|-------------| | `DATABASE_URL` | SQLite path (`file:./data/multisig.db`) | | `NEXT_PUBLIC_RELAYER_URL` | Veridex relayer for gasless txs | | `NEXT_PUBLIC_BASE_SEPOLIA_RPC_URL` | Base Sepolia RPC | | `NEXT_PUBLIC_INTEGRATOR_SPONSOR_KEY` | Sponsor key for gasless vault creation | ### Initialize database and run ```bash bunx prisma generate bunx prisma db push bun run dev ``` Open [http://localhost:3000](http://localhost:3000). --- ## Approach 1: Off-Chain Multisig (Veridex SDK) Uses **pre-deployed vault contracts** on supported chains. Multisig logic (proposals, voting, thresholds) runs off-chain in Next.js with SQLite. Approved proposals execute through the Veridex relayer at zero gas cost. ### Architecture ``` Frontend (React) ├── AuthScreen ─── PasskeyManager (register/login) ├── Dashboard ──── MultisigContext (React Context) └── VaultPanel ├── REST API (Next.js) │ /api/wallets, /api/proposals │ /api/invites, /api/notifications ├── Prisma + SQLite └── @veridex/sdk PasskeyManager, GasSponsor, RelayerClient ``` ### Key SDK patterns ```typescript // Passkey authentication await sdk.passkey.register('user@example.com', 'My Wallet'); // Gasless vault deployment await sdk.createSponsoredVault(); // Gasless transaction execution await sdk.transferViaRelayer({ token: 'native', recipient: '0x...', amount: parseEther('0.5'), targetChain: 10004, }); // Balance queries const portfolio = await sdk.balance.getPortfolioBalance(); ``` ### Proposal lifecycle 1. **Create** — Any signer proposes a transaction (auto-approves their own vote) 2. **Vote** — Other signers approve or reject; status auto-updates at threshold 3. **Execute** — Once approved, any signer triggers execution via relayer 4. **Notify** — All signers receive notifications at each stage ```typescript // Create proposal await api.createProposal(walletId, { title: 'Pay contractor', proposalType: 'transfer', token: 'native', recipient: '0x...', amount: parseEther('0.5'), createdBy: credential.keyHash, }); // Vote await api.voteOnProposal(proposalId, { signerKeyHash: credential.keyHash, vote: 'approve', }); // Execute (gasless) const result = await sdk.transferViaRelayer(proposal.transferParams); await api.markProposalExecuted(proposalId, credential.keyHash, result.transactionHash); ``` ### Database schema (Prisma) | Model | Purpose | |-------|---------| | **Wallet** | Multisig metadata (name, threshold, vault address) | | **Signer** | Passkey key hash, name, status | | **Invite** | Shareable invite links | | **Proposal** | Transaction proposals (type, target, amount, status) | | **ProposalVote** | Individual signer votes | | **Notification** | Push notifications for events | --- ## Approach 2: On-Chain Multisig (Smart Contract) All multisig logic lives on-chain in Solidity. A factory contract deploys new wallet instances. ### Contract API **MultisigFactory:** | Function | Description | |----------|-------------| | `createWallet(name, signers, threshold, ttl)` | Deploy a new MultisigWallet | | `getWalletsByCreator(address)` | Wallets created by address | | `getWalletsBySigner(address)` | Wallets where address is signer | **MultisigWallet:** | Function | Description | |----------|-------------| | `proposeTransfer(title, desc, to, value)` | Propose native transfer | | `proposeTokenTransfer(title, desc, token, to, amount)` | Propose ERC-20 transfer | | `proposeExecute(title, desc, target, value, data)` | Propose contract call | | `proposeAddSigner` / `proposeRemoveSigner` | Signer governance | | `approve(id)` / `reject(id)` | Vote on proposal | | `execute(id)` | Execute approved proposal | ### Deployment ```bash npx hardhat run contracts/scripts/deploy.ts --network monadTestnet ``` ```bash forge create contracts/MultisigFactory.sol:MultisigFactory \ --rpc-url $RPC_URL --private-key $DEPLOYER_PRIVATE_KEY ``` ### End-to-end on-chain flow ```typescript // 1. Deploy 2-of-3 multisig const tx = await factory.createWallet( 'Team Treasury', [signer1.address, signer2.address, signer3.address], 2, 604800 // 7-day TTL ); // 2. Fund the wallet await signer1.sendTransaction({ to: walletAddress, value: ethers.parseEther('1.0') }); // 3. Propose transfer (auto-approves as signer1) await wallet.proposeTransfer( 'Pay contractor', 'Monthly payment', recipientAddress, ethers.parseEther('0.5') ); // 4. Second signer approves → 2-of-3 threshold met await wallet.connect(signer2).approve(1); // 5. Execute await wallet.execute(1); // 0.5 ETH sent ✓ ``` --- ## Project Structure ``` multisig-wallet/ ├── app/ │ ├── api/ # Next.js API routes │ │ ├── wallets/ # Wallet CRUD + signers │ │ ├── proposals/ # Vote + execute │ │ ├── invites/ # Invite accept │ │ └── notifications/ # Notification endpoints │ ├── layout.tsx # Root layout │ └── page.tsx # Main page ├── components/ │ ├── AuthScreen.tsx # Passkey registration/login │ ├── Dashboard.tsx # Main dashboard │ ├── CreateWalletModal.tsx # New wallet form │ ├── ProposalList.tsx # Transaction proposals │ └── VaultPanel.tsx # Vault management ├── contracts/ │ ├── MultisigWallet.sol # Core multisig contract │ ├── MultisigFactory.sol # Factory for deploying wallets │ └── scripts/deploy.ts # Deployment script ├── lib/ │ ├── MultisigContext.tsx # React context + SDK │ ├── api.ts # Frontend API client │ ├── config.ts # Chain configuration │ └── db.ts # Prisma access layer └── prisma/schema.prisma # Database schema ``` ## Tech Stack - **Next.js 16** — React framework with API routes - **Tailwind CSS v4** — Styling - **Prisma ORM** — SQLite database - **@veridex/sdk** — Passkey wallet SDK - **Solidity ^0.8.24** — On-chain multisig contracts --- ## Next Steps - **[Examples Hub](/guides/examples)** — Browse all available examples - **[Session Keys Guide](/guides/session-keys)** — Understand session key concepts - **[Gasless Transactions](/guides/gasless-transactions)** — Learn about relayer-sponsored execution ================================================================================ URL: /guides/advanced-patterns ================================================================================ # Advanced Patterns Deep-dive into cross-chain security and session key lifecycle management with production-ready patterns. > **Note:** **Full source code:** [github.com/Veridex-Protocol/examples/tree/main/advanced](https://github.com/Veridex-Protocol/examples/tree/main/advanced) --- ## VAA Verification Wormhole VAAs (Verifiable Action Approvals) are signed attestations from the Guardian network that prove a cross-chain message is authentic. Verifying VAAs is critical for any application that processes cross-chain data. ```bash npm run advanced:vaa ``` ### What's in a VAA ``` ┌──────────────────────────────────────────┐ │ Header │ │ Version (1 byte) │ │ Guardian Set Index (4 bytes) │ │ Number of Signatures (1 byte) │ ├──────────────────────────────────────────┤ │ Signatures (repeated) │ │ Guardian Index (1 byte) │ │ ECDSA Signature (65 bytes) │ │ Minimum 13/19 Guardians required │ ├──────────────────────────────────────────┤ │ Body │ │ Timestamp (4 bytes) │ │ Nonce (4 bytes) │ │ Emitter Chain ID (2 bytes) │ │ Emitter Address (32 bytes) │ │ Sequence Number (8 bytes) │ │ Consistency Level (1 byte) │ │ Payload (variable) │ └──────────────────────────────────────────┘ ``` ### Parse and verify a VAA ```typescript const sdk = createSDK('base'); const chainConfig = sdk.getChainConfig(); // 1. Execute a cross-chain action to get a VAA const prepared = await sdk.prepareTransfer({ token: 'native', recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5b0e7', amount: parseEther('0.0001'), targetChain: 10005, // Optimism Sepolia }); const result = await sdk.executeTransfer(prepared, signer); // 2. Fetch the VAA (wait ~15s for Guardian signatures) const vaaBytes = await sdk.fetchVAAForTransaction(result.transactionHash); // 3. Parse the VAA const vaa = parseVAA(vaaBytes); console.log('Version:', vaa.version); console.log('Signatures:', vaa.signatures.length, '/ 19'); console.log('Emitter Chain:', vaa.emitterChain); console.log('Sequence:', vaa.sequence); // 4. Verify Guardian quorum (13/19 required) const valid = hasQuorum(vaa, true); // true = testnet // 5. Verify emitter matches your Hub contract const hubContract = chainConfig.contracts?.hub || ''; const emitterMatch = normalizeEmitterAddress(vaa.emitterAddress) === normalizeEmitterAddress(hubContract); ``` ### Verification checklist Before accepting any VAA, verify all of the following: | Check | What to verify | |-------|---------------| | **Signature quorum** | At least 13/19 Guardian signatures present and valid | | **Emitter chain** | Emitter chain ID matches expected source chain | | **Emitter address** | Emitter address matches expected contract (normalized to 32 bytes) | | **Sequence** | Sequence number hasn't been processed before (replay protection) | | **Payload** | Payload format matches expected schema, parameters in range | | **Target chain** | Target chain matches the current chain | ### Common errors | Error | Cause | Fix | |-------|-------|-----| | "VAA not found" | VAA not finalized yet | Wait 15–30 seconds after transaction | | "Invalid signatures" | Tampered VAA or wrong Guardian set | Re-fetch from Wormhole API | | "Emitter mismatch" | VAA from wrong contract | Verify chain config and Hub address | | "Sequence already processed" | Replay attempt | Check sequence tracking before processing | --- ## Session Key Lifecycle Session keys let users authenticate once with a passkey and then execute multiple transactions without repeated biometric prompts. This example walks through the full create → use → refresh → revoke lifecycle. ```bash npm run advanced:session ``` ### Lifecycle overview ``` CREATE ──→ USE ──→ REFRESH (optional) ──→ REVOKE │ │ │ │ │ Passkey │ No prompts│ Passkey re-auth │ Invalidate │ auth │ needed │ extends expiry │ session key ``` ### Step 1: Initialize SessionManager ```typescript const sdk = createSDK('base'); const provider = new JsonRpcProvider('https://sepolia.base.org'); const signer = new Wallet(PRIVATE_KEY, provider); const credential = sdk.getCredential(); if (!credential) { throw new Error('No credential set. Run create-wallet first.'); } const hubClient = new EVMHubClientAdapter( sdk.getChainClient() as any, signer as any, ); const sessionManager = new SessionManager( credential, hubClient, (challenge) => sdk.passkey.sign(challenge), { duration: 3600, maxValue: parseEther('0.1') }, ); ``` ### Step 2: Create a session One passkey authentication creates the session. All subsequent actions within the session's bounds require no prompts. ```typescript const session = await sessionManager.createSession(); console.log('Key Hash:', session.keyHash); console.log('Expires:', new Date(session.expiry).toISOString()); console.log('Max Value:', formatEther(session.maxValue), 'ETH'); console.log('Active:', sessionManager.isActive()); ``` ### Step 3: Sign actions without prompts ```typescript const chainConfig = sdk.getChainConfig(); async function signTransfer(amount: bigint) { const actionPayload = await sdk.buildTransferPayload({ targetChain: chainConfig.wormholeChainId, token: 'native', recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5b0e7', amount, }); return sessionManager.signAction({ action: 'transfer', targetChain: chainConfig.wormholeChainId, payload: getBytes(actionPayload), nonce: Number(await sdk.getNonce()), value: amount, }); } // All of these execute instantly — no biometric prompts await signTransfer(parseEther('0.0001')); await signTransfer(parseEther('0.0001')); await signTransfer(parseEther('0.0001')); ``` ### Step 4: Check session status ```typescript const isActive = sessionManager.isActive(); const timeRemaining = sessionManager.getTimeRemaining(); console.log('Active:', isActive); console.log('Time left:', Math.floor(timeRemaining / 60), 'minutes'); ``` ### Step 5: Test session limits Sessions enforce value limits. Transactions exceeding `maxValue` are rejected: ```typescript try { await signTransfer(parseEther('0.2')); // Exceeds 0.1 ETH limit } catch (e) { console.log('Correctly rejected:', e.message); } ``` ### Step 6: Refresh session Extend a session before it expires (requires passkey re-authentication): ```typescript const refreshed = await sessionManager.refreshSession(); console.log('New expiry:', new Date(refreshed.expiry).toISOString()); ``` ### Step 7: Revoke session Always revoke sessions when done. Revoked sessions cannot be used: ```typescript await sessionManager.revokeSession(); // Verify console.log('Active:', sessionManager.isActive()); // false try { await signTransfer(parseEther('0.0001')); } catch (e) { console.log('Correctly rejected:', e.message); // Session revoked } ``` ### Security best practices | Practice | Recommendation | |----------|---------------| | **Duration** | Keep sessions short (1–24 hours). Longer = higher risk if compromised | | **Value limits** | Set `maxValue` appropriate to the use case. Lower = safer | | **Revocation** | Always revoke on logout. Implement automatic revocation | | **Storage** | Store session keys in encrypted storage. Never expose private keys | | **Refresh** | Refresh before expiry for seamless UX. Require periodic re-auth | | **Monitoring** | Track session usage. Alert on unusual patterns. Log all operations | ### Use cases | Use case | Duration | Max value | Notes | |----------|----------|-----------|-------| | **Gaming** | 2–4 hours | $10–50 | In-game purchases, fast actions | | **DeFi trading** | 1–2 hours | $100–1000 | Multiple swaps in a session | | **Social/tipping** | 24 hours | $5–20 | Micro-transactions, content purchases | | **Mobile apps** | 4–8 hours | $50 | Reduced biometric prompts | | **Automation** | 1 hour | Varies | Scheduled transactions, bots | --- ## Next Steps - **[Examples Hub](/guides/examples)** — Browse all available examples - **[Session Keys Guide](/guides/session-keys)** — Session key concepts and theory - **[Cross-Chain Guide](/guides/cross-chain)** — Wormhole bridging fundamentals - **[Multisig Wallet](/guides/multisig-wallet)** — Build a production multisig app ================================================================================ URL: /chains/evm ================================================================================ # EVM Chains Deploy and integrate Veridex on any EVM-compatible blockchain. ## Supported EVM Chains | Chain | Status | Chain ID | Hub | |-------|--------|----------|-----| | Base | ✅ Mainnet | 8453 | ✓ (Primary) | | Base Sepolia | ✅ Testnet | 84532 | ✓ (Testnet) | | Optimism | ✅ Mainnet | 10 | Spoke | | Arbitrum | ✅ Mainnet | 42161 | Spoke | | Ethereum | 🔜 Coming | 1 | Spoke | | Polygon | 🔜 Coming | 137 | Spoke | ## Initialize SDK ```typescript // Hub chain (Base) const hubSDK = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); // Spoke chain (Optimism) const opSDK = createSDK('optimism', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); ``` ## Hub vs Spoke Chains ### Hub Chain (Base) The hub chain is where the master passkey is registered: ```typescript // Register passkey on hub (Base) const result = await hubSDK.passkey.register('user@example.com', 'My Wallet'); // result.vaultAddress = '0x...' (same on all chains) ``` ### Spoke Chains Spoke chains derive the same vault address: ```typescript // Login on spoke chain (Optimism) await opSDK.passkey.authenticate(); // Same vault address as hub! console.log(opSDK.getVaultAddress()); // 0x...same address ``` ## Chain Configuration ### RPC URLs ```typescript const RPC_URLS = { // Mainnets base: 'https://mainnet.base.org', optimism: 'https://mainnet.optimism.io', arbitrum: 'https://arb1.arbitrum.io/rpc', // Testnets 'base-sepolia': 'https://sepolia.base.org', 'optimism-sepolia': 'https://sepolia.optimism.io', 'arbitrum-sepolia': 'https://sepolia-rollup.arbitrum.io/rpc', }; ``` ### Contract Addresses ```typescript const CONTRACTS = { base: { vaultFactory: '0x...', paymaster: '0x...', entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', }, optimism: { vaultFactory: '0x...', paymaster: '0x...', entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', }, arbitrum: { vaultFactory: '0x...', paymaster: '0x...', entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', }, }; ``` ## Multi-Chain Balances ```typescript async function getAllBalances() { // Get balances from all chains const balances = await hubSDK.getBalances(); // Returns: // { // base: { ETH: '1.5', USDC: '100.00' }, // optimism: { ETH: '0.5', USDC: '50.00' }, // arbitrum: { ETH: '0.2', USDC: '25.00' } // } return balances; } ``` ## Cross-Chain Transfers ### Via Wormhole (Fast Path - 5-7s) ```typescript async function bridgeUSDC( fromChain: 'base' | 'optimism' | 'arbitrum', toChain: 'base' | 'optimism' | 'arbitrum', amount: string ) { const sdk = createSDK(fromChain, { network: 'mainnet' }); await sdk.passkey.authenticate(); const result = await sdk.crossChainTransfer({ token: USDC_ADDRESSES[fromChain], amount: parseUnits(amount, 6), targetChain: toChain, recipient: sdk.getVaultAddress(), // Same address on target path: 'fast', // CCQ path }); return result; } ``` ### Via VAA (Standard Path - ~60s) ```typescript async function bridgeUSDCStandard( fromChain: 'base' | 'optimism' | 'arbitrum', toChain: 'base' | 'optimism' | 'arbitrum', amount: string ) { const sdk = createSDK(fromChain, { network: 'mainnet' }); await sdk.passkey.authenticate(); const result = await sdk.crossChainTransfer({ token: USDC_ADDRESSES[fromChain], amount: parseUnits(amount, 6), targetChain: toChain, recipient: sdk.getVaultAddress(), path: 'standard', // VAA path }); return result; } ``` ## Chain-Specific Considerations ### Base - Primary hub chain - Lowest gas fees - Best for passkey registration ```typescript // Recommended: Register on Base const sdk = createSDK('base', { network: 'mainnet' }); await sdk.passkey.register('user@example.com', 'Wallet'); ``` ### Optimism - L2 with low fees - EIP-4844 blob support - Superchain compatibility ```typescript const sdk = createSDK('optimism', { network: 'mainnet' }); await sdk.passkey.authenticate(); // Works identically to Base await sdk.transferViaRelayer({ token: OP_USDC, recipient: '0x...', amount: parseUnits('10', 6), }); ``` ### Arbitrum - Highest throughput L2 - Nitro architecture - Full EVM equivalence ```typescript const sdk = createSDK('arbitrum', { network: 'mainnet' }); await sdk.passkey.authenticate(); await sdk.transferViaRelayer({ token: ARB_USDC, recipient: '0x...', amount: parseUnits('10', 6), }); ``` ## Custom EVM Chain Add support for any EVM chain: ```typescript // Register custom chain registerChain({ name: 'polygon', chainId: 137, rpcUrl: 'https://polygon-rpc.com', wormholeChainId: 5, contracts: { vaultFactory: '0x...', paymaster: '0x...', entryPoint: '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789', }, tokens: { USDC: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', USDT: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', }, }); // Now use it const polygonSDK = createSDK('polygon', { network: 'mainnet' }); ``` ## Gas Estimation ```typescript async function estimateGas(chain: string, tx: TransactionRequest) { const sdk = createSDK(chain, { network: 'mainnet' }); const estimate = await sdk.estimateGas({ target: tx.to, value: tx.value || 0n, data: tx.data || '0x', }); return { gasLimit: estimate.gasLimit, gasPrice: estimate.gasPrice, maxFeePerGas: estimate.maxFeePerGas, maxPriorityFeePerGas: estimate.maxPriorityFeePerGas, estimatedCostETH: formatUnits(estimate.totalCost, 18), estimatedCostUSD: await getUSDValue(estimate.totalCost), }; } ``` ## Transaction Monitoring ```typescript async function waitForTransaction(chain: string, txHash: string) { const provider = new ethers.JsonRpcProvider(RPC_URLS[chain]); // Wait for confirmation const receipt = await provider.waitForTransaction(txHash, 1); if (receipt?.status === 0) { throw new Error('Transaction reverted'); } return receipt; } ``` ## Contract Deployment Deploy Veridex contracts to new EVM chains: ```bash # Clone contracts repo git clone https://github.com/Veridex-Protocol/contracts # Configure deployment cp .env.example .env # Edit .env with chain RPC and deployer key # Deploy npx hardhat run scripts/deploy.ts --network polygon ``` ## Security Notes 1. **Verify contracts** - Always verify deployed contracts on block explorer 2. **Test on testnet** - Deploy to testnet first 3. **Check gas limits** - Some chains have different limits 4. **Monitor finality** - Different chains have different finality times 5. **RPC reliability** - Use reliable RPC providers for production ================================================================================ URL: /chains/ethereum-sepolia ================================================================================ # Ethereum Sepolia > **Note:** **Status: Live on Testnet** 🟢 Veridex Protocol is fully operational on Ethereum Sepolia testnet. ## Overview Ethereum Sepolia is the recommended testnet for Ethereum development. Veridex's deployment on Sepolia allows developers to test the full authentication and cross-chain functionality before mainnet launch. ## Network Details | Property | Value | |----------|-------| | **Network** | Ethereum Sepolia Testnet | | **Chain ID** | `11155111` | | **Currency** | SepoliaETH | | **Block Explorer** | [sepolia.etherscan.io](https://sepolia.etherscan.io) | | **RPC Endpoint** | `https://rpc.sepolia.org` | ## Contract Addresses ```typescript Hub Contract const VERIDEX_HUB_SEPOLIA = "0x..."; // To be announced ``` ```typescript Spoke Contract const VERIDEX_SPOKE_SEPOLIA = "0x..."; // To be announced ``` ## Getting Started ### 1. Get Testnet ETH You'll need SepoliaETH to pay for gas. Get free testnet tokens from: - [Sepolia Faucet (Alchemy)](https://sepoliafaucet.com/) - [Infura Sepolia Faucet](https://www.infura.io/faucet/sepolia) - [Google Cloud Faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) ### 2. Configure the SDK ```typescript // Initialize for Ethereum Sepolia const sdk = createSDK('ethereum', { network: 'sepolia', relayerUrl: 'https://relayer.veridex.network', }); // Create or access your vault const identity = await sdk.getUnifiedIdentity(); console.log('Vault Address:', identity.vaultAddress); ``` ### 3. Register a Passkey ```typescript // Register a new passkey for the user const credential = await sdk.passkey.register({ username: 'user@example.com', }); console.log('Passkey registered:', credential.id); ``` ### 4. Execute a Gasless Transfer ```typescript // Send ETH from your vault (gasless via relayer) const result = await sdk.transferViaRelayer({ to: '0xRecipientAddress...', amount: '0.01', token: 'ETH', }); console.log('Transaction:', result.txHash); ``` ## Wormhole Integration Sepolia is connected to the Wormhole testnet network, enabling cross-chain messaging to: - Base Sepolia - Optimism Sepolia - Arbitrum Sepolia - Polygon Amoy ### Cross-Chain Transfer Example ```typescript // Bridge tokens from Sepolia to Base Sepolia const bridge = await sdk.bridge({ sourceChain: 'ethereum-sepolia', destinationChain: 'base-sepolia', amount: '0.005', token: 'ETH', }); // Monitor bridge progress bridge.on('status', (status) => { console.log('Bridge status:', status); }); ``` ## Features Available on Sepolia | Feature | Status | |---------|--------| | Passkey Registration | ✅ Live | | Passkey Authentication | ✅ Live | | Gasless Transfers | ✅ Live | | Cross-Chain Bridging | ✅ Live | | Session Keys | ✅ Live | | Spending Limits | ✅ Live | ## Resources - [Sepolia Block Explorer](https://sepolia.etherscan.io) - [Wormhole Testnet](https://wormholescan.io/#/?network=Testnet) - [SDK Documentation](/api-reference/sdk) ## Next Steps Set up a Veridex vault on Sepolia Send transactions without paying gas ================================================================================ URL: /chains/solana ================================================================================ # Solana Integration Use Veridex passkey wallets on Solana for gasless, cross-chain operations. ## Overview Veridex on Solana enables: - **Same wallet address derivation** across EVM and Solana - **Cross-chain bridges** from EVM to Solana and back - **Gasless transactions** via relayer - **Native SPL token support** ## Status | Feature | Status | |---------|--------| | Wallet derivation | ✅ Available | | Cross-chain bridge | ✅ Available | | Native transactions | 🔜 Coming | | SPL token transfers | 🔜 Coming | ## Initialize SDK ```typescript const solanaSDK = createSDK('solana', { network: 'mainnet', // or 'devnet' relayerUrl: 'https://relayer.veridex.network', }); // Login with passkey (registered on EVM hub) await solanaSDK.passkey.authenticate(); // Get Solana address (derived from same passkey) const solanaAddress = solanaSDK.getVaultAddress(); console.log('Solana address:', solanaAddress); ``` ## Address Derivation Veridex derives a Solana address from the same passkey credential: ```typescript // EVM address (Base) const evmSDK = createSDK('base', { network: 'mainnet' }); await evmSDK.passkey.authenticate(); console.log('EVM:', evmSDK.getVaultAddress()); // 0x... // Solana address (same passkey) const solanaSDK = createSDK('solana', { network: 'mainnet' }); await solanaSDK.passkey.authenticate(); console.log('Solana:', solanaSDK.getVaultAddress()); // Solana public key ``` ## Cross-Chain Bridge ### EVM to Solana ```typescript // Bridge USDC from Base to Solana async function bridgeToSolana(amount: string) { const evmSDK = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await evmSDK.passkey.authenticate(); const result = await evmSDK.crossChainTransfer({ token: BASE_USDC, amount: parseUnits(amount, 6), targetChain: 'solana', recipient: evmSDK.getSolanaAddress(), // Derived Solana address }); return result; } ``` ### Solana to EVM ```typescript // Bridge USDC from Solana to Base async function bridgeToEVM(amount: string) { const solanaSDK = createSDK('solana', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await solanaSDK.passkey.authenticate(); const result = await solanaSDK.crossChainTransfer({ token: SOLANA_USDC, amount: parseUnits(amount, 6), targetChain: 'base', recipient: solanaSDK.getEVMAddress(), // Derived EVM address }); return result; } ``` ## SPL Token Operations ### Get Balances ```typescript async function getSolanaBalances() { const solanaSDK = createSDK('solana', { network: 'mainnet' }); await solanaSDK.passkey.authenticate(); const balances = await solanaSDK.getBalances(); return { SOL: balances.solana?.SOL || '0', USDC: balances.solana?.USDC || '0', USDT: balances.solana?.USDT || '0', }; } ``` ### Transfer SPL Tokens ```typescript async function transferSPL( tokenMint: string, recipient: string, amount: bigint ) { const solanaSDK = createSDK('solana', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await solanaSDK.passkey.authenticate(); const result = await solanaSDK.transferViaRelayer({ token: tokenMint, recipient, amount, }); return result; } ``` ## Configuration ### RPC Endpoints ```typescript const SOLANA_RPC = { mainnet: 'https://api.mainnet-beta.solana.com', devnet: 'https://api.devnet.solana.com', }; ``` ### Token Mints ```typescript const SOLANA_TOKENS = { USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', USDT: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', BONK: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', }; ``` ## React Component ```tsx export function SolanaWallet() { const [address, setAddress] = useState(null); const [balances, setBalances] = useState>({}); const [loading, setLoading] = useState(false); const sdk = createSDK('solana', { network: 'mainnet' }); const connect = async () => { setLoading(true); try { await sdk.passkey.authenticate(); setAddress(sdk.getVaultAddress()); await loadBalances(); } catch (error) { console.error(error); } setLoading(false); }; const loadBalances = async () => { const result = await sdk.getBalances(); setBalances(result.solana || {}); }; return ( Solana Wallet {address ? ( {address.slice(0, 8)}...{address.slice(-8)} {Object.entries(balances).map(([token, balance]) => ( {token} {balance} ))} ) : ( {loading ? 'Connecting...' : 'Connect Solana'} )} ); } ``` ## Cross-Chain Component ```tsx export function CrossChainBridge() { const [fromChain, setFromChain] = useState<'base' | 'solana'>('base'); const [amount, setAmount] = useState(''); const [loading, setLoading] = useState(false); const handleBridge = async () => { setLoading(true); try { const sdk = createSDK(fromChain, { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await sdk.passkey.authenticate(); const targetChain = fromChain === 'base' ? 'solana' : 'base'; await sdk.crossChainTransfer({ token: fromChain === 'base' ? BASE_USDC : SOLANA_USDC, amount: parseUnits(amount, 6), targetChain, recipient: fromChain === 'base' ? sdk.getSolanaAddress() : sdk.getEVMAddress(), }); alert('Bridge complete!'); } catch (error) { console.error(error); } setLoading(false); }; return ( Bridge USDC setFromChain('base')} className={fromChain === 'base' ? 'bg-blue-600 text-white' : 'border'} > Base → Solana setFromChain('solana')} className={fromChain === 'solana' ? 'bg-purple-600 text-white' : 'border'} > Solana → Base setAmount(e.target.value)} placeholder="Amount USDC" className="w-full p-2 border rounded mb-4" /> {loading ? 'Bridging...' : 'Bridge'} ); } ``` ## Security Notes 1. **Verify addresses** - Double-check derived Solana addresses match expectations 2. **Test on devnet** - Always test bridges on devnet first 3. **Monitor bridges** - Track bridge transactions for completion 4. **Handle finality** - Solana has different finality guarantees ## Coming Soon - Native Solana program execution - Jupiter aggregator integration - Marinade staking integration - Tensor NFT marketplace support ================================================================================ URL: /chains/aptos ================================================================================ # Aptos Integration Use Veridex passkey wallets on Aptos Move blockchain. ## Overview Veridex on Aptos provides: - **Native secp256r1 support** - Aptos natively supports P-256 curves - **Same passkey, same security** across EVM and Aptos - **Cross-chain bridges** via Wormhole - **Move-based smart accounts** ## Status | Feature | Status | |---------|--------| | Wallet derivation | ✅ Available | | Cross-chain bridge | ✅ Available | | Native transactions | ✅ Available | | APT transfers | ✅ Available | | Coin transfers | ✅ Available | ## Initialize SDK ```typescript const aptosSDK = createSDK('aptos', { network: 'mainnet', // or 'testnet' relayerUrl: 'https://relayer.veridex.network', }); // Login with passkey await aptosSDK.passkey.authenticate(); // Get Aptos address const aptosAddress = aptosSDK.getVaultAddress(); console.log('Aptos address:', aptosAddress); ``` ## Native Aptos Features Aptos has native support for secp256r1 (P-256) curve, making passkey integration seamless: ```typescript // The passkey's P-256 public key works directly on Aptos // No additional abstraction layer needed! ``` ## Cross-Chain Bridge ### EVM to Aptos ```typescript async function bridgeToAptos(amount: string) { const evmSDK = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await evmSDK.passkey.authenticate(); const result = await evmSDK.crossChainTransfer({ token: BASE_USDC, amount: parseUnits(amount, 6), targetChain: 'aptos', recipient: evmSDK.getAptosAddress(), }); return result; } ``` ### Aptos to EVM ```typescript async function bridgeToEVM(amount: string) { const aptosSDK = createSDK('aptos', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await aptosSDK.passkey.authenticate(); const result = await aptosSDK.crossChainTransfer({ token: APTOS_USDC, amount: parseUnits(amount, 6), targetChain: 'base', recipient: aptosSDK.getEVMAddress(), }); return result; } ``` ## Coin Operations ### Get Balances ```typescript async function getAptosBalances() { const aptosSDK = createSDK('aptos', { network: 'mainnet' }); await aptosSDK.passkey.authenticate(); const balances = await aptosSDK.getBalances(); return { APT: balances.aptos?.APT || '0', USDC: balances.aptos?.USDC || '0', }; } ``` ### Transfer APT ```typescript async function transferAPT(recipient: string, amount: bigint) { const aptosSDK = createSDK('aptos', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await aptosSDK.passkey.authenticate(); // Native APT transfer const result = await aptosSDK.transferViaRelayer({ token: '0x1::aptos_coin::AptosCoin', recipient, amount, }); return result; } ``` ### Transfer Other Coins ```typescript async function transferCoin( coinType: string, recipient: string, amount: bigint ) { const aptosSDK = createSDK('aptos', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await aptosSDK.passkey.authenticate(); const result = await aptosSDK.transferViaRelayer({ token: coinType, // e.g., '0x1::usdc::USDC' recipient, amount, }); return result; } ``` ## Move Module Interaction Execute arbitrary Move functions: ```typescript async function callMoveFunction( moduleAddress: string, moduleName: string, functionName: string, typeArgs: string[], args: any[] ) { const aptosSDK = createSDK('aptos', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await aptosSDK.passkey.authenticate(); const result = await aptosSDK.executeViaRelayer({ target: `${moduleAddress}::${moduleName}::${functionName}`, typeArguments: typeArgs, arguments: args, }); return result; } // Example: Register a coin await callMoveFunction( '0x1', 'managed_coin', 'register', ['0x1::usdc::USDC'], [] ); ``` ## Configuration ### Network Endpoints ```typescript const APTOS_ENDPOINTS = { mainnet: 'https://fullnode.mainnet.aptoslabs.com/v1', testnet: 'https://fullnode.testnet.aptoslabs.com/v1', }; ``` ### Common Coin Types ```typescript const APTOS_COINS = { APT: '0x1::aptos_coin::AptosCoin', USDC: '0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDC', USDT: '0xf22bede237a07e121b56d91a491eb7bcdfd1f5907926a9e58338f964a01b17fa::asset::USDT', }; ``` ## React Component ```tsx export function AptosWallet() { const [address, setAddress] = useState(null); const [balances, setBalances] = useState>({}); const [loading, setLoading] = useState(false); const sdk = createSDK('aptos', { network: 'mainnet' }); const connect = async () => { setLoading(true); try { await sdk.passkey.authenticate(); setAddress(sdk.getVaultAddress()); await loadBalances(); } catch (error) { console.error(error); } setLoading(false); }; const loadBalances = async () => { const result = await sdk.getBalances(); setBalances(result.aptos || {}); }; return ( Aptos Wallet {address ? ( {address.slice(0, 10)}...{address.slice(-8)} {Object.entries(balances).map(([coin, balance]) => ( {coin} {balance} ))} ) : ( {loading ? 'Connecting...' : 'Connect Aptos'} )} ); } ``` ## NFT Operations ### Mint NFT ```typescript async function mintNFT( collection: string, name: string, description: string, uri: string ) { const aptosSDK = createSDK('aptos', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await aptosSDK.passkey.authenticate(); const result = await aptosSDK.executeViaRelayer({ target: '0x4::token::mint', typeArguments: [], arguments: [collection, name, description, uri], }); return result; } ``` ### Transfer NFT ```typescript async function transferNFT( recipient: string, creator: string, collection: string, name: string, propertyVersion: number ) { const aptosSDK = createSDK('aptos', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await aptosSDK.passkey.authenticate(); const result = await aptosSDK.executeViaRelayer({ target: '0x3::token::transfer', typeArguments: [], arguments: [recipient, creator, collection, name, propertyVersion, 1], }); return result; } ``` ## Security Notes 1. **Native P-256** - Aptos natively supports the passkey curve 2. **Account model** - Aptos uses resource-based accounts 3. **Gas estimation** - Gas units differ from EVM gas 4. **Sequence numbers** - Aptos uses sequence numbers instead of nonces ## Aptos-Specific Features - **Parallel execution** - Transactions can execute in parallel - **Move language** - Type-safe smart contracts - **Upgradeability** - Modules can be upgraded - **Resources** - Data stored as typed resources ================================================================================ URL: /chains/sui ================================================================================ # Sui Integration Use Veridex passkey wallets on Sui Move blockchain. ## Overview Veridex on Sui provides: - **Object-based model** integration - **Cross-chain bridges** via Wormhole - **Native SUI and coin transfers** - **Move object interactions** ## Status | Feature | Status | |---------|--------| | Wallet derivation | ✅ Available | | Cross-chain bridge | ✅ Available | | Native transactions | 🔜 Coming | | SUI transfers | 🔜 Coming | ## Initialize SDK ```typescript const suiSDK = createSDK('sui', { network: 'mainnet', // or 'testnet' relayerUrl: 'https://relayer.veridex.network', }); // Login with passkey await suiSDK.passkey.authenticate(); // Get Sui address const suiAddress = suiSDK.getVaultAddress(); console.log('Sui address:', suiAddress); ``` ## Cross-Chain Bridge ### EVM to Sui ```typescript async function bridgeToSui(amount: string) { const evmSDK = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await evmSDK.passkey.authenticate(); const result = await evmSDK.crossChainTransfer({ token: BASE_USDC, amount: parseUnits(amount, 6), targetChain: 'sui', recipient: evmSDK.getSuiAddress(), }); return result; } ``` ### Sui to EVM ```typescript async function bridgeToEVM(amount: string) { const suiSDK = createSDK('sui', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await suiSDK.passkey.authenticate(); const result = await suiSDK.crossChainTransfer({ token: SUI_USDC, amount: parseUnits(amount, 6), targetChain: 'base', recipient: suiSDK.getEVMAddress(), }); return result; } ``` ## Coin Operations ### Get Balances ```typescript async function getSuiBalances() { const suiSDK = createSDK('sui', { network: 'mainnet' }); await suiSDK.passkey.authenticate(); const balances = await suiSDK.getBalances(); return { SUI: balances.sui?.SUI || '0', USDC: balances.sui?.USDC || '0', }; } ``` ### Transfer SUI ```typescript async function transferSUI(recipient: string, amount: bigint) { const suiSDK = createSDK('sui', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await suiSDK.passkey.authenticate(); const result = await suiSDK.transferViaRelayer({ token: '0x2::sui::SUI', recipient, amount, }); return result; } ``` ## Configuration ### Network Endpoints ```typescript const SUI_ENDPOINTS = { mainnet: 'https://fullnode.mainnet.sui.io:443', testnet: 'https://fullnode.testnet.sui.io:443', }; ``` ### Common Coin Types ```typescript const SUI_COINS = { SUI: '0x2::sui::SUI', USDC: '0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN', USDT: '0xc060006111016b8a020ad5b33834984a437aaa7d3c74c18e09a95d48aceab08c::coin::COIN', }; ``` ## React Component ```tsx export function SuiWallet() { const [address, setAddress] = useState(null); const [balances, setBalances] = useState>({}); const [loading, setLoading] = useState(false); const sdk = createSDK('sui', { network: 'mainnet' }); const connect = async () => { setLoading(true); try { await sdk.passkey.authenticate(); setAddress(sdk.getVaultAddress()); await loadBalances(); } catch (error) { console.error(error); } setLoading(false); }; const loadBalances = async () => { const result = await sdk.getBalances(); setBalances(result.sui || {}); }; return ( Sui Wallet {address ? ( {address.slice(0, 10)}...{address.slice(-8)} {Object.entries(balances).map(([coin, balance]) => ( {coin} {balance} ))} ) : ( {loading ? 'Connecting...' : 'Connect Sui'} )} ); } ``` ## Object Model Sui uses an object-centric model: ```typescript // Owned objects are transferred directly async function transferObject(objectId: string, recipient: string) { const suiSDK = createSDK('sui', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await suiSDK.passkey.authenticate(); const result = await suiSDK.executeViaRelayer({ target: '0x2::transfer::transfer', typeArguments: ['0x2::coin::Coin'], arguments: [objectId, recipient], }); return result; } ``` ## Security Notes 1. **Object ownership** - Objects are owned by addresses 2. **Gas objects** - SUI coins used for gas must be owned 3. **Shared objects** - Require consensus for access 4. **Programmable transactions** - Multiple operations in one tx ## Sui-Specific Features - **Parallel execution** - Independent objects processed in parallel - **Programmable Transaction Blocks** - Compose multiple operations - **Dynamic fields** - Extensible object storage - **Sponsored transactions** - Third-party gas payment ================================================================================ URL: /chains/starknet ================================================================================ # Starknet Integration Use Veridex passkey wallets on Starknet L2. ## Overview Veridex on Starknet provides: - **Native account abstraction** - Starknet has built-in AA - **Cairo smart accounts** - Passkey verification in Cairo - **Cross-chain bridges** via Wormhole - **ZK-proof based scaling** ## Status | Feature | Status | |---------|--------| | Wallet derivation | ✅ Available | | Cross-chain bridge | ✅ Available | | Native transactions | 🔜 Coming | | ERC-20 transfers | 🔜 Coming | ## Initialize SDK ```typescript const starknetSDK = createSDK('starknet', { network: 'mainnet', // or 'testnet' relayerUrl: 'https://relayer.veridex.network', }); // Login with passkey await starknetSDK.passkey.authenticate(); // Get Starknet address const starknetAddress = starknetSDK.getVaultAddress(); console.log('Starknet address:', starknetAddress); ``` ## Native Account Abstraction Starknet has native account abstraction, making passkey wallets a natural fit: ```typescript // Starknet accounts are smart contracts by design // Our Cairo account verifies P-256 passkey signatures natively ``` ## Cross-Chain Bridge ### EVM to Starknet ```typescript async function bridgeToStarknet(amount: string) { const evmSDK = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await evmSDK.passkey.authenticate(); const result = await evmSDK.crossChainTransfer({ token: BASE_USDC, amount: parseUnits(amount, 6), targetChain: 'starknet', recipient: evmSDK.getStarknetAddress(), }); return result; } ``` ### Starknet to EVM ```typescript async function bridgeToEVM(amount: string) { const starknetSDK = createSDK('starknet', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await starknetSDK.passkey.authenticate(); const result = await starknetSDK.crossChainTransfer({ token: STARKNET_USDC, amount: parseUnits(amount, 6), targetChain: 'base', recipient: starknetSDK.getEVMAddress(), }); return result; } ``` ## Token Operations ### Get Balances ```typescript async function getStarknetBalances() { const starknetSDK = createSDK('starknet', { network: 'mainnet' }); await starknetSDK.passkey.authenticate(); const balances = await starknetSDK.getBalances(); return { ETH: balances.starknet?.ETH || '0', USDC: balances.starknet?.USDC || '0', STRK: balances.starknet?.STRK || '0', }; } ``` ### Transfer Tokens ```typescript async function transferToken( tokenAddress: string, recipient: string, amount: bigint ) { const starknetSDK = createSDK('starknet', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await starknetSDK.passkey.authenticate(); const result = await starknetSDK.transferViaRelayer({ token: tokenAddress, recipient, amount, }); return result; } ``` ## Configuration ### Network Endpoints ```typescript const STARKNET_ENDPOINTS = { mainnet: 'https://starknet-mainnet.public.blastapi.io', testnet: 'https://starknet-sepolia.public.blastapi.io', }; ``` ### Token Addresses ```typescript const STARKNET_TOKENS = { ETH: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', USDC: '0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8', STRK: '0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d', }; ``` ## React Component ```tsx export function StarknetWallet() { const [address, setAddress] = useState(null); const [balances, setBalances] = useState>({}); const [loading, setLoading] = useState(false); const sdk = createSDK('starknet', { network: 'mainnet' }); const connect = async () => { setLoading(true); try { await sdk.passkey.authenticate(); setAddress(sdk.getVaultAddress()); await loadBalances(); } catch (error) { console.error(error); } setLoading(false); }; const loadBalances = async () => { const result = await sdk.getBalances(); setBalances(result.starknet || {}); }; return ( Starknet Wallet {address ? ( {address.slice(0, 10)}...{address.slice(-8)} {Object.entries(balances).map(([token, balance]) => ( {token} {balance} ))} ) : ( {loading ? 'Connecting...' : 'Connect Starknet'} )} ); } ``` ## Cairo Contracts ### P-256 Verification Starknet's Cairo supports P-256 signature verification: ```cairo // Our Cairo account contract #[starknet::contract] mod PasskeyAccount { use starknet::secp256r1::{secp256r1_verify, Secp256r1Point}; #[storage] struct Storage { public_key: Secp256r1Point, } #[external(v0)] fn __validate__(ref self: ContractState, calls: Array) -> felt252 { // Verify P-256 signature from passkey let signature = get_tx_signature(); let tx_hash = get_tx_hash(); assert( secp256r1_verify(tx_hash, signature, self.public_key.read()), 'Invalid signature' ); VALIDATED } } ``` ## Multi-Call Transactions Starknet supports multiple calls in one transaction: ```typescript async function multiCall(calls: Array<{to: string, selector: string, calldata: string[]}>) { const starknetSDK = createSDK('starknet', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); await starknetSDK.passkey.authenticate(); const result = await starknetSDK.executeViaRelayer({ calls: calls.map(call => ({ contractAddress: call.to, entrypoint: call.selector, calldata: call.calldata, })), }); return result; } ``` ## Security Notes 1. **ZK-proofs** - All transactions are verified via STARK proofs 2. **Native AA** - Account abstraction is built into the protocol 3. **Felt arithmetic** - Uses 252-bit field elements 4. **Cairo VM** - Provable execution environment ## Starknet-Specific Features - **STARK proofs** - Computational integrity proofs - **Native AA** - All accounts are smart contracts - **Fee abstraction** - Pay gas in any token - **Multi-call** - Multiple operations in single tx ================================================================================ URL: /chains/stacks ================================================================================ # Stacks Veridex supports Stacks as a spoke chain with native passkey verification via Clarity's built-in `secp256r1-verify`. ## Why Stacks? Stacks is uniquely suited for Veridex because of three native Clarity features: | Feature | Benefit | |---------|---------| | **`secp256r1-verify`** | Native P-256 passkey signature verification — no ZK proofs needed | | **`secp256k1-recover?`** | Native session key verification | | **Sponsored transactions** | Gasless operations — relayer pays STX fees | | **Post-Conditions** | Protocol-level spending safety unique to Stacks | ## Chain Configuration | Property | Testnet | Mainnet | |----------|---------|---------| | **CAIP-2 ID** | `stacks:2147483648` | `stacks:1` | | **Wormhole Chain ID** | 50003 | 50004 | | **RPC URL** | `https://api.testnet.hiro.so` | `https://api.hiro.so` | | **Explorer** | `explorer.hiro.so/?chain=testnet` | `explorer.hiro.so` | | **Native Token** | STX | STX | ## SDK Integration ### Core SDK ```typescript // Initialize for Stacks testnet const sdk = createSDK('stacks', { network: 'testnet' }); // Register passkey (verified on-chain via secp256r1-verify) const credential = await sdk.passkey.register('alice', 'My Stacks Wallet'); // Get vault address const vault = sdk.getVaultAddress(); ``` ### Agent SDK ```typescript // Stacks-specific chain client with Pyth pricing const client = new StacksChainClient({ network: 'testnet' }); // Get real-time STX price const stxPrice = await client.getNativeTokenPriceUSD(); console.log('STX/USD:', stxPrice); // USD-aware spending tracker const tracker = new StacksSpendingTracker({ dailyLimitUSD: 100, network: 'testnet', }); // Convert microSTX to USD const usdValue = await tracker.convertToUSD(1000000n, 'STX'); // 1 STX ``` ### Stacks Utilities The SDK exports Stacks-specific utilities: ```typescript // Address utilities isValidStacksPrincipal, isValidStacksStandardPrincipal, isValidStacksContractName, getStacksNetworkFromAddress, getStacksContractPrincipal, parseStacksContractPrincipal, isStacksContractPrincipal, getStacksExplorerTxUrl, getStacksExplorerAddressUrl, // Cryptographic utilities stacksCompressPublicKey, stacksRsToCompactSignature, stacksDerToCompactSignature, stacksComputeKeyHash, stacksComputeKeyHashFromCoords, // Hash builders for contract interactions stacksBuildRegistrationHash, stacksBuildSessionRegistrationHash, stacksBuildRevocationHash, stacksBuildExecuteHash, stacksBuildWithdrawalHash, // Post-condition builders buildStxWithdrawalPostConditions, buildStxDepositPostConditions, buildSbtcWithdrawalPostConditions, buildStacksExecutePostConditions, validateStacksPostConditions, } from '@veridex/sdk'; ``` ## Deployed Contracts All contracts are deployed on Stacks testnet: | Contract | Address | |----------|---------| | **vault-trait** | `ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.vault-trait` | | **session-trait** | `ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.session-trait` | | **veridex-session-verifier** | `ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-session-verifier` | | **veridex-spoke** | `ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-spoke` | | **veridex-vault** | `ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-vault` | | **veridex-wormhole-verifier** | `ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-wormhole-verifier` | | **veridex-vault-vaa** | `ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-vault-vaa` | ## Architecture ### Three-Layer Spending Limits Stacks provides a unique three-layer spending safety model: ```mermaid block-beta columns 1 block:L1["Layer 1: SDK (Off-chain)"] A["SpendingTracker — USD-aware limits\nChecked before any transaction"] end block:L2["Layer 2: Contract (On-chain)"] B["veridex-vault — daily/per-tx limits\nEnforced by Clarity smart contract"] end block:L3["Layer 3: Post-Conditions (Protocol)"] C["Stacks native — cannot be bypassed\nGuarantees max transfer amounts"] end ``` ### Passkey Flow on Stacks ```mermaid flowchart TD A["User authenticates with FaceID/TouchID"] --> B["WebAuthn P-256 signature generated"] B --> C["Clarity contract calls secp256r1-verify"] C --> D["Signature verified natively (no ZK needed)"] D --> E["Action executed on Stacks"] ``` ### Session Key Flow ```mermaid flowchart TD A["Passkey creates session key (one biometric prompt)"] --> B["Session key registered on veridex-spoke"] B --> C["Agent uses session key for transactions"] C --> D["Clarity calls secp256k1-recover? to verify"] D --> E["veridex-vault checks limits and executes"] ``` ## Post-Conditions Stacks Post-Conditions are a protocol-level safety feature. They guarantee that a transaction will fail if it transfers more than the specified amount. ```typescript buildStxWithdrawalPostConditions, validateStacksPostConditions, } from '@veridex/sdk'; // Build post-conditions for a withdrawal const postConditions = buildStxWithdrawalPostConditions({ vaultAddress: 'ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-vault', amount: 1000000n, // 1 STX in microSTX }); // Validate post-conditions before submitting const validation = validateStacksPostConditions(postConditions); if (!validation.valid) { console.warn('Post-condition issues:', validation.warnings); } ``` ## Gasless Transactions Stacks supports native sponsored transactions. The relayer can pay STX gas fees on behalf of users: ```typescript // The relayer handles sponsorship automatically // Users never need to hold STX for gas const sdk = createSDK('stacks', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); ``` ### Relayer Sponsorship Limits | Limit | Value | |-------|-------| | Per-identity rate | 50 tx/hour | | Per-tx fee cap | 0.5 STX | | Hourly budget | 50 STX | ## x402 on Stacks The Stacks x402 integration uses the `x402-stacks` protocol for HTTP payment gating: ```typescript const agent = await createAgentWallet({ masterCredential: { /* ... */ }, session: { dailyLimitUSD: 50, perTransactionLimitUSD: 10, expiryHours: 24, allowedChains: [50003], // Stacks testnet }, }); // Agent automatically handles x402 payments in STX const response = await agent.fetch('https://stacks-data-api.example.com/market'); ``` ## Testnet Faucet Get testnet STX from the Stacks faucet: - [Stacks Testnet Faucet](https://explorer.hiro.so/sandbox/faucet?chain=testnet) ## Next Steps Build autonomous agents on Stacks Configure session keys for Stacks Set up three-layer spending limits ================================================================================ URL: /integrations/nextjs ================================================================================ # Next.js Integration This guide covers SSR-safe patterns for integrating Veridex with Next.js, a production payment app that uses dynamic imports, cross-origin passkey authentication, and relayer-based gasless transfers. > **Note:** The Veridex SDK uses browser APIs (WebAuthn, localStorage) and **must only run in Client Components**. Always add `'use client'` to components using the SDK. For SSR-safe initialization, use **dynamic imports** as shown below. ## Installation ```bash npm install @veridex/sdk ethers ``` ## SSR-Safe SDK Initialization (Dynamic Import) The sera/dashboard uses a **dynamic import + singleton pattern** to prevent the SDK from being loaded during server-side rendering: ```typescript // lib/veridex-client.ts const SERA_HUB_CHAIN = 'base'; const SERA_NETWORK = 'testnet'; const RELAYER_URL = process.env.NEXT_PUBLIC_VERIDEX_RELAYER_URL || 'https://relay.veridex.network'; const RELAYER_API_KEY = process.env.NEXT_PUBLIC_RELAYER_API_KEY || ''; let sdkInstance: any = null; /** * Lazy-load the SDK to avoid SSR issues. * The SDK uses browser APIs (WebAuthn, localStorage) that don't exist on the server. */ export async function getVeridexSDK(): Promise { if (!sdkInstance) { const { createSDK } = await import('@veridex/sdk'); sdkInstance = createSDK(SERA_HUB_CHAIN, { network: SERA_NETWORK, relayerUrl: RELAYER_URL, relayerApiKey: RELAYER_API_KEY, }); } return sdkInstance; } ``` > **Note:** The `await import('@veridex/sdk')` ensures the SDK bundle is only loaded in the browser, never during SSR. This is critical for Next.js App Router where server components are the default. ## Passkey Registration & Authentication ### Register a New Passkey ```typescript export async function registerPasskey(username: string, displayName?: string) { const sdk = await getVeridexSDK(); // Register passkey via WebAuthn (prompts biometric) const credential = await sdk.passkey.register( username, displayName || username ); // Save credential to localStorage for auto-login sdk.passkey.saveToLocalStorage(); // Sync credential to relayer for cross-device recovery await sdk.passkey.saveCredentialToRelayer(); // Compute the vault address (deterministic from passkey) const vaultAddress = sdk.getVaultAddress(); return { credential, vaultAddress }; } ``` ### Authenticate with Existing Passkey ```typescript export async function authenticateWithPasskey() { const sdk = await getVeridexSDK(); // Discoverable credential authentication (shows passkey picker) const { credential } = await sdk.passkey.authenticate(); sdk.passkey.saveToLocalStorage(); const vaultAddress = sdk.getVaultAddress(); return { credential, vaultAddress }; } ``` ### Sign a Challenge ```typescript export async function signWithPasskey(challenge: string) { const sdk = await getVeridexSDK(); const signature = await sdk.passkey.sign(challenge); return signature; } ``` ### Restore Credential from Storage ```typescript export async function restoreSDKCredential(): Promise { const sdk = await getVeridexSDK(); // Try localStorage first const saved = sdk.passkey.loadFromLocalStorage(); if (saved) { sdk.setCredential(saved); return true; } // Try fetching from relayer (cross-device recovery) try { const relayerCred = await sdk.passkey.loadCredentialFromRelayer(); if (relayerCred) { sdk.setCredential(relayerCred); sdk.passkey.saveToLocalStorage(); return true; } } catch (error) { console.warn('Could not restore from relayer:', error); } return false; } ``` ## Cross-Origin Passkey Authentication When your app runs on a different domain than the passkey's RP ID, use `CrossOriginAuth`: ```typescript export async function authenticateCrossOrigin() { const crossOriginAuth = new CrossOriginAuth({ authPortalUrl: 'https://auth.veridex.network', relayerUrl: RELAYER_URL, rpId: 'veridex.network', }); // Tries Related Origin Requests (ROR) first, falls back to auth portal popup const result = await crossOriginAuth.authenticate(); if (result.credential) { const sdk = await getVeridexSDK(); sdk.setCredential(result.credential); return result; } throw new Error('Cross-origin authentication failed'); } ``` > **Note:** Cross-origin auth uses the [Related Origin Requests](https://w3c.github.io/webauthn/#sctn-related-origins) spec where supported, with an auth portal popup as fallback. This enables passkey sharing across `*.veridex.network` subdomains. ## Gasless Transfers via Relayer The sera/dashboard uses a prepare → sign → execute flow for gasless payments: ### Prepare a Transfer ```typescript export async function prepareTransfer(params: { token: string; recipient: string; amount: string; chain?: number; }) { const sdk = await getVeridexSDK(); const prepared = await sdk.prepareTransfer({ targetChain: params.chain || 10004, // Base Sepolia token: params.token, recipient: params.recipient, amount: BigInt(params.amount), }); return prepared; } ``` ### Execute via Relayer (Gasless) ```typescript export async function executeSignedTransfer( transferId: string, signature: string ) { const sdk = await getVeridexSDK(); // Execute through relayer — user pays no gas const result = await sdk.transferViaRelayer({ transferId, signature, }); return result; } ``` ### Poll for Transaction Completion ```typescript export async function pollForTransactionCompletion( sequenceOrHubTx: string, maxAttempts = 30, intervalMs = 2000 ): Promise<{ status: string; txHash?: string }> { for (let i = 0; i < maxAttempts; i++) { try { const response = await fetch( `${RELAYER_URL}/api/v1/tx/status/${sequenceOrHubTx}` ); const data = await response.json(); if (data.status === 'confirmed' || data.status === 'failed') { return data; } } catch (error) { console.warn('Poll attempt failed:', error); } await new Promise(resolve => setTimeout(resolve, intervalMs)); } return { status: 'timeout' }; } ``` ## Balance Queries ```typescript export async function getBalance(address: string, token?: string) { const sdk = await getVeridexSDK(); const portfolio = await sdk.balance.getPortfolioBalance(address, token); return portfolio; } ``` ## Compute Vault Addresses Vault addresses are deterministic — the same passkey always produces the same address on every chain: ```typescript export function computeVaultAddress( keyHash: string, chainConfig: { vaultFactory: string; vaultImplementation: string } ) { return WalletManager.computeVaultAddress( keyHash, chainConfig.vaultFactory, chainConfig.vaultImplementation ); } ``` ## React Context (Wallet Provider) The sera/dashboard wraps all SDK operations in a `WalletContext` that supports multiple connection methods: ```tsx // lib/wallet-context.tsx 'use client'; getVeridexSDK, registerPasskey, authenticateWithPasskey, signWithPasskey, restoreSDKCredential, prepareTransfer, executeSignedTransfer, getBalance, } from './veridex-client'; type ConnectionMethod = 'injected' | 'walletconnect' | 'passkey'; interface WalletContextType { address: string | null; isConnected: boolean; connectionMethod: ConnectionMethod | null; isLoading: boolean; // Passkey methods connectPasskey: () => Promise; registerNewPasskey: (username: string) => Promise; signMessage: (message: string) => Promise; // Payment methods preparePayment: (params: any) => Promise; confirmPayment: (transferId: string) => Promise; // Balance balance: string | null; refreshBalance: () => Promise; disconnect: () => void; } const WalletContext = createContext(undefined); export function WalletProvider({ children }: { children: ReactNode }) { const [address, setAddress] = useState(null); const [isConnected, setIsConnected] = useState(false); const [connectionMethod, setConnectionMethod] = useState(null); const [isLoading, setIsLoading] = useState(true); const [balance, setBalance] = useState(null); // Auto-restore session on mount useEffect(() => { const restore = async () => { try { const restored = await restoreSDKCredential(); if (restored) { const sdk = await getVeridexSDK(); const vault = sdk.getVaultAddress(); setAddress(vault); setIsConnected(true); setConnectionMethod('passkey'); } } catch (error) { console.warn('Session restore failed:', error); } finally { setIsLoading(false); } }; restore(); }, []); const connectPasskey = async () => { setIsLoading(true); try { const { vaultAddress } = await authenticateWithPasskey(); setAddress(vaultAddress); setIsConnected(true); setConnectionMethod('passkey'); } finally { setIsLoading(false); } }; const registerNewPasskey = async (username: string) => { setIsLoading(true); try { const { vaultAddress } = await registerPasskey(username); setAddress(vaultAddress); setIsConnected(true); setConnectionMethod('passkey'); } finally { setIsLoading(false); } }; const signMessage = async (message: string) => { return await signWithPasskey(message); }; const preparePayment = async (params: any) => { return await prepareTransfer(params); }; const confirmPayment = async (transferId: string) => { // Sign with passkey, then execute via relayer const signature = await signWithPasskey(transferId); return await executeSignedTransfer(transferId, signature); }; const refreshBalance = async () => { if (!address) return; try { const portfolio = await getBalance(address); setBalance(portfolio?.totalUsdValue?.toFixed(2) || '0.00'); } catch (error) { console.error('Balance refresh failed:', error); } }; const disconnect = () => { setAddress(null); setIsConnected(false); setConnectionMethod(null); setBalance(null); localStorage.removeItem('veridex_credentials'); }; return ( {children} ); } export function useWallet() { const context = useContext(WalletContext); if (!context) throw new Error('useWallet must be used within WalletProvider'); return context; } ``` ## App Router Setup ### Layout ```tsx // app/layout.tsx export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ### Page Component ```tsx // app/page.tsx 'use client'; export default function Home() { const { isConnected, address, isLoading, connectPasskey, registerNewPasskey, disconnect, balance, refreshBalance, } = useWallet(); if (isLoading) return Loading...; if (!isConnected) { return ( Sign In with Passkey registerNewPasskey('user@example.com')}> Create New Wallet ); } return ( Connected: {address?.slice(0, 6)}...{address?.slice(-4)} Balance: ${balance || '...'} Refresh Disconnect ); } ``` ## API Routes (Relayer Proxy) Proxy relayer requests through your Next.js API routes to keep API keys server-side: ```typescript // app/api/relayer/[...path]/route.ts const RELAYER_URL = process.env.RELAYER_URL || 'https://relay.veridex.network'; const RELAYER_API_KEY = process.env.RELAYER_API_KEY || ''; export async function GET( request: NextRequest, { params }: { params: { path: string[] } } ) { const path = params.path.join('/'); const response = await fetch(`${RELAYER_URL}/api/v1/${path}`, { headers: { 'x-api-key': RELAYER_API_KEY, 'Content-Type': 'application/json', }, }); const data = await response.json(); return NextResponse.json(data, { status: response.status }); } export async function POST( request: NextRequest, { params }: { params: { path: string[] } } ) { const path = params.path.join('/'); const body = await request.json(); const response = await fetch(`${RELAYER_URL}/api/v1/${path}`, { method: 'POST', headers: { 'x-api-key': RELAYER_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify(body), }); const data = await response.json(); return NextResponse.json(data, { status: response.status }); } ``` ## Environment Variables ```bash # .env.local (client-side — exposed to browser) NEXT_PUBLIC_VERIDEX_RELAYER_URL=https://relay.veridex.network NEXT_PUBLIC_RELAYER_API_KEY=your-public-api-key # .env.local (server-side — API routes only) RELAYER_URL=https://relay.veridex.network RELAYER_API_KEY=your-secret-api-key ``` ## Server Components Considerations > **Note:** **Never import `@veridex/sdk` in Server Components.** The SDK requires browser APIs. Use the dynamic import pattern shown above, or keep all SDK usage in `'use client'` components. For server-side data fetching, call the relayer API directly: ```typescript // app/vault/[address]/page.tsx (Server Component) export default async function VaultPage({ params }: { params: { address: string } }) { const res = await fetch( `${process.env.RELAYER_URL}/api/v1/balances/${params.address}`, { headers: { 'x-api-key': process.env.RELAYER_API_KEY! } } ); const balances = await res.json(); return ( Vault: {params.address} {JSON.stringify(balances, null, 2)} ); } ``` ## Complete Example Clone the sera/dashboard for a complete Next.js integration with passkey payments: ```bash git clone https://github.com/Veridex-Protocol/demo cd demo/sera/dashboard cp .env.example .env.local npm install npm run dev ``` Open [http://localhost:3000](http://localhost:3000). ## Next Steps Multi-chain context provider with VeridexSDK class Share passkeys across domains with ROR How relayer-sponsored transfers work Full payment integration example ================================================================================ URL: /integrations/react ================================================================================ # React Integration Complete guide to integrating Veridex with React applications, based on real patterns from the [test-app](https://github.com/Veridex-Protocol/demo/tree/main/test-app) — a production-grade multi-chain passkey wallet. > **Note:** The Veridex SDK uses browser APIs (WebAuthn, localStorage) and **must only run in client-side code**. In Next.js, always add `'use client'` to components that use the SDK. ## Setup ### Install Dependencies > **Note:** **Recommended:** For most React apps, use the `@veridex/react` hooks package instead of manual SDK integration. See the [React Hooks Guide](/guides/react-hooks) for pre-built hooks with loading states, error handling, chain name resolution, and polling. ```bash npm install @veridex/sdk ethers ``` ### Two Ways to Initialize The SDK supports two initialization patterns: **1. Quick Start (`createSDK`)** — for simple apps that only need one chain: ```typescript const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); ``` **2. Full Control (`VeridexSDK` + `EVMClient`)** — for multi-chain apps (recommended): ```typescript const evmClient = new EVMClient({ chainId: 84532, wormholeChainId: 10004, rpcUrl: 'https://sepolia.base.org', hubContractAddress: '0xD5D29b6EaeE6FF4b765e704298a7e48D22607059', wormholeCoreBridge: '0x79A1027a6A159502049F10906D333EC57E95F083', name: 'Base Sepolia', explorerUrl: 'https://sepolia.basescan.org', vaultFactory: '0xCFaEb5652aa2Ee60b2229dC8895B4159749C7e53', vaultImplementation: '0x0d13367C16c6f0B24eD275CC67C7D9f42878285c', }); const sdk = new VeridexSDK({ chain: evmClient, persistWallet: true, testnet: true, relayerUrl: '/api/relayer', relayerApiKey: process.env.NEXT_PUBLIC_RELAYER_API_KEY, queryApiKey: process.env.NEXT_PUBLIC_WORMHOLE_QUERY_API_KEY, sponsorPrivateKey: process.env.NEXT_PUBLIC_VERIDEX_SPONSOR_KEY, }); ``` > **Note:** The test-app uses the full control pattern with `VeridexSDK` + `EVMClient` because it manages vaults across 6+ chains (Base, Optimism, Arbitrum, Solana, Sui, Aptos, Starknet). ## React Context (Production Pattern) This is the real pattern used in the test-app. It manages SDK initialization, passkey authentication, multi-chain vault addresses, balances, and transfers. ### Configuration File ```typescript // lib/config.ts const RELAYER_DIRECT_URL = process.env.NEXT_PUBLIC_RELAYER_URL || 'https://relay.veridex.network'; const RELAYER_PROXY_URL = '/api/relayer'; const relayerUrl = process.env.NODE_ENV === 'production' ? RELAYER_PROXY_URL : RELAYER_DIRECT_URL; export const config = { chainId: 84532, wormholeChainId: 10004, rpcUrl: process.env.NEXT_PUBLIC_BASE_SEPOLIA_RPC_URL || 'https://sepolia.base.org', hubContract: '0x66D87dE68327f48A099c5B9bE97020Feab9a7c82', wormholeCoreBridge: '0x79A1027a6A159502049F10906D333EC57E95F083', chainName: 'Base Sepolia', explorerUrl: 'https://sepolia.basescan.org', vaultFactory: '0xb25b73D5FeD5693dcd1Bb78f8e33387B59A022EC', vaultImplementation: '0xD65E996CD6d5B01689dc54ad30B51f1D88a100f5', relayerUrl, }; export const spokeConfigs = { optimismSepolia: { chainId: 11155420, wormholeChainId: 10005, rpcUrl: 'https://sepolia.optimism.io', vaultFactory: '0x3c5e4aCdC8Cd53ae5ae603B4c511885191fBb868', vaultImplementation: '0x26C4FD8fC66150ef5964562F7A69271fB0cd02A4', }, arbitrumSepolia: { chainId: 421614, wormholeChainId: 10003, rpcUrl: 'https://sepolia-rollup.arbitrum.io/rpc', vaultFactory: '0xB9C3e6bad3c6f26956be4a4bb5a366376Fd3045D', vaultImplementation: '0x9f74Dc14A98E59df7AEC5571a2B9E329153dF5Cd', }, }; ``` ### Context Provider ```tsx // lib/VeridexContext.tsx 'use client'; VeridexSDK, PasskeyCredential, UnifiedIdentity, PortfolioBalance, PreparedTransfer, TransferResult, TokenInfo, TransferParams, SpendingLimits, FormattedSpendingLimits, LimitCheckResult, } from '@veridex/sdk'; interface VeridexContextType { sdk: VeridexSDK | null; credential: PasskeyCredential | null; identity: UnifiedIdentity | null; isRegistered: boolean; vaultAddress: string | null; vaultDeployed: boolean; isLoading: boolean; // Authentication register: (username: string, displayName: string) => Promise; login: () => Promise; logout: () => void; hasStoredCredential: () => boolean; // Balances vaultBalances: PortfolioBalance | null; isLoadingBalances: boolean; refreshBalances: () => Promise; getTokenList: () => TokenInfo[]; // Transfers prepareTransfer: (params: TransferParams) => Promise; executeTransfer: (prepared: PreparedTransfer) => Promise; transferGasless: (params: TransferParams) => Promise; // Spending Limits spendingLimits: SpendingLimits | null; formattedSpendingLimits: FormattedSpendingLimits | null; refreshSpendingLimits: () => Promise; checkTransactionLimit: (amount: bigint) => Promise; } const VeridexContext = createContext(undefined); export function VeridexProvider({ children }: { children: ReactNode }) { const [sdk, setSdk] = useState(null); const [credential, setCredential] = useState(null); const [identity, setIdentity] = useState(null); const [vaultAddress, setVaultAddress] = useState(null); const [vaultDeployed, setVaultDeployed] = useState(false); const [isLoading, setIsLoading] = useState(true); const [vaultBalances, setVaultBalances] = useState(null); const [isLoadingBalances, setIsLoadingBalances] = useState(false); const [spendingLimits, setSpendingLimits] = useState(null); const [formattedSpendingLimits, setFormattedSpendingLimits] = useState(null); // Initialize SDK on mount useEffect(() => { const initSdk = async () => { setIsLoading(true); try { // Create the EVM chain client (hub chain) const evmClient = new EVMClient({ chainId: config.chainId, wormholeChainId: config.wormholeChainId, rpcUrl: config.rpcUrl, hubContractAddress: config.hubContract, wormholeCoreBridge: config.wormholeCoreBridge, name: config.chainName, explorerUrl: config.explorerUrl, vaultFactory: config.vaultFactory, vaultImplementation: config.vaultImplementation, }); // Create the SDK instance const veridexSdk = new VeridexSDK({ chain: evmClient, persistWallet: true, testnet: true, relayerUrl: config.relayerUrl, relayerApiKey: process.env.NEXT_PUBLIC_RELAYER_API_KEY, queryApiKey: process.env.NEXT_PUBLIC_WORMHOLE_QUERY_API_KEY, sponsorPrivateKey: process.env.NEXT_PUBLIC_VERIDEX_SPONSOR_KEY, chainRpcUrls: { 10004: config.rpcUrl, 10005: spokeConfigs.optimismSepolia.rpcUrl, 10003: spokeConfigs.arbitrumSepolia.rpcUrl, }, }); setSdk(veridexSdk); // Try to restore saved credential (auto-login) const savedCred = veridexSdk.passkey.loadFromLocalStorage(); if (savedCred) { veridexSdk.setCredential(savedCred); setCredential(savedCred); await loadIdentity(veridexSdk); // Auto-create vaults on all chains (sponsored, gasless) if (veridexSdk.isSponsorshipAvailable()) { await veridexSdk.ensureSponsoredVaultsOnAllChains(); } } } catch (error) { console.error('SDK initialization error:', error); } finally { setIsLoading(false); } }; initSdk(); }, []); const loadIdentity = async (sdkInstance: VeridexSDK) => { try { const unifiedIdentity = await sdkInstance.getUnifiedIdentity(); setIdentity(unifiedIdentity); const chainAddr = unifiedIdentity.addresses.find( a => a.wormholeChainId === config.wormholeChainId ); if (chainAddr) { setVaultAddress(chainAddr.address); setVaultDeployed(chainAddr.deployed); } else { const addr = sdkInstance.getVaultAddress(); setVaultAddress(addr); setVaultDeployed(await sdkInstance.vaultExists()); } } catch (error) { console.warn('Could not load identity:', error); } }; // --- Authentication --- const register = async (username: string, displayName: string) => { if (!sdk) throw new Error('SDK not initialized'); setIsLoading(true); try { const cred = await sdk.passkey.register(username, displayName); sdk.setCredential(cred); setCredential(cred); sdk.passkey.saveToLocalStorage(); // Save to relayer for cross-device recovery sdk.passkey.saveCredentialToRelayer().catch(console.warn); await loadIdentity(sdk); // Auto-create vaults (gasless) if (sdk.isSponsorshipAvailable()) { await sdk.ensureSponsoredVaultsOnAllChains(); await loadIdentity(sdk); } } finally { setIsLoading(false); } }; const login = async () => { if (!sdk) throw new Error('SDK not initialized'); setIsLoading(true); try { const { credential: cred } = await sdk.passkey.authenticate(); sdk.setCredential(cred); setCredential(cred); sdk.passkey.saveToLocalStorage(); await loadIdentity(sdk); } finally { setIsLoading(false); } }; const logout = () => { if (sdk) { sdk.clearCredential(); sdk.wallet.clearCache(); } setCredential(null); setIdentity(null); setVaultAddress(null); setVaultDeployed(false); setVaultBalances(null); }; const hasStoredCredential = (): boolean => { if (!sdk) return false; return sdk.passkey.hasStoredCredential(); }; // --- Balances --- const refreshBalances = async () => { if (!sdk || !vaultAddress) return; setIsLoadingBalances(true); try { const balances = await sdk.getVaultBalances(true); setVaultBalances(balances); } catch (error) { console.error('Error fetching balances:', error); } finally { setIsLoadingBalances(false); } }; const getTokenList = (): TokenInfo[] => { if (!sdk) return []; return sdk.getTokenList(); }; // --- Transfers --- const prepareTransfer = async (params: TransferParams) => { if (!sdk) throw new Error('SDK not initialized'); return await sdk.prepareTransfer(params); }; const executeTransfer = async (prepared: PreparedTransfer) => { if (!sdk) throw new Error('SDK not initialized'); const signer = new ethers.BrowserProvider(window.ethereum!).getSigner(); return await sdk.executeTransfer(prepared, await signer); }; const transferGasless = async (params: TransferParams) => { if (!sdk) throw new Error('SDK not initialized'); return await sdk.transferViaRelayer(params); }; // --- Spending Limits --- const refreshSpendingLimits = useCallback(async () => { if (!sdk || !vaultAddress) return; try { const limits = await sdk.spendingLimits.getSpendingLimits( vaultAddress, config.wormholeChainId ); setSpendingLimits(limits); const formatted = await sdk.spendingLimits.getFormattedSpendingLimits( vaultAddress, config.wormholeChainId ); setFormattedSpendingLimits(formatted); } catch (error) { console.error('Error fetching spending limits:', error); } }, [sdk, vaultAddress]); const checkTransactionLimit = useCallback(async (amount: bigint) => { if (!sdk || !vaultAddress) return null; return await sdk.spendingLimits.checkTransactionLimit( vaultAddress, config.wormholeChainId, amount ); }, [sdk, vaultAddress]); return ( {children} ); } export function useVeridex() { const context = useContext(VeridexContext); if (!context) throw new Error('useVeridex must be used within VeridexProvider'); return context; } ``` ### Use in App ```tsx // app/layout.tsx (Next.js App Router) export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` ## Components ### Connect / Register Button ```tsx // components/ConnectButton.tsx 'use client'; export function ConnectButton() { const { isRegistered, vaultAddress, isLoading, register, login, logout, hasStoredCredential, } = useVeridex(); const [username, setUsername] = useState(''); const [error, setError] = useState(''); if (isLoading) { return Loading...; } if (isRegistered) { return ( {vaultAddress?.slice(0, 6)}...{vaultAddress?.slice(-4)} Disconnect ); } return ( {/* Sign in with existing passkey */} { try { await login(); } catch (err: any) { setError(err.message); } }} className="w-full bg-purple-600 text-white py-3 rounded-xl" > Sign In with Passkey {/* Register new passkey */} { e.preventDefault(); try { await register(username, username); } catch (err: any) { setError(err.message); } }}> setUsername(e.target.value)} placeholder="Choose a username" className="w-full p-3 border rounded-xl mb-2" required /> Create Wallet {error && {error}} ); } ``` ### Balance Display ```tsx // components/BalanceDisplay.tsx 'use client'; export function BalanceDisplay() { const { isRegistered, vaultBalances, isLoadingBalances, refreshBalances, } = useVeridex(); useEffect(() => { if (isRegistered) refreshBalances(); }, [isRegistered]); if (!isRegistered) return null; if (isLoadingBalances) { return Loading balances...; } return ( Balances Refresh {vaultBalances?.tokens.map((entry) => ( {entry.token.symbol} {entry.formatted} ))} {vaultBalances?.totalUsdValue && ( Total: ${vaultBalances.totalUsdValue.toFixed(2)} )} ); } ``` ### Gasless Transfer Form The test-app uses gasless transfers via the relayer — the user only needs their passkey, no MetaMask or gas tokens required. ```tsx // components/SendForm.tsx 'use client'; const USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e'; export function SendForm() { const { isRegistered, transferGasless, vaultAddress } = useVeridex(); const [recipient, setRecipient] = useState(''); const [amount, setAmount] = useState(''); const [loading, setLoading] = useState(false); const [result, setResult] = useState(null); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setResult(null); try { const tx = await transferGasless({ targetChain: 10004, // Base Sepolia token: USDC_ADDRESS, recipient, amount: ethers.parseUnits(amount, 6), // USDC has 6 decimals }); setResult(`Sent! Tx: ${tx.transactionHash}`); setRecipient(''); setAmount(''); } catch (error) { setResult(error instanceof Error ? error.message : 'Transfer failed'); } finally { setLoading(false); } }; if (!isRegistered) { return Connect your wallet to send tokens; } return ( setRecipient(e.target.value)} placeholder="Recipient address (0x...)" className="w-full p-3 border rounded-lg" required /> setAmount(e.target.value)} placeholder="Amount (USDC)" step="0.01" className="w-full p-3 border rounded-lg" required /> {loading ? 'Sending...' : 'Send USDC (Gasless)'} {result && {result}} ); } ``` ## Multi-Chain Support The test-app initializes chain clients for Solana, Sui, Aptos, and Starknet alongside the EVM hub: ```typescript // Inside your SDK initialization: const solClient = new SolanaClient({ wormholeChainId: 1, rpcUrl: 'https://api.devnet.solana.com', programId: 'J7JehynQjN4XrucGQ5joMfhQWiViDmmLhQLriGUcWAM2', wormholeCoreBridge: '3u8hJUVTA4jH1wYAyUur7FFZVQ8H635K3tSHHF4ssjQ5', tokenBridge: 'DZnkkTmCiFWfYTfT41X3Rd1kDgozqzxWaHqsw6W4x2oe', network: 'devnet', commitment: 'confirmed', }); const suiClientInstance = new SuiClient({ wormholeChainId: 21, rpcUrl: 'https://fullnode.testnet.sui.io:443', packageId: '0x6ae854c698d73e39f5dc07c4d2291fa81e8732aded14bbff3b98cfa8bfaebff5', wormholeCoreBridge: '0x31358d198147da50db32eda2562951d53973a0c0ad5ed738e9b17d88b213d790', network: 'testnet', }); ``` Each chain client provides: - `computeVaultAddress(keyHash)` — deterministic vault address - `getVaultViaRelayer(keyHash, relayerUrl)` — check if vault exists - `createVaultViaRelayer(keyHash, relayerUrl)` — create vault (gasless) ## Sponsored Vault Creation The SDK can auto-create vaults on all chains without gas: ```typescript // After registration or login if (sdk.isSponsorshipAvailable()) { const result = await sdk.ensureSponsoredVaultsOnAllChains(); console.log(`Created vaults on ${result.results.filter(r => r.success).length} chains`); } ``` ## Environment Variables ```bash # .env.local NEXT_PUBLIC_NETWORK=testnet NEXT_PUBLIC_RELAYER_URL=https://relay.veridex.network NEXT_PUBLIC_RELAYER_API_KEY=your-api-key NEXT_PUBLIC_WORMHOLE_QUERY_API_KEY=your-query-key NEXT_PUBLIC_VERIDEX_SPONSOR_KEY=your-sponsor-key NEXT_PUBLIC_INTEGRATOR_SPONSOR_KEY=your-integrator-key ``` ## Complete Example Clone the test-app for a complete working example with 6-chain support: ```bash git clone https://github.com/Veridex-Protocol/demo cd demo/test-app cp .env.local.example .env.local npm install npm run dev ``` Open [http://localhost:3000](http://localhost:3000). ## Next Steps SSR-safe patterns with dynamic imports How relayer-sponsored transfers work Bridge tokens across chains with Wormhole Configure on-chain spending safety ================================================================================ URL: /integrations/sera ================================================================================ # Sera Protocol Integration Build payment solutions with Veridex authentication and Sera DEX settlement. > **Info:** Sera Protocol is a decentralized exchange with an orderbook model, enabling atomic on-chain stablecoin settlement. Combined with Veridex passkey authentication, it provides a complete payment gateway solution. ## Overview ```mermaid graph LR A[Customer] -->|Passkey Auth| B[Veridex Wallet] B -->|Payment| C[Merchant] C -->|Settlement| D[Sera DEX] D -->|Stablecoins| E[Merchant Bank] ``` ## Prerequisites Install and configure the Veridex SDK Access to Sepolia testnet RPC ## Quick Start ### 1. Install Dependencies ```bash bun bun add @veridex/sdk ethers axios ``` ```bash npm npm install @veridex/sdk ethers axios ``` ### 2. Configure Environment ```env .env # Ethereum Sepolia RPC NEXT_PUBLIC_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY # Sera GraphQL API NEXT_PUBLIC_GRAPHQL_URL=https://api.goldsky.com/api/public/project_cmicv6kkbhyto01u3agb155hg/subgraphs/sera-pro/1.0.9/gn # Settlement wallet (keep secure!) SETTLEMENT_WALLET_KEY=your-private-key ``` ### 3. Initialize Services This uses **dynamic imports** to avoid SSR issues in Next.js: ```typescript // Veridex SDK, lazy-loaded singleton to avoid SSR issues let sdkInstance: any = null; export async function getVeridexSDK() { if (!sdkInstance) { const { createSDK } = await import('@veridex/sdk'); sdkInstance = createSDK('base', { network: 'testnet', relayerUrl: process.env.NEXT_PUBLIC_VERIDEX_RELAYER_URL, relayerApiKey: process.env.NEXT_PUBLIC_RELAYER_API_KEY, }); } return sdkInstance; } // Sera for settlement const sera = new SeraService(process.env.NEXT_PUBLIC_RPC_URL!); ``` > **Note:** The `await import('@veridex/sdk')` pattern ensures the SDK is only loaded in the browser, never during server-side rendering. See the [Next.js Integration](/integrations/nextjs) guide for the full pattern. ## Contract Addresses | Contract | Address | |----------|---------| | Sera Router | `0x82bfe1b31b6c1c3d201a0256416a18d93331d99e` | | Market Factory | `0xe54648526027e236604f0d91413a6aad3a80c01e` | | Order Canceler | `0x53ad1ffcd7afb1b14c5f18be8f256606efb11b1b` | | Veridex VaultFactory | `0x265c10763B4d16AD970bC3d7670c645e37f63AF4` | | Veridex VaultImpl | `0xfab72dd1fd3AD79f738B49506f32251B60c95f01` | > **Warning:** Mainnet deployment coming soon ## Payment Flow Customer connects using Veridex passkey or WalletConnect ```typescript // Option 1: Passkey (recommended) await veridex.passkey.authenticate(); // Option 2: WalletConnect const { address } = await connectWalletConnect(); ``` Create a payment intent and get signing challenge ```typescript const { transferId, challenge } = await veridex.prepareTransfer({ recipient: merchantAddress, amount: BigInt('99990000'), // 99.99 USDC (6 decimals) token: USDC_ADDRESS, }); ``` Biometric prompt for payment authorization ```typescript const signature = await veridex.passkey.sign(challenge); ``` Submit signed transaction ```typescript const { txHash } = await veridex.executeTransfer(prepared, signature); ``` Convert received payment to merchant's preferred currency ```typescript // Place limit order on Sera DEX await sera.placeLimitBid(signer, { market: usdcEurcMarket, priceIndex: currentPrice, rawAmount: amount, }); // Poll for fill const orders = await sera.getOrderStatus(address, market); // Claim when filled await sera.claimProceeds(signer, orders); ``` ## Sera Service Implementation ```typescript sera-service.ts const SERA_CONFIG = { chainId: 11155111, routerAddress: '0x82bfe1b31b6c1c3d201a0256416a18d93331d99e', graphqlUrl: 'https://api.goldsky.com/api/public/project_cmicv6kkbhyto01u3agb155hg/subgraphs/sera-pro/1.0.9/gn', }; const ROUTER_ABI = [ 'function limitBid((address market, uint64 deadline, uint32 claimBounty, address user, uint16 priceIndex, uint64 rawAmount, bool postOnly, bool useNative, uint256 baseAmount)) returns (uint256)', 'function claim(uint64 deadline, (address market, (bool isBid, uint16 priceIndex, uint256 orderIndex)[] orderKeys)[])', ]; export class SeraService { private router: ethers.Contract; constructor(rpcUrl: string) { const provider = new ethers.JsonRpcProvider(rpcUrl); this.router = new ethers.Contract( SERA_CONFIG.routerAddress, ROUTER_ABI, provider ); } async getMarkets() { const response = await axios.post(SERA_CONFIG.graphqlUrl, { query: ` query { markets(first: 100) { id quoteToken { symbol decimals } baseToken { symbol decimals } minPrice tickSpace quoteUnit latestPrice latestPriceIndex } } `, }); return response.data.data.markets; } async placeLimitBid(signer: ethers.Signer, params: { market: string; priceIndex: number; rawAmount: bigint; }) { const routerWithSigner = this.router.connect(signer); return routerWithSigner.limitBid({ market: params.market, deadline: Math.floor(Date.now() / 1000) + 3600, claimBounty: 0, user: await signer.getAddress(), priceIndex: params.priceIndex, rawAmount: params.rawAmount, postOnly: false, useNative: false, baseAmount: 0n, }); } async getOrderStatus(user: string, market: string) { const response = await axios.post(SERA_CONFIG.graphqlUrl, { query: ` query($user: String!, $market: String!) { openOrders(where: { user: $user, market: $market }) { id nftId priceIndex rawAmount status claimableAmount } } `, variables: { user: user.toLowerCase(), market: market.toLowerCase() }, }); return response.data.data.openOrders; } async claimProceeds(signer: ethers.Signer, paramsList: any[]) { const routerWithSigner = this.router.connect(signer); return routerWithSigner.claim( Math.floor(Date.now() / 1000) + 3600, paramsList ); } } ``` ## Price Conversion Sera uses indexed pricing: ```typescript // Price formula price = minPrice + (tickSpace × priceIndex) // Example: Convert $1.05 to price index const priceIndex = (price - minPrice) / tickSpace; // Amount conversion quoteAmount = rawAmount × quoteUnit rawAmount = quoteAmount / quoteUnit ``` > **Tip:** Always fetch current market parameters from GraphQL before placing orders to ensure accurate price indices. ## Security Best Practices Always approve exact amounts, never unlimited: ```typescript await token.approve(ROUTER_ADDRESS, exactAmount); ``` Use reasonable deadlines to prevent stale orders: ```typescript const deadline = Math.floor(Date.now() / 1000) + 3600; // 1 hour ``` Never store private keys in code. Use: - Environment variables (development) - Hardware security modules (production) - Cloud secret managers (AWS Secrets, GCP Secret Manager) ## Error Handling ```typescript try { await sera.placeLimitBid(signer, params); } catch (error) { if (error.message.includes('insufficient allowance')) { // Approve tokens first await token.approve(ROUTER_ADDRESS, amount); await sera.placeLimitBid(signer, params); } else if (error.message.includes('deadline')) { // Retry with fresh deadline params.deadline = Math.floor(Date.now() / 1000) + 3600; } else { throw error; } } ``` ## Example: Complete Payment Gateway Full-featured payment dashboard with invoice management, payment links, and settlement tracking. ```typescript // Complete payment flow async function processPayment(invoiceId: string) { const invoice = await db.invoice.findUnique({ where: { id: invoiceId } }); // 1. Customer pays via Veridex const { txHash } = await executePayment(invoice.amount); // 2. Update invoice await db.invoice.update({ where: { id: invoiceId }, data: { status: 'paid', paymentTxHash: txHash }, }); // 3. Settle via Sera (if currency conversion needed) if (invoice.settlementCurrency !== invoice.paymentCurrency) { const settlement = await settleViaSera( invoice.amount, invoice.paymentCurrency, invoice.settlementCurrency ); await db.invoice.update({ where: { id: invoiceId }, data: { status: 'settled', settlementTxHash: settlement.txHash, }, }); } // 4. Send webhook await sendWebhook(invoice.merchantId, 'invoice.settled', invoice); } ``` ## Related Guides Build a complete payment gateway React hooks and components Full-stack Next.js integration ================================================================================ URL: /integrations/defi ================================================================================ # DeFi Integration Integrate Veridex passkey wallets with DeFi protocols for gasless, user-friendly DeFi. > **🏦 Try it yourself:** Clone and run the full DeFi vault example: > ```bash > git clone https://github.com/Veridex-Protocol/examples.git > cd examples && npm install && npm run integration:defi > ``` > [View source on GitHub](https://github.com/Veridex-Protocol/examples/tree/main/integrations/defi-vault) · [Browse all examples](/guides/examples) ## Overview Enable your users to: - **Swap tokens** without gas - **Provide liquidity** with one click - **Stake assets** using FaceID/TouchID - **Manage DeFi positions** seamlessly ## Token Swaps ### Uniswap V3 Integration ```typescript const sdk = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); const UNISWAP_ROUTER = '0x2626664c2603336E57B271c5C0b26F421741e481'; // Base interface SwapParams { tokenIn: string; tokenOut: string; amountIn: bigint; slippagePercent: number; } async function swapTokens({ tokenIn, tokenOut, amountIn, slippagePercent }: SwapParams) { // 1. Approve router await sdk.approveViaRelayer({ token: tokenIn, spender: UNISWAP_ROUTER, amount: amountIn, }); // 2. Get quote and calculate minimum out const quote = await getQuote(tokenIn, tokenOut, amountIn); const minAmountOut = quote * BigInt(100 - slippagePercent) / 100n; // 3. Execute swap const routerInterface = new ethers.Interface([ 'function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96)) returns (uint256 amountOut)' ]); const tx = await sdk.executeViaRelayer({ target: UNISWAP_ROUTER, data: routerInterface.encodeFunctionData('exactInputSingle', [{ tokenIn, tokenOut, fee: 3000, // 0.3% recipient: sdk.getVaultAddress(), amountIn, amountOutMinimum: minAmountOut, sqrtPriceLimitX96: 0, }]), }); return tx; } ``` ### Swap Component ```tsx const TOKENS = { ETH: { address: '0x4200000000000000000000000000000000000006', decimals: 18 }, USDC: { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', decimals: 6 }, DAI: { address: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', decimals: 18 }, }; export function SwapWidget() { const [tokenIn, setTokenIn] = useState('USDC'); const [tokenOut, setTokenOut] = useState('ETH'); const [amountIn, setAmountIn] = useState(''); const [quote, setQuote] = useState(null); const [loading, setLoading] = useState(false); const handleQuote = async () => { if (!amountIn) return; const amount = parseUnits(amountIn, TOKENS[tokenIn].decimals); const quoteAmount = await getQuote( TOKENS[tokenIn].address, TOKENS[tokenOut].address, amount ); setQuote(formatUnits(quoteAmount, TOKENS[tokenOut].decimals)); }; const handleSwap = async () => { setLoading(true); try { await swapTokens({ tokenIn: TOKENS[tokenIn].address, tokenOut: TOKENS[tokenOut].address, amountIn: parseUnits(amountIn, TOKENS[tokenIn].decimals), slippagePercent: 1, }); alert('Swap successful!'); } catch (error) { console.error(error); } setLoading(false); }; return ( Swap Tokens {/* From */} From setAmountIn(e.target.value)} onBlur={handleQuote} className="flex-1 p-2 border rounded" placeholder="0.0" /> setTokenIn(e.target.value)} className="p-2 border rounded" > {Object.keys(TOKENS).map(t => {t})} {/* To */} To (estimated) setTokenOut(e.target.value)} className="p-2 border rounded" > {Object.keys(TOKENS).map(t => {t})} {loading ? 'Swapping...' : 'Swap'} ); } ``` ## Liquidity Provision ### Add Liquidity ```typescript const POSITION_MANAGER = '0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1'; // Base async function addLiquidity( token0: string, token1: string, amount0: bigint, amount1: bigint, tickLower: number, tickUpper: number ) { // 1. Approve tokens await sdk.approveViaRelayer({ token: token0, spender: POSITION_MANAGER, amount: amount0 }); await sdk.approveViaRelayer({ token: token1, spender: POSITION_MANAGER, amount: amount1 }); // 2. Mint position const nftManagerInterface = new ethers.Interface([ 'function mint((address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint256 amount0Desired, uint256 amount1Desired, uint256 amount0Min, uint256 amount1Min, address recipient, uint256 deadline)) returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1)' ]); return await sdk.executeViaRelayer({ target: POSITION_MANAGER, data: nftManagerInterface.encodeFunctionData('mint', [{ token0, token1, fee: 3000, tickLower, tickUpper, amount0Desired: amount0, amount1Desired: amount1, amount0Min: 0, amount1Min: 0, recipient: sdk.getVaultAddress(), deadline: Math.floor(Date.now() / 1000) + 3600, }]), }); } ``` ## Staking ### Stake Component ```tsx const STAKING_CONTRACT = '0xYourStakingContract'; export function StakingWidget({ token }: { token: string }) { const [amount, setAmount] = useState(''); const [staked, setStaked] = useState(0n); const [rewards, setRewards] = useState(0n); const [loading, setLoading] = useState(false); const handleStake = async () => { setLoading(true); try { const amountWei = parseUnits(amount, 18); // Approve await sdk.approveViaRelayer({ token, spender: STAKING_CONTRACT, amount: amountWei, }); // Stake await sdk.executeViaRelayer({ target: STAKING_CONTRACT, data: stakingIface.encodeFunctionData('stake', [amountWei]), }); setAmount(''); await loadStakeInfo(); } catch (error) { console.error(error); } setLoading(false); }; const handleUnstake = async () => { setLoading(true); try { await sdk.executeViaRelayer({ target: STAKING_CONTRACT, data: stakingIface.encodeFunctionData('withdraw', [staked]), }); await loadStakeInfo(); } catch (error) { console.error(error); } setLoading(false); }; const handleClaim = async () => { setLoading(true); try { await sdk.executeViaRelayer({ target: STAKING_CONTRACT, data: stakingIface.encodeFunctionData('getReward', []), }); await loadStakeInfo(); } catch (error) { console.error(error); } setLoading(false); }; return ( Staking Staked {formatUnits(staked, 18)} Rewards {formatUnits(rewards, 18)} setAmount(e.target.value)} placeholder="Amount to stake" className="w-full p-2 border rounded" /> {loading ? 'Staking...' : 'Stake'} Unstake All Claim Rewards ); } ``` ## Lending Protocols ### Aave Integration ```typescript const AAVE_POOL = '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5'; // Base // Supply tokens async function supply(token: string, amount: bigint) { // Approve await sdk.approveViaRelayer({ token, spender: AAVE_POOL, amount }); // Supply const poolInterface = new ethers.Interface([ 'function supply(address asset, uint256 amount, address onBehalfOf, uint16 referralCode)' ]); return await sdk.executeViaRelayer({ target: AAVE_POOL, data: poolInterface.encodeFunctionData('supply', [ token, amount, sdk.getVaultAddress(), 0, ]), }); } // Borrow tokens async function borrow(token: string, amount: bigint, interestRateMode: 1 | 2) { const poolInterface = new ethers.Interface([ 'function borrow(address asset, uint256 amount, uint256 interestRateMode, uint16 referralCode, address onBehalfOf)' ]); return await sdk.executeViaRelayer({ target: AAVE_POOL, data: poolInterface.encodeFunctionData('borrow', [ token, amount, interestRateMode, // 1 = stable, 2 = variable 0, sdk.getVaultAddress(), ]), }); } // Repay loan async function repay(token: string, amount: bigint, interestRateMode: 1 | 2) { await sdk.approveViaRelayer({ token, spender: AAVE_POOL, amount }); const poolInterface = new ethers.Interface([ 'function repay(address asset, uint256 amount, uint256 interestRateMode, address onBehalfOf) returns (uint256)' ]); return await sdk.executeViaRelayer({ target: AAVE_POOL, data: poolInterface.encodeFunctionData('repay', [ token, amount, interestRateMode, sdk.getVaultAddress(), ]), }); } ``` ## Session Keys for DeFi Enable automated DeFi operations: ```typescript // Create session for DeFi operations const session = await sdk.sessions.create({ duration: 3600, // 1 hour maxValue: parseUnits('1000', 6), // Max $1000 allowedTokens: [USDC, ETH], allowedContracts: [UNISWAP_ROUTER, AAVE_POOL], }); // Now operations within limits don't need biometric await swapTokens({ ... }); // Auto-approved ✓ await supply(USDC, parseUnits('100', 6)); // Auto-approved ✓ ``` ## Best Practices 1. **Slippage protection** - Always set reasonable slippage limits 2. **Deadline handling** - Use deadlines for all DeFi transactions 3. **Quote freshness** - Refresh quotes before execution 4. **Session limits** - Set conservative session spending limits 5. **Error handling** - Handle revert reasons gracefully ## Security Considerations - Verify contract addresses against official sources - Use price oracles for sanity checks - Implement circuit breakers for large transactions - Monitor for frontrunning in quotes - Validate user inputs thoroughly ================================================================================ URL: /integrations/payment-gateway ================================================================================ # Payment Gateway Integration Accept cryptocurrency payments in your application using Veridex passkeys. > **💳 Try it yourself:** Clone and run the full payment gateway example: > ```bash > git clone https://github.com/Veridex-Protocol/examples.git > cd examples && npm install && npx ts-node integrations/payment-gateway/index.ts > ``` > [View source on GitHub](https://github.com/Veridex-Protocol/examples/tree/main/integrations/payment-gateway) · [Browse all examples](/guides/examples) ## Overview Veridex enables: - **Gasless payments** - Customers don't need gas tokens - **One-click checkout** - FaceID/TouchID to confirm - **Multi-chain** - Accept payments on any supported chain - **Session-based** - Optional smooth checkout flows ## Basic Payment Flow ```typescript const sdk = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); // Customer authenticates await sdk.passkey.authenticate(); // Process payment const payment = await sdk.transferViaRelayer({ token: USDC_ADDRESS, recipient: MERCHANT_WALLET, amount: parseUnits('29.99', 6), // $29.99 }); console.log('Payment complete:', payment.txHash); ``` ## React Checkout Component ```tsx const sdk = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // Base Mainnet USDC const MERCHANT = '0xYourMerchantWallet'; interface CheckoutProps { amount: string; // e.g., "29.99" orderId: string; onSuccess: (txHash: string) => void; onError: (error: Error) => void; } export function VeridexCheckout({ amount, orderId, onSuccess, onError }: CheckoutProps) { const [loading, setLoading] = useState(false); const [step, setStep] = useState<'connect' | 'confirm' | 'processing' | 'done'>('connect'); const handleCheckout = async () => { setLoading(true); try { // Step 1: Authenticate setStep('connect'); const stored = sdk.passkey.getAllStoredCredentials(); if (stored.length > 0) { await sdk.passkey.authenticate(); } else { await sdk.passkey.register('customer@example.com', 'Checkout'); } // Step 2: Confirm payment setStep('confirm'); // Step 3: Process setStep('processing'); const result = await sdk.transferViaRelayer({ token: USDC, recipient: MERCHANT, amount: parseUnits(amount, 6), }); setStep('done'); onSuccess(result.txHash); } catch (error) { onError(error instanceof Error ? error : new Error('Payment failed')); } finally { setLoading(false); } }; return ( ${amount} Order #{orderId} {step === 'done' ? ( ✓ Payment Complete! ) : ( {loading ? ( {step === 'connect' && 'Authenticating...'} {step === 'confirm' && 'Confirming...'} {step === 'processing' && 'Processing...'} ) : ( 'Pay with Passkey' )} )} Powered by Veridex • No gas fees ); } ``` ## Session-Based Checkout For smoother repeat purchases: ```typescript // First purchase: Create session for future payments const session = await sdk.sessions.create({ duration: 1800, // 30 minutes maxValue: parseUnits('100', 6), // Max $100 allowedTokens: [USDC], allowedRecipients: [MERCHANT], }); // Subsequent purchases in session: No biometric prompt await sdk.executeTransfer({ token: USDC, recipient: MERCHANT, amount: parseUnits('9.99', 6) }); await sdk.executeTransfer({ token: USDC, recipient: MERCHANT, amount: parseUnits('14.99', 6) }); // Both auto-signed! ✓ ``` ## Server-Side Verification Verify payments on your backend: ```typescript // pages/api/verify-payment.ts (Next.js) const USDC_ADDRESS = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; const MERCHANT_ADDRESS = '0xYourMerchantWallet'; export async function POST(req: Request) { const { txHash, orderId, expectedAmount } = await req.json(); // Connect to Base const provider = new ethers.JsonRpcProvider('https://mainnet.base.org'); // Get transaction receipt const receipt = await provider.getTransactionReceipt(txHash); if (!receipt || receipt.status !== 1) { return Response.json({ valid: false, error: 'Transaction not confirmed' }); } // Parse transfer event const iface = new ethers.Interface([ 'event Transfer(address indexed from, address indexed to, uint256 value)' ]); const transferLog = receipt.logs.find(log => log.address.toLowerCase() === USDC_ADDRESS.toLowerCase() ); if (!transferLog) { return Response.json({ valid: false, error: 'No USDC transfer found' }); } const parsed = iface.parseLog(transferLog); const { to, value } = parsed.args; // Verify recipient and amount if (to.toLowerCase() !== MERCHANT_ADDRESS.toLowerCase()) { return Response.json({ valid: false, error: 'Wrong recipient' }); } if (value < ethers.parseUnits(expectedAmount, 6)) { return Response.json({ valid: false, error: 'Insufficient amount' }); } // Payment verified - update order status await updateOrderStatus(orderId, 'paid', txHash); return Response.json({ valid: true, txHash }); } ``` ## Webhook Integration Receive payment notifications: ```typescript // Configure webhook with relayer (if using managed relayer) await sdk.webhooks.register({ url: 'https://your-app.com/api/webhooks/veridex', events: ['payment.completed', 'payment.failed'], secret: process.env.WEBHOOK_SECRET, }); ``` Handle webhooks: ```typescript // pages/api/webhooks/veridex.ts export async function POST(req: Request) { const body = await req.text(); const signature = req.headers.get('x-veridex-signature'); // Verify signature const expectedSig = createHmac('sha256', process.env.WEBHOOK_SECRET!) .update(body) .digest('hex'); if (signature !== expectedSig) { return Response.json({ error: 'Invalid signature' }, { status: 401 }); } const event = JSON.parse(body); switch (event.type) { case 'payment.completed': await handlePaymentComplete(event.data); break; case 'payment.failed': await handlePaymentFailed(event.data); break; } return Response.json({ received: true }); } ``` ## Multi-Currency Support Accept multiple stablecoins: ```typescript const TOKENS = { USDC: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', USDT: '0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2', DAI: '0x50c5725949A6F0c72E6C4a641F24049A917DB0Cb', }; function CurrencySelector({ onSelect }: { onSelect: (token: string) => void }) { return ( {Object.entries(TOKENS).map(([symbol, address]) => ( onSelect(address)} className="px-4 py-2 border rounded-lg" > {symbol} ))} ); } ``` ## Invoice Links Generate payment links: ```typescript // Generate payment URL function generatePaymentLink(orderId: string, amount: string, currency: string) { const params = new URLSearchParams({ orderId, amount, currency, merchant: MERCHANT_ADDRESS, }); return `https://your-app.com/pay?${params}`; } // Payment page reads params and initiates checkout // /pay?orderId=123&amount=29.99¤cy=USDC&merchant=0x... ``` ## Best Practices 1. **Verify on server** - Always verify payments server-side 2. **Use webhooks** - Don't rely solely on client-side callbacks 3. **Set timeouts** - Expire payment sessions after reasonable time 4. **Show progress** - Clear UI for each checkout step 5. **Handle errors** - Graceful fallbacks for failed payments ## Security Considerations - Never trust client-side payment confirmations alone - Verify transaction on-chain before fulfilling orders - Use unique order IDs to prevent replay attacks - Implement rate limiting on payment endpoints ================================================================================ URL: /integrations/gaming ================================================================================ # Gaming Integration Build blockchain games with seamless passkey authentication and gasless transactions. > **🎮 Try it yourself:** Clone and run the full gaming integration example: > ```bash > git clone https://github.com/Veridex-Protocol/examples.git > cd examples && npm install && npm run integration:gaming > ``` > [View source on GitHub](https://github.com/Veridex-Protocol/examples/tree/main/integrations/gaming) · [Browse all examples](/guides/examples) ## Overview Veridex enables game developers to: - **No wallet popups** - Players authenticate with device biometrics - **Invisible transactions** - Gasless, auto-signed game actions - **Session keys** - Smooth gameplay without constant prompts - **Cross-chain assets** - Use items across games on different chains ## Quick Start ```typescript const sdk = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); // Player login - one-time biometric await sdk.passkey.authenticate(); // Create game session (1 hour, no prompts during gameplay) const session = await sdk.sessions.create({ duration: 3600, maxValue: parseUnits('10', 6), // $10 max spend }); // Now all game actions are frictionless! await claimDailyReward(); await mintItem(itemId); await transferGold(playerId, 100); ``` ## Session Keys for Gaming The key to smooth gaming is session keys: ```typescript // Create optimized game session async function startGameSession() { return await sdk.sessions.create({ duration: 7200, // 2 hour play session maxValue: parseUnits('50', 6), // Max $50 in transactions allowedTokens: [GAME_TOKEN, GOLD_TOKEN], allowedContracts: [GAME_CONTRACT, MARKETPLACE], }); } // Check if session is still valid function canAutoSign(): boolean { const session = sdk.sessions.getActive(); return session && session.expiresAt > Date.now(); } // Game actions during session need no prompts async function gameAction() { if (canAutoSign()) { await sdk.execute(...); // Instant! } else { // Session expired, prompt for new one await startGameSession(); } } ``` ## In-Game Wallet UI ```tsx export function GameWallet() { const [gold, setGold] = useState('0'); const [gems, setGems] = useState('0'); const [session, setSession] = useState(null); useEffect(() => { loadBalances(); setSession(sdk.sessions.getActive()); }, []); const loadBalances = async () => { const balances = await sdk.getBalances(); setGold(balances.base?.GOLD || '0'); setGems(balances.base?.GEMS || '0'); }; return ( {/* Balances */} 🪙 {gold} 💎 {gems} {/* Session Status */} {session && ( Session active )} ); } ``` ## Game Actions ### Claim Rewards ```typescript async function claimDailyReward() { const tx = await sdk.executeViaRelayer({ target: GAME_CONTRACT, data: gameContract.interface.encodeFunctionData('claimDailyReward', []), }); // Refresh UI await loadBalances(); showToast('Daily reward claimed!'); } ``` ### Mint Items ```typescript async function mintItem(itemId: number) { const tx = await sdk.executeViaRelayer({ target: GAME_CONTRACT, data: gameContract.interface.encodeFunctionData('mintItem', [ sdk.getVaultAddress(), itemId, ]), }); return tx; } ``` ### In-Game Purchases ```typescript async function purchaseItem(itemId: number, price: bigint) { // Approve spend (if using ERC-20) await sdk.approveViaRelayer({ token: GOLD_TOKEN, spender: MARKETPLACE, amount: price, }); // Purchase await sdk.executeViaRelayer({ target: MARKETPLACE, data: marketplaceIface.encodeFunctionData('purchase', [itemId]), }); showToast('Item purchased!'); } ``` ### Player-to-Player Trade ```typescript async function tradeItem( itemId: bigint, partnerAddress: string, goldAmount: bigint ) { // Create escrow trade await sdk.executeViaRelayer({ target: TRADE_CONTRACT, data: tradeIface.encodeFunctionData('createTrade', [ ITEM_CONTRACT, itemId, GOLD_TOKEN, goldAmount, partnerAddress, Math.floor(Date.now() / 1000) + 3600, // 1 hour expiry ]), }); } ``` ## Inventory System ```tsx interface GameItem { tokenId: bigint; itemType: number; rarity: 'common' | 'rare' | 'epic' | 'legendary'; stats: { attack?: number; defense?: number; speed?: number; }; } export function Inventory() { const [items, setItems] = useState([]); const [selectedItem, setSelectedItem] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { loadInventory(); }, []); const loadInventory = async () => { setLoading(true); const provider = new ethers.JsonRpcProvider('https://mainnet.base.org'); const itemContract = new ethers.Contract(ITEM_CONTRACT, itemAbi, provider); const balance = await itemContract.balanceOf(sdk.getVaultAddress()); const loadedItems: GameItem[] = []; for (let i = 0; i < balance; i++) { const tokenId = await itemContract.tokenOfOwnerByIndex(sdk.getVaultAddress(), i); const metadata = await itemContract.getItemMetadata(tokenId); loadedItems.push({ tokenId, itemType: metadata.itemType, rarity: ['common', 'rare', 'epic', 'legendary'][metadata.rarity], stats: { attack: metadata.attack?.toNumber(), defense: metadata.defense?.toNumber(), speed: metadata.speed?.toNumber(), }, }); } setItems(loadedItems); setLoading(false); }; const equipItem = async (item: GameItem) => { await sdk.executeViaRelayer({ target: GAME_CONTRACT, data: gameIface.encodeFunctionData('equip', [item.tokenId]), }); showToast('Item equipped!'); }; const sellItem = async (item: GameItem, price: bigint) => { // Approve marketplace await sdk.executeViaRelayer({ target: ITEM_CONTRACT, data: itemIface.encodeFunctionData('approve', [MARKETPLACE, item.tokenId]), }); // List for sale await sdk.executeViaRelayer({ target: MARKETPLACE, data: marketplaceIface.encodeFunctionData('list', [ ITEM_CONTRACT, item.tokenId, GOLD_TOKEN, price, ]), }); showToast('Item listed for sale!'); }; return ( {items.map((item) => ( setSelectedItem(item)} className={` p-2 border rounded cursor-pointer ${getRarityColor(item.rarity)} `} > {getItemName(item.itemType)} ))} ); } ``` ## Leaderboards & Achievements ```typescript // Submit score (signed by passkey for authenticity) async function submitScore(score: number) { const message = ethers.solidityPackedKeccak256( ['address', 'uint256', 'uint256'], [sdk.getVaultAddress(), score, Date.now()] ); // Sign with passkey (proves authenticity) const signature = await sdk.signMessage(message); // Submit to game server await fetch('/api/leaderboard', { method: 'POST', body: JSON.stringify({ player: sdk.getVaultAddress(), score, timestamp: Date.now(), signature, }), }); } // Claim achievement NFT async function claimAchievement(achievementId: number) { await sdk.executeViaRelayer({ target: ACHIEVEMENT_CONTRACT, data: achievementIface.encodeFunctionData('claim', [ sdk.getVaultAddress(), achievementId, ]), }); } ``` ## Multiplayer Considerations ```typescript // Join match with entry fee async function joinMatch(matchId: string, entryFee: bigint) { await sdk.approveViaRelayer({ token: GOLD_TOKEN, spender: MATCH_CONTRACT, amount: entryFee, }); await sdk.executeViaRelayer({ target: MATCH_CONTRACT, data: matchIface.encodeFunctionData('join', [matchId]), }); } // Claim winnings async function claimWinnings(matchId: string) { await sdk.executeViaRelayer({ target: MATCH_CONTRACT, data: matchIface.encodeFunctionData('claimPrize', [matchId]), }); } ``` ## Best Practices 1. **Long sessions** - Create 2-4 hour game sessions for smooth play 2. **Batch actions** - Combine multiple game actions when possible 3. **Optimistic UI** - Update UI immediately, sync state async 4. **Retry logic** - Handle network issues gracefully 5. **Session refresh** - Prompt for new session before expiry ## Session Management Pattern ```typescript class GameSession { private sessionPromise: Promise | null = null; async ensureSession(): Promise { const active = sdk.sessions.getActive(); if (active && active.expiresAt > Date.now() + 300000) { return; // Session valid for at least 5 more minutes } // Prevent multiple concurrent session creations if (!this.sessionPromise) { this.sessionPromise = this.createSession(); } await this.sessionPromise; this.sessionPromise = null; } private async createSession(): Promise { showModal('Session expired. Touch to continue playing.'); await sdk.sessions.create({ duration: 7200, maxValue: parseUnits('50', 6), allowedContracts: [GAME_CONTRACT, MARKETPLACE], }); hideModal(); } async executeGameAction(action: () => Promise): Promise { await this.ensureSession(); await action(); } } const gameSession = new GameSession(); // Usage await gameSession.executeGameAction(() => claimReward()); await gameSession.executeGameAction(() => mintItem(1)); ``` ## Security for Games - Use server-side validation for competitive games - Sign scores/actions with passkey for authenticity - Rate limit game actions to prevent abuse - Use commit-reveal for fairness in random outcomes - Store sensitive game state on-chain ================================================================================ URL: /integrations/nft-marketplace ================================================================================ # NFT Marketplace Integration Build NFT marketplaces with gasless, passkey-authenticated transactions. > **🖼️ Try it yourself:** Clone and run the full NFT marketplace example: > ```bash > git clone https://github.com/Veridex-Protocol/examples.git > cd examples && npm install && npm run integration:nft > ``` > [View source on GitHub](https://github.com/Veridex-Protocol/examples/tree/main/integrations/nft-marketplace) · [Browse all examples](/guides/examples) ## Overview Veridex enables NFT marketplaces to offer: - **No wallet extensions** - Users authenticate with FaceID/TouchID - **Gasless minting** - Creators mint without ETH - **Gasless purchases** - Buyers pay only the NFT price - **Cross-chain NFTs** - Bridge NFTs between chains ## Basic NFT Operations ### Minting ```typescript const sdk = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', }); // Authenticate await sdk.passkey.authenticate(); // Mint NFT via relayer (gasless) const tx = await sdk.executeViaRelayer({ target: NFT_CONTRACT, value: 0n, data: nftContract.interface.encodeFunctionData('mint', [ sdk.getVaultAddress(), // to 'ipfs://Qm...', // tokenURI ]), }); ``` ### Transfer ```typescript // Transfer NFT const tx = await sdk.executeViaRelayer({ target: NFT_CONTRACT, value: 0n, data: nftContract.interface.encodeFunctionData('transferFrom', [ sdk.getVaultAddress(), // from recipientAddress, // to tokenId, // tokenId ]), }); ``` ## Marketplace Components ### Listing Component ```tsx const MARKETPLACE = '0xYourMarketplace'; export function ListNFT({ nftContract, tokenId }: { nftContract: string; tokenId: bigint }) { const [price, setPrice] = useState(''); const [loading, setLoading] = useState(false); const handleList = async () => { setLoading(true); try { // 1. Approve marketplace to transfer NFT await sdk.executeViaRelayer({ target: nftContract, data: new ethers.Interface([ 'function approve(address to, uint256 tokenId)' ]).encodeFunctionData('approve', [MARKETPLACE, tokenId]), }); // 2. List on marketplace await sdk.executeViaRelayer({ target: MARKETPLACE, data: new ethers.Interface([ 'function list(address nft, uint256 tokenId, uint256 price)' ]).encodeFunctionData('list', [ nftContract, tokenId, parseUnits(price, 6), // USDC price ]), }); alert('NFT listed!'); } catch (error) { console.error(error); } setLoading(false); }; return ( List NFT #{tokenId.toString()} setPrice(e.target.value)} placeholder="Price in USDC" className="w-full p-2 border rounded mb-4" /> {loading ? 'Listing...' : 'List for Sale'} ); } ``` ### Buy Component ```tsx export function BuyNFT({ nftContract, tokenId, price, seller }: { nftContract: string; tokenId: bigint; price: bigint; seller: string; }) { const [loading, setLoading] = useState(false); const handleBuy = async () => { setLoading(true); try { // 1. Approve USDC spend await sdk.approveViaRelayer({ token: USDC_ADDRESS, spender: MARKETPLACE, amount: price, }); // 2. Execute purchase await sdk.executeViaRelayer({ target: MARKETPLACE, data: new ethers.Interface([ 'function buy(address nft, uint256 tokenId)' ]).encodeFunctionData('buy', [nftContract, tokenId]), }); alert('NFT purchased!'); } catch (error) { console.error(error); } setLoading(false); }; return ( {loading ? 'Purchasing...' : `Buy for ${formatUnits(price, 6)} USDC`} ); } ``` ### Gallery Component ```tsx interface NFT { tokenId: bigint; tokenURI: string; metadata?: { name: string; image: string; description: string; }; } export function NFTGallery({ nftContract }: { nftContract: string }) { const { vaultAddress } = useVeridex(); const [nfts, setNfts] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { if (vaultAddress) { loadNFTs(); } }, [vaultAddress]); const loadNFTs = async () => { setLoading(true); try { const provider = new ethers.JsonRpcProvider('https://mainnet.base.org'); const contract = new ethers.Contract( nftContract, ['function balanceOf(address) view returns (uint256)', 'function tokenOfOwnerByIndex(address, uint256) view returns (uint256)', 'function tokenURI(uint256) view returns (string)'], provider ); const balance = await contract.balanceOf(vaultAddress); const nftData: NFT[] = []; for (let i = 0; i < balance; i++) { const tokenId = await contract.tokenOfOwnerByIndex(vaultAddress, i); const tokenURI = await contract.tokenURI(tokenId); // Fetch metadata const metadataUrl = tokenURI.replace('ipfs://', 'https://ipfs.io/ipfs/'); const metadata = await fetch(metadataUrl).then(r => r.json()); nftData.push({ tokenId, tokenURI, metadata }); } setNfts(nftData); } catch (error) { console.error(error); } setLoading(false); }; if (loading) return Loading NFTs...; return ( {nfts.map((nft) => ( {nft.metadata?.name} #{nft.tokenId.toString()} ))} ); } ``` ## Batch Operations For multiple NFT operations: ```typescript // Batch list multiple NFTs async function batchList(nfts: { contract: string; tokenId: bigint; price: bigint }[]) { for (const nft of nfts) { await sdk.executeViaRelayer({ target: nft.contract, data: nftIface.encodeFunctionData('approve', [MARKETPLACE, nft.tokenId]), }); await sdk.executeViaRelayer({ target: MARKETPLACE, data: marketplaceIface.encodeFunctionData('list', [ nft.contract, nft.tokenId, nft.price ]), }); } } ``` ## Cross-Chain NFTs Bridge NFTs between chains using Wormhole: ```typescript // Bridge NFT from Base to Optimism async function bridgeNFT(nftContract: string, tokenId: bigint, targetChain: string) { // 1. Lock NFT in bridge contract await sdk.executeViaRelayer({ target: nftContract, data: nftIface.encodeFunctionData('approve', [NFT_BRIDGE, tokenId]), }); await sdk.executeViaRelayer({ target: NFT_BRIDGE, data: bridgeIface.encodeFunctionData('bridge', [ nftContract, tokenId, getWormholeChainId(targetChain), sdk.getVaultAddress(), ]), }); // 2. Claim on target chain (handled by relayer) } ``` ## Royalties Support EIP-2981 royalties: ```typescript // Check royalties before sale async function getRoyaltyInfo(nftContract: string, tokenId: bigint, salePrice: bigint) { const provider = new ethers.JsonRpcProvider('https://mainnet.base.org'); const contract = new ethers.Contract( nftContract, ['function royaltyInfo(uint256 tokenId, uint256 salePrice) view returns (address, uint256)'], provider ); try { const [receiver, royaltyAmount] = await contract.royaltyInfo(tokenId, salePrice); return { receiver, royaltyAmount }; } catch { return null; // No royalty support } } ``` ## Metadata Standards ### ERC-721 Metadata ```typescript interface NFTMetadata { name: string; description: string; image: string; external_url?: string; attributes?: Array<{ trait_type: string; value: string | number; display_type?: 'number' | 'boost_percentage' | 'boost_number' | 'date'; }>; } ``` ### Upload to IPFS ```typescript const ipfs = create({ url: 'https://ipfs.infura.io:5001' }); async function uploadMetadata(metadata: NFTMetadata): Promise { const { cid } = await ipfs.add(JSON.stringify(metadata)); return `ipfs://${cid}`; } async function uploadImage(file: File): Promise { const { cid } = await ipfs.add(file); return `ipfs://${cid}`; } ``` ## Best Practices 1. **Lazy minting** - Only mint when sold to save gas 2. **Batch approvals** - Use `setApprovalForAll` for multiple NFTs 3. **Cache metadata** - Store metadata in database for fast loading 4. **Verify ownership** - Always check on-chain before allowing actions 5. **Handle pending** - Show pending transactions in UI ## Security - Verify NFT ownership before listing - Check approval status before purchase - Validate metadata before display - Use IPFS for decentralized storage ================================================================================ URL: /security/audits ================================================================================ # Security Audits Information about Veridex security audits, bug bounty program, and security practices. ## Audit Status | Component | Auditor | Status | Date | |-----------|---------|--------|------| | Smart Contracts | TBD | Planned | Q1 2025 | | SDK | Internal | Ongoing | - | | Relayer | Internal | Ongoing | - | ## Planned Audits ### Smart Contract Audit We are planning a comprehensive audit of our EVM smart contracts: **Scope:** - VaultFactory.sol - PasskeyVault.sol - Paymaster.sol - Session key management - Cross-chain bridge integration **Focus Areas:** - Passkey signature verification (P-256/secp256r1) - Account abstraction implementation - Session key constraints - Cross-chain message handling - Reentrancy protection - Access control ### SDK Security Review **Scope:** - WebAuthn credential handling - Signature generation - Transaction construction - State management ## Security Practices ### Development - **Code Review**: All code changes require peer review - **Testing**: Comprehensive unit and integration tests - **Static Analysis**: Automated security scanning - **Dependencies**: Regular dependency audits and updates ### Cryptography - **P-256 (secp256r1)**: Standard curve used by WebAuthn - **No custom cryptography**: Using well-audited libraries - **Secure random**: Platform-provided secure random number generation ### Infrastructure - **HTTPS only**: All API communication encrypted - **Rate limiting**: Protection against abuse - **Monitoring**: Real-time security monitoring - **Incident response**: Documented incident response plan ## Bug Bounty Program ### Overview We are launching a bug bounty program to reward security researchers who help us identify vulnerabilities. **Status:** Coming Soon ### Scope (Planned) **In Scope:** - Smart contracts on mainnet - SDK security issues - Relayer API vulnerabilities - Cross-chain bridge logic **Out of Scope:** - Third-party services - Social engineering - DoS attacks - Issues already reported ### Rewards (Planned) | Severity | Reward | |----------|--------| | Critical | Up to $50,000 | | High | Up to $20,000 | | Medium | Up to $5,000 | | Low | Up to $1,000 | ### Responsible Disclosure Please report security issues to: **security@veridex.network** Do NOT: - Publicly disclose before we've addressed the issue - Access or modify user data - Disrupt service availability We commit to: - Acknowledge receipt within 24 hours - Provide initial assessment within 72 hours - Work with you on disclosure timeline - Credit researchers (if desired) ## Known Issues & Mitigations ### Passkey Platform Support **Issue:** Not all platforms support P-256 passkeys equally. **Mitigation:** - Clear messaging about supported platforms - Fallback guidance for unsupported browsers - Testing on all major platforms ### Cross-Chain Latency **Issue:** Cross-chain operations have inherent latency. **Mitigation:** - Clear UI feedback during cross-chain operations - Transaction status tracking - Retry mechanisms for failed operations ## Security Architecture ### Passkey Security ```mermaid block-beta columns 1 block:Device["User Device"] columns 1 block:Enclave["Secure Enclave / TPM"] PK["Private Key (never exposed)\nGenerated on device · Cannot be extracted · Biometric protected"] end block:Browser["Browser / App"] BA["WebAuthn API · Public key only\nSignature requests go to secure enclave"] end end ``` ### Transaction Flow ```mermaid flowchart TD A["User Action"] --> B["Biometric Verification\n(user verification required)"] B --> C["Secure Enclave Signing\n(signs in hardware)"] C --> D["Relayer Submission\n(submits to blockchain)"] D --> E["On-Chain Verification\n(P-256 signature check)"] ``` ## Compliance ### Standards - **WebAuthn Level 2**: W3C standard for web authentication - **FIDO2**: Cross-platform authentication standard - **EIP-4337**: Account abstraction standard ### Privacy - No private key storage on servers - Minimal personal data collection - Credential IDs are anonymized ## Contact **Security Issues:** security@veridex.network **General Inquiries:** hello@veridex.network **Emergency:** [Contact form on website] --- *Last updated: December 2024* ================================================================================ URL: /security/best-practices ================================================================================ # Security Best Practices Essential security guidelines for integrating Veridex passkey wallets. ## Passkey Security ### Credential Storage ```typescript // ✅ Good: Let the browser/OS manage credentials await sdk.passkey.register('user@example.com', 'My Wallet'); // Credential stored securely in platform authenticator // ❌ Bad: Don't try to extract or store private keys // The private key never leaves the secure enclave ``` ### User Verification ```typescript // ✅ Require user verification for sensitive operations await sdk.passkey.authenticate({ userVerification: 'required' }); // The platform will require biometric/PIN verification ``` ### Credential Binding ```typescript // Bind credentials to your domain const credential = await sdk.passkey.register('user@example.com', 'Wallet', { // Only your domain can use this credential rp: { id: 'yourdomain.com', name: 'Your App', }, }); ``` ## Cross-Origin Authentication Security When integrating cross-domain passkeys, follow these additional security practices. ### Register Your Production Origin ```typescript // ✅ Register your exact production origin // POST /api/v1/apps/register // { "name": "My App", "origin": "https://myapp.com" } // ❌ Don't rely on wildcard or unregistered origins // Unregistered origins are blocked by the Auth Portal ``` ### Validate Sessions Server-Side ```typescript // ✅ Good: Validate server session tokens on your backend async function protectedEndpoint(req: Request) { const sessionId = req.headers.get('X-Veridex-Session'); const response = await fetch( `https://relayer.veridex.network/api/v1/session/${sessionId}` ); const data = await response.json(); if (!data.valid) { return new Response('Unauthorized', { status: 401 }); } // Verify the session belongs to the expected origin if (data.session.appOrigin !== 'https://myapp.com') { return new Response('Origin mismatch', { status: 403 }); } // Proceed with the request } // ❌ Bad: Trust client-side session data without verification // const session = JSON.parse(localStorage.getItem('veridex_session')); // if (session) { grantAccess(); } // INSECURE ``` ### Protect Your API Key ```typescript // ✅ Store API key server-side only // Use environment variables, never client-side code const apiKey = process.env.VERIDEX_API_KEY; // Server only // ❌ Never expose in client-side code // const apiKey = 'vdx_sk_...'; // EXPOSED TO USERS ``` ### Revoke Sessions on Logout ```typescript // ✅ Always revoke server sessions when users log out async function logout(serverSessionId: string) { await fetch( `https://relayer.veridex.network/api/v1/session/${serverSessionId}`, { method: 'DELETE' } ); localStorage.removeItem('veridex_session'); } ``` ### Validate the Auth Portal Origin ```typescript // ✅ When listening for postMessage from the Auth Portal, // always verify the origin window.addEventListener('message', (event) => { // Only accept messages from the Veridex Auth Portal if (!event.origin.includes('veridex.network')) { return; // Ignore messages from unknown origins } // Process the auth response const { type, payload } = event.data; // ... }); ``` ### Use Short-Lived Sessions ```typescript // ✅ Good: Short-lived sessions with minimal permissions const { serverSession } = await auth.authenticateAndCreateSession({ permissions: ['read'], // Only what you need expiresInMs: 1800000, // 30 minutes }); // ❌ Bad: Long-lived sessions with broad permissions const { serverSession } = await auth.authenticateAndCreateSession({ permissions: ['read', 'transfer', 'admin'], expiresInMs: 86400000 * 30, // 30 days }); ``` ## Session Key Security ### Limit Session Scope ```typescript // ✅ Good: Minimal permissions const session = await sdk.sessions.create({ duration: 1800, // 30 minutes, not days maxValue: parseUnits('100', 6), // $100 max, not unlimited allowedTokens: [USDC], // Only needed tokens allowedContracts: [SPECIFIC_CONTRACT], // Only needed contracts }); // ❌ Bad: Overly permissive const session = await sdk.sessions.create({ duration: 86400 * 30, // 30 days maxValue: parseUnits('1000000', 6), // $1M max // No token/contract restrictions }); ``` ### Session Expiry Handling ```typescript function isSessionValid(): boolean { const session = sdk.sessions.getActive(); if (!session) return false; // Add buffer for network latency const buffer = 60 * 1000; // 1 minute return session.expiresAt > Date.now() + buffer; } async function ensureValidSession() { if (!isSessionValid()) { await sdk.sessions.create({ duration: 1800 }); } } ``` ### Revoke Sessions ```typescript // Revoke session on logout async function logout() { await sdk.sessions.revoke(); sdk.passkey.clearCredential(); } // Revoke session on suspicious activity async function handleSuspiciousActivity() { await sdk.sessions.revoke(); notifyUser('Session terminated for security'); } ``` ## Transaction Security ### Validate Recipients ```typescript // ✅ Validate addresses before sending function isValidAddress(address: string): boolean { return ethers.isAddress(address); } async function safeSend(recipient: string, amount: bigint) { if (!isValidAddress(recipient)) { throw new Error('Invalid recipient address'); } // Additional checks if (recipient === ethers.ZeroAddress) { throw new Error('Cannot send to zero address'); } return await sdk.transferViaRelayer({ token, recipient, amount }); } ``` ### Amount Validation ```typescript // ✅ Validate amounts async function validateTransfer(amount: bigint, token: string) { const balance = await sdk.getBalance(token); if (amount <= 0n) { throw new Error('Amount must be positive'); } if (amount > balance) { throw new Error('Insufficient balance'); } // Check against user's spending limit const limit = await getUserSpendingLimit(); if (amount > limit) { throw new Error('Exceeds spending limit'); } } ``` ### Transaction Signing ```typescript // ✅ Always verify what you're signing async function signTransaction(tx: TransactionRequest) { // Show user what they're signing const confirmed = await showConfirmationDialog({ to: tx.to, value: tx.value, data: tx.data, }); if (!confirmed) { throw new Error('User rejected transaction'); } return await sdk.signAndSend(tx); } ``` ## Contract Interaction Security ### Verify Contract Addresses ```typescript // ✅ Maintain allowlist of known contracts const VERIFIED_CONTRACTS = { uniswap: '0x2626664c2603336E57B271c5C0b26F421741e481', aave: '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5', }; async function safeContractCall(contract: string, data: string) { const isVerified = Object.values(VERIFIED_CONTRACTS).includes(contract); if (!isVerified) { // Require additional confirmation for unknown contracts const confirmed = await confirmUnknownContract(contract); if (!confirmed) throw new Error('Rejected unknown contract'); } return await sdk.executeViaRelayer({ target: contract, data }); } ``` ### Decode Transaction Data ```typescript // ✅ Show users decoded transaction data function decodeTransaction(to: string, data: string) { const knownInterfaces = { erc20: new Interface([ 'function transfer(address to, uint256 amount)', 'function approve(address spender, uint256 amount)', ]), // Add more known interfaces }; for (const [name, iface] of Object.entries(knownInterfaces)) { try { const decoded = iface.parseTransaction({ data }); return { contract: name, method: decoded.name, args: decoded.args, }; } catch { continue; } } return { contract: 'unknown', method: 'unknown', args: [] }; } ``` ## API Security ### Secure Relayer Communication ```typescript // ✅ Always use HTTPS const sdk = createSDK('base', { relayerUrl: 'https://relayer.veridex.network', // HTTPS only }); // ❌ Never use HTTP in production // relayerUrl: 'http://...' // INSECURE ``` ### Rate Limiting ```typescript // Implement client-side rate limiting class RateLimiter { private requests: number[] = []; private limit: number; private window: number; constructor(limit: number, windowMs: number) { this.limit = limit; this.window = windowMs; } canRequest(): boolean { const now = Date.now(); this.requests = this.requests.filter(t => t > now - this.window); return this.requests.length < this.limit; } recordRequest() { this.requests.push(Date.now()); } } const limiter = new RateLimiter(10, 60000); // 10 requests per minute async function rateLimitedCall(fn: () => Promise) { if (!limiter.canRequest()) { throw new Error('Rate limit exceeded'); } limiter.recordRequest(); return await fn(); } ``` ## Error Handling ### Don't Expose Sensitive Information ```typescript // ✅ Safe error handling try { await sdk.transferViaRelayer({ token, recipient, amount }); } catch (error) { // Log full error internally console.error('Transfer failed:', error); // Show generic message to user showError('Transfer failed. Please try again.'); } // ❌ Don't expose internal errors // showError(error.message); // May contain sensitive info ``` ### Handle Authentication Errors ```typescript async function handleAuthError(error: Error) { if (error.message.includes('NotAllowedError')) { // User cancelled or timeout showMessage('Authentication cancelled'); } else if (error.message.includes('InvalidStateError')) { // Credential not found showMessage('Please register a passkey first'); } else if (error.message.includes('NotSupportedError')) { // Browser doesn't support WebAuthn showMessage('Your browser does not support passkeys'); } } ``` ## Monitoring & Logging ### Audit Logging ```typescript // Log security-relevant events function auditLog(event: string, data: object) { const entry = { timestamp: new Date().toISOString(), event, data, // Don't log sensitive data like credentials }; // Send to logging service sendToLogger(entry); } // Usage auditLog('session_created', { duration: 1800, maxValue: '100' }); auditLog('transfer_initiated', { to: recipient, amount: '50' }); ``` ### Monitor for Anomalies ```typescript // Detect unusual activity async function checkForAnomalies(tx: Transaction) { const recentTxs = await getRecentTransactions(); // Check for unusual patterns const unusualAmount = tx.amount > averageAmount(recentTxs) * 10; const newRecipient = !recentTxs.some(t => t.to === tx.to); const highFrequency = recentTxs.length > 10; if (unusualAmount || newRecipient && highFrequency) { await requestAdditionalVerification(); } } ``` ## Checklist ### Before Launch - [ ] All API calls use HTTPS - [ ] Session keys have reasonable limits - [ ] Error messages don't expose internals - [ ] Rate limiting implemented - [ ] Audit logging in place - [ ] Contract addresses verified - [ ] Amount validation implemented - [ ] User verification required for sensitive ops - [ ] Production origin registered at [developers.veridex.network](https://developers.veridex.network) - [ ] API key stored server-side only (never in client code) - [ ] Server-side session validation implemented - [ ] Sessions revoked on user logout ### Ongoing - [ ] Monitor for unusual activity - [ ] Keep SDK updated - [ ] Review session configurations - [ ] Audit access logs regularly - [ ] Test recovery procedures - [ ] Review cross-origin session token expiry settings - [ ] Monitor app registration status via relayer API ================================================================================ URL: /security/recovery ================================================================================ # Account Recovery Understanding and implementing recovery options for Veridex passkey wallets. ## Overview Passkey wallets are secure by design, but this security means careful planning is needed for recovery. Veridex provides multiple recovery mechanisms to ensure users never lose access to their funds. ## Recovery Methods ### 1. Platform Sync (Recommended) Modern platforms sync passkeys across devices automatically: | Platform | Sync Method | |----------|-------------| | Apple | iCloud Keychain | | Google | Google Password Manager | | Microsoft | Microsoft Account | ```typescript // If user has a new device but same account await sdk.passkey.authenticate(); // The synced passkey works automatically! ``` ### 2. Backup Passkey Register a second passkey as backup: ```typescript // Register primary passkey const primary = await sdk.passkey.register('user@example.com', 'Primary'); // Register backup passkey on a different device const backup = await sdk.passkey.registerBackup({ existingCredential: primary.credentialId, }); // Either passkey can now access the wallet ``` ### 3. Social Recovery Designate trusted guardians who can help recover the account: ```typescript // Setup guardians (requires 2 of 3 to recover) await sdk.recovery.setupGuardians({ threshold: 2, guardians: [ { address: '0xGuardian1...', email: 'guardian1@example.com' }, { address: '0xGuardian2...', email: 'guardian2@example.com' }, { address: '0xGuardian3...', email: 'guardian3@example.com' }, ], }); ``` Guardian recovery process: ```typescript // 1. User initiates recovery const recoveryRequest = await sdk.recovery.initiate({ newCredential: newPasskeyCredential, }); // 2. Guardians approve (each guardian does this) await sdk.recovery.approveAsGuardian({ recoveryId: recoveryRequest.id, guardianSignature: signature, }); // 3. After threshold met, complete recovery await sdk.recovery.complete({ recoveryId: recoveryRequest.id, }); ``` ### 4. Time-Delayed Recovery Set up a recovery address with a time delay: ```typescript // Setup recovery address with 7-day delay await sdk.recovery.setupDelayedRecovery({ recoveryAddress: '0xRecoveryWallet...', delay: 7 * 24 * 60 * 60, // 7 days in seconds }); ``` Recovery process: ```typescript // 1. Initiate recovery (from recovery address) await sdk.recovery.initiateDelayed({ vaultAddress: '0xVaultToRecover...', }); // 2. Wait for delay period (7 days) // User can cancel during this time if it's unauthorized // 3. Complete recovery after delay await sdk.recovery.completeDelayed({ vaultAddress: '0xVaultToRecover...', }); ``` Cancel unauthorized recovery: ```typescript // If user still has access, they can cancel await sdk.recovery.cancelDelayed(); ``` ## Recovery UI Components ### Recovery Setup ```tsx export function RecoverySetup() { const [method, setMethod] = useState<'backup' | 'guardian' | 'delayed'>('backup'); const [loading, setLoading] = useState(false); const setupBackupPasskey = async () => { setLoading(true); try { await sdk.passkey.registerBackup({ existingCredential: sdk.passkey.getCredential()!, }); alert('Backup passkey registered!'); } catch (error) { console.error(error); } setLoading(false); }; const setupGuardians = async (guardians: string[]) => { setLoading(true); try { await sdk.recovery.setupGuardians({ threshold: 2, guardians: guardians.map(addr => ({ address: addr })), }); alert('Guardians configured!'); } catch (error) { console.error(error); } setLoading(false); }; return ( Setup Recovery setMethod('backup')} className={method === 'backup' ? 'bg-blue-600 text-white' : 'border'} > Backup Passkey {method === 'backup' && ( Register a second passkey on another device for backup access. {loading ? 'Setting up...' : 'Register Backup Passkey'} )} {/* Add guardian and delayed recovery UI */} ); } ``` ### Recovery Flow ```tsx export function RecoveryFlow() { const [step, setStep] = useState<'start' | 'verify' | 'complete'>('start'); const [recoveryMethod, setRecoveryMethod] = useState(null); return ( Account Recovery {step === 'start' && ( Select how you'd like to recover your account: { setRecoveryMethod('sync'); setStep('verify'); }} className="w-full p-4 border rounded-lg text-left" > Synced Passkey Use a passkey synced from another device { setRecoveryMethod('backup'); setStep('verify'); }} className="w-full p-4 border rounded-lg text-left" > Backup Passkey Use your registered backup passkey { setRecoveryMethod('guardian'); setStep('verify'); }} className="w-full p-4 border rounded-lg text-left" > Guardian Recovery Request recovery from your guardians )} {step === 'verify' && recoveryMethod === 'sync' && ( setStep('complete')} /> )} {step === 'complete' && ( ✓ Recovery Complete! Your account has been recovered. )} ); } ``` ## Best Practices ### During Onboarding 1. **Encourage backup setup** - Prompt users to set up recovery immediately ```tsx function OnboardingComplete() { return ( Wallet Created! 🎉 ⚠️ Set Up Recovery Protect your wallet by setting up a backup method now. navigate('/recovery/setup')}> Set Up Recovery → ); } ``` 2. **Explain platform sync** - Help users understand their passkey is backed up ```tsx function PlatformSyncInfo() { const platform = detectPlatform(); const syncInfo = { apple: 'Your passkey is synced via iCloud Keychain', google: 'Your passkey is synced via Google Password Manager', microsoft: 'Your passkey is synced via Microsoft Account', }; return ( {syncInfo[platform]} You can access your wallet from any device signed into your {platform} account. ); } ``` ### For High-Value Accounts 1. **Multiple recovery methods** - Use both backup passkey AND guardians ```typescript // Setup comprehensive recovery async function setupComprehensiveRecovery() { // 1. Backup passkey await sdk.passkey.registerBackup({...}); // 2. Guardian recovery await sdk.recovery.setupGuardians({ threshold: 2, guardians: [...], }); // 3. Time-delayed recovery as last resort await sdk.recovery.setupDelayedRecovery({ recoveryAddress: coldWalletAddress, delay: 14 * 24 * 60 * 60, // 14 days }); } ``` 2. **Regular testing** - Periodically verify recovery methods work ## Emergency Recovery If all else fails, contact support with: - Proof of identity - Transaction history from the wallet - Original registration details Note: Emergency recovery is not instant and requires extensive verification. ## Recovery Configuration ```typescript interface RecoveryConfig { // Backup passkey backupEnabled: boolean; backupCredentialId?: string; // Guardian recovery guardians?: { addresses: string[]; threshold: number; }; // Delayed recovery delayedRecovery?: { recoveryAddress: string; delaySeconds: number; }; } ``` ## FAQ **Q: What if I lose all my devices?** A: Use guardian recovery or time-delayed recovery. Platform sync also works if you can restore your account on a new device. **Q: Can guardians steal my funds?** A: No. Guardians can only help recover to a new passkey YOU control. They cannot access funds directly. **Q: What's the safest recovery setup?** A: Use platform sync (automatic) + backup passkey + 2-of-3 guardian recovery for comprehensive protection. **Q: How long does guardian recovery take?** A: Once threshold approvals are collected, recovery is immediate. Collecting approvals depends on guardian responsiveness. ================================================================================ URL: /security/agent-security ================================================================================ # Agent Security Gateway The Agent Security Gateway (`@veridex/agent-security`) provides defense-in-depth for any AI agent — whether built with Veridex, LangChain, CrewAI, or custom code. ## Deployment Patterns ### Embedded (In-Process) Run the gateway inside your agent process for lowest latency: ```typescript const gateway = new SecurityGateway({ packs: createDefaultPacks(), defaultAction: 'block', }); // Evaluate before every tool call const result = await gateway.evaluate({ type: 'tool_call', toolName: toolName, arguments: args, agentId: 'my-agent', }); ``` ### Sidecar Service Deploy as an HTTP service alongside your agent fleet: ```typescript const server = createSecurityServer({ packs: createDefaultPacks(), port: 4600, authToken: process.env.SECURITY_GATEWAY_TOKEN, }); await server.start(); ``` ### Centralized Gateway Single gateway serving multiple agent teams: ``` ┌─────────────┐ ┌──────────────────┐ │ Agent Team A │────▶│ │ ├─────────────┤ │ Security │ │ Agent Team B │────▶│ Gateway │ ├─────────────┤ │ (port 4600) │ │ Agent Team C │────▶│ │ └─────────────┘ └──────────────────┘ ``` ## Security Packs ### Pack Selection Not every agent needs all 12 packs. Select based on your threat model: | Agent Type | Recommended Packs | |-----------|------------------| | Read-only data agents | `injectionDetection`, `secretDetection`, `endpointAllowlist` | | Financial agents | All above + `budgetCeiling`, `financialSafety`, `crossTurnAnomaly` | | Tool-using agents | All above + `toolPoisoning`, `shellCommandSafety`, `handoffSafety` | | Multi-jurisdiction agents | All above + `dataSovereignty` | | Production fleet | `createDefaultPacks()` (all 12) | ### Custom Packs Implement the `SecurityPack` interface: ```typescript const customPack: SecurityPack = { id: 'custom-ip-filter', name: 'IP Filter', description: 'Block requests to internal IP ranges', async evaluate(action) { if (action.type === 'tool_call' && action.arguments?.url) { const url = new URL(action.arguments.url as string); if (isInternalIP(url.hostname)) { return { verdict: 'block', reasons: ['Request to internal IP range blocked'], packId: 'custom-ip-filter', confidence: 1.0, }; } } return { verdict: 'allow', reasons: [], packId: 'custom-ip-filter', confidence: 1.0 }; }, }; ``` ## Monitoring Enable telemetry to track security evaluations: ```typescript const reporter = new TelemetryReporter({ endpoint: 'https://telemetry.example.com/events', batchSize: 50, flushIntervalMs: 10_000, }); const gateway = new SecurityGateway({ packs: createDefaultPacks(), telemetry: reporter, }); ``` ## Related - [Agent Security SDK](/sdks/agent-security) — Package documentation - [API Reference](/api-reference/agent-security) — Full type signatures - [Security: Data Sovereignty](/security/data-sovereignty) — Jurisdictional compliance ================================================================================ URL: /security/response-integrity ================================================================================ # Response Integrity Every LLM response in the Veridex agent runtime is sealed with an HMAC chain-of-custody signature. This provides cryptographic proof that model outputs haven't been modified between the provider and your application. ## Threat Model | Threat | Mitigation | |--------|-----------| | Man-in-the-middle modifying LLM responses | HMAC seal verification detects tampering | | Proxy or middleware injecting content | Raw response hash comparison reveals modifications | | Disputed agent behavior | Sealed response envelopes provide non-repudiable evidence | | Audit trail integrity | Content-hashed traces with embedded seals | ## Seal Algorithm 1. **Key Derivation**: HKDF-SHA256 with API key as IKM, salt `"veridex-response-seal-v1"`, info `"hmac-signing-key"` 2. **Signing**: HMAC-SHA256 over the raw response bytes 3. **Hashing**: SHA-256 of raw response bytes for fingerprinting ## Verification ```typescript const isValid = verifyResponseSeal( envelope.chainOfCustodySeal, rawResponseBytes, apiKeyBytes, ); ``` ## Limitations - Seals verify integrity from the Veridex runtime, not from the model provider's infrastructure - API key rotation requires re-deriving signing keys - Streaming responses are sealed after assembly (not per-chunk) The [Provider Response Signing Initiative](/guides/agent-integrity) proposes a future where model providers cryptographically sign responses at origin. ## Related - [Guide: Agent Integrity](/guides/agent-integrity) — Full walkthrough - [Agents Framework](/sdks/agents-framework#response-integrity-responseseal) — ResponseSeal API - [Governance: Traces](/governance/traces) — Audit trail with seals ================================================================================ URL: /security/data-sovereignty ================================================================================ # Data Sovereignty Data sovereignty policies enforce jurisdictional rules for PII (Personally Identifiable Information) processed by AI agents. These policies ensure compliance with GDPR, CCPA, PIPEDA, and other data protection regulations. ## Policy Configuration Configure sovereignty rules in the `dataSovereigntyPack`: ```typescript const pack = dataSovereigntyPack({ defaultJurisdiction: 'US', piiCategories: ['email', 'phone', 'ssn', 'name', 'address', 'dob', 'financial'], jurisdictionRules: [ { from: 'EU', to: 'US', verdict: 'block', reason: 'GDPR prohibits PII transfer to US without adequacy decision', regulations: ['GDPR Art. 44-49'], }, ], toolJurisdictions: { 'eu_database': 'EU', 'us_analytics': 'US', }, }); ``` ## Supported Regulations | Regulation | Jurisdiction | Key Requirements | |-----------|-------------|-----------------| | GDPR | EU/EEA | Data transfer restrictions (Art. 44-49), right to erasure, DPO | | CCPA/CPRA | California, US | Consumer opt-out, sale restrictions, access rights | | PIPEDA | Canada | Consent for cross-border transfer, purpose limitation | | LGPD | Brazil | Adequacy requirements, data localization | | POPIA | South Africa | Cross-border conditions, consent | ## Enforcement Levels | Verdict | Behavior | |---------|----------| | `block` | Tool call is prevented. Agent receives a denial reason. | | `flag` | Tool call proceeds but a violation is recorded for review. | | `allow` | No restriction. | ## Audit Trail All sovereignty evaluations are recorded: - Blocked calls appear as `sovereignty_violation` events in traces - Flagged calls create advisory records in the audit log - Evidence bundles include sovereignty context for compliance review ## Related - [Guide: Data Sovereignty](/guides/data-sovereignty) — Configuration walkthrough - [Agent Security](/sdks/agent-security) — Security gateway - [Governance: Sovereignty Compliance](/governance/sovereignty) — Violation audit trail ================================================================================ URL: /api-reference/sdk ================================================================================ # SDK Reference Complete API reference for the Veridex Core SDK (`@veridex/sdk`). ## Installation ```bash npm install @veridex/sdk ethers ``` --- ## Initialization ### createSDK Creates a new `VeridexSDK` instance with sensible defaults. ```typescript const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }); ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `chain` | `ChainName` | Chain name — see supported chains below | | `config` | `SimpleSDKConfig` | Optional configuration | **Supported chains:** `'base'`, `'optimism'`, `'arbitrum'`, `'ethereum'`, `'polygon'`, `'monad'`, `'solana'`, `'aptos'`, `'sui'`, `'starknet'`, `'stacks'` ```typescript interface SimpleSDKConfig { network?: 'testnet' | 'mainnet'; rpcUrl?: string; relayerUrl?: string; relayerApiKey?: string; sponsorPrivateKey?: string; integratorSponsorKey?: string; rpcUrls?: Record; } ``` ### Convenience Factories ```typescript const hubSdk = createHubSDK(); // Base hub chain const testSdk = createTestnetSDK('optimism'); // Force testnet const mainSdk = createMainnetSDK('base'); // Force mainnet const sessSdk = createSessionSDK('base'); // Session-optimized ``` ### VeridexSDK (Direct Constructor) For full control, use the class directly with an `EVMClient`: ```typescript const evmClient = new EVMClient({ chainId: 84532, wormholeChainId: 10004, rpcUrl: 'https://sepolia.base.org', hubContractAddress: '0x66D87dE68327f48A099c5B9bE97020Feab9a7c82', wormholeCoreBridge: '0x79A1027a6A159502049F10906D333EC57E95F083', name: 'Base Sepolia', explorerUrl: 'https://sepolia.basescan.org', vaultFactory: '0xCFaEb5652aa2Ee60b2229dC8895B4159749C7e53', vaultImplementation: '0x0d13367C16c6f0B24eD275CC67C7D9f42878285c', }); const sdk = new VeridexSDK({ chain: evmClient, testnet: true, relayerUrl: '/api/relayer', relayerApiKey: process.env.NEXT_PUBLIC_RELAYER_API_KEY, sponsorPrivateKey: process.env.NEXT_PUBLIC_VERIDEX_SPONSOR_KEY, chainRpcUrls: { 10004: 'https://sepolia.base.org', 10005: 'https://sepolia.optimism.io', 10003: 'https://sepolia-rollup.arbitrum.io/rpc', }, }); ``` ### Dynamic Import (SSR-Safe) For Next.js apps, use dynamic imports to avoid SSR issues: ```typescript let sdkInstance: VeridexSDK | null = null; export async function getVeridexSDK() { if (!sdkInstance) { const { createSDK } = await import('@veridex/sdk'); sdkInstance = createSDK('base', { network: 'testnet', relayerUrl: process.env.NEXT_PUBLIC_RELAYER_URL, }); } return sdkInstance; } ``` --- ## Passkey Management (`sdk.passkey`) ### sdk.passkey.register Register a new passkey with biometric authentication. ```typescript const credential = await sdk.passkey.register('alice', 'Alice Doe'); ``` | Parameter | Type | Description | |-----------|------|-------------| | `username` | `string` | Username (shown in passkey prompt) | | `displayName` | `string` | Display name for the credential | **Returns:** `Promise` ### sdk.passkey.authenticate Authenticate with an existing passkey (shows passkey picker). ```typescript const { credential, signature } = await sdk.passkey.authenticate(); ``` **Returns:** `Promise<{ credential: PasskeyCredential; signature: WebAuthnSignature }>` ### sdk.passkey.getCredential ```typescript const credential = sdk.passkey.getCredential(); // PasskeyCredential | null ``` ### sdk.passkey.setCredential ```typescript sdk.passkey.setCredential(credential); ``` ### sdk.passkey.getAllStoredCredentials ```typescript const credentials = sdk.passkey.getAllStoredCredentials(); // PasskeyCredential[] ``` ### sdk.passkey.sign ```typescript const signature = await sdk.passkey.sign(challenge); // WebAuthnSignature ``` ### Local Storage ```typescript sdk.passkey.saveToLocalStorage(); const credential = sdk.passkey.loadFromLocalStorage(); const hasStored = sdk.passkey.hasStoredCredential(); ``` ### Relayer Sync (Cross-Device) ```typescript await sdk.passkey.saveCredentialToRelayer(); const credential = await sdk.passkey.loadCredentialFromRelayer(); ``` --- ## Credential Management ```typescript sdk.setCredential(credential); // Set active credential sdk.clearCredential(); // Clear active credential const cred = sdk.getCredential(); // Get active credential const has = sdk.hasCredential(); // Check if credential is set ``` --- ## Vault & Identity ### sdk.getVaultAddress Get the deterministic vault address derived from the passkey. Same address on all EVM chains. ```typescript const address = sdk.getVaultAddress(); // string ``` ### sdk.vaultExists ```typescript const exists = await sdk.vaultExists(); // boolean ``` ### sdk.getVaultInfo ```typescript const info = await sdk.getVaultInfo(targetChainId?); // VaultInfo | null ``` ### sdk.getUnifiedIdentity ```typescript const identity = await sdk.getUnifiedIdentity(); for (const addr of identity.addresses) { console.log(`${addr.chainName}: ${addr.address} (deployed: ${addr.deployed})`); } ``` ### sdk.getMultiChainAddresses Get vault addresses across all configured chains. ```typescript const addresses = sdk.getMultiChainAddresses(); // { 10004: '0x...', 10005: '0x...', 10003: '0x...' } ``` **Returns:** `Record` — Wormhole chain ID → vault address ### sdk.getMultiChainPortfolio Get combined portfolio across multiple chains. ```typescript const portfolios = await sdk.getMultiChainPortfolio([10004, 10005]); for (const p of portfolios) { console.log(`${p.chainName}: $${p.totalUsdValue}`); } ``` **Returns:** `Promise` ### sdk.getChainConfig ```typescript const config = sdk.getChainConfig(); // ChainConfig ``` ### sdk.getReceiveAddress ```typescript const addr = sdk.getReceiveAddress(); // ReceiveAddress ``` ### sdk.getNonce ```typescript const nonce = await sdk.getNonce(); // bigint ``` --- ## Feature Detection ### sdk.supportsFeature Check if the current chain supports a specific feature. ```typescript if (sdk.supportsFeature('sessions')) { // Sessions are available } if (sdk.supportsFeature('crossChainBridge')) { // Bridging is available } ``` **Returns:** `boolean` ### sdk.getCapabilityMatrix Get the full capability matrix for the current chain and platform. ```typescript const matrix = sdk.getCapabilityMatrix({ webauthnSupported: true, conditionalUISupported: true, platformAuthenticatorAvailable: true, }); ``` **Returns:** `PlatformCapabilityMatrix` --- ## Transfers ### sdk.prepareTransfer Prepare a transfer with gas estimation. Call before signing to show costs. ```typescript const prepared = await sdk.prepareTransfer({ targetChain: 10004, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', amount: 1_000_000n, // 1 USDC }); console.log('Gas cost:', prepared.formattedCost); console.log('Expires:', new Date(prepared.expiresAt)); ``` **Returns:** `Promise` ### sdk.executeTransfer Execute a prepared transfer (requires a signer to pay gas). ```typescript const result = await sdk.executeTransfer(prepared, signer); console.log('Tx:', result.transactionHash); ``` **Returns:** `Promise` ### sdk.transferViaRelayer **Gasless transfer** through the relayer — the primary transfer method. The user only needs their passkey. ```typescript const result = await sdk.transferViaRelayer({ targetChain: 10004, token: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', amount: ethers.parseUnits('10', 6), }); ``` **Returns:** `Promise` ### sdk.transferWithTracking ```typescript const result = await sdk.transferWithTracking(params, signer, (status) => { console.log(status.state); // 'pending' → 'confirmed' }); ``` ### sdk.getTransactionSummary ```typescript const summary = await sdk.getTransactionSummary(prepared); console.log(summary.title); // "Transfer" console.log(summary.description); // "Send 1.0 USDC to 0x742d...5A234" console.log(summary.risks); // [{level:'warning', message:'Large transaction'}] ``` --- ## Cross-Chain Bridges ### sdk.prepareBridge ```typescript const prepared = await sdk.prepareBridge({ sourceChain: 10004, destinationChain: 10005, token: '0x...', recipient: '0x...', amount: 1_000_000n, }); ``` ### sdk.executeBridge ```typescript const result = await sdk.executeBridge(prepared, signer, (progress) => { console.log(`Step ${progress.step}/${progress.totalSteps}: ${progress.message}`); }); ``` ### sdk.bridgeViaRelayer Gasless bridge using the relayer. ```typescript const result = await sdk.bridgeViaRelayer(params, onProgress?); ``` ### sdk.getBridgeFees ```typescript const fees = await sdk.getBridgeFees(params); console.log('Total:', fees.formattedTotal); // "0.0012 ETH" ``` --- ## Spending Limits ### sdk.getSpendingLimits Get raw on-chain spending limits. ```typescript const limits = await sdk.getSpendingLimits(chainId?); console.log('Daily remaining:', limits.dailyRemaining); ``` ### sdk.getFormattedSpendingLimits Get spending limits formatted for UI display. ```typescript const formatted = await sdk.getFormattedSpendingLimits(); console.log(`${formatted.dailyUsedPercentage}% used`); console.log(`Resets in: ${formatted.timeUntilReset}`); console.log(`Per-tx limit: ${formatted.transactionLimit}`); ``` ### sdk.checkSpendingLimit Check if a transaction amount is within limits. ```typescript const check = await sdk.checkSpendingLimit(ethers.parseEther('1.0')); if (!check.allowed) { console.log(check.message); console.log('Suggestions:', check.suggestions); } ``` ### sdk.prepareSetDailyLimit ```typescript const prepared = await sdk.prepareSetDailyLimit(ethers.parseEther('5.0')); await sdk.executeTransfer(prepared, signer); ``` ### sdk.preparePauseVault / sdk.prepareUnpauseVault ```typescript const pause = await sdk.preparePauseVault(); const unpause = await sdk.prepareUnpauseVault(); ``` --- ## Balance Watching ### sdk.watchBalance Subscribe to vault balance changes via polling. Returns an unsubscribe function. ```typescript const unsub = sdk.watchBalance( (event) => { for (const change of event.changes) { console.log(`${change.token.symbol}: ${change.delta > 0n ? '+' : ''}${change.delta}`); } }, { intervalMs: 10_000, emitInitial: true }, (error) => console.error('Watch error:', error), ); // Later: stop watching unsub(); ``` See the [Balance Watching guide](/guides/balance-watching) for full usage patterns. --- ## Balance Management ### sdk.getVaultBalances ```typescript const portfolio = await sdk.getVaultBalances(); for (const entry of portfolio.tokens) { console.log(`${entry.token.symbol}: ${entry.formatted}`); } ``` ### sdk.getVaultNativeBalance ```typescript const native = await sdk.getVaultNativeBalance(); console.log(`${native.token.symbol}: ${native.formatted}`); ``` ### sdk.getVaultTokenBalance ```typescript const balance = await sdk.getVaultTokenBalance(tokenAddress); ``` ### sdk.getMultiChainBalances ```typescript const results = await sdk.getMultiChainBalances([10004, 10005, 10003]); ``` --- ## Sponsored Vault Creation ### sdk.isSponsorshipAvailable ```typescript const canSponsor = sdk.isSponsorshipAvailable(); // boolean ``` ### sdk.ensureSponsoredVaultsOnAllChains Auto-create vaults on all configured chains using the sponsor key. ```typescript const result = await sdk.ensureSponsoredVaultsOnAllChains(); console.log(`Created on ${result.results.filter(r => r.success).length} chains`); ``` ### sdk.createSponsoredVault ```typescript const result = await sdk.createSponsoredVault(10004); // Base Sepolia ``` ### sdk.getSponsoredChains ```typescript const chains = sdk.getSponsoredChains(); ``` --- ## Backup Passkeys (Multi-Key Identity) ### sdk.addBackupPasskey ```typescript const newCred = await sdk.passkey.register('alice-backup', 'Alice Backup'); const result = await sdk.addBackupPasskey(newCred, signer); ``` ### sdk.listAuthorizedPasskeys ```typescript const keys = await sdk.listAuthorizedPasskeys(); ``` ### sdk.hasBackupPasskeys ```typescript const hasBackup = await sdk.hasBackupPasskeys(); // boolean ``` ### sdk.removePasskey ```typescript await sdk.removePasskey(keyHash, signer); ``` --- ## Multisig (ADR-0037) Access via `sdk.multisig`: ```typescript // Configure policy await sdk.multisig.configurePolicy({ signature, threshold: 2, signer, }); // Create proposal const proposal = await sdk.multisig.proposeTransfer(params, signer); // Approve const approval = await sdk.multisig.approveProposal(proposal.proposalId, signer); // Execute (once threshold reached) if (approval.thresholdReached) { const result = await sdk.multisig.executeProposal(proposal.proposalId, signer); } ``` See the [Multisig Wallet guide](/guides/multisig-wallet) for full setup. --- ## Enterprise Manager Orchestration layer for batch operations and admin dashboards. ```typescript const enterprise = new EnterpriseManager({ sdk, maxConcurrency: 5 }); ``` ### enterprise.batchCreateVaults ```typescript const result = await enterprise.batchCreateVaults( { keyHashes: ['0xabc...', '0xdef...'], maxConcurrency: 3 }, (event) => console.log(event.type, event), ); console.log(`${result.succeeded}/${result.total} vaults created`); ``` ### enterprise.batchTransfer ```typescript const result = await enterprise.batchTransfer({ transfers: [ { targetChain: 10004, token: USDC, recipient: '0xAlice', amount: 1_000_000n }, { targetChain: 10004, token: USDC, recipient: '0xBob', amount: 2_000_000n }, ], signer, maxConcurrency: 2, }); ``` ### enterprise.batchSetSpendingLimits ```typescript const result = await enterprise.batchSetSpendingLimits({ updates: [ { newLimit: ethers.parseEther('5.0') }, { newLimit: ethers.parseEther('10.0') }, ], signer, }); ``` ### enterprise.watchVaultBalance ```typescript const unsub = enterprise.watchVaultBalance(10004, vaultAddress, (event) => { callWebhook(event); }); ``` ### enterprise.getSpendingLimitsForVault ```typescript const limits = await enterprise.getSpendingLimitsForVault('0xVault...', 10004); ``` See the [Enterprise Manager guide](/guides/enterprise-manager) for full patterns. --- ## Error Handling All SDK methods throw `VeridexError` with unified error codes: ```typescript try { await sdk.transferViaRelayer(params); } catch (err) { if (err instanceof VeridexError) { switch (err.code) { case VeridexErrorCode.INSUFFICIENT_FUNDS: showToast('Not enough tokens'); break; case VeridexErrorCode.DAILY_LIMIT_EXCEEDED: showToast('Daily limit reached'); break; default: if (err.retryable) { /* retry */ } } } } ``` See [Error Handling](/api-reference/errors) for the full error code reference and chain-specific mappings. --- ## Token Management ```typescript const tokens = sdk.getTokenList(); const usdc = sdk.getTokenBySymbol('USDC'); ``` ### Token Constants ```typescript NATIVE_TOKEN_ADDRESS, BASE_SEPOLIA_TOKENS, TOKEN_REGISTRY, getTokenList, getTokenBySymbol, getTokenByAddress, isNativeToken, } from '@veridex/sdk'; ``` --- ## Chain Presets & Utilities ```typescript getChainConfig, getSupportedChains, getHubChains, isChainSupported, getDefaultHub, CHAIN_PRESETS, CHAIN_NAMES, } from '@veridex/sdk'; const chains = getSupportedChains('testnet'); const config = getChainConfig('stacks', 'testnet'); ``` --- ## Chain Clients ```typescript EVMClient, SolanaClient, AptosClient, SuiClient, StarknetClient, StacksClient, } from '@veridex/sdk'; ``` --- ## Session Key Utilities ```typescript SessionManager, generateSecp256k1KeyPair, computeSessionKeyHash, signWithSessionKey, hashAction, verifySessionSignature, deriveEncryptionKey, encrypt, decrypt, createSessionStorage, } from '@veridex/sdk'; ``` --- ## ERC-8004 Utilities ```typescript getERC8004Addresses, isERC8004Chain, ERC8004_CHAINS, IDENTITY_REGISTRY_ABI, REPUTATION_REGISTRY_ABI, } from '@veridex/sdk'; const addresses = getERC8004Addresses('mainnet'); const supported = isERC8004Chain('base'); // true ``` | Registry | Mainnet | Testnet | |----------|---------|---------| | **Identity** | `0x8004A169FB4a3325136EB29fA0ceB6D2e539a432` | `0x8004A818BFB912233c491871b3d84c89A494BD9e` | | **Reputation** | `0x8004BAa17C55a88189AE136b182e5fdA19dE9b63` | `0x8004B663056A597Dffe9eCcC1965A193B7388713` | --- ## Transaction Tracking ```typescript sdk.transactions.track(txHash, chainId, (state) => { console.log(state); // 'pending' | 'confirmed' | 'failed' }, sequence); const state = await sdk.waitForTransaction(txHash); ``` --- ## Enterprise Factories ### createEnterpriseSDK ```typescript const sdk = createEnterpriseSDK(chain, { network: 'mainnet', multisig: { threshold: number, signers: Signer[] }, recovery: { guardians: string[], delay: number }, }); ``` Returns a `VeridexSDK` instance pre-configured with `MultisigManager` and `RecoveryManager`. ### createHubSDK ```typescript const sdk = createHubSDK(chain, { network: 'testnet' }); ``` Hub SDK for shared passkey credential management across multiple applications. ### createSessionSDK ```typescript const sdk = createSessionSDK(chain, { network: 'testnet', sessionKeyHash: '0x...', }); ``` Session-scoped SDK for backend services operating with a pre-existing session key. --- ## MultisigManager ```typescript const multisig = sdk.multisig; await multisig.propose({ to, value, data }); // Propose transaction await multisig.approve(proposalId); // Approve as signer await multisig.execute(proposalId); // Execute (threshold met) await multisig.getProposal(proposalId); // Get proposal details await multisig.listPending(); // List pending proposals ``` --- ## RecoveryManager ```typescript const recovery = sdk.recovery; await recovery.initiateRecovery(newOwner); // Start recovery await recovery.attestAsGuardian(requestId, signer); // Guardian attestation await recovery.executeRecovery(requestId); // Execute after delay await recovery.cancelRecovery(requestId); // Cancel (by current owner) await recovery.getRecoveryStatus(requestId); // Check status ``` --- ## CrossOriginAuth Enable Veridex passkey authentication in third-party applications. The SDK detects browser support and picks the best flow automatically. ```typescript const auth = createCrossOriginAuth({ rpId: 'veridex.network', // default authPortalUrl: 'https://auth.veridex.network', // default relayerUrl: 'https://relayer.veridex.network/api/v1', // default mode: 'popup', // 'popup' | 'redirect' redirectUri: 'https://myapp.com/callback', // only for redirect mode timeout: 120000, // 2 minutes default }); ``` ### auth.supportsRelatedOrigins Check if the browser supports WebAuthn Related Origin Requests (seamless, no popup). ```typescript const rorSupported = await auth.supportsRelatedOrigins(); // boolean ``` ### auth.authenticate Native passkey authentication (only works when ROR is supported). ```typescript const { credential, signature } = await auth.authenticate(); console.log('Key hash:', credential.keyHash); console.log('Vault:', '0x' + credential.keyHash.slice(-40)); ``` ### auth.connectWithVeridex Auth Portal flow (popup or redirect). Works on all browsers. ```typescript const session = await auth.connectWithVeridex(); console.log('Address:', session.address); console.log('Credential:', session.credential.keyHash); ``` **Options:** | Parameter | Type | Description | |-----------|------|-------------| | `register` | `boolean` | Open in registration mode | | `username` | `string` | Pre-fill username for registration | | `displayName` | `string` | Pre-fill display name | | `createSession` | `boolean` | Handle full session flow inside a single popup | | `permissions` | `string[]` | Session permissions (e.g. `['read', 'transfer']`) | | `expiresInMs` | `number` | Session expiry duration in ms | ### auth.authenticateAndCreateSession **Recommended.** Full flow: authenticate + create a server-validated session in one call. Uses a single popup when ROR is unavailable (no double-prompt). ```typescript const { session, serverSession } = await auth.authenticateAndCreateSession({ permissions: ['read', 'transfer'], expiresInMs: 3600000, // 1 hour }); console.log('Vault address:', session.address); console.log('Server session ID:', serverSession.id); console.log('Expires at:', new Date(serverSession.expiresAt)); // Store for subsequent API calls localStorage.setItem('veridex_session_id', serverSession.id); ``` ### auth.createServerSession Create a server-validated session token from a cross-origin session. ```typescript const serverSession = await auth.createServerSession(session); ``` ### auth.validateServerSession Validate an existing session token (returns `null` if expired/revoked). ```typescript const valid = await auth.validateServerSession('ses_abc123'); if (valid) { console.log('Session valid, expires:', new Date(valid.expiresAt)); } ``` ### auth.revokeServerSession ```typescript await auth.revokeServerSession('ses_abc123'); ``` ### auth.completeRedirectAuth Call on your redirect callback page to extract the session from URL params. ```typescript const session = auth.completeRedirectAuth(); // CrossOriginSession | null ``` --- ## WalletBackupManager PRF-based encrypted wallet backup and recovery. Backs up credentials to the relayer encrypted with a key derived from your passkey's PRF extension. ```typescript const backup = new WalletBackupManager({ passkey: sdk.passkey, relayerUrl: 'https://relayer.veridex.network/api/v1', }); ``` ### backup.backupCredentials Encrypt and store your wallet credentials on the relayer. The encryption key is derived from your passkey's PRF output — only you can decrypt it. ```typescript const result = await backup.backupCredentials(keyHash); console.log('Archive ID:', result.archiveId); console.log('Backed up:', result.credentialCount, 'credentials'); ``` **What happens:** 1. SDK triggers a WebAuthn ceremony and extracts the PRF seed from your authenticator 2. Derives an AES-GCM-256 encryption key via HKDF 3. Encrypts all locally-stored credentials 4. Uploads the encrypted archive to the relayer > **Note:** If your device doesn't support PRF (the passkey extension for deterministic secrets), the SDK falls back to a credential-ID-based key. This is less secure — the account page will show a warning. ### backup.recoverCredentials Recover your credentials on a new device. Requires a passkey that was previously registered for this identity. ```typescript const recovered = await backup.recoverCredentials(keyHash); console.log('Recovered:', recovered.credentials.length, 'credentials'); // Credentials are automatically saved to localStorage ``` **What happens:** 1. Fetches the encrypted archive from the relayer 2. Triggers a WebAuthn ceremony to extract the same PRF seed 3. Derives the same AES-GCM key 4. Decrypts and restores all credentials to localStorage ### backup.getBackupStatus Check backup readiness for an identity. ```typescript const status = await backup.getBackupStatus(keyHash); console.log('Has archive:', status.hasArchive); console.log('PRF supported:', status.prfSupported); console.log('Guardians:', status.guardianCount); ``` **Returns:** `BackupStatus` | Field | Type | Description | |-------|------|-------------| | `hasArchive` | `boolean` | Whether an encrypted backup exists | | `archiveVersion` | `number \| null` | Archive schema version | | `archiveUpdatedAt` | `number \| null` | Last update timestamp (epoch ms) | | `guardianCount` | `number` | Number of active social recovery guardians | | `prfSupported` | `boolean` | Whether PRF extension is available | ### WalletBackupManager.checkPlatformSupport (static) Check if the current platform supports PRF-based backup. ```typescript const support = await WalletBackupManager.checkPlatformSupport(); console.log('WebAuthn:', support.webauthnSupported); console.log('PRF:', support.prfSupported); if (!support.prfSupported) { showWarning('Your device uses weaker backup encryption'); } ``` --- ## InjectedWalletAdapter ```typescript const adapter = new InjectedWalletAdapter(); const session = await adapter.createSessionFromInjected(sessionConfig); ``` Bridges MetaMask, WalletConnect, and other injected wallets into Veridex session key management. ================================================================================ URL: /api-reference/react ================================================================================ # React SDK API Reference Full API for `@veridex/react` — the React bindings for the Veridex Protocol core SDK. --- ## `VeridexProvider` Context provider that makes the SDK available to all child components. ```tsx ``` **Props:** | Prop | Type | Description | |------|------|-------------| | `sdk` | `VeridexSDK` | SDK instance from `createSDK()` | | `children` | `ReactNode` | Child components | --- ## `useVeridexSDK` ```tsx const sdk: VeridexSDK = useVeridexSDK(); ``` Returns the SDK instance from the nearest `VeridexProvider`. Throws if used outside of a provider. --- ## `useNetwork` ```tsx const { chain, network, chainId } = useNetwork(); ``` **Returns:** | Field | Type | Description | |-------|------|-------------| | `chain` | `string` | Chain name (e.g. `'base'`) | | `network` | `'testnet' \| 'mainnet'` | Network environment | | `chainId` | `number` | Wormhole chain ID | --- ## `usePasskey` ```tsx const { register, authenticate, credential, isRegistering, isAuthenticating, error, } = usePasskey(); ``` **Returns:** | Field | Type | Description | |-------|------|-------------| | `register` | `(username: string, displayName: string) => Promise` | Register a new passkey | | `authenticate` | `() => Promise` | Authenticate with an existing passkey | | `credential` | `Credential \| null` | Current credential | | `isRegistering` | `boolean` | Registration in progress | | `isAuthenticating` | `boolean` | Authentication in progress | | `error` | `Error \| null` | Last error | --- ## `useBalance` ```tsx const { balance, isLoading, error, refresh } = useBalance(tokenAddress?); ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `tokenAddress` | `string?` | Token contract address (optional, defaults to native) | **Returns:** | Field | Type | Description | |-------|------|-------------| | `balance` | `TokenBalance \| null` | Token balance with `formatted`, `symbol`, `decimals` | | `isLoading` | `boolean` | Loading state | | `error` | `Error \| null` | Last error | | `refresh` | `() => void` | Force refresh | --- ## `useTransfer` ```tsx const { prepare, execute, status, receipt, error } = useTransfer(); ``` **Returns:** | Field | Type | Description | |-------|------|-------------| | `prepare` | `(params: TransferParams) => Promise` | Prepare a transfer for review | | `execute` | `() => Promise` | Execute the prepared transfer | | `status` | `string \| null` | Current status (`'preparing'`, `'signing'`, `'confirming'`, `'complete'`) | | `receipt` | `TransferReceipt \| null` | Transaction receipt after completion | | `error` | `Error \| null` | Last error | --- ## `useMultiChainPortfolio` ```tsx const { portfolio, totalValueUSD, isLoading, error } = useMultiChainPortfolio(chainIds?); ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `chainIds` | `number[]?` | Chains to query (default: all configured) | **Returns:** | Field | Type | Description | |-------|------|-------------| | `portfolio` | `PortfolioEntry[]` | Balance per chain | | `totalValueUSD` | `number` | Aggregate USD value | | `isLoading` | `boolean` | Loading state | | `error` | `Error \| null` | Last error | --- ## `useSpendingLimits` ```tsx const { limits, spent, remaining, percentUsed } = useSpendingLimits(); ``` **Returns:** | Field | Type | Description | |-------|------|-------------| | `limits` | `SpendingLimits \| null` | Configured limits | | `spent` | `number` | Amount spent in current period | | `remaining` | `number` | Remaining allowance | | `percentUsed` | `number` | Utilization percentage (0-100) | --- ## Utility Functions ### `resolveChainId` ```typescript resolveChainId(name: string): number ``` Resolve a chain name to its Wormhole chain ID. ### `resolveChainIds` ```typescript resolveChainIds(names: string[]): number[] ``` Resolve multiple chain names. --- Usage examples and setup guide Patterns and best practices ================================================================================ URL: /api-reference/agent-sdk ================================================================================ # Agent SDK Reference Complete API reference for `@veridex/agentic-payments` — the autonomous payment, identity, and reputation layer for AI agents. > Looking for the general-purpose runtime layer? > See the [Agents Framework](/api-reference/agents-framework) page for `@veridex/agents`, React hooks, adapters, OpenClaw interop, and the control plane. ## Installation ```bash npm install @veridex/agentic-payments ``` ## Core: AgentWallet The `AgentWallet` is the central orchestration class. It manages session keys, multi-protocol payment negotiation, on-chain identity (ERC-8004), reputation, trust gates, agent discovery, and monitoring. ### createAgentWallet Factory function that creates and initializes an `AgentWallet`. ```typescript const agent = await createAgentWallet(config); ``` **Parameters:** ```typescript interface AgentWalletConfig { masterCredential: { credentialId: string; publicKeyX: bigint; publicKeyY: bigint; keyHash: string; }; session: { dailyLimitUSD: number; perTransactionLimitUSD: number; expiryHours: number; allowedChains: number[]; // Wormhole chain IDs }; erc8004?: ERC8004Config; relayerUrl?: string; relayerApiKey?: string; x402?: X402ClientConfig; mcp?: { enabled: boolean }; } interface ERC8004Config { enabled: boolean; testnet?: boolean; // Use testnet registry addresses (default: false) registryProvider?: any; // Custom ethers provider for registry chain agentId?: bigint; // Pre-registered agent ID minReputationScore?: number; // Minimum reputation (0-100) for trust gate trustedReviewers?: string[]; // Only count feedback from these addresses } ``` **Example:** ```typescript const agent = await createAgentWallet({ masterCredential: { credentialId: 'abc123', publicKeyX: BigInt('0x...'), publicKeyY: BigInt('0x...'), keyHash: '0x...', }, session: { dailyLimitUSD: 100, perTransactionLimitUSD: 25, expiryHours: 24, allowedChains: [10004], // Base Sepolia }, erc8004: { enabled: true, testnet: true, minReputationScore: 30, }, relayerUrl: 'https://relayer.veridex.network', }); ``` --- ### agent.fetch Make an HTTP request with automatic x402 payment handling. If the server responds with `402 Payment Required`, the agent automatically negotiates and pays. ```typescript const response = await agent.fetch(url, options?); ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `url` | `string` | URL to fetch | | `options` | `RequestInit` | Standard fetch options | **Returns:** `Promise` **Example:** ```typescript // Agent automatically handles 402 Payment Required responses const response = await agent.fetch('https://paid-api.example.com/market-data'); const data = await response.json(); console.log('Market data:', data); ``` --- ### agent.pay Execute a direct token payment. ```typescript const receipt = await agent.pay(params); ``` **Parameters:** ```typescript interface PaymentParams { chain: number; // Wormhole chain ID (e.g., 10004 for Base Sepolia) token: string; // Token symbol ('ETH', 'USDC', 'native') amount: string; // Amount in smallest unit (e.g., '1000000' for 1 USDC) recipient: string; // Recipient address protocol?: string; // Payment protocol (default: 'direct') } ``` **Returns:** ```typescript interface PaymentReceipt { txHash: string; status: 'confirmed' | 'pending' | 'failed'; chain: number; token: string; amount: bigint; recipient: string; protocol: string; timestamp: number; } ``` **Example:** ```typescript const receipt = await agent.pay({ chain: 10004, // Base Sepolia token: 'USDC', amount: '5000000', // 5 USDC (6 decimals) recipient: '0x742d35Cc6634C0532925a3b844Bc9e7595f5A234', }); console.log('Tx:', receipt.txHash); console.log('Status:', receipt.status); ``` --- ### agent.getSessionStatus Get the current session's status including spending limits and usage. ```typescript const status = agent.getSessionStatus(); ``` **Returns:** ```typescript interface SessionStatus { isValid: boolean; keyHash: string; expiry: number; // Unix timestamp remainingDailyLimitUSD: number; totalSpentUSD: number; masterKeyHash: string; address: string; // Session wallet address limits: { dailyLimitUSD: number; perTransactionLimitUSD: number; }; } ``` **Example:** ```typescript const status = agent.getSessionStatus(); console.log('Session valid:', status.isValid); console.log('Spent today:', `$${status.totalSpentUSD.toFixed(2)}`); console.log('Remaining:', `$${status.remainingDailyLimitUSD.toFixed(2)}`); console.log('Wallet:', status.address); ``` --- ### agent.getBalance Get token balances for the session wallet. ```typescript const balances = await agent.getBalance(chain?); ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `chain` | `number` | Wormhole chain ID (default: 30 for Base) | **Returns:** `Promise` **Example:** ```typescript const balances = await agent.getBalance(10004); for (const entry of balances) { console.log(`${entry.token.symbol}: ${entry.formatted}`); } ``` --- ### agent.getMultiChainBalance Get balances across all supported chains. ```typescript const portfolio = await agent.getMultiChainBalance(); ``` **Returns:** `Promise` --- ### agent.getPaymentHistory Get the agent's payment history with optional filtering. ```typescript const history = await agent.getPaymentHistory(options?); ``` **Parameters:** ```typescript interface HistoryOptions { limit?: number; offset?: number; chain?: number; token?: string; since?: number; // Unix timestamp } ``` **Returns:** `Promise` --- ### agent.revokeSession Revoke the current session key. ```typescript await agent.revokeSession(); ``` --- ### agent.exportAuditLog Export the agent's payment audit log for compliance. ```typescript const log = await agent.exportAuditLog(format?); ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `format` | `'json' \| 'csv'` | Export format (default: `'json'`) | **Returns:** `Promise` --- ### agent.onSpendingAlert Register a callback for spending limit alerts. ```typescript agent.onSpendingAlert((alert) => { console.log('Alert:', alert.type, alert.message); }); ``` **Alert types:** | Type | Description | |------|-------------| | `approaching_limit` | Spending is near the daily limit | | `limit_exceeded` | Transaction was blocked by limit | | `session_expiring` | Session key is about to expire | --- ### agent.getMCPTools Get MCP tools for integration with AI models (Gemini, Claude, GPT, Cursor). Each tool has a name, description, input schema, and handler function. This is the primary integration point used by the [veri_agent](/guides/agent-payments) to give Gemini function-calling access to wallet operations. ```typescript const tools = agent.getMCPTools(); ``` **Returns:** `MCPTool[]` ```typescript interface MCPTool { name: string; // e.g., 'veridex_pay', 'veridex_balance' description: string; // Human-readable description inputSchema: { type: 'object'; properties: Record; required?: string[]; }; handler: (args: any) => Promise; // Execute the tool } ``` **Available tools:** | Tool Name | Description | |-----------|-------------| | `veridex_pay` | Execute a token payment (chain, token, amount, recipient) | | `veridex_balance` | Check wallet balance on a specific chain | | `veridex_status` | Get session status and spending limits | | `veridex_history` | Get payment history | **Example — Mapping to Gemini function declarations:** ```typescript const mcpTools = agent.getMCPTools(); const geminiTools = mcpTools.map(tool => ({ declaration: { name: tool.name, description: tool.description, parameters: { type: SchemaType.OBJECT, properties: tool.inputSchema.properties, required: tool.inputSchema.required || [], }, }, execute: async (args: any) => { const result = await tool.handler(args); // Serialize BigInts for JSON compatibility return JSON.parse(JSON.stringify(result, (key, value) => typeof value === 'bigint' ? value.toString() : value )); }, })); ``` **Example — Mapping to OpenAI/Claude tool format:** ```typescript const openaiTools = mcpTools.map(tool => ({ type: 'function' as const, function: { name: tool.name, description: tool.description, parameters: tool.inputSchema, }, })); ``` --- ### agent.importSession Import a session key that was provisioned from a frontend dashboard. This enables the **human-in-the-loop provisioning flow** where a user creates a passkey wallet, configures spending limits, generates a session key, and sends it to the agent backend. ```typescript await agent.importSession(sessionData); ``` **Parameters:** ```typescript interface SessionData { keyHash: string; encryptedPrivateKey: string; publicKey: string; config: { dailyLimitUSD: number; perTransactionLimitUSD: number; expiryTimestamp: number; allowedChains: number[]; }; masterKeyHash: string; } ``` **Example (from veri_agent):** ```typescript // Backend receives session data from frontend app.post('/api/provision', async (req, res) => { const { sessionData } = req.body; const wallet = await getAgentWallet(); await wallet.importSession(sessionData); console.log('Session injected into Wallet.'); res.json({ success: true }); }); ``` --- ## Session Management ### SessionKeyManager Manages session key lifecycle, spending tracking, and limit enforcement. ```typescript const manager = new SessionKeyManager(); // Create a session const session = await manager.createSession(masterCredential, { dailyLimitUSD: 50, perTransactionLimitUSD: 10, expiryTimestamp: Date.now() + 24 * 60 * 60 * 1000, }); // Check limits before payment const check = manager.checkLimits(session, amountUSD); if (check.allowed) { // Execute payment... await manager.recordSpending(session, amountUSD); } // Revoke when done await manager.revokeSession(session.keyHash); ``` --- ## x402 Protocol ### X402Client Handles automatic negotiation of HTTP 402 "Payment Required" responses. ```typescript ``` The x402 flow: 1. Agent makes HTTP request to a paid API 2. Server responds with `402 Payment Required` + `PAYMENT-REQUIRED` header 3. `X402Client` parses payment requirements (amount, recipient, network) 4. Agent signs and submits payment 5. Agent retries request with `PAYMENT-SIGNATURE` header 6. Server verifies payment and returns data ### PaymentParser Parses x402 payment requirement headers. ```typescript const parser = new PaymentParser(); const requirements = parser.parse(response.headers); // { amount, recipient, network, token, ... } ``` --- ## Chain Clients The Agent SDK provides chain-specific clients for multi-chain operations. ### Available Clients ```typescript EVMChainClient, SolanaChainClient, AptosChainClient, SuiChainClient, StarknetChainClient, StacksChainClient, ChainClientFactory, } from '@veridex/agentic-payments'; ``` ### StacksChainClient Stacks-specific client with native STX/sBTC pricing via Pyth. ```typescript const client = new StacksChainClient({ network: 'testnet', }); // Get STX price const stxPrice = await client.getNativeTokenPriceUSD(); // Get vault balance const balance = await client.getVaultBalance(address); ``` ### StacksSpendingTracker USD-aware spending tracker for Stacks with real-time Pyth pricing. ```typescript const tracker = new StacksSpendingTracker({ dailyLimitUSD: 100, network: 'testnet', }); // Convert STX amount to USD const usdValue = await tracker.convertToUSD(1000000n, 'STX'); // Check if payment is within limits const allowed = await tracker.checkPayment(amountMicroSTX); ``` --- ## Oracle ### PythOracle Real-time price feeds from Pyth Network's Hermes API. ```typescript const oracle = new PythOracle(); // Get price by symbol (auto-resolves feed ID) const ethPrice = await oracle.getPrice('ETH'); const stxPrice = await oracle.getPrice('STX'); const btcPrice = await oracle.getPrice('BTC'); // Get price by feed ID const price = await oracle.getPrice(PYTH_FEED_IDS.SOL); // Get native token price for a chain const nativePrice = await oracle.getNativeTokenPrice('stacks'); ``` **Supported symbols:** `ETH`, `BTC`, `SOL`, `STX`, `APT`, `SUI`, `STRK`, `USDC`, `USDT` --- ## Monitoring ### AuditLogger Records all payment activity for compliance and debugging. ```typescript const logger = new AuditLogger(); // Get recent logs const logs = await logger.getLogs({ limit: 50 }); // Filter by chain const baseLogs = await logger.getLogs({ chain: 10004 }); ``` ### AlertManager Monitors spending and triggers alerts. ```typescript const alerts = new AlertManager(); alerts.onAlert((alert) => { if (alert.type === 'approaching_limit') { console.warn('Approaching daily limit!'); } }); ``` ### ComplianceExporter Export audit logs in standard formats. ```typescript const exporter = new ComplianceExporter(); const csv = exporter.exportToCSV(logs); const json = exporter.exportToJSON(logs); ``` --- ## ERC-8004 Identity & Reputation The Agent SDK provides full ERC-8004 integration for on-chain agent identity, reputation, and discovery. ### agent.register Register the agent on-chain by minting an ERC-721 identity NFT. ```typescript const result = await agent.register(options); ``` **Parameters:** ```typescript interface RegisterAgentOptions { name: string; description: string; services?: Array<{ name: string; endpoint: string }>; x402Support?: boolean; ipfsConfig?: { gateway: string; apiKey: string; provider: 'pinata' | 'web3.storage' }; } ``` **Returns:** `Promise<{ agentId: bigint; agentURI: string }>` **Example:** ```typescript const { agentId, agentURI } = await agent.register({ name: 'Sentiment Analyzer', description: 'NLP-powered sentiment analysis for crypto markets', services: [{ name: 'analyze', endpoint: 'https://my-agent.com/analyze' }], x402Support: true, }); console.log(`Registered as agent #${agentId}`); console.log(`URI: ${agentURI}`); ``` --- ### agent.getIdentity Get the on-chain registration for this agent. ```typescript const identity = await agent.getIdentity(); ``` **Returns:** `Promise` --- ### agent.getAgentId Get the stored agent ID (set after `register()` or via config). ```typescript const agentId = agent.getAgentId(); ``` **Returns:** `bigint | undefined` --- ### agent.submitFeedback Submit reputation feedback for another agent. ```typescript await agent.submitFeedback(agentId, options); ``` **Parameters:** ```typescript interface FeedbackOptions { score: number; // 0-100 tags?: string[]; // e.g., ['fast', 'accurate'] comment?: string; feedbackURI?: string; // Optional URI with detailed feedback } ``` **Example:** ```typescript await agent.submitFeedback(42n, { score: 85, tags: ['fast', 'accurate', 'reliable'], comment: 'Excellent sentiment analysis results', }); ``` --- ### agent.getReputation Get the reputation summary for an agent. ```typescript const summary = await agent.getReputation(agentId, trustedReviewers?); ``` **Returns:** `Promise` ```typescript interface FeedbackSummary { feedbackCount: number; normalizedScore: number; // 0-100 tags: string[]; recentFeedback: FeedbackEntry[]; } ``` --- ### agent.getReputationScore Get a normalized reputation score (0-100) for an agent. ```typescript const score = await agent.getReputationScore(agentId, trustedReviewers?); ``` **Returns:** `Promise` — Score from 0 to 100. --- ### agent.discover Discover agents by capability, category, or chain. ```typescript const agents = await agent.discover(query); ``` **Parameters:** ```typescript interface DiscoveryQuery { category?: string; chain?: string; minReputation?: number; tags?: string[]; limit?: number; } ``` **Returns:** `Promise` **Example:** ```typescript const sentimentAgents = await agent.discover({ category: 'sentiment', minReputation: 30, limit: 10, }); for (const a of sentimentAgents) { console.log(`Agent #${a.agentId}: ${a.name}`); } ``` --- ### agent.resolveAgent Resolve an agent from a UAI, endpoint URL, chain address, or search term. ```typescript const resolved = await agent.resolveAgent(input); ``` **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `input` | `string` | UAI, URL, address, or search term | **Returns:** `Promise` ```typescript interface ResolvedAgent { agentId: bigint; name: string; agentURI: string; reputationScore: number; resolvedFrom: 'uai' | 'url' | 'address' | 'search'; } ``` --- ### agent.checkMerchantTrust Check a merchant's reputation before making a payment. ```typescript const trust = await agent.checkMerchantTrust(endpoint); ``` **Returns:** `Promise` ```typescript interface TrustCheckResult { trusted: boolean; score: number; // 0-100 agentId?: bigint; reason?: string; // Why trusted/untrusted } ``` **Example:** ```typescript const trust = await agent.checkMerchantTrust('https://data-provider.com'); if (trust.trusted) { console.log(`Trusted (score: ${trust.score}/100)`); } else { console.log(`Untrusted: ${trust.reason}`); } ``` --- ### agent.getIdentityClient / getReputationClient / getAgentDiscovery Access the underlying modular clients directly. ```typescript const identityClient = agent.getIdentityClient(); const reputationClient = agent.getReputationClient(); const discovery = agent.getAgentDiscovery(); ``` --- ## Identity Clients (Standalone) These clients can be used independently of `AgentWallet`. ### IdentityClient Chain-agnostic client for the ERC-8004 Identity Registry. ```typescript const provider = new ethers.JsonRpcProvider('https://mainnet.base.org'); const signer = new ethers.Wallet(privateKey, provider); const identity = new IdentityClient(provider, signer, { testnet: false }); // Register with auto-built registration file const { agentId, agentURI } = await identity.registerWithFile({ name: 'My Agent', description: 'Does cool things', services: [{ name: 'api', endpoint: 'https://my-agent.com/api' }], }); // Query an agent const agent = await identity.getAgent(agentId); console.log(agent.name, agent.agentURI, agent.agentWallet); // Get agent URI const uri = await identity.getAgentURI(agentId); ``` --- ### ReputationClient Chain-agnostic client for the ERC-8004 Reputation Registry. ```typescript const reputation = new ReputationClient(provider, signer, { testnet: false }); // Submit feedback await reputation.submitFeedback(agentId, { score: 85, tags: ['fast', 'accurate'], }); // Get summary const summary = await reputation.getSummary(agentId, []); // Get normalized score (0-100) const score = await reputation.getReputationScore(agentId); // Revoke feedback await reputation.revokeFeedback(agentId); ``` --- ### RegistrationFileManager Build, validate, and publish ERC-8004 registration files. ```typescript // Build a registration file const file = RegistrationFileManager.buildRegistrationFile({ name: 'My Agent', description: 'Does cool things', services: [{ name: 'api', endpoint: 'https://my-agent.com/api' }], }); // Validate against ERC-8004 schema const { valid, errors } = RegistrationFileManager.validate(file); // Encode as data URI (no external deps) const dataURI = RegistrationFileManager.buildDataURI(file); // Publish to IPFS const manager = new RegistrationFileManager({ ipfs: { gateway: 'https://api.pinata.cloud', apiKey: '...', provider: 'pinata' }, }); const ipfsURI = await manager.publishToIPFS(file); // Fetch from URI const fetched = await RegistrationFileManager.fetchFromURI(ipfsURI); // Add/remove services const updated = RegistrationFileManager.addService(file, { name: 'new-service', endpoint: 'https://my-agent.com/new', }); // Build well-known file for zero-lookup resolution const wellKnown = RegistrationFileManager.buildWellKnownFile(agentId, uai, ipfsURI); // Serve at: /.well-known/agent-registration.json ``` --- ### TrustGate Pre-payment reputation checks. ```typescript const gate = new TrustGate(reputationClient, identityClient, { minReputation: 30, trustedReviewers: ['0x...'], trustModel: 'reputation', mode: 'reject', // 'reject' throws, 'warn' logs }); const result = await gate.check(agentId); // result.trusted, result.score, result.reason ``` --- ### AgentDiscovery Resolve agents from multiple input types. ```typescript const discovery = new AgentDiscovery(identityClient, reputationClient); // Resolve from URL (checks /.well-known/agent-registration.json) const fromUrl = await discovery.resolve('https://my-agent.com'); // Resolve from UAI (CAIP-2 extended format) const fromUai = await discovery.resolve('eip155:8453:0x8004A169...:42'); // Resolve from chain address const fromAddr = await discovery.resolve('0x742d35Cc6634C0532925a3b844Bc9e7595f5A234'); ``` --- ## Protocol Abstraction Layer ### Supported Protocols | Protocol | Creator | Priority | Detection | |----------|---------|----------|-----------| | **UCP** | Google/Shopify | 100 | `.well-known/ucp` manifest or `Link: rel="ucp-manifest"` header | | **ACP** | OpenAI/Stripe | 90 | `openai-acp-version` or `x-acp-checkout-url` header | | **MPP** | Tempo | 85 | `WWW-Authenticate: Payment` header (charge/session intents) | | **AP2** | Google | 80 | `x-ap2-mandate-url` or `x-google-a2a-mandate` header | | **x402** | Coinbase | 70 | HTTP 402 with `PAYMENT-REQUIRED` header | ### ProtocolRegistry Formal capability declarations for intelligent protocol selection. ```typescript const registry = new ProtocolRegistry(); // Find by capabilities const escrowProtocols = registry.findByCapabilities(['escrow', 'refund']); // Find best for requirements const best = registry.findBest({ capabilities: ['one_time_payment', 'cross_chain'], chainId: 10004, token: 'USDC', amountUSD: 5, }); // Find by chain const baseProtocols = registry.findByChain(10004); ``` **Capabilities:** `one_time_payment`, `subscription`, `streaming`, `escrow`, `refund`, `partial_refund`, `prepaid_session`, `multi_token`, `cross_chain`, `gasless`, `eip712_signing`, `reputational_feedback`, `metered_billing`, `mandate_based`. ### PaymentIntent Universal payment normalization — every protocol converts its challenge into a `PaymentIntent`: ```typescript const intent = createPaymentIntent('x402', costEstimate, resourceUrl, { recipient: '0x...', ttlMs: 300_000, description: 'Premium API access', }); // Check expiry if (isIntentExpired(intent)) { /* handle */ } // Convert for policy evaluation const action = intentToProposedAction(intent); ``` **PaymentSchemes:** `exact`, `upto`, `subscription`, `streaming`, `escrow`, `prepaid`. ### SettlementVerifier Confirms payments settled on-chain with pluggable strategies: ```typescript const verifier = new SettlementVerifier({ rpcEndpoints: { 10004: 'https://sepolia.base.org' }, confirmations: 1, timeoutMs: 30_000, }); verifier.registerStrategy(new EVMSettlementStrategy('x402', { 10004: 'https://sepolia.base.org', })); const proof = await verifier.verify(settlement, traceHash); // → { txHash, traceHashInCalldata, chain } ``` --- ## Agent-Safe Execution Control Plane ### Policy Engine Evaluates every proposed action against a versioned mandate before execution. ```typescript PolicyEngine, SpendingLimitRule, VelocityRule, AssetWhitelistRule, ChainWhitelistRule, ProtocolWhitelistRule, CounterpartyRule, TimeWindowRule, HumanApprovalRule, } from '@veridex/agentic-payments'; const engine = new PolicyEngine(); engine.addRule(new SpendingLimitRule()); engine.addRule(new VelocityRule({ maxPerMinute: 5, maxPerHour: 50 })); engine.addRule(new AssetWhitelistRule()); engine.addRule(new ChainWhitelistRule()); const verdict = await engine.evaluate(proposedAction, context); // verdict.verdict: 'allow' | 'deny' | 'escalate' // verdict.reasons: string[] ``` ### Security Firewall ```typescript InjectionDetector, ToolSanitizer, OutputGuard, AnomalyDetector, } from '@veridex/agentic-payments'; // Prompt injection detection const detector = new InjectionDetector(); const result = detector.detect(input); // result.detected, result.matches: [{ category, pattern, matched }] // Tool description sanitization (strips hidden instructions) const sanitizer = new ToolSanitizer(detector); const sanitized = sanitizer.sanitizeToolDescription(tool); // sanitized.safe, sanitized.strippedContent // Tool pinning for rug-pull detection sanitizer.pinToolDescriptions(tools); const validation = sanitizer.validatePins(tools); // validation[i].valid, validation[i].reason // Secret scanning on outputs const guard = new OutputGuard(); const scan = guard.scanForSecrets(text); // scan.found, scan.matches: [{ type, value }] // Anomaly detection const anomaly = new AnomalyDetector({ baselineWindowMs: 7 * 24 * 60 * 60 * 1000, amountStdDevThreshold: 2.5, }); const analysis = anomaly.analyze(action, history, Date.now()); // analysis.detected, analysis.anomalies, analysis.riskScore ``` ### Trace & Evidence Cryptographically verifiable audit trail for every agent decision. ```typescript const interceptor = new TraceInterceptor(storageAdapter); const trace = await interceptor.captureTrace({ traceId: 'unique-id', agentId: 'agent-1', toolCalls: [{ name: 'veridex_pay', args: {}, result: {} }], reasoning: { prompt: '...', response: '...' }, }); const bundle = new EvidenceBundle(trace); const evidence = bundle.build(); ``` ### Storage Adapters | Adapter | Backend | Import | |---------|---------|--------| | `MemoryStorage` | In-memory | `@veridex/agentic-payments` | | `JSONFileStorage` | Local filesystem | `@veridex/agentic-payments` | | `PostgresStorage` | PostgreSQL | `@veridex/agentic-payments` | | `IPFSStorage` | IPFS (Kubo/Pinata/Infura) | `@veridex/agentic-payments` | | `ArweaveStorage` | Arweave permaweb | `@veridex/agentic-payments` | | `FilecoinStorage` | Filecoin Cloud | `@veridex/agentic-payments` | | `StorachaStorage` | Storacha | `@veridex/agentic-payments` | | `AkaveStorage` | Akave | `@veridex/agentic-payments` | ```typescript MemoryStorage, PostgresStorage, FilecoinStorage, StorachaStorage, } from '@veridex/agentic-payments'; // PostgreSQL — works with pg, postgres.js, drizzle, etc. const postgres = new PostgresStorage({ db: pool, // any object with query(sql, params) method tableName: 'veridex_traces', autoCreateTable: true, }); // Filecoin Cloud via @filoz/synapse-sdk const synapse = Synapse.create({ account: privateKeyToAccount('0x...'), source: 'veridex' }); const filecoin = new FilecoinStorage({ client: synapse.storage }); // Storacha via @storacha/client const principal = Signer.parse(process.env.KEY); const client = await Client.create({ principal, store: new StoreMemory() }); const proof = await Proof.parse(process.env.PROOF); const space = await client.addSpace(proof); await client.setCurrentSpace(space.did()); const storacha = new StorachaStorage({ client }); ``` ### Escalation Manager Human-in-the-loop escalation for high-risk actions. ```typescript const escalation = new EscalationManager(5 * 60 * 1000); // 5 min timeout // Create ticket const ticket = escalation.escalate(proposedAction, verdict); // ticket.id, ticket.status: 'pending' // Listen for events escalation.on('escalated', (t) => notifyAdmin(t)); escalation.on('approved', (t) => proceedWithAction(t)); escalation.on('rejected', (t) => abortAction(t)); escalation.on('timeout', (t) => handleTimeout(t)); // Approve or reject escalation.approve(ticket.id, 'admin@company.com'); escalation.reject(ticket.id, 'admin@company.com', 'Too risky'); ``` ### Circuit Breaker Halts agent operations after repeated failures. ```typescript const breaker = new CircuitBreaker({ failureThreshold: 3, resetTimeoutMs: 60_000, }); // Record outcomes breaker.recordSuccess(); breaker.recordFailure(); // Check state before operations if (breaker.isOpen()) { console.warn('Agent is halted'); } // Listen for state changes breaker.on('open', () => alertOps('Agent halted')); breaker.on('half-open', () => log('Testing recovery')); breaker.on('close', () => log('Agent resumed')); ``` --- ## Hardened MCP Server ```typescript const server = new MCPServer(agent, { allowedTools: ['veridex_pay', 'veridex_check_balance'], toolSpendingLimits: { veridex_pay: 10 }, sanitizeDescriptions: true, pinDescriptions: true, scanInputs: true, scanOutputs: true, }); const tools = server.getTools(); ``` **MCPServerConfig:** | Option | Type | Default | Description | |--------|------|---------|-------------| | `allowedTools` | `string[]` | `[]` (all) | Whitelist of tool names | | `toolSpendingLimits` | `Record` | `{}` | Per-tool USD limits | | `sanitizeDescriptions` | `boolean` | `true` | Strip hidden instructions | | `pinDescriptions` | `boolean` | `true` | Detect rug-pulls | | `scanInputs` | `boolean` | `true` | Injection detection | | `scanOutputs` | `boolean` | `true` | Secret scanning | --- ## React Hooks The Agent SDK provides React hooks for frontend integration. ```typescript AgentWalletProvider, useAgentWallet, useAgentWalletContext, usePayment, useSessionStatus, useFetchWithPayment, useCostEstimate, useProtocolDetection, useMultiChainBalance, usePaymentHistory, useSpendingAlerts, useCanPay, } from '@veridex/agentic-payments'; ``` | Hook | Purpose | |------|---------| | `useAgentWallet(config)` | Create and manage an AgentWallet instance | | `useAgentWalletContext()` | Access wallet from context (inside `AgentWalletProvider`) | | `usePayment(wallet)` | Make payments with loading/error state | | `useSessionStatus(wallet)` | Get session status with auto-refresh | | `useFetchWithPayment(wallet)` | Make paid HTTP requests with protocol detection | | `useCostEstimate(wallet, url)` | Estimate cost before paying | | `useProtocolDetection(wallet, url)` | Detect which payment protocol a URL uses | | `useMultiChainBalance(wallet)` | Fetch balances across all chains | | `usePaymentHistory(wallet)` | Get payment history | | `useSpendingAlerts(wallet)` | Subscribe to spending alerts | | `useCanPay(wallet, amountUSD)` | Check if wallet can make a payment | --- ## Error Handling ```typescript try { await agent.pay({ ... }); } catch (error) { if (error instanceof AgentPaymentError) { switch (error.code) { case AgentPaymentErrorCode.LIMIT_EXCEEDED: console.log('Spending limit exceeded:', error.message); break; case AgentPaymentErrorCode.INSUFFICIENT_BALANCE: console.log('Not enough tokens:', error.message); console.log('Suggestion:', error.suggestion); break; case AgentPaymentErrorCode.SESSION_EXPIRED: console.log('Session expired, creating new one...'); break; case AgentPaymentErrorCode.NETWORK_ERROR: if (error.retryable) { console.log('Retryable error, will retry...'); } break; } } } ``` ### Error Codes | Code | Description | Retryable | |------|-------------|-----------| | `LIMIT_EXCEEDED` | Transaction exceeds spending limit | No | | `INSUFFICIENT_BALANCE` | Not enough tokens in wallet | No | | `SESSION_EXPIRED` | Session key has expired | No | | `CHAIN_NOT_SUPPORTED` | Chain ID not configured | No | | `TOKEN_NOT_SUPPORTED` | Token not available on chain | No | | `NETWORK_ERROR` | Network or RPC failure | Yes | | `X402_PAYMENT_FAILED` | x402 payment negotiation failed | Yes | | `SIGNATURE_FAILED` | Transaction signing failed | No | --- ## Full Example: Autonomous Data Agent ```typescript async function runDataAgent() { // 1. Initialize agent with spending limits const agent = await createAgentWallet({ masterCredential: { credentialId: process.env.CREDENTIAL_ID!, publicKeyX: BigInt(process.env.PUBLIC_KEY_X!), publicKeyY: BigInt(process.env.PUBLIC_KEY_Y!), keyHash: process.env.KEY_HASH!, }, session: { dailyLimitUSD: 50, perTransactionLimitUSD: 5, expiryHours: 8, allowedChains: [10004], }, }); // 2. Set up spending alerts agent.onSpendingAlert((alert) => { console.warn(`[ALERT] ${alert.type}: ${alert.message}`); }); // 3. Fetch paid data (x402 handled automatically) const priceData = await agent.fetch('https://data-provider.example.com/api/v1/prices'); const prices = await priceData.json(); console.log('Fetched price data:', prices); // 4. Make a direct payment const receipt = await agent.pay({ chain: 10004, token: 'USDC', amount: '1000000', recipient: '0x...', }); console.log('Payment confirmed:', receipt.txHash); // 5. Check session status const status = agent.getSessionStatus(); console.log(`Spent: $${status.totalSpentUSD} / $${status.limits!.dailyLimitUSD}`); // 6. Export audit log const auditLog = await agent.exportAuditLog('json'); console.log('Audit:', auditLog); // 7. Clean up await agent.revokeSession(); } runDataAgent().catch(console.error); ``` ================================================================================ URL: /api-reference/agent-security ================================================================================ # Agent Security API Reference Full API for `@veridex/agent-security` — the framework-agnostic security gateway for AI agents. --- ## `SecurityGateway` In-process security evaluation gateway. ```typescript const gateway = new SecurityGateway(options); ``` **Constructor options:** | Option | Type | Description | |--------|------|-------------| | `packs` | `SecurityPack[]` | Security packs to evaluate | | `defaultAction` | `'block' \| 'warn' \| 'allow'` | Default verdict when no pack matches | | `telemetry` | `TelemetryReporter?` | Optional telemetry sink | ### `gateway.evaluate(action)` Evaluate an agent action against all registered packs. ```typescript const result = await gateway.evaluate(action); ``` **Parameters:** ```typescript interface SecurityAction { type: 'tool_call' | 'model_response' | 'handoff' | 'output'; toolName?: string; arguments?: Record; response?: string; agentId: string; turnIndex?: number; metadata?: Record; } ``` **Returns:** `SecurityEvalResult` ```typescript interface SecurityEvalResult { overall: 'allow' | 'block' | 'escalate'; verdicts: SecurityVerdict[]; metadata: Record; } ``` --- ## `SecurityClient` HTTP client for a remote SecurityGateway server. ```typescript const client = new SecurityClient({ baseUrl: string, authToken?: string, timeoutMs?: number, }); const result = await client.evaluate(action); ``` --- ## `TelemetryReporter` Batched telemetry reporting for security evaluations. ```typescript const reporter = new TelemetryReporter({ endpoint: string, batchSize?: number, // default: 50 flushIntervalMs?: number, // default: 10_000 }); ``` --- ## Security Packs ### `createDefaultPacks()` Returns all 12 built-in packs with default configuration. ```typescript const packs: SecurityPack[] = createDefaultPacks(); ``` ### Individual Packs | Function | Returns | |----------|---------| | `injectionDetectionPack(options?)` | Pack detecting prompt/SQL/command injection | | `toolPoisoningPack(options?)` | Pack detecting malicious tool chains | | `secretDetectionPack(options?)` | Pack detecting leaked secrets | | `budgetCeilingPack(options?)` | Pack enforcing spend limits | | `shellCommandSafetyPack(options?)` | Pack blocking dangerous shell commands | | `endpointAllowlistPack(options?)` | Pack restricting HTTP endpoints | | `handoffSafetyPack(options?)` | Pack validating agent handoffs | | `financialSafetyPack(options?)` | Pack enforcing financial limits | | `crossTurnAnomalyPack(options?)` | Pack detecting behavioral anomalies | | `llmResponseGuardPack(options?)` | Pack filtering model responses | | `dataSovereigntyPack(options)` | Pack enforcing jurisdictional compliance | ### `SecurityPack` Interface ```typescript interface SecurityPack { id: string; name: string; description: string; evaluate(action: SecurityAction, context?: EvalContext): Promise; } ``` --- ## `dataSovereigntyPack` Options ```typescript interface DataSovereigntyPackOptions { defaultJurisdiction: string; piiCategories: string[]; jurisdictionRules: JurisdictionRule[]; toolJurisdictions: Record; } interface JurisdictionRule { from: string; to: string; verdict: 'block' | 'flag' | 'allow'; reason: string; regulations: string[]; } ``` --- ## Types ### `SecurityVerdict` ```typescript interface SecurityVerdict { verdict: 'allow' | 'block' | 'escalate' | 'flag'; reasons: string[]; packId: string; confidence: number; // 0.0 – 1.0 } ``` ### `SecurityCheckResult` ```typescript interface SecurityCheckResult { passed: boolean; pack: string; message?: string; } ``` ### `ToolCallAction` ```typescript interface ToolCallAction extends SecurityAction { type: 'tool_call'; toolName: string; arguments: Record; } ``` --- ## Server ### `createSecurityServer` Deploy the gateway as a standalone HTTP service (Hono-based). ```typescript const server = createSecurityServer({ packs: SecurityPack[], port?: number, authToken?: string, }); await server.start(); ``` **Endpoints:** | Method | Path | Description | |--------|------|-------------| | `POST` | `/evaluate` | Evaluate a security action | | `GET` | `/health` | Health check | | `GET` | `/packs` | List registered packs | --- ## Framework Adapters ### LangChain ```typescript const callback = new VeridexSecurityCallback(gateway); ``` Implements LangChain's `CallbackHandler` interface. ### CrewAI ```typescript const guard = new VeridexCrewAIGuard(gateway); const result = await guard.evaluate(crewAction); ``` --- Usage guide with examples Deployment patterns and best practices ================================================================================ URL: /api-reference/agents-framework ================================================================================ # Agents Framework API Reference This page documents every public API in the Veridex Agent Fabric package family. If you want a narrative guide instead, see [Veridex Agents](/guides/veridex-agents). > **Note:** The framework is implemented and tested. Some APIs may still evolve. If something is unclear or broken, please open an issue. ## Install ```bash npm install @veridex/agents zod ``` Optional companion packages: ```bash npm install @veridex/agents-react @veridex/agents-adapters @veridex/agents-openclaw @veridex/agents-control-plane ``` --- ## `@veridex/agents` — Core Runtime ### `createAgent(definition, options?)` Factory function that returns an `AgentRuntime`. ```ts const agent = createAgent( { id: 'my-agent', name: 'My Agent', model: { provider: 'openai', model: 'gpt-4o-mini' }, instructions: 'You are a helpful assistant.', tools: [], maxTurns: 10, }, { modelProviders: { openai: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }), }, enableTracing: true, enableCheckpoints: true, }, ); ``` **`AgentDefinition`** ```ts interface AgentDefinition { id: string; name: string; model: ModelConfig; instructions: string; tools?: ToolContract[]; maxTurns?: number; // default: 10 handoffs?: HandoffHandler[]; payment?: PaymentConfig; metadata?: Record; } interface ModelConfig { provider: string; // 'openai' | 'anthropic' | custom model: string; // e.g. 'gpt-4o', 'claude-sonnet-4-20250514' fallback?: ModelConfig; temperature?: number; } ``` **`RuntimeOptions`** ```ts interface RuntimeOptions { modelProviders?: Record; veridexSDK?: unknown; // optional @veridex/sdk instance agentWallet?: unknown; // optional @veridex/agentic-payments wallet enableTracing?: boolean; enableCheckpoints?: boolean; } ``` --- ### `AgentRuntime` The runtime instance returned by `createAgent`. Exposes every subsystem as a readonly property. | Property | Type | Description | |----------|------|-------------| | `definition` | `AgentDefinition` | The agent config passed at creation | | `events` | `EventBus` | Typed event emitter | | `eventLog` | `EventLog` | Immutable log of all emitted events | | `models` | `ModelRegistry` | Routes model calls to providers | | `tools` | `ToolRegistry` | Registers and looks up tool contracts | | `hooks` | `HookRegistry` | Lifecycle hook registry | | `context` | `ContextCompiler` | Token-bounded context builder | | `memory` | `MemoryManager` | Multi-tier memory (working, episodic, semantic, procedural) | | `checkpoints` | `CheckpointManager` | Run state snapshots | | `policyEngine` | `PolicyEngine` | Rule evaluation engine | | `approvals` | `ApprovalManager` | Approval routing and resolution | | `audit` | `AuditEmitter` | Audit bundle recording | **Key methods:** ```ts // Start a run const result = await agent.run('What is the status of order ORD-1042?'); // result shape interface RunResult { output: string; state: RunState; // 'completed' | 'failed' | 'suspended' turns: Turn[]; runId: string; } type RunState = 'pending' | 'running' | 'completed' | 'failed' | 'suspended' | 'cancelled'; ``` --- ### `tool(options)` Creates a typed, validated tool contract. Every tool has a `safetyClass` that policies and approvals use to decide whether execution requires review. ```ts const searchOrders = tool({ name: 'search_orders', description: 'Search orders by customer email.', input: z.object({ email: z.string().email(), limit: z.number().int().min(1).max(100).default(10), }), safetyClass: 'read', timeoutMs: 5000, retries: 2, idempotent: true, async execute({ input }) { const orders = await db.orders.findByEmail(input.email, input.limit); return { success: true, llmOutput: `Found ${orders.length} orders.`, data: orders, }; }, }); ``` **`ToolContract` interface:** ```ts interface ToolContract { name: string; description: string; input: z.ZodType | Record; output?: z.ZodType | Record; safetyClass: ToolSafetyClass; execute: (params: ToolExecuteParams) => Promise; timeoutMs?: number; retries?: number; idempotent?: boolean; permissions?: string[]; costEstimate?: (input: TInput) => CostEstimate; metadata?: Record; } type ToolSafetyClass = 'read' | 'write' | 'network' | 'financial' | 'destructive' | 'privileged'; interface ToolResult { success: boolean; llmOutput: string; data?: unknown; error?: string; metadata?: Record; } ``` **Safety classes explained:** | Class | Meaning | Default policy | |-------|---------|----------------| | `read` | Side-effect-free lookups | Auto-allow | | `write` | Mutations to internal state | Allow with logging | | `network` | External HTTP/RPC calls | Allow with logging | | `financial` | Payments or balance changes | Require approval | | `destructive` | Deletes or irreversible actions | Require approval | | `privileged` | Admin-level operations | Block by default | --- ### `ToolRegistry` Registers, looks up, and enumerates tool contracts. ```ts const registry = agent.tools; registry.register(searchOrders); registry.get('search_orders'); // ToolContract | undefined registry.has('search_orders'); // boolean registry.list(); // ToolContract[] registry.remove('search_orders'); // boolean ``` ### `ToolExecutor` Executes tool calls with validation, timeout, and retry logic. ### `ToolValidator` Validates tool input against a Zod schema or JSON schema before execution. --- ### `PolicyEngine` Registers and evaluates policy rules against action proposals. Rules are evaluated in priority order and produce `allow`, `deny`, or `escalate` verdicts. ```ts PolicyEngine, blockSafetyClasses, requireApprovalFor, maxRunSpendUSD, maxTokenBudget, allowChains, } from '@veridex/agents'; const engine = agent.policyEngine; // Block all destructive and privileged tools engine.register(blockSafetyClasses(['destructive', 'privileged'], agent.tools.list())); // Require human approval for financial tools engine.register(requireApprovalFor(['financial'], agent.tools.list())); // Cap spend and token usage per run engine.register(maxRunSpendUSD(50)); engine.register(maxTokenBudget(100_000)); // Restrict to specific chains engine.register(allowChains(['base', 'optimism'])); ``` **Custom rules:** ```ts interface PolicyRule { id: string; name: string; description: string; priority?: number; // lower = evaluated first evaluate(ctx: PolicyContext): PolicyCheckResult | Promise; } interface PolicyCheckResult { verdict: 'allow' | 'deny' | 'escalate'; reason?: string; metadata?: Record; } ``` **Methods:** | Method | Signature | Description | |--------|-----------|-------------| | `register` | `(rule: PolicyRule) => void` | Add a rule | | `remove` | `(ruleId: string) => boolean` | Remove a rule by id | | `evaluate` | `(ctx: PolicyContext) => Promise` | Evaluate all rules | --- ### `ApprovalManager` Routes proposals to the correct approval mode and manages pending requests. ```ts const approvals = agent.approvals; // Add approval routes approvals.addRoute({ match: (proposal, policy) => policy.verdict === 'escalate', mode: 'human_required', timeoutMs: 300_000, // 5 minutes }); // Register handlers approvals.registerHandler('human_required', async (request) => { // send to Slack, email, or dashboard return { approved: true, decidedBy: 'ops-team' }; }); // Inspect pending requests const pending = approvals.getPending(); // Resolve a request programmatically approvals.resolve(requestId, true, 'admin@company.com', 'Looks safe.'); ``` **Types:** ```ts type ApprovalMode = | 'auto_allow' | 'auto_block' | 'human_required' | 'dual_approval' | 'policy_pack'; interface ApprovalRequest { id: string; runId: string; agentId: string; turnIndex: number; proposal: ActionProposal; policyDecision: PolicyDecision; mode: ApprovalMode; reason?: string; createdAt: number; expiresAt?: number; } interface ApprovalDecision { requestId: string; approved: boolean; decidedBy: string; reason?: string; decidedAt: number; conditions?: Record; } ``` **Methods:** | Method | Signature | Description | |--------|-----------|-------------| | `addRoute` | `(route: ApprovalRoute) => void` | Add a routing rule | | `registerHandler` | `(mode, handler) => void` | Register an approval handler | | `determineMode` | `(proposal, decision) => ApprovalMode` | Resolve the approval mode | | `requestApproval` | `(runId, agentId, turnIndex, proposal, decision) => Promise` | Request and wait for approval | | `startApproval` | `(runId, agentId, turnIndex, proposal, decision, options?) => Promise` | Start without blocking | | `getPending` | `() => ApprovalRequest[]` | List all pending | | `getPendingById` | `(id) => ApprovalRequest \| undefined` | Get a specific request | | `resolve` | `(requestId, approved, decidedBy, reason?) => ApprovalDecision \| null` | Resolve a pending request | | `getDecisions` | `() => ApprovalDecision[]` | List all decisions | --- ### `MemoryManager` and `InMemoryStore` Multi-tier memory system. Tiers: `working` (run-scoped), `episodic` (session-scoped), `semantic` (global), `procedural` (tagged routines). ```ts const memory = agent.memory; // Write a memory entry await memory.write({ tier: 'episodic', content: 'User prefers email notifications over SMS.', scope: 'session', tags: ['preferences'], confidence: 0.9, ttlMs: 86_400_000, // 24 hours }); // Search memory const results = await memory.search({ query: 'notification preference', tiers: ['episodic', 'semantic'], limit: 5, }); // Compact old entries const { removedCount, retainedCount } = await memory.compact(); ``` **Types:** ```ts type MemoryTier = 'working' | 'episodic' | 'semantic' | 'procedural'; type MemoryScope = 'run' | 'session' | 'global'; interface MemoryEntry { id: string; tier: MemoryTier; content: string; scope?: MemoryScope; confidence: number; tags: string[]; ttlMs: number; createdAt: number; lastAccessedAt: number; } ``` **Methods:** | Method | Signature | Description | |--------|-----------|-------------| | `read` | `(id) => Promise` | Read by id | | `write` | `(request) => Promise` | Write an entry | | `search` | `(query) => Promise` | Search by query and tier | | `delete` | `(id) => Promise` | Delete by id | | `getWorkingMemory` | `(runId?) => Promise` | Get working-tier entries | | `getEpisodicMemory` | `(limit?) => Promise` | Get episodic entries | | `getSemanticMemory` | `(query?, limit?) => Promise` | Get semantic entries | | `getProceduralMemory` | `(tags?) => Promise` | Get routine entries | | `compact` | `() => Promise` | Remove expired entries | | `clear` | `(scope?) => Promise` | Clear entries by scope | --- ### `CheckpointManager` Saves and restores run state so agents can suspend (e.g., for approval) and resume later. ```ts const cp = agent.checkpoints; // Save a checkpoint await cp.save(runId, agentId, turnIndex, stateObject, 'approval'); // Restore the latest checkpoint const latest = await cp.loadLatest(runId); if (latest) { const state = cp.parseState(latest); // resume the run from this state } // List all checkpoints for a run const all = await cp.list(runId); ``` **Types:** ```ts interface Checkpoint { id: string; runId: string; agentId: string; turnIndex: number; state: string; // JSON-serialized run state createdAt: number; reason: 'suspend' | 'approval' | 'handoff' | 'periodic' | 'error'; metadata?: Record; } ``` **Methods:** | Method | Signature | Description | |--------|-----------|-------------| | `save` | `(runId, agentId, turnIndex, state, reason?, metadata?) => Promise` | Create checkpoint | | `load` | `(id) => Promise` | Load by id | | `loadLatest` | `(runId) => Promise` | Load most recent for a run | | `list` | `(runId) => Promise` | List all for a run | | `delete` | `(id) => Promise` | Remove a checkpoint | | `clear` | `(runId?) => Promise` | Clear checkpoints | | `parseState` | `(checkpoint) => Record` | Deserialize checkpoint state | --- ### `ContextCompiler` Builds token-bounded context plans. Sections are prioritized and truncated to fit within the model's context window. ```ts const compiler = agent.context; const plan = compiler.compile({ maxTokens: 4096, sections: [ { id: 'instructions', content: agent.definition.instructions, priority: 1 }, { id: 'memory', content: memoryText, priority: 2 }, { id: 'history', content: conversationHistory, priority: 3, truncation: 'tail' }, ], }); // plan.totalTokens — actual tokens used // plan.hadTruncation — true if any section was truncated ``` **Types:** ```ts interface ContextCompilerOptions { maxTokens: number; sections: ContextSectionInput[]; } interface ContextSectionInput { id: string; content: string; priority: number; // lower = more important truncation?: TruncationStrategy; // 'head' | 'tail' | 'middle' | 'none' } interface ContextPlan { sections: ContextSection[]; totalTokens: number; budgetTokens: number; hadTruncation: boolean; compiledAt: number; } ``` --- ### `EventBus` and `EventLog` Typed event emitter for runtime trace events. Every runtime action emits events that can be observed for logging, UI, or audit. ```ts const bus = agent.events; // Listen for specific events const unsub = bus.on('tool_started', (event) => { console.log(`Tool ${event.toolName} started at turn ${event.turnIndex}`); }); // Listen for all events bus.onAny((event) => { console.log(event.type, event.timestamp); }); // One-shot listener bus.once('run_completed', (event) => { console.log('Run finished:', event.runId); }); // Access the full immutable log const log = agent.eventLog; const allEvents = log.getAll(); const toolEvents = log.getByType('tool_completed'); ``` **Event types:** ```ts type TraceEventType = | 'run_started' | 'run_completed' | 'run_failed' | 'run_suspended' | 'turn_started' | 'turn_completed' | 'tool_started' | 'tool_completed' | 'tool_failed' | 'model_called' | 'model_responded' | 'policy_evaluated' | 'approval_requested' | 'approval_resolved' | 'memory_written' | 'checkpoint_saved' | 'handoff_initiated' | 'handoff_completed'; ``` **Methods:** | Method | Signature | Description | |--------|-----------|-------------| | `on` | `(type, handler) => () => void` | Subscribe (returns unsubscribe fn) | | `onAny` | `(handler) => () => void` | Subscribe to all events | | `off` | `(type, handler) => void` | Unsubscribe | | `once` | `(type, handler) => () => void` | One-shot listener | | `emit` | `(event) => void` | Emit an event | | `clear` | `() => void` | Remove all listeners | | `listenerCount` | `(type?) => number` | Count active listeners | --- ### `HookRegistry` and `HookRunner` Lifecycle hooks run at specific phases of the runtime loop. Hooks can annotate context, override execution flow, or inject side effects. ```ts agent.hooks.register({ name: 'log-tool-calls', phase: 'before_tool', priority: 10, async execute(context) { console.log('About to call tool:', context.toolName); }, }); agent.hooks.register({ name: 'block-after-hours', phase: 'before_run', async execute() { const hour = new Date().getHours(); if (hour < 6 || hour > 22) { return { override: 'deny', reason: 'Outside business hours' }; } }, }); ``` **Types:** ```ts type HookPhase = | 'before_run' | 'after_run' | 'before_turn' | 'after_turn' | 'before_tool' | 'after_tool' | 'on_error'; interface RuntimeHook { name: string; phase: HookPhase; priority?: number; // lower = runs first timeoutMs?: number; execute(context: unknown): Promise | HookResult | void; } interface HookResult { annotations?: Record; override?: 'continue' | 'retry' | 'pause' | 'deny'; reason?: string; } ``` **HookRegistry methods:** | Method | Signature | Description | |--------|-----------|-------------| | `register` | `(hook: RuntimeHook) => void` | Add a hook | | `getForPhase` | `(phase) => RuntimeHook[]` | Get hooks for a phase | | `remove` | `(name) => boolean` | Remove by name | | `list` | `() => string[]` | List registered hook names | | `clear` | `() => void` | Remove all hooks | --- ### Model Providers #### `OpenAIProvider` ```ts const provider = new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY, organization: 'org-xxx', // optional baseUrl: 'https://api.openai.com/v1', // optional }); const response = await provider.complete( [ { role: 'system', content: 'You are a helpful assistant.' }, { role: 'user', content: 'Hello!' }, ], { temperature: 0.7, maxTokens: 1024, tools: [searchOrders], // optional tool contracts }, ); ``` #### `AnthropicProvider` ```ts const provider = new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY, }); ``` Both providers implement: ```ts interface ModelProvider { complete( messages: ModelMessage[], options?: ModelCompletionOptions, ): Promise; } interface ModelMessage { role: 'user' | 'assistant' | 'system'; content: string; } interface ModelResponse { content: string; toolCalls?: Array<{ id: string; name: string; arguments: unknown }>; usage: ModelUsage; finishReason: string; } interface ModelUsage { promptTokens: number; completionTokens: number; totalTokens: number; } ``` #### `ModelRegistry` Routes model calls to the correct provider based on `ModelConfig.provider`. --- ### `AuditEmitter` Records tool executions, policy decisions, and approvals into audit bundles for compliance. ```ts interface AuditBundle { id: string; runId: string; agentId: string; startedAt: number; completedAt?: number; toolExecutions: ToolExecutionSummary[]; policies: Record; approvals: Record; } type AuditExportFormat = 'json' | 'jsonl' | 'csv'; ``` --- ### Transports Three transport surfaces for inter-system communication: #### `MCPServerTransport` — Model Context Protocol Exposes agent tools as an MCP-compatible server so external LLM hosts can discover and call them. ```ts const mcp = new MCPServerTransport({ serverName: 'my-agent-mcp', serverVersion: '1.0.0', tools: agent.tools.list(), }); await mcp.connect(); ``` #### `ACPTransport` — Agent Communication Protocol Enables agent-to-agent task delegation via ACP. ```ts const acp = new ACPTransport({ agentCard: { name: 'my-agent', description: 'Handles order lookups', capabilities: [ { name: 'lookup_order', description: 'Find orders', inputSchema: {} }, ], }, }); await acp.connect(); ``` #### `A2ATransport` — Agent-to-Agent Direct inter-agent calls with discovery via agent cards. ```ts const a2a = new A2ATransport({ agentCard: { name: 'coordinator', description: 'Coordinates between specialist agents', capabilities: [], }, }); await a2a.connect(); ``` **Common transport interface:** ```ts interface Transport { connect(): Promise; disconnect(): Promise; send(request: unknown): Promise; } type TransportState = 'disconnected' | 'connecting' | 'connected' | 'error'; ``` --- ### Testing utilities #### `ReplayProvider` Replays recorded model interactions for deterministic, offline tests. ```ts // Record interactions during a live run const recorder = new TraceRecorder(); // ... run agent with recorder attached ... const trace = recorder.getTrace(); // Replay later const replay = new ReplayProvider(trace.interactions); const response = await replay.complete(messages); ``` #### `compareTraces(trace1, trace2)` Compares two golden traces and returns a similarity score and diffs. ```ts const result = compareTraces(baseline, current); console.log(result.identical); // boolean console.log(result.similarityScore); // 0..1 console.log(result.diffs); // EventDiff[] ``` --- ### Action proposals The runtime interprets every model response as a typed proposal: ```ts type ActionProposal = | { type: 'tool_call'; toolName: string; arguments: unknown } | { type: 'handoff'; targetAgent: string; message: string } | { type: 'memory_write'; tier: MemoryTier; content: string } | { type: 'final_answer'; content: string } | { type: 'approval_request'; reason: string }; ``` --- ## `@veridex/agents-react` React bindings for the agent runtime. Provides a context provider and hooks that manage subscriptions, state, and lifecycle automatically. ### `AgentProvider` Wraps your React tree and provides the runtime context to all hooks. ```tsx function App() { return ( ); } ``` ### Hooks #### `useAgent()` Returns the agent definition and current state. ```ts const { definition, state } = useAgent(); ``` #### `useRun()` Start runs and observe their state. ```ts const { run, state, result, error, isLoading } = useRun(); // Start a run await run('What is order ORD-1042?'); // state: 'pending' | 'running' | 'completed' | 'failed' | 'suspended' ``` **Return type:** ```ts interface UseRunReturn { run: (input: string, options?: RunOptions) => Promise; state: RunState; result?: RunResult; error?: Error; isLoading: boolean; } ``` #### `useTurnStream()` Stream individual turns as they happen. ```ts const { stream, cancel } = useTurnStream(); ``` #### `useApprovals()` List pending approvals and resolve them from the UI. ```tsx const { pending, resolve } = useApprovals(); // Render approval inbox {pending.map(req => ( {req.proposal.toolName} needs approval resolve(req.id, true)}>Approve resolve(req.id, false)}>Deny ))} ``` **Return type:** ```ts interface UseApprovalsReturn { pending: ApprovalRequest[]; resolve: (requestId: string, approved: boolean) => Promise; } ``` #### `useTrace()` Observe runtime trace events in real time. ```ts const { events, latest } = useTrace(); ``` #### `useMemory()` Read and write agent memory from React components. ```ts const { entries, write, search, clear } = useMemory(); await write({ tier: 'episodic', content: 'User prefers dark mode.', tags: ['preferences'], }); ``` #### `useBudget()` Track spend against the run budget. ```ts const { snapshot, estimateSpend } = useBudget(); // snapshot.spent, snapshot.budget, snapshot.percentUtilized ``` **Return type:** ```ts interface BudgetSnapshot { spent: number; budget: number; percentUtilized: number; } ``` #### `useContextPlan()` Inspect the current context plan (sections, tokens, truncation). ```ts const { plan, recompile } = useContextPlan(); ``` #### `useToolRegistry()` Register and inspect tools from React. ```ts const { tools, register, remove } = useToolRegistry(); ``` #### `usePayments()` Access `@veridex/agentic-payments` wallet functions when wired. ```ts const { balance, send, history } = usePayments(); ``` #### `useAgentIdentity()` Read the agent's on-chain identity (ERC-8004). ```ts const { identity, isVerified } = useAgentIdentity(); ``` #### `useAgentReputation()` Read the agent's on-chain reputation score. ```ts const { score, history } = useAgentReputation(); ``` #### `useOpenClawBridge()` Bridge to OpenClaw/Pi capabilities from React. ```ts const { importSkills, translateSession } = useOpenClawBridge(); ``` --- ## `@veridex/agents-adapters` Import, export, and live-bridge agents between Veridex and other frameworks. ### Adapter interface All adapters implement: ```ts interface FrameworkAdapter { readonly name: string; readonly framework: string; readonly frameworkVersion: string; importAgent(config: TImport): ImportResult; exportAgent(definition: AgentDefinition): ExportResult; importTool(toolDef: unknown): ToolContract; } ``` ### `OpenAIAgentsAdapter` ```ts const adapter = new OpenAIAgentsAdapter(); // Import an OpenAI agent config into Veridex const { definition, warnings } = adapter.importAgent({ name: 'My OpenAI Agent', instructions: 'Help users with billing.', model: 'gpt-4o', tools: [{ type: 'function', function: { name: 'lookup', ... } }], }); // Export a Veridex definition back to OpenAI format const { config, warnings: exportWarnings } = adapter.exportAgent(myAgent.definition); ``` ### `LangGraphAdapter` ```ts const adapter = new LangGraphAdapter(); const { definition } = adapter.importAgent(langGraphConfig); ``` ### `PydanticAIAdapter` ```ts const adapter = new PydanticAIAdapter(); const { definition } = adapter.importAgent(pydanticAIConfig); ``` ### `OpenAPIImporter` Converts an OpenAPI spec into Veridex tool contracts, so you can expose REST APIs as agent tools. ```ts const importer = new OpenAPIImporter(); const tools = importer.importTools(openAPISpec, { baseUrl: 'https://api.example.com', auth: { type: 'bearer', value: process.env.API_TOKEN }, }); // Each path+method becomes a ToolContract ``` **Options:** ```ts interface OpenAPIImportOptions { baseUrl?: string; auth?: { type: 'bearer' | 'api-key'; value: string }; toolNameTransform?: (path: string, method: string) => string; } ``` ### Runtime bridges Runtime bridges invoke external agent runtimes live, wrapping them as Veridex tools or handoff targets. ```ts const bridge = new OpenAIRuntimeBridge({ name: 'billing-assistant', endpoint: 'https://api.openai.com/v1/agents/asst_xxx', apiKey: process.env.OPENAI_API_KEY, }); // Use as a Veridex tool const tool = bridge.asTool({ name: 'ask_billing_agent' }); // Use as a handoff target const handoff = bridge.asHandoffHandler(); // Or invoke directly const result = await bridge.invoke({ prompt: 'What is the refund policy?', context: { customerId: 'C-123' }, }); ``` **Interface:** ```ts interface ExecutableAgentBridge { readonly name: string; invoke(input: BridgeInvocation): Promise; asTool(options?: BridgeToolOptions): ToolContract; asHandoffHandler(): HandoffHandler; } interface BridgeInvocation { prompt: string; history?: unknown[]; context?: Record; threadId?: string; signal?: AbortSignal; } interface BridgeInvocationResult { success: boolean; output: string; error?: string; metadata?: Record; } ``` ### Utilities | Function | Signature | Description | |----------|-----------|-------------| | `inferSafetyClass` | `(name, description?) => ToolSafetyClass` | Guess safety class from tool name/description | | `jsonSchemaToZod` | `(schema) => z.ZodType` | Convert JSON Schema to Zod | | `createStubExecutor` | `(framework, name) => ToolExecutor` | Create a placeholder executor | --- ## `@veridex/agents-openclaw` Bridge for OpenClaw / Pi agent ecosystems. Imports context files and skills into Veridex, translates sessions, and creates ACP-compatible agent cards. ### Context file parsing Import OpenClaw workspace context files (`AGENTS.md`, `IDENTITY.md`, `TOOLS.md`, etc.) into structured Veridex context sections. ```ts const files = { 'AGENTS.md': agentsMdContent, 'IDENTITY.md': identityMdContent, 'TOOLS.md': toolsMdContent, }; const contextFiles = parseWorkspaceContextFiles(files); const compiled = compileContextSections(contextFiles); // compiled is Record keyed by context type ``` **Individual functions:** | Function | Signature | Description | |----------|-----------|-------------| | `detectContextFileType` | `(filename) => ContextFileType \| undefined` | Detect type from filename | | `parseMarkdownSections` | `(content) => ContextSection[]` | Parse markdown into sections | | `parseContextFile` | `(filename, content) => ContextFile \| null` | Parse a single context file | | `parseWorkspaceContextFiles` | `(files) => ContextFile[]` | Parse a workspace directory | | `compileContextSections` | `(files) => Record` | Compile into text blocks | **Recognized context types:** ```ts type ContextFileType = | 'AGENTS' | 'BOOTSTRAP' | 'HEARTBEAT' | 'IDENTITY' | 'MEMORY' | 'USER' | 'SOUL' | 'TOOLS' | 'RULES' | 'KNOWLEDGE'; ``` ### Skill importer Import OpenClaw `SKILL.md` documents and skill manifests into Veridex tool contracts. ```ts // Parse a SKILL.md file const doc = parseSkillDocument('web-search/SKILL.md', skillMdContent); // doc.frontmatter — parsed YAML frontmatter // doc.instructions — the markdown body // doc.frontmatter.tools — skill definitions // Import into a Veridex tool contract const { tool, warnings } = importSkillDocument(doc); // Import a full manifest of skills const { tools, warnings: allWarnings } = importSkillManifest(manifest); ``` **Types:** ```ts interface OpenClawSkill { id: string; name: string; description?: string; inputSchema?: Record; outputSchema?: Record; tags?: string[]; mutates?: boolean; requiresNetwork?: boolean; requiresApproval?: boolean; timeoutSeconds?: number; } interface SkillImportResult { tool: ToolContract; warnings: string[]; } ``` ### ACP bridge Convert Veridex agent definitions to ACP agent cards, import ACP capabilities as tools, and create remote-agent tool contracts. ```ts // Export your agent as an ACP card const card = toACPAgentCard(agent.definition); // Import another agent's ACP capabilities as Veridex tools const tools = fromACPCapabilities(remoteCard.capabilities); // Create a tool that calls a remote ACP agent const remoteTool = createRemoteAgentTool({ name: 'research-agent', description: 'Performs deep research', endpoint: 'https://agents.example.com/research', auth: { type: 'bearer', token: process.env.RESEARCH_AGENT_TOKEN }, timeoutMs: 30_000, }); ``` **Types:** ```ts interface ACPAgentCard { name: string; description: string; version?: string; capabilities: ACPCapability[]; endpoint?: string; metadata?: Record; } interface ACPCapability { name: string; description: string; inputSchema?: Record; outputSchema?: Record; tags?: string[]; } interface RemoteAgentConfig { name: string; description: string; endpoint: string; auth?: { type: 'bearer' | 'api-key' | 'none'; token?: string; headerName?: string }; capabilities?: ACPCapability[]; timeoutMs?: number; invoker?: ACPInvoker; } ``` ### Session translator Translate OpenClaw session histories into Veridex trace events for migration and analysis. ```ts const result = translateSession(openClawSession); // result.events — Veridex-format trace events // result.warnings — any translation issues // Batch translate const results = translateSessions(openClawSessions); ``` --- ## `@veridex/agents-control-plane` Enterprise-grade control plane for multi-tenant agent governance. Provides policy pack management, approval workflows, trace retention, audit export, tenant isolation, and deployable HTTP APIs. ### `PolicyPackManager` Manages versioned policy packs. Policy packs are collections of rules that can be applied to agents at the tenant or system level. ```ts const manager = new PolicyPackManager(); // Create a policy pack const pack = manager.create({ name: 'production-safety', description: 'Standard safety rules for production agents', version: '1.0.0', rules: [ { type: 'block_safety_class', params: { classes: ['destructive', 'privileged'] }, enabled: true }, { type: 'max_spend', params: { limitUSD: 100 }, enabled: true }, { type: 'require_approval', params: { classes: ['financial'] }, enabled: true }, ], tags: ['production', 'safety'], }); // List, update, delete const packs = manager.list(['production']); manager.update(pack.id, { version: '1.1.0' }); manager.delete(pack.id); // System packs are immutable manager.registerSystemPack({ name: 'core-safety', ... }); // Snapshot and restore const snapshot = manager.snapshot(); manager.load(snapshot); ``` **Types:** ```ts interface PolicyPack { id: string; name: string; description: string; version: string; rules: PolicyRuleDefinition[]; tags?: string[]; immutable?: boolean; createdAt: number; updatedAt: number; } interface PolicyRuleDefinition { type: string; params: Record; enabled: boolean; description?: string; } ``` **Methods:** | Method | Signature | Description | |--------|-----------|-------------| | `create` | `(options) => PolicyPack` | Create a pack | | `get` | `(id) => PolicyPack \| undefined` | Get by id | | `list` | `(tags?) => PolicyPack[]` | List, optionally filter by tags | | `update` | `(id, updates) => PolicyPack \| undefined` | Update a pack | | `delete` | `(id) => boolean` | Delete a pack | | `registerSystemPack` | `(options) => PolicyPack` | Create an immutable system pack | | `count` | `() => number` | Count packs | | `snapshot` | `() => PolicyPack[]` | Export all packs | | `load` | `(packs) => void` | Import packs | --- ### `TraceStore` Ingests, queries, and manages run traces with content hashing for tamper detection. ```ts const store = new TraceStore(new InMemoryTraceStore()); // Ingest a trace const record = await store.ingest({ runId: 'run-001', agentId: 'ops-agent', tenantId: 'tenant-acme', startedAt: Date.now() - 5000, completedAt: Date.now(), state: 'completed', turnCount: 3, totalTokens: 1500, totalCostUSD: 0.02, events: traceEvents, }); // Query traces const traces = await store.query({ agentId: 'ops-agent', state: 'completed', startAfter: Date.now() - 86_400_000, // last 24h limit: 50, }); // Verify content hash (tamper detection) const valid = await store.verify(record.id); // Purge expired traces const purged = await store.purgeExpired(); ``` **Types:** ```ts interface TraceRecord { id: string; runId: string; agentId: string; tenantId?: string; startedAt: number; completedAt?: number; state: 'completed' | 'failed' | 'cancelled'; turnCount: number; totalTokens: number; totalCostUSD?: number; events: unknown[]; contentHash?: string; metadata?: Record; } interface TraceQuery { runId?: string; agentId?: string; tenantId?: string; state?: TraceRecord['state']; startAfter?: number; startBefore?: number; limit?: number; offset?: number; } ``` **Methods:** | Method | Signature | Description | |--------|-----------|-------------| | `ingest` | `(options) => Promise` | Store a trace | | `get` | `(id) => Promise` | Get by id | | `query` | `(filters) => Promise` | Query with filters | | `delete` | `(id) => Promise` | Delete a trace | | `count` | `() => Promise` | Count traces | | `purgeExpired` | `() => Promise` | Remove traces past retention | | `verify` | `(id) => Promise` | Verify content hash | --- ### `ApprovalWorkflowEngine` Multi-step approval workflows with escalation chains, timeouts, and auto-approval. ```ts const engine = new ApprovalWorkflowEngine(); // Register a workflow engine.registerWorkflow({ id: 'high-risk', name: 'High Risk Approval', description: 'Two-step escalation for high-risk actions', escalationChain: [ { name: 'Team Lead', approvers: ['lead@company.com'], timeoutSeconds: 300, minApprovals: 1 }, { name: 'VP Engineering', approvers: ['vp@company.com'], timeoutSeconds: 600, minApprovals: 1 }, ], defaultMode: 'block', }); // Start an approval const approval = engine.startApproval('high-risk', 'run-001', 'agent-x', 'Transfer $10K', 0.9); // Vote engine.vote(approval.id, 'lead@company.com', true, 'Looks correct'); // Process timeouts (call periodically) const timedOut = engine.processTimeouts(); ``` **Types:** ```ts interface ApprovalWorkflow { id: string; name: string; description: string; escalationChain: ApprovalEscalationStep[]; autoApproveAfterSeconds?: number; defaultMode: 'block' | 'flag' | 'auto-approve'; } interface ApprovalEscalationStep { name: string; approvers: string[]; timeoutSeconds: number; minApprovals: number; } interface ActiveApproval { id: string; workflowId: string; currentStep: number; runId: string; agentId: string; proposal: string; riskScore: number; approvals: ApprovalVote[]; createdAt: number; currentStepDeadline: number; resolution?: 'approved' | 'denied' | 'auto-approved' | 'escalated' | 'timed-out'; } ``` **Methods:** | Method | Signature | Description | |--------|-----------|-------------| | `registerWorkflow` | `(workflow) => void` | Register a workflow | | `getWorkflow` | `(id) => ApprovalWorkflow \| undefined` | Get workflow by id | | `listWorkflows` | `() => ApprovalWorkflow[]` | List all workflows | | `startApproval` | `(workflowId, runId, agentId, proposal, riskScore) => ActiveApproval` | Start approval | | `vote` | `(approvalId, approver, approved, reason?) => ActiveApproval` | Cast a vote | | `processTimeouts` | `() => ActiveApproval[]` | Escalate or timeout stale approvals | | `getActive` | `(id) => ActiveApproval \| undefined` | Get active approval | | `listActive` | `(runId?) => ActiveApproval[]` | List active approvals | --- ### Audit export ```ts // Export traces in JSON, JSONL, or CSV const result = await exportTraces(traces, 'jsonl', { includeMetadata: true, startTime: Date.now() - 86_400_000, compress: false, }); // result.data — the exported string // result.contentHash — SHA-256 hash for verification // Generate a tamper-evident evidence bundle const bundle = await generateEvidenceBundle( traceId, policyDecisions, approvalDecisions, ); // bundle.contentHash — combined hash of trace + policy + approvals ``` **Types:** ```ts type ExportFormat = 'json' | 'jsonl' | 'csv'; interface AuditExportResult { format: ExportFormat; data: string | Buffer; contentHash: string; exportedAt: number; } interface EvidenceBundle { id: string; runId: string; version: string; traceHash: string; policyHash: string; approvalHash: string; contentHash: string; generatedAt: number; traceId: string; policyDecisions: unknown[]; approvalDecisions: unknown[]; } ``` --- ### `TenantManager` Multi-tenant isolation with per-tenant configuration. ```ts const tenants = new TenantManager(); const tenant = tenants.create({ name: 'Acme Corp', config: { maxConcurrentRuns: 10, dailySpendLimitUSD: 500, allowedProviders: ['openai'], auditEnabled: true, traceRetentionDays: 90, deploymentMode: 'managed', }, }); // Update config tenants.update(tenant.id, { dailySpendLimitUSD: 1000 }); // List all tenants const all = tenants.list(); ``` **Types:** ```ts interface Tenant { id: string; name: string; status: 'active' | 'suspended' | 'pending'; config: TenantConfig; metadata?: Record; createdAt: number; } interface TenantConfig { maxConcurrentRuns?: number; dailySpendLimitUSD?: number; allowedProviders?: string[]; auditEnabled?: boolean; traceRetentionDays?: number; deploymentMode?: 'local' | 'self-hosted' | 'private-cloud' | 'managed'; } ``` **Methods:** | Method | Signature | Description | |--------|-----------|-------------| | `create` | `(options) => Tenant` | Create a tenant | | `get` | `(id) => Tenant \| undefined` | Get by id | | `list` | `() => Tenant[]` | List all tenants | | `update` | `(id, config) => Tenant \| undefined` | Update tenant config | | `delete` | `(id) => boolean` | Remove a tenant | --- ### `ControlPlaneService` and HTTP API Deploy the control plane as an HTTP service. ```ts const server = await startControlPlaneServer({ port: 4500, storageBackend: 'postgres', postgres: { connectionString: process.env.DATABASE_URL, }, authRequired: true, bootstrapTokens: [ { token: process.env.ADMIN_TOKEN, role: 'admin' }, ], traceRetentionDays: 90, }); ``` ### `RemoteControlPlaneClient` Connect to a running control plane server from agent runtimes. ```ts const client = new RemoteControlPlaneClient({ baseUrl: 'https://cp.example.com', token: process.env.CP_TOKEN, tenantId: 'tenant-acme', }); ``` ### Persistence backends | Backend | Class | Use case | |---------|-------|----------| | In-memory | `InMemoryTraceStore` | Tests, prototyping | | File-based | `JSONFileMetadataStore`, `FileTraceStoreBackend` | Single-node deploys | | Postgres | `PostgresMetadataStore`, `PostgresTraceStoreBackend` | Production multi-node | **Config:** ```ts interface ControlPlaneServiceConfig { storageBackend?: 'file' | 'postgres'; host?: string; port?: number; dataDir?: string; authRequired?: boolean; tenantHeader?: string; bootstrapTokens?: Array; approvalSweepIntervalMs?: number; tracePurgeIntervalMs?: number; traceRetentionDays?: number; postgres?: { connectionString: string; ssl?: boolean }; } ``` --- ## Full working example This example shows how the packages fit together: an agent with tools, policy, approvals, memory, and a React UI. ```ts // === 1. Define tools === blockSafetyClasses, requireApprovalFor, maxRunSpendUSD } from '@veridex/agents'; const getBalance = tool({ name: 'get_balance', description: 'Check account balance', input: z.object({ accountId: z.string() }), safetyClass: 'read', async execute({ input }) { return { success: true, llmOutput: `Account ${input.accountId} has $1,234.56` }; }, }); const transferFunds = tool({ name: 'transfer_funds', description: 'Transfer funds between accounts', input: z.object({ from: z.string(), to: z.string(), amount: z.number().positive(), }), safetyClass: 'financial', async execute({ input }) { return { success: true, llmOutput: `Transferred $${input.amount} from ${input.from} to ${input.to}` }; }, }); // === 2. Create the agent with policy === const agent = createAgent( { id: 'treasury-agent', name: 'Treasury Agent', model: { provider: 'openai', model: 'gpt-4o' }, instructions: 'Help the finance team manage treasury operations safely.', tools: [getBalance, transferFunds], maxTurns: 5, }, { modelProviders: { openai: new OpenAIProvider({ apiKey: process.env.OPENAI_API_KEY }), }, enableTracing: true, enableCheckpoints: true, }, ); // === 3. Configure policy === const toolMap = new Map(agent.tools.list().map(t => [t.name, t])); agent.policyEngine.register(blockSafetyClasses(['destructive', 'privileged'], toolMap)); agent.policyEngine.register(requireApprovalFor(['financial'], toolMap)); agent.policyEngine.register(maxRunSpendUSD(100)); // === 4. Set up approval handler === agent.approvals.addRoute({ match: (proposal, policy) => policy.verdict === 'escalate', mode: 'human_required', timeoutMs: 300_000, }); // === 5. Listen for events === agent.events.on('tool_started', (e) => console.log(`Tool: ${e.toolName}`)); agent.events.on('approval_requested', (e) => console.log(`Approval needed: ${e.requestId}`)); // === 6. Run === const result = await agent.run('Transfer $500 from ACCT-A to ACCT-B'); console.log(result.output); console.log(result.state); // 'completed' or 'suspended' if approval needed ``` ```tsx // === 7. React UI === function TreasuryApp() { return ( ); } function AgentChat() { const { run, result, isLoading } = useRun(); return ( run('Check balance for ACCT-A')} disabled={isLoading}> Ask Agent {result && {result.output}} ); } function ApprovalInbox() { const { pending, resolve } = useApprovals(); return ( {pending.map(req => ( {req.proposal.toolName}: {JSON.stringify(req.proposal.arguments)} resolve(req.id, true)}>Approve resolve(req.id, false)}>Deny ))} ); } function BudgetBar() { const { snapshot } = useBudget(); return ; } ``` --- ## Response Integrity ### `ResponseSeal` HMAC-based chain-of-custody seal for LLM responses. Created automatically during agent runs. ```typescript // Create a seal (done automatically by model providers) const seal = createResponseSeal(rawResponseBytes, apiKeyBytes); // Verify a seal const isValid = verifyResponseSeal(seal, rawResponseBytes, apiKeyBytes); ``` **Seal structure:** ```typescript interface ResponseSealData { algorithm: 'HMAC-SHA256'; seal: string; // hex-encoded HMAC rawHash: string; // SHA-256 of raw response bytes timestamp: number; // Unix ms } ``` ### `ResponseEnvelope` Full response envelope with chain-of-custody: ```typescript interface ResponseEnvelope { runId: string; turnIndex: number; model: string; provider: string; output: string; tokensUsed: { prompt: number; completion: number }; chainOfCustodySeal?: ResponseSealData; } ``` --- ## Agent Identity Types ### `AgentIdentityClaims` OIDC-A (OpenID Connect for Agents) claims: ```typescript interface AgentIdentityClaims { iss: string; // Issuer sub: string; // Agent ID aud: string; // Audience iat: number; // Issued at exp: number; // Expires scope: string[]; // Permissions agentName: string; agentVersion: string; } ``` ### `AgentIntegrityBinding` Binds agent identity to a specific code + config snapshot: ```typescript interface AgentIntegrityBinding { agentId: string; identityClaims: AgentIdentityClaims; codeHash: string; // SHA-256 of agent code configHash: string; // SHA-256 of configuration toolManifestHash: string; // SHA-256 of registered tools timestamp: number; } ``` --- ## Execution Accountability Graph ```typescript interface ExecutionNode { id: string; type: 'turn' | 'tool_call' | 'model_call' | 'policy_check' | 'approval'; timestamp: number; data: Record; } interface ExecutionEdge { from: string; to: string; type: 'triggered' | 'required' | 'produced'; } interface ExecutionGraph { nodes: ExecutionNode[]; edges: ExecutionEdge[]; rootId: string; } ``` --- ## Model Providers ### `OpenAIProvider` ```typescript new OpenAIProvider({ apiKey: string, model?: string, baseURL?: string }) ``` ### `AnthropicProvider` ```typescript new AnthropicProvider({ apiKey: string, model?: string }) ``` ### `GeminiProvider` ```typescript new GeminiProvider({ apiKey: string, model?: string }) ``` ### `OpenAICompatibleProvider` ```typescript new OpenAICompatibleProvider({ baseURL: string, apiKey?: string, model: string }) ``` Subclasses: `GroqProvider`, `TogetherAIProvider`, `FireworksProvider`, `DeepSeekProvider`, `PerplexityProvider`, `MistralProvider` All providers automatically create `ResponseSeal` on every completion. --- ## Sovereignty Types ### `SovereigntyViolationSummary` ```typescript interface SovereigntyViolationSummary { runId: string; agentId: string; turnIndex: number; toolName: string; piiCategories: string[]; fromJurisdiction: string; toJurisdiction: string; regulation: string; timestamp: number; } ``` --- Narrative walkthrough with architecture and examples Payment, identity, trust, and protocol-routing primitives ADRs explaining the design rationale ================================================================================ URL: /api-reference/agents-adapters ================================================================================ # Agents Adapters API Reference Full API for `@veridex/agents-adapters` — import, export, and bridge agents across frameworks. --- ## Framework Adapters All adapters implement the `FrameworkAdapter` interface: ```typescript interface FrameworkAdapter { importAgent(config: TImport): { definition: AgentDefinition; warnings: string[] }; exportAgent(definition: AgentDefinition): { config: TExport; warnings: string[] }; } ``` ### `OpenAIAgentsAdapter` ```typescript const adapter = new OpenAIAgentsAdapter(); const { definition, warnings } = adapter.importAgent(openAIConfig); const { config } = adapter.exportAgent(definition); ``` ### `LangGraphAdapter` ```typescript const adapter = new LangGraphAdapter(); const { definition, warnings } = adapter.importAgent(langGraphConfig); const { config } = adapter.exportAgent(definition); ``` ### `PydanticAIAdapter` ```typescript const adapter = new PydanticAIAdapter(); const { definition, warnings } = adapter.importAgent(pydanticAIConfig); const { config } = adapter.exportAgent(definition); ``` --- ## `OpenAPIImporter` Convert OpenAPI 3.x specifications into Veridex `ToolContract` objects. ```typescript const importer = new OpenAPIImporter(); ``` ### `importer.importTools(spec, options)` ```typescript const tools: ToolContract[] = importer.importTools(spec, { baseUrl: string, auth?: { type: 'bearer' | 'api-key'; value: string; header?: string }, safetyClassOverrides?: Record, }); ``` Each path + method combination becomes a `ToolContract` with: - Name derived from `operationId` or path - Zod input schema generated from request body / query parameters - Safety class inferred from HTTP method (`GET` → `read`, `POST` → `write`, `DELETE` → `destructive`) --- ## Runtime Bridges Live bridges invoke external runtimes as Veridex tools or handoff targets. ### `OpenAIRuntimeBridge` ```typescript const bridge = new OpenAIRuntimeBridge({ name: string, endpoint: string, apiKey: string, timeoutMs?: number, }); const tool = bridge.asTool({ name: string, description?: string }); const handler = bridge.asHandoffHandler(); ``` ### `LangGraphRuntimeBridge` ```typescript const bridge = new LangGraphRuntimeBridge({ name: string, endpoint: string, apiKey?: string, }); ``` ### `PydanticAIRuntimeBridge` ```typescript const bridge = new PydanticAIRuntimeBridge({ name: string, endpoint: string, apiKey?: string, }); ``` --- ## Capability Binding ### `AgentCapabilityBinder` ```typescript const binder = new AgentCapabilityBinder(); const boundTools: ToolContract[] = binder.bind(importedTools); ``` ### `inferSafetyClass` ```typescript const safetyClass: ToolSafetyClass = inferSafetyClass(toolDefinition); ``` Heuristic inference based on tool name, description, and schema analysis. --- ## Utilities ### `jsonSchemaToZod` ```typescript const zodSchema = jsonSchemaToZod(jsonSchemaObject); ``` Converts a JSON Schema object to a Zod schema for `ToolContract` input validation. ### `createStubExecutor` ```typescript const executor = createStubExecutor(toolName); ``` Creates a stub executor that returns a placeholder result, useful for testing imported tools. --- Usage guide with examples Core agent runtime ================================================================================ URL: /api-reference/agents-openclaw ================================================================================ # Agents OpenClaw API Reference Full API for `@veridex/agents-openclaw` — bridging the OpenClaw/Pi ecosystem into Veridex. --- ## Context File Parsing ### `parseContextFile` ```typescript function parseContextFile( filename: string, content: string, ): ContextFile; ``` Parse a single OpenClaw context file (e.g., `AGENTS.md`, `IDENTITY.md`). ### `parseWorkspaceContextFiles` ```typescript function parseWorkspaceContextFiles( files: Record, ): ContextFile[]; ``` Parse all context files from a workspace directory. ### `compileContextSections` ```typescript function compileContextSections( contextFiles: ContextFile[], ): Record; ``` Compile parsed context files into text blocks keyed by type. --- ## Skill Import ### `parseSkillDocument` ```typescript function parseSkillDocument( path: string, content: string, ): SkillDocument; ``` Parse a `SKILL.md` file with YAML frontmatter into a structured document. ### `importSkillDocument` ```typescript function importSkillDocument( doc: SkillDocument, ): { tool: ToolContract; warnings: string[] }; ``` Convert a parsed skill document into a Veridex `ToolContract`. ### `importSkill` ```typescript function importSkill( path: string, content: string, ): { tool: ToolContract; warnings: string[] }; ``` Convenience function: parse + import in one step. ### `importSkillManifest` ```typescript function importSkillManifest( manifest: SkillManifest, ): { tools: ToolContract[]; warnings: string[] }; ``` Import all skills in a manifest. ### `inferSafetyClassFromSkill` ```typescript function inferSafetyClassFromSkill( doc: SkillDocument, ): ToolSafetyClass; ``` Infer Veridex safety class from skill metadata (name, description, tags). --- ## ACP Agent Cards ### `toACPAgentCard` ```typescript function toACPAgentCard( definition: AgentDefinition, ): ACPAgentCard; ``` Export a Veridex agent definition as an ACP-compatible agent card. ### `fromACPCapabilities` ```typescript function fromACPCapabilities( capabilities: ACPCapability[], ): ToolContract[]; ``` Import ACP capabilities as Veridex tool contracts. ### `createRemoteAgentTool` ```typescript function createRemoteAgentTool(options: { name: string; description: string; endpoint: string; auth?: { type: 'bearer'; token: string }; timeoutMs?: number; }): ToolContract; ``` Create a tool that calls a remote ACP agent. --- ## Session Translation ### `translateEvent` ```typescript function translateEvent( event: OpenClawEvent, ): { event: TraceEvent; warnings: string[] }; ``` Translate a single OpenClaw event into a Veridex trace event. ### `translateSession` ```typescript function translateSession( session: OpenClawSession, ): { events: TraceEvent[]; warnings: string[] }; ``` Translate a full OpenClaw session into Veridex trace events. ### `translateSessions` ```typescript function translateSessions( sessions: OpenClawSession[], ): { events: TraceEvent[]; warnings: string[] }[]; ``` Batch translate multiple sessions. --- ## Compatibility Audit ### `buildCompatibilityAuditReport` ```typescript function buildCompatibilityAuditReport( contextFiles: ContextFile[], skills: SkillDocument[], ): CompatibilityReport; interface CompatibilityReport { compatible: number; warnings: CompatibilityWarning[]; unsupported: UnsupportedFeature[]; summary: string; } ``` --- Usage guide with examples Migration tutorial ================================================================================ URL: /api-reference/agents-control-plane ================================================================================ # Agents Control Plane API Reference Full API for `@veridex/agents-control-plane` — multi-tenant agent governance. --- ## Service & Client ### `ControlPlaneService` In-process control plane for embedded use. ```typescript const service = new ControlPlaneService({ policyPackManager: PolicyPackManager, traceStore: TraceStore, approvalEngine?: ApprovalWorkflowEngine, tenantManager?: TenantManager, }); ``` ### `ControlPlaneClient` Local client that talks to a `ControlPlaneService` directly. ```typescript const client = new ControlPlaneClient(service); ``` ### `RemoteControlPlaneClient` HTTP client for a deployed control plane server. ```typescript const client = new RemoteControlPlaneClient({ baseUrl: string, token: string, tenantId?: string, timeoutMs?: number, }); ``` --- ## Server ### `createControlPlaneApp` Create a Hono app without starting the server. ```typescript const app = createControlPlaneApp(options); ``` ### `startControlPlaneServer` Start a full HTTP server. ```typescript const server = await startControlPlaneServer({ port?: number, // default: 4500 storageBackend: 'memory' | 'file' | 'postgres', postgres?: { connectionString: string }, fileStorage?: { directory: string }, authRequired?: boolean, bootstrapTokens?: { token: string; role: string }[], traceRetentionDays?: number, }); ``` --- ## `PolicyPackManager` ```typescript const manager = new PolicyPackManager(); manager.create(pack: PolicyPackDefinition): PolicyPack; manager.registerSystemPack(pack: PolicyPackDefinition): void; manager.get(name: string): PolicyPack | undefined; manager.list(): PolicyPack[]; manager.update(name: string, updates: Partial): PolicyPack; manager.delete(name: string): boolean; ``` **PolicyPackDefinition:** ```typescript interface PolicyPackDefinition { name: string; description: string; version: string; rules: PolicyRule[]; tags?: string[]; } interface PolicyRule { type: string; params: Record; enabled: boolean; } ``` --- ## `ApprovalWorkflowEngine` ```typescript const engine = new ApprovalWorkflowEngine(); engine.registerWorkflow(workflow: WorkflowDefinition): void; engine.startApproval( workflowId: string, runId: string, agentId: string, description: string, riskScore: number, ): ApprovalProcess; engine.vote(approvalId: string, voter: string, approved: boolean, reason?: string): void; engine.processTimeouts(): ApprovalProcess[]; engine.getApproval(approvalId: string): ApprovalProcess | undefined; ``` **WorkflowDefinition:** ```typescript interface WorkflowDefinition { id: string; name: string; description: string; escalationChain: EscalationStep[]; defaultMode: 'block' | 'allow'; } interface EscalationStep { name: string; approvers: string[]; timeoutSeconds: number; minApprovals: number; } ``` --- ## `TraceStore` ```typescript const store = new TraceStore(backend); await store.ingest(trace: TraceInput): Promise; await store.query(filter: TraceFilter): Promise; await store.get(traceId: string): Promise; await store.verify(traceId: string): Promise; await store.delete(traceId: string): Promise; ``` **Backends:** | Backend | Import | Use Case | |---------|--------|----------| | `InMemoryTraceStore` | `@veridex/agents-control-plane` | Development, testing | | `FileTraceStoreBackend` | `@veridex/agents-control-plane` | Single-node production | | `PostgresTraceStoreBackend` | `@veridex/agents-control-plane` | Multi-node production | --- ## `TenantManager` ```typescript const tenants = new TenantManager(); tenants.create(config: TenantConfig): Tenant; tenants.get(tenantId: string): Tenant | undefined; tenants.list(): Tenant[]; tenants.update(tenantId: string, updates: Partial): Tenant; tenants.delete(tenantId: string): boolean; ``` **TenantConfig:** ```typescript interface TenantConfig { name: string; config: { maxConcurrentRuns: number; dailySpendLimitUSD: number; allowedProviders: string[]; auditEnabled: boolean; traceRetentionDays: number; deploymentMode: 'managed' | 'self-hosted'; }; } ``` --- ## Metadata Stores ### `JSONFileMetadataStore` ```typescript const store = new JSONFileMetadataStore({ directory: './metadata' }); ``` ### `PostgresMetadataStore` ```typescript const store = new PostgresMetadataStore({ connectionString: process.env.DATABASE_URL }); ``` --- ## Audit Export ### `exportTraces` ```typescript const result = await exportTraces(traces, format, options?); ``` | Parameter | Type | Description | |-----------|------|-------------| | `traces` | `TraceRecord[]` | Traces to export | | `format` | `'json' \| 'jsonl' \| 'csv'` | Export format | | `options.includeMetadata` | `boolean?` | Include metadata fields | | `options.startTime` | `number?` | Filter by start time | **Returns:** `{ data: string; contentHash: string }` ### `generateEvidenceBundle` ```typescript const bundle = await generateEvidenceBundle(traceId, policyDecisions, approvalDecisions); // bundle.contentHash — SHA-256 combined hash for tamper detection ``` --- ## Workforce OS Types ```typescript interface AgentManifest { agentId: string; name: string; version: string; capabilities: string[]; dependencies: string[]; resources: { cpu: string; memory: string }; } interface Workspace { id: string; name: string; tenantId: string; agentIds: string[]; } interface Task { id: string; description: string; assignee?: string; status: string; priority: number; } interface Deployment { id: string; agentId: string; environment: string; version: string; replicas: number; } interface Incident { id: string; agentId: string; severity: string; description: string; status: string; timeline: IncidentEvent[]; } interface EvalSuite { id: string; name: string; tests: EvalTest[]; } interface PolicyBinding { id: string; packName: string; target: string; scope: 'tenant' | 'workspace' | 'agent'; } interface Assignment { id: string; agentId: string; taskId: string; priority: number; constraints: Record; } interface MaintenanceJob { id: string; type: string; schedule: string; target: string; } ``` --- Usage guide with examples Deployment walkthrough ================================================================================ URL: /api-reference/relayer ================================================================================ # Relayer API Reference The Veridex Relayer provides a REST API for gasless transactions and cross-chain messaging. ## Base URL | Environment | URL | |-------------|-----| | Testnet | `https://relayer.veridex.network` | | Local | `http://localhost:3001` | ## Authentication Most endpoints are public. Rate limiting is applied per IP address. --- ## Health & Info ### GET /health Health check endpoint. **Response:** ```json { "status": "healthy", "version": "1.0.0", "uptime": 3600, "chains": { "base-sepolia": "connected", "optimism-sepolia": "connected", "arbitrum-sepolia": "connected", "solana-devnet": "connected" } } ``` **Status Codes:** | Code | Description | |------|-------------| | 200 | Healthy or degraded | | 503 | Unhealthy | --- ### GET /api/v1/info Get relayer configuration. **Response:** ```json { "relayer": { "address": "0x...", "version": "1.0.0" }, "hub": { "chainId": 84532, "wormholeChainId": 10004, "contracts": { "hub": "0x..." } }, "supportedChains": [ { "name": "optimism-sepolia", "chainId": 11155420, "wormholeChainId": 10005, "type": "evm" } ], "capabilities": { "gaslessSubmit": true, "vaaRelay": true, "balanceQueries": true } } ``` --- ## Transactions ### POST /api/v1/submit Submit a gasless transaction. **Request Body:** ```json { "action": "transfer", "targetChain": 10005, "payload": { "token": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "recipient": "0x742d35Cc6634C0532925a3b844Bc9e7595f5A234", "amount": "1000000" }, "signature": { "r": "0x...", "s": "0x...", "authenticatorData": "0x...", "clientDataJSON": "..." }, "credentialId": "base64-encoded-id", "vaultAddress": "0x..." } ``` **Parameters:** | Field | Type | Description | |-------|------|-------------| | `action` | string | Action type (`transfer`, `bridge`, `createVault`) | | `targetChain` | number | Wormhole chain ID | | `payload` | object | Action-specific payload | | `signature` | object | WebAuthn signature | | `credentialId` | string | Base64 credential ID | | `vaultAddress` | string | User's vault address | **Response:** ```json { "success": true, "txHash": "0x...", "targetChain": 10005, "estimatedConfirmation": 60 } ``` **Error Response:** ```json { "error": "INVALID_SIGNATURE", "message": "WebAuthn signature verification failed", "details": { "expected": "0x...", "received": "0x..." } } ``` --- ### POST /api/v1/submit/query Submit via Wormhole Query path (faster). **Request Body:** Same as `/api/v1/submit` **Response:** ```json { "success": true, "txHash": "0x...", "path": "query", "confirmationTime": 5200 } ``` --- ### GET /api/v1/status/:txHash Get transaction status. **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `txHash` | string | Transaction hash | **Response:** ```json { "txHash": "0x...", "status": "confirmed", "blockNumber": 12345678, "timestamp": 1704067200, "action": "transfer", "targetChain": 10005, "destinationTxHash": "0x..." } ``` **Status Values:** | Status | Description | |--------|-------------| | `pending` | Submitted, awaiting confirmation | | `attested` | VAA attested by guardians | | `confirmed` | Confirmed on destination chain | | `failed` | Transaction failed | --- ## Fees ### GET /api/v1/fee Get fee quote. **Query Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `targetChain` | number | Wormhole chain ID (optional) | | `action` | string | Action type (optional) | **Response:** ```json { "fees": { "wormhole": "100000000000000", "relayer": "500000000000000", "total": "600000000000000" }, "targetChain": 10005, "estimatedTimeSeconds": 60, "currency": "ETH" } ``` --- ## Balances ### GET /api/v1/balances/:address Get multi-chain balances for an address. **Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `address` | string | Vault address | **Query Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `chains` | string | Comma-separated chain names (optional) | **Response:** ```json { "address": "0x...", "balances": { "base-sepolia": { "native": "100000000000000000", "tokens": { "0x036CbD53842c5426634e7929541eC2318f3dCF7e": { "symbol": "USDC", "decimals": 6, "balance": "100000000" } } }, "optimism-sepolia": { "native": "50000000000000000", "tokens": {} } }, "timestamp": 1704067200 } ``` --- ## Vaults ### POST /api/v1/vault/create Create a vault on a spoke chain. **Request Body:** ```json { "targetChain": 10005, "credentialId": "base64-encoded-id", "publicKey": { "x": "0x...", "y": "0x..." } } ``` **Response:** ```json { "success": true, "vaultAddress": "0x...", "txHash": "0x...", "targetChain": 10005 } ``` --- ## Stacks Endpoints The relayer provides Stacks-specific endpoints for direct relay (not Wormhole VAAs). ### POST /api/v1/stacks/submit Submit a sponsored Stacks transaction. ```bash curl -X POST https://relayer.veridex.network/api/v1/stacks/submit \ -H "Content-Type: application/json" \ -d '{ "transaction": "", "keyHash": "0x..." }' ``` **Response:** ```json { "success": true, "txId": "0x7f9e5c51...", "status": "pending" } ``` ### GET /api/v1/stacks/identity/:keyHash Query identity registration status on the Stacks spoke contract. ```bash curl https://relayer.veridex.network/api/v1/stacks/identity/0x1234... ``` ### GET /api/v1/stacks/session/:sessionHash Query session key status. ```bash curl https://relayer.veridex.network/api/v1/stacks/session/0xabcd... ``` ### GET /api/v1/stacks/vault/:keyHash Query vault balance and status. ```bash curl https://relayer.veridex.network/api/v1/stacks/vault/0x1234... ``` ### GET /api/v1/stacks/tx/:txId Get Stacks transaction status. ```bash curl https://relayer.veridex.network/api/v1/stacks/tx/0x7f9e5c51... ``` ### Stacks Sponsorship Limits | Limit | Value | |-------|-------| | Per-identity rate | 50 tx/hour | | Per-tx fee cap | 0.5 STX | | Hourly budget | 50 STX | --- ## Cross-Domain Passkey Sharing Endpoints for third-party app registration, origin validation, and server-side session tokens. See the [Cross-Domain Passkeys Guide](/guides/cross-domain-passkeys) for full integration instructions. ### POST /api/v1/apps/register Register a new third-party application for cross-domain passkey sharing. **Request Body:** ```json { "name": "My DeFi App", "origin": "https://myapp.com", "description": "A decentralized exchange", "contactEmail": "dev@myapp.com" } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `name` | string | Yes | Application name (min 2 characters) | | `origin` | string | Yes | HTTPS origin (e.g. `https://myapp.com`) | | `description` | string | No | Short description | | `contactEmail` | string | No | Developer contact email | **Response (201):** ```json { "success": true, "app": { "id": "app_a1b2c3d4e5f6", "name": "My DeFi App", "origin": "https://myapp.com", "apiKey": "vdx_sk_...", "trustLevel": "registered", "status": "active", "createdAt": 1738800000000 } } ``` **Error Responses:** | Code | Description | |------|-------------| | 400 | Invalid name, origin, or non-HTTPS origin | | 409 | Origin already registered | --- ### GET /api/v1/apps List registered apps. Public access returns origins only. Admin access (with `X-API-Key` header matching the metrics API key) returns full details. **Public Response:** ```json { "origins": ["https://myapp.com", "https://partner.io"], "count": 2 } ``` --- ### GET /api/v1/apps/:appId Get details of a registered app. Requires the app's API key or admin key via `X-API-Key` header. **Response:** ```json { "app": { "id": "app_a1b2c3d4e5f6", "name": "My DeFi App", "origin": "https://myapp.com", "trustLevel": "registered", "status": "active", "requestCount": 42, "createdAt": 1738800000000, "apiKey": "vdx_sk_..." } } ``` --- ### DELETE /api/v1/apps/:appId Delete a registered app and revoke all its active sessions. Requires the app's API key or admin key via `X-API-Key` header. **Response:** ```json { "success": true, "revokedSessions": 3 } ``` --- ### GET /api/v1/apps/info Get public app info by origin. Used by the Auth Portal to display app details during authentication. **Query Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `origin` | string | The origin to look up (e.g. `https://myapp.com`) | **Response:** ```json { "name": "My DeFi App", "origin": "https://myapp.com", "description": "A decentralized exchange", "trustLevel": "registered", "registered": true } ``` --- ### GET /api/v1/origins/validate Check if an origin is allowed for cross-domain passkey sharing. **Query Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `origin` | string | The origin to validate | **Response:** ```json { "allowed": true, "reason": "registered", "origin": "https://myapp.com" } ``` **Reason values:** `builtin`, `development`, `registered`, `not_registered`, `invalid_origin` --- ### GET /api/v1/origins/well-known Generate the `.well-known/webauthn` JSON dynamically from all built-in and registered origins. **Response:** ```json { "origins": [ "https://veridex.network", "https://auth.veridex.network", "https://sera.veridex.network", "https://myapp.com" ] } ``` --- ### POST /api/v1/session/create Create a server-validated cross-origin session token. **Request Body:** ```json { "keyHash": "0x1234...", "appOrigin": "https://myapp.com", "sessionPublicKey": "0xabcd...", "permissions": ["read", "transfer"], "expiresInMs": 3600000, "signature": { "r": "0x...", "s": "0x..." } } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `keyHash` | string | Yes | User's passkey key hash (0x-prefixed) | | `appOrigin` | string | Yes | Requesting app's origin | | `sessionPublicKey` | string | Yes | Session key public key | | `permissions` | string[] | No | Granted permissions (default: `["read", "transfer"]`) | | `expiresInMs` | number | No | Session duration in ms (default: 3600000 = 1 hour) | | `signature` | object | Yes | WebAuthn signature proof | **Response (201):** ```json { "success": true, "session": { "id": "ses_abc123", "keyHash": "0x1234...", "appOrigin": "https://myapp.com", "permissions": ["read", "transfer"], "expiresAt": 1738903600000, "createdAt": 1738900000000 } } ``` **Error Responses:** | Code | Description | |------|-------------| | 400 | Missing or invalid fields | | 403 | Origin not registered | | 404 | Credential not found (user must register a passkey first) | --- ### GET /api/v1/session/:sessionId Validate a cross-origin session token. **Response (valid):** ```json { "valid": true, "session": { "id": "ses_abc123", "keyHash": "0x1234...", "appOrigin": "https://myapp.com", "permissions": ["read", "transfer"], "expiresAt": 1738903600000, "createdAt": 1738900000000 } } ``` **Response (invalid/expired):** ```json { "valid": false, "error": "Session not found or expired" } ``` --- ### DELETE /api/v1/session/:sessionId Revoke a cross-origin session token. **Response:** ```json { "success": true, "revoked": true } ``` --- ## Recovery Encrypted credential backup and recovery endpoints. Archives are encrypted client-side with a PRF-derived key — the relayer cannot read them. ### POST /api/v1/recovery/archive Store or update an encrypted recovery archive for a key hash. **Request Body:** ```json { "keyHash": "0x1234...", "ciphertext": "base64-encoded-encrypted-data", "iv": "base64-encoded-initialization-vector", "version": 1 } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `keyHash` | string | Yes | Owner's key hash (0x-prefixed hex) | | `ciphertext` | string | Yes | AES-GCM encrypted credential data (base64) | | `iv` | string | Yes | AES-GCM initialization vector (base64) | | `version` | number | No | Archive schema version (default: 1) | **Response (200):** ```json { "success": true, "updatedAt": 1691234567890 } ``` **Error Responses:** | Code | Description | |------|-------------| | 400 | Invalid keyHash format, missing ciphertext or iv | | 404 | Credential not found (user must register a passkey first) | --- ### GET /api/v1/recovery/archive/:keyHash Retrieve an encrypted recovery archive. Public endpoint — the data is encrypted and only meaningful to the key holder. **Path Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `keyHash` | string | Owner's key hash (0x-prefixed hex) | **Response (archive exists):** ```json { "exists": true, "ciphertext": "base64-encoded-encrypted-data", "iv": "base64-encoded-initialization-vector", "version": 1, "updatedAt": 1691234567890 } ``` **Response (no archive):** ```json { "exists": false } ``` **Error Responses:** | Code | Description | |------|-------------| | 400 | Invalid keyHash format | --- ### DELETE /api/v1/recovery/archive/:keyHash Delete a recovery archive. Requires ownership proof (valid session for this keyHash). **Response:** ```json { "success": true } ``` **Error Responses:** | Code | Description | |------|-------------| | 400 | Invalid keyHash format | | 404 | Archive not found | --- ### GET /api/v1/recovery/status/:keyHash Check recovery readiness for a given identity. Returns whether a backup exists, its version, and guardian count. **Path Parameters:** | Parameter | Type | Description | |-----------|------|-------------| | `keyHash` | string | Owner's key hash (0x-prefixed hex) | **Response (200):** ```json { "hasArchive": true, "archiveVersion": 1, "archiveUpdatedAt": 1691234567890, "guardianCount": 2 } ``` | Field | Type | Description | |-------|------|-------------| | `hasArchive` | boolean | Whether an encrypted archive exists | | `archiveVersion` | number \| null | Archive schema version (null if no archive) | | `archiveUpdatedAt` | number \| null | Last update timestamp in ms (null if no archive) | | `guardianCount` | number | Number of active recovery guardians | **Error Responses:** | Code | Description | |------|-------------| | 400 | Invalid keyHash format | --- ## Error Codes | Code | HTTP Status | Description | |------|-------------|-------------| | `INVALID_SIGNATURE` | 400 | WebAuthn signature verification failed | | `INSUFFICIENT_BALANCE` | 400 | Vault doesn't have enough tokens | | `UNSUPPORTED_CHAIN` | 400 | Target chain not configured | | `INVALID_PAYLOAD` | 400 | Malformed request payload | | `NONCE_MISMATCH` | 400 | Transaction nonce mismatch | | `ORIGIN_NOT_REGISTERED` | 403 | Origin not registered for cross-domain auth | | `RATE_LIMITED` | 429 | Too many requests | | `RELAYER_ERROR` | 500 | Internal relayer error | | `CHAIN_UNAVAILABLE` | 503 | Target chain not responding | | `VAA_NOT_FOUND` | 404 | Wormhole VAA not yet available | | `STACKS_SPONSORSHIP_LIMIT` | 429 | Stacks sponsorship rate limit exceeded | | `STACKS_POST_CONDITION_FAIL` | 400 | Stacks post-condition validation failed | --- ## Rate Limits | Endpoint | Limit | |----------|-------| | All endpoints | 100 requests/minute per IP | | `/api/v1/submit` | 10 requests/minute per vault | | `/api/v1/balances` | 30 requests/minute per IP | --- ## WebSocket (Coming Soon) Real-time transaction status updates: ```javascript const ws = new WebSocket('wss://relayer.veridex.network/ws'); ws.send(JSON.stringify({ type: 'subscribe', txHash: '0x...' })); ws.onmessage = (event) => { const { type, data } = JSON.parse(event.data); if (type === 'status') { console.log('Status update:', data); } }; ``` --- ## Self-Hosting See the [Relayer README](/api-reference/relayer-setup) for self-hosting instructions. ```bash # Clone and setup git clone https://github.com/Veridex-Protocol/demo cd demo/packages/relayer cp .env.example .env # Configure .env with your keys # Start relayer npm run dev ``` ================================================================================ URL: /api-reference/types ================================================================================ # TypeScript Types All types below are exported from `@veridex/sdk` and available via: ```typescript ``` --- ## Configuration ### SimpleSDKConfig Passed to `createSDK()` for quick setup. ```typescript interface SimpleSDKConfig { network?: 'testnet' | 'mainnet'; rpcUrl?: string; relayerUrl?: string; relayerApiKey?: string; sponsorPrivateKey?: string; integratorSponsorKey?: string; rpcUrls?: Record; } ``` ### VeridexConfig Full config for the `VeridexSDK` constructor. ```typescript interface VeridexConfig { chain: ChainClient; persistWallet?: boolean; testnet?: boolean; relayerUrl?: string; relayerApiKey?: string; queryApiKey?: string; sponsorPrivateKey?: string; integratorSponsorKey?: string; chainRpcUrls?: Record; } ``` ### ChainConfig Returned by `sdk.getChainConfig()`. ```typescript interface ChainConfig { chainId: number; wormholeChainId: number; rpcUrl: string; hubContractAddress: string; wormholeCoreBridge: string; name: string; explorerUrl: string; vaultFactory: string; vaultImplementation: string; } ``` ### ChainName ```typescript type ChainName = | 'base' | 'optimism' | 'arbitrum' | 'ethereum' | 'polygon' | 'monad' | 'solana' | 'aptos' | 'sui' | 'starknet' | 'stacks'; ``` ### NetworkType ```typescript type NetworkType = 'testnet' | 'mainnet'; ``` --- ## Passkey & Identity ### PasskeyCredential Returned by `sdk.passkey.register()` and `sdk.passkey.authenticate()`. ```typescript interface PasskeyCredential { credentialId: string; publicKeyX: bigint; publicKeyY: bigint; keyHash: string; } ``` ### WebAuthnSignature ```typescript interface WebAuthnSignature { r: string; s: string; authenticatorData: string; clientDataJSON: string; } ``` ### UnifiedIdentity Returned by `sdk.getUnifiedIdentity()`. ```typescript interface UnifiedIdentity { keyHash: string; addresses: Array<{ wormholeChainId: number; chainName: string; address: string; deployed: boolean; }>; } ``` ### IdentityState ```typescript interface IdentityState { keyHash: string; authorizedKeys: AuthorizedKey[]; keyCount: number; } ``` ### AuthorizedKey ```typescript interface AuthorizedKey { keyHash: string; publicKeyX: bigint; publicKeyY: bigint; addedAt: bigint; label?: string; } ``` --- ## Transfer & Dispatch ### TransferParams ```typescript interface TransferParams { targetChain: number; // Wormhole chain ID token: string; // Token address or 'native' recipient: string; // Recipient address amount: bigint; // Amount in smallest unit (wei, lamports, etc.) } ``` ### PreparedTransfer Returned by `sdk.prepareTransfer()`. ```typescript interface PreparedTransfer { params: TransferParams; actionPayload: string; nonce: bigint; challenge: string; estimatedGas: bigint; gasPrice: bigint; messageFee: bigint; totalCost: bigint; formattedCost: string; preparedAt: number; expiresAt: number; // 5-minute TTL } ``` ### TransferResult Returned by `sdk.executeTransfer()` and `sdk.transferViaRelayer()`. ```typescript interface TransferResult { transactionHash: string; sequence?: bigint; status: 'pending' | 'confirmed' | 'failed'; } ``` ### DispatchResult Returned by low-level `sdk.transfer()`, `sdk.execute()`, `sdk.bridge()`. ```typescript interface DispatchResult { transactionHash: string; sequence: bigint; userKeyHash: string; targetChain: number; blockNumber: number; } ``` ### ExecuteParams ```typescript interface ExecuteParams { targetChain: number; target: string; value: bigint; callData: string; } ``` --- ## Cross-Chain / Bridge ### BridgeParams ```typescript interface BridgeParams { sourceChain: number; destinationChain: number; token: string; recipient: string; amount: bigint; } ``` ### PreparedBridge ```typescript interface PreparedBridge { params: BridgeParams; actionPayload: string; nonce: bigint; challenge: string; estimatedGas: bigint; messageFee: bigint; totalCost: bigint; formattedCost: string; preparedAt: number; expiresAt: number; } ``` ### BridgeResult ```typescript interface BridgeResult { success: boolean; txHash: string; estimatedTimeSeconds: number; path: 'query' | 'vaa'; } ``` ### CrossChainFees ```typescript interface CrossChainFees { sourceGas: bigint; messageFee: bigint; relayerFee: bigint; totalCost: bigint; formattedTotal: string; currency: string; } ``` --- ## Balance ### TokenBalance ```typescript interface TokenBalance { token: TokenInfo; balance: bigint; formatted: string; usdValue?: number; } ``` ### PortfolioBalance ```typescript interface PortfolioBalance { wormholeChainId: number; chainName: string; address: string; tokens: TokenBalance[]; totalUsdValue?: number; lastUpdated: Date; } ``` ### TokenInfo ```typescript interface TokenInfo { address: string; symbol: string; name: string; decimals: number; wormholeChainId: number; logoUrl?: string; } ``` --- ## Balance Watcher ### BalanceChangeEvent Delivered to `watchBalance()` subscribers when token balances change. ```typescript interface BalanceChangeEvent { wormholeChainId: number; address: string; portfolio: PortfolioBalance; changes: TokenBalanceChange[]; timestamp: number; } ``` ### TokenBalanceChange ```typescript interface TokenBalanceChange { token: TokenInfo; previousBalance: bigint; currentBalance: bigint; delta: bigint; // positive = received, negative = sent } ``` ### BalanceWatcherOptions ```typescript interface BalanceWatcherOptions { intervalMs?: number; // Poll interval (default: 15000) minIntervalMs?: number; // Floor (default: 5000) emitInitial?: boolean; // Emit current balances immediately } ``` --- ## Spending Limits ### SpendingLimits Raw on-chain spending limits. ```typescript interface SpendingLimits { dailyLimit: bigint; dailySpent: bigint; dailyRemaining: bigint; dayResetTime: Date; timeUntilReset: number; // milliseconds transactionLimit: bigint; isPaused: boolean; lastUpdated: Date; chainId: number; } ``` ### FormattedSpendingLimits Human-readable version for UI display, returned by `sdk.getFormattedSpendingLimits()`. ```typescript interface FormattedSpendingLimits { dailyLimit: string; dailyLimitValue: number; dailySpent: string; dailySpentValue: number; dailyRemaining: string; dailyRemainingValue: number; dailyUsedPercentage: number; // 0–100 timeUntilReset: string; // e.g. "6h 23m" transactionLimit: string; transactionLimitValue: number; isPaused: boolean; hasDailyLimit: boolean; hasTransactionLimit: boolean; } ``` ### LimitCheckResult Returned by `sdk.checkSpendingLimit()`. ```typescript interface LimitCheckResult { allowed: boolean; message?: string; suggestions?: string[]; } ``` --- ## Sessions ### SessionConfig ```typescript interface SessionConfig { duration: number; // seconds maxValue?: bigint; // max transfer per session tx allowedTokens?: string[]; // token addresses allowedRecipients?: string[]; } ``` ### SessionSignature ```typescript interface SessionSignature { r: string; s: string; v: number; } ``` ### KeyPair ```typescript interface KeyPair { privateKey: string; publicKey: string; address: string; } ``` --- ## Transaction Summary ### TransactionSummary Returned by `sdk.getTransactionSummary()`. ```typescript interface TransactionSummary { title: string; description: string; details: ActionDetails; risks: RiskWarning[]; gasCost: string; expiresIn: number; } ``` ### RiskWarning ```typescript interface RiskWarning { level: 'info' | 'warning' | 'danger'; message: string; code: string; } ``` --- ## Multisig (ADR-0037) ### MultisigPolicy ```typescript interface MultisigPolicy { enabled: boolean; threshold: number; protectedActionMask: number; proposalTtl: number; disableSessions: boolean; } ``` ### TransactionProposal ```typescript interface TransactionProposal { proposalId: string; identityKeyHash: string; proposerKeyHash: string; targetChain: number; actionType: number; actionHash: string; actionPayload: string; createdAt: bigint; expiresAt: bigint; approvalCount: number; requiredThreshold: number; state: 'none' | 'pending' | 'approved' | 'executed' | 'cancelled' | 'expired'; } ``` --- ## Enterprise Manager ### EnterpriseManagerConfig ```typescript interface EnterpriseManagerConfig { sdk: VeridexSDK; maxConcurrency?: number; // default: 3 } ``` ### BatchVaultRequest / BatchVaultResult ```typescript interface BatchVaultRequest { keyHashes: string[]; chainIds?: number[]; maxConcurrency?: number; } interface BatchVaultResult { total: number; succeeded: number; failed: number; results: Array<{ keyHash: string; result: MultiChainVaultResult | null; error?: string; }>; } ``` ### BatchTransferRequest / BatchTransferResult ```typescript interface BatchTransferRequest { transfers: TransferParams[]; signer: any; maxConcurrency?: number; } interface BatchTransferResult { total: number; succeeded: number; failed: number; results: Array<{ params: TransferParams; result: TransferResult | null; error?: string; }>; } ``` ### BatchSpendingLimitRequest / BatchSpendingLimitResult ```typescript interface BatchSpendingLimitRequest { updates: Array<{ newLimit: bigint }>; signer: any; } interface BatchSpendingLimitResult { total: number; succeeded: number; failed: number; results: Array<{ newLimit: bigint; result: TransferResult | null; error?: string; }>; } ``` ### BatchLifecycleEvent Callback events emitted during batch operations. ```typescript type BatchLifecycleEvent = | { type: 'started'; total: number } | { type: 'item_started'; index: number; total: number } | { type: 'item_completed'; index: number; total: number; success: boolean; error?: string } | { type: 'completed'; succeeded: number; failed: number; total: number }; ``` --- ## Error Types ### VeridexError ```typescript class VeridexError extends Error { readonly code: VeridexErrorCode; readonly chain: string | undefined; readonly cause: unknown; readonly retryable: boolean; } ``` ### VeridexErrorCode ```typescript enum VeridexErrorCode { NO_CREDENTIAL = 'NO_CREDENTIAL', UNAUTHORIZED = 'UNAUTHORIZED', INVALID_SIGNATURE = 'INVALID_SIGNATURE', VAULT_NOT_FOUND = 'VAULT_NOT_FOUND', VAULT_PAUSED = 'VAULT_PAUSED', PROTOCOL_PAUSED = 'PROTOCOL_PAUSED', INSUFFICIENT_FUNDS = 'INSUFFICIENT_FUNDS', DAILY_LIMIT_EXCEEDED = 'DAILY_LIMIT_EXCEEDED', INVALID_PAYLOAD = 'INVALID_PAYLOAD', INVALID_ACTION = 'INVALID_ACTION', EXPIRED = 'EXPIRED', VAA_ALREADY_PROCESSED = 'VAA_ALREADY_PROCESSED', INVALID_VAA = 'INVALID_VAA', INVALID_EMITTER = 'INVALID_EMITTER', BRIDGE_ERROR = 'BRIDGE_ERROR', RPC_ERROR = 'RPC_ERROR', TIMEOUT = 'TIMEOUT', RELAYER_ERROR = 'RELAYER_ERROR', SESSION_EXPIRED = 'SESSION_EXPIRED', SESSION_INVALID = 'SESSION_INVALID', UNSUPPORTED_FEATURE = 'UNSUPPORTED_FEATURE', UNKNOWN = 'UNKNOWN', } ``` --- ## Cross-Origin Auth ### CrossOriginAuthConfig Passed to `createCrossOriginAuth()`. ```typescript interface CrossOriginAuthConfig { /** The Veridex RP ID (defaults to veridex.network) */ rpId?: string; /** Auth portal URL for popup/redirect flow */ authPortalUrl?: string; /** Relayer API URL for server-side session tokens */ relayerUrl?: string; /** Authentication mode: popup or redirect */ mode?: 'popup' | 'redirect'; /** Popup window features (CSS string) */ popupFeatures?: string; /** Timeout for auth operations in ms */ timeout?: number; /** Callback URL for redirect mode */ redirectUri?: string; } ``` ### CrossOriginSession Returned by `auth.authenticate()`, `auth.connectWithVeridex()`, and `auth.authenticateAndCreateSession()`. ```typescript interface CrossOriginSession { /** User's vault address */ address: string; /** Session key public key */ sessionPublicKey: string; /** Session key (encrypted, stored on client) */ encryptedSessionKey?: string; /** When the session expires */ expiresAt: number; /** Proof of passkey ownership */ signature: WebAuthnSignature; /** The credential used */ credential: PasskeyCredential; /** Server-validated session token ID (from relayer) */ serverSessionId?: string; /** Relayer-issued challenge binding for server session creation */ serverChallengeId?: string; } ``` ### ServerSessionToken Returned by `auth.createServerSession()` and `auth.authenticateAndCreateSession()`. ```typescript interface ServerSessionToken { id: string; keyHash: string; appOrigin: string; permissions: string[]; expiresAt: number; createdAt: number; } ``` ### AuthPortalMessage Message format exchanged between the SDK and the auth portal popup. ```typescript interface AuthPortalMessage { type: 'VERIDEX_AUTH_REQUEST' | 'VERIDEX_AUTH_RESPONSE' | 'VERIDEX_AUTH_ERROR'; payload: CrossOriginSession | { error: string; code: string }; origin: string; state?: string; } ``` ### AUTH_MESSAGE_TYPES ```typescript const AUTH_MESSAGE_TYPES = { AUTH_REQUEST: 'VERIDEX_AUTH_REQUEST', AUTH_RESPONSE: 'VERIDEX_AUTH_RESPONSE', AUTH_ERROR: 'VERIDEX_AUTH_ERROR', } as const; ``` --- ## Wallet Backup & Recovery ### WalletBackupConfig Passed to the `WalletBackupManager` constructor. ```typescript interface WalletBackupConfig { /** PasskeyManager instance (must be configured with rpId and relayerUrl) */ passkey: PasskeyManager; /** Relayer API URL */ relayerUrl: string; /** API key for relayer authentication */ apiKey?: string; } ``` ### BackupStatus Returned by `backup.getBackupStatus()`. ```typescript interface BackupStatus { /** Whether an encrypted archive exists on the relayer */ hasArchive: boolean; /** Archive schema version */ archiveVersion: number | null; /** When the archive was last updated (epoch ms) */ archiveUpdatedAt: number | null; /** Number of active guardians (from relayer) */ guardianCount: number; /** Whether PRF is supported (true = strong backup, false = rawId fallback) */ prfSupported: boolean; } ``` ### WalletArchivePayload Internal structure of an encrypted backup archive. ```typescript interface WalletArchivePayload { /** All stored credentials */ credentials: SerializedCredential[]; /** Backup metadata */ meta: { version: number; createdAt: number; deviceInfo?: string; }; } ``` --- ## Full Import Example ```typescript // Config type VeridexConfig, type SimpleSDKConfig, type ChainConfig, type ChainName, type NetworkType, // Passkey type PasskeyCredential, type WebAuthnSignature, // Actions type TransferParams, type ExecuteParams, type BridgeParams, type PreparedTransfer, type PreparedBridge, // Results type DispatchResult, type TransferResult, type BridgeResult, type CrossChainFees, // Balance type TokenBalance, type PortfolioBalance, type TokenInfo, type BalanceChangeEvent, type TokenBalanceChange, type BalanceWatcherOptions, // Spending Limits type SpendingLimits, type FormattedSpendingLimits, type LimitCheckResult, // Sessions type SessionConfig, type SessionSignature, type KeyPair, // Summaries type TransactionSummary, type RiskWarning, // Identity type UnifiedIdentity, type VaultInfo, // Cross-Origin Auth type CrossOriginAuthConfig, type CrossOriginSession, type ServerSessionToken, type AuthPortalMessage, // Wallet Backup & Recovery type WalletBackupConfig, type BackupStatus, type WalletArchivePayload, // Enterprise type EnterpriseManagerConfig, type BatchVaultRequest, type BatchVaultResult, type BatchTransferRequest, type BatchTransferResult, type BatchSpendingLimitRequest, type BatchSpendingLimitResult, type BatchLifecycleEvent, // Multisig type MultisigPolicy, type TransactionProposal, // Errors VeridexError, VeridexErrorCode, } from '@veridex/sdk'; ``` ================================================================================ URL: /api-reference/errors ================================================================================ # Error Handling The Veridex SDK ships a **unified error system** that normalizes chain-specific errors from EVM, Solana, Starknet, and Stacks into a single `VeridexError` class. Catch one type everywhere — no more guessing which chain threw what. ## VeridexError Class ```typescript try { await sdk.transferViaRelayer({ targetChain: 10004, token: USDC, recipient: '0x...', amount: 1_000_000n, }); } catch (err) { if (err instanceof VeridexError) { console.log(err.code); // VeridexErrorCode.INSUFFICIENT_FUNDS console.log(err.message); // "Insufficient funds in vault." console.log(err.chain); // "base" console.log(err.retryable); // false console.log(err.cause); // original ethers / RPC error } } ``` ### Properties | Property | Type | Description | |----------|------|-------------| | `code` | `VeridexErrorCode` | Unified error code (see table below) | | `message` | `string` | Human-readable description | | `chain` | `string \| undefined` | Chain that produced the error (`'base'`, `'solana'`, `'starknet'`, etc.) | | `cause` | `unknown` | Original chain-specific error object | | `retryable` | `boolean` | Whether the operation could succeed if retried | --- ## Error Codes ### Wallet & Identity | Code | Default Message | Retryable | |------|----------------|-----------| | `NO_CREDENTIAL` | No credential set. Call `passkey.register()` or `passkey.setCredential()` first. | No | | `UNAUTHORIZED` | Unauthorized: the signer is not an owner of this vault. | No | | `INVALID_SIGNATURE` | Signature verification failed. | No | ### Vault State | Code | Default Message | Retryable | |------|----------------|-----------| | `VAULT_NOT_FOUND` | Vault does not exist. Call `ensureVault()` first. | No | | `VAULT_PAUSED` | Vault is paused. Unpause before continuing. | No | | `PROTOCOL_PAUSED` | Protocol is paused. Try again later. | No | ### Balance & Limits | Code | Default Message | Retryable | |------|----------------|-----------| | `INSUFFICIENT_FUNDS` | Insufficient funds in vault. | No | | `DAILY_LIMIT_EXCEEDED` | Daily spending limit exceeded. Try a smaller amount or wait for reset. | No | ### Payload & Dispatch | Code | Default Message | Retryable | |------|----------------|-----------| | `INVALID_PAYLOAD` | Invalid action payload. | No | | `INVALID_ACTION` | Unknown or invalid action type. | No | | `EXPIRED` | Prepared transaction has expired. Please prepare again. | No | ### Cross-Chain (Wormhole) | Code | Default Message | Retryable | |------|----------------|-----------| | `VAA_ALREADY_PROCESSED` | This cross-chain message has already been processed (replay protection). | No | | `INVALID_VAA` | Invalid VAA: verification failed. | No | | `INVALID_EMITTER` | Invalid emitter: message source is not trusted. | No | | `BRIDGE_ERROR` | Cross-chain bridge error. | No | ### Network & Infrastructure | Code | Default Message | Retryable | |------|----------------|-----------| | `RPC_ERROR` | RPC call failed. The node may be unavailable. | **Yes** | | `TIMEOUT` | Operation timed out. | **Yes** | | `RELAYER_ERROR` | Relayer submission failed. | **Yes** | ### Sessions | Code | Default Message | Retryable | |------|----------------|-----------| | `SESSION_EXPIRED` | Session key has expired. Create a new session. | No | | `SESSION_INVALID` | Session key is invalid or revoked. | No | ### Capability | Code | Default Message | Retryable | |------|----------------|-----------| | `UNSUPPORTED_FEATURE` | This feature is not supported on the current chain. | No | ### Catch-All | Code | Default Message | Retryable | |------|----------------|-----------| | `UNKNOWN` | An unknown error occurred. | No | --- ## normalizeError() The `normalizeError` function converts any chain-specific error into a `VeridexError`. The SDK calls this internally at every boundary, but you can use it directly when working with chain clients. ```typescript try { await evmClient.dispatch(signature, pubX, pubY, payload, signer); } catch (err) { const veridexErr = normalizeError(err, 'base'); // veridexErr.code === 'INSUFFICIENT_FUNDS' (if ethers threw "insufficient funds") } ``` ### Chain-Specific Mappings The normalizer recognizes errors from each chain's native error surface: **EVM regex patterns** match against the error message: | Pattern | Maps To | |---------|---------| | `insufficient funds` | `INSUFFICIENT_FUNDS` | | `execution reverted.*paused` | `VAULT_PAUSED` | | `execution reverted.*unauthorized` or `not owner` | `UNAUTHORIZED` | | `daily.*limit` | `DAILY_LIMIT_EXCEEDED` | | `nonce.*expired` or `nonce.*too low` | `EXPIRED` | | `already.*processed` or `already known` | `VAA_ALREADY_PROCESSED` | | `invalid.*signature` or `ECDSA` | `INVALID_SIGNATURE` | | `timeout` or `ETIMEDOUT` or `ECONNREFUSED` | `TIMEOUT` | | `could not detect network` or `failed to fetch` | `RPC_ERROR` | **ethers error codes** are also detected: | ethers Code | Maps To | |-------------|---------| | `INSUFFICIENT_FUNDS` | `INSUFFICIENT_FUNDS` | | `CALL_EXCEPTION` | `RPC_ERROR` | | `NETWORK_ERROR` / `SERVER_ERROR` | `RPC_ERROR` (retryable) | | `TIMEOUT` | `TIMEOUT` (retryable) | **Anchor program error codes** are extracted from `custom program error: 0x...`: | Anchor Code | Maps To | |-------------|---------| | 6000 | `PROTOCOL_PAUSED` | | 6001 | `VAULT_PAUSED` | | 6002 | `VAA_ALREADY_PROCESSED` | | 6003–6004 | `INVALID_EMITTER` | | 6005 | `UNAUTHORIZED` | | 6006 | `BRIDGE_ERROR` | | 6007–6008 | `INVALID_PAYLOAD` | | 6009 | `INVALID_ACTION` | | 6010 | `DAILY_LIMIT_EXCEEDED` | | 6011 | `INSUFFICIENT_FUNDS` | | 6012 | `UNAUTHORIZED` | | 6013 | `INVALID_VAA` | **Starknet patterns** match against felt/Cairo error messages: | Pattern | Maps To | |---------|---------| | `insufficient.*balance` or `not enough` | `INSUFFICIENT_FUNDS` | | `PAUSED` or `is paused` | `VAULT_PAUSED` | | `UNAUTHORIZED` or `not.*authorized` | `UNAUTHORIZED` | | `already.*processed` | `VAA_ALREADY_PROCESSED` | | `invalid.*signature` | `INVALID_SIGNATURE` | **Clarity error codes** are extracted from `(err uNNN)`: | Clarity Code | Maps To | |--------------|---------| | 100 | `UNAUTHORIZED` | | 101 | `VAULT_PAUSED` | | 102 | `INVALID_PAYLOAD` | | 103 | `INSUFFICIENT_FUNDS` | | 104 | `DAILY_LIMIT_EXCEEDED` | | 105 | `VAULT_NOT_FOUND` | | 106 | `INVALID_SIGNATURE` | | 200 | `VAA_ALREADY_PROCESSED` | | 201 | `INVALID_VAA` | | 202 | `INVALID_EMITTER` | --- ## Handling Patterns ### Switch on Error Code ```typescript try { await sdk.transferViaRelayer(params); } catch (err) { if (!(err instanceof VeridexError)) throw err; switch (err.code) { case VeridexErrorCode.INSUFFICIENT_FUNDS: showToast('Not enough tokens in your vault'); break; case VeridexErrorCode.DAILY_LIMIT_EXCEEDED: showToast('Daily limit reached — try again after reset'); break; case VeridexErrorCode.SESSION_EXPIRED: await sdk.sessions.create({ duration: 3600 }); break; // retry case VeridexErrorCode.NO_CREDENTIAL: router.push('/login'); break; default: if (err.retryable) { await sleep(2000); // retry the operation } else { showToast(err.message); } } } ``` ### Retry Retryable Errors ```typescript async function withRetry(fn: () => Promise, maxRetries = 3): Promise { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await fn(); } catch (err) { if (err instanceof VeridexError && err.retryable && attempt < maxRetries - 1) { await new Promise(r => setTimeout(r, 1000 * 2 ** attempt)); continue; } throw err; } } throw new Error('unreachable'); } const result = await withRetry(() => sdk.transferViaRelayer(params)); ``` ### React Error Boundary ```tsx class VeridexErrorBoundary extends Component< { children: ReactNode; fallback: ReactNode }, { hasError: boolean; error?: VeridexError } > { state = { hasError: false, error: undefined as VeridexError | undefined }; static getDerivedStateFromError(error: Error) { if (error instanceof VeridexError) { return { hasError: true, error }; } return { hasError: true }; } render() { if (this.state.hasError) { if (this.state.error?.code === VeridexErrorCode.NO_CREDENTIAL) { return Please log in with your passkey.; } return this.props.fallback; } return this.props.children; } } ``` --- ## Agent SDK Errors The Agent SDK (`@veridex/agentic-payments`) has its own error class: ```typescript ``` | Code | Description | Retryable | |------|-------------|-----------| | `LIMIT_EXCEEDED` | Transaction exceeds daily or per-tx spending limit | No | | `INSUFFICIENT_BALANCE` | Not enough tokens in session wallet | No | | `SESSION_EXPIRED` | Session key has expired | No | | `CHAIN_NOT_SUPPORTED` | Chain ID not in `allowedChains` | No | | `TOKEN_NOT_SUPPORTED` | Token not available on target chain | No | | `NETWORK_ERROR` | Network or RPC failure | Yes | ================================================================================ URL: /api-reference/agents-react ================================================================================ # Agents React API Reference Full API for `@veridex/agents-react` — React hooks for agent runtimes and fleet operations. --- ## Providers ### `AgentProvider` ```tsx {children} ``` | Prop | Type | Description | |------|------|-------------| | `runtime` | `AgentRuntime` | Agent instance from `createAgent()` | ### `ControlPlaneProvider` ```tsx {children} ``` | Prop | Type | Description | |------|------|-------------| | `client` | `ControlPlaneClient \| RemoteControlPlaneClient` | Control plane client | --- ## Context Hooks ### `useAgentContext` ```tsx const context = useAgentContext(); ``` Returns the raw agent context. Prefer specific hooks below. ### `useControlPlaneContext` ```tsx const context = useControlPlaneContext(); ``` Returns the raw control plane context. --- ## Agent Hooks ### `useAgent` ```tsx const { definition, state } = useAgent(); ``` | Field | Type | Description | |-------|------|-------------| | `definition` | `AgentDefinition` | Agent name, description, tools, instructions | | `state` | `AgentState` | Current runtime state | ### `useRun` ```tsx const { run, result, state, isLoading, error } = useRun(); ``` | Field | Type | Description | |-------|------|-------------| | `run` | `(input: string) => Promise` | Start a new run | | `result` | `RunResult \| null` | Latest run result | | `state` | `RunState` | `'idle' \| 'running' \| 'suspended' \| 'completed' \| 'failed'` | | `isLoading` | `boolean` | True while a run is active | | `error` | `Error \| null` | Last error | ### `useTurnStream` ```tsx const { stream, cancel } = useTurnStream(); ``` | Field | Type | Description | |-------|------|-------------| | `stream` | `AsyncIterable` | Async iterable of turn events | | `cancel` | `() => void` | Cancel the stream | ### `useApprovals` ```tsx const { pending, resolve } = useApprovals(); ``` | Field | Type | Description | |-------|------|-------------| | `pending` | `ApprovalRequest[]` | Pending approval requests | | `resolve` | `(id: string, approved: boolean) => void` | Approve or deny | ### `useTrace` ```tsx const { events, latest } = useTrace(); ``` | Field | Type | Description | |-------|------|-------------| | `events` | `TraceEvent[]` | All trace events for the current run | | `latest` | `TraceEvent \| null` | Most recent event | ### `useToolRegistry` ```tsx const { tools, register, remove } = useToolRegistry(); ``` | Field | Type | Description | |-------|------|-------------| | `tools` | `ToolContract[]` | Registered tools | | `register` | `(tool: ToolContract) => void` | Add a tool | | `remove` | `(name: string) => void` | Remove a tool | ### `useMemory` ```tsx const { entries, write, search, clear } = useMemory(); ``` | Field | Type | Description | |-------|------|-------------| | `entries` | `MemoryEntry[]` | All memory entries | | `write` | `(entry: WriteParams) => Promise` | Write a memory entry | | `search` | `(params: SearchParams) => Promise` | Semantic search | | `clear` | `(tier?: string) => Promise` | Clear entries by tier | ### `useBudget` ```tsx const { snapshot, estimateSpend } = useBudget(); ``` | Field | Type | Description | |-------|------|-------------| | `snapshot` | `BudgetSnapshot` | `{ spent, budget, percentUtilized }` | | `estimateSpend` | `(model: string, tokens: number) => number` | Estimate cost | ### `useContextPlan` ```tsx const { plan, recompile } = useContextPlan(); ``` | Field | Type | Description | |-------|------|-------------| | `plan` | `ContextPlan` | Compiled context sections with token counts | | `recompile` | `() => void` | Re-trigger context compilation | ### `usePayments` ```tsx const { balance, send, history } = usePayments(); ``` | Field | Type | Description | |-------|------|-------------| | `balance` | `TokenBalance[]` | Current balances | | `send` | `(params: TransferParams) => Promise` | Send tokens | | `history` | `PaymentRecord[]` | Transaction history | ### `useAgentIdentity` ```tsx const { identity, isVerified } = useAgentIdentity(); ``` | Field | Type | Description | |-------|------|-------------| | `identity` | `AgentIdentity \| null` | ERC-8004 on-chain identity | | `isVerified` | `boolean` | Whether identity is registered and valid | ### `useAgentReputation` ```tsx const { score, history } = useAgentReputation(); ``` | Field | Type | Description | |-------|------|-------------| | `score` | `number` | Aggregate reputation score | | `history` | `FeedbackEntry[]` | Historical feedback entries | ### `useOpenClawBridge` ```tsx const { importSkills, translateSession } = useOpenClawBridge(); ``` | Field | Type | Description | |-------|------|-------------| | `importSkills` | `(manifest: SkillManifest) => Promise` | Import OpenClaw skills | | `translateSession` | `(session: OpenClawSession) => TranslationResult` | Translate a session | --- ## Operator Hooks Require `ControlPlaneProvider` in the component tree. ### `useFleetStatus` ```tsx const { agents, activeRuns, healthSummary } = useFleetStatus(); ``` ### `useOperatorTasks` ```tsx const { tasks, assign, complete, cancel } = useOperatorTasks(); ``` ### `useDeployments` ```tsx const { deployments, deploy, rollback, scale } = useDeployments(); ``` ### `useIncidents` ```tsx const { incidents, acknowledge, resolve, escalate } = useIncidents(); ``` ### `useWorkspaceResources` ```tsx const { workspaces, create, archive } = useWorkspaceResources(); ``` ### `useEvals` ```tsx const { suites, run, results } = useEvals(); ``` ### `usePolicyBindings` ```tsx const { bindings, bind, unbind, update } = usePolicyBindings(); ``` --- Usage guide with examples Core wallet/passkey hooks ================================================================================ URL: /resources/architecture-decisions ================================================================================ # Architecture Decisions Veridex maintains Architecture Decision Records (ADRs) for all significant protocol design choices. Below is a curated selection of the most impactful decisions that help developers understand **why** the protocol works the way it does. > **Note:** ADRs document the context, decision, and consequences of architectural choices. Status can be **Accepted** (implemented), **Proposed** (in review), or **Deprecated**. --- ## Core Architecture ### ADR-0001: Hub-and-Spoke Architecture **Status:** Accepted The Veridex protocol uses a **hub-and-spoke model** where one chain (Base) serves as the identity and configuration hub, while other chains are spokes that receive instructions via Wormhole cross-chain messages. This design choice provides: - **Single source of truth** for identity, vault configuration, and spending limits - **Consistent addresses** across all EVM chains via CREATE2-based vault factories - **Simplified security model** — authentication happens once on the hub - **Extensibility** — new chains are added as spokes without modifying the hub The tradeoff is cross-chain latency for spoke operations that require hub authorization. ### ADR-0002: WebAuthn / secp256r1 Only **Status:** Accepted Veridex uses **passkeys (WebAuthn)** as the sole authentication mechanism — no seed phrases, no private key management, no MetaMask dependency. The P-256 (secp256r1) elliptic curve is used for all cryptographic operations. **Why passkeys only:** - Users authenticate with biometrics (Face ID, fingerprint, Windows Hello) - Credentials are hardware-bound and phishing-resistant - Cross-device sync via iCloud Keychain, Google Password Manager - No browser extensions required **Consequences:** - Requires hardware that supports WebAuthn (most modern devices) - On-chain signature verification uses precompile (`P256VERIFY`) - Recovery requires backup passkeys or social recovery (not seed phrases) ### ADR-0003: Wormhole Cross-Chain Messaging **Status:** Accepted Wormhole was selected as the cross-chain messaging layer for hub-to-spoke communication. All administrative actions (vault creation, config changes, spending limits) are dispatched from the hub via Wormhole VAAs (Verified Action Approvals). **Key factors:** - Guardian network provides decentralized attestation - Broad EVM + non-EVM chain support (Solana, Sui, Aptos) - Query API for low-latency verification without waiting for full finality - Established security track record --- ## UX Optimizations ### ADR-0004: Session Keys **Status:** Accepted Session keys allow dApps to execute transactions on behalf of users without requiring a biometric prompt for every action. A temporary secp256k1 key pair is registered on-chain with configurable constraints: - **Duration** — session expires after N seconds - **Value limits** — max transfer amount per transaction - **Token whitelist** — only specified tokens can be transferred - **Recipient whitelist** — only specified addresses can receive Session keys are disabled when multisig (ADR-0037) is enabled. ### ADR-0009: Spending Limits **Status:** Accepted On-chain spending limits provide a security layer against compromised passkeys: - **Daily limit** — caps total spending in a 24-hour rolling window - **Per-transaction limit** — caps individual transfer amounts - **Circuit breaker** — auto-pauses vault on limit violation - **Emergency pause** — owner can freeze vault instantly Limits are enforced by the vault contract and can be updated via passkey-signed transactions through the hub. ### ADR-0018: Cross-Domain Passkey Sharing **Status:** Accepted Enables passkey credentials to work across different origins (domains) using the WebAuthn Related Origins (ROR) specification. This allows: - Auth portal at `auth.veridex.network` creates credentials - Third-party dApps at `app.example.com` can authenticate with the same passkey - No popup windows or redirects needed (when ROR is supported) - Falls back to cross-origin iframe messaging on older browsers --- ## Chain Expansion ### ADR-0026: Stacks Integration **Status:** Proposed Strategy for integrating Stacks (Bitcoin L2) as a spoke chain: - Clarity smart contracts for vault management - sBTC support for Bitcoin-native assets - Wormhole integration for cross-chain messaging to/from Stacks - Passkey authentication via Clarity contract verification ### ADR-0029: ERC-8004 Agent Identity **Status:** Proposed Integration of the ERC-8004 standard for trustless agent identity and reputation. Provides on-chain identity registries for AI agents operating on behalf of users: - Singleton registries deployed via CREATE2 on all EVM chains - Agent registration with metadata, capabilities, and operator info - Reputation tracking via feedback and validation systems - Universal Agent Identifiers (UAIs) for cross-chain agent identity --- ## Security ### ADR-0035: Enterprise Treasury Architecture **Status:** Accepted Defines the security architecture for enterprise-grade treasury management: - Cryptographically enforced access controls - Multi-signer approval workflows - Spending limit hierarchies (org → team → individual) - Audit logging and compliance hooks ### ADR-0037: Threshold Multisig Authorization **Status:** Proposed Adds threshold-based multi-key transaction authorization: - **TransactionPolicy** on the Hub defines threshold requirements - Protected actions (transfer, execute, bridge, config) require N-of-M approval - Proposal → Approve → Execute lifecycle - Proposals expire after configurable TTL (default: 24 hours) - Sessions are disabled for threshold-governed identities ```typescript // Example: require 2-of-3 approval for transfers await sdk.multisig.configurePolicy({ signature, threshold: 2, protectedActionMask: PROTECTED_ACTION.TRANSFER | PROTECTED_ACTION.BRIDGE, signer, }); ``` ### ADR-0042: Veridex Agent Fabric as a General-Purpose Runtime **Status:** Proposed This decision expands Veridex from "agent commerce infrastructure" into a broader agent-runtime thesis: - `@veridex/agents` should be usable for general-purpose agents, not only commerce agents - payments remain a native advantage, but not a mandatory starting point - the framework must compete on orchestration maturity, TypeScript DX, and governance - React hooks, adapters, OpenClaw interop, and the control plane are part of the framework story The goal is to combine: - serious runtime primitives - strong TypeScript ergonomics - governance, approvals, traces, payments, and enterprise control by default ### ADR-0043: Veridex Workforce OS Layer Above Agent Fabric **Status:** Proposed This follow-up decision says the runtime is necessary but not sufficient. Veridex should also define the operating layer that lets teams manage an entire fleet of agents as one governed system: - fleet registry and versioned discovery for deployed agents - task routing, inboxes, retries, scheduling, priorities, and SLAs - shared workspace resources such as tools, knowledge, secrets, and policies - deployment lifecycle management including drafts, canaries, rollback, and hot updates - incident controls, governance, and fleet-level evaluations over time - a command-center experience backed by typed APIs and manifests, not natural language alone The core distinction is: - `@veridex/agents` stays the runtime kernel - `@veridex/agents-control-plane` becomes the system-services backbone - the Workforce OS sits above both as the operator-facing management layer --- ## Full ADR Index The complete set of 43+ ADRs is maintained in the source repository under `docs/architecture/decisions/`. Each ADR follows a consistent template with Context, Decision, Status, and Consequences sections. | ADR | Title | Status | |-----|-------|--------| | 0001 | Hub-and-Spoke Architecture | Accepted | | 0002 | WebAuthn / secp256r1 Only | Accepted | | 0003 | Wormhole Cross-Chain Messaging | Accepted | | 0004 | Session Keys for UX | Accepted | | 0005 | Social Recovery Integration | Accepted | | 0006 | Backup Passkey Registration | Accepted | | 0008 | Human-Readable Transactions | Accepted | | 0009 | Spending Limits Configuration | Accepted | | 0012 | x402 Protocol Agentic Payments | Accepted | | 0014 | Agent SDK Capability Negotiation | Accepted | | 0018 | Cross-Domain Passkey Sharing | Accepted | | 0026 | Stacks Integration | Proposed | | 0029 | ERC-8004 Agent Identity | Proposed | | 0030 | Layered Passkey Recovery | Accepted | | 0033 | Avalanche Hub Deployment | Accepted | | 0034 | Decentralized Relayer Network | Accepted | | 0035 | Enterprise Treasury AI Trust | Accepted | | 0037 | Threshold Multisig | Proposed | | 0042 | Veridex Agent Fabric as a General-Purpose Runtime | Proposed | | 0043 | Veridex Workforce OS Layer Above Agent Fabric | Proposed | ================================================================================ URL: /resources/contributing ================================================================================ # Contributing We want contributions across: - docs corrections - examples - tests - runtime improvements - interop bridges - control-plane hardening > **Note:** Some of the newer agent-framework surfaces are still evolving and parts of the docs may still be incomplete or slightly behind the code. If you notice a mismatch, please tell us or send a fix. Those reports are useful, not annoying. ## What to read first - The root repository guide: [`CONTRIBUTING.md`](https://github.com/Veridex-Protocol/demo/blob/main/CONTRIBUTING.md) - Internal ADRs under `docs/architecture/decisions/` - The framework overview at `/guides/veridex-agents` ## Contribution standards ### Product standards - Keep `@veridex/agents` useful for general-purpose agents, not only commerce agents. - Keep payments, identity, and trust **native but optional**. - Prefer explicit typed surfaces over magic or hidden runtime behavior. - Preserve interoperability where possible instead of introducing closed abstractions. ### Runtime standards - Keep the core runtime small and inspectable. - New tools, hooks, and transports should be typed and documented. - Changes that affect runtime semantics should include tests. - New enterprise or control-plane behavior should explain tenancy, persistence, and failure behavior clearly. ### Documentation standards - Update docs when public APIs or package boundaries change. - If a change alters framework direction, add or update an ADR. - Be honest about maturity. If something is still rough, say so plainly. - Prefer examples that show real use, not only idealized demos. ## Where to contribute | Area | Good for | |---|---| | `docs/` | ADRs, internal architecture notes, package and implementation docs | | `documentation/` | Public docs site pages, navigation, onboarding, examples | | `packages/agents` | Core runtime, tools, hooks, memory, transports, policies | | `packages/agents-react` | React provider and hooks | | `packages/agents-adapters` | Imports, exports, and live bridges | | `packages/agents-openclaw` | OpenClaw / Pi interop | | `packages/agents-control-plane` | Control-plane service, clients, and persistence | | `packages/agent-sdk` | Payment protocols, trust, identity, and commerce primitives | | `packages/sdk` | Passkeys, sessions, wallets, and chain integrations | ## Pull request expectations - explain what changed - explain why it changed - include tests when behavior changed - update docs when public behavior changed - avoid weakening existing safety or control boundaries without an ADR ## Especially useful contributions right now - better examples for general-purpose agents - migration examples from LangGraph, OpenAI Agents, and OpenClaw - docs clarifying package boundaries - more transport and interop tests - control-plane operational hardening and deployment feedback ## If you find something broken Please report: - the package - the version or commit - what you expected - what actually happened - a small repro if possible That helps much more than a generic “it doesn’t work.” ================================================================================ URL: /resources/llm-access ================================================================================ # AI & LLM Access Veridex documentation is designed to be easily consumed by AI agents, coding assistants, and large language models. Use these resources to feed our docs into your preferred AI tool. ## Quick Copy Need to paste our full docs into ChatGPT, Claude, or another AI assistant? Download the complete documentation as a single plain-text file: **[Download llms-full.txt →](/llms-full.txt)** This file contains all 48 pages of documentation (~420 KB) concatenated into clean, readable markdown — no HTML, no JSX, no imports. Just content. ## Discovery (`llms.txt`) We follow the [llms.txt standard](https://llmstxt.org/) — a structured index file that helps AI agents discover and navigate our documentation automatically. ``` https://docs.veridex.network/llms.txt ``` This lightweight file lists every page in the docs with a short description, organized by section. Point your agent here first. ## API Endpoint For programmatic access, use our API to fetch docs as plain text: ```bash # Full documentation curl https://docs.veridex.network/api/docs-for-llm # Filter by section curl https://docs.veridex.network/api/docs-for-llm?section=guides curl https://docs.veridex.network/api/docs-for-llm?section=api-reference curl https://docs.veridex.network/api/docs-for-llm?section=chains ``` ### Available Sections | Section | Description | |---------|-------------| | `getting-started` | Quick start, installation, core concepts, FAQ | | `guides` | Wallets, transfers, sessions, agents, React hooks | | `api-reference` | SDK, Agent SDK, Relayer API | | `chains` | EVM, Solana, Aptos, Sui, Starknet, Stacks | | `integrations` | React, Next.js, DeFi, gaming, payments | | `security` | Audits, best practices, recovery | | `resources` | Architecture decisions, glossary, changelog | ## How to Use ### ChatGPT / Claude / Gemini 1. Download [llms-full.txt](/llms-full.txt) 2. Attach it to your conversation or paste the contents 3. Ask your questions about Veridex ### Cursor / GitHub Copilot / Windsurf Add the docs URL to your AI coding assistant's context: ``` https://docs.veridex.network/llms-full.txt ``` ### Custom Agents If you're building an agent that integrates with Veridex: ```python # Fetch just the guides section response = requests.get( "https://docs.veridex.network/api/docs-for-llm", params={"section": "guides"} ) docs = response.text ``` ## Staying Up to Date The `llms-full.txt` file is regenerated on every build, so it always reflects the latest documentation. The API endpoint serves the same content dynamically. ================================================================================ URL: /resources/glossary ================================================================================ # Glossary Key terms and concepts used in Veridex documentation. ## A ### Account Abstraction (AA) A paradigm where user accounts are smart contracts instead of externally owned accounts (EOAs). Enables features like gasless transactions, session keys, and custom authentication. ### Authenticator A device or software that can generate and verify passkey credentials. Examples: TouchID, FaceID, Windows Hello, hardware security keys. ## B ### Biometric Authentication Using biological characteristics (fingerprint, face, iris) to verify identity. Passkeys use biometrics to protect the signing key. ### Bridge A protocol for moving assets between different blockchains. Veridex uses Wormhole for cross-chain transfers. ### Bundler In ERC-4337, the service that bundles multiple UserOperations into a single transaction for submission to the blockchain. ## C ### CCQ (Cross-Chain Query) Wormhole's fast path for cross-chain operations, providing 5-7 second finality instead of the standard 60+ seconds. ### Credential ID A unique identifier for a passkey credential, used to look up the correct key during authentication. ## E ### EIP-4337 Ethereum Improvement Proposal defining the Account Abstraction standard, enabling smart contract wallets without protocol changes. ### Entry Point The singleton contract in ERC-4337 that validates and executes UserOperations. ### EVM (Ethereum Virtual Machine) The runtime environment for smart contracts on Ethereum and compatible chains (Base, Optimism, Arbitrum, etc.). ## F ### FIDO2 An authentication standard that includes WebAuthn and CTAP protocols, enabling passwordless authentication. ## G ### Gasless Transactions Transactions where the user doesn't pay gas fees directly. A relayer or paymaster covers the gas costs. ### Guardian A trusted party who can help recover a wallet if access is lost. Used in social recovery systems. ## H ### Hub Chain In Veridex's hub-and-spoke architecture, the central chain (Base) where passkeys are registered and master state is maintained. ### Hub-and-Spoke Architecture pattern where a central hub coordinates with multiple spoke chains. Veridex uses this for cross-chain passkey authentication. ## P ### P-256 (secp256r1) The elliptic curve used by WebAuthn/passkeys. Also known as prime256v1 or NIST P-256. ### Passkey A cryptographic credential that replaces passwords, using public-key cryptography with biometric protection. ### Paymaster In ERC-4337, a contract that sponsors gas fees for users, enabling gasless transactions. ### Platform Authenticator A passkey authenticator built into the device (TouchID, FaceID, Windows Hello) as opposed to a roaming authenticator (USB security key). ## R ### Relayer A service that submits transactions on behalf of users, enabling gasless operations. Veridex's relayer handles transaction submission and gas payment. ### RP (Relying Party) The website or app that uses passkeys for authentication. In WebAuthn, the RP receives and verifies credentials. ### Roaming Authenticator A portable passkey authenticator like a USB security key that can be used across multiple devices. ## S ### secp256r1 See P-256. The elliptic curve standard used by passkeys. ### secp256k1 The elliptic curve used by Bitcoin and Ethereum for traditional wallets. Different from secp256r1 used by passkeys. ### Session Key A temporary key with limited permissions that can sign transactions without requiring passkey authentication for each action. ### Smart Contract Wallet A wallet implemented as a smart contract, enabling features like multi-sig, recovery, and custom authentication. ### Social Recovery A recovery mechanism where designated guardians can help restore access to a wallet. ### Spoke Chain In hub-and-spoke architecture, a chain that syncs with the hub. Veridex supports Optimism, Arbitrum, Solana, Aptos, Sui, and Starknet as spokes. ## U ### UserOperation In ERC-4337, the data structure representing a user's intended transaction, processed by bundlers and the entry point. ## V ### VAA (Verifiable Action Approval) Wormhole's standard message format, signed by guardians to verify cross-chain messages. ### Vault In Veridex, the smart contract wallet controlled by a passkey. Each user has one vault address across all chains. ### Vault Factory The contract that deploys new vault instances with deterministic addresses. ## W ### WebAuthn The W3C standard for web authentication using public-key cryptography, the foundation of passkeys. ### Wormhole A cross-chain messaging protocol used by Veridex for bridging assets and syncing state between chains. ## Common Acronyms | Acronym | Meaning | |---------|---------| | AA | Account Abstraction | | CCQ | Cross-Chain Query | | EIP | Ethereum Improvement Proposal | | EOA | Externally Owned Account | | EVM | Ethereum Virtual Machine | | RP | Relying Party | | SDK | Software Development Kit | | SPL | Solana Program Library | | VAA | Verifiable Action Approval | ================================================================================ URL: /resources/changelog ================================================================================ # Changelog All notable changes to the Veridex SDK. ## [1.0.0-beta.1] - 2024-12-XX ### Added - Initial beta release - Passkey registration and login - Session key management - Gasless transactions via relayer - Cross-chain transfers (Base, Optimism, Arbitrum) - Multi-chain balance queries - Wormhole CCQ (fast path) integration - Wormhole VAA (standard path) integration - TypeScript types and documentation ### Supported Chains - Base (Hub) - Optimism (Spoke) - Arbitrum (Spoke) - Base Sepolia (Testnet Hub) ### Known Limitations - Solana, Aptos, Sui, Starknet in development - Recovery features in beta - Session keys limited to EVM chains --- ## Versioning We use [Semantic Versioning](https://semver.org/): - **MAJOR**: Breaking changes - **MINOR**: New features (backwards compatible) - **PATCH**: Bug fixes (backwards compatible) During beta (`1.0.0-beta.x`), minor breaking changes may occur. --- ## Upgrade Guide ### From 0.x to 1.0.0-beta.1 ```typescript // Old (0.x) const sdk = new Veridex({ chain: 'base' }); // New (1.0.0-beta.1) const sdk = createSDK('base', { network: 'mainnet' }); ``` **Breaking Changes:** 1. `new Veridex()` → `createSDK()` 2. `chain` option → first argument 3. `testnet: true` → `network: 'testnet'` 4. `sdk.register()` → `sdk.passkey.register()` 5. `sdk.login()` → `sdk.passkey.authenticate()` --- ## Upcoming ### 1.0.0-beta.2 (Planned) - [ ] Solana support - [ ] Improved error messages - [ ] React hooks package - [ ] Vue composables package ### 1.0.0-beta.3 (Planned) - [ ] Aptos support - [ ] Sui support - [ ] Social recovery ### 1.0.0 (Planned) - [ ] Starknet support - [ ] Full audit completion - [ ] Production relayer - [ ] Stable API --- ## Release Process 1. Changes merged to `main` 2. Version bump and changelog update 3. npm publish with `--tag beta` 4. GitHub release created 5. Documentation updated --- ## Subscribe to Updates - **GitHub Releases**: Watch the [SDK repository](https://github.com/Veridex-Protocol/sdk) - **Discord**: Join [#announcements](https://discord.gg/veridex) - **Twitter/X**: Follow [@VeridexProtocol](https://twitter.com/VeridexProtocol) ================================================================================ URL: /resources/troubleshooting ================================================================================ # Troubleshooting Solutions for common issues when integrating Veridex. ## Passkey Issues ### "NotAllowedError: The operation either timed out or was not allowed" **Cause:** User cancelled the passkey prompt, or it timed out. **Solutions:** 1. Ensure user interaction triggered the request (button click, not page load) 2. Increase timeout if needed 3. Check that the user has a compatible authenticator ```typescript // ✅ Triggered by user interaction sdk.passkey.authenticate()}>Login // ❌ May fail - not user triggered useEffect(() => { sdk.passkey.authenticate(); // May be blocked }, []); ``` ### "InvalidStateError: An authenticator was created with this credential ID" **Cause:** Trying to register a passkey that already exists. **Solution:** Check for existing credential first: ```typescript const existing = sdk.passkey.getAllStoredCredentials(); if (existing.length > 0) { await sdk.passkey.authenticate(); } else { await sdk.passkey.register(email, name); } ``` ### "NotSupportedError: The authenticator doesn't support this algorithm" **Cause:** Browser or device doesn't support P-256/secp256r1. **Solution:** Check WebAuthn support and show appropriate message: ```typescript async function checkPasskeySupport(): Promise { if (!window.PublicKeyCredential) { return false; } // Check if platform authenticator available const available = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable(); return available; } // Usage const supported = await checkPasskeySupport(); if (!supported) { showMessage('Your device does not support passkeys. Please use a different device.'); } ``` ### "SecurityError: The operation is insecure" **Cause:** Not running on HTTPS or localhost. **Solution:** WebAuthn requires a secure context: - Use `https://` in production - Use `localhost` for development (not `127.0.0.1` or IP addresses) ## Transaction Issues ### "Transaction reverted: insufficient funds for gas" **Cause:** Relayer doesn't have gas funds, or paymaster issue. **Solutions:** 1. Check relayer status: `GET /api/v1/health` 2. Verify paymaster is funded 3. Contact support if issue persists ```typescript // Check relayer health const health = await fetch(`${RELAYER_URL}/api/v1/health`); const status = await health.json(); console.log('Relayer status:', status); ``` ### "Transaction reverted: execution reverted" **Cause:** The on-chain transaction itself failed. **Solution:** Check the specific revert reason: ```typescript try { await sdk.transferViaRelayer({ token, recipient, amount }); } catch (error) { if (error.message.includes('ERC20: transfer amount exceeds balance')) { showError('Insufficient token balance'); } else if (error.message.includes('ERC20: insufficient allowance')) { showError('Token approval needed'); } else { showError('Transaction failed: ' + error.message); } } ``` ### "NONCE_TOO_LOW" or "replacement transaction underpriced" **Cause:** Transaction ordering issue. **Solution:** Wait for pending transactions to complete: ```typescript // The SDK handles nonces automatically, but if issues occur: await sdk.waitForPendingTransactions(); await sdk.transferViaRelayer({ token, recipient, amount }); ``` ### Transaction stuck pending **Cause:** Network congestion or low gas price. **Solutions:** 1. Check transaction on block explorer 2. Wait for network congestion to clear 3. Contact support for stuck relayer transactions ## SDK Issues ### "Cannot read properties of undefined (reading 'passkey')" **Cause:** SDK not properly initialized. **Solution:** Ensure SDK is created before use: ```typescript // ✅ Correct const sdk = createSDK('base', { network: 'testnet' }); await sdk.passkey.authenticate(); // ❌ Incorrect let sdk; sdk.passkey.authenticate(); // sdk is undefined! ``` ### "Network mismatch" or wrong chain **Cause:** SDK initialized for different network than expected. **Solution:** Verify network configuration: ```typescript const sdk = createSDK('base', { network: 'testnet', // or 'mainnet' }); // Verify console.log('Chain:', sdk.getChain()); console.log('Network:', sdk.getNetwork()); ``` ### TypeScript errors with SDK **Cause:** Type definitions not matching usage. **Solution:** Import types correctly: ```typescript const config: SDKConfig = { network: 'testnet', relayerUrl: 'https://relayer.veridex.network', }; const sdk = createSDK('base', config); ``` ## Relayer Issues ### "Relayer unavailable" or connection refused **Cause:** Relayer is down or unreachable. **Solutions:** 1. Check relayer URL is correct 2. Verify network connectivity 3. Check relayer status page 4. Use fallback relayer if available ```typescript const sdk = createSDK('base', { network: 'mainnet', relayerUrl: 'https://relayer.veridex.network', fallbackRelayerUrl: 'https://relayer-backup.veridex.network', }); ``` ### "Rate limit exceeded" **Cause:** Too many requests to relayer. **Solution:** Implement backoff: ```typescript async function withRetry(fn: () => Promise, maxRetries = 3): Promise { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (error.message.includes('rate limit') && i < maxRetries - 1) { await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000)); continue; } throw error; } } throw new Error('Max retries exceeded'); } // Usage await withRetry(() => sdk.transferViaRelayer({ token, recipient, amount })); ``` ### "Invalid signature" from relayer **Cause:** Signature verification failed. **Solutions:** 1. Ensure vault is deployed on the chain 2. Check that the correct credential is being used 3. Verify the passkey hasn't been revoked ## Cross-Chain Issues ### Bridge transaction stuck **Cause:** Wormhole guardian attestation delay. **Solution:** Check bridge status and wait: ```typescript const status = await sdk.getBridgeStatus(txHash); console.log('Bridge status:', status); // { state: 'pending' | 'attested' | 'completed', estimatedTime: number } ``` ### "Wormhole: VAA not found" **Cause:** Attestation not yet available. **Solution:** Wait for attestation (can take up to 60 seconds): ```typescript async function waitForBridge(txHash: string, maxWait = 120000) { const start = Date.now(); while (Date.now() - start < maxWait) { const status = await sdk.getBridgeStatus(txHash); if (status.state === 'completed') return status; if (status.state === 'failed') throw new Error('Bridge failed'); await new Promise(r => setTimeout(r, 5000)); } throw new Error('Bridge timeout'); } ``` ## Browser-Specific Issues ### Safari: Passkey not working **Solution:** Ensure: 1. iCloud Keychain is enabled 2. User is signed into iCloud 3. Safari version 16+ ### Firefox: WebAuthn issues **Solution:** 1. Update to Firefox 119+ 2. Check `security.webauthn.ctap2` is enabled in about:config ### Chrome: "An internal error has occurred" **Solutions:** 1. Clear browser cache 2. Update Chrome 3. Check for conflicting extensions 4. Try incognito mode ## Debug Mode Enable debug mode for detailed logs: ```typescript const sdk = createSDK('base', { network: 'testnet', debug: true, // Enable debug logs }); // All SDK operations will now log details await sdk.passkey.authenticate(); // [Veridex] Starting authentication... // [Veridex] WebAuthn credential retrieved // [Veridex] Signature generated // [Veridex] Authentication complete ``` ## Getting Help If you're still stuck: 1. **Check documentation**: [docs.veridex.network](https://docs.veridex.network) 2. **Search issues**: [GitHub Issues](https://github.com/Veridex-Protocol/sdk/issues) 3. **Ask community**: [Discord](https://discord.gg/veridex) 4. **Contact support**: support@veridex.network When reporting issues, include: - SDK version - Browser and version - Device/OS - Error message and stack trace - Steps to reproduce ================================================================================ URL: /resources/migration ================================================================================ # Migration Guide Guides for upgrading between Veridex SDK versions. ## Migrating to 1.0.0-beta.1 ### From 0.x The 1.0.0 beta introduces a new API structure for better TypeScript support and modularity. #### SDK Initialization ```typescript // ❌ Old (0.x) const sdk = new Veridex({ chain: 'base', testnet: true, relayerUrl: 'https://...', }); // ✅ New (1.0.0-beta.1) const sdk = createSDK('base', { network: 'testnet', relayerUrl: 'https://...', }); ``` #### Passkey Methods ```typescript // ❌ Old await sdk.register(email, name); await sdk.login(); await sdk.getStoredCredential(); // ✅ New await sdk.passkey.register(email, name); await sdk.passkey.authenticate(); sdk.passkey.getAllStoredCredentials(); ``` #### Session Keys ```typescript // ❌ Old await sdk.createSession({ duration: 3600 }); await sdk.getActiveSession(); await sdk.revokeSession(); // ✅ New await sdk.sessions.create({ duration: 3600 }); sdk.sessions.getActive(); await sdk.sessions.revoke(); ``` #### Transfers ```typescript // ❌ Old await sdk.transfer({ token: '0x...', to: '0x...', amount: 1000000n, gasless: true, }); // ✅ New await sdk.transferViaRelayer({ token: '0x...', recipient: '0x...', amount: 1000000n, }); // Or prepare + execute with own signer: const prepared = await sdk.prepareTransfer({ token: '0x...', recipient: '0x...', amount: 1000000n }); await sdk.executeTransfer(prepared, signer); ``` #### Cross-Chain ```typescript // ❌ Old await sdk.bridge({ token: '0x...', amount: 1000000n, toChain: 'optimism', toAddress: '0x...', }); // ✅ New await sdk.crossChainTransfer({ token: '0x...', amount: 1000000n, targetChain: 'optimism', recipient: '0x...', path: 'fast', // 'fast' (CCQ) or 'standard' (VAA) }); ``` #### Getting Addresses ```typescript // ❌ Old const address = sdk.address; const evmAddress = sdk.getEVMAddress(); const solanaAddress = sdk.getSolanaAddress(); // ✅ New const address = sdk.getVaultAddress(); const evmAddress = sdk.getVaultAddress('base'); const solanaAddress = sdk.getVaultAddress('solana'); // Or use chain-specific getters const solana = sdk.getSolanaAddress(); const aptos = sdk.getAptosAddress(); ``` #### Balances ```typescript // ❌ Old const balance = await sdk.getBalance('0x...'); // Single token const allBalances = await sdk.getAllBalances(); // ✅ New const balance = await sdk.getBalance('0x...'); // Single token const allBalances = await sdk.getBalances(); // All chains, all tokens // Returns: // { // base: { ETH: '1.5', USDC: '100.00' }, // optimism: { ETH: '0.5', USDC: '50.00' }, // } ``` ### Type Changes ```typescript // ❌ Old interface TransferParams { token: string; to: string; amount: bigint; gasless?: boolean; } // ✅ New interface TransferParams { token: string; recipient: string; amount: bigint; } interface TransferViaRelayerParams { token: string; recipient: string; amount: bigint; } ``` ### Import Changes ```typescript // ❌ Old // ✅ New createSDK, type TransferParams, type SessionConfig, type SDKConfig, type VeridexSDK, } from '@veridex/sdk'; ``` ### Error Handling ```typescript // ❌ Old try { await sdk.transfer(...); } catch (error) { if (error.code === 'INSUFFICIENT_FUNDS') { ... } } // ✅ New try { await sdk.transferViaRelayer(...); } catch (error) { if (error instanceof VeridexError) { if (error.code === ErrorCodes.INSUFFICIENT_BALANCE) { ... } } } ``` ## Breaking Changes Summary | Old | New | |-----|-----| | `new Veridex(config)` | `createSDK(chain, config)` | | `sdk.register()` | `sdk.passkey.register()` | | `sdk.login()` | `sdk.passkey.authenticate()` | | `sdk.createSession()` | `sdk.sessions.create()` | | `sdk.transfer({ gasless: true })` | `sdk.transferViaRelayer()` | | `sdk.bridge()` | `sdk.crossChainTransfer()` | | `sdk.address` | `sdk.getVaultAddress()` | | `testnet: true` | `network: 'testnet'` | | `to` parameter | `recipient` parameter | | `toChain` parameter | `targetChain` parameter | ## Codemods We provide a codemod to automatically migrate your codebase: ```bash npx @veridex/codemod@latest migrate-to-1.0 ``` This will update: - Import statements - SDK initialization - Method calls - Type imports Review the changes before committing. ## Need Help? - [Discord](https://discord.gg/veridex) - Ask in #migration channel - [GitHub Issues](https://github.com/Veridex-Protocol/sdk/issues) - Report migration issues - [Documentation](https://docs.veridex.network) - Full API reference