diff --git a/bin/evd/src/main.rs b/bin/evd/src/main.rs index 80bfcc9..724b89c 100644 --- a/bin/evd/src/main.rs +++ b/bin/evd/src/main.rs @@ -31,8 +31,8 @@ //! # Start with default testapp genesis //! evd run //! -//! # Start with a custom genesis file (e.g. for x402 demo) -//! evd run --genesis-file examples/x402-demo/genesis.json +//! # Start with a custom genesis file +//! evd run --genesis-file path/to/genesis.json //! //! # Custom addresses //! evd run --grpc-addr 0.0.0.0:50051 --rpc-addr 0.0.0.0:8545 diff --git a/docs/x402-client-integration.md b/docs/x402-client-integration.md deleted file mode 100644 index a293d52..0000000 --- a/docs/x402-client-integration.md +++ /dev/null @@ -1,648 +0,0 @@ -# X402 Integration Guide for Evolve - -How to build and consume [X402](https://x402.org)-protected APIs on the Evolve blockchain using JavaScript. - ---- - -## What is X402? - -X402 uses HTTP status code **402 (Payment Required)** to create a machine-readable payment flow between clients and APIs. Instead of API keys or subscriptions, each request is paid individually on-chain. - -``` -Client Server Evolve Node - │ │ │ - │ 1. POST /api/transform/hash │ │ - │──────────────────────────────>│ │ - │ │ │ - │ 2. 402 + PAYMENT-REQUIRED │ │ - │<──────────────────────────────│ │ - │ │ │ - │ 3. Token transfer tx │ │ - │───────────────────────────────┼──────────────────────────────>│ - │ 4. txHash │ │ - │<──────────────────────────────┼───────────────────────────────│ - │ │ │ - │ 5. POST + PAYMENT-SIGNATURE │ │ - │──────────────────────────────>│ │ - │ │ 6. Verify tx on-chain │ - │ │──────────────────────────────>│ - │ │<──────────────────────────────│ - │ │ │ - │ 7. 200 + result │ │ - │<──────────────────────────────│ │ -``` - ---- - -## Part 1: Building an X402-Protected Server - -This section shows how to create an API server that requires on-chain payment for access, using [Hono](https://hono.dev) and the `@x402` libraries. - -### Dependencies - -```json -{ - "hono": "^4.6.0", - "@x402/core": "^2.2.0", - "@x402/hono": "^2.2.0", - "viem": "^2.21.0" -} -``` - -### Step 1: Define Routes and Pricing - -Each protected route needs a `RouteConfig` that specifies the payment scheme, price, and recipient. - -```typescript -import type { Address } from "viem"; -import type { Network } from "@x402/core/types"; -import type { RouteConfig, RoutesConfig } from "@x402/core/http"; - -const TREASURY_ADDRESS: Address = "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"; -const NETWORK: Network = "evolve:1337" as Network; - -function route(price: string, description: string): RouteConfig { - return { - accepts: { scheme: "exact", payTo: TREASURY_ADDRESS, price, network: NETWORK }, - description, - mimeType: "application/json", - }; -} - -const PROTECTED_ROUTES: RoutesConfig = { - "POST /api/transform/echo": route("100", "Echo - returns input unchanged"), - "POST /api/transform/reverse": route("100", "Reverse - reverses input string"), - "POST /api/transform/uppercase": route("100", "Uppercase - uppercases input string"), - "POST /api/transform/hash": route("200", "Hash - returns SHA256 of input"), -}; -``` - -The keys in `RoutesConfig` follow the format `"METHOD /path"`. The `price` is in raw token units (no decimals). - -### Step 2: Implement the Facilitator - -The facilitator verifies payment transactions on-chain and settles them. Implement the `FacilitatorClient` interface from `@x402/core/server`. - -```typescript -import type { - PaymentPayload, - PaymentRequirements, - VerifyResponse, - SettleResponse, - SupportedResponse, - Network, -} from "@x402/core/types"; -import type { FacilitatorClient } from "@x402/core/server"; -import { createPublicClient, http, defineChain } from "viem"; - -const evolveChain = defineChain({ - id: 1337, - name: "Evolve Testnet", - nativeCurrency: { decimals: 18, name: "Evolve Token", symbol: "EVO" }, - rpcUrls: { default: { http: ["http://127.0.0.1:8545"] } }, -}); - -const publicClient = createPublicClient({ - chain: evolveChain, - transport: http("http://127.0.0.1:8545"), -}); - -class EvolveFacilitator implements FacilitatorClient { - private usedTxHashes = new Map(); - private network: Network; - - constructor(network: Network) { - this.network = network; - } - - async verify( - paymentPayload: PaymentPayload, - _paymentRequirements: PaymentRequirements, - ): Promise { - // Evict stale entries (older than 1 hour) - const now = Date.now(); - for (const [hash, ts] of this.usedTxHashes) { - if (now - ts > 3_600_000) this.usedTxHashes.delete(hash); - } - - const txHash = paymentPayload.payload.txHash as string; - - if (!txHash) { - return { isValid: false, invalidReason: "Missing transaction hash" }; - } - - if (this.usedTxHashes.has(txHash)) { - return { isValid: false, invalidReason: "Transaction already used" }; - } - - try { - const receipt = await publicClient.getTransactionReceipt({ - hash: txHash as `0x${string}`, - }); - - if (receipt.status !== "success") { - return { isValid: false, invalidReason: "Transaction failed" }; - } - - return { isValid: true, payer: txHash.slice(0, 42) }; - } catch { - return { isValid: false, invalidReason: "Transaction not found" }; - } - } - - async settle( - paymentPayload: PaymentPayload, - _paymentRequirements: PaymentRequirements, - ): Promise { - const txHash = paymentPayload.payload.txHash as string; - this.usedTxHashes.set(txHash, Date.now()); - - return { - success: true, - transaction: txHash, - network: this.network, - payer: txHash.slice(0, 42), - }; - } - - async getSupported(): Promise { - return { - kinds: [{ x402Version: 2, scheme: "exact", network: this.network }], - extensions: [], - signers: {}, - }; - } -} -``` - -`verify()` is called before your route handler runs. `settle()` is called after the handler returns a successful response. This prevents charging for failed requests. - -### Step 3: Implement the Scheme Server - -The scheme server tells the framework how to parse prices for your payment scheme. - -```typescript -import type { - SchemeNetworkServer, - PaymentRequirements, - Network, - Price, - AssetAmount, -} from "@x402/core/types"; - -class EvolveSchemeServer implements SchemeNetworkServer { - readonly scheme = "exact"; - - async parsePrice(price: Price, _network: Network): Promise { - const amount = - typeof price === "object" && "amount" in price - ? price.amount - : String(price); - return { amount, asset: "native" }; - } - - async enhancePaymentRequirements( - paymentRequirements: PaymentRequirements, - _supportedKind: { x402Version: number; scheme: string; network: Network }, - _facilitatorExtensions: string[], - ): Promise { - return paymentRequirements; - } -} -``` - -For Evolve, prices are raw token amounts, so `parsePrice` just passes the value through. - -### Step 4: Wire It All Together - -```typescript -import { Hono } from "hono"; -import { cors } from "hono/cors"; -import { x402ResourceServer } from "@x402/core/server"; -import { paymentMiddleware } from "@x402/hono"; - -const app = new Hono(); - -// CORS: expose x402 headers so clients can read them -app.use("*", cors({ - origin: "*", - allowHeaders: ["Content-Type", "PAYMENT-SIGNATURE", "X-Agent-ID"], - exposeHeaders: ["PAYMENT-REQUIRED", "PAYMENT-RESPONSE"], -})); - -// Set up x402 resource server -const facilitator = new EvolveFacilitator(NETWORK); -const resourceServer = new x402ResourceServer(facilitator); -resourceServer.register(NETWORK, new EvolveSchemeServer()); - -// Apply payment middleware to protected routes -app.use("/api/transform/*", paymentMiddleware(PROTECTED_ROUTES, resourceServer)); - -// Pricing discovery endpoint (unprotected) -app.get("/api/pricing", (c) => { - const endpoints = Object.entries(PROTECTED_ROUTES).map(([route, config]) => ({ - route, - price: String((config as { accepts: { price: string } }).accepts.price), - description: (config as { description?: string }).description ?? "", - })); - return c.json({ treasury: TREASURY_ADDRESS, network: NETWORK, endpoints }); -}); - -// Protected routes — only reachable after x402 payment -app.post("/api/transform/echo", async (c) => { - const { input } = await c.req.json<{ input: string }>(); - return c.json({ output: input, operation: "echo" }); -}); - -app.post("/api/transform/reverse", async (c) => { - const { input } = await c.req.json<{ input: string }>(); - return c.json({ output: input.split("").reverse().join(""), operation: "reverse" }); -}); - -app.post("/api/transform/uppercase", async (c) => { - const { input } = await c.req.json<{ input: string }>(); - return c.json({ output: input.toUpperCase(), operation: "uppercase" }); -}); - -app.post("/api/transform/hash", async (c) => { - const { input } = await c.req.json<{ input: string }>(); - const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input)); - const hex = Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join(""); - return c.json({ output: `0x${hex}`, operation: "hash" }); -}); - -export default app; // Works with Bun, Node, Deno, Cloudflare Workers, etc. -``` - -Any request to `/api/transform/*` without a valid `PAYMENT-SIGNATURE` header will receive a `402` response with a `PAYMENT-REQUIRED` header. - ---- - -## Part 2: Building a Client - -This section shows how to build a JS client that discovers pricing, pays on-chain, and accesses protected endpoints. - -### Dependencies - -```json -{ - "viem": "^2.21.0" -} -``` - -### Step 1: Setup - -```typescript -import { - createWalletClient, - createPublicClient, - http, - defineChain, - keccak256, - toBytes, - bytesToHex, -} from "viem"; -import { privateKeyToAccount } from "viem/accounts"; - -const evolveChain = defineChain({ - id: 1337, // check via eth_chainId - name: "Evolve Testnet", - nativeCurrency: { decimals: 18, name: "Evolve", symbol: "EVO" }, - rpcUrls: { default: { http: ["http://localhost:8545"] } }, -}); - -const account = privateKeyToAccount("0xYOUR_PRIVATE_KEY"); - -const walletClient = createWalletClient({ - account, - chain: evolveChain, - transport: http("http://localhost:8545"), -}); - -const publicClient = createPublicClient({ - chain: evolveChain, - transport: http("http://localhost:8545"), -}); -``` - -### Step 2: Evolve Helpers - -Evolve uses its own calldata encoding for token transfers. You need these helper functions (see [Evolve Reference](#evolve-reference) for details): - -```typescript -function addressToAccountId(address: `0x${string}`): bigint { - return BigInt(`0x${address.slice(10)}`); -} - -function accountIdToAddress(id: bigint): `0x${string}` { - const idBytes = new Uint8Array(16); - let v = id; - for (let i = 15; i >= 0; i--) { - idBytes[i] = Number(v & 0xffn); - v >>= 8n; - } - const addrBytes = new Uint8Array(20); - addrBytes.set(idBytes, 4); - return bytesToHex(addrBytes) as `0x${string}`; -} - -function u128ToLeBytes(value: bigint): Uint8Array { - const bytes = new Uint8Array(16); - let v = value; - for (let i = 0; i < 16; i++) { - bytes[i] = Number(v & 0xffn); - v >>= 8n; - } - return bytes; -} - -function buildTransferData(toAccountId: bigint, amount: bigint): `0x${string}` { - const selector = keccak256(toBytes("transfer")).slice(0, 10); - const args = new Uint8Array(32); - args.set(u128ToLeBytes(toAccountId), 0); - args.set(u128ToLeBytes(amount), 16); - const data = new Uint8Array(4 + args.length); - data.set(Buffer.from(selector.slice(2), "hex"), 0); - data.set(args, 4); - return bytesToHex(data) as `0x${string}`; -} -``` - -### Step 3: Submit Payment - -```typescript -const TOKEN_ACCOUNT_CANDIDATES = [65535n, 65537n]; - -async function submitPayment(payTo: `0x${string}`, amount: bigint): Promise<`0x${string}`> { - const recipientAccountId = addressToAccountId(payTo); - - for (const tokenAccountId of TOKEN_ACCOUNT_CANDIDATES) { - const tokenAddress = accountIdToAddress(tokenAccountId); - const data = buildTransferData(recipientAccountId, amount); - - try { - const txHash = await walletClient.sendTransaction({ - to: tokenAddress, - data, - value: 0n, - gas: 100_000n, - maxFeePerGas: 1_000_000_000n, - maxPriorityFeePerGas: 1_000_000_000n, - }); - - const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash }); - if (receipt.status === "success") return txHash; - } catch { - // Try next token account candidate - } - } - throw new Error("Failed to submit payment"); -} -``` - -The token contract AccountId varies by genesis (commonly `65535` or `65537`). The client tries both candidates until one succeeds. - -### Step 4: Complete Payment Flow - -This function handles the full cycle: request -> 402 -> pay -> retry. - -```typescript -async function callPaidEndpoint( - url: string, - method: string, - body: unknown, -): Promise { - // 1. Initial request - const initialResponse = await fetch(url, { - method, - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(body), - }); - - if (initialResponse.status !== 402) { - return initialResponse; - } - - // 2. Parse payment requirement - const paymentHeader = initialResponse.headers.get("PAYMENT-REQUIRED"); - if (!paymentHeader) throw new Error("402 without PAYMENT-REQUIRED header"); - - const paymentRequired = JSON.parse( - Buffer.from(paymentHeader, "base64").toString("utf-8") - ); - - const requirement = paymentRequired.accepts[0]; - - // 3. Pay on-chain - const txHash = await submitPayment( - requirement.payTo as `0x${string}`, - BigInt(requirement.amount) - ); - - // 4. Build v2 payment proof - const paymentPayload = { - x402Version: 2, - resource: paymentRequired.resource, - accepted: requirement, - payload: { txHash }, - }; - - const paymentSignature = Buffer.from( - JSON.stringify(paymentPayload) - ).toString("base64"); - - // 5. Retry with proof - return fetch(url, { - method, - headers: { - "Content-Type": "application/json", - "PAYMENT-SIGNATURE": paymentSignature, - }, - body: JSON.stringify(body), - }); -} -``` - -### Usage - -```typescript -// Discover pricing -const pricing = await fetch("http://localhost:3000/api/pricing").then(r => r.json()); -console.log(pricing.endpoints); -// [{ route: "POST /api/transform/echo", price: "100", description: "..." }, ...] - -// Make a paid request -const response = await callPaidEndpoint( - "http://localhost:3000/api/transform/reverse", - "POST", - { input: "hello world" } -); - -const result = await response.json(); -// { "output": "dlrow olleh", "operation": "reverse" } -``` - ---- - -## Evolve Reference - -### Address Mapping - -Evolve uses 128-bit `AccountId` internally. Ethereum addresses (20 bytes) embed the AccountId in the last 16 bytes: - -``` -Ethereum address: 0x 0000 0000 3C44CdDdB6a900fa2b585dd299e03d12FA4293BC - ^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - padding AccountId (16 bytes = 128 bits) -``` - -- `addressToAccountId(address)`: strips the `0x` prefix and 4-byte padding, returns the remaining 16 bytes as a bigint. -- `accountIdToAddress(id)`: writes the bigint as 16 big-endian bytes, prepends 4 zero bytes, returns as `0x`-prefixed hex. - -### Calldata Encoding - -Evolve uses its own ABI convention, different from Solidity: - -``` -[4 bytes] Function selector = keccak256("transfer")[0..4] = 0xb483afd3 -[16 bytes] Recipient AccountId (little-endian u128) -[16 bytes] Amount (little-endian u128) -``` - -Total: **36 bytes** of calldata. - -The selector `0xb483afd3` comes from hashing just the string `"transfer"`. Solidity would hash `"transfer(address,uint256)"` producing `0xa9059cbb` — these are different. - -### Token Transfers (not native value) - -Evolve's execution layer ignores the `value` field on transactions. Payments are made via **token contract calldata**: - -``` -Transaction: - to: (derived from AccountId 65535 or 65537) - value: 0 - data: (36 bytes, see above) -``` - -### Chain Configuration - -| Parameter | Default | How to Discover | -|-----------|---------|-----------------| -| Chain ID | 1337 | `eth_chainId` RPC call | -| Token AccountId | 65535 or 65537 | Check genesis or try both | -| RPC URL | `http://localhost:8545` | Server config | -| Gas limit per tx | 100,000 | Sufficient for token transfers | -| Network identifier | `evolve:1337` | `evolve:{chainId}` | - -### Nonce Management - -Under load, `eth_getTransactionCount` may lag. For high-throughput clients: - -1. Fetch nonce once at startup via `eth_getTransactionCount` with `blockTag: "pending"` -2. Increment locally for each subsequent transaction -3. If you get a "nonce too high" error, re-fetch from the node - ---- - -## Protocol Reference (v2) - -### Headers - -| Header | Direction | Encoding | Description | -|--------|-----------|----------|-------------| -| `PAYMENT-REQUIRED` | Server -> Client | Base64 JSON | Payment requirements | -| `PAYMENT-SIGNATURE` | Client -> Server | Base64 JSON | Payment proof | -| `PAYMENT-RESPONSE` | Server -> Client | Base64 JSON | Settlement confirmation | -| `X-Agent-ID` | Client -> Server | Plain text | Optional: identify the paying agent | - -### PaymentRequired (Server -> Client) - -```typescript -{ - x402Version: 2, - error: "payment_required", - resource: { - url: string, // Requested URL - description: string, // Human-readable description - mimeType: string, // e.g. "application/json" - }, - accepts: [{ - scheme: "exact", - network: string, // e.g. "evolve:1337" - asset: string, // e.g. "native" - amount: string, // Price in token units - payTo: string, // Treasury Ethereum address - maxTimeoutSeconds: number, - extra?: Record, - }], -} -``` - -### PaymentPayload (Client -> Server) - -```typescript -{ - x402Version: 2, - resource: { - url: string, // Echo from PaymentRequired - description: string, - mimeType: string, - }, - accepted: { // Echo back the chosen option from accepts[0] - scheme: "exact", - network: string, - asset: string, - amount: string, - payTo: string, - maxTimeoutSeconds: number, - extra?: Record, - }, - payload: { - txHash: string, // On-chain transaction hash (0x-prefixed) - }, -} -``` - -### PaymentSettled (Server -> Client) - -Returned in the `PAYMENT-RESPONSE` header on successful payment: - -```typescript -{ - x402Version: 2, - success: boolean, - transaction?: string, // Confirmed txHash - network: string, - payer?: string, - error?: string, -} -``` - ---- - -## Error Handling - -| HTTP Status | Meaning | Action | -|-------------|---------|--------| -| 402 (no PAYMENT-SIGNATURE) | Payment required | Parse PAYMENT-REQUIRED, pay, retry | -| 402 (with PAYMENT-SIGNATURE) | Verification failed | Check tx status, amount, or replay | -| 400 | Invalid payment header | Fix base64 encoding or payload format | -| 503 | Evolve node unavailable | Retry later | -| 200 | Success | Parse result from response body | - -Common verification errors: - -```json -{"error": "Payment verification failed", "reason": "Transaction not found"} -{"error": "Payment verification failed", "reason": "Transaction failed"} -{"error": "Payment verification failed", "reason": "Transaction already used"} -``` - ---- - -## Security Considerations - -- **Replay protection**: Each `txHash` can only be used once. The server caches used hashes. -- **Transaction timeout**: Payments must be submitted within `maxTimeoutSeconds` (default 300s). -- **On-chain verification**: The server verifies the transaction receipt on the Evolve node before granting access. -- **No API keys**: Authentication is purely based on on-chain payment proof. diff --git a/docs/x402-demo-requirements.md b/docs/x402-demo-requirements.md deleted file mode 100644 index 69b3694..0000000 --- a/docs/x402-demo-requirements.md +++ /dev/null @@ -1,193 +0,0 @@ -# X402 Payments Demo - -## Overview - -Demo showcasing Evolve SDK through pay-per-request API monetization using the [Coinbase X402 protocol](https://x402.org). - -X402 uses HTTP 402 (Payment Required) for machine-readable payment flows: client requests resource → server returns 402 with payment details → client pays → client retries with proof → server delivers resource. - -## Decisions - -| Aspect | Decision | -|------------------|-----------------------------------------------| -| Use Case | API Monetization (pay-per-request) | -| X402 Libraries | `@x402/core` types + custom Evolve middleware | -| Frontend | React + Vite + `@x402/fetch` | -| Wallet | Passkey/WebAuthn (server-side signing) | -| Token | Native Evolve Token | -| Backend | Bun + Hono | -| Deployment | Fly.io (node + server) + Vercel (frontend) | - ---- - -## Architecture - -``` -┌─────────────────────────────────────────────────────────────┐ -│ Frontend (React) │ -│ - @x402/fetch handles 402 → pay → retry automatically │ -│ - @simplewebauthn/browser for passkey auth │ -└─────────────────────────────┬───────────────────────────────┘ - │ -┌─────────────────────────────▼───────────────────────────────┐ -│ Bun + Hono Server │ -│ │ -│ ┌────────────────────────────────────────────────────────┐ │ -│ │ Passkey Auth (@simplewebauthn/server) │ │ -│ │ - WebAuthn registration/authentication │ │ -│ │ - Maps credentialId → secp256k1 keypair │ │ -│ └────────────────────────────────────────────────────────┘ │ -│ ┌────────────────────────────────────────────────────────┐ │ -│ │ X402 Middleware (custom, uses @x402/core types) │ │ -│ │ - Returns 402 with PaymentRequired per X402 spec │ │ -│ │ - Verifies payment via Evolve JSON-RPC │ │ -│ └────────────────────────────────────────────────────────┘ │ -│ ┌────────────────────────────────────────────────────────┐ │ -│ │ Transform API │ │ -│ │ - /echo, /reverse, /uppercase, /hash │ │ -│ └────────────────────────────────────────────────────────┘ │ -└─────────────────────────────┬───────────────────────────────┘ - │ JSON-RPC -┌─────────────────────────────▼───────────────────────────────┐ -│ Evolve Node │ -│ - Token balances and transfers │ -│ - Transaction processing │ -└─────────────────────────────────────────────────────────────┘ -``` - -### Why Custom Middleware? - -The Coinbase X402 libraries assume a facilitator (Coinbase/Cloudflare) for payment settlement. We verify directly against Evolve node, so we: -- Use `@x402/core` for protocol types (`PaymentRequired`, `PaymentPayload`) -- Write custom Hono middleware for Evolve-specific verification -- Use `@x402/fetch` on frontend for automatic 402 handling - ---- - -## X402 Flow - -``` -1. GET /api/transform/reverse - -2. Server returns 402 with X402-compliant headers: - HTTP/1.1 402 Payment Required - X-Payment: - -3. @x402/fetch intercepts, calls payment callback - -4. Client pays via /wallet/transfer → gets txHash - -5. @x402/fetch retries with payment proof: - GET /api/transform/reverse - X-Payment-Response: - -6. Server verifies on Evolve node, returns resource -``` - ---- - -## Directory Structure - -``` -examples/x402-demo/ -├── docker-compose.yml -├── server/ -│ ├── package.json -│ └── src/ -│ ├── index.ts -│ ├── x402.ts # Custom middleware + verification -│ ├── passkey.ts # WebAuthn registration/auth -│ ├── wallet.ts # Balance, transfer, faucet -│ ├── transform.ts # Protected API endpoints -│ └── evolve.ts # JSON-RPC client -└── frontend/ - ├── package.json - └── src/ - ├── App.tsx - ├── pages/ - │ ├── Landing.tsx - │ ├── Wallet.tsx - │ └── Playground.tsx - ├── hooks/ - │ ├── usePasskey.ts - │ └── useX402Client.ts # Wraps @x402/fetch - └── lib/ - └── api.ts -``` - ---- - -## API Endpoints - -### Auth -``` -POST /auth/register → WebAuthn registration options -POST /auth/register/verify → Complete registration, return address -POST /auth/login → WebAuthn auth options -POST /auth/login/verify → Complete auth, return session token -``` - -### Wallet -``` -GET /wallet/balance → { address, balance } -POST /wallet/faucet → { txHash, newBalance } -POST /wallet/transfer → { txHash } -``` - -### Transform (X402 Protected) -``` -POST /api/transform/echo → 100 tokens -POST /api/transform/reverse → 100 tokens -POST /api/transform/uppercase → 100 tokens -POST /api/transform/hash → 200 tokens -``` - ---- - -## Dependencies - -### Server -```json -{ - "dependencies": { - "hono": "^4.0.0", - "@x402/core": "latest", - "@simplewebauthn/server": "^9.0.0", - "viem": "^2.0.0" - } -} -``` - -### Frontend -```json -{ - "dependencies": { - "react": "^18.0.0", - "react-router-dom": "^6.0.0", - "@x402/fetch": "latest", - "@simplewebauthn/browser": "^9.0.0", - "@tanstack/react-query": "^5.0.0" - } -} -``` - ---- - -## Implementation Phases - -| Phase | Scope | -|-------|-------| -| 1 | Bun/Hono skeleton, Evolve JSON-RPC client, balance query | -| 2 | X402 middleware using @x402/core types, Transform API | -| 3 | Passkey auth with @simplewebauthn | -| 4 | React frontend with @x402/fetch integration | -| 5 | Docker compose + Fly.io deployment | - ---- - -## Security Notes - -- Passkey proves user identity (phishing-resistant, device-bound) -- Server holds signing keys (custodial, protected by passkey) -- Replay protection: cache used txHashes, enforce nonce ordering -- Future: P-256 on-chain verification enables client-side signing diff --git a/examples/x402-demo/.claude/skills/onboard.md b/examples/x402-demo/.claude/skills/onboard.md deleted file mode 100644 index 6af53b4..0000000 --- a/examples/x402-demo/.claude/skills/onboard.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -description: Onboard onto the x402-demo project. Use when developers ask about the x402 demo, need to understand its architecture, or want to contribute to it. ---- - -# X402 Demo Onboarding - -## Overview - -This demo showcases pay-per-request API monetization using the [X402 protocol](https://x402.org) (HTTP 402 Payment Required) with the Evolve SDK. - -## Architecture - -``` -Frontend (React + Vite) → API Server (Bun + Hono) → Evolve Node -localhost:5173 localhost:3000 localhost:8545 -``` - -### Key Flow - -1. User registers with passkey → server creates secp256k1 keypair -2. User requests protected API → server returns 402 with payment requirements -3. User pays via `/wallet/transfer` → server signs tx with user's key -4. User retries with `PAYMENT-SIGNATURE` header containing txHash -5. Server verifies tx on Evolve node → returns result - -## Directory Structure - -``` -examples/x402-demo/ -├── server/ # Bun + Hono backend -│ └── src/ -│ ├── index.ts # Entry point, route mounting -│ ├── evolve.ts # Viem-based JSON-RPC client -│ ├── x402.ts # X402 middleware (402 responses, payment verification) -│ ├── passkey.ts # WebAuthn registration/auth, session management -│ ├── wallet.ts # Balance queries, transfers, faucet -│ └── transform.ts # Protected API endpoints -├── frontend/ # React + Vite -│ └── src/ -│ ├── App.tsx # Router + nav -│ ├── lib/api.ts # API client with X402 error handling -│ ├── hooks/usePasskey.ts # WebAuthn + session state -│ └── pages/ -│ ├── Landing.tsx # Register/login -│ ├── Wallet.tsx # Balance + faucet -│ └── Playground.tsx # X402 flow visualization -├── docker-compose.yml -└── README.md -``` - -## Key Files to Understand - -1. **`server/src/x402.ts`** - Core X402 protocol implementation - - `x402Middleware()` - Hono middleware that returns 402 or verifies payment - - `PaymentRequired`, `PaymentPayload` types match X402 v2 spec - - Replay protection via txHash cache - -2. **`server/src/passkey.ts`** - Authentication - - Uses `@simplewebauthn/server` for WebAuthn - - Maps passkey credentials to server-managed secp256k1 keypairs - - Session tokens stored in memory (use Redis for production) - -3. **`server/src/evolve.ts`** - Blockchain client - - Viem client configured for Evolve chain - - Standard eth_* methods + custom evolve_* methods - -4. **`frontend/src/pages/Playground.tsx`** - X402 flow demo - - Shows step-by-step: request → 402 → pay → retry → success - -## Running the Demo - -```bash -# Terminal 1: Evolve node -cargo run -p evolve_testapp - -# Terminal 2: Server -cd examples/x402-demo/server && bun run dev - -# Terminal 3: Frontend -cd examples/x402-demo/frontend && bun run dev -``` - -## Tests - -```bash -cd examples/x402-demo/server && bun test -``` - -Key test files: -- `test/x402.test.ts` - Payment flow, replay protection -- `test/auth.test.ts` - WebAuthn options, session validation - -## Environment Variables - -| Variable | Default | Purpose | -|----------|---------|---------| -| `EVOLVE_RPC_URL` | `http://127.0.0.1:8545` | Evolve node endpoint | -| `TREASURY_ADDRESS` | `0x...0001` | Receives payments | -| `FAUCET_PRIVATE_KEY` | - | Funded account for faucet | -| `RP_ID` | `localhost` | WebAuthn relying party | -| `RP_ORIGIN` | `http://localhost:5173` | WebAuthn origin | - -## Known Limitations - -1. **Transaction format** - Currently uses viem's `sendTransaction` which produces standard Ethereum transactions. May need adaptation if Evolve uses a different tx format. - -2. **In-memory storage** - Sessions, users, and txHash cache are in memory. Use Redis/SQLite for production. - -3. **No full payment verification** - Currently only checks tx exists and succeeded. Should verify: amount, recipient, sender matches user, tx recency. - -## Common Tasks - -### Add a new protected endpoint - -1. Add route config in `server/src/transform.ts`: - ```typescript - export const TRANSFORM_ROUTES = { - "POST /api/transform/new": { price: 150n, description: "New endpoint" }, - }; - ``` - -2. Add handler in same file: - ```typescript - app.post("/new", (c) => { - const input = c.get("transformInput"); - return c.json({ output: process(input) }); - }); - ``` - -### Modify payment verification - -Edit `verifyPayment()` in `server/src/x402.ts` to add checks for amount, recipient, or timing. diff --git a/examples/x402-demo/DESIGN.md b/examples/x402-demo/DESIGN.md deleted file mode 100644 index 7ce2254..0000000 --- a/examples/x402-demo/DESIGN.md +++ /dev/null @@ -1,619 +0,0 @@ -# X402 Agent Simulator - Design Document - -## Overview - -Transform the X402 demo from a user-facing login flow into a multi-agent simulator that demonstrates high-throughput micropayments on the Evolve chain. - -## Goals - -1. Simulate N agents making paid API requests concurrently -2. Visualize agent activity and payment flow in real-time -3. Demonstrate sub-100ms payment latency with fast block times -4. Showcase the X402 protocol at scale - -## Architecture - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Agent Simulator │ -│ ┌───────────────────────────────────────────────────────────┐ │ -│ │ Agent Pool │ │ -│ │ - N agents with pre-funded wallets │ │ -│ │ - Private keys in memory │ │ -│ │ - Timestamp-based signing (no nonce tracking) │ │ -│ │ - Configurable request rate per agent │ │ -│ └───────────────────────────────────────────────────────────┘ │ -└──────────────────────┬──────────────────────────────────────────┘ - │ HTTP/2 -┌──────────────────────▼──────────────────────────────────────────┐ -│ X402 Server │ -│ ┌───────────────────────────────────────────────────────────┐ │ -│ │ Payment Verification │ │ -│ │ - gRPC client to Evolve node │ │ -│ │ - SubmitAndWait for synchronous confirmation │ │ -│ │ - Replay protection via chain state │ │ -│ └───────────────────────────────────────────────────────────┘ │ -│ ┌───────────────────────────────────────────────────────────┐ │ -│ │ Event Emitter │ │ -│ │ - WebSocket server for dashboard │ │ -│ │ - Events: payment_submitted, confirmed, request_served │ │ -│ └───────────────────────────────────────────────────────────┘ │ -└──────────────────────┬──────────────────────────────────────────┘ - │ gRPC -┌──────────────────────▼──────────────────────────────────────────┐ -│ Evolve Node │ -│ - Configurable block time (1ms, 10ms, 100ms) │ -│ - Timestamp-based nonceless transactions │ -│ - gRPC interface for low-latency communication │ -└─────────────────────────────────────────────────────────────────┘ - │ WebSocket -┌──────────────────────▼──────────────────────────────────────────┐ -│ Dashboard │ -│ - Real-time agent status grid │ -│ - Payment stream visualization │ -│ - Metrics: TPS, latency histogram, success rate │ -│ - Treasury balance tracker │ -└─────────────────────────────────────────────────────────────────┘ -``` - -## Components to Build - -### 1. Agent Simulator (`/simulator`) - -New directory containing the agent simulation logic. - -#### Files - -``` -simulator/ -├── src/ -│ ├── index.ts # Entry point, CLI -│ ├── agent.ts # Single agent logic -│ ├── pool.ts # Agent pool management -│ ├── signer.ts # Timestamp-based signing -│ ├── metrics.ts # Latency tracking, stats -│ └── config.ts # Configuration schema -├── package.json -└── tsconfig.json -``` - -#### Agent Class - -```typescript -interface AgentConfig { - id: string; - privateKey: Hex; - address: Address; - requestsPerSecond: number; - endpoints: WeightedEndpoint[]; -} - -interface WeightedEndpoint { - method: string; - path: string; - weight: number; // probability of selection - payload: () => unknown; // payload generator -} - -class Agent { - private config: AgentConfig; - private running: boolean = false; - private metrics: AgentMetrics; - - async start(): Promise; - async stop(): Promise; - - private async makeRequest(): Promise { - const endpoint = this.selectEndpoint(); - - // Step 1: Make request, expect 402 - const initialResponse = await fetch(endpoint.url, { - method: endpoint.method, - body: JSON.stringify(endpoint.payload()), - }); - - if (initialResponse.status !== 402) { - // Unexpected - either error or no payment required - return this.handleNonPaymentResponse(initialResponse); - } - - // Step 2: Parse payment requirement - const paymentRequired = this.parsePaymentRequired(initialResponse); - - // Step 3: Sign and submit payment - const signedTx = this.signPayment(paymentRequired); - const txHash = await this.submitPayment(signedTx); - - // Step 4: Retry with payment proof - const paymentSignature = this.createPaymentSignature(txHash); - const finalResponse = await fetch(endpoint.url, { - method: endpoint.method, - body: JSON.stringify(endpoint.payload()), - headers: { - 'PAYMENT-SIGNATURE': paymentSignature, - }, - }); - - return { - success: finalResponse.ok, - latencyMs: /* measured */, - txHash, - }; - } -} -``` - -#### Pool Manager - -```typescript -interface PoolConfig { - agentCount: number; - fundingAmount: bigint; - serverUrl: string; - evolveRpcUrl: string; - requestsPerSecond: number; // total across all agents -} - -class AgentPool { - private agents: Agent[] = []; - private faucetKey: Hex; - - async initialize(config: PoolConfig): Promise { - // Generate N wallets - // Fund each from faucet - // Create Agent instances - } - - async start(): Promise { - // Start all agents - } - - async stop(): Promise { - // Graceful shutdown - } - - getMetrics(): PoolMetrics { - // Aggregate metrics from all agents - } -} -``` - -#### CLI Interface - -```bash -# Start simulator with 10 agents, 100 req/s total -bun run simulator --agents 10 --rps 100 --server http://localhost:3000 - -# With custom funding -bun run simulator --agents 50 --rps 500 --funding 10000 - -# Specify block time expectation (for latency calculations) -bun run simulator --agents 10 --rps 100 --block-time 10 -``` - -### 2. Server Changes (`/server`) - -#### New: gRPC Client (`/server/src/grpc-client.ts`) - -Replace viem JSON-RPC with gRPC for Evolve communication. - -```typescript -import { createClient } from '@grpc/grpc-js'; -import { EvolvePaymentClient } from './generated/evolve_pb'; - -interface EvolveGrpcClient { - submitAndWait(signedTx: SignedTx): Promise; - submit(signedTx: SignedTx): Promise; - getBalance(address: Address): Promise; -} - -export function createEvolveGrpcClient(endpoint: string): EvolveGrpcClient { - const client = new EvolvePaymentClient( - endpoint, - grpc.credentials.createInsecure() - ); - - return { - async submitAndWait(tx) { - return new Promise((resolve, reject) => { - client.submitAndWait(tx, (err, response) => { - if (err) reject(err); - else resolve(response); - }); - }); - }, - // ... other methods - }; -} -``` - -#### New: WebSocket Event Server (`/server/src/events.ts`) - -```typescript -import { Hono } from 'hono'; -import { createBunWebSocket } from 'hono/bun'; - -interface PaymentEvent { - type: 'payment_submitted' | 'payment_confirmed' | 'request_served' | 'error'; - timestamp: number; - agentId: string; - txHash?: string; - amount?: string; - endpoint?: string; - latencyMs?: number; - error?: string; -} - -class EventEmitter { - private connections: Set = new Set(); - - addConnection(ws: WebSocket): void { - this.connections.add(ws); - ws.onclose = () => this.connections.delete(ws); - } - - emit(event: PaymentEvent): void { - const message = JSON.stringify(event); - for (const ws of this.connections) { - ws.send(message); - } - } -} - -export const eventEmitter = new EventEmitter(); - -export function createEventRoutes(): Hono { - const app = new Hono(); - const { upgradeWebSocket, websocket } = createBunWebSocket(); - - app.get('/events', upgradeWebSocket((c) => ({ - onOpen(_, ws) { - eventEmitter.addConnection(ws); - }, - }))); - - return app; -} -``` - -#### Modified: X402 Middleware (`/server/src/x402.ts`) - -Update to use gRPC and emit events. - -```typescript -import { eventEmitter } from './events'; -import { evolveGrpcClient } from './grpc-client'; - -export function x402Middleware(config: X402Config): MiddlewareHandler { - return async (c, next) => { - // ... existing route matching logic ... - - const paymentSignature = c.req.header('PAYMENT-SIGNATURE'); - - if (!paymentSignature) { - // Return 402 as before - return c.json(paymentRequired, 402); - } - - // Parse and verify payment - const payload = decodePaymentPayload(paymentSignature); - const agentId = extractAgentId(c); // From request or derived from tx - - eventEmitter.emit({ - type: 'payment_submitted', - timestamp: Date.now(), - agentId, - txHash: payload.payload.txHash, - amount: routeConfig.amount, - }); - - // Verify via gRPC (faster than JSON-RPC) - const receipt = await evolveGrpcClient.getReceipt(payload.payload.txHash); - - if (!receipt.success) { - eventEmitter.emit({ - type: 'error', - timestamp: Date.now(), - agentId, - txHash: payload.payload.txHash, - error: 'Transaction failed', - }); - return c.json({ error: 'Payment verification failed' }, 402); - } - - eventEmitter.emit({ - type: 'payment_confirmed', - timestamp: Date.now(), - agentId, - txHash: payload.payload.txHash, - }); - - // Continue to handler - await next(); - - eventEmitter.emit({ - type: 'request_served', - timestamp: Date.now(), - agentId, - endpoint: `${c.req.method} ${c.req.path}`, - }); - }; -} -``` - -### 3. Dashboard (`/frontend`) - -Replace current user-facing UI with agent monitoring dashboard. - -#### New Pages - -``` -frontend/src/ -├── pages/ -│ ├── Dashboard.tsx # Main dashboard view -│ ├── AgentGrid.tsx # Grid of agent status cards -│ ├── PaymentStream.tsx # Real-time payment log -│ └── Metrics.tsx # Charts and statistics -├── components/ -│ ├── AgentCard.tsx # Individual agent status -│ ├── LatencyChart.tsx # Histogram of latencies -│ ├── TpsCounter.tsx # Live TPS display -│ └── TreasuryBalance.tsx -└── hooks/ - ├── useEventStream.ts # WebSocket connection - └── useMetrics.ts # Aggregated metrics -``` - -#### Dashboard Layout - -```typescript -// Dashboard.tsx -export function Dashboard() { - const events = useEventStream('ws://localhost:3000/events'); - const metrics = useMetrics(events); - - return ( -
-
-

X402 Agent Simulator

- - -
- -
-
- -
- -
- - -
- -
- -
-
-
- ); -} -``` - -#### Event Stream Hook - -```typescript -// useEventStream.ts -export function useEventStream(url: string) { - const [events, setEvents] = useState([]); - - useEffect(() => { - const ws = new WebSocket(url); - - ws.onmessage = (e) => { - const event = JSON.parse(e.data) as PaymentEvent; - setEvents((prev) => [...prev.slice(-999), event]); // Keep last 1000 - }; - - return () => ws.close(); - }, [url]); - - return events; -} -``` - -#### Agent Card Component - -```typescript -// AgentCard.tsx -interface AgentCardProps { - agent: AgentStatus; -} - -export function AgentCard({ agent }: AgentCardProps) { - return ( -
-
{agent.id.slice(0, 8)}
-
{agent.address.slice(0, 10)}...
-
- Requests: {agent.totalRequests} - Avg Latency: {agent.avgLatencyMs}ms - Balance: {formatEvo(agent.balance)} -
-
-
- ); -} -``` - -### 4. Remove/Deprecate - -The following components from the current implementation should be removed: - -- `/frontend/src/pages/Landing.tsx` - User login flow -- `/frontend/src/pages/Wallet.tsx` - User wallet management -- `/frontend/src/pages/Playground.tsx` - Manual API testing -- `/frontend/src/hooks/usePasskey.ts` - WebAuthn hooks -- `/server/src/passkey.ts` - WebAuthn authentication -- User session management in `/server/src/wallet.ts` - -Keep the wallet routes for agent funding (faucet) but remove user-specific logic. - -## Configuration - -### Environment Variables - -```bash -# Server -EVOLVE_GRPC_URL=localhost:9090 # gRPC endpoint -EVOLVE_RPC_URL=http://localhost:8545 # Fallback JSON-RPC -TREASURY_ADDRESS=0x0000000000000000000000000000000000000001 -FAUCET_PRIVATE_KEY=0x... -WS_PORT=3001 # WebSocket for dashboard - -# Simulator -SIMULATOR_AGENTS=10 -SIMULATOR_RPS=100 -SIMULATOR_SERVER_URL=http://localhost:3000 -SIMULATOR_FUNDING_AMOUNT=10000 -``` - -### Simulator Config File - -```yaml -# simulator.config.yaml -agents: 10 -requestsPerSecond: 100 -server: http://localhost:3000 -evolve: - grpcUrl: localhost:9090 - chainId: 1337 - -funding: - amount: 10000 - faucetKey: ${FAUCET_PRIVATE_KEY} - -endpoints: - - method: POST - path: /api/transform/reverse - weight: 40 - payload: - text: "hello world" - - method: POST - path: /api/transform/uppercase - weight: 30 - payload: - text: "hello world" - - method: POST - path: /api/transform/hash - weight: 20 - payload: - text: "hello world" - - method: POST - path: /api/transform/echo - weight: 10 - payload: - text: "hello world" -``` - -## Metrics - -### Collected Metrics - -| Metric | Type | Description | -|--------|------|-------------| -| `requests_total` | Counter | Total requests made | -| `requests_success` | Counter | Successful requests | -| `requests_failed` | Counter | Failed requests | -| `payments_total` | Counter | Total payments submitted | -| `payments_confirmed` | Counter | Confirmed payments | -| `request_latency_ms` | Histogram | End-to-end latency | -| `payment_latency_ms` | Histogram | Payment confirmation latency | -| `agent_balance` | Gauge | Per-agent balance | -| `treasury_balance` | Gauge | Treasury balance | -| `tps_current` | Gauge | Current transactions per second | - -### Dashboard Displays - -1. **TPS Counter**: Large real-time display of current throughput -2. **Latency Histogram**: Distribution of request latencies (p50, p95, p99) -3. **Agent Grid**: Visual grid showing all agents, color-coded by status -4. **Payment Stream**: Scrolling log of recent payments -5. **Success Rate**: Gauge showing percentage of successful requests -6. **Treasury Balance**: Live balance with incoming payment animation - -## Implementation Phases - -### Phase 1: Core Infrastructure -- [x] Create `/simulator` directory structure -- [x] Implement Agent and AgentPool classes -- [x] Add timestamp-based signing (prepare for nonceless) -- [x] Basic CLI to run simulator - -### Phase 2: Server Updates -- [x] Add WebSocket event emitter -- [x] Integrate event emission into X402 middleware -- [x] Prepare gRPC client interface (can stub with JSON-RPC initially) - using JSON-RPC for now -- [x] Add `/events` WebSocket endpoint - -### Phase 3: Dashboard -- [x] Remove old user-facing pages (simplified to Dashboard only) -- [x] Implement Dashboard layout -- [x] Add AgentGrid component -- [x] Add PaymentStream component -- [x] Add metrics charts (latency histogram, TPS counter) - -### Phase 4: gRPC Integration -- [x] Create gRPC client using existing protos from `crates/rpc/grpc/proto/evolve/v1/` -- [x] Unified EvolveClient interface supporting both JSON-RPC and gRPC backends -- [x] Auto-detection: uses gRPC if `EVOLVE_GRPC_URL` is set, otherwise JSON-RPC -- [ ] Performance testing with different block times - -### Phase 5: Optimization -- [ ] Implement nonceless transaction support (depends on Evolve changes) -- [ ] Connection pooling for gRPC -- [ ] Dashboard performance optimization for high event rates - -## Testing - -### Load Testing Scenarios - -```bash -# Baseline: 10 agents, 10 req/s, 100ms blocks -bun run simulator --agents 10 --rps 10 --block-time 100 - -# Medium: 50 agents, 100 req/s, 10ms blocks -bun run simulator --agents 50 --rps 100 --block-time 10 - -# High: 100 agents, 1000 req/s, 1ms blocks -bun run simulator --agents 100 --rps 1000 --block-time 1 -``` - -### Expected Latency Targets - -| Block Time | Target p50 | Target p99 | -|------------|------------|------------| -| 100ms | <150ms | <300ms | -| 10ms | <30ms | <100ms | -| 1ms | <10ms | <30ms | - -## Dependencies - -### New Dependencies - -```json -{ - "dependencies": { - "@grpc/grpc-js": "^1.10.0", - "@grpc/proto-loader": "^0.7.0", - "recharts": "^2.12.0", - "yaml": "^2.4.0" - } -} -``` - -## Open Questions - -1. Should agents have different "personalities" (aggressive vs conservative spending)? -2. Do we want to simulate agent failures/recovery? -3. Should the dashboard support replaying historical data? -4. What's the maximum agent count we want to support in the demo? diff --git a/examples/x402-demo/README.md b/examples/x402-demo/README.md deleted file mode 100644 index 2247cf5..0000000 --- a/examples/x402-demo/README.md +++ /dev/null @@ -1,163 +0,0 @@ -# X402 Demo - -Pay-per-request API monetization using [HTTP 402](https://x402.org) and the Evolve SDK. - -## Architecture - -``` - ┌──────────────────────┐ - │ Frontend (React) │ - │ http://localhost:5173 │ - └──────────┬───────────┘ - │ HTTP + WebSocket - ┌──────────▼───────────┐ - │ API Server (Bun+Hono)│ - │ http://localhost:3000 │ - │ - /api/transform/* │ - │ - /ws/events │ - └──────────┬───────────┘ - │ JSON-RPC -┌───────────────┐ gRPC ┌──────────▼───────────┐ -│ ev-node / │◄───────►│ evd (Evolve node) │ -│ local-da │ :50051 │ http://localhost:8545 │ -└───────────────┘ └──────────────────────┘ -``` - -**Components:** - -| Component | Port | Description | -|-----------|------|-------------| -| **evd** | 8545 (RPC), 50051 (gRPC) | Evolve execution node | -| **ev-node / local-da** | - | External consensus driving block production via gRPC | -| **server** | 3000 | X402 API server with payment middleware | -| **simulator** | - | CLI tool that spawns agents making paid requests | -| **frontend** | 5173 | React dashboard showing real-time metrics | - -## Prerequisites - -- Rust toolchain (for evd) -- [Bun](https://bun.sh) (for server + simulator) -- Node.js + npm (for frontend) -- External consensus process (ev-node or local-da) - -## Quick Start - -You need **4 terminals** (5 if you want the dashboard). - -### Terminal 1: Start evd - -```bash -# From repo root -rm -rf ./data -cargo run -p evd -- init --genesis-file examples/x402-demo/genesis.json -cargo run -p evd -- run --genesis-file examples/x402-demo/genesis.json -``` - -Wait for `Server ready. Press Ctrl+C to stop.` The genesis log will show all pre-registered EOA accounts with their addresses and balances. - -### Terminal 2: External consensus - -Start whatever drives block production via gRPC on `:50051`. Without this, transactions will be accepted into the mempool but never mined. - -### Terminal 3: API server - -```bash -cd examples/x402-demo/server -bun install # first time only -bun run src/index.ts -``` - -Verify: `curl http://localhost:3000/health` - -### Terminal 4: Simulator - -```bash -cd examples/x402-demo/simulator -bun install # first time only -FAUCET_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ - bun run src/index.ts --agents 5 --rps 2 --duration 60 -``` - -### Terminal 5 (optional): Frontend dashboard - -```bash -cd examples/x402-demo/frontend -npm install # first time only -npm run dev -``` - -Open http://localhost:5173 - -## Account Setup - -The sample `examples/x402-demo/genesis.json` pre-registers **20 Hardhat accounts** with well-known deterministic keys: - -| Account | Role | Address | Private Key | -|---------|------|---------|-------------| -| #0 (Alice) | Faucet | `0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266` | `0xac0974...f2ff80` | -| #1 (Bob) | - | `0x70997970C51812dc3A010C7d01b50e0d17dc79C8` | `0x59c699...8690d` | -| #2 | Treasury | `0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC` | `0x5de411...b365a` | -| #3-#19 | Agents (17) | See genesis log | See genesis log | - -**Token balances at genesis:** -- Alice: 1,000,000,000 (= 1000 tokens, 6 decimals) -- Bob: 2,000,000 (= 2 tokens, 6 decimals) - -## X402 Payment Flow - -1. Agent sends `POST /api/transform/reverse` with `{"input": "hello"}` -2. Server returns **402** with `PAYMENT-REQUIRED` header (base64-encoded JSON) -3. Agent parses the required amount and treasury address -4. Agent sends a **token transfer** transaction on-chain (calldata to the token contract) -5. Agent retries the request with `PAYMENT-SIGNATURE` header containing the txHash -6. Server verifies the payment on-chain, returns the result - -Payments use **token calldata** (not native value transfers), because the Evolve execution layer ignores the `value` field on transactions. - -## Simulator Options - -``` -Options: - -a, --agents Number of agents to spawn (max 17) [default: 10] - -r, --rps Target requests per second [default: 10] - -s, --server X402 server URL [default: http://localhost:3000] - -e, --evolve-rpc Evolve RPC URL [default: http://localhost:8545] - -f, --funding Funding per agent (6 decimals) [default: 1000000 = 1 token] - -k, --faucet-key Faucet private key [env: FAUCET_PRIVATE_KEY] - -d, --duration Run duration (0 = infinite) [default: 0] -``` - -## Environment Variables - -| Variable | Default | Description | -|----------|---------|-------------| -| `EVOLVE_RPC_URL` | `http://127.0.0.1:8545` | Evolve node JSON-RPC | -| `EVOLVE_GRPC_URL` | - | Evolve node gRPC (optional, enables gRPC client mode) | -| `TREASURY_ADDRESS` | `0x3C44Cd...93BC` | Hardhat #2, receives payments | -| `EVOLVE_NETWORK` | `evolve:1` | Network identifier for X402 | -| `EVOLVE_ASSET` | `native` | Asset identifier for X402 | -| `FAUCET_PRIVATE_KEY` | - | Alice's key, funds simulator agents | -| `PORT` | `3000` | Server listen port | -| `CORS_ORIGIN` | `*` | Allowed CORS origins | - -## API Pricing - -| Endpoint | Price (tokens) | Description | -|----------|---------------|-------------| -| `/api/transform/echo` | 100 | Returns input unchanged | -| `/api/transform/reverse` | 100 | Reverses input string | -| `/api/transform/uppercase` | 100 | Uppercases input | -| `/api/transform/hash` | 200 | SHA256 hash of input | - -## Verification - -```bash -# Check server health -curl http://localhost:3000/health - -# Check treasury balance (should increase as simulator runs) -curl http://localhost:3000/api/treasury - -# Check pricing -curl http://localhost:3000/api/pricing -``` \ No newline at end of file diff --git a/examples/x402-demo/docker-compose.yml b/examples/x402-demo/docker-compose.yml deleted file mode 100644 index 928e769..0000000 --- a/examples/x402-demo/docker-compose.yml +++ /dev/null @@ -1,36 +0,0 @@ -# X402 Demo - Docker Compose -# -# Prerequisites: -# 1. Run Evolve node separately: cargo run -p evolve_testapp -# 2. Or use EVOLVE_RPC_URL to point to an existing node -# -# Usage: -# docker compose up --build - -services: - server: - build: - context: ./server - ports: - - "3000:3000" - environment: - - PORT=3000 - # Point to host machine's Evolve node (use host.docker.internal on Mac/Windows) - - EVOLVE_RPC_URL=${EVOLVE_RPC_URL:-http://host.docker.internal:8545} - - EVOLVE_NETWORK=evolve:1337 - - RP_ID=localhost - - RP_ORIGIN=http://localhost:5173 - - TREASURY_ADDRESS=0x0000000000000000000000000000000000000001 - - FAUCET_PRIVATE_KEY=${FAUCET_PRIVATE_KEY:-} - extra_hosts: - - "host.docker.internal:host-gateway" - - frontend: - build: - context: ./frontend - ports: - - "5173:5173" - environment: - - VITE_API_URL=http://server:3000 - depends_on: - - server diff --git a/examples/x402-demo/frontend/Dockerfile b/examples/x402-demo/frontend/Dockerfile deleted file mode 100644 index e1de3fd..0000000 --- a/examples/x402-demo/frontend/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM oven/bun:1.1-alpine - -WORKDIR /app - -# Install dependencies -COPY package.json bun.lock* ./ -RUN bun install --frozen-lockfile - -# Copy source -COPY . . - -# Expose port -EXPOSE 5173 - -# Run dev server (use --host to allow external connections) -CMD ["bun", "run", "dev", "--", "--host", "0.0.0.0"] diff --git a/examples/x402-demo/frontend/bun.lock b/examples/x402-demo/frontend/bun.lock deleted file mode 100644 index 50aa9b8..0000000 --- a/examples/x402-demo/frontend/bun.lock +++ /dev/null @@ -1,266 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "x402-demo-frontend", - "dependencies": { - "@simplewebauthn/browser": "^11.0.0", - "@tanstack/react-query": "^5.60.0", - "react": "^18.3.0", - "react-dom": "^18.3.0", - "react-router-dom": "^6.28.0", - }, - "devDependencies": { - "@types/react": "^18.3.0", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.0", - "typescript": "^5.6.0", - "vite": "^5.4.0", - }, - }, - }, - "packages": { - "@babel/code-frame": ["@babel/code-frame@7.28.6", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q=="], - - "@babel/compat-data": ["@babel/compat-data@7.28.6", "", {}, "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg=="], - - "@babel/core": ["@babel/core@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw=="], - - "@babel/generator": ["@babel/generator@7.28.6", "", { "dependencies": { "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw=="], - - "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], - - "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], - - "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], - - "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], - - "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], - - "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], - - "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - - "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], - - "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="], - - "@babel/parser": ["@babel/parser@7.28.6", "", { "dependencies": { "@babel/types": "^7.28.6" }, "bin": "./bin/babel-parser.js" }, "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ=="], - - "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], - - "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], - - "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], - - "@babel/traverse": ["@babel/traverse@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/generator": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.6", "@babel/template": "^7.28.6", "@babel/types": "^7.28.6", "debug": "^4.3.1" } }, "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg=="], - - "@babel/types": ["@babel/types@7.28.6", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg=="], - - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="], - - "@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="], - - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="], - - "@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="], - - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="], - - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="], - - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="], - - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="], - - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="], - - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="], - - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="], - - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="], - - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="], - - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="], - - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="], - - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="], - - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="], - - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="], - - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="], - - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="], - - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="], - - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="], - - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@remix-run/router": ["@remix-run/router@1.23.2", "", {}, "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w=="], - - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], - - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.56.0", "", { "os": "android", "cpu": "arm" }, "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw=="], - - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.56.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q=="], - - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.56.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w=="], - - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.56.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g=="], - - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.56.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ=="], - - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.56.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg=="], - - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A=="], - - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw=="], - - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ=="], - - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA=="], - - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg=="], - - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA=="], - - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw=="], - - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg=="], - - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew=="], - - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ=="], - - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.56.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ=="], - - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw=="], - - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA=="], - - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.56.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA=="], - - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.56.0", "", { "os": "none", "cpu": "arm64" }, "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ=="], - - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.56.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing=="], - - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.56.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg=="], - - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ=="], - - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g=="], - - "@simplewebauthn/browser": ["@simplewebauthn/browser@11.0.0", "", { "dependencies": { "@simplewebauthn/types": "^11.0.0" } }, "sha512-KEGCStrl08QC2I561BzxqGiwoknblP6O1YW7jApdXLPtIqZ+vgJYAv8ssLCdm1wD8HGAHd49CJLkUF8X70x/pg=="], - - "@simplewebauthn/types": ["@simplewebauthn/types@11.0.0", "", {}, "sha512-b2o0wC5u2rWts31dTgBkAtSNKGX0cvL6h8QedNsKmj8O4QoLFQFR3DBVBUlpyVEhYKA+mXGUaXbcOc4JdQ3HzA=="], - - "@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="], - - "@tanstack/react-query": ["@tanstack/react-query@5.90.20", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw=="], - - "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], - - "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], - - "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], - - "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], - - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], - - "@types/prop-types": ["@types/prop-types@15.7.15", "", {}, "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw=="], - - "@types/react": ["@types/react@18.3.27", "", { "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" } }, "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w=="], - - "@types/react-dom": ["@types/react-dom@18.3.7", "", { "peerDependencies": { "@types/react": "^18.0.0" } }, "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ=="], - - "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], - - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.18", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA=="], - - "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001766", "", {}, "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="], - - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - - "electron-to-chromium": ["electron-to-chromium@1.5.278", "", {}, "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw=="], - - "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], - - "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - - "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], - - "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], - - "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], - - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], - - "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], - - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - - "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - - "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - - "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - - "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], - - "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], - - "react-router": ["react-router@6.30.3", "", { "dependencies": { "@remix-run/router": "1.23.2" }, "peerDependencies": { "react": ">=16.8" } }, "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw=="], - - "react-router-dom": ["react-router-dom@6.30.3", "", { "dependencies": { "@remix-run/router": "1.23.2", "react-router": "6.30.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag=="], - - "rollup": ["rollup@4.56.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.56.0", "@rollup/rollup-android-arm64": "4.56.0", "@rollup/rollup-darwin-arm64": "4.56.0", "@rollup/rollup-darwin-x64": "4.56.0", "@rollup/rollup-freebsd-arm64": "4.56.0", "@rollup/rollup-freebsd-x64": "4.56.0", "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", "@rollup/rollup-linux-arm-musleabihf": "4.56.0", "@rollup/rollup-linux-arm64-gnu": "4.56.0", "@rollup/rollup-linux-arm64-musl": "4.56.0", "@rollup/rollup-linux-loong64-gnu": "4.56.0", "@rollup/rollup-linux-loong64-musl": "4.56.0", "@rollup/rollup-linux-ppc64-gnu": "4.56.0", "@rollup/rollup-linux-ppc64-musl": "4.56.0", "@rollup/rollup-linux-riscv64-gnu": "4.56.0", "@rollup/rollup-linux-riscv64-musl": "4.56.0", "@rollup/rollup-linux-s390x-gnu": "4.56.0", "@rollup/rollup-linux-x64-gnu": "4.56.0", "@rollup/rollup-linux-x64-musl": "4.56.0", "@rollup/rollup-openbsd-x64": "4.56.0", "@rollup/rollup-openharmony-arm64": "4.56.0", "@rollup/rollup-win32-arm64-msvc": "4.56.0", "@rollup/rollup-win32-ia32-msvc": "4.56.0", "@rollup/rollup-win32-x64-gnu": "4.56.0", "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg=="], - - "scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], - - "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], - - "vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="], - - "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - } -} diff --git a/examples/x402-demo/frontend/dist/index.html b/examples/x402-demo/frontend/dist/index.html deleted file mode 100644 index 6e11c1b..0000000 --- a/examples/x402-demo/frontend/dist/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - X402 Demo - - - - -
- - diff --git a/examples/x402-demo/frontend/index.html b/examples/x402-demo/frontend/index.html deleted file mode 100644 index 0cfde5f..0000000 --- a/examples/x402-demo/frontend/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - X402 Demo - - - -
- - - diff --git a/examples/x402-demo/frontend/package-lock.json b/examples/x402-demo/frontend/package-lock.json deleted file mode 100644 index 2341cdc..0000000 --- a/examples/x402-demo/frontend/package-lock.json +++ /dev/null @@ -1,1812 +0,0 @@ -{ - "name": "x402-demo-frontend", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "x402-demo-frontend", - "version": "0.1.0", - "dependencies": { - "@simplewebauthn/browser": "^11.0.0", - "@tanstack/react-query": "^5.60.0", - "react": "^18.3.0", - "react-dom": "^18.3.0", - "react-router-dom": "^6.28.0" - }, - "devDependencies": { - "@types/react": "^18.3.0", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.0", - "typescript": "^5.6.0", - "vite": "^5.4.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@remix-run/router": { - "version": "1.23.2", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", - "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", - "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", - "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", - "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", - "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", - "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", - "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", - "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", - "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", - "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", - "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", - "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", - "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", - "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", - "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", - "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", - "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", - "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", - "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", - "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", - "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", - "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", - "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", - "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", - "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", - "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@simplewebauthn/browser": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-11.0.0.tgz", - "integrity": "sha512-KEGCStrl08QC2I561BzxqGiwoknblP6O1YW7jApdXLPtIqZ+vgJYAv8ssLCdm1wD8HGAHd49CJLkUF8X70x/pg==", - "license": "MIT", - "dependencies": { - "@simplewebauthn/types": "^11.0.0" - } - }, - "node_modules/@simplewebauthn/types": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-11.0.0.tgz", - "integrity": "sha512-b2o0wC5u2rWts31dTgBkAtSNKGX0cvL6h8QedNsKmj8O4QoLFQFR3DBVBUlpyVEhYKA+mXGUaXbcOc4JdQ3HzA==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT" - }, - "node_modules/@tanstack/query-core": { - "version": "5.90.20", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", - "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.90.20", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", - "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", - "license": "MIT", - "dependencies": { - "@tanstack/query-core": "5.90.20" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18 || ^19" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", - "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.19", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", - "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001768", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001768.tgz", - "integrity": "sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.286", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", - "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "6.30.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", - "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.30.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", - "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.2", - "react-router": "6.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/rollup": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", - "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.57.1", - "@rollup/rollup-android-arm64": "4.57.1", - "@rollup/rollup-darwin-arm64": "4.57.1", - "@rollup/rollup-darwin-x64": "4.57.1", - "@rollup/rollup-freebsd-arm64": "4.57.1", - "@rollup/rollup-freebsd-x64": "4.57.1", - "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", - "@rollup/rollup-linux-arm-musleabihf": "4.57.1", - "@rollup/rollup-linux-arm64-gnu": "4.57.1", - "@rollup/rollup-linux-arm64-musl": "4.57.1", - "@rollup/rollup-linux-loong64-gnu": "4.57.1", - "@rollup/rollup-linux-loong64-musl": "4.57.1", - "@rollup/rollup-linux-ppc64-gnu": "4.57.1", - "@rollup/rollup-linux-ppc64-musl": "4.57.1", - "@rollup/rollup-linux-riscv64-gnu": "4.57.1", - "@rollup/rollup-linux-riscv64-musl": "4.57.1", - "@rollup/rollup-linux-s390x-gnu": "4.57.1", - "@rollup/rollup-linux-x64-gnu": "4.57.1", - "@rollup/rollup-linux-x64-musl": "4.57.1", - "@rollup/rollup-openbsd-x64": "4.57.1", - "@rollup/rollup-openharmony-arm64": "4.57.1", - "@rollup/rollup-win32-arm64-msvc": "4.57.1", - "@rollup/rollup-win32-ia32-msvc": "4.57.1", - "@rollup/rollup-win32-x64-gnu": "4.57.1", - "@rollup/rollup-win32-x64-msvc": "4.57.1", - "fsevents": "~2.3.2" - } - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - } - } -} diff --git a/examples/x402-demo/frontend/package.json b/examples/x402-demo/frontend/package.json deleted file mode 100644 index 8d120ca..0000000 --- a/examples/x402-demo/frontend/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "x402-demo-frontend", - "version": "0.1.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "react": "^18.3.0", - "react-dom": "^18.3.0", - "react-router-dom": "^6.28.0", - "@simplewebauthn/browser": "^11.0.0", - "@tanstack/react-query": "^5.60.0" - }, - "devDependencies": { - "@types/react": "^18.3.0", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.0", - "typescript": "^5.6.0", - "vite": "^5.4.0" - } -} diff --git a/examples/x402-demo/frontend/src/App.tsx b/examples/x402-demo/frontend/src/App.tsx deleted file mode 100644 index e8543a7..0000000 --- a/examples/x402-demo/frontend/src/App.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Dashboard } from "./pages/Dashboard"; - -const styles = { - container: { - maxWidth: 1400, - margin: "0 auto", - padding: 24, - backgroundColor: "#0a0a0a", - minHeight: "100vh", - color: "#fff", - }, -}; - -export function App() { - return ( -
- -
- ); -} diff --git a/examples/x402-demo/frontend/src/components/AgentGrid.tsx b/examples/x402-demo/frontend/src/components/AgentGrid.tsx deleted file mode 100644 index b0fd81e..0000000 --- a/examples/x402-demo/frontend/src/components/AgentGrid.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import type { AgentStatus } from "../hooks/useMetrics"; - -interface AgentGridProps { - agents: Map; -} - -const styles = { - grid: { - display: "grid", - gridTemplateColumns: "repeat(auto-fill, minmax(120px, 1fr))", - gap: 8, - maxHeight: 400, - overflowY: "auto" as const, - }, - card: (status: AgentStatus["lastStatus"]) => ({ - padding: 12, - borderRadius: 6, - backgroundColor: "#252525", - borderLeft: `3px solid ${ - status === "success" ? "#22c55e" : status === "failed" ? "#ef4444" : "#f59e0b" - }`, - }), - agentId: { - fontSize: 12, - fontFamily: "monospace", - color: "#fff", - marginBottom: 4, - }, - stats: { - fontSize: 11, - color: "#888", - display: "flex", - flexDirection: "column" as const, - gap: 2, - }, - statRow: { - display: "flex", - justifyContent: "space-between", - }, - empty: { - color: "#666", - textAlign: "center" as const, - padding: 32, - }, -}; - -export function AgentGrid({ agents }: AgentGridProps) { - const agentList = Array.from(agents.values()).sort((a, b) => - a.id.localeCompare(b.id) - ); - - if (agentList.length === 0) { - return
No agents connected
; - } - - return ( -
- {agentList.map((agent) => { - const avgLatency = - agent.totalRequests > 0 - ? Math.round(agent.totalLatencyMs / agent.totalRequests) - : 0; - const successRate = - agent.totalRequests > 0 - ? Math.round((agent.successfulRequests / agent.totalRequests) * 100) - : 0; - - return ( -
-
{agent.id}
-
-
- Requests: - {agent.totalRequests} -
-
- Success: - {successRate}% -
-
- Avg Lat: - {avgLatency}ms -
-
-
- ); - })} -
- ); -} diff --git a/examples/x402-demo/frontend/src/components/MetricsPanel.tsx b/examples/x402-demo/frontend/src/components/MetricsPanel.tsx deleted file mode 100644 index f920a2d..0000000 --- a/examples/x402-demo/frontend/src/components/MetricsPanel.tsx +++ /dev/null @@ -1,144 +0,0 @@ -import type { Metrics } from "../hooks/useMetrics"; - -interface MetricsPanelProps { - metrics: Metrics; -} - -const styles = { - container: { - display: "grid", - gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", - gap: 16, - }, - metric: { - padding: 16, - backgroundColor: "#252525", - borderRadius: 8, - }, - label: { - fontSize: 12, - color: "#888", - marginBottom: 4, - }, - value: { - fontSize: 28, - fontWeight: 600, - fontVariantNumeric: "tabular-nums", - color: "#fff", - }, - errorValue: { - fontSize: 28, - fontWeight: 600, - fontVariantNumeric: "tabular-nums", - color: "#ef4444", - }, - unit: { - fontSize: 14, - color: "#666", - marginLeft: 4, - }, - histogram: { - gridColumn: "span 2", - padding: 16, - backgroundColor: "#252525", - borderRadius: 8, - }, - histogramTitle: { - fontSize: 12, - color: "#888", - marginBottom: 12, - }, - bars: { - display: "flex", - alignItems: "flex-end", - gap: 8, - height: 80, - }, - barContainer: { - flex: 1, - display: "flex", - flexDirection: "column" as const, - alignItems: "center", - gap: 4, - }, - bar: (height: number) => ({ - width: "100%", - height: `${height}%`, - backgroundColor: "#3b82f6", - borderRadius: 2, - minHeight: 2, - }), - barLabel: { - fontSize: 10, - color: "#666", - }, - barValue: { - fontSize: 10, - color: "#888", - }, -}; - -const BUCKET_LABELS = ["<50ms", "50-100", "100-200", "200-500", "500+"]; - -export function MetricsPanel({ metrics }: MetricsPanelProps) { - const maxBucket = Math.max(...metrics.latencyHistogram, 1); - - return ( -
-
-
Total Requests
-
{metrics.totalRequests.toLocaleString()}
-
- -
-
Success Rate
-
- {metrics.successRate} - % -
-
- -
-
Avg Latency
-
- {metrics.avgLatencyMs} - ms -
-
- -
-
p95 Latency
-
- {metrics.p95LatencyMs} - ms -
-
- -
-
p99 Latency
-
- {metrics.p99LatencyMs} - ms -
-
- -
-
Active Agents
-
{metrics.agents.size}
-
- -
-
Latency Distribution
-
- {metrics.latencyHistogram.map((count, idx) => ( -
-
{count}
-
-
{BUCKET_LABELS[idx]}
-
- ))} -
-
-
- ); -} diff --git a/examples/x402-demo/frontend/src/components/PaymentStream.tsx b/examples/x402-demo/frontend/src/components/PaymentStream.tsx deleted file mode 100644 index 9968746..0000000 --- a/examples/x402-demo/frontend/src/components/PaymentStream.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import { useRef, useEffect } from "react"; -import type { PaymentEvent } from "../hooks/useEventStream"; - -interface PaymentStreamProps { - events: PaymentEvent[]; -} - -const styles = { - container: { - maxHeight: 400, - overflowY: "auto" as const, - fontFamily: "monospace", - fontSize: 12, - }, - event: (type: PaymentEvent["type"]) => ({ - padding: "6px 8px", - borderBottom: "1px solid #333", - display: "flex", - alignItems: "center", - gap: 8, - backgroundColor: - type === "payment_failed" || type === "error" - ? "rgba(239, 68, 68, 0.1)" - : type === "request_served" - ? "rgba(34, 197, 94, 0.05)" - : "transparent", - }), - timestamp: { - color: "#666", - flexShrink: 0, - width: 80, - }, - type: (type: PaymentEvent["type"]) => ({ - color: - type === "payment_submitted" - ? "#f59e0b" - : type === "payment_confirmed" - ? "#3b82f6" - : type === "request_served" - ? "#22c55e" - : type === "payment_failed" || type === "error" - ? "#ef4444" - : "#888", - flexShrink: 0, - width: 110, - }), - agent: { - color: "#888", - flexShrink: 0, - width: 90, - }, - details: { - color: "#aaa", - flex: 1, - overflow: "hidden", - textOverflow: "ellipsis", - whiteSpace: "nowrap" as const, - }, - empty: { - color: "#666", - textAlign: "center" as const, - padding: 32, - }, -}; - -function formatTime(timestamp: number): string { - const date = new Date(timestamp); - return date.toLocaleTimeString("en-US", { - hour12: false, - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); -} - -function formatEventDetails(event: PaymentEvent): string { - switch (event.type) { - case "payment_submitted": - return `${event.amount} wei → ${event.to?.slice(0, 10)}...`; - case "payment_confirmed": - return event.txHash ? `tx: ${event.txHash.slice(0, 14)}...` : ""; - case "request_served": - return `${event.endpoint} (${event.latencyMs}ms)`; - case "payment_failed": - case "error": - return event.error ?? "Unknown error"; - default: - return ""; - } -} - -export function PaymentStream({ events }: PaymentStreamProps) { - const containerRef = useRef(null); - const shouldScrollRef = useRef(true); - - // Auto-scroll to bottom when new events arrive - useEffect(() => { - if (containerRef.current && shouldScrollRef.current) { - containerRef.current.scrollTop = containerRef.current.scrollHeight; - } - }, [events]); - - // Detect if user has scrolled up - const handleScroll = () => { - if (containerRef.current) { - const { scrollTop, scrollHeight, clientHeight } = containerRef.current; - shouldScrollRef.current = scrollHeight - scrollTop - clientHeight < 50; - } - }; - - // Show only recent events (last 100) - const recentEvents = events.slice(-100); - - if (recentEvents.length === 0) { - return
Waiting for events...
; - } - - return ( -
- {recentEvents.map((event, idx) => ( -
- {formatTime(event.timestamp)} - {event.type} - {event.agentId} - {formatEventDetails(event)} -
- ))} -
- ); -} diff --git a/examples/x402-demo/frontend/src/components/TpsCounter.tsx b/examples/x402-demo/frontend/src/components/TpsCounter.tsx deleted file mode 100644 index 6cab1ae..0000000 --- a/examples/x402-demo/frontend/src/components/TpsCounter.tsx +++ /dev/null @@ -1,33 +0,0 @@ -interface TpsCounterProps { - value: number; -} - -const styles = { - container: { - display: "flex", - alignItems: "baseline", - gap: 4, - padding: "8px 16px", - backgroundColor: "#1a1a1a", - borderRadius: 8, - }, - value: { - fontSize: 32, - fontWeight: 700, - fontVariantNumeric: "tabular-nums", - color: "#3b82f6", - }, - label: { - fontSize: 14, - color: "#888", - }, -}; - -export function TpsCounter({ value }: TpsCounterProps) { - return ( -
- {value.toFixed(1)} - TPS -
- ); -} diff --git a/examples/x402-demo/frontend/src/hooks/useChainStats.ts b/examples/x402-demo/frontend/src/hooks/useChainStats.ts deleted file mode 100644 index 4e3d203..0000000 --- a/examples/x402-demo/frontend/src/hooks/useChainStats.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { useEffect, useState } from "react"; -import { - ApiError, - getChainStats, - getEventsSummary, - getHealth, - getLatestBlockTxCountViaRpc, - type ChainStats, -} from "../lib/api"; - -export interface UseChainStatsResult { - data: ChainStats | null; - error: string | null; - loading: boolean; -} - -export function useChainStats(intervalMs: number = 1000): UseChainStatsResult { - const [data, setData] = useState(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - let cancelled = false; - - const fetchStats = async () => { - try { - let stats: ChainStats; - try { - stats = await getChainStats(); - } catch (err) { - // Backward compatibility with older server versions lacking /api/chain. - if (!(err instanceof ApiError) || err.status !== 404) { - throw err; - } - - const [health, events, latestBlockTxCount] = await Promise.all([ - getHealth(), - getEventsSummary(), - getLatestBlockTxCountViaRpc(), - ]); - stats = { - chainId: health.chain.id, - blockNumber: health.chain.blockNumber, - latestBlockTxCount, - latestBlockTimestamp: null, - observedPaymentTxs: events.metrics.totalPayments, - observedServedRequests: events.metrics.totalRequests, - }; - } - - if (!cancelled) { - setData(stats); - setError(null); - setLoading(false); - } - } catch (err) { - if (!cancelled) { - setError(err instanceof Error ? err.message : "Failed to fetch chain stats"); - setLoading(false); - } - } - }; - - fetchStats(); - const timer = setInterval(fetchStats, intervalMs); - - return () => { - cancelled = true; - clearInterval(timer); - }; - }, [intervalMs]); - - return { data, error, loading }; -} diff --git a/examples/x402-demo/frontend/src/hooks/useEventStream.ts b/examples/x402-demo/frontend/src/hooks/useEventStream.ts deleted file mode 100644 index ea65a30..0000000 --- a/examples/x402-demo/frontend/src/hooks/useEventStream.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { useState, useEffect, useRef, useCallback } from "react"; - -export interface PaymentEvent { - type: - | "payment_submitted" - | "payment_confirmed" - | "payment_failed" - | "request_served" - | "agent_connected" - | "agent_disconnected" - | "error"; - timestamp: number; - agentId: string; - txHash?: string; - amount?: string; - endpoint?: string; - latencyMs?: number; - error?: string; - from?: string; - to?: string; -} - -interface HistoryMessage { - type: "history"; - events: PaymentEvent[]; -} - -type WebSocketMessage = PaymentEvent | HistoryMessage; - -export interface UseEventStreamReturn { - events: PaymentEvent[]; - connected: boolean; - error: string | null; - clearEvents: () => void; -} - -export function useEventStream(url: string): UseEventStreamReturn { - const [events, setEvents] = useState([]); - const [connected, setConnected] = useState(false); - const [error, setError] = useState(null); - const wsRef = useRef(null); - const reconnectTimeoutRef = useRef>(); - - const connect = useCallback(() => { - if (wsRef.current?.readyState === WebSocket.OPEN) return; - - try { - const ws = new WebSocket(url); - wsRef.current = ws; - - ws.onopen = () => { - setConnected(true); - setError(null); - console.log("WebSocket connected"); - }; - - ws.onclose = () => { - setConnected(false); - console.log("WebSocket disconnected, reconnecting..."); - // Reconnect after 2 seconds - reconnectTimeoutRef.current = setTimeout(connect, 2000); - }; - - ws.onerror = (e) => { - setError("WebSocket connection error"); - console.error("WebSocket error:", e); - }; - - ws.onmessage = (e) => { - try { - const message = JSON.parse(e.data) as WebSocketMessage; - - if ("type" in message && message.type === "history") { - // Initial history load - setEvents(message.events); - } else { - // Single event - setEvents((prev) => { - const newEvents = [...prev, message as PaymentEvent]; - // Keep last 500 events - return newEvents.slice(-500); - }); - } - } catch (err) { - console.error("Failed to parse WebSocket message:", err); - } - }; - } catch (err) { - setError("Failed to connect to WebSocket"); - console.error("WebSocket connection failed:", err); - } - }, [url]); - - useEffect(() => { - connect(); - - return () => { - if (reconnectTimeoutRef.current) { - clearTimeout(reconnectTimeoutRef.current); - } - if (wsRef.current) { - wsRef.current.close(); - } - }; - }, [connect]); - - const clearEvents = useCallback(() => { - setEvents([]); - }, []); - - return { events, connected, error, clearEvents }; -} diff --git a/examples/x402-demo/frontend/src/hooks/useMetrics.ts b/examples/x402-demo/frontend/src/hooks/useMetrics.ts deleted file mode 100644 index 1669beb..0000000 --- a/examples/x402-demo/frontend/src/hooks/useMetrics.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { useMemo, useState, useEffect, useRef } from "react"; -import type { PaymentEvent } from "./useEventStream"; - -export interface AgentStatus { - id: string; - totalRequests: number; - successfulRequests: number; - failedRequests: number; - totalLatencyMs: number; - lastSeen: number; - lastStatus: "success" | "failed" | "pending"; -} - -export interface Metrics { - totalRequests: number; - successfulRequests: number; - failedRequests: number; - mempoolErrors: number; - successRate: number; - avgLatencyMs: number; - p50LatencyMs: number; - p95LatencyMs: number; - p99LatencyMs: number; - currentTps: number; - agents: Map; - latencyHistogram: number[]; -} - -function percentile(sorted: number[], p: number): number { - if (sorted.length === 0) return 0; - const idx = Math.ceil((p / 100) * sorted.length) - 1; - return sorted[Math.max(0, idx)]; -} - -function isMempoolError(message?: string): boolean { - if (!message) return false; - return /transaction already in mempool|already known/i.test(message); -} - -export function useMetrics(events: PaymentEvent[]): Metrics { - const [tps, setTps] = useState(0); - const recentRequestsRef = useRef([]); - - // Calculate TPS every 100ms - useEffect(() => { - const interval = setInterval(() => { - const now = Date.now(); - const windowMs = 1000; - recentRequestsRef.current = recentRequestsRef.current.filter( - (t) => now - t < windowMs - ); - setTps(recentRequestsRef.current.length); - }, 100); - - return () => clearInterval(interval); - }, []); - - // Track new request_served events for TPS - useEffect(() => { - const servedEvents = events.filter((e) => e.type === "request_served"); - if (servedEvents.length > 0) { - const lastEvent = servedEvents[servedEvents.length - 1]; - if ( - !recentRequestsRef.current.includes(lastEvent.timestamp) && - Date.now() - lastEvent.timestamp < 2000 - ) { - recentRequestsRef.current.push(lastEvent.timestamp); - } - } - }, [events]); - - return useMemo(() => { - const agents = new Map(); - const latencies: number[] = []; - - let totalRequests = 0; - let successfulRequests = 0; - let failedRequests = 0; - let mempoolErrors = 0; - - for (const event of events) { - if (!event.agentId) continue; - - // Initialize agent if not exists - if (!agents.has(event.agentId)) { - agents.set(event.agentId, { - id: event.agentId, - totalRequests: 0, - successfulRequests: 0, - failedRequests: 0, - totalLatencyMs: 0, - lastSeen: event.timestamp, - lastStatus: "pending", - }); - } - - const agent = agents.get(event.agentId)!; - agent.lastSeen = event.timestamp; - - switch (event.type) { - case "request_served": - totalRequests++; - successfulRequests++; - agent.totalRequests++; - agent.successfulRequests++; - agent.lastStatus = "success"; - if (event.latencyMs) { - latencies.push(event.latencyMs); - agent.totalLatencyMs += event.latencyMs; - } - break; - - case "payment_failed": - case "error": - failedRequests++; - agent.failedRequests++; - agent.lastStatus = "failed"; - if (isMempoolError(event.error)) { - mempoolErrors++; - } - break; - - case "payment_submitted": - agent.lastStatus = "pending"; - break; - } - } - - // Calculate latency stats - const sortedLatencies = [...latencies].sort((a, b) => a - b); - const avgLatencyMs = - sortedLatencies.length > 0 - ? sortedLatencies.reduce((a, b) => a + b, 0) / sortedLatencies.length - : 0; - - // Create histogram buckets (0-50, 50-100, 100-200, 200-500, 500+) - const buckets = [0, 0, 0, 0, 0]; - for (const lat of latencies) { - if (lat < 50) buckets[0]++; - else if (lat < 100) buckets[1]++; - else if (lat < 200) buckets[2]++; - else if (lat < 500) buckets[3]++; - else buckets[4]++; - } - - const successRate = - totalRequests > 0 ? (successfulRequests / totalRequests) * 100 : 0; - - return { - totalRequests, - successfulRequests, - failedRequests, - mempoolErrors, - successRate: Math.round(successRate * 10) / 10, - avgLatencyMs: Math.round(avgLatencyMs), - p50LatencyMs: percentile(sortedLatencies, 50), - p95LatencyMs: percentile(sortedLatencies, 95), - p99LatencyMs: percentile(sortedLatencies, 99), - currentTps: tps, - agents, - latencyHistogram: buckets, - }; - }, [events, tps]); -} diff --git a/examples/x402-demo/frontend/src/hooks/usePasskey.ts b/examples/x402-demo/frontend/src/hooks/usePasskey.ts deleted file mode 100644 index 7ae2f9e..0000000 --- a/examples/x402-demo/frontend/src/hooks/usePasskey.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { useState, useCallback, useEffect } from "react"; -import { - startRegistration as webauthnRegister, - startAuthentication as webauthnAuth, -} from "@simplewebauthn/browser"; -import { - startRegistration, - verifyRegistration, - startLogin, - verifyLogin, -} from "../lib/api"; - -type Session = { - token: string; - address: string; - username: string; -}; - -const SESSION_KEY = "x402_session"; - -function loadSession(): Session | null { - try { - const stored = localStorage.getItem(SESSION_KEY); - return stored ? JSON.parse(stored) : null; - } catch { - return null; - } -} - -function saveSession(session: Session | null) { - if (session) { - localStorage.setItem(SESSION_KEY, JSON.stringify(session)); - } else { - localStorage.removeItem(SESSION_KEY); - } -} - -export function usePasskey() { - const [session, setSession] = useState(loadSession); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - - useEffect(() => { - saveSession(session); - }, [session]); - - const register = useCallback(async (username: string) => { - setLoading(true); - setError(null); - - try { - const { options, userId } = await startRegistration(username); - const credential = await webauthnRegister({ optionsJSON: options }); - const result = await verifyRegistration(userId, credential); - - const newSession: Session = { - token: result.sessionToken, - address: result.address, - username, - }; - setSession(newSession); - return newSession; - } catch (err) { - const message = err instanceof Error ? err.message : "Registration failed"; - setError(message); - throw err; - } finally { - setLoading(false); - } - }, []); - - const login = useCallback(async (username?: string) => { - setLoading(true); - setError(null); - - try { - const { options, authId } = await startLogin(username); - const credential = await webauthnAuth({ optionsJSON: options }); - const result = await verifyLogin(authId, credential); - - const newSession: Session = { - token: result.sessionToken, - address: result.address, - username: result.username, - }; - setSession(newSession); - return newSession; - } catch (err) { - const message = err instanceof Error ? err.message : "Login failed"; - setError(message); - throw err; - } finally { - setLoading(false); - } - }, []); - - const logout = useCallback(() => { - setSession(null); - setError(null); - }, []); - - return { - session, - isAuthenticated: !!session, - loading, - error, - register, - login, - logout, - }; -} diff --git a/examples/x402-demo/frontend/src/lib/api.ts b/examples/x402-demo/frontend/src/lib/api.ts deleted file mode 100644 index c2f2b98..0000000 --- a/examples/x402-demo/frontend/src/lib/api.ts +++ /dev/null @@ -1,267 +0,0 @@ -const API_BASE = ""; - -type ApiOptions = { - method?: string; - body?: unknown; - token?: string; - paymentSignature?: string; -}; - -export class ApiError extends Error { - constructor( - public status: number, - public data: unknown, - public headers: Headers - ) { - super(`API Error: ${status}`); - } - - get isPaymentRequired(): boolean { - return this.status === 402; - } - - get paymentRequirements(): PaymentRequired | null { - const header = this.headers.get("PAYMENT-REQUIRED"); - if (!header) return null; - try { - return JSON.parse(atob(header)); - } catch { - return null; - } - } -} - -export type PaymentRequired = { - x402Version: number; - resource?: { - url: string; - description: string; - mimeType: string; - }; - accepts: Array<{ - scheme: string; - network: string; - asset: string; - amount: string; - payTo: string; - maxTimeoutSeconds?: number; - extra?: Record; - }>; - description?: string; -}; - -async function api(path: string, options: ApiOptions = {}): Promise { - const headers: Record = { - "Content-Type": "application/json", - }; - - if (options.token) { - headers["Authorization"] = `Bearer ${options.token}`; - } - - if (options.paymentSignature) { - headers["PAYMENT-SIGNATURE"] = options.paymentSignature; - } - - const res = await fetch(`${API_BASE}${path}`, { - method: options.method ?? "GET", - headers, - body: options.body ? JSON.stringify(options.body) : undefined, - }); - - if (!res.ok) { - const data = await res.json().catch(() => ({})); - throw new ApiError(res.status, data, res.headers); - } - - return res.json(); -} - -// Auth endpoints - using 'any' for WebAuthn JSON types as they're passed through -export async function startRegistration(username: string) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return api<{ options: any; userId: string }>( - "/auth/register", - { method: "POST", body: { username } } - ); -} - -export async function verifyRegistration( - userId: string, - credential: unknown -) { - return api<{ success: boolean; address: string; sessionToken: string }>( - "/auth/register/verify", - { method: "POST", body: { userId, credential } } - ); -} - -export async function startLogin(username?: string) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return api<{ options: any; authId: string }>( - "/auth/login", - { method: "POST", body: { username } } - ); -} - -export async function verifyLogin(authId: string, credential: unknown) { - return api<{ - success: boolean; - address: string; - username: string; - sessionToken: string; - }>("/auth/login/verify", { method: "POST", body: { authId, credential } }); -} - -// Wallet endpoints -export async function getBalance(address: string) { - return api<{ - address: string; - balance: string; - balanceFormatted: string; - nonce: number; - }>(`/wallet/balance?address=${address}`); -} - -export async function getMyWallet(token: string) { - return api<{ - address: string; - username: string; - balance: string; - balanceFormatted: string; - }>("/wallet/me", { token }); -} - -export async function requestFaucet(token: string) { - return api<{ txHash: string; newBalance: string }>("/wallet/faucet", { - method: "POST", - token, - }); -} - -export async function transfer( - token: string, - to: string, - amount: string -) { - return api<{ txHash: string }>("/wallet/transfer", { - method: "POST", - token, - body: { to, amount }, - }); -} - -// Transform endpoints (X402 protected) -export async function transform( - operation: "echo" | "reverse" | "uppercase" | "hash", - input: string, - paymentSignature?: string -) { - return api<{ output: string; operation: string; cost: string }>( - `/api/transform/${operation}`, - { method: "POST", body: { input }, paymentSignature } - ); -} - -export async function getPricing() { - return api<{ - treasury: string; - network: string; - endpoints: Array<{ route: string; price: string; description: string }>; - }>("/api/pricing"); -} - -export type HealthResponse = { - status: string; - chain: { - id: number; - blockNumber: string; - }; -}; - -export async function getHealth() { - return api("/health"); -} - -export type EventsSummaryResponse = { - metrics: { - totalPayments: number; - successfulPayments: number; - failedPayments: number; - totalRequests: number; - }; -}; - -export async function getEventsSummary() { - return api("/api/events"); -} - -type RpcResponse = { - result?: T; - error?: unknown; -}; - -async function callRpc(method: string, params: unknown[]): Promise { - const res = await fetch("/rpc", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ - jsonrpc: "2.0", - id: 1, - method, - params, - }), - }); - - const data = (await res.json()) as RpcResponse; - if (data.error || data.result === undefined) return null; - return data.result; -} - -export async function getLatestBlockTxCountViaRpc(): Promise { - const result = await callRpc( - "eth_getBlockTransactionCountByNumber", - ["latest"] - ); - if (typeof result !== "string") return null; - return Number.parseInt(result, 16); -} - -export type ChainStats = { - chainId: number; - blockNumber: string; - latestBlockTxCount: number | null; - latestBlockTimestamp: string | null; - observedPaymentTxs: number; - observedServedRequests: number; -}; - -export async function getChainStats() { - return api("/api/chain"); -} - -// Payment helper — builds v2 PaymentPayload with resource + accepted -export function createPaymentSignature( - txHash: string, - paymentRequired?: PaymentRequired, -): string { - const payload = { - x402Version: 2, - resource: paymentRequired?.resource ?? { - url: "", - description: "", - mimeType: "application/json", - }, - accepted: paymentRequired?.accepts?.[0] ?? { - scheme: "exact", - network: "evolve:1337", - asset: "native", - amount: "0", - payTo: "", - maxTimeoutSeconds: 300, - extra: {}, - }, - payload: { txHash }, - }; - return btoa(JSON.stringify(payload)); -} diff --git a/examples/x402-demo/frontend/src/main.tsx b/examples/x402-demo/frontend/src/main.tsx deleted file mode 100644 index f258e49..0000000 --- a/examples/x402-demo/frontend/src/main.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import { App } from "./App"; - -createRoot(document.getElementById("root")!).render( - - - -); diff --git a/examples/x402-demo/frontend/src/pages/Dashboard.tsx b/examples/x402-demo/frontend/src/pages/Dashboard.tsx deleted file mode 100644 index 118bce7..0000000 --- a/examples/x402-demo/frontend/src/pages/Dashboard.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import { useEventStream } from "../hooks/useEventStream"; -import { useMetrics } from "../hooks/useMetrics"; -import { AgentGrid } from "../components/AgentGrid"; -import { PaymentStream } from "../components/PaymentStream"; -import { MetricsPanel } from "../components/MetricsPanel"; -import { TpsCounter } from "../components/TpsCounter"; -import { useChainStats } from "../hooks/useChainStats"; - -const WS_URL = `ws://${window.location.hostname}:3000/ws/events`; - -const styles = { - container: { - display: "flex", - flexDirection: "column" as const, - gap: 24, - minHeight: "100vh", - }, - header: { - display: "flex", - alignItems: "center", - justifyContent: "space-between", - padding: "16px 0", - borderBottom: "1px solid #333", - }, - title: { - fontSize: 24, - fontWeight: 600, - margin: 0, - }, - status: { - display: "flex", - alignItems: "center", - gap: 8, - }, - statusDot: (connected: boolean) => ({ - width: 8, - height: 8, - borderRadius: "50%", - backgroundColor: connected ? "#22c55e" : "#ef4444", - }), - statusText: { - fontSize: 12, - color: "#888", - }, - grid: { - display: "grid", - gridTemplateColumns: "1fr 1fr", - gap: 24, - }, - chainGrid: { - display: "grid", - gridTemplateColumns: "repeat(auto-fit, minmax(180px, 1fr))", - gap: 12, - }, - chainCard: { - backgroundColor: "#111", - border: "1px solid #2a2a2a", - borderRadius: 8, - padding: 12, - }, - chainLabel: { - fontSize: 11, - color: "#888", - marginBottom: 4, - textTransform: "uppercase" as const, - letterSpacing: 0.8, - }, - chainValue: { - fontSize: 22, - fontWeight: 600, - color: "#fff", - fontVariantNumeric: "tabular-nums", - }, - chainSubvalue: { - fontSize: 12, - color: "#777", - marginTop: 4, - }, - fullWidth: { - gridColumn: "1 / -1", - }, - section: { - backgroundColor: "#1a1a1a", - borderRadius: 8, - padding: 16, - }, - sectionTitle: { - fontSize: 14, - fontWeight: 600, - color: "#888", - marginBottom: 12, - textTransform: "uppercase" as const, - letterSpacing: 1, - }, -}; - -export function Dashboard() { - const { events, connected, error } = useEventStream(WS_URL); - const metrics = useMetrics(events); - const { data: chainStats, error: chainError, loading: chainLoading } = useChainStats(1000); - - return ( -
-
-

X402 Agent Simulator

-
- -
- - {connected ? "Connected" : "Disconnected"} - -
-
- - {error && ( -
- {error} -
- )} - {chainError && ( -
- Chain stats error: {chainError} -
- )} - -
-
-
EV Node
-
-
-
Latest Block
-
- {chainLoading || !chainStats ? "..." : Number(chainStats.blockNumber).toLocaleString()} -
-
-
-
Txs In Latest Block
-
- {chainLoading || !chainStats - ? "..." - : chainStats.latestBlockTxCount === null - ? "n/a" - : chainStats.latestBlockTxCount.toLocaleString()} -
-
- {chainStats?.latestBlockTimestamp - ? new Date(chainStats.latestBlockTimestamp).toLocaleTimeString() - : "timestamp n/a"} -
-
-
-
Observed Payment Txs
-
- {chainLoading || !chainStats ? "..." : chainStats.observedPaymentTxs.toLocaleString()} -
-
-
-
- -
-
Metrics
- -
- -
-
Active Agents ({metrics.agents.size})
- -
- -
-
Payment Stream
- -
-
-
- ); -} diff --git a/examples/x402-demo/frontend/src/pages/Landing.tsx b/examples/x402-demo/frontend/src/pages/Landing.tsx deleted file mode 100644 index 3ab43d0..0000000 --- a/examples/x402-demo/frontend/src/pages/Landing.tsx +++ /dev/null @@ -1,191 +0,0 @@ -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { usePasskey } from "../hooks/usePasskey"; - -const styles = { - hero: { - textAlign: "center" as const, - padding: "48px 0", - }, - title: { - fontSize: 48, - fontWeight: 700, - marginBottom: 16, - }, - subtitle: { - fontSize: 18, - color: "#888", - marginBottom: 48, - }, - form: { - maxWidth: 320, - margin: "0 auto", - display: "flex", - flexDirection: "column" as const, - gap: 12, - }, - input: { - padding: "12px 16px", - fontSize: 16, - border: "1px solid #333", - borderRadius: 8, - background: "#111", - color: "#fff", - }, - button: { - padding: "12px 16px", - fontSize: 16, - border: "none", - borderRadius: 8, - cursor: "pointer", - fontWeight: 600, - }, - primary: { - background: "#3b82f6", - color: "#fff", - }, - secondary: { - background: "#222", - color: "#fff", - }, - error: { - color: "#ef4444", - fontSize: 14, - textAlign: "center" as const, - }, - divider: { - display: "flex", - alignItems: "center", - gap: 12, - color: "#666", - fontSize: 14, - }, - line: { - flex: 1, - height: 1, - background: "#333", - }, - features: { - marginTop: 64, - display: "grid", - gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", - gap: 24, - }, - feature: { - padding: 24, - background: "#111", - borderRadius: 12, - border: "1px solid #222", - }, - featureTitle: { - fontSize: 16, - fontWeight: 600, - marginBottom: 8, - }, - featureText: { - fontSize: 14, - color: "#888", - }, -} as const; - -export function Landing() { - const navigate = useNavigate(); - const { isAuthenticated, loading, error, register, login } = usePasskey(); - const [username, setUsername] = useState(""); - const [mode, setMode] = useState<"register" | "login">("register"); - - if (isAuthenticated) { - navigate("/wallet"); - return null; - } - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - try { - if (mode === "register") { - await register(username); - } else { - await login(username || undefined); - } - navigate("/wallet"); - } catch { - // Error handled by hook - } - }; - - return ( -
-
-

X402 Demo

-

- Pay-per-request API monetization with HTTP 402 -

- -
- {mode === "register" && ( - setUsername(e.target.value)} - minLength={3} - required - /> - )} - - - - {error &&

{error}

} - -
-
- or -
-
- - - -
- -
-
-

Passkey Wallet

-

- Secure, phishing-resistant authentication with WebAuthn -

-
-
-

HTTP 402

-

- Machine-readable payment flows using the X402 protocol -

-
-
-

Micropayments

-

- Pay-per-request for API access with instant settlement -

-
-
-
- ); -} diff --git a/examples/x402-demo/frontend/src/pages/Playground.tsx b/examples/x402-demo/frontend/src/pages/Playground.tsx deleted file mode 100644 index 298f515..0000000 --- a/examples/x402-demo/frontend/src/pages/Playground.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import { useState } from "react"; -import { useQuery } from "@tanstack/react-query"; -import { usePasskey } from "../hooks/usePasskey"; -import { - getPricing, - transform, - transfer, - ApiError, - createPaymentSignature, -} from "../lib/api"; - -const styles = { - grid: { - display: "grid", - gridTemplateColumns: "1fr 1fr", - gap: 24, - }, - card: { - padding: 24, - background: "#111", - borderRadius: 12, - border: "1px solid #222", - }, - label: { - fontSize: 14, - color: "#888", - marginBottom: 8, - display: "block", - }, - textarea: { - width: "100%", - padding: 12, - fontSize: 14, - border: "1px solid #333", - borderRadius: 8, - background: "#0a0a0a", - color: "#fff", - fontFamily: "monospace", - resize: "vertical" as const, - minHeight: 100, - }, - select: { - width: "100%", - padding: 12, - fontSize: 14, - border: "1px solid #333", - borderRadius: 8, - background: "#0a0a0a", - color: "#fff", - marginBottom: 16, - }, - button: { - width: "100%", - padding: "12px 24px", - fontSize: 16, - border: "none", - borderRadius: 8, - cursor: "pointer", - fontWeight: 600, - background: "#3b82f6", - color: "#fff", - }, - disabled: { - opacity: 0.5, - cursor: "not-allowed", - }, - output: { - padding: 16, - background: "#0a0a0a", - borderRadius: 8, - fontFamily: "monospace", - fontSize: 14, - wordBreak: "break-all" as const, - minHeight: 100, - }, - step: { - padding: 12, - background: "#1a1a1a", - borderRadius: 8, - marginBottom: 8, - fontSize: 14, - }, - stepLabel: { - color: "#3b82f6", - fontWeight: 600, - marginBottom: 4, - }, - stepContent: { - color: "#888", - fontFamily: "monospace", - fontSize: 12, - }, - error: { - color: "#ef4444", - }, - success: { - color: "#22c55e", - }, -} as const; - -type Operation = "echo" | "reverse" | "uppercase" | "hash"; - -type Step = { - label: string; - content: string; - status: "pending" | "success" | "error"; -}; - -export function Playground() { - const { session } = usePasskey(); - const [input, setInput] = useState("Hello, X402!"); - const [operation, setOperation] = useState("reverse"); - const [output, setOutput] = useState(null); - const [steps, setSteps] = useState([]); - const [loading, setLoading] = useState(false); - - const { data: pricing } = useQuery({ - queryKey: ["pricing"], - queryFn: getPricing, - }); - - const addStep = (label: string, content: string, status: Step["status"]) => { - setSteps((prev) => [...prev, { label, content, status }]); - }; - - const handleSubmit = async () => { - if (!session) return; - - setLoading(true); - setOutput(null); - setSteps([]); - - try { - // Step 1: Try the request (expect 402) - addStep("Request", `POST /api/transform/${operation}`, "pending"); - - try { - const result = await transform(operation, input); - // If we get here without paying, something's wrong (or route is free) - setOutput(result.output); - setSteps((prev) => - prev.map((s, i) => - i === 0 ? { ...s, status: "success", content: "No payment required" } : s - ) - ); - return; - } catch (err) { - if (!(err instanceof ApiError) || !err.isPaymentRequired) { - throw err; - } - - const requirements = err.paymentRequirements; - if (!requirements) throw new Error("No payment requirements"); - - setSteps((prev) => - prev.map((s, i) => - i === 0 - ? { - ...s, - status: "success", - content: `402 Payment Required: ${requirements.accepts[0].amount} tokens`, - } - : s - ) - ); - - // Step 2: Pay - addStep("Payment", `Transferring ${requirements.accepts[0].amount} to treasury...`, "pending"); - - const paymentResult = await transfer( - session.token, - requirements.accepts[0].payTo, - requirements.accepts[0].amount - ); - - setSteps((prev) => - prev.map((s, i) => - i === 1 - ? { ...s, status: "success", content: `TX: ${paymentResult.txHash.slice(0, 22)}...` } - : s - ) - ); - - // Step 3: Retry with payment proof - addStep("Retry", "Retrying with payment signature...", "pending"); - - const signature = createPaymentSignature(paymentResult.txHash, requirements); - const result = await transform(operation, input, signature); - - setSteps((prev) => - prev.map((s, i) => - i === 2 ? { ...s, status: "success", content: "200 OK" } : s - ) - ); - - setOutput(result.output); - } - } catch (err) { - const message = err instanceof Error ? err.message : "Request failed"; - addStep("Error", message, "error"); - } finally { - setLoading(false); - } - }; - - const getPrice = (op: Operation) => { - const route = pricing?.endpoints.find((e) => e.route.includes(op)); - return route?.price ?? "?"; - }; - - return ( -
-

API Playground

- -
-
- -