From 9d18020b25950bb1705eb352fadf4af5514040b9 Mon Sep 17 00:00:00 2001 From: sarthib7 Date: Wed, 26 Aug 2026 18:10:38 +0200 Subject: [PATCH 1/7] docs: correct 46 claims against code, add status and checked to every page Truth fixes on all 22 hand-written pages, checked against the product code and live endpoints on 2026-08-26. Highlights: - Chat app: encryption is a toggle (Maximum Privacy Mode), off by default. Attachments and knowledge-base documents are not encrypted at rest. Persistent history is encrypted with a Solrouter-held key. Image and video generation is disabled (Archived). - Models: gpt-oss:20b and qwen3.8:27b (Live), gemma4:31b (Soon), qwen3:8b retired. One id format. - Nosana GPU node sees the prompt during inference; stated on every page that described the request path. - Agent: useTools runs a tool loop (up to 8 model calls); the guided path is reasoning: 'braid'. Benchmark numbers without a source removed. Skill graph has 44 nodes; the response carries no skillGraph. - @solrouter/agent-tools is not on npm: install steps removed, Soon. - Attestation: both report_data formulas; receipts are Light Protocol compressed accounts, not PDAs; umbra_attestation is a swap artifact. - Managed wallets: KEK lives in the backend process, not the enclave. - MCP: 22 tools; four use the encrypted path for the model step; search and market lookups leave the machine in plaintext. - x402: the manifest is on api.solrouter.com and advertises Coinbase. - Removed both duplicated ASCII request-flow blocks and all em dashes. Every page now declares status (live | soon | archived | mixed) and checked: 2026-08-26 in frontmatter. The status sentence renders under the title and is prepended in llms-full.txt and the .md routes. EncryptionFlow gains the Nosana stage, a return arrow, role=img, and an encrypted={false} variant. --- content/docs/api-reference/agent.mdx | 112 +++++++++--- content/docs/api-reference/authentication.mdx | 22 ++- content/docs/api-reference/overview.mdx | 41 +++-- content/docs/api-reference/tee/public-key.mdx | 50 +++-- content/docs/chat-app.mdx | 42 +++-- content/docs/concepts/agent-framework.mdx | 95 ++++++---- content/docs/concepts/attestation.mdx | 92 +++++----- content/docs/concepts/encryption-proof.mdx | 49 +++-- content/docs/concepts/encryption.mdx | 97 ++++------ content/docs/concepts/how-it-works.mdx | 81 ++++----- content/docs/concepts/serv-reasoning.mdx | 69 ++++--- content/docs/concepts/skill-graphs.mdx | 61 ++----- content/docs/concepts/supported-models.mdx | 67 ++++--- content/docs/develop/agent-tools-sdk.mdx | 85 ++++----- content/docs/develop/authentication.mdx | 51 +++--- content/docs/develop/mcp-server.mdx | 81 +++++---- content/docs/develop/privacy-sdk.mdx | 89 ++++++--- content/docs/develop/private-swaps.mdx | 172 +++++++++++------- content/docs/index.mdx | 135 ++++++++------ content/docs/payments/overview.mdx | 72 ++++++-- content/docs/payments/tokenomics.mdx | 77 ++++---- content/docs/quickstart.mdx | 43 +++-- source.config.ts | 11 +- src/app/docs/[[...slug]]/page.tsx | 7 + src/components/diagrams/encryption-flow.tsx | 69 +++++-- .../diagrams/typical-vs-solrouter.tsx | 88 +++++++++ src/lib/source.ts | 5 +- src/lib/status.ts | 32 ++++ 28 files changed, 1163 insertions(+), 732 deletions(-) create mode 100644 src/components/diagrams/typical-vs-solrouter.tsx create mode 100644 src/lib/status.ts diff --git a/content/docs/api-reference/agent.mdx b/content/docs/api-reference/agent.mdx index 99c2069..bbc842d 100644 --- a/content/docs/api-reference/agent.mdx +++ b/content/docs/api-reference/agent.mdx @@ -1,12 +1,20 @@ --- title: "POST /agent" icon: Webhook -description: "The /agent endpoint runs your prompt through SERV guided reasoning with access to web search, on-chain data, DEX quotes, and Solana tools." +description: "The /agent endpoint runs your prompt through a tool loop with web search, on-chain data, DEX quotes, and Solana tools. Optional guided reasoning (BRAID) and encrypted mode." +status: live +checked: "2026-08-26" --- import { Callout } from 'fumadocs-ui/components/callout'; -The `/agent` endpoint runs your prompt through Solrouter's full SERV-guided agent pipeline. Unlike a direct chat completion, the agent has access to a suite of built-in tools — web search, on-chain data, DEX quotes, token prices, and more — and uses SERV (Structured Execution via Reasoning Virtualization) to walk a deterministic reasoning graph rather than letting the model make freeform structural decisions. The result is faster, cheaper, and more reliable than a standard agent loop, while still producing synthesis-quality answers for complex multi-step queries. +The `/agent` endpoint runs your prompt through Solrouter's agent pipeline. Unlike a direct chat completion, the agent can call built-in tools: web search, on-chain data, DEX quotes, token prices, and more. + +The request body selects one of three paths. + +- Default: a standard tool loop with up to 8 model calls (`MAX_ITERATIONS = 8`). +- `reasoning: 'braid'`: guided reasoning (BRAID). A fixed Guided Reasoning Diagram (GRD) collects data, then one synthesis call writes the reply. Older material calls this SERV. +- `encryptedPrompt`: encrypted mode. The tool loop runs inside the CVM (a confidential virtual machine, which is a TEE, trusted execution environment) with a 5-tool allowlist. ## Endpoint @@ -18,11 +26,15 @@ POST https://api.solrouter.com/agent | Field | Type | Description | | --- | --- | --- | -| `prompt` | string (required) | The question or task you want the agent to work on. You can ask for research, comparisons, on-chain analysis, swap quotes, or any other task covered by the built-in tools. | -| `model` | string | The model used for synthesis at the end of the reasoning pipeline. All models run on Solrouter's self-hosted Nosana GPU network — no third-party APIs. `gpt-oss:20b` (default) — Apache-2.0 open weights, 20B parameters. `qwen3:8b` — open weights, 8B parameters. | -| `useTools` | boolean | Enable or disable tool calls during agent execution. When `true`, the agent can call any of its built-in tools to gather data before synthesising a final answer. Set to `false` to run the prompt through guided reasoning without external data retrieval. Defaults to `true`. | +| `prompt` | string (required) | The question or task. Research, comparisons, on-chain analysis, swap quotes, or any task the built-in tools cover. | +| `model` | string | The model that runs the loop and writes the reply. Models run on Nosana GPU nodes. `gpt-oss:20b` (default) is Live. `qwen3.8:27b` is Live. `gemma4:31b` is Soon. `qwen3:8b` is retired (Archived). A model with no configured Nosana endpoint returns 501 `nosana_not_configured`. | +| `useTools` | boolean | Defaults to `true`. When `true`, the model can call any built-in tool before it writes the reply. Set `false` to run one plain completion with no tools. This field does not select guided reasoning. | +| `chatId` | string | Optional conversation id. When present, the backend stores the turn in that chat's history and sends prior turns as context. When absent, the call is stateless. | +| `reasoning` | string | Set `'braid'` to run guided reasoning instead of the tool loop. | +| `braidOptions` | object | BRAID only. `includeTrace: true` adds `braidTrace` to the response. `forceGrdId` picks a GRD by id instead of intent detection. | +| `encryptedPrompt` | string (JSON) | Encrypted mode. The prompt is encrypted client-side to the enclave public key and packaged as a JSON string with `ciphertext`, `nonce`, `publicKey`, `algorithm`, and `version`. The backend forwards it to the CVM without reading it. Status: Live for REST callers who send `encryptedPrompt`. Soon for the SDK. Not used by the chat app, whose agent mode runs the plaintext tool loop. | -## Example Request +## Example request ```bash curl -X POST "https://api.solrouter.com/agent" \ @@ -35,39 +47,93 @@ curl -X POST "https://api.solrouter.com/agent" \ }' ``` -## Example Response +## Example response (default path) ```json { "success": true, "reply": "## Marginfi vs Kamino Lending Comparison\n\n...", "toolCalls": [ - { "tool": "web_search", "args": { "query": "Marginfi vs Kamino lending Solana" } }, - { "tool": "token_price", "args": { "token": "MNDE" } } + { "tool": "web_search", "args": { "query": "Marginfi vs Kamino lending Solana" }, "result": { "...": "..." } }, + { "tool": "token_price", "args": { "token": "MNDE" }, "result": { "...": "..." } } ], + "usage": { "promptTokens": 0, "completionTokens": 0, "totalTokens": 0 }, "iterations": 4, - "skillGraph": { - "nodesTraversed": ["defi-analysis", "liquidity-risk", "comparative-analysis"], - "relevanceScore": 0.72 - } + "model": "gpt-oss:20b", + "provider": "nosana", + "billing": null, + "freeMessagesRemaining": 0 } ``` -## Response Fields +## Response fields (default path) | Field | Type | Description | | --- | --- | --- | -| `success` | boolean | Whether the request completed successfully. `true` on success; `false` if an error occurred. | -| `reply` | string | The agent's final synthesised answer in Markdown. This is produced by the LLM synthesis step after SERV has collected all relevant data through tool calls. | -| `toolCalls` | array | The list of tools the agent called during execution, in order. Each entry contains a `tool` name and an `args` object with the parameters that were passed to that tool. | -| `iterations` | number | The number of reasoning iterations the SERV pipeline executed before producing the final answer. | -| `skillGraph.nodesTraversed` | array | The skill graph nodes that were activated for this query. Nodes represent structured domain knowledge (e.g. `defi-analysis`, `liquidity-risk`, `comparative-analysis`) that was injected into the synthesis context. Simple queries may return an empty array if skill-graph traversal was skipped. | -| `skillGraph.relevanceScore` | number | A score between 0 and 1 indicating how relevant the activated domain knowledge was to the query. Higher scores mean the skill graph contributed meaningfully to the final answer. | +| `success` | boolean | `true` when the request completed. A caller with no free messages and no USDC balance gets HTTP 200 with `success: false`, `requiresDeposit: true`, and `reason: "free_trial_exhausted"`. | +| `reply` | string | The final reply in Markdown. | +| `toolCalls` | array | Every tool the agent called, in order. Each entry has `tool`, `args`, and `result`. | +| `usage` | object | `promptTokens`, `completionTokens`, and `totalTokens`, summed over every model call in the loop. | +| `iterations` | number | The number of model calls the loop made. At most 8. | +| `model` | string | The model id that ran. | +| `provider` | string | Always `"nosana"`. | +| `billing` | object or null | The result of the billing step. `null` when billing failed or did not run. | +| `freeMessagesRemaining` | number | Free messages left on the account after this call. | + +The response has no `skillGraph` field. The skill graph shapes the system prompt only. + +## Example response (BRAID path) + +```json +{ + "success": true, + "reply": "## Marginfi vs Kamino Lending Comparison\n\n...", + "reasoning": "braid", + "braidTrace": { + "grdId": "comparison", + "intent": "comparison", + "nodes": [ + { "nodeId": "...", "label": "...", "type": "action", "status": "completed" } + ], + "totalDurationMs": 0, + "totalTokens": 0 + }, + "usage": { "promptTokens": 0, "completionTokens": 0, "totalTokens": 0 }, + "iterations": 1, + "model": "gpt-oss:20b", + "provider": "nosana", + "cost": "FREE", + "freeMessagesRemaining": 0 +} +``` + +`braidTrace` is present only when `braidOptions.includeTrace` is `true`. On this path `iterations` counts GRD nodes, not model calls. If BRAID fails, the route falls back to the default tool loop. + +## Example response (encrypted mode) + +```json +{ + "success": true, + "encrypted": true, + "encryptedResponse": { "...": "..." }, + "toolCallsCount": 2, + "attestation": { "...": "..." }, + "privacyGuarantee": { + "backendSawPlaintext": false, + "toolsExecutedInTEE": true + }, + "freeMessagesRemaining": 0 +} +``` + +The reply is encrypted to the `publicKey` inside `encryptedPrompt`. Any enclave error returns HTTP 500. The route does not fall back to a plaintext path. + +## Available tools -## Available Tools +The default path registers 18 tools. They are listed on the [Agent Framework](/docs/concepts/agent-framework) page: `web_search`, `scrape_url`, `crawl_url`, `solana_balance`, `token_price`, `swap_quote`, `trending_tokens`, `deepwiki`, `colosseum_search`, `colosseum_archives`, `paysh_search_apis`, `paysh_call_api`, `github_list_repos`, `github_issues`, `github_read_file`, `notion_search`, `notion_get_page`, and `notion_query_database`. The model picks the tools at each step of the loop. -The agent has access to all built-in tools listed on the [Agents Overview](/docs/concepts/agent-framework) page, including `web_search`, `scrape_url`, `crawl_url`, `solana_balance`, `token_price`, `swap_quote`, `trending_tokens`, and `deepwiki`. Tool selection is handled automatically by the SERV reasoning pipeline — you don't need to specify which tools to use. +Encrypted mode allows 5 tools inside the CVM: `web_search` (SearXNG inside the CVM), `token_price`, `trending_tokens`, `swap_quote`, and `solana_balance`. - If you are using the `@solrouter/sdk`, you can reach the same SERV-guided agent pipeline by passing `reasoning: 'braid'` to `client.chat()`. This routes your request through `/agent` automatically and returns the same structured response, with the added benefit of client-side encryption if `encrypted: true` is set. + With `@solrouter/sdk` 1.1.0, `client.chat(prompt, { reasoning: 'braid' })` sends the request to `/agent`. The SDK sends the prompt in plaintext on this path and returns `encrypted: false`. The SDK does not send `encryptedPrompt` to `/agent`. Encrypted agent mode is Live over REST and Soon in the SDK. diff --git a/content/docs/api-reference/authentication.mdx b/content/docs/api-reference/authentication.mdx index 140f383..7fd76ab 100644 --- a/content/docs/api-reference/authentication.mdx +++ b/content/docs/api-reference/authentication.mdx @@ -1,12 +1,14 @@ --- title: "Authentication" icon: KeyRound -description: "Pass your Solrouter API key as a Bearer token in the Authorization header. Generate keys at solrouter.com/sdk — no email or credit card required." +description: "Pass your Solrouter API key as a Bearer token in the Authorization header. Generate keys at solrouter.com/sdk. No email or credit card required." +status: live +checked: "2026-08-26" --- import { Callout } from 'fumadocs-ui/components/callout'; -Solrouter authenticates REST API requests using Bearer tokens. Every request you make must include your API key in the `Authorization` header. Keys are tied to a prepaid balance denominated in USDC or `$ROUTER`, and usage is metered per call — there are no subscriptions or seat fees. +Solrouter authenticates REST API requests using Bearer tokens. Every request you make must include your API key in the `Authorization` header. Keys are tied to a prepaid balance denominated in USDC or `$ROUTER`, and usage is metered per call. There are no subscriptions or seat fees. ## Request Format @@ -19,7 +21,7 @@ Authorization: Bearer sk_solrouter_... ## Getting an API Key 1. Go to [solrouter.com/sdk](https://solrouter.com/sdk). -2. Connect your Solana wallet — no email or credit card is required. +2. Connect your Solana wallet. No email or credit card is required. 3. Copy the generated API key. All keys follow the format `sk_solrouter_...`. 4. Top up your balance in **USDC** or **`$ROUTER`** to start making calls. @@ -36,20 +38,22 @@ curl -X POST "https://api.solrouter.com/agent" \ ## x402 Keyless Authentication -If you are building an agent that does not hold an API key, Solrouter supports **x402 per-call USDC payment**. Instead of a long-lived API key, each request is settled individually on Solana mainnet via a Coinbase facilitator — no account or prepaid balance required. +If you are building an agent that does not hold an API key, Solrouter supports **x402 per-call USDC payment**. Instead of a long-lived API key, each request is settled individually on Solana mainnet. No account or prepaid balance is required. -To discover the x402 payment manifest and per-call pricing, send a request to: +x402 is the standard. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. It does not show which facilitator settles payments. + +To discover the x402 payment manifest and per-call pricing, send a request to the API host: ``` -GET /.well-known/x402 +GET https://api.solrouter.com/.well-known/x402 ``` -Any x402-aware agent can use this manifest to self-fund calls autonomously. The x402 inference endpoint (`POST /api/v1/x402/chat/completions`) is Arcium-encrypted end-to-end and priced at \$0.005 per call. +Any x402-aware agent can use this manifest to self-fund calls autonomously. The x402 inference endpoint (`POST /api/v1/x402/chat/completions`) takes an Arcium-encrypted prompt and is priced at \$0.005 per call. For agent-to-agent interoperability, Solrouter also publishes an A2A protocol v1.0 discovery card at: ``` -GET /.well-known/agent-card.json +GET https://api.solrouter.com/.well-known/agent-card.json ``` This card describes the full skill list available to agents integrating with the Solrouter Agent Privacy API. @@ -60,7 +64,7 @@ This card describes the full skill list available to agents integrating with the | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 401 Unauthorized | Missing or invalid API key. Check that your `Authorization` header is present and that the key begins with `sk_solrouter_`. | | 402 Payment Required | x402 payment is required, or your prepaid balance is insufficient. Top up at [solrouter.com/sdk](https://solrouter.com/sdk) or use x402 per-call settlement. | -| 403 Forbidden | Your API key does not have permission to access this endpoint. | +| 403 Forbidden | Which endpoints return 403, and when, is not determined. The API key check itself returns 401, not 403. | Never expose your API key in client-side code, public repositories, or browser bundles. If your key is compromised, anyone can drain your prepaid balance. Rotate it immediately at [solrouter.com/sdk](https://solrouter.com/sdk) and treat the new key as a secret environment variable on your server or in a secrets manager. diff --git a/content/docs/api-reference/overview.mdx b/content/docs/api-reference/overview.mdx index 731c6ca..f7455f1 100644 --- a/content/docs/api-reference/overview.mdx +++ b/content/docs/api-reference/overview.mdx @@ -1,14 +1,21 @@ --- title: "Overview" icon: List -description: "The Solrouter REST API gives you encrypted AI chat, agent reasoning, TEE attestation, and private on-chain swaps. Base URL: https://api.solrouter.com" +description: "The Solrouter REST API gives you encrypted chat completions, agent reasoning, TEE key and attestation reads, and private on-chain swaps. Base URL: https://api.solrouter.com" +status: mixed +checked: "2026-08-26" +statusNote: "Chat, agent, and TEE endpoints are Live. Agent Privacy API swap execution is Soon." --- import { Cards, Card } from 'fumadocs-ui/components/card'; import { Callout } from 'fumadocs-ui/components/callout'; import { MessageSquare, Bot, ShieldCheck, Lock } from 'lucide-react'; -The Solrouter REST API is the direct HTTP interface to Solrouter's cryptographically private AI infrastructure. You can use it to send encrypted chat completions, run SERV-guided agent reasoning with tool calls, verify TEE attestation, and orchestrate privacy-preserving on-chain swaps — all over a single authenticated surface without email, credit card, or KYC. +The Solrouter REST API is the direct HTTP interface to Solrouter's private AI infrastructure. With it you can send encrypted chat completions and run agent reasoning with tool calls. You can also read the TEE (trusted execution environment) key and attestation quote. Private on-chain swaps use the same base URL. All of it sits on one authenticated surface with no email, credit card, or KYC. + + + Raw REST is plaintext unless you send `encryptedPrompt`. When you use `@solrouter/sdk`, chat requests are encrypted before they leave your machine, and the Solrouter backend relays the ciphertext to the TEE without reading it. If you call `POST /agent` with `curl` and a plain `prompt`, you are sending plaintext to the backend. `POST /tee/process` and `POST /api/v1/chat/completions` reject requests without `encryptedPrompt`. + ## Base URL @@ -20,31 +27,31 @@ https://api.solrouter.com ## Authentication -All requests require a Bearer token in the `Authorization` header. You generate your key at [solrouter.com/sdk](https://solrouter.com/sdk) by connecting a Solana wallet — no email or credit card required. Balance is prepaid in USDC or `$ROUTER`. +All requests require a Bearer token in the `Authorization` header. You generate your key at [solrouter.com/sdk](https://solrouter.com/sdk) by connecting a Solana wallet. No email or credit card is required. Balance is prepaid in USDC or `$ROUTER`. ``` Authorization: Bearer sk_solrouter_... ``` -For agents that don't hold an API key, Solrouter also supports **x402 per-call USDC payment** facilitated by Coinbase on Solana mainnet. See the [Authentication](/docs/api-reference/authentication) page for full details on both methods. +For agents that do not hold an API key, Solrouter also supports **x402 per-call USDC payment** on Solana mainnet. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest does not show which facilitator settles payments. See the [Authentication](/docs/api-reference/authentication) page for full details on both methods. ## Endpoints } href="/docs/develop/privacy-sdk"> - Send end-to-end encrypted chat completions with **`@solrouter/sdk`** (encrypted client-side by default), or pay-per-call via the x402 `POST /api/v1/x402/chat/completions` endpoint in the Agent Privacy API below. + Status: Live. **POST /api/v1/chat/completions** takes an API key and requires `encryptedPrompt` and `model`; it returns 400 `bad_request` without them. `GET /api/v1/models`, `/api/v1/balance`, and `/api/v1/usage` sit beside it. `@solrouter/sdk` calls **POST /tee/process** and encrypts client-side. Keyless agents pay per call on `POST /api/v1/x402/chat/completions`. } href="/docs/api-reference/agent"> - **POST /agent** — Run your prompt through the full SERV-guided agent pipeline with built-in tools including web search, on-chain data, DEX quotes, and Solana wallet inspection. + Status: Live. **POST /agent** runs your prompt through the tool-calling agent, with built-in tools for web search, on-chain data, DEX quotes, and Solana wallet inspection. Send `reasoning: 'braid'` for guided reasoning. } href="/docs/api-reference/tee/public-key"> - **GET /tee/public-key** — Fetch the enclave's live X25519 public key, then verify it against the Intel-signed TDX attestation quote. See the [attestation guide](/docs/concepts/attestation) for the full verification flow. + Status: Live. **GET /tee/public-key** returns the enclave's live X25519 public key. **GET /tee/attestation** returns the TDX quote when the CVM can reach the dStack agent; otherwise `tdxQuote` is null and `tdxQuoteError` says why. See the [attestation guide](/docs/concepts/attestation) for the verification flow. } href="/docs/develop/private-swaps"> - **/agents/v1/**\* — Agent-first surface for private on-chain swaps, managed encrypted-balance wallets, and x402-paywalled encrypted inference. Full OpenAPI spec at `/agents/v1/openapi.json`. + **/agents/v1/**\* is the agent-first surface for private on-chain swaps, managed wallets, and x402-paywalled encrypted inference. Discovery, quote, and anonymity-set reads are Live. Swap execution is Soon. Full OpenAPI spec at `/agents/v1/openapi.json`. @@ -58,22 +65,20 @@ GET /agents/v1/openapi.json You can import this spec directly into tools like Postman, Insomnia, or any OpenAPI-compatible client to explore all `/agents/v1/*` endpoints with type-safe request and response schemas. -Two well-known discovery documents are also published for agent and payment interoperability: +Two well-known discovery documents are also published for agent and payment interoperability. Both are served by the API host, `https://api.solrouter.com`. ``` -GET /.well-known/agent-card.json — A2A protocol v1.0 card with the full skill list -GET /.well-known/x402 — x402 paywall manifest with per-call USDC pricing +GET /.well-known/agent-card.json # A2A protocol v1.0 card with the full skill list +GET /.well-known/x402 # x402 paywall manifest with per-call USDC pricing ``` ## Response Format -All endpoints return JSON. Every response includes a top-level `success` field that indicates whether the request completed without error: +All endpoints return JSON. A top-level `success` field exists on `POST /agent` and `POST /tee/process` only: -* `success: true` — the request succeeded; additional fields carry the result data. -* `success: false` — the request failed; an `error` field describes what went wrong. +* `success: true`: the request succeeded, and the other fields carry the result. +* `success: false`: the request failed, and an `error` field names the failure. -For a complete list of error codes and how to handle them, see the [Authentication](/docs/api-reference/authentication) page, which covers `401`, `402`, and `403` responses in detail. +The TEE key and attestation reads, x402 inference, the discovery documents, and `/agents/v1` responses have no `success` field. They return the result object directly, or an `error` field with a non-2xx status. - - When you use the `@solrouter/sdk`, all chat requests are encrypted by default before leaving your machine. The Solrouter backend never sees plaintext — it routes the encrypted blob blindly to the Intel TDX enclave. If you call the REST API directly with `curl` or another HTTP client, you are sending plaintext unless you implement client-side encryption yourself or pass `"encrypted": false` intentionally. - +For error codes on authentication, see the [Authentication](/docs/api-reference/authentication) page, which covers `401`, `402`, and `403` responses. diff --git a/content/docs/api-reference/tee/public-key.mdx b/content/docs/api-reference/tee/public-key.mdx index a5acce3..9345d96 100644 --- a/content/docs/api-reference/tee/public-key.mdx +++ b/content/docs/api-reference/tee/public-key.mdx @@ -1,12 +1,14 @@ --- title: "GET /tee/public-key" icon: KeyRound -description: "Fetch the TDX enclave's X25519 public key for client-side encryption. The SDK fetches this automatically; required only for custom client implementations." +description: "Fetch the enclave's X25519 public key for client-side encryption. The SDK fetches it once per process and caches it. Call it yourself only in a custom client." +status: live +checked: "2026-08-26" --- import { Callout } from 'fumadocs-ui/components/callout'; -This endpoint returns the X25519 public key that is currently active inside the Solrouter Intel TDX enclave. Before sending a prompt or payload, your client uses this key to encrypt the data with Arcium's RescueCipher so that only the enclave can decrypt it. The Solrouter backend receives an opaque ciphertext blob and routes it to the enclave without being able to read its contents. +This endpoint returns the X25519 public key that is active inside the Solrouter Intel TDX enclave (a TEE, a trusted execution environment: a hardware-isolated virtual machine). Before you send a prompt, your client encrypts it to this key with Arcium's RescueCipher. Only the enclave can decrypt it. The Solrouter backend receives an opaque ciphertext blob and relays it to the enclave. It cannot read the contents. ## Endpoint @@ -14,14 +16,18 @@ This endpoint returns the X25519 public key that is currently active inside the GET https://api.solrouter.com/tee/public-key ``` -No authentication is required for this endpoint. +No authentication is required for this endpoint. The backend proxies the call to the enclave and returns the enclave's body unchanged. ## Response | Field | Type | Description | | --- | --- | --- | -| publicKey | string | The enclave's current X25519 public key, encoded in base64. Use this as the recipient key when performing client-side encryption. | -| algorithm | string | The key exchange algorithm. Always `X25519`. | +| publicKey | string | The enclave's current X25519 public key, base64 encoded. Use it as the recipient key for client-side encryption. | +| publicKeySha256 | string | Hex sha256 of the raw 32-byte public key. `GET /tee/attestation` pins this same digest in the quote's `report_data`. | +| algorithm | string | Always `x25519` (lower case). | +| teeType | string | Always `INTEL-TDX-PHALA`. | + +This response has no top-level `success` field. ## Example @@ -29,25 +35,41 @@ No authentication is required for this endpoint. curl "https://api.solrouter.com/tee/public-key" ``` -Example response: +Example response (shape checked against the live endpoint on 2026-08-26; values shortened): ```json { - "publicKey": "abc123...base64encodedkey...==", - "algorithm": "X25519" + "publicKey": "base64...=", + "publicKeySha256": "hex...", + "algorithm": "x25519", + "teeType": "INTEL-TDX-PHALA" +} +``` + +## Errors + +When the backend cannot reach the enclave, it answers with status 502 and this body: + +```json +{ + "error": "tee_unreachable", + "message": "...", + "teeEndpoint": "..." } ``` ## When to use this -In most cases, you do not need to call this endpoint directly: +In most cases you do not need to call this endpoint yourself. + +* **`@solrouter/sdk`**: the SDK fetches the key once per process and caches it. You never handle the key yourself. +* **`@solrouter/agent-tools`** (Soon): the package is not on npm yet. +* **Custom client**: if you write your own encryption layer (for example in a language with no Solrouter SDK), fetch this endpoint first. Then use `publicKey` as the X25519 recipient key in your RescueCipher key-exchange flow. -* **Using the `@solrouter/sdk`** — the SDK fetches the TEE public key automatically before every encrypted request. You never handle the key yourself. -* **Using the `@solrouter/agent-tools` SDK** — same behavior; key fetch and encryption are handled transparently. -* **Building a custom client** — if you are implementing your own encryption layer (for example, in a language without an official Solrouter SDK), fetch this endpoint first, then use the returned key as the X25519 recipient public key in your RescueCipher / ECDH key-exchange flow. +## Key lifetime and caching -The SDK always fetches a fresh key before each session rather than caching a previously retrieved key. You should follow the same practice in custom clients to ensure you are always encrypting to the currently active enclave key. +The enclave generates a new X25519 keypair on every CVM boot. The SDK fetches the key once per process and keeps it until you call `clearSession()`. A long-lived process can hold a stale key after the enclave restarts. When a request fails with `tee_unreachable`, or a reply fails to decrypt, call `clearSession()` and retry. The next request fetches the current key. Custom clients should do the same: cache the key, and fetch it again after a failure. - The X25519 keypair is generated inside the Confidential VM at boot time. The private key never leaves the enclave — not to the Solrouter backend, not to any host process, and not to Solrouter employees. You can verify this claim independently by checking the TDX attestation quote, which binds the public key to the exact code measurement running inside the enclave — see the [attestation verification guide](/docs/concepts/attestation). + The X25519 keypair is generated inside the Confidential VM at boot. The private key never leaves the enclave: not to the Solrouter backend, not to any host process, and not to Solrouter staff. You can check this claim yourself. `GET /tee/attestation` returns a TDX quote whose `report_data` equals `sha256(publicKey)`. See the [attestation guide](/docs/concepts/attestation). diff --git a/content/docs/chat-app.mdx b/content/docs/chat-app.mdx index cc70b80..e236167 100644 --- a/content/docs/chat-app.mdx +++ b/content/docs/chat-app.mdx @@ -1,43 +1,45 @@ --- title: "Chat App" icon: MessageSquare -description: "Solrouter Chat at solrouter.com/chat gives you encrypted AI chat with file attachments, image generation, and a RAG knowledge base — no email required." +description: "Solrouter Chat at solrouter.com/chat is a wallet-based AI chat with an encryption toggle, file attachments, and a RAG knowledge base. No email required." +status: live +checked: "2026-08-26" --- import { Cards, Card } from 'fumadocs-ui/components/card'; import { Callout } from 'fumadocs-ui/components/callout'; import { Step, Steps } from 'fumadocs-ui/components/steps'; -import { Lock, Paperclip, Image as ImageIcon, Database } from 'lucide-react'; +import { Lock, Paperclip, Database } from 'lucide-react'; -Most AI chat tools want your email, store your conversations in plaintext, and lock you into a single model. Solrouter Chat at [solrouter.com/chat](https://solrouter.com/chat) takes the opposite stance: every conversation is end-to-end encrypted by default, and you never create an account. +Most AI chat tools want your email, store your conversations in plaintext, and lock you into a single model. Solrouter Chat at [solrouter.com/chat](https://solrouter.com/chat) works differently. You never create an account. You can turn on Maximum Privacy Mode to encrypt each prompt before it leaves your browser. -You connect a Solana wallet, top up a prepaid balance, and switch freely between models. Along the way you get file attachments, image and video generation, and a RAG knowledge base — all private, all in one interface. +You connect a Solana wallet, top up a prepaid balance, and pick one of two open-weight models. You also get file attachments and a RAG knowledge base. Encryption is a toggle. It is off by default. ## Features -Here is what you get out of the box, and why each one matters for keeping your data yours. +Here is what you get, and what each feature does with your data. - }> - Your conversations stay private even from us. Each message is encrypted end-to-end and stored only in encrypted form, so neither Solrouter nor anyone else can read your history. - + }> + **Persistent Privacy Mode** (default): your prompt goes to the Solrouter backend in plaintext, then to a Nosana GPU node. Chat history is stored encrypted at rest with AES-256-GCM. The backend holds the key, so this protects against a database dump, not against Solrouter. - }> - Send documents along with your question. The file contents are encrypted together with your prompt before they ever leave your browser. + **Maximum Privacy Mode** (toggle on): your prompt is encrypted in your browser. It is decrypted only inside the TEE (Trusted Execution Environment: hardware that isolates code and data from the machine's operator). Nothing is stored. History is lost on refresh. - }> - Create visuals next to your text responses in the same window — no separate tool, no extra account. + }> + Send images and documents with your question. Attachments are not encrypted in your browser. Images become data URLs. Documents upload to Cloudflare R2 through a presigned URL, and the backend parses them. Maximum Privacy Mode encrypts the text prompt only. }> - RAG (Retrieval-Augmented Generation — answering from your own documents instead of only the model's training data) lets you upload files and query them in plain language. Your documents stay encrypted the whole time. + RAG (Retrieval-Augmented Generation: answering from your own documents instead of only the model's training data) lets you upload files. You then query them in plain language. The backend chunks and embeds your documents in plaintext. Embeddings are computed on Solrouter's own servers, so no third-party embedding API sees them. Stored chunks are not encrypted. +Image and video generation: Archived. It is disabled in the chat app. + ## Getting started -Four steps take you from a blank browser tab to your first private message. +Four steps take you from a blank browser tab to your first message. @@ -49,7 +51,7 @@ Four steps take you from a blank browser tab to your first private message. ### Connect your Solana wallet - Click **Connect Wallet** and approve the request in your wallet. Your wallet is your identity here — no email address or personal information is required. + Click **Connect Wallet** and approve the request in your wallet. Your wallet is your identity here. No email address or personal information is required. @@ -61,16 +63,18 @@ Four steps take you from a blank browser tab to your first private message. ### Select a model and start chatting - Pick a model from the selector and send your first message. Every request is encrypted by default — you do not have to turn anything on. + Pick a model from the selector and send your first message. To encrypt the prompt in your browser, turn on **Maximum Privacy Mode** first. It is off by default, and history is not kept while it is on. + + If the GPU node was idle, the first reply can say "Nosana GPU node is warming up". Wait a moment and send the message again. ## Supported models -Solrouter routes to many models, and which ones support full privacy depends on where they run. +The chat model picker lists two self-hosted open-weight models on the Nosana GPU network: `gpt-oss:20b` (Default, Live) and `qwen3.8:27b` (Uncensored, Live). No proprietary model is reachable in the chat app. Maximum Privacy Mode works with both models. -For the full list of available models and their identifiers, see the [Supported Models](/docs/concepts/supported-models) page. Privacy mode is available for all self-hosted, open-weight models running on Solrouter's infrastructure. +For model ids and their status, see the [Supported Models](/docs/concepts/supported-models) page. - All chat history is encrypted — Solrouter cannot read your conversations. Your prompts and responses only exist in plaintext on your device and briefly inside the Intel TDX enclave during inference. + In Maximum Privacy Mode, Solrouter's backend cannot read your prompt. Plaintext exists on your device, inside the Intel TDX enclave, and on the Nosana GPU node that runs the model during inference. Solrouter does not control that node's hardware. diff --git a/content/docs/concepts/agent-framework.mdx b/content/docs/concepts/agent-framework.mdx index 945a8d3..43c0fc8 100644 --- a/content/docs/concepts/agent-framework.mdx +++ b/content/docs/concepts/agent-framework.mdx @@ -1,53 +1,77 @@ --- title: "Agent Framework" icon: LayoutDashboard -description: "Solrouter's agent framework combines SERV guided reasoning, skill graphs, and built-in Solana tools for deterministic, cost-efficient AI agent execution." +description: "The /agent endpoint runs a tool loop by default, a guided reasoning path (BRAID) on request, and a skill graph that adds domain knowledge to the answer." +status: live +checked: "2026-08-26" --- import { Cards, Card } from 'fumadocs-ui/components/card'; import { Callout } from 'fumadocs-ui/components/callout'; -import { Workflow, Share2, Lock } from 'lucide-react'; +import { Workflow, Share2 } from 'lucide-react'; -A standard agent asks the LLM what to do at every step, which burns tokens, adds latency, and makes behavior hard to predict. Solrouter's agent framework removes that uncertainty: each request runs through SERV (Structured Execution via Reasoning Virtualization), which replaces freeform LLM decision-making with a deterministic execution graph. +The `/agent` endpoint has three paths. The request body selects the path. -A skill-graph layer rides alongside SERV, injecting structured domain knowledge — DeFi, on-chain data, market analysis, and more — straight into the synthesis context, but only when your query actually needs it. You get an agent that reasons more reliably, spends far fewer tokens, and responds faster than a typical agent loop. +The default path is a standard tool loop. The model picks a tool, the backend runs it, and the model reads the result. The loop runs up to 8 model calls (`MAX_ITERATIONS = 8`). The last call has no tools, so the model must write the answer. -## Core components +The guided reasoning path (BRAID) runs only when the request has `reasoning: 'braid'`. It walks a fixed Guided Reasoning Diagram (GRD) and calls the model once at the end to write the answer. + +The encrypted path runs when the request has `encryptedPrompt`. The tool loop then runs inside the CVM (a confidential virtual machine, which is a TEE, trusted execution environment) with a 5-tool allowlist. + +A skill graph adds domain knowledge (DeFi, on-chain data, market analysis, and more) to the system prompt on the plaintext paths. It fires only when the prompt matches the trigger words of a node. -The framework rests on three pieces. Start here to learn how each one works. +Older material calls the guided reasoning path SERV. The code, the SDK option, and the API value call it BRAID. + +## Core components - } href="/docs/concepts/serv-reasoning"> - Deterministic execution graphs that replace freeform LLM decisions, cutting token cost by 79.7% and latency by 35%. + } href="/docs/concepts/serv-reasoning"> + A fixed execution graph collects data, then one synthesis call writes the answer. Runs when the request has `reasoning: 'braid'`. Status: Live. } href="/docs/concepts/skill-graphs"> - Domain knowledge injection across 14 nodes — DeFi, on-chain signals, privacy tech, and more — activated only when needed. - - - } href="/docs/develop/private-swaps"> - Agent-first surface for private on-chain swaps and x402-paywalled encrypted inference on Solana. + Domain knowledge from 44 nodes, added to the system prompt only when the prompt matches a node. Status: Live. -## Built-in tools +## Three /agent paths -These are the tools every agent can reach without any setup. SERV picks which ones to call — and in what order — from the pre-defined execution graph for your query type, rather than asking the LLM at runtime. +| Path | Request field | Where it runs | Tools | Status | +| --- | --- | --- | --- | --- | +| Tool loop | `useTools: true` (default) | Backend | 18 built-in tools | Live | +| Guided reasoning (BRAID) | `reasoning: 'braid'` | Backend | Tools in the order the GRD sets | Live | +| Encrypted | `encryptedPrompt` | Inside the CVM | 5-tool allowlist | Live for REST callers who send `encryptedPrompt`. Soon for the SDK. Not used by the chat app, whose agent mode runs the plaintext tool loop. | -| Tool | Description | -| ----------------- | ------------------------------------------------------------------------------- | -| `web_search` | Search the web via Brave Search API for real-time information | -| `scrape_url` | Extract and clean content from any URL | -| `crawl_url` | Crawl entire websites via Cloudflare Browser Rendering (handles JS-heavy sites) | -| `solana_balance` | Check SOL and SPL token balances for any wallet | -| `token_price` | Real-time price, volume, liquidity, market cap via DexScreener + Jupiter | -| `swap_quote` | DEX swap quotes from Jupiter aggregator | -| `trending_tokens` | Trending / boosted tokens from DexScreener with price data | -| `deepwiki` | AI-powered GitHub repository research via DeepWiki | +## Built-in tools + +The plaintext `/agent` path registers 18 tools. The model picks which tools to call at each step of the loop. + +| Tool | Description | +| --- | --- | +| `web_search` | Search the web for current information. | +| `scrape_url` | Extract the main content from a URL. | +| `crawl_url` | Crawl a website and return its content as clean text. Uses Cloudflare Browser Rendering. | +| `solana_balance` | Get SOL and token balances for a Solana wallet address. | +| `token_price` | Get the current USD price, volume, liquidity, and market cap of a token. | +| `swap_quote` | Get a swap quote from the Jupiter DEX aggregator. | +| `trending_tokens` | List trending or boosted tokens from DexScreener with price data. | +| `deepwiki` | Ask questions about a GitHub repository through DeepWiki. | +| `colosseum_search` | Search Colosseum hackathon project submissions. | +| `colosseum_archives` | Search Colosseum's curated crypto archives. | +| `paysh_search_apis` | Find paid third-party APIs on pay.sh with their per-call USDC price. | +| `paysh_call_api` | Call a pay.sh endpoint. The call is paid from your USDC balance. | +| `github_list_repos` | List your GitHub repositories. Needs a connected GitHub account. | +| `github_issues` | List issues for a GitHub repository. | +| `github_read_file` | Read a file or list a directory in a GitHub repository. | +| `notion_search` | Search your connected Notion workspace. | +| `notion_get_page` | Read the text of a Notion page. | +| `notion_query_database` | List the rows of a Notion database. | + +The encrypted path runs a 5-tool allowlist inside the CVM: `web_search` (SearXNG inside the CVM), `token_price`, `trending_tokens`, `swap_quote`, and `solana_balance`. Every other tool fails closed in that mode. ## Quick example -Here's the smallest request that exercises the whole pipeline. Send a prompt to the agent endpoint with your API key, and set `useTools: true` to turn on SERV-guided tool execution. +This is the smallest request that runs the default tool loop. `useTools` defaults to `true`, so you can omit it. ```bash curl -X POST "https://api.solrouter.com/agent" \ @@ -60,24 +84,27 @@ curl -X POST "https://api.solrouter.com/agent" \ }' ``` -The response gives you the synthesized reply plus a record of how the agent got there: every tool call it made, how many reasoning iterations it ran, and a skill-graph summary listing which knowledge nodes it traversed. +The response holds the reply, every tool call with its result, the token usage, and the number of model calls. ```json { "success": true, "reply": "## Marginfi vs Kamino Lending Comparison\n\n...", "toolCalls": [ - { "tool": "web_search", "args": { "query": "Marginfi vs Kamino lending Solana" } }, - { "tool": "token_price", "args": { "token": "MNDE" } } + { "tool": "web_search", "args": { "query": "Marginfi vs Kamino lending Solana" }, "result": { "...": "..." } }, + { "tool": "token_price", "args": { "token": "MNDE" }, "result": { "...": "..." } } ], + "usage": { "promptTokens": 0, "completionTokens": 0, "totalTokens": 0 }, "iterations": 4, - "skillGraph": { - "nodesTraversed": ["defi-analysis", "liquidity-risk", "comparative-analysis"], - "relevanceScore": 0.72 - } + "model": "gpt-oss:20b", + "provider": "nosana", + "billing": null, + "freeMessagesRemaining": 0 } ``` +The skill graph runs on this path, but the response does not include a `skillGraph` field. The traversal only shapes the system prompt. + - SERV is the default reasoning mode for every agent request with `useTools: true`. A standard agent loop asks the LLM which tool to call at each step; SERV instead walks a pre-defined execution graph and calls the LLM only once at the end, to synthesize the answer. That single difference is why token cost and latency drop so sharply — and output quality holds. + `useTools: true` is the default. It runs the standard tool loop with up to 8 model calls. It does not turn on guided reasoning. To use the BRAID path, send `reasoning: 'braid'`. See [POST /agent](/docs/api-reference/agent) for every field. diff --git a/content/docs/concepts/attestation.mdx b/content/docs/concepts/attestation.mdx index 6ac8933..a8d7dd3 100644 --- a/content/docs/concepts/attestation.mdx +++ b/content/docs/concepts/attestation.mdx @@ -1,16 +1,18 @@ --- title: "Attestation" icon: BadgeCheck -description: "Every Solrouter TEE response is backed by an Intel-signed TDX quote. Fetch the attestation and verify the enclave code yourself — on-chain or off-chain." +description: "Fetch the Intel-signed TDX quote from the Solrouter enclave, check that it binds the key you encrypt to, and look up the on-chain receipt for each private inference." +status: mixed +checked: "2026-08-26" +statusNote: "The quote endpoints and on-chain receipts are live. Reference measurements to compare against are not published yet." --- import { Callout } from 'fumadocs-ui/components/callout'; import { Step, Steps } from 'fumadocs-ui/components/steps'; -import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; -When you send a prompt to a private inference service, how do you know it actually ran where the service claims — and on the code the service published, not a tampered copy that quietly logs your data? Solrouter answers that with attestation: hardware-signed proof you can check yourself. +When you send a prompt to a private inference service, how do you know it ran where the service claims? How do you know the code is the published code and not a tampered copy that logs your data? Solrouter answers that with attestation: hardware-signed proof you can check yourself. -Every response processed inside the Solrouter TEE (Trusted Execution Environment — hardware that isolates code and data even from the machine's owner) is backed by a real Intel-signed TDX quote. That quote cryptographically binds the enclave's public key to the exact code measurement running inside the Confidential VM. You never have to take Solrouter's word for it: fetch the quote, verify Intel's signature chain, inspect the event log measurements, and confirm on-chain that your specific session ran inside an attested enclave. Trust is optional; verification is always available. +The Solrouter TEE (Trusted Execution Environment: hardware that isolates code and data even from the machine's owner) publishes an Intel-signed TDX quote. That quote binds the enclave's public key to the code measurement running inside the Confidential VM. Each `/tee/process` response also carries a quote when the CVM can reach the dStack agent. Otherwise the `attestation.tdxQuote` field is `null` and `attestation.tdxQuoteError` says why. You never have to take Solrouter's word for it: fetch the quote, verify Intel's signature chain, and confirm on-chain that your inference has a receipt. ## Live attestation endpoints @@ -22,7 +24,7 @@ These two endpoints hand you the raw material for verification. Query them any t GET https://api.solrouter.com/tee/public-key ``` -This returns the X25519 public key currently active inside the Confidential VM — the key your SDK uses to encrypt prompts client-side. The enclave generates it at boot, so only the enclave holds the matching private key. Nobody on the host, including Solrouter, can decrypt traffic sealed to it. +This returns the X25519 public key currently active inside the Confidential VM. The SDK uses this key to encrypt prompts client-side. The enclave generates it at boot, so only the enclave holds the matching private key. The key changes on every CVM boot. Nobody on the host, including Solrouter, can decrypt traffic sealed to it. ### Get the TDX attestation quote @@ -30,66 +32,66 @@ This returns the X25519 public key currently active inside the Confidential VM GET https://api.solrouter.com/tee/attestation ``` -This returns the Intel TDX attestation quote — the hardware-signed proof that ties everything together. Its `report_data` field contains `sha256(pubkey)`, which binds the public key you fetched above to the exact code measurement inside the CVM. Verify the quote and you confirm three things at once: +This returns the Intel TDX attestation quote, the hardware-signed proof that ties everything together. The response includes `teePublicKey`, `teePublicKeySha256`, `reportDataHex`, and `tdxQuote`. Its `report_data` is `sha256(teePublicKey)`, which binds the public key you fetched above to the enclave that produced the quote. Verify the quote and you confirm two things: 1. The host CPU is a genuine Intel TDX-capable processor. 2. The public key was generated inside that specific enclave instance. -3. The enclave is running the code Solrouter publishes — not a modified version. + +Confirming that the enclave runs the published Solrouter code needs reference measurements. Those are not published yet (see below). + +### Two report_data formulas + +Solrouter produces two kinds of quote. They pin different data, so check the right formula for the quote you hold. + +| Quote | `report_data` | +| ------------------------------------------------ | ------------------------------------------ | +| `GET /tee/attestation` | `sha256(X25519 public key)` | +| `attestation.tdxQuote` in a `/tee/process` reply | `sha256(X25519 public key ‖ ed25519 public key)` | + +The ed25519 key is a signing key the enclave also generates at boot. The enclave uses it to sign the encryption proof that goes on-chain. ## What you can verify -Attestation only matters if you can check the claims independently. Here is exactly what the quote lets you prove on your own, with no input from Solrouter: +Attestation only matters if you can check the claims independently. Here is what the quote lets you prove on your own today, and what it does not yet. -* **Intel root chain** — the TDX quote is signed by an Intel-issued key. Verifying the signature chain confirms the hardware is a genuine TDX CPU, not a simulated or spoofed environment. -* **Code measurements** — the event log inside the quote includes verifiable measurements of every component in the running stack: - * `compose-hash` — the container composition that defines the enclave workload - * `app-id` — the specific application image - * `os-image-hash` — the guest OS image loaded into the CVM - * `mr-kms` — the KMS measurement used for key management -* **Public key binding** — because `report_data = sha256(pubkey)`, you can confirm that the key used to encrypt your prompt belongs to this exact enclave instance, not an interceptor sitting in the middle. +* **Intel root chain**: the TDX quote is signed by an Intel-issued key. Verifying the signature chain confirms the hardware is a genuine TDX CPU, not a simulated or spoofed environment. Live. +* **Public key binding**: `report_data` commits to the enclave's public key. So you can confirm the key you encrypted to belongs to this enclave instance. An interceptor cannot substitute its own key. Live. +* **Code measurements**: the `tdxQuote` field is the dStack guest agent's response, passed through unparsed. Solrouter does not publish a parser or reference values for the measurements inside it. The product repository is private. Reference measurements: Soon. - Advanced users can verify the full Intel TDX quote chain independently using Intel's DCAP (Data Center Attestation Primitives) libraries or a third-party TEE verification service. The quote Solrouter returns is a standard TDX quote — no proprietary format. + Advanced users can verify the full Intel TDX quote chain independently using Intel's DCAP (Data Center Attestation Primitives) libraries or a third-party TEE verification service. The quote Solrouter returns is a standard TDX quote, no proprietary format. ## On-chain attestation anchor -Off-chain verification proves the enclave is genuine, but it lives in a response you have to trust Solrouter to keep. For a record nobody can quietly edit later, Solrouter anchors attestation data to Solana mainnet. +Off-chain verification proves the enclave is genuine, but it lives in a response you have to trust Solrouter to keep. For a record nobody can quietly edit later, Solrouter anchors attestation data to Solana. Cluster: the commit code targets mainnet; the program deployment on mainnet has not been re-measured. -The Solrouter encryption-attestation program is deployed at: +The Solrouter attestation program is deployed at: ``` ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb ``` -Each privacy-mode session can publish a **PDA (Program Derived Address — an account whose address is deterministically derived from the program)** that links that specific request to the attested TEE. Once it settles on-chain, there is an immutable, publicly verifiable record that your interaction ran inside a verified enclave — not just a log entry in Solrouter's database that could change. +Each inference sent through `POST /tee/process` gets a **Light Protocol compressed account** on Solana. Solrouter's deployer wallet commits it after the CVM signs the proof. This is automatic; you do not publish anything. The account address is derived from the seeds `attestation_v2` and `sha256(encryptedPrompt)`, so anyone who holds the ciphertext can derive the same address. The `/tee/process` response reports it under `onchainAttestation` with `address`, `signature`, and `explorerUrl`. If the commit fails, the inference still succeeds and `onchainAttestation` is `null`. + +To read a receipt back, use the public attestation endpoints. They read from the Solana ledger through a Photon indexer. -To fetch the on-chain attestation PDA for any session, use the `umbra_attestation` tool in the Agent Tools SDK or MCP server: +```bash +# By the commit transaction signature from onchainAttestation.signature +GET https://api.solrouter.com/attestation/by-tx/:sig - - - ```typescript - import { SolrouterAgentClient, callTool } from '@solrouter/agent-tools'; +# By sha256 of the encrypted prompt +GET https://api.solrouter.com/attestation/by-hash/:hash - const client = new SolrouterAgentClient({ apiKey: 'sk_solrouter_...' }); +# By the compressed account address from onchainAttestation.address +GET https://api.solrouter.com/attestation/:address - // Fetch the on-chain attestation for a completed session. - // callTool is a standalone function — pass the client as the first arg. - // (Equivalent direct method: client.attestation('your-session-id')) - const attestation = await callTool(client, 'umbra_attestation', { - sessionId: 'your-session-id', - }); +# Derive hash and address from a ciphertext you hold +POST https://api.solrouter.com/attestation/derive +{ "encryptedPrompt": "..." } +``` - console.log(attestation); - ``` - - - ```bash - # In your MCP-connected client, call: - umbra_attestation({ sessionId: "your-session-id" }) - ``` - - +The `umbra_attestation` MCP tool is a different thing. It returns the settlement record of a private-swap session from `GET /agents/v1/attestations/:sessionId`. It does not read inference receipts. ## Verification flow @@ -117,18 +119,18 @@ Use Intel DCAP or a compatible TEE verification library to validate the quote's ### Check report_data -Confirm that `report_data` in the quote equals `sha256(pubkey)` from Step 1. This binds the public key to the verified enclave, so you know you encrypted to the right key. +Decode `publicKey` from Step 1 from base64 and hash the 32 raw bytes with sha256. The hex digest must equal `reportDataHex` and `publicKeySha256`. This binds the public key to the verified enclave, so you know you encrypted to the right key. -### Inspect code measurements +### Compare code measurements (Soon) -Compare the `compose-hash`, `app-id`, `os-image-hash`, and `mr-kms` values against what Solrouter publishes in its open-source repository to confirm you are running the expected code. +This step needs published reference measurements. Solrouter does not publish them yet. Until then, you can prove genuine hardware and key binding, but not which image is running. ### Check the on-chain anchor (optional) -Look up the session PDA on Solana mainnet at `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb` to confirm the on-chain settlement record. +Take `onchainAttestation.signature` from your `/tee/process` response and call `GET https://api.solrouter.com/attestation/by-tx/:sig`. You can also look up `onchainAttestation.address` directly on a Solana explorer. diff --git a/content/docs/concepts/encryption-proof.mdx b/content/docs/concepts/encryption-proof.mdx index 948af35..e855b93 100644 --- a/content/docs/concepts/encryption-proof.mdx +++ b/content/docs/concepts/encryption-proof.mdx @@ -1,29 +1,32 @@ --- title: "Encryption Proof" icon: Stamp -description: "Every private inference writes an on-chain receipt signed inside the Intel TDX enclave. Paste the lock-icon link from any chat message — or an address, hash, or tx — and verify it yourself." +description: "Each inference sent through POST /tee/process writes an on-chain receipt signed inside the Intel TDX enclave. Paste the lock-icon link from any chat message (or an address, hash, or tx) and verify it yourself." +status: live +checked: "2026-08-26" --- import { EncryptionProofVerifier } from '@/components/verify/encryption-proof-verifier'; import { Callout } from 'fumadocs-ui/components/callout'; import { Step, Steps } from 'fumadocs-ui/components/steps'; -[Attestation](/docs/concepts/attestation) proves the enclave is genuine. The **encryption proof** ties *your specific request* to that enclave: a TDX-attested enclave signs your exact ciphertext hash with a key that exists only inside the enclave, and writes the receipt to Solana. The backend relays it but can't forge it. +[Attestation](/docs/concepts/attestation) proves the enclave is genuine. The **encryption proof** ties *your specific request* to that enclave. A TDX-attested enclave (a TEE, a trusted execution environment: a hardware-isolated virtual machine) signs your exact ciphertext hash with a key that exists only inside the enclave, and the receipt goes to Solana. The backend relays it. It cannot change it without breaking the signature. A verifier who also checks the per-request quote can detect a forged record. -Each receipt is a **Light Protocol compressed account** — about **0.000005 SOL each, ~400× cheaper than a normal Solana PDA**. That cost gap is the whole reason a proof *per message* is viable: a standard PDA for every inference would be economically absurd at scale; compression makes it routine. +Each receipt is a **Light Protocol compressed account**: about **0.000005 SOL each, about 400 times cheaper than a normal Solana PDA**. That cost gap is why a proof *per message* is viable. A standard PDA for every inference would cost too much at scale. Compression makes it routine. ## Verify a proof -In chat, every private-mode reply has a **🔒 next to it — click it, copy the link, and paste it here.** You can also paste a Light attestation address, the commit-transaction signature, or the 64-character encrypted-prompt hash. The widget reads the on-chain record and verifies the enclave's ed25519 signature **in your browser** — no trust in Solrouter required. +In chat, every private-mode reply has a **🔒 next to it. Click it, copy the link, and paste it here.** You can also paste a Light attestation address, the commit-transaction signature, or the 64-character encrypted-prompt hash. The widget fetches the record through `api.solrouter.com/attestation/*` and checks the enclave's ed25519 signature **in your browser**. Solrouter serves the record. The signature check proves the signer holds the key in `enclave_pubkey`. To prove that key lives in the enclave, do Step 4 below with the per-request quote. ## Verifying from an agent or the SDK -Chat hands you a clickable lock link. The **agent API and SDK** hand you the proof in the response payload instead — every private inference returns an `onchainAttestation` object: +Chat hands you a clickable lock link. The REST endpoint `POST /tee/process` hands you the proof in the response payload instead. Every private inference that commits a receipt returns an `onchainAttestation` object: ```json { + "type": "light-compressed", "address": "12Qenx1LK3ddTX5F3Apm6HYFFjswL3acSYNYXqUHTAVn", // Light compressed account "encryptedPromptHash": "5b17ccd7…", // sha256(ciphertext) "signature": "4RFJVwSC…", // the commit transaction @@ -31,7 +34,9 @@ Chat hands you a clickable lock link. The **agent API and SDK** hand you the pro } ``` -Verify it three equivalent ways — paste any of these into the widget above, or hit the public, CORS-open endpoints directly: +The field is `null` when the commit failed. The `@solrouter/sdk` `chat()` response does not pass this object through today. It exposes `privacyAttestationId` only. To get the full object, call `POST /tee/process` over HTTP. + +Verify it three equivalent ways. Paste any of these into the widget above, or call the public endpoints directly: ```bash # by the commit-transaction signature (what `signature` / the 🔒 link points to) @@ -44,25 +49,25 @@ curl https://api.solrouter.com/attestation/
curl https://api.solrouter.com/attestation/by-hash/ ``` -Each returns the full record with `version: "v2"` and every proof field below — ready to check, in your own code, against the enclave's signature. +Each returns the full record with `version: "v2"` and every proof field below. You can check it in your own code against the enclave's signature. The lock link is a transaction link (a compressed account can't be browsed on Solscan). The `by-tx` endpoint reads the commit transaction, pulls the encrypted-prompt hash out of its instruction data, and re-derives the - attestation address — so the link you already have is enough. + attestation address. So the link you already have is enough. ## What the record stores, and what gets signed -A v2 attestation lives under the Solrouter program `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`. Its address is **deterministic** — `deriveAddressV2(["attestation_v2", sha256(ciphertext)], …)` — which is why a bare hash is enough to find it. Alongside the basics (`model`, `provider`, `timestamp`, `backend_saw_plaintext`, `tee_processed`) it stores the proof: +A v2 attestation lives under the Solrouter program `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`. Its address is **deterministic**: `deriveAddressV2(["attestation_v2", sha256(ciphertext)], …)`. That is why a bare hash is enough to find it. Alongside the basics (`model`, `provider`, `timestamp`, `backend_saw_plaintext`, `tee_processed`) it stores the proof: | Field | Meaning | | --- | --- | | `client_pubkey` | Your ephemeral X25519 key from the request | | `tee_pubkey` | The enclave's X25519 sealing key your ciphertext was sealed to | | `nonce` | The RescueCipher nonce | -| `enclave_pubkey` | The enclave's ed25519 signing key — **bound inside the TDX quote** | +| `enclave_pubkey` | The enclave's ed25519 signing key, **bound inside the per-request TDX quote** | | `enclave_sig_r` / `enclave_sig_s` | The two halves of the ed25519 signature | | `tdx_quote_hash` | `sha256` of the TDX quote that attests `enclave_pubkey` | @@ -84,7 +89,7 @@ Inside the Confidential VM, the enclave signs this exact byte tuple: ### Read the record -Use any of the `curl` calls above. You get back `version: "v2"` plus every field — `encryptedPromptHash` (hex); `teePubkey`, `nonce`, `clientPubkey`, `enclavePubkey`, `enclaveSigR`, `enclaveSigS` (base64). +Use any of the `curl` calls above. You get back `version: "v2"` plus every field: `encryptedPromptHash` (hex); `teePubkey`, `nonce`, `clientPubkey`, `enclavePubkey`, `enclaveSigR`, `enclaveSigS` (base64). @@ -96,24 +101,32 @@ Concatenate the tuple above from the record bytes (`encryptedPromptHash` decoded ### Check the signature -Reassemble the 64-byte signature as `enclaveSigR ‖ enclaveSigS` and verify it over your message against `enclavePubkey` with any Ed25519 library. If a single byte was tampered — different ciphertext, swapped key, forged signer — it fails. +Reassemble the 64-byte signature as `enclaveSigR ‖ enclaveSigS` and verify it over your message against `enclavePubkey` with any Ed25519 library. If a single byte was tampered (different ciphertext, swapped key, forged signer), it fails. ### (Optional) Anchor the signing key in hardware -Confirm `enclave_pubkey` is the key bound into the TDX quote: fetch `GET /tee/attestation` and check the quote's `report_data` equals `sha256(tee_pubkey ‖ enclave_pubkey)`. That proves the signer is the attested enclave, not an impostor. +Two quotes exist, and they pin different values in `report_data`: + +| Quote | Where you get it | `report_data` | +| --- | --- | --- | +| Service quote | `GET /tee/attestation` | `sha256(tee_pubkey)` | +| Per-request quote | `attestation.tdxQuote` in a `POST /tee/process` response | `sha256(tee_pubkey ‖ enclave_pubkey)` | + +To confirm `enclave_pubkey` is bound to hardware, use the per-request quote. Compute `sha256(teePubkey ‖ enclavePubkey)` from the record and compare it with the quote's `report_data`. `GET /tee/attestation` proves only that the enclave owns the X25519 sealing key. The record's `tdx_quote_hash` is the sha256 of the per-request quote's JSON. -## Why the backend can't forge it +## Why a forged receipt is detectable -The ed25519 signing key is generated **inside** the enclave at boot and never leaves it. Its public half is committed into the TDX quote's `report_data`, so anyone can confirm the signer is the genuine, attested enclave. The backend relays the proof onto Solana but never holds the private key — it can publish a real receipt, and it has nothing to forge a fake one with. +The ed25519 signing key is generated **inside** the enclave at boot and never leaves it. The backend signs and pays for the Solana transaction with the deployer wallet. It could commit a record that carries any ed25519 key and signature. The on-chain program stores those fields and does not check them. The browser widget checks the signature against the `enclave_pubkey` in the record. That check alone does not prove the signer is the enclave. To detect a forged record, also check that `enclave_pubkey` is bound into the per-request TDX quote (Step 4 above). A verifier who skips the quote check cannot tell a real enclave key from another key. Step 4 closes that gap. - A passing check proves an attested TDX enclave received and signed *your exact - ciphertext*, and that the record is committed on-chain. For the deepest level — - verifying Intel's full DCAP signature chain on the raw TDX quote — fetch the + A passing check proves the key named in the record signed *your exact + ciphertext*, and that the record is committed on-chain. The per-request + quote check (Step 4) proves that key belongs to an attested TDX enclave. For the deepest level, + verifying Intel's full DCAP signature chain on the raw TDX quote, fetch the live quote from `GET /tee/attestation` and run it through Intel's DCAP libraries. The on-chain record stores the quote's hash, not the full quote. diff --git a/content/docs/concepts/encryption.mdx b/content/docs/concepts/encryption.mdx index f291c74..1771032 100644 --- a/content/docs/concepts/encryption.mdx +++ b/content/docs/concepts/encryption.mdx @@ -1,15 +1,17 @@ --- title: "Encryption" icon: Lock -description: "Solrouter uses Arcium RescueCipher with X25519 key exchange for client-side encryption. Plaintext exists only inside an attested Intel TDX enclave." +description: "Solrouter encrypts prompts on your device with Arcium RescueCipher and X25519 key exchange. Only an Intel TDX enclave can decrypt them. Solrouter's backend never sees plaintext." +status: live +checked: "2026-08-26" --- import { Callout } from 'fumadocs-ui/components/callout'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; -When you send a prompt to an AI provider, you normally trust that provider to read it, store it, and not misuse it. Solrouter removes that trust requirement. Your prompt is encrypted on your own device before it leaves, and Solrouter's backend never holds the key to read it. +When you send a prompt to an AI provider, you normally trust that provider to read it, store it, and not misuse it. Solrouter removes that trust requirement for its own backend. Your prompt is encrypted on your own device before it leaves, and Solrouter's backend never holds the key to read it. -Solrouter encrypts your prompts and responses with Arcium's `RescueCipher` cipher and `X25519` key exchange (a fast, modern way for two parties to agree on a shared secret without ever transmitting it). The ciphertext travels through Solrouter's backend untouched, and is decrypted only inside a hardware-isolated Intel TDX Confidential VM — a TEE (Trusted Execution Environment: hardware that isolates code and data even from the machine's owner). Solrouter's backend is a blind relay: it routes encrypted blobs it cannot read, and the private key that could decrypt them never leaves the enclave. +Solrouter encrypts your prompts and responses with Arcium's `RescueCipher` cipher and `X25519` key exchange. X25519 lets two parties agree on a shared secret without sending it. The ciphertext travels through Solrouter's backend untouched. It is decrypted only inside a hardware-isolated Intel TDX Confidential VM, a TEE. A TEE (Trusted Execution Environment) is hardware that isolates code and data from the machine's owner. Solrouter's backend is a blind relay: it routes encrypted blobs it cannot read, and the private key that could decrypt them never leaves the enclave. ## Encryption components @@ -17,109 +19,84 @@ Here are the three building blocks that make the guarantee work, and what each o ### Client-side encryption -The first line of defense is simple: encrypt before you transmit. Your prompt is encrypted locally — in the browser or in your server process — before it is sent anywhere. +The first line of defense is simple: encrypt before you transmit. The SDK encrypts your prompt in the browser or in your server process before it sends anything. -* **`RescueCipher`** — Arcium's field-element symmetric cipher. Arcium chose it for compatibility with MPC, FHE, and ZK computation, so the same encrypted payload can be processed under any of those paradigms as Arcium's network matures. -* **`X25519` key exchange** — your SDK session generates an ephemeral (single-use, per-session) X25519 keypair. The shared secret is derived from your ephemeral private key and the TEE's attested public key. -* **TEE-generated keypair** — the TEE's own X25519 keypair is generated inside the Confidential VM at boot time. The private key never leaves the enclave — not even to Solrouter's own infrastructure. +* **`RescueCipher`**: Arcium's field-element symmetric cipher. Arcium chose it for compatibility with MPC, FHE, and ZK computation, so the same encrypted payload can be processed under any of those paradigms as Arcium's network matures. +* **`X25519` key exchange**: your SDK session generates an ephemeral (single-use, per-session) X25519 keypair. The SDK derives the shared secret from your ephemeral private key and the TEE public key. The SDK fetches that key from `GET /tee/public-key`. It does not fetch or verify the attestation quote. Verification is a manual step; see [Attestation](/docs/concepts/attestation). +* **TEE-generated keypair**: the TEE's own X25519 keypair is generated inside the Confidential VM at boot time. The private key never leaves the enclave, not even to Solrouter's own infrastructure. ### Inference isolation -Your data has to be decrypted somewhere to run the model. The question is *where*, and who can see it. With Solrouter, decryption and inference happen exclusively inside an attested enclave. +Your data has to be decrypted somewhere to run the model. The question is *where*, and who can see it. With Solrouter, decryption happens inside an attested enclave. The model runs on a Nosana GPU node that the enclave calls. -* **Intel TDX Confidential VM** — a hardware-enforced TEE. Memory is encrypted by the CPU and inaccessible to the host OS, hypervisor, and any Solrouter process running outside the enclave. -* **Plaintext exists only inside attested enclave memory** — the moment the model finishes generating a response, it is re-encrypted with your session's ephemeral key before it exits the enclave. -* **No host access** — no Solrouter employee, server process, or privileged operator can read your prompt or response. This is a hardware guarantee, not a policy promise. +* **Intel TDX Confidential VM**: a hardware-enforced TEE. Memory is encrypted by the CPU and inaccessible to the host OS, hypervisor, and any Solrouter process running outside the enclave. +* **Where plaintext exists**: the enclave decrypts your prompt, then calls the model on a Nosana GPU node (HTTPS per the documented node URL, not re-verified). The prompt and reply exist in plaintext in that node's memory during inference. The node runs outside the TDX enclave. The node operator could read the prompt at that moment; Solrouter does not control that hardware. The request is not linked to your identity on the node. When the reply returns to the enclave, it is encrypted with your session's ephemeral key before it leaves. +* **No backend access**: no Solrouter employee, server process, or privileged operator on the backend can read your prompt or response. The hardware enforces this. ### Transport -Encryption only helps if there is no gap where plaintext leaks in transit. There is none. - -* All traffic between your client and the enclave is encrypted end-to-end. There is no TLS termination point where plaintext is visible to an intermediary. -* The Solrouter backend is a **blind relay** — it forwards encrypted blobs without being able to decrypt them. It never has the keys. - -``` -You (Browser / SDK) - │ - ├── RescueCipher encrypts prompt client-side - │ with TEE's attested X25519 public key - │ - ▼ -Solrouter Backend - │ - ├── Cannot decrypt. Routes encrypted blob blindly. - │ - ▼ -Intel TDX Enclave - │ - ├── Hardware-isolated decryption inside the enclave - ├── Runs the model with plaintext (in-enclave only) - ├── Encrypts response with your ephemeral session key - │ - ▼ -Solrouter Backend - │ - ├── Still cannot see anything - │ - ▼ -You - │ - └── Decrypt with your ephemeral private key -``` +Encryption only helps if there is no gap where plaintext leaks in transit between you and the enclave. There is none. + +* Your prompt and the reply are encrypted end-to-end between your client and the enclave. The request metadata (API key, model id, `chatId`, and any `systemPrompt`) reaches the backend in plaintext. +* The Solrouter backend is a **blind relay**. It forwards encrypted blobs without being able to decrypt them. It never has the keys. + +The full request path, hop by hop, is on [How It Works](/docs/concepts/how-it-works#request-flow). ## Why RescueCipher? -You might wonder why Solrouter does not just use a familiar cipher like AES. The answer is about where your data can go next. +You might wonder why Solrouter does not use a familiar cipher like AES. The answer is about where your data can go next. Most symmetric ciphers (AES-GCM, ChaCha20) are designed for classical computation. They are efficient on CPUs and GPUs but are not naturally compatible with the algebraic structures that MPC, FHE, and ZK proofs operate over. -RescueCipher is a **field-element cipher** — it operates natively over the same finite-field arithmetic that MPC, FHE, and ZK systems use. That gives you three things: +RescueCipher is a **field-element cipher**. It operates natively over the same finite-field arithmetic that MPC, FHE, and ZK systems use. That gives you three things: * The same encrypted payload you send today can, in principle, be processed directly under MPC or FHE computation without re-encryption. * As Arcium's MXE (Multiparty eXecution Environment) network ships support for more cryptographic compute primitives, Solrouter's encryption layer does not need to change. * You get a smooth upgrade path: stronger cryptographic compute guarantees over time, zero migration work on your side. -This is why Arcium chose RescueCipher as the cipher for its MXE substrate — and why Solrouter uses it today, even before full MPC/FHE inference is live. +This is why Arcium chose RescueCipher as the cipher for its MXE substrate, and why Solrouter uses it today, even before full MPC/FHE inference is live. ## What encryption does NOT cover (yet) Privacy claims in this space are often inflated, so here is the honest line on what Solrouter does and does not do today. - Solrouter is **not** running pure FHE (Fully Homomorphic Encryption) inference today — and no production system does. LLM-scale FHE inference is many orders of magnitude away from viable latency. Anyone claiming "FHE LLM inference" in production is overclaiming. + Solrouter is **not** running pure FHE (Fully Homomorphic Encryption) inference today, and no production system does. LLM-scale FHE inference is many orders of magnitude away from viable latency. Anyone claiming "FHE LLM inference" in production is overclaiming. - What Solrouter offers today is **client-side encryption + hardware TEE isolation**, which is a real and meaningful guarantee. Arcium's MXE is a hybrid of MPC + FHE + ZK primitives, and RescueCipher is designed to work with all three. As Arcium's network matures, more of the inference pipeline will move from TEE-isolated plaintext into cryptographic compute — MPC first, then FHE/ZK where they are practical. The client encryption layer stays unchanged throughout. + What Solrouter offers today is **client-side encryption + hardware TEE isolation for decryption**, which is a real and meaningful guarantee. Arcium's MXE is a hybrid of MPC + FHE + ZK primitives, and RescueCipher is designed to work with all three. As Arcium's network matures, more of the inference pipeline will move from TEE-isolated plaintext into cryptographic compute: MPC first, then FHE/ZK where they are practical. The client encryption layer stays unchanged throughout. To be precise about what is and is not guaranteed today: -| Property | Today | -| ------------------------------------------ | ---------------------------------- | -| Client-side encryption before transmission | ✅ Yes — RescueCipher + X25519 | -| Plaintext isolated from Solrouter backend | ✅ Yes — Intel TDX enclave | -| Verifiable attestation of enclave code | ✅ Yes — Intel-signed TDX quote | -| MPC-based inference | 🔜 Roadmap — Arcium MXE | -| Full FHE inference | ❌ Not in production anywhere today | +| Property | Today | +| ------------------------------------------ | -------------------------------------------------------------- | +| Client-side encryption before transmission | Live: RescueCipher + X25519 | +| Plaintext hidden from Solrouter backend | Live: decryption happens only inside the Intel TDX enclave | +| Plaintext hidden from the Nosana GPU node | No: the model runs on plaintext on that node during inference | +| Intel-signed TDX quote you can fetch | Live: `GET /tee/attestation` | +| Published reference measurements | Soon: the repository is private, no reference values yet | +| MPC-based inference | Soon: Arcium MXE roadmap | +| Full FHE inference | Not in production anywhere today | ## Encryption options in the SDK -Encryption is on by default — you do not have to do anything to get it. You can turn it off for a faster plaintext path, but you give up every privacy guarantee on this page when you do. +Encryption is on by default in `@solrouter/sdk`. You do not have to do anything to get it. You can turn it off for a plaintext path, but you give up every privacy guarantee on this page when you do. The plaintext path sends your prompt to the same self-hosted Nosana models. It does not unlock any other model. ```typescript - // Default — fully encrypted (recommended) + // Default: fully encrypted (recommended) const response = await client.chat('Your prompt', { encrypted: true }); ``` ```typescript - // Plaintext path — faster, no encryption guarantees + // Plaintext path: no encryption guarantees const response = await client.chat('Your prompt', { encrypted: false }); ``` - When you set `encrypted: false`, your prompt and response travel in plaintext through Solrouter's infrastructure. Use the plaintext path only for non-sensitive workloads where latency is your primary concern. + When you set `encrypted: false`, your prompt and response travel in plaintext through Solrouter's infrastructure. Use the plaintext path only for non-sensitive workloads. diff --git a/content/docs/concepts/how-it-works.mdx b/content/docs/concepts/how-it-works.mdx index 2740b1c..6b5113b 100644 --- a/content/docs/concepts/how-it-works.mdx +++ b/content/docs/concepts/how-it-works.mdx @@ -1,72 +1,57 @@ --- title: "How It Works" icon: Workflow -description: Solrouter uses Arcium RescueCipher with X25519 key exchange and Intel TDX Trusted Execution Environments so plaintext never leaves the enclave. +description: "Your prompt is encrypted on your device. Solrouter's backend relays ciphertext it cannot read. An Intel TDX enclave decrypts it and runs the model on a Nosana GPU node." +status: live +checked: "2026-08-26" --- import { Callout } from 'fumadocs-ui/components/callout'; import { EncryptionFlow } from '@/components/diagrams/encryption-flow'; -Most AI providers see every word you send. Solrouter is built so that it cannot — even though it routes your traffic. +Most AI providers see every word you send. Solrouter routes your traffic, but its backend cannot read it. -When you send a message, your prompt is encrypted on your device before it leaves your application. The Solrouter backend never sees plaintext. It forwards an opaque encrypted blob to an Intel TDX Confidential VM (a Trusted Execution Environment, or TEE — hardware that isolates code and data even from the machine's owner), where only attested enclave code can decrypt and process your request. +When you send a message, your prompt is encrypted on your device before it leaves your application. The Solrouter backend never sees plaintext. It forwards an opaque encrypted blob to an Intel TDX Confidential VM (a Trusted Execution Environment, or TEE: hardware that isolates code and data even from the machine's owner). Only the enclave holds the key that can decrypt your request. -The enclave re-encrypts the response and returns it for you to decrypt locally. Unencrypted content never exists outside the enclave boundary. +The enclave decrypts the prompt and calls the model on a Nosana GPU node. The enclave then encrypts the reply and returns it for you to decrypt locally. Plaintext exists in two places: inside the enclave, and on the Nosana GPU node during inference. Solrouter's backend is never one of them. ## Encryption Stack -This section walks through the four layers of the pipeline. Each is built so that a breach at any single layer — including Solrouter's own infrastructure — still does not expose your data. +This section walks through the four layers of the pipeline. A breach of Solrouter's own backend does not expose your prompt or the reply on the encrypted path. **Client-side encryption** -Your prompt is encrypted before it leaves your device using Arcium's **RescueCipher**, a field-element symmetric cipher. The session key comes from an **X25519 key exchange** with the TEE's attested public key. The TEE generates its X25519 keypair inside the Confidential VM at boot and the private key never leaves the enclave, so only the enclave can derive the shared session secret. No one in the middle can. +Your prompt is encrypted before it leaves your device using Arcium's **RescueCipher**, a field-element symmetric cipher. The session key comes from an **X25519 key exchange** with the TEE's public key, which the SDK fetches from `GET /tee/public-key`. The TEE generates its X25519 keypair inside the Confidential VM at boot. The private key never leaves the enclave, so only the enclave can derive the shared session secret. No one in the middle can. **Inference isolation** -The Solrouter backend acts as a blind relay: it takes the encrypted blob and forwards it to an **Intel TDX Confidential VM**, a hardware-level Trusted Execution Environment where the host operating system and hypervisor cannot read enclave memory. Decryption, model inference, and response encryption all happen inside that isolated boundary — never on the open server. +The Solrouter backend acts as a blind relay. It takes the encrypted blob and forwards it to an **Intel TDX Confidential VM**, a hardware-level Trusted Execution Environment. The host operating system and hypervisor cannot read enclave memory. Decryption and response encryption happen inside that isolated boundary. + +The model itself runs on a Nosana GPU node, not inside the enclave. The enclave sends the decrypted prompt to that node at the configured Nosana endpoint URL (HTTPS per the documented node URL, not re-verified) and receives the reply. The prompt and reply exist in plaintext in that node's memory during inference. The node operator could read the prompt at that moment. Solrouter does not control that hardware. The request is not linked to your identity on the node. **Transport** -The channel between your client and the enclave is encrypted end-to-end. The backend infrastructure handles only ciphertext, never plaintext content. +The channel between your client and the enclave is encrypted end-to-end. The backend never sees your prompt or the reply. It does see your API key, the model id, `chatId`, and any `systemPrompt` you set, in plaintext. **Verifiable attestation** -You should not have to take our word that the right code is running. The enclave publishes an **Intel-signed TDX quote** that cryptographically binds its X25519 public key to the exact code measurement of the running image. Fetch the quote from `GET /tee/attestation` and verify for yourself that the enclave handling your request is the published, unmodified Solrouter code — not something we swapped in. +You should not have to take our word that the right code is running. The enclave publishes an **Intel-signed TDX quote** that binds its X25519 public key to the code measurement of the running image. Fetch the quote from `GET /tee/attestation`. Verify Intel's signature chain and confirm that the key you encrypt to belongs to that enclave. Reference measurements to compare against: Soon. The product repository is private, so no reference values are published today. ## Request Flow -To make the guarantees concrete, here is the complete path a privacy-mode request travels — and where, at each hop, your data stays encrypted: +Here is the complete path a privacy-mode request travels, and where your data is encrypted at each hop. -```text -You (Browser / SDK) - │ - ├── Arcium RescueCipher encrypts prompt client-side - │ with TEE's attested X25519 public key - │ - ▼ -Solrouter Backend - │ - ├── Cannot decrypt. Routes encrypted blob blindly - │ - ▼ -Intel TDX Enclave - │ - ├── Hardware-isolated decryption inside the enclave - ├── Runs the model on plaintext (in-enclave only) - ├── Encrypts response with the session's ephemeral key - │ - ▼ -Solrouter Backend - │ - ├── Still can't see anything - │ - ▼ -You - │ - └── Decrypt with your ephemeral private key -``` +In words: + +1. Your device encrypts the prompt with RescueCipher. The key comes from an X25519 exchange with the TEE public key. +2. The Solrouter backend receives the ciphertext. It cannot decrypt it. It forwards the blob to the Intel TDX enclave. +3. The enclave decrypts the prompt inside hardware-isolated memory. +4. The enclave calls the model on a Nosana GPU node. The prompt is plaintext on that node during inference. +5. The enclave encrypts the reply with your session's ephemeral key. +6. The Solrouter backend relays the encrypted reply. It still cannot read it. +7. Your device decrypts the reply with your ephemeral private key. ## What Is Not (Yet) Encrypted @@ -74,23 +59,23 @@ Privacy claims are easy to inflate, so here is exactly where the guarantee ends Solrouter is **not** running pure fully homomorphic encryption (FHE) inference - today. No production system runs LLM-scale inference under FHE in 2026 — the + today. No production system runs LLM-scale inference under FHE in 2026. The compute overhead is many orders of magnitude away from viable latency. Anyone claiming "FHE LLM inference" in production is overclaiming. - What Solrouter provides is Arcium-encrypted transport combined with Intel TDX - hardware isolation during compute, anchored by a real on-chain attestation - program on Solana mainnet. That is a meaningful and verifiable privacy - guarantee — it is simply not the same as FHE inference, and we will not claim - otherwise. + What Solrouter provides is Arcium-encrypted transport, Intel TDX hardware + isolation for decryption, and a Light Protocol compressed account on Solana + as a receipt for each inference sent through `POST /tee/process`. That is a meaningful and + verifiable privacy guarantee. It is not the same as FHE inference, and we + will not claim otherwise. -The honest summary: your prompt is encrypted in transit and isolated during compute inside a hardware-enforced enclave. The model runs on plaintext inside that enclave. That plaintext is inaccessible to Solrouter, the host, or anyone watching the network — but it is not processed under FHE. +The honest summary: your prompt is encrypted in transit and decrypted inside a hardware-enforced enclave. The model runs on plaintext on a Nosana GPU node. That plaintext is inaccessible to Solrouter's backend and to anyone watching the network. The Nosana node operator could read it during inference. It is not processed under FHE. ## Roadmap -The current TEE pipeline is a starting point, not the ceiling. Here is where the privacy model is headed and why your integration won't have to change to follow it. +The current TEE pipeline is the first step. Here is where the privacy model is headed and why your integration will not have to change to follow it. -Solrouter is built on Arcium's MXE (Multiparty eXecution Environment) substrate, which combines MPC, FHE, and ZK primitives. Choosing **RescueCipher** — a field-element cipher — was deliberate: the same encrypted payloads that flow through today's TEE pipeline can later be processed under MPC, FHE, or ZK circuits as Arcium's network matures, with no changes to the client encryption layer. +Solrouter is built on Arcium's MXE (Multiparty eXecution Environment) substrate, which combines MPC, FHE, and ZK primitives. Choosing **RescueCipher**, a field-element cipher, was deliberate. As Arcium's network matures, the same encrypted payloads that flow through today's TEE pipeline can later run under MPC, FHE, or ZK circuits. The client encryption layer does not change. -As the MXE network ships in production, more of the inference pipeline moves from TEE-isolated plaintext into cryptographic compute: MPC first, then FHE and ZK primitives where they make practical sense for latency and cost. When that shift happens, your integration stays the same — the encryption layer you use today is already compatible. +As the MXE network ships in production, more of the inference pipeline moves from TEE-isolated plaintext into cryptographic compute. MPC comes first. FHE and ZK follow where latency and cost allow. When that shift happens, your integration stays the same. The encryption layer you use today is already compatible. diff --git a/content/docs/concepts/serv-reasoning.mdx b/content/docs/concepts/serv-reasoning.mdx index 752e9ac..9041a6f 100644 --- a/content/docs/concepts/serv-reasoning.mdx +++ b/content/docs/concepts/serv-reasoning.mdx @@ -1,88 +1,82 @@ --- -title: "SERV Reasoning" +title: "Guided reasoning (BRAID)" icon: BrainCircuit -description: "SERV replaces freeform LLM decisions with deterministic guided reasoning diagrams, cutting token costs by 79.7% and latency by 35% with no quality loss." +description: "BRAID walks a fixed Guided Reasoning Diagram to collect data, then calls the model once to write the answer. Older material calls it SERV." +status: live +checked: "2026-08-26" --- import { Callout } from 'fumadocs-ui/components/callout'; import { Step, Steps } from 'fumadocs-ui/components/steps'; -A standard agent loop asks the LLM what to do at every step, which is slow, expensive, and unpredictable. SERV (Structured Execution via Reasoning Virtualization) fixes that by separating the two jobs an agent actually does: deciding *what* to gather, and writing *the answer*. +A standard agent loop asks the model what to do at every step. Each step is one model call. BRAID separates the two jobs an agent does: deciding what data to gather, and writing the answer. -Instead of asking the model what to do next, SERV walks a Guided Reasoning Diagram (GRD) — a pre-defined execution graph that controls tool selection, sequencing, and data collection on its own, with no LLM in the loop. The model is called exactly once, at the very end, to turn the collected data into a natural language response. +BRAID walks a Guided Reasoning Diagram (GRD). A GRD is a fixed execution graph. It sets which tools run and in what order. The model is called once at the end to turn the collected data into a reply. -Splitting structure from synthesis is the whole trick: it makes SERV far cheaper and faster than a standard agent loop while keeping output quality high. +The code, the SDK option, and the API value call this path BRAID. Older material calls it SERV. -## Performance results +Six GRDs exist: `comparison`, `defi-analysis`, `general-research`, `market-overview`, `token-research`, and `wallet-analysis`. -Here is what that split buys you, benchmarked against a standard agent loop on identical complex research queries: +## What the split buys you -| Metric | Standard | SERV | Improvement | -| ----------- | ------------ | ----------- | ----------- | -| Quality | 80/100 | 93/100 | +13 | -| Token cost | 19,917/query | 4,047/query | -79.7% | -| Latency | 24.0s | 15.7s | -35% | -| Reliability | 100% | 100% | Parity | +The tool loop spends one model call per step. BRAID spends one synthesis call at the end, plus a short model call only for a branch that no rule can decide. That is the whole mechanism behind the cost and latency claim. No benchmark numbers are published. -SERV cuts cost and latency *and* improves quality, with no loss in reliability. The quality gain is not magic: because the GRD gathers data the same structured way every time, the model synthesizes from a complete, consistent picture instead of improvising its own research path. +## How BRAID works -## How SERV works - -This section walks through the four stages a query passes through, and why each one keeps the LLM out of the decisions it isn't good at. - -A standard agent loop asks the model the same question at every step: "Given what you know so far, what tool should you call next?" That burns tokens on structural choices the model has no special advantage in making, and it introduces non-determinism that can derail a complex query halfway through. - -SERV takes a different approach: +A query passes through four stages. - ### Query classification + ### Intent detection - The incoming prompt is classified into a query type (e.g. DeFi comparison, wallet audit, token research). Each query type maps to a pre-defined Guided Reasoning Diagram. + Keyword rules map the prompt to one of six intents, for example wallet analysis, comparison, or DeFi analysis. Each intent maps to one GRD. No model call is made here. ### GRD execution - SERV walks the diagram node by node — calling tools, collecting data, and branching on results — all deterministically, without LLM involvement. + BRAID walks the diagram node by node. Tool nodes run in the order the GRD sets. Branch nodes use a rule first. When no rule applies, BRAID asks the model a short one-word question to pick the branch. ### Skill graph injection - If the query type triggers relevant knowledge nodes, the skill graph injects domain context into the synthesis payload. See [Skill Graphs](/docs/concepts/skill-graphs) for details. + If the prompt matches skill nodes, the skill graph adds domain context to the synthesis prompt. See [Skill Graphs](/docs/concepts/skill-graphs). - ### LLM synthesis + ### Synthesis - Only after all data is collected does SERV call the LLM — once — to transform the structured results into a coherent natural language response. + After the walk, BRAID calls the model once. The model turns the collected data into a reply. -The key insight: don't ask a 20B model to make structural decisions. Do those deterministically, and reserve the LLM for the one task it does best — turning raw information into useful language. +Two prompt shapes skip the GRD walk. A swap prompt with two known tokens, or a prompt with three or more known tokens, calls the tools directly and then runs the same single synthesis call. -## Using SERV in the SDK +## Use BRAID in the SDK -The fastest way to try SERV is through the SDK. Pass `reasoning: 'braid'` to the `chat()` method, and your request routes through the agent endpoint with SERV-guided reasoning enabled. +Pass `reasoning: 'braid'` to `chat()`. The SDK then sends the request to `/agent`. ```typescript import { SolRouter } from '@solrouter/sdk'; const client = new SolRouter({ - apiKey: 'sk_solrouter_...' + apiKey: 'sk_solrouter_...', + baseUrl: 'https://api.solrouter.com', }); const response = await client.chat('Compare Marginfi vs Kamino lending on Solana', { - reasoning: 'braid', // enables SERV-guided reasoning + reasoning: 'braid', }); console.log(response.message); ``` -## Using SERV via the API +The SDK sends the prompt in plaintext on this path. The response has `encrypted: false`. Client-side encryption is not available for BRAID in SDK 1.1.0. + +## Use BRAID over HTTP -If you're not using the SDK, you can call the agent endpoint directly over HTTP. Set `useTools: true` to activate the SERV execution graph. +Call `/agent` directly and set `reasoning: 'braid'`. `useTools: true` alone does not select BRAID. It runs the standard tool loop. ```bash curl -X POST "https://api.solrouter.com/agent" \ @@ -91,12 +85,13 @@ curl -X POST "https://api.solrouter.com/agent" \ -d '{ "prompt": "Compare Marginfi vs Kamino lending on Solana", "model": "gpt-oss:20b", - "useTools": true + "reasoning": "braid", + "braidOptions": { "includeTrace": true } }' ``` -The response includes an `iterations` field showing how many GRD steps SERV executed, and a `toolCalls` array logging every tool the agent invoked and with what arguments. +The response has `reasoning: 'braid'` and an `iterations` field. On this path `iterations` counts GRD nodes, not model calls. When `braidOptions.includeTrace` is `true`, the response also has a `braidTrace` object with the GRD id, the intent, and one entry per node. See [POST /agent](/docs/api-reference/agent) for the full envelope. - SERV shines on complex, multi-step research queries — protocol comparisons, wallet audits, market analysis, tokenomics deep-dives — where structured data gathering pays off. For simple lookups like a single token price or swap quote, a direct tool call is faster. SERV's skill graph traversal is selective and skips automatically for lightweight queries, but if you already know your query is simple, calling the relevant tool directly gives you the lowest possible latency. + BRAID fits multi-step research prompts: protocol comparisons, wallet audits, market overviews. For a single token price or swap quote, a direct tool call is faster. diff --git a/content/docs/concepts/skill-graphs.mdx b/content/docs/concepts/skill-graphs.mdx index ddb74c5..a91207f 100644 --- a/content/docs/concepts/skill-graphs.mdx +++ b/content/docs/concepts/skill-graphs.mdx @@ -1,70 +1,37 @@ --- title: "Skill Graphs" icon: Network -description: "Skill graphs inject structured domain knowledge into Solrouter agent responses for DeFi, on-chain data, and market analysis — activated only when needed." +description: "Skill graphs add domain knowledge for DeFi, on-chain data, and market analysis to the agent's system prompt when the prompt matches a node." +status: live +checked: "2026-08-26" --- import { Callout } from 'fumadocs-ui/components/callout'; -An LLM asked about a DeFi protocol has to reconstruct specialist knowledge — risk frameworks, liquidity heuristics, evaluation criteria — from whatever it happened to absorb during training. That is slow and unreliable. Skill graphs fix this by handing the model that expertise as structured input, before it ever starts answering. +A model asked about a DeFi protocol has to rebuild specialist knowledge (risk frameworks, liquidity heuristics, evaluation criteria) from its training data. Skill graphs hand the model that knowledge as structured text before it starts to answer. -A skill graph is Solrouter's domain-knowledge layer. It sits between tool execution and LLM synthesis in the SERV pipeline. When your query matches the trigger conditions of one or more skill nodes, the engine walks the connected graph and injects the relevant expertise straight into the synthesis context. +A skill graph is Solrouter's domain-knowledge layer. The engine runs on the plaintext `/agent` paths. When the prompt matches the trigger words of one or more nodes, the engine walks the connected graph and adds the node content to the system prompt. -The payoff: the model writes from curated knowledge rather than guessing. You get more accurate, more nuanced answers on hard topics — and because the knowledge is injected only when it applies, there is no extra token overhead on queries that don't need it. +The knowledge is added only when it applies. A prompt that matches no node gets no extra tokens. -## Knowledge domains +## Knowledge nodes -This is the catalog of expertise the engine can draw on. Solrouter ships with 14 base knowledge nodes across four areas. Each node carries curated heuristics, definitions, risk frameworks, and evaluation criteria that the LLM uses during synthesis. +The engine defines 44 node ids. Each node has a name, trigger words, an `edges` list of connected nodes, and a Markdown body with heuristics, definitions, and evaluation criteria. -**DeFi & Markets** - -* **DeFi protocol analysis** — lending, borrowing, AMM mechanics, TVL interpretation, protocol risk -* **Liquidity risk** — slippage, depth, impermanent loss, pool concentration -* **Tokenomics** — emission schedules, vesting, buyback models, supply dynamics -* **Market analysis** — price action, volume interpretation, trend identification -* **On-chain signals** — transaction patterns, fee pressure, validator behavior, network health -* **Whale tracking** — large-wallet behavior, accumulation/distribution signals - -**Portfolio & Wallets** - -* **Wallet analysis** — address clustering, activity patterns, counterparty relationships -* **Portfolio risk assessment** — concentration risk, correlation, drawdown analysis - -**Technical & Research** - -* **Privacy / encryption technology** — MPC, ZK proofs, TEE architecture, FHE fundamentals -* **Research methodology** — structured inquiry, source triangulation, claim verification -* **Source evaluation** — credibility scoring, recency weighting, conflict detection -* **Comparative analysis** — side-by-side frameworks, trade-off matrices, scoring rubrics - -**Ecosystem** - -* **Solana ecosystem knowledge** — native programs, validator economics, fee markets, SPL standards -* **Solana DeFi landscape** — protocol relationships, liquidity flows, ecosystem interdependencies +The node ids are: `research-core`, `source-eval`, `defi-analysis`, `liquidity-risk`, `token-economics`, `market-analysis`, `on-chain-analysis`, `wallet-analysis`, `privacy-research`, `risk-assessment`, `smart-contract-risk`, `comparative-analysis`, `data-synthesis`, `colosseum-research`, `colosseum-archives`, `arcium-mpc`, `solana-ecosystem`, `jupiter-defi`, `raydium-defi`, `orca-defi`, `meteora-defi`, `kamino-defi`, `sanctum-staking`, `pump-fun`, `lulo-lending`, `ranger-perps`, `prediction-markets`, `helius-infra`, `light-protocol-zk`, `metaplex-nfts`, `pyth-oracle`, `switchboard-oracle`, `squads-multisig`, `debridge-cross-chain`, `coingecko-analytics`, `solana-kit-dev`, `anchor-dev`, `pinocchio-dev`, `framework-kit-frontend`, `solana-testing`, `solana-security-audit`, `token2022-extensions`, `quicknode-infra`, and `magicblock-gaming`. ## Selective activation -Domain knowledge is only worth injecting when the query actually calls for it — so the engine decides per query whether to use the skill graph at all. It checks the incoming query against each node's trigger conditions before traversing anything. +The engine scores every node against the prompt. A node scores when the prompt contains one of its trigger words. The engine then walks the graph from the top three scoring nodes. It follows `edges` to a depth of 2 and stops at 5 nodes. - Simple queries — price checks, swap quotes, balance lookups — skip skill-graph traversal entirely. Activating domain knowledge for a single `token_price` call would add latency and tokens with no quality benefit. SERV is designed to match overhead to complexity: the skill graph fires when it helps, and stays silent when it doesn't. + A prompt that matches no trigger word gets no skill-graph text and no extra tokens. The graph fires when it matches and stays silent when it does not. -When traversal does happen, the engine pulls only the nodes the query needs. A DeFi protocol comparison might walk `defi-analysis`, `liquidity-risk`, and `comparative-analysis` while leaving the wallet and privacy nodes untouched. +When a walk happens, the engine adds only the nodes it reached. A DeFi protocol comparison might walk `defi-analysis`, `liquidity-risk`, and `comparative-analysis` and leave the wallet and privacy nodes alone. ## Skill graph in API responses -You don't have to guess what the engine did — every response tells you. Each agent response includes a `skillGraph` object reporting exactly which nodes were traversed and how confident the engine was in the match. - -```json -{ - "skillGraph": { - "nodesTraversed": ["defi-analysis", "liquidity-risk", "comparative-analysis"], - "relevanceScore": 0.72 - } -} -``` - -**`nodesTraversed`** — the ordered list of skill nodes the engine walked before synthesis. Read it to see which domain lenses the agent applied to your query. An empty array means the query was handled without skill-graph activation. +The `/agent` response does not include a `skillGraph` field. The engine computes the walked path and a relevance score, but the route does not send them. The traversal only shapes the system prompt. -**`relevanceScore`** — a float between 0 and 1 measuring how strongly the query matched the traversed nodes. A higher score means a tight semantic match between the query and the activated domain knowledge; a lower score means the engine traversed cautiously on a weaker signal. The `0.72` above is a solid but not perfect match — typical for a broad comparative query that spans several DeFi topics. +On the BRAID path, `braidOptions.includeTrace: true` returns a `braidTrace` object. That trace lists GRD nodes, not skill-graph nodes. See [POST /agent](/docs/api-reference/agent) for the response envelopes. diff --git a/content/docs/concepts/supported-models.mdx b/content/docs/concepts/supported-models.mdx index 60ab214..05ddd75 100644 --- a/content/docs/concepts/supported-models.mdx +++ b/content/docs/concepts/supported-models.mdx @@ -1,56 +1,75 @@ --- title: "Models" icon: Layers -description: "Solrouter runs only self-hosted open-weight models on the Nosana GPU network — no prompts reach OpenAI, Anthropic, or any third-party provider." +description: "Solrouter runs only self-hosted open-weight models on Nosana GPU nodes. No prompt reaches OpenAI, Anthropic, or any other proprietary model API." +status: mixed +checked: "2026-08-26" +statusNote: "The model table carries a Status column: gemma4:31b is Soon and qwen3:8b is Archived." --- import { Callout } from 'fumadocs-ui/components/callout'; -When you turn on privacy mode, Solrouter answers you using only self-hosted, open-weight models — never a proprietary API. That choice is deliberate: it's what lets the privacy guarantee actually hold. +In privacy mode Solrouter answers with self-hosted, open-weight models only. It never calls a proprietary API. That choice is what makes the privacy claim hold. -Every model runs on the [Nosana](https://nosana.io) decentralized GPU network. Your encrypted request travels from your device to an Intel TDX enclave (a hardware-isolated execution environment that keeps your data sealed even from the machine's operator), runs against one of the models below, and goes nowhere else. There are no integrations with proprietary model APIs in this path. +Every model runs on its own [Nosana](https://nosana.io) GPU node. Your encrypted request travels from your device to an Intel TDX enclave (a TEE, a trusted execution environment: a hardware-isolated virtual machine). The enclave decrypts it and calls the model on the Nosana node (HTTPS per the documented node URL, not re-verified). No proprietary model API is in this path. -## Available Models +## Available models -These are the open-weight models you can run in privacy mode today, with the ID you pass when selecting one. +Each row is one model with its ids and its status on 2026-08-26. -| Model | ID | License | Notes | -| ----------- | ------------- | ------------ | ------------------------------------------------------------------ | -| GPT-OSS 20B | `gpt-oss-20b` | Apache-2.0 | Default model. Strong general reasoning and instruction following. | -| Qwen 3 8B | `qwen3-8b` | Open weights | Lighter-weight alternative. Faster responses for simpler tasks. | +| Model | Catalog id | SDK id | Status | +| --- | --- | --- | --- | +| GPT-OSS 20B | `gpt-oss:20b` | `gpt-oss-20b` | Live | +| Qwen 3.8 27B | `qwen3.8:27b` | none yet | Live | +| Gemma 4 31B | `gemma4:31b` | none | Soon | +| Qwen 3 8B | `qwen3:8b` | `qwen3-8b` | Archived | -Both models are open-weight: their architecture and weights are public, so anyone can audit them. You aren't trusting a black box — you can inspect exactly what is running on your prompt. +`gpt-oss:20b`: default in the chat app and the SDK. Context 8192 tokens. -## Choosing a Model +`qwen3.8:27b`: listed as "Uncensored" in the chat picker. In SDK 1.1.0 it needs a type cast (see below). -This is how you pick a model in code, and when to reach for each one. +`gemma4:31b`: listed by `GET /api/v1/models` with a 262144-token context. The enclave has no endpoint for it yet, so the encrypted path is not confirmed. -Leave the model out and Solrouter defaults to `gpt-oss-20b`. To choose explicitly, pass it as an option to `client.chat()`: +`qwen3:8b`: retired node. The chat app folds this id to `qwen3.8:27b`. + +The REST API returns catalog ids with a `nosana:` prefix, for example `nosana:gpt-oss:20b`. The backend accepts both forms. + +All of these models are open-weight. Their weights are public, so anyone can inspect what runs on your prompt. + +## Choosing a model + +Leave the model out and the SDK defaults to `gpt-oss-20b`. To pick one, pass `model` to `client.chat()`: ```typescript const response = await client.chat('Your prompt here', { - model: 'gpt-oss-20b', // gpt-oss-20b (default) | qwen3-8b + model: 'gpt-oss-20b', // the only typed live model in SDK 1.1.0 }); ``` -Reach for `qwen3-8b` when speed matters more than depth — short, straightforward queries. Reach for `gpt-oss-20b` when the task needs stronger reasoning, longer context, or detailed synthesis. +The SDK type lists `gpt-oss-20b` and `qwen3-8b`. The second maps to the retired `qwen3:8b` node, so do not use it. Any other string passes through unchanged, so `nosana:qwen3.8:27b` works with a type cast. A typed alias needs a new SDK release (Soon). The chat app lets you pick `qwen3.8:27b` today. - Call `list_models` via the MCP server to confirm which models are currently live and what each one costs per call. + Call `list_models` on the MCP server to see the models `GET /api/v1/models` returns, with the price per million tokens. Today that list holds `gpt-oss:20b` and `gemma4:31b`, so it differs from the chat picker. -## Why Self-Hosted Models? +## Node warm-up + +A Nosana node goes idle when nobody uses it. The first request after idle time can fail with the error "Nosana GPU node is warming up". The error is retryable. Wait a short time and send the request again. + +## Why self-hosted models? + +End-to-end privacy only holds if your prompt never reaches a proprietary API. If Solrouter handed your decrypted prompt to OpenAI or Anthropic, that provider would see your plaintext. Client-side encryption and TEE isolation would then buy you nothing. -Here's the reasoning behind the constraint — why Solrouter refuses to route privacy-mode traffic to any third party. +Running only open-weight models on Nosana nodes closes that gap. It means: -End-to-end privacy only holds if your prompt never reaches a third-party API. The moment Solrouter handed your decrypted prompt to OpenAI or Anthropic, that provider would see your plaintext — and client-side encryption plus TEE isolation would have bought you nothing. +* No proprietary model provider ever sees your query, your documents, or your reply. +* Solrouter's backend never sees your plaintext. It relays ciphertext only. +* The model weights are public, so anyone can inspect what runs on your prompt. -Running only open-weight models on the Nosana network closes that gap. It means: +## Where plaintext exists -* Your prompt leaves the Intel TDX enclave only as a re-encrypted response sent back to you. -* No third-party model provider ever sees your query, your documents, or your response. -* The inference infrastructure stays auditable — Nosana's decentralized network and open-weight models don't depend on any single company's closed systems. +The enclave decrypts your prompt and then calls the model on a Nosana GPU node (HTTPS per the documented node URL, not re-verified) with `POST /v1/chat/completions` on that node. The node runs Ollama outside the TDX enclave. So your prompt and the reply exist in plaintext in that node's memory during inference. The node operator could read the prompt during inference. Solrouter does not control that hardware. What holds: Solrouter's backend never sees plaintext, and the request is not linked to your identity on the node. - There are no third-party model APIs in the Solrouter privacy pipeline — no OpenAI, no Anthropic, no Google, no Midjourney. If you need a proprietary model, you can disable encryption with `{ encrypted: false }`, but those prompts no longer get TEE isolation or the privacy guarantees described here. + There are no proprietary model APIs in the Solrouter privacy pipeline: no OpenAI, no Anthropic, no Google. `{ encrypted: false }` does not unlock one. It sends your prompt in plaintext to the same self-hosted Nosana models, without TEE isolation. The SDK cannot reach any proprietary model. diff --git a/content/docs/develop/agent-tools-sdk.mdx b/content/docs/develop/agent-tools-sdk.mdx index b953920..5c06b75 100644 --- a/content/docs/develop/agent-tools-sdk.mdx +++ b/content/docs/develop/agent-tools-sdk.mdx @@ -1,41 +1,30 @@ --- title: "Agent Tools SDK" icon: Bot -description: "Typed tools for the Solrouter Agent Privacy API with a Vercel AI SDK adapter. Enable AI agents to execute privacy-preserving on-chain swaps on Solana." +description: "Typed tools for the Solrouter Agent Privacy API with a Vercel AI SDK adapter. The package is not on npm yet. Use the HTTP API or the MCP tools today." +status: soon +checked: "2026-08-26" +statusNote: "The @solrouter/agent-tools package is not on npm yet." --- import { Callout } from 'fumadocs-ui/components/callout'; -import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; -If you want an AI agent to swap tokens on Solana without leaking who is trading what, you normally have to wire up quoting, signing, mixing, and settlement yourself. `@solrouter/agent-tools` removes that work: it gives you typed tools for the Solrouter Agent Privacy API (`/agents/v1`) so your agent can quote, execute, and settle privacy-preserving swaps and encrypted inference out of the box. + + `@solrouter/agent-tools` is not published on npm. The package exists in the Solrouter repository at version 1.0.0, but you cannot install it today. The code samples on this page show the planned API. To use the Agent Privacy API now, call `POST /agents/v1/*` over HTTP or use the `umbra_*` tools in the [MCP server](/docs/develop/mcp-server). + -It ships with a first-class Vercel AI SDK adapter, so you drop it into your agent without writing an orchestration layer. Not on the AI SDK? The raw `TOOLS` (JSON Schema definitions) and the `callTool()` function are also exported, so any function-calling framework works — you're never locked into a single AI runtime. +An AI agent that swaps tokens on Solana leaves a link from payer to destination. To break that link you normally wire up quoting, signing, mixing, and settlement yourself. `@solrouter/agent-tools` will do that work. It gives your agent typed tools for the Agent Privacy API (`/agents/v1`). The agent can quote, run, and settle private swaps, and call encrypted inference. -## Installation +It ships with a Vercel AI SDK adapter, so you can drop it into an agent without an orchestration layer. Not on the AI SDK? The raw `TOOLS` (JSON Schema definitions) and the `callTool()` function are also exported, so any function-calling framework works. -Add the package with your usual package manager. +## What to use today - - - ```bash - npm install @solrouter/agent-tools - ``` - - - ```bash - yarn add @solrouter/agent-tools - ``` - - - ```bash - pnpm add @solrouter/agent-tools - ``` - - +- **HTTP.** Call the Agent Privacy API directly. Quotes and anonymity-set reads are live at `GET /agents/v1/quote` and `GET /agents/v1/anonymity-set`. Swap execution routes are Soon. +- **MCP.** The [MCP server](/docs/develop/mcp-server) wraps the same routes as `umbra_*` tools for Claude Desktop and Cursor. -## Vercel AI SDK quickstart +## Vercel AI SDK quickstart (Soon) -This is the fastest path: let an LLM decide when to swap, and let the adapter handle the plumbing. Pass `aiSdkTools(solrouter)` straight into `generateText` or `streamText` — it wires up the tool schemas, validates the model's arguments, and marshals results back for you. +This will be the fastest path: let an LLM decide when to swap, and let the adapter handle the plumbing. Pass `aiSdkTools(solrouter)` into `generateText` or `streamText`. The adapter wires up the tool schemas, validates the model's arguments, and returns results to the model. ```typescript import { generateText } from "ai"; @@ -53,9 +42,9 @@ const result = await generateText({ }); ``` -## Direct usage (no LLM) +## Direct usage, no LLM (Soon) -Sometimes you don't want a model in the loop — you want to drive the privacy pipeline yourself in code. Mode B one-shot swaps do exactly that. Your agent signs the funding transaction with its own wallet, then Solrouter runs the 7-step mixer, the Jupiter swap, and the forward-to-destination pipeline. +Sometimes you do not want a model in the loop. You want to drive the privacy pipeline yourself in code. Mode B one-shot swaps do that. Your agent signs the funding transaction with its own wallet. Solrouter then runs the mixer round trip, the Jupiter swap, and the forward to the destination. ```typescript import { SolrouterAgentClient } from "@solrouter/agent-tools"; @@ -67,7 +56,7 @@ const session = await client.swapOneshot({ fromMint: "So11111111111111111111111111111111111111112", // SOL toMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC amount: "10000000", - destinationPubkey: "...", // fresh address — no on-chain link to payer + destinationPubkey: "...", // use a fresh address }); // Sign session.fundingTx with your wallet, broadcast, then: @@ -77,30 +66,30 @@ const settled = await client.pollUntilSettled(session.sessionId); ## Authentication modes -How your agent proves it can pay shapes how you deploy it, so pick the model that fits before you build. The Agent Privacy API supports two. +How your agent proves it can pay shapes how you deploy it, so pick the mode that fits before you build. The Agent Privacy API supports two. -* **API key** — Include `Authorization: Bearer sk_solrouter_...` in every request. Usage bills against your prepaid balance in USDC or `$ROUTER`. Best when your agent is long-lived and you've already funded an account. -* **x402 (keyless)** — Pay per call in USDC on Solana mainnet through the Coinbase facilitator, with no account at all. Discover pricing and payment endpoints at [`/.well-known/x402`](https://solrouter.com/.well-known/x402). +* **API key.** Include `Authorization: Bearer sk_solrouter_...` in every request. Usage bills against your prepaid balance in USDC or `$ROUTER`. Best when your agent is long-lived and you have already funded an account. +* **x402 (keyless).** Pay per call in USDC on Solana mainnet through an x402 facilitator, with no account at all. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`). The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. It does not show which facilitator settles payments. Discover pricing and payment endpoints at [`/.well-known/x402`](https://api.solrouter.com/.well-known/x402). - x402 is ideal for agents that operate without a pre-registered API key — for example, autonomous agents that spin up on demand and pay for exactly the calls they make. No balance top-up or account creation required. + x402 fits agents that run without a pre-registered API key, for example autonomous agents that start on demand and pay for exactly the calls they make. No balance top-up or account creation is required. The agent still holds a wallet private key to sign payments. ## Available tools -These are the building blocks your agent can call, whether through `aiSdkTools()` or the raw `TOOLS` / `callTool()` exports. Each one maps to a single step of the private-swap or encrypted-inference flow. - -| Tool | Description | -| ---------------------------- | ------------------------------------------------------ | -| `umbra_quote` | Quote a private swap with anonymity-set sizing | -| `umbra_anonymity_set` | Inspect current anonymity set for a mint pair | -| `umbra_swap_oneshot` | One-shot swap session — agent signs funding tx | -| `umbra_swap_oneshot_execute` | Submit signed funding tx to start execution | -| `umbra_session_status` | Poll session state until `settled` | -| `umbra_create_wallet` | Provision a managed Umbra wallet for an agent | -| `umbra_swap_managed` | Run a swap from a managed wallet | -| `umbra_encrypt` | Convert balance to encrypted balance on same wallet | -| `umbra_shield` | Mixer round-trip → withdraw → forward to fresh address | -| `umbra_balance` | Read encrypted balance of a managed wallet | -| `umbra_attestation` | Fetch the on-chain attestation PDA for a session | -| `private_inference_paid` | x402-paywalled encrypted inference (no API key) | +These are the building blocks your agent will call, through `aiSdkTools()` or the raw `TOOLS` / `callTool()` exports. Each one maps to one step of the private-swap or encrypted-inference flow. The Status column describes the API route behind the tool, not the package. + +| Tool | Description | Status | +| ---------------------------- | ------------------------------------------------------------------------------------ | ------ | +| `umbra_quote` | Quote a private swap with anonymity-set sizing | Live | +| `umbra_anonymity_set` | Inspect the current anonymity set for a mint pair | Live | +| `umbra_swap_oneshot` | Open a one-shot swap session. The agent signs the funding transaction | Soon | +| `umbra_swap_oneshot_execute` | Submit the signed funding transaction to start execution | Soon | +| `umbra_session_status` | Poll session state until `settled` | Soon | +| `umbra_create_wallet` | Provision a managed wallet for an agent | Soon | +| `umbra_swap_managed` | Run a swap from a managed wallet | Soon | +| `umbra_encrypt` | Convert a balance to an encrypted balance on the same wallet | Soon | +| `umbra_shield` | Mixer round trip, withdraw, then forward to a fresh address | Soon | +| `umbra_balance` | Read the encrypted balance of a managed wallet | Soon | +| `umbra_attestation` | Read the settlement record of a swap session (`GET /agents/v1/attestations/:sessionId`) | Soon | +| `private_inference_paid` | x402-paywalled encrypted inference, no API key | Live | diff --git a/content/docs/develop/authentication.mdx b/content/docs/develop/authentication.mdx index b941fd0..dbd7ffd 100644 --- a/content/docs/develop/authentication.mdx +++ b/content/docs/develop/authentication.mdx @@ -1,19 +1,21 @@ --- title: "Authentication" icon: KeyRound -description: "Solrouter uses bearer token authentication. Generate an API key at solrouter.com/sdk by connecting a Solana wallet — no email or KYC required." +description: "Get an API key by connecting a Solana wallet at solrouter.com/sdk. No email, no KYC. Send the key as a bearer token, or pay per call with x402." +status: live +checked: "2026-08-26" --- import { Callout } from 'fumadocs-ui/components/callout'; import { Step, Steps } from 'fumadocs-ui/components/steps'; -Most AI APIs make you sign up with an email, verify your identity, and hand over a credit card before you can send a single request. Solrouter skips all of that. You authenticate with an API key passed as a bearer token, and you get that key by connecting a Solana wallet — no email sign-up, no KYC (Know Your Customer identity checks), no card. +Most AI APIs make you sign up with an email, verify your identity, and add a credit card before your first request. Solrouter skips all of that. You authenticate with an API key sent as a bearer token. You get that key by connecting a Solana wallet. There is no email sign-up, no KYC (Know Your Customer identity check), and no card. -Fund a prepaid balance in USDC or \$ROUTER, and you're making calls in minutes. +Fund a prepaid balance in USDC or \$ROUTER, and you can make calls in minutes. ## Getting your API key -Here's the full path from zero to your first authenticated request — four steps, all in the browser. +Here is the full path from zero to your first authenticated request. It has four steps, all in the browser. @@ -25,19 +27,19 @@ Open [solrouter.com/sdk](https://solrouter.com/sdk) in your browser. ### Connect your Solana wallet -Connect any compatible Solana wallet (e.g. Phantom, Backpack, Solflare). Your wallet is your only identity credential — Solrouter collects no email and no personal information. +Connect Phantom, Solflare, or a Privy embedded wallet (any Wallet Standard wallet). Your wallet is your only identity credential. Solrouter collects no email and no personal information. ### Generate an API key -Click **Generate API Key**. Your key is issued immediately and starts with `sk_solrouter_...`. Copy it and store it somewhere safe — it won't be shown again. +Click **Generate API Key**. Your key is issued at once and starts with `sk_solrouter_...`. Copy it and store it somewhere safe. It is not shown again. ### Top up your balance -Add funds to your prepaid account in **USDC** or **\$ROUTER**. Solrouter meters every API call per request and deducts the cost from this balance, so there's no monthly bill — you pay only for what you use. +Add funds to your prepaid account in **USDC** or **\$ROUTER**. Solrouter meters every API call and deducts the cost from this balance. There is no monthly bill. You pay only for what you use. @@ -47,17 +49,20 @@ Once you have a key, you attach it to requests in one of two ways: through the S ### With the SDK -Pass your API key when you create the `SolRouter` client. From then on, the SDK attaches it to every request automatically — you never touch the header yourself. +Pass your API key when you create the `SolRouter` client. From then on, the SDK attaches it to every request. You never touch the header yourself. ```typescript import { SolRouter } from '@solrouter/sdk'; -const client = new SolRouter({ apiKey: 'sk_solrouter_...' }); +const client = new SolRouter({ + apiKey: 'sk_solrouter_...', + baseUrl: 'https://api.solrouter.com', +}); ``` ### Direct HTTP (REST API) -If you're not using the SDK, send the key yourself as a bearer token in the `Authorization` header. +If you are not using the SDK, send the key yourself as a bearer token in the `Authorization` header. ```bash curl -X POST "https://api.solrouter.com/agent" \ @@ -68,32 +73,34 @@ curl -X POST "https://api.solrouter.com/agent" \ ## Authentication tiers -Solrouter offers three ways to authenticate, each suited to a different kind of caller. Pick the row that matches how your code runs. +External callers have two ways to authenticate. Pick the row that matches how your code runs. -| Tier | How it works | Best for | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| **API key** | `Authorization: Bearer sk_solrouter_...`, billed from your prepaid balance | Most applications and development workflows | -| **x402 (keyless)** | Per-call USDC settlement on Solana mainnet via Coinbase facilitator. Service discovery at [`/.well-known/x402`](https://solrouter.com/.well-known/x402) | Autonomous agents that don't hold API keys | -| **Internal JWT** | Short-lived JWT issued to Solrouter's own first-party products | Solrouter-hosted products only; not available to external developers | +| Tier | How it works | Best for | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| **API key** | `Authorization: Bearer sk_solrouter_...`, billed from your prepaid balance | Most applications and development workflows | +| **x402 (keyless)** | Per-call USDC settlement on Solana mainnet | Autonomous agents that do not hold API keys | -The x402 tier is worth a closer look if you're building agents: instead of provisioning and storing a long-lived key, an agent pays for each call on the spot in USDC. That means it can authenticate and transact entirely on-chain, with no secret to leak. +x402 settles through a facilitator. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`). Service discovery is at [`/.well-known/x402`](https://api.solrouter.com/.well-known/x402). The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. The manifest does not show which facilitator settles payments. Wire-level facilitator: not determined. + +The x402 tier is worth a closer look if you build agents. Instead of storing a long-lived key, an agent pays for each call on the spot in USDC. x402 removes the API key, not the wallet key. The agent still holds a wallet private key to sign payments, so protect that key the same way. ## Keeping your API key safe Your key can spend real money, so treat it with the same care as a password. The practices below keep it out of the wrong hands. -* **Never commit your API key to source control.** Treat `sk_solrouter_...` like a password — keep it out of Git history, `.env` files that are checked in, and any public repository. +* **Never commit your API key to source control.** Treat `sk_solrouter_...` like a password. Keep it out of Git history, checked-in `.env` files, and any public repository. -* **Use environment variables.** Store your key in `SOLROUTER_API_KEY` and read it at runtime so the secret never lives in your code: +* **Use environment variables.** Store your key in `SOLROUTER_API_KEY` and read it at runtime, so the secret never lives in your code: ```typescript const client = new SolRouter({ - apiKey: process.env.SOLROUTER_API_KEY + apiKey: process.env.SOLROUTER_API_KEY, + baseUrl: 'https://api.solrouter.com', }); ``` -* **Rotate compromised keys immediately.** If a key is exposed, go to [solrouter.com/sdk](https://solrouter.com/sdk), revoke the affected key, and generate a new one. +* **Rotate compromised keys at once.** If a key is exposed, go to [solrouter.com/sdk](https://solrouter.com/sdk), delete the affected key, and generate a new one. - Never expose your API key in client-side code or public repositories. Anyone with your key can spend your prepaid balance. If you suspect a key has been leaked, rotate it immediately at solrouter.com/sdk. + Never expose your API key in client-side code or public repositories. Anyone with your key can spend your prepaid balance. If you suspect a key has leaked, rotate it at once at solrouter.com/sdk. diff --git a/content/docs/develop/mcp-server.mdx b/content/docs/develop/mcp-server.mdx index e077ec0..2ce9a7c 100644 --- a/content/docs/develop/mcp-server.mdx +++ b/content/docs/develop/mcp-server.mdx @@ -1,20 +1,23 @@ --- title: "MCP Server" icon: Plug -description: "Use Solrouter's encrypted AI and private swap tools directly from Claude Desktop or Cursor by installing the @solrouter/mcp-server MCP integration." +description: "Use Solrouter's encrypted chat and Agent Privacy API tools from Claude Desktop or Cursor with @solrouter/mcp-server. Some tool calls leave your machine in plaintext." +status: mixed +checked: "2026-08-26" +statusNote: "The server is on npm. Encrypted chat is live. Swap execution tools are Soon. Read the status word beside each tool." --- import { Callout } from 'fumadocs-ui/components/callout'; -You already work inside Claude Desktop or Cursor. The Solrouter MCP server lets you run Solrouter's encrypted AI and Agent Privacy API tools right there — no separate app, no copy-paste between windows. +You already work inside Claude Desktop or Cursor. The Solrouter MCP server lets you run Solrouter's encrypted chat and Agent Privacy API tools right there. You need no separate app. -MCP (Model Context Protocol — an open standard for connecting AI clients to external tools) is the bridge. Once you wire up the server, every tool call — research, token analysis, private swaps — runs through the same end-to-end encrypted pipeline as the SDK. Your queries stay private even though they start in your editor or desktop assistant. +MCP (Model Context Protocol, an open standard for connecting AI clients to external tools) is the bridge. The server runs on your machine as `npx @solrouter/mcp-server`. It holds your API key and encrypts prompts for the `encrypted_chat` tool and for the AI synthesis step of the research tools. Other calls leave your machine in plaintext. The tables below say which. ## Configuration This is the one-time setup that registers Solrouter as a tool provider in your client. -Add the following block to your MCP configuration file. For **Claude Desktop**, that file is `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS (or the equivalent path on Windows). For **Cursor**, add it under `mcpServers` in your Cursor settings JSON (`~/.cursor/mcp.json`). +Add the following block to your MCP configuration file. For **Claude Desktop**, that file is `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS (or the equivalent path on Windows). For **Cursor**, add it under `mcpServers` in `~/.cursor/mcp.json`. ```json { @@ -23,47 +26,61 @@ Add the following block to your MCP configuration file. For **Claude Desktop**, "command": "npx", "args": ["@solrouter/mcp-server"], "env": { - "SOLROUTER_API_KEY": "sk_solrouter_..." + "SOLROUTER_API_KEY": "sk_solrouter_...", + "SOLROUTER_API_URL": "https://api.solrouter.com" } } } } ``` -Save the config, then restart Claude Desktop or reload the Cursor window. The Solrouter tools appear in the tool list automatically — nothing else to install. +Set `SOLROUTER_API_URL`. Without it, the server defaults to a Render host, not `api.solrouter.com`. Set `BRAVE_API_KEY` too: `private_research` and `private_token_analysis` fail without it. `HELIUS_RPC_URL` is optional (default: `https://api.mainnet-beta.solana.com`). + +Save the config, then restart Claude Desktop or reload the Cursor window. The Solrouter tools appear in the tool list. There is nothing else to install. ## Privacy and research tools -Use these tools when you want encrypted AI inference or on-chain research from inside your client. +Use these tools for encrypted AI inference or on-chain research from inside your client. Only the AI step is encrypted. The data-gathering steps call third parties directly from your machine in plaintext. + +| Tool | Description | Leaves your machine in plaintext | +| --- | --- | --- | +| `encrypted_chat` (Live) | Encrypted AI query through `/tee/process` | Nothing. The prompt is encrypted on your machine | +| `private_research` (Live) | Web search, DEX data, and on-chain lookups, then an encrypted AI synthesis | Query to Brave; token symbols to DexScreener; wallet addresses to the Solana RPC | +| `private_token_analysis` (Live) | DEX data, price, and web results for one token, then an encrypted AI synthesis | The token to DexScreener, CoinGecko, and Brave | +| `private_wallet_audit` (Live) | Holdings of one wallet, then an encrypted AI synthesis | The wallet address to the Solana RPC and DexScreener | +| `list_models` (Live) | Models from `GET /api/v1/models` with pricing | Nothing sensitive | +| `account_balance` (Live) | Your credit balance from `GET /api/v1/balance` | Nothing sensitive | + +## Agent and swap helper tools -| Tool | Description | -| ------------------------ | --------------------------------------------------------------------- | -| `private_research` | Encrypted multi-source research (web + DEX + on-chain + AI synthesis) | -| `encrypted_chat` | Direct E2E encrypted AI query | -| `private_token_analysis` | Comprehensive encrypted token research | -| `private_wallet_audit` | Encrypted wallet intelligence | -| `list_models` | Available models with pricing | -| `account_balance` | USDC + `$ROUTER` credit balance | +These tools call the Solrouter backend in plaintext or return text without any network call. + +| Tool | Description | Leaves your machine in plaintext | +| --- | --- | --- | +| `agent_run` (Live) | Tool-augmented agent completion through `POST /agent` (web search, Solana data, paid APIs) | Your prompt, to the Solrouter backend | +| `umbra_describe` (Live) | Explains private swaps. Makes no network call | Nothing | +| `umbra_anonymity_stats` (Soon) | Deposit count for one denomination bucket from `GET /umbra/anonymity-set` | The bucket and network | +| `umbra_initiate_private_swap_widget` (Soon) | Returns a directive that tells the Solrouter web chat to show the swap widget. Executes nothing | Suggested tokens and network | ## Agent Privacy API tools -When you need to move funds privately — quoting, executing, and managing swaps — reach for these. They expose the full Solrouter Agent Privacy API, including private swap execution and managed wallet operations. - -| Tool | Description | -| ---------------------------- | ------------------------------------------------------ | -| `umbra_quote` | Quote a private swap with anonymity-set sizing | -| `umbra_anonymity_set` | Inspect current anonymity set for a mint pair | -| `umbra_swap_oneshot` | One-shot swap session — agent signs funding tx | -| `umbra_swap_oneshot_execute` | Submit signed funding tx to start execution | -| `umbra_session_status` | Poll session state until `settled` | -| `umbra_create_wallet` | Provision a managed Umbra wallet for an agent | -| `umbra_swap_managed` | Run a swap from a managed wallet | -| `umbra_encrypt` | Convert balance to encrypted balance on same wallet | -| `umbra_shield` | Mixer round-trip → withdraw → forward to fresh address | -| `umbra_balance` | Read encrypted balance of a managed wallet | -| `umbra_attestation` | Fetch the on-chain attestation PDA for a session | -| `private_inference_paid` | x402-paywalled encrypted inference (no API key) | +These tools wrap `POST` and `GET /agents/v1/*` over HTTPS with your API key. Swap parameters travel in plaintext to the Solrouter backend. Swap execution is Soon. Tools marked Soon can move real funds once they go live, so read the session state before you act on it. + +| Tool | Description | +| --- | --- | +| `umbra_quote` (Live) | Quote a private swap with anonymity-set sizing | +| `umbra_anonymity_set` (Live) | Inspect the current anonymity set for a mint pair | +| `umbra_swap_oneshot` (Soon) | Open a one-shot swap session. The agent signs the funding transaction | +| `umbra_swap_oneshot_execute` (Soon) | Submit the signed funding transaction to start execution | +| `umbra_session_status` (Soon) | Poll session state until `settled` | +| `umbra_create_wallet` (Soon) | Provision a managed wallet for an agent | +| `umbra_swap_managed` (Soon) | Run a swap from a managed wallet | +| `umbra_encrypt` (Soon) | Convert a balance to an encrypted balance on the same wallet | +| `umbra_shield` (Soon) | Mixer round trip, withdraw, then forward to a fresh address | +| `umbra_balance` (Soon) | Read the encrypted balance of a managed wallet | +| `umbra_attestation` (Soon) | Read the settlement record of a swap session (`GET /agents/v1/attestations/:sessionId`) | +| `private_inference_paid` (Live) | x402-paywalled encrypted inference, no API key. You supply the encrypted prompt yourself | - Every request through the MCP server is end-to-end encrypted with the same Arcium RescueCipher + Intel TDX pipeline as the Privacy SDK. Your prompt starts in Claude Desktop or Cursor, but Solrouter's backend never sees the plaintext — only the enclave does. + Not every request through the MCP server is encrypted. `encrypted_chat` and the AI synthesis step of the three research tools use the Privacy SDK path: RescueCipher and Intel TDX. The Solrouter backend never sees that plaintext. Web search (Brave), DexScreener, CoinGecko, and Solana RPC lookups go from your machine to those third parties in plaintext. `agent_run` and the `umbra_*` tools send their inputs to the Solrouter backend in plaintext. diff --git a/content/docs/develop/privacy-sdk.mdx b/content/docs/develop/privacy-sdk.mdx index 4f088fd..c42edf2 100644 --- a/content/docs/develop/privacy-sdk.mdx +++ b/content/docs/develop/privacy-sdk.mdx @@ -1,15 +1,19 @@ --- title: "Privacy SDK" icon: Code -description: "Add end-to-end encrypted AI to any app in minutes. Install @solrouter/sdk, pass your API key, and call client.chat() — encryption handled automatically." +description: "Add encrypted AI calls to any app. Install @solrouter/sdk, pass your API key, and call client.chat(). The SDK encrypts the prompt on your machine." +status: live +checked: "2026-08-26" +statusNote: "The SDK on npm (1.1.0) types gpt-oss-20b only. Other catalog ids pass through with a type cast. The model table below shows the status of each id." --- import { Callout } from 'fumadocs-ui/components/callout'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; +import { EncryptionFlow } from '@/components/diagrams/encryption-flow'; -Most AI APIs read your prompts in the clear. The Solrouter Privacy SDK is built so that no one — not even Solrouter — can. It handles encryption for you, so you never have to wire up cryptography yourself. +Most AI APIs read your prompts in the clear. The Solrouter Privacy SDK encrypts your prompt before it leaves your machine, so the Solrouter backend cannot read it. The SDK handles the cryptography for you. -Here is what happens when you call `client.chat()`. The SDK fetches the attested X25519 public key from the TEE (Trusted Execution Environment — hardware that isolates code and data even from the machine's owner), then encrypts your prompt on your machine using Arcium's RescueCipher. It sends the encrypted blob through the Solrouter backend, which routes it blindly — the backend cannot decrypt it. The response comes back encrypted and the SDK decrypts it with your ephemeral session key. Your plaintext prompt and response exist only on your machine and inside the Intel TDX enclave — nowhere else. +Here is what happens when you call `client.chat()`. The SDK fetches the enclave's X25519 public key from `GET /tee/public-key`. It does not fetch or verify the attestation quote. That check is a manual step. See the [attestation guide](/docs/concepts/attestation). The SDK then encrypts your prompt on your machine with Arcium's RescueCipher. It sends the encrypted blob through the Solrouter backend, which forwards it without decrypting it. The TEE (Trusted Execution Environment, a CPU-isolated confidential VM) decrypts the prompt and calls the model on a Nosana GPU node. The node runs the model outside the enclave, so it sees the prompt and the reply in plaintext during inference. The reply comes back encrypted, and the SDK decrypts it with your session key. ## Installation @@ -39,49 +43,85 @@ This section walks through the calls you will make most often, starting with the ### Encrypted chat (default) -You get end-to-end encryption with zero setup — every call is encrypted unless you opt out. Instantiate `SolRouter` with your API key and start chatting. +Every call is encrypted unless you opt out. Create a `SolRouter` client with your API key and the production `baseUrl`, then start chatting. ```typescript import { SolRouter } from '@solrouter/sdk'; -const client = new SolRouter({ apiKey: 'sk_solrouter_...' }); +const client = new SolRouter({ + apiKey: 'sk_solrouter_...', + baseUrl: 'https://api.solrouter.com', +}); -// Encrypted end-to-end. Solrouter backend never sees plaintext. +// Encrypted on your machine. The Solrouter backend never sees plaintext. const response = await client.chat('What are the risks of this DeFi protocol?'); console.log(response.message); ``` + + Set `baseUrl` in every client. Without it, SDK 1.1.0 defaults to a Render host, not `api.solrouter.com`. + + ### Choosing a model -Solrouter runs two self-hosted models. Pick one with the `model` option; leave it out to use the default. +Solrouter runs self-hosted open-weight models on Nosana GPU nodes. The backend catalog has three ids. The SDK type allows two strings. At runtime any other string passes through unchanged. + +| SDK string | Backend id | Status | Note | +| ------------- | --------------- | -------- | ---------------------------------------------------------------- | +| `gpt-oss-20b` | `gpt-oss:20b` | Live | Default. Use this with SDK 1.1.0. | +| `qwen3-8b` | `qwen3:8b` | Archived | Alias of a retired node. Do not use. | +| none yet | `qwen3.8:27b` | Live | Reachable with a type cast: `model: 'nosana:qwen3.8:27b' as any`. A typed alias needs a new SDK release. | +| none yet | `gemma4:31b` | Soon | Encrypted path not confirmed end to end. | + +A new SDK release with the current model map is Soon. ```typescript const response = await client.chat('Summarize the latest Solana validator outage', { - model: 'gpt-oss-20b', // gpt-oss-20b (default) | qwen3-8b + model: 'gpt-oss-20b', // the only typed live model string in SDK 1.1.0 }); ``` -### Opt out of encryption (faster, plaintext) +After an idle period, a node can answer with a retryable "warming up" error. Wait a moment and send the request again. + +### Opt out of encryption (plaintext) -Encryption adds a little latency. When a query isn't sensitive and you want it back faster, disable client-side encryption for that single call. +You can turn off client-side encryption for one call. The prompt then goes to the Solrouter backend in plaintext. The backend reads it and routes it to the same self-hosted Nosana models. No proprietary model is reachable this way. ```typescript const response = await client.chat('Hello', { encrypted: false }); ``` -### SERV-guided reasoning (agent path) + -For questions that need structured analysis rather than a single freeform answer, route through the agent endpoint. Setting `reasoning: 'braid'` runs your request through SERV guided reasoning — a deterministic execution graph that replaces freeform LLM decision-making with structured, tool-augmented steps. +In words, with `encrypted: true` (the default): + +- Your device encrypts the prompt with RescueCipher and an X25519 shared secret. +- The Solrouter backend forwards the ciphertext. It cannot read it. +- The TEE decrypts the prompt and calls the model at the configured Nosana endpoint URL. The node sees the prompt in plaintext. +- The reply comes back encrypted to your session key. + +In words, with `encrypted: false`: + +- Your device sends the prompt as plaintext. +- The Solrouter backend reads the prompt and routes it to the same Nosana model. +- The TEE is not used. No on-chain receipt is created. +- The reply comes back in plaintext. + +### Guided reasoning (BRAID, agent path) + +For questions that need structured analysis, route through the agent endpoint. Setting `reasoning: 'braid'` runs your request through BRAID guided reasoning. BRAID walks a fixed Guided Reasoning Diagram (GRD) of tool steps, then makes one synthesis call to the model. Older material calls this SERV. + +This path is plaintext. The SDK sends the prompt to `POST /agent` without encryption and the response reports `encrypted: false`. ```typescript const response = await client.chat('Compare Marginfi vs Kamino lending on Solana', { - reasoning: 'braid', // routes through agent endpoint with guided reasoning + reasoning: 'braid', // plaintext path through the agent endpoint }); ``` ### Check balance -You pay per call from a prepaid balance. Check what's left at any time. +You pay per call from a prepaid balance. Check what is left at any time. ```typescript const { balance, balanceFormatted } = await client.getBalance(); @@ -91,16 +131,23 @@ const { balance, balanceFormatted } = await client.getBalance(); Each of these options goes in the object you pass as the second argument to `client.chat()`. -| Option | Type | Default | Description | -| ----------- | ------- | ------------- | ------------------------------------------ | -| `model` | string | `gpt-oss-20b` | Model to use: `gpt-oss-20b` or `qwen3-8b` | -| `encrypted` | boolean | `true` | Enable/disable client-side encryption | -| `reasoning` | string | — | Set to `'braid'` for SERV-guided reasoning | +On the encrypted path, this leaves your machine: the ciphertext bundle (`ciphertext`, `nonce`, `publicKey`, `version`), plus in plaintext your API key, the model id, `chatId`, and any `systemPrompt`, `useRAG`, `ragCollection`, or `useLiveSearch` you set. The backend forwards only the bundle and the model id to the CVM. + +| Option | Type | Default | Description | +| --------------- | ------- | ------------- | ------------------------------------------------------------------------------------ | +| `model` | string | `gpt-oss-20b` | Model string. Use `gpt-oss-20b` with SDK 1.1.0. `qwen3-8b` maps to a retired node. Other catalog ids pass through with a type cast. | +| `encrypted` | boolean | `true` | Turn client-side encryption on or off for this call | +| `reasoning` | string | none | Set to `'braid'` for guided reasoning. This path is plaintext. | +| `chatId` | string | none | Sent in plaintext to the backend. Not forwarded to the CVM. | +| `systemPrompt` | string | none | Sent in plaintext to the backend. Dropped before the CVM, so it never reaches the model. | +| `useRAG` | boolean | none | Sent in plaintext to the backend and ignored on the encrypted path. | +| `ragCollection` | string | none | Sent in plaintext to the backend and ignored on the encrypted path. | +| `useLiveSearch` | boolean | none | Sent in plaintext to the backend and ignored on the encrypted path. | ## No KYC required -Privacy starts at sign-up: there's nothing to identify you. You don't need an email address, credit card, or any personal information to use the SDK. Connect your Solana wallet at [solrouter.com/sdk](https://solrouter.com/sdk), generate an API key, and top up your balance in USDC or `$ROUTER`. Pricing is metered per call from your prepaid balance. +Privacy starts at sign-up. You do not need an email address, a credit card, or any personal information to use the SDK. Connect your Solana wallet at [solrouter.com/sdk](https://solrouter.com/sdk), generate an API key, and top up your balance in USDC or `$ROUTER`. Pricing is metered per call from your prepaid balance. - Solrouter runs only self-hosted, open-weight models (`gpt-oss:20b` and `qwen3:8b`) on the Nosana decentralized GPU network. There are no third-party model APIs — no OpenAI, Anthropic, or Google. Your prompts and documents never leave to an external model provider. + Solrouter runs only self-hosted, open-weight models on the Nosana decentralized GPU network. The catalog ids are `gpt-oss:20b` (Live), `qwen3.8:27b` (Live), and `gemma4:31b` (Soon). There are no third-party model APIs: no OpenAI, Anthropic, or Google. Your prompts never go to an external model provider. The Nosana node that runs the model does see the prompt in plaintext during inference. Solrouter does not control that hardware. diff --git a/content/docs/develop/private-swaps.mdx b/content/docs/develop/private-swaps.mdx index d52aaf4..0e4c72c 100644 --- a/content/docs/develop/private-swaps.mdx +++ b/content/docs/develop/private-swaps.mdx @@ -1,7 +1,10 @@ --- title: "Private Swaps" icon: ArrowRightLeft -description: "The /agents/v1 API lets AI agents execute privacy-preserving token swaps on Solana through two modes: managed wallets and one-shot transactions." +description: "The Agent Privacy API (/agents/v1) gives AI agents private token swaps on Solana in two modes, plus pay-per-call encrypted inference over x402." +status: mixed +checked: "2026-08-26" +statusNote: "Swap execution is Soon: no mainnet swap run is on record. Discovery, quote, and anonymity-set reads are Live." --- import { Callout } from 'fumadocs-ui/components/callout'; @@ -9,49 +12,71 @@ import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; When an autonomous agent moves tokens on Solana, the transaction graph exposes who paid whom. Anyone can trace the link between the funding source and the destination. The Agent Privacy API (`/agents/v1`) exists to break that link. -This is a dedicated, agent-first surface for two things: privacy-preserving on-chain actions and encrypted inference. The main Solrouter SDK covers encrypted chat and research; this API is purpose-built for agents that need to swap tokens privately, and for keyless agents that pay per inference call via x402 (a pay-per-request HTTP standard) instead of holding a pre-funded API key. +This is a dedicated, agent-first surface for two things: privacy-preserving on-chain actions and encrypted inference. The main Solrouter SDK covers encrypted chat and research. This API serves two kinds of agent. One needs to swap tokens privately. The other has no API key and pays per inference call with x402, a pay-per-request HTTP standard. -Every action here runs through the same Intel TDX-isolated enclave — hardware that keeps code and data sealed even from the machine's owner — and the same Arcium-encrypted transport as the rest of Solrouter's infrastructure. +The Solrouter backend and a swap worker run the swaps. The TEE (trusted execution environment, a hardware-isolated enclave) does not. Only the x402 inference endpoint on this page uses the encrypted TEE path. + + + No sanctions screening runs today. Agents are responsible for their own compliance. + + +## Feature status + +| Feature | Status | +| --- | --- | +| Discovery documents (`/.well-known/*`, `/agents/v1/openapi.json`, `/agents/v1/capabilities`) | Live | +| `GET /agents/v1/quote` and `GET /agents/v1/anonymity-set` | Live | +| Mode A managed-wallet swaps | Soon | +| Mode B one-shot swaps | Soon | +| `POST /api/v1/x402/chat/completions` | Live | +| `@solrouter/agent-tools` npm package | Soon | ## Execution modes How you run a private swap depends on one question: does your agent keep its own funded wallet with Solrouter, or does it sign each operation on the fly? The API supports both. Pick the mode that matches how your agent already works. - - - Use this mode when you want to fund once and forget the setup. Your agent provisions a long-lived encrypted-balance wallet through the API, funds it a single time, then runs as many private swaps as it needs — no repeated wallet provisioning. + + Both swap modes are Soon. The code path exists, but no mainnet swap run is on record as of 2026-08-26. The samples below show the request shapes the routes accept today. + + + + + Use this mode when you want to fund once and forget the setup. Your agent provisions a long-lived managed Umbra wallet through the API, funds it a single time, then runs as many private swaps as it needs. No repeated wallet provisioning. **How custody works:** - * The per-wallet Data Encryption Key (DEK) is envelope-encrypted with a KMS-held Key Encryption Key (KEK) - * The DEK is never persisted in plaintext — it exists only inside the enclave during an active operation - * Your agent interacts with the wallet through authenticated API calls; the underlying key material never leaves the TEE + * Solrouter holds the wallet keypair. The per-wallet Data Encryption Key (DEK) is wrapped with a Key Encryption Key (KEK) that the backend reads from `WALLET_VAULT_KEK`. + * The DEK is unwrapped inside the backend API and the swap worker for the duration of an operation, then wiped. + * The wallet vault runs in the backend process, not inside a TEE. **When to use it:** agents that run frequent swaps, need to accumulate balance over time, or operate on a recurring schedule. The managed wallet removes the overhead of signing a new funding transaction on every operation. - ```typescript - import { SolrouterAgentClient } from "@solrouter/agent-tools"; - - const client = new SolrouterAgentClient({ - apiKey: process.env.SOLROUTER_API_KEY, - }); - - // Provision a managed wallet for this agent - const wallet = await client.createWallet(); - - // Run a swap from the managed wallet — no funding tx required - // (walletId is the first positional argument) - const swap = await client.swapManaged(wallet.walletId, { - fromMint: "So11111111111111111111111111111111111111112", // SOL - toMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC - amount: "10000000", - destinationPubkey: "YOUR_FRESH_DESTINATION_ADDRESS", - }); + Wallet routes require an API key. + + ```bash + # Provision a managed wallet for this agent + curl -X POST "https://api.solrouter.com/agents/v1/wallets" \ + -H "Authorization: Bearer sk_solrouter_..." \ + -H "Content-Type: application/json" \ + -d '{}' + # -> { "walletId": "...", "umbraAddress": "...", "network": "mainnet", "fundingHint": "..." } + + # Fund umbraAddress, then run a swap from the managed wallet + curl -X POST "https://api.solrouter.com/agents/v1/wallets/WALLET_ID/swap" \ + -H "Authorization: Bearer sk_solrouter_..." \ + -H "Content-Type: application/json" \ + -d '{ + "fromMint": "So11111111111111111111111111111111111111112", + "toMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "amount": "10000000", + "destinationPubkey": "YOUR_FRESH_DESTINATION_ADDRESS" + }' + # -> { "sessionId": "...", "status": "running", "estimatedSeconds": 70 } ``` - - Use this mode when your agent already has its own wallet and you'd rather not hold a balance with Solrouter. Nothing is provisioned: your agent receives an unsigned funding transaction, signs it with its own wallet, submits the signature, and the orchestrator handles the rest. + + Use this mode when your agent already has its own wallet and you would rather not hold a balance with Solrouter. Nothing is provisioned: your agent receives an unsigned funding transaction, signs it with its own wallet, submits the signature, and the worker handles the rest. **The 7-step pipeline:** @@ -59,66 +84,79 @@ How you run a private swap depends on one question: does your agent keep its own 2. API returns an unsigned funding transaction 3. Agent signs the transaction with its own wallet and broadcasts it 4. Agent submits the transaction signature to the API to start execution - 5. Orchestrator runs the mixer round-trip to break the on-chain link - 6. Jupiter aggregator executes the swap at best available price - 7. Proceeds are forwarded to the destination address — with no on-chain connection to the original payer - - ```typescript - import { SolrouterAgentClient } from "@solrouter/agent-tools"; - - const client = new SolrouterAgentClient({ apiKey: "sk_solrouter_..." }); - - const session = await client.swapOneshot({ - payerPubkey: "YOUR_AGENT_WALLET_PUBKEY", - fromMint: "So11111111111111111111111111111111111111112", // SOL - toMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", // USDC - amount: "10000000", - destinationPubkey: "FRESH_DESTINATION_ADDRESS", - }); - - // Sign session.fundingTx with your wallet and broadcast - // Then submit the confirmed signature: - await client.swapOneshotExecute(session.sessionId, fundingTxSig); - - // Poll until the full pipeline settles - const settled = await client.pollUntilSettled(session.sessionId); + 5. Worker runs the Umbra mixer round-trip to break the on-chain link + 6. Jupiter aggregator executes the swap + 7. Worker forwards the proceeds to the destination address, with no on-chain connection to the original payer + + ```bash + # 1. Create the session + curl -X POST "https://api.solrouter.com/agents/v1/swaps/oneshot" \ + -H "Content-Type: application/json" \ + -d '{ + "payerPubkey": "YOUR_AGENT_WALLET_PUBKEY", + "fromMint": "So11111111111111111111111111111111111111112", + "toMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "amount": "10000000", + "destinationPubkey": "FRESH_DESTINATION_ADDRESS" + }' + # -> { "sessionId": "...", "ephemeralPubkey": "...", "fundingTx": "", "expectedSeconds": 75, ... } + + # 2. Sign fundingTx with your wallet and broadcast it. + # 3. Submit the confirmed signature: + curl -X POST "https://api.solrouter.com/agents/v1/swaps/oneshot/SESSION_ID/execute" \ + -H "Content-Type: application/json" \ + -d '{"fundingTxSig": "..."}' + + # 4. Poll until state is settled + curl "https://api.solrouter.com/agents/v1/sessions/SESSION_ID" ``` **When to use it:** stateless agents, single-operation workflows, or any agent that already manages its own wallet and prefers not to maintain a separate funded balance with Solrouter. +**What Solrouter stores per session:** `from_mint`, `to_mint`, `amount_base_units`, `destination_pubkey`, `payer_user_id`, `ephemeral_pubkey`, the wrapped ephemeral key, `final_tx_sig`, and `actual_out`. Solrouter's backend can read every one of these columns. Retention period: not published. + ## Discovery endpoints -So your agent doesn't have to hardcode URLs, the API publishes its own configuration. A2A-compatible agents (the Agent-to-Agent interop protocol) and x402-aware runtimes read these endpoints to self-configure at runtime. +So your agent does not have to hardcode URLs, the API publishes its own configuration. A2A-compatible agents (the Agent-to-Agent interop protocol) and x402-aware runtimes read these endpoints to self-configure at runtime. -| Endpoint | Description | -| ------------------------------ | ---------------------------------------------------------------- | -| `/.well-known/agent-card.json` | A2A protocol v1.0 card with the full skill list | -| `/.well-known/x402` | x402 paywall manifest — per-call USDC pricing for keyless agents | -| `/agents/v1/openapi.json` | Full OpenAPI 3.1 specification | -| `/agents/v1/capabilities` | Capability summary for runtime introspection | +| Endpoint | Description | Status | +| ------------------------------ | ----------------------------------------------------------------- | ------ | +| `/.well-known/agent-card.json` | A2A protocol v1.0 card with the full skill list | Live | +| `/.well-known/x402` | x402 paywall manifest: per-call USDC pricing for keyless agents | Live | +| `/agents/v1/openapi.json` | Full OpenAPI 3.1 specification | Live | +| `/agents/v1/capabilities` | Capability summary for runtime introspection | Live | + +The manifest is served by the API host: `https://api.solrouter.com/.well-known/x402`. ## x402 encrypted inference -Not every agent has an API key, and account creation is friction you may not want. For those cases Solrouter exposes a pay-per-call encrypted inference endpoint built on the pay.sh x402 standard: your agent pays in USDC on Solana mainnet, with no account and no key management. +Not every agent has an API key, and account creation is friction you may not want. For those cases Solrouter exposes a pay-per-call encrypted inference endpoint. Your agent pays in USDC on Solana mainnet, with no account and no key management. + +x402 is the standard. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. It does not show which facilitator settles payments. pay.sh is a catalog that lists the endpoint. * **Endpoint:** `POST /api/v1/x402/chat/completions` * **Pricing:** \$0.005 per call, settled via x402 USDC on Solana mainnet -* **Encryption:** Arcium-encrypted prompt in, encrypted response out — the same TEE-isolated path as the SDK -* **Discovery:** `/.well-known/x402` — any x402-aware agent runtime can auto-discover pricing and payment instructions +* **Encryption:** Arcium-encrypted prompt in, encrypted response out. The same TEE path as the SDK. +* **Discovery:** `/.well-known/x402`. Any x402-aware agent runtime can auto-discover pricing and payment instructions. ```bash -# x402 paywalled encrypted inference — no API key needed. -# `encryptedPrompt` MUST be an Arcium ciphertext produced client-side -# (use @solrouter/sdk's encryptPrompt() helper); `model` is required. +# x402 paywalled encrypted inference. No API key needed. +# `encryptedPrompt` MUST be an Arcium ciphertext produced client-side. +# Use encrypt(message, baseUrl) and packageForTEE(encryptedData) from @solrouter/sdk. +# `model` is required. curl -X POST "https://api.solrouter.com/api/v1/x402/chat/completions" \ -H "Content-Type: application/json" \ -d '{"encryptedPrompt": "", "model": "gpt-oss:20b"}' ``` -When an x402-aware runtime calls this endpoint, it negotiates and settles payment for you automatically. Call it directly without completing the payment handshake and the server replies with `402 Payment Required`, returning the payment terms in the `X-Payment` header so you can pay and retry. +Call the endpoint without payment and the server replies `402 Payment Required` with the price, the network, and the `payTo` address. An x402-aware runtime signs a USDC payment payload with the agent's wallet key and retries with the `X-PAYMENT` header. Solrouter's server sends that payload to its facilitator to verify and settle the transfer, then returns 200. The agent never talks to the facilitator. + + + The paywall charges \$0.005 per call. The response body of this endpoint currently reports `paid.amount: 0.02`. This is a backend follow-up; the manifest price is the one charged. + - Each privacy-mode session can publish a Program Derived Address (PDA) on Solana mainnet, anchored to the Solrouter encryption-attestation program at `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`. The PDA links this specific request to the attested TEE, providing on-chain proof that the interaction was processed inside a verified Intel TDX enclave — something you or any third party can verify independently. + This endpoint returns the encrypted reply only. It does not commit an on-chain receipt. Receipts are created for `POST /tee/process`, the route the SDK uses. See [Encryption Proof](/docs/concepts/encryption-proof). diff --git a/content/docs/index.mdx b/content/docs/index.mdx index 2a19c54..ae063b9 100644 --- a/content/docs/index.mdx +++ b/content/docs/index.mdx @@ -1,11 +1,15 @@ --- title: "Introduction" icon: Sparkles -description: Solrouter is a cryptographically private AI layer — prompts are encrypted client-side and processed in an Intel TDX enclave, so no one can read your data. +description: "Solrouter is a private AI layer for Solana developers. Your prompt is encrypted on your device, and the Solrouter backend never sees it in plaintext." +status: mixed +checked: "2026-08-26" +statusNote: "The product cards carry their own status. The Agent Tools SDK is Soon because @solrouter/agent-tools is not on npm yet." --- import { Cards, Card } from 'fumadocs-ui/components/card'; import { Callout } from 'fumadocs-ui/components/callout'; +import { TypicalVsSolrouter } from '@/components/diagrams/typical-vs-solrouter'; import { Shield, Bot, @@ -19,99 +23,128 @@ import { Link2, } from 'lucide-react'; -Solrouter is a cryptographically private AI infrastructure layer for Solana -developers. The idea is simple: your prompts, documents, and responses should -never exist in plaintext anywhere we could read them. So they don't — not on the -wire, not on our backend, and not in any log file. Plaintext lives only inside a -hardware-isolated enclave. Privacy here is enforced by math, not by a promise. +Solrouter is a private AI infrastructure layer for Solana developers. The idea +is simple: your prompts and responses should never exist in plaintext anywhere +we could read them. On the encrypted path, your prompt is encrypted on your +device. It stays encrypted through our backend and is decrypted only inside a +hardware-isolated enclave. The SDK uses this path by default. The chat app uses +it when you turn on Maximum Privacy Mode. ## Why Solrouter? Start with the problem. Today, every AI request you send is something the provider can read, store, and analyze. -Mainstream providers keep your prompts indefinitely, ask for personal details to -sign up, and back their privacy claims with a terms-of-service page. Nothing -technical stops them from reading what you send — you're trusting a policy. +Mainstream providers can keep your prompts, ask for personal details at sign-up, +and back their privacy claims with a terms-of-service page. Nothing technical +stops them from reading what you send. You trust a policy. -Solrouter removes the need for that trust. We built the system so that **we -cannot see your data** in the first place. +Solrouter removes the need for that trust on the encrypted path. We built the +system so that **our backend cannot read your prompt or the reply**. -Here's how. Your prompt is encrypted on your device before it leaves your browser -or app. Our backend only ever receives an opaque encrypted blob and forwards it, -blindly, to an Intel TDX Confidential VM — a TEE (Trusted Execution Environment: -hardware that isolates code and data so even the machine's operator can't inspect -it at runtime). Plaintext appears only inside that attested enclave, which even -we cannot read. +Here is how. Your prompt is encrypted on your device before it leaves your +browser or app. Our backend receives only an encrypted blob and forwards it to +an Intel TDX Confidential VM. This is a TEE (Trusted Execution Environment: +hardware that isolates code and data so even the machine's operator cannot +inspect it at runtime). The enclave decrypts your prompt and sends it to an +open-weight model on a Nosana GPU node. Solrouter's backend never sees the +plaintext, and the request is not linked to your identity on the node. + + + +**In words** + +- With a typical AI API, your prompt travels as readable text to the provider's server. That server can read it, store it, and train on it. +- With Solrouter and encryption on, your device encrypts the prompt before it leaves. +- The Solrouter backend passes the sealed message along. It sees the ciphertext and your wallet or key, never the text. +- Only the enclave holds the key that opens it. The enclave sends the readable text to a Nosana GPU node, which runs the model and sees the prompt while it works. +- The enclave encrypts the reply again, so only your device can read it. + +## Start here + + + The sealed room, in plain words, with a picture. + Who can see what, cell by cell. + Paste a lock link and watch the check pass. + What people, developers, agents, traders, and teams do with it. + In privacy mode, Solrouter does not use OpenAI, Anthropic, Google, or any - third-party model provider. All private inference runs on self-hosted, - open-weight models on the Nosana decentralized GPU network. Your prompts never - reach an external model API. + other third-party model provider. All private inference runs on self-hosted, + open-weight models on the Nosana decentralized GPU network. The Nosana node + that runs the model can see your prompt during inference. Solrouter does not + control that hardware. ## Products -Pick the surface that fits how you work — an SDK to embed in your app, typed +Pick the surface that fits how you work: an SDK to embed in your app, typed agent tools, an MCP server for your editor, or the hosted chat app. They all run on the same private backend. } title="Privacy SDK" href="/docs/develop/privacy-sdk"> - **`@solrouter/sdk`** — integrate end-to-end encrypted AI into your app. The - SDK handles key exchange, client-side encryption with Arcium RescueCipher, - and response decryption for you. No email or KYC — connect a Solana wallet - and start building. + **`@solrouter/sdk`**: add encrypted AI calls to your app. The SDK handles + key exchange, client-side encryption with Arcium RescueCipher, and response + decryption for you. No email or KYC: connect a Solana wallet and start + building. } title="Agent Tools SDK" href="/docs/develop/agent-tools-sdk"> - **`@solrouter/agent-tools`** — typed tools for the Agent Privacy API with a - Vercel AI SDK adapter. Quote, execute, and settle privacy-preserving swaps - and encrypted inference without writing orchestration boilerplate. + Status: Soon. **`@solrouter/agent-tools`** is not on npm yet. It will give + you typed tools for the Agent Privacy API with a Vercel AI SDK adapter. + Today, call `POST /agents/v1/*` over HTTP or use the `umbra_*` MCP tools. } title="MCP Server" href="/docs/develop/mcp-server"> - **`@solrouter/mcp-server`** — use Solrouter from Claude Desktop, Cursor, or - any MCP-compatible client. Encrypted chat, private token research, and wallet + **`@solrouter/mcp-server`**: use Solrouter from Claude Desktop, Cursor, or + any MCP-compatible client. Encrypted chat, token research, and wallet analysis without writing any code. } title="Chat App" href="https://solrouter.com/chat"> - Multi-model encrypted chat with file attachments, image and video - generation, and a RAG knowledge base. No account needed beyond a connected - Solana wallet. + Chat in the browser with two open-weight models, file attachments, and a + RAG knowledge base. Encryption is a toggle, off by default. No account + needed beyond a connected Solana wallet. ## Key Guarantees -These are the promises that hold for any privacy-mode request, no matter which -product you reach for. Each one is a property the system enforces, not a feature -you have to remember to turn on. +These properties hold for every encrypted request, whichever product you use. +In the SDK, encryption is on by default. In the chat app, you turn on Maximum +Privacy Mode. } title="Client-Side Encryption"> - Your prompt is encrypted on your own device — using Arcium's RescueCipher - with X25519 key exchange — before it leaves your browser or application. + Your prompt is encrypted on your own device with Arcium's RescueCipher and + X25519 key exchange, before it leaves your browser or application. - } title="TEE-Isolated Inference"> - Plaintext exists only inside an Intel TDX Confidential VM. No host process — - including Solrouter's own backend — can read enclave memory at runtime. + } title="TEE-Isolated Decryption"> + Your prompt is decrypted inside an Intel TDX Confidential VM. No host + process, including Solrouter's own backend, can read enclave memory at + runtime. The enclave then sends the prompt to an open-weight model on a + Nosana GPU node, where it exists in plaintext during inference. } title="Verifiable Attestation"> - Don't take our word for it. Every TEE response carries an Intel-signed TDX - quote, so you can independently verify the enclave's public key and the exact - code running inside it. + Don't take our word for it. A TEE response carries an Intel TDX quote when + the CVM can reach the dStack agent. Otherwise `attestation.tdxQuote` is + null and `tdxQuoteError` says why. The quote lets you check the enclave's + public key. Comparing it against published enclave measurements: Soon. } title="No KYC or Email Required"> - Connect a Solana wallet, generate an API key, and start building. You pay per - call in USDC or `$ROUTER` from a prepaid balance — no signup form, no PII. + Connect a Solana wallet, generate an API key, and start building. You pay + per call in USDC or `$ROUTER` from a prepaid balance. There is no signup + form and no personal data. } title="Open-Weight Models Only"> - All privacy-mode inference runs on self-hosted open-weight models - (`gpt-oss:20b` and `qwen3:8b`) on the Nosana decentralized GPU network. + All privacy-mode inference runs on self-hosted open-weight models on the + Nosana decentralized GPU network. Models: `gpt-oss:20b` (Live), + `qwen3.8:27b` (Live), `gemma4:31b` (Soon). `qwen3:8b` is retired + (Archived). } title="On-Chain Attestation Anchor"> - The Solrouter attestation program is deployed on Solana mainnet. Each - privacy-mode session can publish a PDA that links the request to the verified - enclave on-chain. + Each inference sent through `POST /tee/process` (the SDK path) gets a Light + Protocol compressed account on Solana. Solrouter's deployer wallet commits + it after the CVM signs the proof. This is automatic: you do not publish + anything. The REST and x402 chat routes do not create a receipt today. diff --git a/content/docs/payments/overview.mdx b/content/docs/payments/overview.mdx index 8e5f677..0117b57 100644 --- a/content/docs/payments/overview.mdx +++ b/content/docs/payments/overview.mdx @@ -1,7 +1,10 @@ --- title: "Pricing" icon: CircleDollarSign -description: "Solrouter is metered per API call. Prepay in USDC or $ROUTER from your Solana wallet — no subscription, no credit card, no email required." +description: "Solrouter is metered per API call. Prepay in USDC or $ROUTER from your Solana wallet. No subscription, no credit card, no email required." +status: mixed +checked: "2026-08-26" +statusNote: "Prepaid billing and x402 are Live. Buyback and burn is Soon: the configured ratios are not published." --- import { Cards, Card } from 'fumadocs-ui/components/card'; @@ -10,38 +13,73 @@ import { CircleDollarSign, RotateCw } from 'lucide-react'; Most AI platforms make you commit before you build: a monthly subscription, a credit card on file, an email to verify. Solrouter does none of that. You pay per API call from a balance you fund yourself, so you only ever spend what you use. -The setup is short. Connect a Solana wallet at [solrouter.com/sdk](https://solrouter.com/sdk), fund your balance with USDC or `$ROUTER`, generate an API key, and start building. Every product — the Privacy SDK, Agent Privacy API, MCP server, and chat app — draws from that same prepaid balance. +The setup is short. Connect a Solana wallet at [solrouter.com/sdk](https://solrouter.com/sdk), fund your balance with USDC or `$ROUTER`, generate an API key, and start building. Every product (the Privacy SDK, Agent Privacy API, MCP server, and chat app) draws from that same prepaid balance. - There are no hidden fees. You pay per call from your prepaid balance. When the balance reaches zero, calls stop — nothing is billed retroactively. + You pay per call from your prepaid balance. `GET /payments/pricing` returns the live rate table. +## Feature status + +| Feature | Status | +| --- | --- | +| Prepaid balance in USDC or `$ROUTER` | Live | +| Per-token metering (tables below) | Live | +| x402 keyless payment on `POST /api/v1/x402/chat/completions` | Live | +| `$ROUTER` buyback and burn | Soon | + ## Payment methods -You can fund your balance two ways. Both cost the same per call; the difference is what each one means for you and for the token. +You can fund your balance two ways. The difference is what each one means for you and for the token. }> - Pay with USDC from your Solana wallet. As a stablecoin pegged to the dollar, its value stays put — so you carry no price risk between top-ups. Top up at any time from [solrouter.com/sdk](https://solrouter.com/sdk). + Pay with USDC from your Solana wallet. As a stablecoin pegged to the dollar, its value stays put, so you carry no price risk between top-ups. Top up at any time from [solrouter.com/sdk](https://solrouter.com/sdk). }> - Pay with the native `$ROUTER` token at the same per-call price as USDC. Every fee you pay in `$ROUTER` feeds the buyback-and-burn model — see [\$ROUTER buyback and burn](#router-buyback-and-burn) below. + Pay with the native `$ROUTER` token. Fees paid in `$ROUTER` feed the buyback-and-burn mechanism. See [\$ROUTER buyback and burn](#router-buyback-and-burn) below. +## Per-call rates + +Two rate tables exist in code. Table A bills the chat app and `POST /agent`. Table B bills the REST route `POST /api/v1/chat/completions`. `GET /payments/pricing` is the live source for table A. + +**Table A: chat and `/agent` billing.** Rates are USD per 1M tokens, before a 20% margin. The balance is held as app tokens at 1,000 app tokens per USD. Each billed direction has a floor of 10 app tokens (\$0.01), so a normal inference costs at least \$0.02. Charges settle in `$ROUTER` first and fall back to USDC. + +| Model | Input | Output | +| --- | --- | --- | +| `gpt-oss:20b` | \$0.10 | \$0.20 | +| `qwen3.8:27b` | \$0.15 | \$0.30 | +| `gemma4:31b` | \$0.15 | \$0.30 | + +**Table B: `POST /api/v1/chat/completions`.** Rates are USD per 1M tokens. No margin and no floor apply. Token counts are estimated from character counts (about 4 characters per token). The cost is debited from your USDC balance only. The route answers 402 when your USDC balance is zero, even if you hold `$ROUTER`. + +| Model | Input | Output | +| --- | --- | --- | +| `gpt-oss:20b` | \$0.15 | \$0.30 | +| `qwen3.8:27b` | \$0.15 | \$0.30 | +| `gemma4:31b` | \$0.15 | \$0.30 | + +The two tables disagree on `gpt-oss:20b`. Unifying them is a backend follow-up. x402 route prices come from the manifest at `https://api.solrouter.com/.well-known/x402`. + ## x402 keyless payments An autonomous agent often has no human around to sign up for an account or manage an API key. x402 solves that: it lets an agent pay for each call on its own, with no registration at all. -x402 is a standard for HTTP-native micropayments — payments built directly into the web request itself — settled in USDC on Solana mainnet and facilitated by Coinbase. Any x402-aware agent can discover the payment manifest and start paying immediately. +x402 is a standard for HTTP-native micropayments (payments built into the web request itself), settled in USDC on Solana mainnet. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. It does not show which facilitator settles payments. Any x402-aware agent can discover the payment manifest and start paying immediately. -* **Discovery:** `/.well-known/x402` — the x402 paywall manifest listing available endpoints and pricing. -* **Endpoint:** `POST /api/v1/x402/chat/completions` — Arcium-encrypted prompt in, encrypted response out. -* **Price:** \$0.005 per call, settled on-chain before the request is processed. +* **Discovery:** `https://api.solrouter.com/.well-known/x402`, the x402 paywall manifest listing available endpoints and pricing. +* **Endpoint:** `POST /api/v1/x402/chat/completions`. Arcium-encrypted prompt in, encrypted response out. +* **Price:** \$0.005 per call. Solrouter's server verifies and settles the USDC payment through its facilitator and then returns the reply. * **Best for:** autonomous agents that self-fund their own inference costs without a human managing API keys. -Going keyless costs you no privacy. The x402 path runs the same end-to-end encrypted inference as the API-key path: your prompt is still encrypted with RescueCipher before it reaches Solrouter's backend. +Going keyless costs you no privacy. The x402 path runs the same end-to-end encrypted inference as the API-key path: your prompt is still encrypted with Arcium's RescueCipher before it reaches Solrouter's backend. + + + The paywall charges \$0.005 per call. The response body of this endpoint currently reports `paid.amount: 0.02`. This is a backend follow-up; the manifest price is the one charged. + ## Managing your balance @@ -52,14 +90,14 @@ const { balance, balanceFormatted } = await client.getBalance(); console.log(`Balance: ${balanceFormatted}`); ``` -To add funds, visit [solrouter.com/sdk](https://solrouter.com/sdk) and connect your Solana wallet. Deposit USDC or `$ROUTER` in any amount — there is no minimum. +To add funds, visit [solrouter.com/sdk](https://solrouter.com/sdk) and connect your Solana wallet. Deposit USDC or `$ROUTER`. Deposit minimum: not determined. ## \$ROUTER buyback and burn -Here is what your fees do for the token. The model is deliberately simple and mechanical: no emissions, no staking curves, no tiered discount programs — just supply that tightens as the network is used. +Status: Soon. The mechanism exists in code. The ratios are runtime settings, and the worker skips its run while they are zero. -* **100% of USDC revenue** flows into buying back `$ROUTER` on the open market. -* **50% of each buyback** is permanently burned, reducing total supply. -* **50% of `$ROUTER` fees** paid directly are burned at the time of payment. +* A buyback worker reads the USDC inflow for each window and buys `$ROUTER` with a configured share of it (`BURN_USDC_BPS`). +* A configured share of the `$ROUTER` bought back is burned (`BURN_OUTPUT_TOKEN_BPS`). The rest stays in treasury. +* A configured share of fees paid directly in `$ROUTER` is burned on receipt (`BURN_TOKEN_BPS`). The rest stays in treasury. -Every call you make — whether you pay in USDC or `$ROUTER` — tightens supply. See [/docs/payments/tokenomics](/docs/payments/tokenomics) for the full token mechanics, supply schedule, and vesting details. +Configured ratios: not published. `GET /payments/buyback/log` returns the recent buyback worker ticks, including skipped ones. See [/docs/payments/tokenomics](/docs/payments/tokenomics) for the token supply schedule and vesting details. diff --git a/content/docs/payments/tokenomics.mdx b/content/docs/payments/tokenomics.mdx index 0d57182..7ffd524 100644 --- a/content/docs/payments/tokenomics.mdx +++ b/content/docs/payments/tokenomics.mdx @@ -1,7 +1,10 @@ --- title: "$ROUTER Token" icon: Coins -description: "$ROUTER is Solrouter's utility token on Solana. 1B total supply, 53.04% at TGE, with a buyback-and-burn model funded by 100% of protocol revenue." +description: "$ROUTER is Solrouter's utility token on Solana. 1B total supply, 53.04% at TGE, with a buyback-and-burn mechanism whose ratios are not yet published." +status: mixed +checked: "2026-08-26" +statusNote: "The token and its supply schedule are Live. Buyback and burn is Soon: the configured ratios are not published." --- import { Cards, Card } from 'fumadocs-ui/components/card'; @@ -10,7 +13,7 @@ import { TokenAllocation } from '@/components/diagrams/token-allocation'; Most utility tokens bury their value behind staking lockups, emissions schedules, and governance you have to opt into. `$ROUTER` does the opposite: it is a way to pay for what you use, and nothing more. -`$ROUTER` is Solrouter's utility token on Solana. You spend it on API calls across every Solrouter product — the Privacy SDK, Agent Privacy API, MCP server, and chat app — at the same per-call rate as USDC. There are no emissions, no staking rewards, and no governance complexity. One mechanic drives the whole design: protocol revenue buys back and burns `$ROUTER`, so supply tightens as usage grows. +`$ROUTER` is Solrouter's utility token on Solana. You spend it on API calls across every Solrouter product (the Privacy SDK, Agent Privacy API, MCP server, and chat app). There are no emissions, no staking rewards, and no governance complexity. One mechanic drives the whole design: protocol revenue can buy back and burn `$ROUTER`, so supply tightens as usage grows. ## Token details @@ -42,11 +45,11 @@ Here is where the 1B supply goes, and how quickly each slice becomes spendable. ## Circulating supply schedule -Locked supply can't be sold, so this is how the float — and the potential sell pressure — grows over time. +Locked supply cannot be sold, so this is how the float (and the potential sell pressure) grows over time. -Tokens enter circulation over 24 months. The Liquidity Pool, OpenServ, and Superteam Germany allocations unlock at TGE (Token Generation Event — the moment the token first goes live). Treasury releases 9.5% at TGE and vests the rest linearly over 24 months. +Tokens enter circulation over 24 months. The Liquidity Pool, OpenServ, and Superteam Germany allocations unlock at TGE (Token Generation Event, the moment the token first goes live). Treasury releases 9.5% at TGE and vests the rest linearly over 24 months. -The Team allocation has a 3-month cliff (zero unlocks until month three), then vests linearly over the following 12 months. Algorithmic Fundraising unlocks per FDV band as each band clears, so the schedule below is indicative — the real cadence depends on demand. +The Team allocation has a 3-month cliff (zero unlocks until month three), then vests linearly over the following 12 months. Algorithmic Fundraising unlocks per FDV band as each band clears, so the schedule below is indicative. The real cadence depends on demand. | Milestone | Circulating % | Circulating Tokens | | --------- | ------------- | ------------------ | @@ -57,27 +60,27 @@ The Team allocation has a 3-month cliff (zero unlocks until month three), then v ## Algorithmic fundraising -Instead of selling the raise allocation all at once, Solrouter releases it in steps tied to the token's own valuation — so capital comes in only as the market values the protocol higher. - -5% of total supply (50,000,000 `$ROUTER`) sells across 14 FDV (Fully Diluted Valuation — the value of the entire 1B supply at the current price) bands ranging from $500K to $100M. Each band unlocks only after the previous band's valuation threshold clears, raising capital progressively as demand grows. Total estimated capital across all 14 bands is roughly \$807,750, calculated using each band's midpoint valuation. - -| Band | Valuation (USD) | % of Supply | Capital Raised | Cumulative | -| --------- | --------------- | ----------- | -------------- | ------------- | -| 1 | $500K – $750K | 0.30% | \$1,875 | \$1,875 | -| 2 | $750K – $1M | 0.30% | \$2,625 | \$4,500 | -| 3 | $1M – $1.5M | 0.35% | \$4,375 | \$8,875 | -| 4 | $1.5M – $2M | 0.35% | \$6,125 | \$15,000 | -| 5 | $2M – $3M | 0.40% | \$10,000 | \$25,000 | -| 6 | $3M – $5M | 0.40% | \$16,000 | \$41,000 | -| 7 | $5M – $8M | 0.45% | \$29,250 | \$70,250 | -| 8 | $8M – $12M | 0.45% | \$45,000 | \$115,250 | -| 9 | $12M – $18M | 0.50% | \$75,000 | \$190,250 | -| 10 | $18M – $25M | 0.50% | \$107,500 | \$297,750 | -| 11 | $25M – $40M | 0.40% | \$130,000 | \$427,750 | -| 12 | $40M – $60M | 0.30% | \$150,000 | \$577,750 | -| 13 | $60M – $80M | 0.20% | \$140,000 | \$717,750 | -| 14 | $80M – $100M | 0.10% | \$90,000 | \$807,750 | -| **Total** | | **5.00%** | **\$807,750** | **\$807,750** | +Instead of selling the raise allocation all at once, Solrouter releases it in steps tied to the token's own valuation. Capital comes in only as the market values the protocol higher. + +5% of total supply (50,000,000 `$ROUTER`) sells across 14 FDV (Fully Diluted Valuation, the value of the entire 1B supply at the current price) bands ranging from $500K to $100M. Each band unlocks only after the previous band's valuation threshold clears, raising capital progressively as demand grows. Total estimated capital across all 14 bands is roughly \$807,750, calculated using each band's midpoint valuation. + +| Band | Valuation (USD) | % of Supply | Capital Raised | Cumulative | +| --------- | ---------------- | ----------- | -------------- | ------------- | +| 1 | $500K to $750K | 0.30% | \$1,875 | \$1,875 | +| 2 | $750K to $1M | 0.30% | \$2,625 | \$4,500 | +| 3 | $1M to $1.5M | 0.35% | \$4,375 | \$8,875 | +| 4 | $1.5M to $2M | 0.35% | \$6,125 | \$15,000 | +| 5 | $2M to $3M | 0.40% | \$10,000 | \$25,000 | +| 6 | $3M to $5M | 0.40% | \$16,000 | \$41,000 | +| 7 | $5M to $8M | 0.45% | \$29,250 | \$70,250 | +| 8 | $8M to $12M | 0.45% | \$45,000 | \$115,250 | +| 9 | $12M to $18M | 0.50% | \$75,000 | \$190,250 | +| 10 | $18M to $25M | 0.50% | \$107,500 | \$297,750 | +| 11 | $25M to $40M | 0.40% | \$130,000 | \$427,750 | +| 12 | $40M to $60M | 0.30% | \$150,000 | \$577,750 | +| 13 | $60M to $80M | 0.20% | \$140,000 | \$717,750 | +| 14 | $80M to $100M | 0.10% | \$90,000 | \$807,750 | +| **Total** | | **5.00%** | **\$807,750** | **\$807,750** | *Capital estimated using each band's midpoint valuation.* @@ -89,28 +92,30 @@ For the first 130 seconds after the liquidity pool opens, per-transfer and per-w | Time After Open | Max Per Transfer | Max Per Wallet (Buys) | | ----------------- | ---------------- | --------------------- | -| 0 – 70 seconds | 100,000 (0.01%) | 1,000,000 (0.1%) | -| 70 – 130 seconds | 1,000,000 (0.1%) | 5,000,000 (0.5%) | +| 0 to 70 seconds | 100,000 (0.01%) | 1,000,000 (0.1%) | +| 70 to 130 seconds | 1,000,000 (0.1%) | 5,000,000 (0.5%) | | After 130 seconds | No limit | No limit | ## Buyback and burn -This is the engine that ties token value to real usage: the more people pay Solrouter, the more `$ROUTER` permanently leaves circulation. Revenue flows straight into supply reduction — no intermediary pools, no governance votes, no discretionary treasury spending. +Status: Soon. This is the engine that ties token value to real usage: the more people pay Solrouter, the more `$ROUTER` can leave circulation. The mechanism exists in code. The ratios are runtime settings (`BURN_USDC_BPS`, `BURN_OUTPUT_TOKEN_BPS`, `BURN_TOKEN_BPS`), and the worker skips its run while they are zero. - }> - Of USDC revenue goes toward buying back `$ROUTER` on the open market. + }> + Status: Soon. A configured share of USDC revenue buys back `$ROUTER` on the open market through Jupiter. - }> - Of each buyback is permanently burned, permanently reducing total supply. + }> + Status: Soon. A configured share of each buyback is burned. The rest stays in treasury. - }> - Of `$ROUTER` fees paid directly are burned at the time of payment. + }> + Status: Soon. A configured share of `$ROUTER` fees paid directly is burned on receipt. The rest stays in treasury. -The model stays simple on purpose. No emissions mint new tokens, no staking curves add complexity, and no tiered discount programs fragment the tokenomics. Every API call reduces supply — whether you pay in USDC or `$ROUTER`. The more Solrouter is used, the tighter the supply becomes. +Configured ratios: not published. `GET /payments/buyback/log` returns the recent buyback worker ticks, including skipped ones. + +The model stays simple on purpose. No emissions mint new tokens, no staking curves add complexity, and no tiered discount programs fragment the tokenomics. To pay for API calls with `$ROUTER`, see [Pricing](/docs/payments/overview). diff --git a/content/docs/quickstart.mdx b/content/docs/quickstart.mdx index 955d73b..4068403 100644 --- a/content/docs/quickstart.mdx +++ b/content/docs/quickstart.mdx @@ -1,27 +1,29 @@ --- title: "Quickstart" icon: Rocket -description: "Install @solrouter/sdk, generate an API key with your Solana wallet, and send your first encrypted AI request with just a few lines of TypeScript." +description: "Get an API key with your Solana wallet, install @solrouter/sdk, and send your first encrypted request in a few lines of TypeScript." +status: live +checked: "2026-08-26" --- import { Cards, Card } from 'fumadocs-ui/components/card'; import { Callout } from 'fumadocs-ui/components/callout'; import { Step, Steps } from 'fumadocs-ui/components/steps'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; -import { Lock, KeyRound, Bot, Code } from 'lucide-react'; +import { Lock, KeyRound, Code } from 'lucide-react'; -Most AI APIs can read every prompt you send them. Solrouter doesn't: your message is encrypted on your machine before it leaves, and the backend relays it without ever seeing the plaintext. In the next five steps you'll set that up end to end — sign in with a Solana wallet, install the SDK, and send your first encrypted request. No email, no credit card, just a wallet and a few lines of TypeScript. +Most AI APIs can read every prompt you send them. Solrouter does not: your message is encrypted on your machine before it leaves, and the backend relays it without seeing the plaintext. The next five steps set that up end to end. You sign in with a Solana wallet, install the SDK, and send your first encrypted request. No email and no credit card: you need a wallet and a few lines of TypeScript. ### Get an API key - Your key is how Solrouter authenticates you and bills your usage — and it's tied to your wallet, not your identity. + Your key is how Solrouter authenticates you and bills your usage. It is tied to your wallet, not your identity. Go to [solrouter.com/sdk](https://solrouter.com/sdk), connect your Solana wallet, and generate an API key. Top up your prepaid balance in USDC or \$ROUTER to start making calls. - No email, no credit card, and no KYC required. Your API key is tied to your wallet — that's it. + No email, no credit card, and no KYC required. Your API key is tied to your wallet. @@ -54,37 +56,46 @@ Most AI APIs can read every prompt you send them. Solrouter doesn't: your messag ### Initialize the client - One line gets you a configured client. Pass your API key and you're ready to make calls. + One call gets you a configured client. Pass your API key and the API base URL. - The SDK automatically fetches the TEE's attested public key — the encryption target for your prompts (a TEE, or Trusted Execution Environment, is hardware that isolates code and data even from the machine's owner). You don't need to configure anything else. + The SDK fetches the enclave's published public key from `GET /tee/public-key` before your first encrypted request. That key is the encryption target for your prompts. The SDK does not fetch or verify the attestation quote. To check the enclave yourself, see [Attestation](/docs/concepts/attestation). ```typescript import { SolRouter } from '@solrouter/sdk'; const client = new SolRouter({ - apiKey: 'sk_solrouter_...' + apiKey: 'sk_solrouter_...', + baseUrl: 'https://api.solrouter.com', }); ``` + + + Set `baseUrl` explicitly. The SDK default points at a Render host, not at `api.solrouter.com`. + ### Send your first encrypted chat - This is where the privacy guarantee pays off: your prompt travels encrypted the whole way, and only the TEE can read it. + Here the privacy guarantee pays off. Your prompt travels encrypted through the Solrouter backend. Only the TEE (Trusted Execution Environment: hardware that isolates code and data from the machine's operator) can decrypt it. The enclave then sends the plaintext to an open-weight model on a Nosana GPU node. + + Call `client.chat()` to send a message. The SDK encrypts it client-side with Arcium's RescueCipher. It sends the encrypted blob through the Solrouter backend, which cannot read it. Then it decrypts the response for you. - Call `client.chat()` to send a message. The SDK encrypts it client-side with Arcium's RescueCipher, routes the encrypted blob through the Solrouter backend (which can't read it), and decrypts the response for you automatically. + With the current SDK, use the default model `gpt-oss-20b`. Other catalog ids pass through with a type cast; see [Models](/docs/concepts/supported-models). ```typescript - // Encrypted end-to-end — Solrouter backend never sees plaintext + // Encrypted end to end: the Solrouter backend never sees plaintext const response = await client.chat('What are the risks of this DeFi protocol?'); console.log(response.message); ``` + + If the GPU node was idle, the first reply can say "Nosana GPU node is warming up". Wait a moment and send the request again. ### Check your balance - Solrouter bills against prepaid funds, so check your remaining balance any time — for example, before a batch of calls or to surface it in your own UI. + Solrouter bills against prepaid funds, so you can check your remaining balance at any time. For example, check it before a batch of calls, or show it in your own UI. Query your prepaid balance: @@ -97,21 +108,17 @@ Most AI APIs can read every prompt you send them. Solrouter doesn't: your messag ## Next steps -You've sent an encrypted request and checked your balance — that's the core loop. Where you go next depends on what you're building: the full SDK surface, private on-chain agent actions, or direct HTTP access. +You have sent an encrypted request and checked your balance. That is the core loop. Where you go next depends on what you build: the full SDK surface, keyless payments, or direct HTTP access. } href="/docs/develop/privacy-sdk"> - Full `@solrouter/sdk` documentation: model selection, opt-out mode, SERV reasoning, and more. + Full `@solrouter/sdk` documentation: model selection, plaintext mode (`encrypted: false`), BRAID reasoning (`reasoning: 'braid'`), and more. } href="/docs/develop/authentication"> Learn about API key auth, x402 keyless payments, and keeping your credentials safe. - } href="/docs/develop/agent-tools-sdk"> - Typed tools for private swaps and encrypted inference in any function-calling agent framework. - - } href="/docs/api-reference/overview"> Browse the full REST API, including the agent endpoint and x402 paywall spec. diff --git a/source.config.ts b/source.config.ts index a35628a..42a267a 100644 --- a/source.config.ts +++ b/source.config.ts @@ -1,12 +1,17 @@ import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; import { metaSchema, pageSchema } from 'fumadocs-core/source/schema'; +import { z } from 'zod'; -// You can customize Zod schemas for frontmatter and `meta.json` here -// see https://fumadocs.dev/docs/mdx/collections +// Frontmatter schema. Every hand-written page carries a status and the date it +// was last checked against code or a live endpoint. See README "Status policy". export const docs = defineDocs({ dir: 'content/docs', docs: { - schema: pageSchema, + schema: pageSchema.extend({ + status: z.enum(['live', 'soon', 'archived', 'mixed']).optional(), + checked: z.string().optional(), + statusNote: z.string().optional(), + }), postprocess: { includeProcessedMarkdown: true, }, diff --git a/src/app/docs/[[...slug]]/page.tsx b/src/app/docs/[[...slug]]/page.tsx index 53ce219..9059bb8 100644 --- a/src/app/docs/[[...slug]]/page.tsx +++ b/src/app/docs/[[...slug]]/page.tsx @@ -1,4 +1,5 @@ import { getPageImage, getPageMarkdownUrl, source } from '@/lib/source'; +import { statusSentence } from '@/lib/status'; import { DocsBody, DocsDescription, @@ -20,11 +21,17 @@ export default async function Page(props: PageProps<'/docs/[[...slug]]'>) { const MDX = page.data.body; const markdownUrl = getPageMarkdownUrl(page).url; + const status = statusSentence(page.data); return ( {page.data.title} {page.data.description} + {status ? ( +

+ {status} +

+ ) : null}
- +
{title}
{sub}
@@ -28,9 +28,13 @@ function Stage({ ); } -function Hop({ label }: { label: string }) { +function Hop({ label, open = false }: { label: string; open?: boolean }) { return ( -
+
{label} @@ -39,35 +43,60 @@ function Hop({ label }: { label: string }) { ); } -/** Clean, theme-adaptive infographic of the encrypt → blind-relay → TEE flow. */ -export function EncryptionFlow() { +/** + * Theme-adaptive infographic of the request path. + * + * encrypted (default): device encrypts, backend relays ciphertext, the CVM + * decrypts and calls the model on a Nosana GPU node, the reply comes back + * encrypted. + * + * encrypted={false}: the same path with plaintext at the backend. Used on the + * Privacy SDK page to show what `encrypted: false` gives up. + */ +export function EncryptionFlow({ encrypted = true }: { encrypted?: boolean }) { + const wire = encrypted ? 'ciphertext' : 'plaintext'; + const label = encrypted + ? 'Request path with encryption on: your device encrypts the prompt, the Solrouter backend relays ciphertext it cannot read, the Intel TDX enclave decrypts it and calls the model on a Nosana GPU node, and the reply returns encrypted to your device.' + : 'Request path with encryption off: your device sends plaintext, the Solrouter backend reads and routes it to the same self-hosted model on a Nosana GPU node, and the reply returns in plaintext.'; + return ( -
+
- + - + + +
-

- The response is re-encrypted inside the enclave — only your device can - read it. -

-
+
+ {encrypted + ? 'The reply is encrypted inside the enclave with your session key. Only your device can read it.' + : 'The reply returns in plaintext. No enclave, no on-chain receipt.'} +
+ ); } diff --git a/src/components/diagrams/typical-vs-solrouter.tsx b/src/components/diagrams/typical-vs-solrouter.tsx new file mode 100644 index 0000000..dbb9d34 --- /dev/null +++ b/src/components/diagrams/typical-vs-solrouter.tsx @@ -0,0 +1,88 @@ +import { Eye, EyeOff, KeyRound, Lock, Server, User, Zap, type LucideIcon } from 'lucide-react'; +import type { ReactNode } from 'react'; + +function Stage({ + icon: Icon, + title, + sub, + accent = false, +}: { + icon: LucideIcon; + title: string; + sub: string; + accent?: boolean; +}) { + return ( +
+
+ +
+
{title}
+
{sub}
+
+ ); +} + +function Wire({ label, open = false }: { label: string; open?: boolean }) { + const Icon = open ? Eye : Lock; + return ( +
+ + {label} +
+ ); +} + +function Row({ title, children }: { title: string; children: ReactNode }) { + return ( +
+
{title}
+
{children}
+
+ ); +} + +/** + * Before and after picture. Row 1: a typical AI API, where the provider's + * server reads the prompt. Row 2: Solrouter with encryption on, where the + * backend relays ciphertext and only the TDX enclave opens it. + */ +export function TypicalVsSolrouter() { + return ( +
+ + + + + + + + + + + + + + + + + + +
+ The reply is encrypted inside the enclave before it travels back. The backend never sees the text. +
+
+ ); +} diff --git a/src/lib/source.ts b/src/lib/source.ts index a00a3fc..056d2eb 100644 --- a/src/lib/source.ts +++ b/src/lib/source.ts @@ -2,6 +2,7 @@ import { docs } from 'collections/server'; import { loader } from 'fumadocs-core/source'; import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons'; import { docsContentRoute, docsImageRoute, docsRoute } from './shared'; +import { statusSentence } from './status'; // See https://fumadocs.dev/docs/headless/source-api for more info export const source = loader({ @@ -30,8 +31,10 @@ export function getPageMarkdownUrl(page: (typeof source)['$inferPage']) { export async function getLLMText(page: (typeof source)['$inferPage']) { const processed = await page.data.getText('processed'); + const status = statusSentence(page.data); + const head = status ? `${status}\n\n` : ''; return `# ${page.data.title} (${page.url}) -${processed}`; +${head}${processed}`; } diff --git a/src/lib/status.ts b/src/lib/status.ts new file mode 100644 index 0000000..0f2d75b --- /dev/null +++ b/src/lib/status.ts @@ -0,0 +1,32 @@ +export type PageStatus = 'live' | 'soon' | 'archived' | 'mixed'; + +const LABEL: Record = { + live: 'Live', + soon: 'Soon', + archived: 'Archived', + mixed: 'Mixed', +}; + +const MEANING: Record = { + live: 'This page describes what runs in production today.', + soon: 'The code exists, but this surface is not published or not confirmed end to end.', + archived: 'This feature was removed or disabled. The page stays to explain the change.', + mixed: 'See the Status column in the tables on this page.', +}; + +export function statusLabel(status: PageStatus): string { + return LABEL[status]; +} + +/** One or two plain sentences that appear under the page description and in llms output. */ +export function statusSentence(input: { + status?: PageStatus; + checked?: string; + statusNote?: string; +}): string | null { + if (!input.status) return null; + const parts = [`Status: ${LABEL[input.status]}.`]; + parts.push(input.statusNote ? input.statusNote.trim().replace(/\.?$/, '.') : MEANING[input.status]); + if (input.checked) parts.push(`Checked against code and api.solrouter.com on ${input.checked}.`); + return parts.join(' '); +} From ce6a9bf0de4fcd2036b04c1b7cc831a119f480be Mon Sep 17 00:00:00 2001 From: sarthib7 Date: Wed, 26 Aug 2026 18:10:51 +0200 Subject: [PATCH 2/7] chore: sitemap, robots, metadataBase, CI build check, docs lint, README - sitemap.xml and robots.txt from the page tree; lastmod from checked. - metadataBase so OG image URLs are absolute. - llms-full.txt puts Introduction first and generated API pages last. - next.config.mjs redirects hook with a moves list (empty until pages move). - scripts/check-docs.mjs (npm run check): fails on em or en dashes, banned words, missing status or checked frontmatter, and diagrams without an In words list. - GitHub Actions workflow: npm ci, types:check, check, build. - README replaces the Create Fumadocs boilerplate with the status policy, the truth rule, the diagram rule, and the PR checklist. - package-lock.json re-synced with package.json (npm ci failed on @emnapi/wasi-threads). Adds @xyflow/react 12.11.5 for the interactive diagrams that land next. - .scratch/ ignored (local wayfinder tracker). --- .github/workflows/build.yml | 20 +++ .gitignore | 4 +- README.md | 84 +++++++---- next.config.mjs | 10 ++ package-lock.json | 248 ++++++++++++++++++++++++++++++++- package.json | 4 +- scripts/check-docs.mjs | 71 ++++++++++ src/app/layout.tsx | 7 + src/app/llms-full.txt/route.ts | 14 +- src/app/robots.ts | 9 ++ src/app/sitemap.ts | 14 ++ src/lib/openapi.ts | 2 +- src/lib/shared.ts | 1 + 13 files changed, 450 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 scripts/check-docs.mjs create mode 100644 src/app/robots.ts create mode 100644 src/app/sitemap.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..dd4db6c --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,20 @@ +name: build + +on: + pull_request: + push: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run types:check + - run: npm run check + - run: npm run build diff --git a/.gitignore b/.gitignore index 9e429e4..312cde8 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,6 @@ yarn-error.log* # others .env*.local .vercel -next-env.d.ts \ No newline at end of file +next-env.d.ts +# local issue tracker (wayfinder map, never published) +.scratch/ diff --git a/README.md b/README.md index 9b7bba9..aadca86 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,71 @@ -# docs +# Solrouter docs -This is a Next.js application generated with -[Create Fumadocs](https://github.com/fuma-nama/fumadocs). +Source for [docs.solrouter.com](https://docs.solrouter.com). Built with [Fumadocs](https://fumadocs.dev) on Next.js. -Run development server: +This repository is the source of truth for the public docs. Changes land through pull requests into `main`, and `main` deploys automatically. `scripts/publish-mirror.sh` is retired: it force-pushes a copy from the product monorepo and would erase merged pull requests. + +## Run it ```bash -npm run dev -# or -pnpm dev -# or -yarn dev +npm ci +npm run dev # http://localhost:3000 +npm run build # production build +npm run types:check # MDX collection, route types, tsc +npm run check # prose, status, and diagram-fallback checks +``` + +## Layout + +``` +content/docs/ hand-written pages (MDX) and meta.json sidebars +content/docs/api-reference/agent-privacy/ + generated from openapi/agent-privacy.json, do not edit by hand +openapi/agent-privacy.json snapshot of https://api.solrouter.com/agents/v1/openapi.json +scripts/generate-openapi.mjs regenerates the Agent Privacy API pages from the snapshot +scripts/check-docs.mjs the checks behind `npm run check` +src/components/diagrams/ static, theme-aware diagrams (React + Tailwind) +src/components/verify/ client widgets that call the live API +src/lib/status.ts the status sentence shown under every page title ``` -Open http://localhost:3000 with your browser to see the result. +The docs have two tiers. Start here, Products, and Account are written for readers with no technical background. Under the hood and Reference are written for engineers and auditors and cite the code that backs each claim. + +## Status policy + +Every hand-written page declares three frontmatter fields: + +```yaml +status: live # live | soon | archived | mixed +checked: "2026-08-26" +statusNote: "Optional one-sentence reason for soon, archived, or mixed." +``` -## Explore +- Live: the code path exists and the surface answered on api.solrouter.com, npm, or solrouter.com on the `checked` date. +- Soon: the code exists, but the surface is not published, not deployed, or not confirmed end to end. +- Archived: removed or disabled. The page stays to explain the change. +- Mixed: the page holds a table with a Status column. Read the rows. -In the project, you can see: +A feature nobody has confirmed ships as Soon, never as Live. The `checked` date changes only when someone re-checks the page against code or a live endpoint. -- `lib/source.ts`: Code for content source adapter, [`loader()`](https://fumadocs.dev/docs/headless/source-api) provides the interface to access your content. -- `lib/layout.shared.tsx`: Shared options for layouts, optional but preferred to keep. +## Truth rule -| Route | Description | -| ------------------------- | ------------------------------------------------------ | -| `app/(home)` | The route group for your landing page and other pages. | -| `app/docs` | The documentation layout and pages. | -| `app/api/search/route.ts` | The Route Handler for search. | +Every product claim in a pull request cites a file path in the product code or a live response in the PR body. No number, name, date, or benchmark goes in without a source. -### Fumadocs MDX +## Diagrams -A `source.config.ts` config file has been included, you can customise different options like frontmatter schema. +Add a diagram as a React component under `src/components/diagrams/`. Give the root element `role="img"` and an `aria-label` that describes the whole picture in one sentence. In the MDX, follow the component with a short Markdown list titled **In words**. That list is what screen readers, `llms.txt`, and the `.md` routes see, because component markup carries no meaning there. `npm run check` fails when the list is missing. -Read the [Introduction](https://fumadocs.dev/docs/mdx) for further details. +## Writing rules -## Learn More +- Short sentences, about 20 words or fewer. One idea per sentence. Active voice. +- No em dashes or en dashes. Use a period, a comma, a colon, or parentheses. +- No filler vocabulary. `npm run check` lists the banned words. +- Define a term in plain words before you use its acronym, or link to the glossary. -To learn more about Next.js and Fumadocs, take a look at the following -resources: +## Pull request checklist -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js - features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. -- [Fumadocs](https://fumadocs.dev) - learn about Fumadocs +1. `npm run types:check`, `npm run check`, and `npm run build` pass. +2. Every changed factual sentence cites a file path or a live response in the PR body. +3. Touched pages have no horizontal scroll at 375 px. Wrap wide tables in a scroll container. +4. Moved or renamed pages have a redirect row in `next.config.mjs`. +5. No `Co-Authored-By` trailer in commits. diff --git a/next.config.mjs b/next.config.mjs index 457dcf2..d501d28 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,9 +2,19 @@ import { createMDX } from 'fumadocs-mdx/next'; const withMDX = createMDX(); +// Old slug -> new slug. Each row also redirects its `.md` twin, which proxy.ts +// rewrites for text readers. Add a row whenever a page moves. +const moves = []; + /** @type {import('next').NextConfig} */ const config = { reactStrictMode: true, + async redirects() { + return moves.flatMap(([from, to]) => [ + { source: from, destination: to, permanent: true }, + { source: `${from}.md`, destination: `${to}.md`, permanent: true }, + ]); + }, }; export default withMDX(config); diff --git a/package-lock.json b/package-lock.json index 9b61311..b2f5a19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "hasInstallScript": true, "dependencies": { + "@xyflow/react": "12.11.5", "fumadocs-core": "16.9.3", "fumadocs-mdx": "15.0.11", "fumadocs-openapi": "^10.10.3", @@ -288,9 +289,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", + "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", "dev": true, "license": "MIT", "optional": true, @@ -2975,6 +2976,55 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", @@ -3733,6 +3783,48 @@ "win32" ] }, + "node_modules/@xyflow/react": { + "version": "12.11.5", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.11.5.tgz", + "integrity": "sha512-QqoryGkqEhWBuQN9bZWRKhwr3Uoj9lCGj/tg0NnIWHHEOXV+5c8cYsc7Q9TST63V92lHqcNV9P5cjvJ9ZAmblQ==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.81", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "@types/react": ">=17", + "@types/react-dom": ">=17", + "react": ">=17", + "react-dom": ">=17" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.81", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.81.tgz", + "integrity": "sha512-hfbafW4i7uLq7ILok8QWFFm4KMFw22lbZNJHKfHOMSOOoCk5e5m8yfr84UV9NaJajmogWaLVnp2XFU9JQejlqg==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -4300,6 +4392,12 @@ "url": "https://polar.sh/cva" } }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -4397,6 +4495,112 @@ "devOptional": true, "license": "MIT" }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -7435,7 +7639,6 @@ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", "license": "ISC", - "peer": true, "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -10723,6 +10926,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -10933,6 +11145,34 @@ "zod": "^3.25.0 || ^4.0.0" } }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index bc0c1d0..85bbb44 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,11 @@ "start": "next start", "types:check": "fumadocs-mdx && next typegen && tsc --noEmit", "postinstall": "fumadocs-mdx", - "lint": "eslint" + "lint": "eslint", + "check": "node scripts/check-docs.mjs" }, "dependencies": { + "@xyflow/react": "12.11.5", "fumadocs-core": "16.9.3", "fumadocs-mdx": "15.0.11", "fumadocs-openapi": "^10.10.3", diff --git a/scripts/check-docs.mjs b/scripts/check-docs.mjs new file mode 100644 index 0000000..1e43db3 --- /dev/null +++ b/scripts/check-docs.mjs @@ -0,0 +1,71 @@ +// Docs checks that run in CI and locally: node scripts/check-docs.mjs +// +// 1. Prose: no em dash (U+2014), en dash (U+2013), spaced double hyphen, or banned words +// in hand-written pages. +// 2. Status: every hand-written page declares `status:` and `checked:` in frontmatter. +// 3. Fallback: every diagram component is followed within 6 lines by an "In words" +// Markdown list or a table, so text readers (llms.txt, .md routes) keep the meaning. +// +// Generated pages under content/docs/api-reference/agent-privacy/** are skipped. +import { readdirSync, readFileSync, statSync } from 'node:fs'; +import { join, relative } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = fileURLToPath(new URL('../content/docs/', import.meta.url)); +const SKIP = /api-reference\/agent-privacy\//; +const BANNED = [ + 'delve', 'crucial', 'pivotal', 'robust', 'seamless', 'tapestry', 'testament', + 'underscore', 'showcase', 'foster', 'intricate', 'vibrant', 'enhance', 'garner', + 'interplay', 'align with', 'additionally', +]; +// Components that draw a picture. Each use needs an "In words" list or a table right after it. +const DIAGRAM = /<(EncryptionFlow|TokenAllocation|ArchitectureMap|RequestFlowStepper|SkillGraphMap|[A-Z][A-Za-z]*Diagram)\b/; + +function walk(dir, out = []) { + for (const name of readdirSync(dir)) { + const p = join(dir, name); + if (statSync(p).isDirectory()) walk(p, out); + else if (p.endsWith('.mdx')) out.push(p); + } + return out; +} + +const problems = []; +let checked = 0; + +for (const file of walk(ROOT)) { + const rel = relative(ROOT, file); + if (SKIP.test(rel)) continue; + checked += 1; + const text = readFileSync(file, 'utf8'); + const lines = text.split('\n'); + + // Frontmatter block. + const fm = text.startsWith('---') ? text.slice(3, text.indexOf('\n---', 3)) : ''; + if (!/^status:\s*(live|soon|archived|mixed)\s*$/m.test(fm)) problems.push(`${rel}: missing or invalid \`status:\` in frontmatter`); + if (!/^checked:\s*"?\d{4}-\d{2}-\d{2}"?\s*$/m.test(fm)) problems.push(`${rel}: missing \`checked: YYYY-MM-DD\` in frontmatter`); + + let inCode = false; + lines.forEach((line, i) => { + const n = i + 1; + if (line.trim().startsWith('```')) inCode = !inCode; + if (inCode) return; + if (/[—–]/.test(line)) problems.push(`${rel}:${n}: em or en dash`); + if (/\s--\s/.test(line)) problems.push(`${rel}:${n}: spaced double hyphen`); + const lower = line.toLowerCase(); + for (const w of BANNED) { + if (new RegExp(`\\b${w}\\b`).test(lower)) problems.push(`${rel}:${n}: banned word "${w}"`); + } + if (DIAGRAM.test(line)) { + const window = lines.slice(i + 1, i + 8).join('\n'); + const hasFallback = /^\s*([-*]\s|\d+\.\s|\|)/m.test(window); + if (!hasFallback) problems.push(`${rel}:${n}: diagram without an "In words" list or table within 6 lines`); + } + }); +} + +if (problems.length) { + console.error(`check-docs: ${problems.length} problem(s) in ${checked} page(s)\n` + problems.map((p) => ` ${p}`).join('\n')); + process.exit(1); +} +console.log(`check-docs: ${checked} page(s) ok`); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 32a17b5..0ca0054 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,13 @@ import { RootProvider } from 'fumadocs-ui/provider/next'; import './global.css'; import { Instrument_Serif, DM_Sans, JetBrains_Mono } from 'next/font/google'; +import type { Metadata } from 'next'; +import { SITE } from '@/lib/shared'; + +export const metadata: Metadata = { + metadataBase: new URL(SITE), + title: { default: 'Solrouter Docs', template: '%s | Solrouter Docs' }, +}; // Brand fonts from docs.solrouter.com: Instrument Serif (display), DM Sans (body), JetBrains Mono (code). const instrument = Instrument_Serif({ diff --git a/src/app/llms-full.txt/route.ts b/src/app/llms-full.txt/route.ts index d494d2c..561bcc5 100644 --- a/src/app/llms-full.txt/route.ts +++ b/src/app/llms-full.txt/route.ts @@ -2,9 +2,19 @@ import { getLLMText, source } from '@/lib/source'; export const revalidate = false; +type Page = ReturnType[number]; + +// Order for text readers: Introduction first, then the other hand-written pages, +// then the generated Agent Privacy API endpoint pages last. +function rank(page: Page): number { + if (page.slugs.length === 0) return 0; + if (page.slugs[0] === 'api-reference' && page.slugs[1] === 'agent-privacy') return 2; + return 1; +} + export async function GET() { - const scan = source.getPages().map(getLLMText); - const scanned = await Promise.all(scan); + const ordered = [...source.getPages()].sort((a, b) => rank(a) - rank(b)); + const scanned = await Promise.all(ordered.map(getLLMText)); return new Response(scanned.join('\n\n')); } diff --git a/src/app/robots.ts b/src/app/robots.ts new file mode 100644 index 0000000..7677537 --- /dev/null +++ b/src/app/robots.ts @@ -0,0 +1,9 @@ +import type { MetadataRoute } from 'next'; +import { SITE } from '@/lib/shared'; + +export default function robots(): MetadataRoute.Robots { + return { + rules: { userAgent: '*', allow: '/' }, + sitemap: `${SITE}/sitemap.xml`, + }; +} diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts new file mode 100644 index 0000000..4a8c0d2 --- /dev/null +++ b/src/app/sitemap.ts @@ -0,0 +1,14 @@ +import type { MetadataRoute } from 'next'; +import { source } from '@/lib/source'; +import { SITE } from '@/lib/shared'; + +export const revalidate = false; + +export default function sitemap(): MetadataRoute.Sitemap { + const pages = source.getPages().map((page) => ({ + url: new URL(page.url, SITE).toString(), + lastModified: page.data.checked ? new Date(page.data.checked) : undefined, + })); + + return [{ url: `${SITE}/` }, { url: `${SITE}/llms.txt` }, ...pages]; +} diff --git a/src/lib/openapi.ts b/src/lib/openapi.ts index bf52858..6255e15 100644 --- a/src/lib/openapi.ts +++ b/src/lib/openapi.ts @@ -2,7 +2,7 @@ import { createOpenAPI } from 'fumadocs-openapi/server'; // Agent Privacy API schema (Solrouter /agents/v1). Snapshot committed at // ./openapi/agent-privacy.json — refresh it from the live spec when the API changes: -// curl https://solrouter-obb4.onrender.com/agents/v1/openapi.json -o openapi/agent-privacy.json +// curl https://api.solrouter.com/agents/v1/openapi.json -o openapi/agent-privacy.json export const openapi = createOpenAPI({ input: ['./openapi/agent-privacy.json'], }); diff --git a/src/lib/shared.ts b/src/lib/shared.ts index bc922e1..9821e5e 100644 --- a/src/lib/shared.ts +++ b/src/lib/shared.ts @@ -1,4 +1,5 @@ export const appName = 'Solrouter'; +export const SITE = 'https://docs.solrouter.com'; export const docsRoute = '/docs'; export const docsImageRoute = '/og/docs'; export const docsContentRoute = '/llms.mdx/docs'; From 5296f501c1096e440c5c2e0ba17d6c2899e98514 Mon Sep 17 00:00:00 2001 From: sarthib7 Date: Wed, 26 Aug 2026 18:11:04 +0200 Subject: [PATCH 3/7] docs(wip): newcomer layer, verify page, and interactive maps (unreviewed drafts) New pages: what-is-a-tee, what-is-private, verify-a-reply, use-cases, glossary, under-the-hood (architecture map), under-the-hood/request-flow, under-the-hood/agent-reasoning. Introduction gains the before/after diagram and Start-here cards. Sidebar lists the new pages. New components: sealed-room, plaintext-zones, typical-vs-solrouter (static SVG), key-quote-inspector (live check of /tee/public-key against /tee/attestation in the browser), and the React Flow scaffolding (flow-canvas, box-node, flow-figure, data for the system map, the ten-step request flow, and the 44-node skill graph copied from skillGraphEngine.js). React Flow token overrides appended to global.css. Status: drafts. Truth-checked by their authors against code, type-check and build pass, not yet reviewed by a second pass and not yet checked in a browser. Do not merge before that review. --- content/docs/glossary.mdx | 178 +++++ content/docs/meta.json | 8 +- .../docs/under-the-hood/agent-reasoning.mdx | 50 ++ content/docs/under-the-hood/index.mdx | 41 ++ content/docs/under-the-hood/meta.json | 5 + content/docs/under-the-hood/request-flow.mdx | 44 ++ content/docs/use-cases.mdx | 234 ++++++ content/docs/verify-a-reply.mdx | 106 +++ content/docs/what-is-a-tee.mdx | 81 +++ content/docs/what-is-private.mdx | 195 +++++ src/app/global.css | 24 + src/components/diagrams/architecture-map.tsx | 14 + src/components/diagrams/plaintext-zones.tsx | 106 +++ .../diagrams/request-flow-stepper.tsx | 19 + src/components/diagrams/sealed-room.tsx | 98 +++ src/components/diagrams/skill-graph-map.tsx | 19 + src/components/flow/box-node.tsx | 76 ++ src/components/flow/data/request-flow.ts | 104 +++ src/components/flow/data/skill-graph.ts | 675 ++++++++++++++++++ src/components/flow/data/system-map.ts | 148 ++++ src/components/flow/data/types.ts | 76 ++ src/components/flow/flow-canvas.tsx | 113 +++ src/components/flow/flow-figure.tsx | 190 +++++ src/components/verify/key-quote-inspector.tsx | 150 ++++ 24 files changed, 2753 insertions(+), 1 deletion(-) create mode 100644 content/docs/glossary.mdx create mode 100644 content/docs/under-the-hood/agent-reasoning.mdx create mode 100644 content/docs/under-the-hood/index.mdx create mode 100644 content/docs/under-the-hood/meta.json create mode 100644 content/docs/under-the-hood/request-flow.mdx create mode 100644 content/docs/use-cases.mdx create mode 100644 content/docs/verify-a-reply.mdx create mode 100644 content/docs/what-is-a-tee.mdx create mode 100644 content/docs/what-is-private.mdx create mode 100644 src/components/diagrams/architecture-map.tsx create mode 100644 src/components/diagrams/plaintext-zones.tsx create mode 100644 src/components/diagrams/request-flow-stepper.tsx create mode 100644 src/components/diagrams/sealed-room.tsx create mode 100644 src/components/diagrams/skill-graph-map.tsx create mode 100644 src/components/flow/box-node.tsx create mode 100644 src/components/flow/data/request-flow.ts create mode 100644 src/components/flow/data/skill-graph.ts create mode 100644 src/components/flow/data/system-map.ts create mode 100644 src/components/flow/data/types.ts create mode 100644 src/components/flow/flow-canvas.tsx create mode 100644 src/components/flow/flow-figure.tsx create mode 100644 src/components/verify/key-quote-inspector.tsx diff --git a/content/docs/glossary.mdx b/content/docs/glossary.mdx new file mode 100644 index 0000000..6ec8537 --- /dev/null +++ b/content/docs/glossary.mdx @@ -0,0 +1,178 @@ +--- +title: "Glossary" +icon: BookA +description: "Plain-language definitions of every term used in these docs, in alphabetical order, each with a link to the page that explains it." +status: live +checked: "2026-08-26" +statusNote: "Definitions match the code and the live API on the checked date. Where a term names a feature, its own status (Live, Soon, Archived) is given in the entry." +--- + +This page defines each term in plain words first. The technical name comes after. Every entry links to the page that explains the idea in full. + +Some entries use an analogy. Each analogy also says where it stops being accurate. + +## A + +**A2A agent card.** A small public file that describes what an AI agent can do, in a format other agents can read. Solrouter serves one at `/.well-known/agent-card.json` (Live). See [Discovery documents](/docs/api-reference/discovery). + +**/agent endpoint.** The address (`POST /agent`) where a program sends a research question and gets back an answer built with tools. By default it runs a tool loop. The model decides what to look up, runs a tool, and repeats up to 8 times. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). + +**Agent Privacy API.** A separate set of endpoints under `/agents/v1` built for autonomous software agents. It covers private token swaps (Soon) and pay-per-call encrypted answers with no account (Live). It is not the same thing as the `/agent` endpoint above. See [Agent Privacy API](/docs/products/agent-privacy-api). + +**Agent Tools SDK.** A planned code package, `@solrouter/agent-tools`, that would wrap the Agent Privacy API for developers. It is not published on npm yet (Soon). See [Agent Tools SDK](/docs/products/agent-tools-sdk). + +**Anonymity set.** The group of deposits a mixer cannot tell apart from yours. A bigger group gives more privacy. `GET /agents/v1/anonymity-set` reports the size for a given amount bucket (Live). See [Private swaps internals](/docs/under-the-hood/private-swaps). + +**API key.** A secret string that starts with `sk_solrouter_`. You send it with a request so Solrouter knows which prepaid balance to charge. Treat it like a password. See [Get an API key](/docs/account/api-key). + +**Arcium.** The company whose software library Solrouter uses to encrypt prompts on your device. The chat app labels this "encrypted with Arcium". Arcium also runs a network for computing on encrypted data, which Solrouter does not use for inference today. See [RescueCipher and X25519](/docs/under-the-hood/encryption). + +**Attestation.** A signed statement from the computer chip itself. It says two things. A real Intel chip runs this sealed program, and the program owns this public key. Think of it as a tamper-evident seal on a package. The analogy breaks here. The seal proves the hardware and the key. Solrouter has not published reference values, so you cannot yet prove which program image is inside. See [What is a TEE?](/docs/what-is-a-tee) and [TDX attestation](/docs/under-the-hood/attestation). + +## B + +**Backend.** The ordinary Solrouter servers that receive your request, charge your balance, and forward the encrypted message to the enclave. On the encrypted path the backend holds no key and cannot read your prompt. It is a blind courier. See [What is private here](/docs/what-is-private). + +**BRAID.** Solrouter's guided reasoning feature. A normal agent asks the model what to do at each step. BRAID instead follows a fixed plan (a GRD) and gathers data with tools. Then it calls the model once to write the answer. You request it with `reasoning: 'braid'`. Older material calls this SERV. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). + +## C + +**Ciphertext and plaintext.** Plaintext is text anyone can read. Ciphertext is the scrambled form that only a key holder can turn back into text. On the encrypted path your prompt leaves your device as ciphertext. See [What is private here](/docs/what-is-private). + +**Confidential VM (CVM).** A virtual computer whose memory the chip encrypts. The owner of the physical machine cannot look inside it. Solrouter's enclave is a CVM on Intel TDX hardware hosted by Phala. See [What is a TEE?](/docs/what-is-a-tee). + +## D + +**DEK and KEK.** Two keys used for managed swap wallets. The DEK (data encryption key) locks one wallet's secret. The KEK (key encryption key) is a wrapping key the backend holds, and it locks the DEK. Both are handled in the backend process, not in the enclave. See [Private swaps internals](/docs/under-the-hood/private-swaps). + +**Discovery documents.** Public files a program can fetch to learn what Solrouter offers and what each call costs, without reading these docs. They include the A2A agent card, the x402 manifest, an OpenAPI file (a machine-readable list of endpoints), and `/agents/v1/capabilities`. All are Live. See [Discovery documents](/docs/api-reference/discovery). + +## E + +**ed25519 signature.** A digital signature scheme. The enclave creates an ed25519 signing key at boot and uses it to sign the encryption proof for each private reply. The signature lets anyone check that the enclave, and not the backend, produced the receipt. See [On-chain encryption proof](/docs/under-the-hood/encryption-proof). + +**Enclave.** The sealed program that decrypts your prompt. In these docs "enclave" and "Confidential VM" mean the same running service. Picture a locked room with one mail slot: encrypted letters go in, encrypted replies come out. The analogy breaks here: the enclave sends your decrypted prompt to a GPU computer outside the room to run the model. See [What is a TEE?](/docs/what-is-a-tee). + +**Encryption proof.** A receipt for one private reply, written to the Solana blockchain. The enclave signs a summary of your encrypted prompt, and Solrouter stores it in a compressed account. Anyone with the lock link can check it. It proves the enclave handled that exact ciphertext. It does not prove what the model said. See [Check a reply yourself](/docs/verify-a-reply). + +## F + +**Facilitator (x402).** The third-party service that checks and settles a pay-per-call payment. In production the manifest names Coinbase's facilitator. See [x402 payments](/docs/under-the-hood/x402). + +**FDV (fully diluted valuation).** The value of every token that will ever exist, at today's price. Solrouter's fundraising sells tokens in steps tied to FDV bands. See [$ROUTER token](/docs/account/token). + +**FHE, MPC, and ZK.** Three families of maths for working with data while it stays encrypted. Solrouter does not use any of them to run the model today. The cipher it uses was chosen so a future move in that direction would not change the client side. See [RescueCipher and X25519](/docs/under-the-hood/encryption). + +## G + +**GRD (Guided Reasoning Diagram).** A fixed plan for one kind of question. It tells BRAID which tools to run and in what order. Six exist: comparison, DeFi analysis, general research, market overview, token research, and wallet analysis. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). + +**Guest mode.** Using the chat app without a wallet. Guests get 5 free messages per day per network address. See [Chat app](/docs/products/chat-app). + +## I + +**Intel DCAP.** Intel's free software for checking that a TDX quote came from real Intel hardware. A security researcher can run it against the quote Solrouter returns. See [Check a reply yourself](/docs/verify-a-reply). + +**Intel TDX.** The Intel chip feature that creates Confidential VMs and signs attestation quotes. TDX stands for Trust Domain Extensions. Solrouter's enclave reports its type as `INTEL-TDX-PHALA`. See [What is a TEE?](/docs/what-is-a-tee). + +## J + +**Jupiter.** A Solana service that finds the best price across many exchanges for a token swap. The private swap worker uses Jupiter for the swap step (Soon). See [Private swaps internals](/docs/under-the-hood/private-swaps). + +## L + +**Lamports.** The smallest unit of SOL, Solana's native coin. One SOL is one billion lamports. API amounts are given in these base units, so `10000000` means 0.01 SOL. See [Agent Privacy API](/docs/products/agent-privacy-api). + +**Light Protocol compressed account.** A cheap kind of record on the Solana blockchain. Solrouter stores each encryption proof in one. Older Solrouter material called this record a "PDA". The current record is a compressed account. Its address is derived from the hash of your ciphertext. See [On-chain encryption proof](/docs/under-the-hood/encryption-proof). + +**Liquidity pool.** A shared pot of two tokens on an exchange that lets people trade one for the other at any time. Part of the $ROUTER supply is placed in one at launch. See [$ROUTER token](/docs/account/token). + +## M + +**Managed wallet (Mode A).** A swap mode where Solrouter creates and holds a wallet for your agent. You fund it once and run swaps from it. Solrouter holds the key, so this is custody, not self-custody. Swap execution is Soon. See [Agent Privacy API](/docs/products/agent-privacy-api). + +**Maximum Privacy Mode.** A chat app setting. When it is on, your messages are encrypted on your device and never stored. Refresh the page and the conversation is gone. It is off by default. See [Chat app](/docs/products/chat-app). + +**MCP (Model Context Protocol).** A standard that lets desktop AI apps such as Claude Desktop or Cursor call outside tools. Solrouter's MCP server adds its tools to those apps (Live). Only some of those tools use the encrypted path. See [MCP server](/docs/products/mcp-server). + +**Memory (wallet-encrypted).** A chat app feature that remembers facts across conversations. The facts are encrypted with a key made from your wallet's signature, so only your wallet can unlock them. The backend stores only the sealed form. See [Chat app](/docs/products/chat-app). + +**Mint address.** The unique on-chain address that identifies one token type on Solana, such as USDC or $ROUTER. Swap requests name tokens by mint address. See [Agent Privacy API](/docs/products/agent-privacy-api). + +**Mixer.** A shared on-chain pool. It breaks the link between the wallet that puts money in and the wallet that takes it out. Picture many people dropping same-size envelopes into one box, then each taking one out. The analogy breaks here. The fact that you used the box is public. Amounts at the edges of the pool are visible. Solrouter uses the Umbra mixer (Soon). See [Private swaps internals](/docs/under-the-hood/private-swaps). + +## N + +**Nonce.** A random number used once per encrypted message so two identical prompts never produce the same ciphertext. The nonce is stored in the encryption proof. See [RescueCipher and X25519](/docs/under-the-hood/encryption). + +**Nosana GPU node.** A rented computer with a graphics card on the Nosana network. It runs the AI model. The enclave sends it your decrypted prompt over an encrypted connection. The model runs outside the enclave, so the node operator could read the prompt during that moment. Solrouter does not control that hardware, and the request is not tied to your identity there. See [What is private here](/docs/what-is-private). + +**Nosana job.** One running task on the Nosana network. Solrouter runs each model as its own job, so each model has its own node and address. After idle time a node can answer "Nosana GPU node is warming up"; wait and retry. See [Models and Nosana nodes](/docs/under-the-hood/models). + +## O + +**Ollama.** Free software that runs open-weight models on a computer and answers requests in the common OpenAI format. Each Nosana node runs Ollama to serve its model. See [Models and Nosana nodes](/docs/under-the-hood/models). + +**One-shot swap (Mode B).** A swap mode where your agent keeps its own wallet. Solrouter returns an unsigned funding transaction, your agent signs it, and a worker does the rest. Swap execution is Soon. See [Agent Privacy API](/docs/products/agent-privacy-api). + +**Open-weight model.** An AI model whose files are public, so anyone can download and run it on their own hardware. This is what makes private hosting possible. Solrouter runs `gpt-oss:20b` (Live), `qwen3.8:27b` (Live), and `gemma4:31b` (Soon). See [Models and Nosana nodes](/docs/under-the-hood/models). + +## P + +**Persistent Privacy Mode.** The chat app default. Messages are encrypted for transport, then saved so your history survives a reload. Saved history is encrypted at rest with a key the backend holds. That protects against a stolen database copy. It does not hide history from Solrouter. See [Chat app](/docs/products/chat-app). + +**Phala dStack.** The hosting platform that runs Solrouter's Confidential VM on Intel TDX hardware. It also provides the small service (tappd) that hands out attestation quotes. See [TDX attestation](/docs/under-the-hood/attestation). + +**Plaintext mode.** Sending a prompt with `encrypted: false` in the SDK. The prompt travels unencrypted through Solrouter's backend to the same models. You give up every privacy guarantee. It does not unlock any other model. See [Privacy SDK](/docs/products/privacy-sdk). + +**Prepaid balance.** Money you add to your Solrouter account before use, in USDC or $ROUTER. Each call deducts from it. Adding money is called a top-up. See [Pricing and balance](/docs/account/pricing). + +## Q + +**Quote (TDX quote).** The signed attestation document produced by the Intel chip through Phala's dStack service. It carries a `report_data` field that pins the enclave's public key. `GET /tee/attestation` returns one (Live). A reply's quote is `null` with a `tdxQuoteError` when the enclave cannot reach dStack. See [TDX attestation](/docs/under-the-hood/attestation). + +## R + +**RAG (retrieval-augmented generation).** Asking questions over your own uploaded documents. The chat app splits documents into pieces and finds the relevant pieces before the model answers. Those pieces are stored unencrypted on the backend. See [Chat app](/docs/products/chat-app) and [Data at rest](/docs/under-the-hood/data-at-rest). + +**report_data.** A 64-byte field inside a TDX quote that the enclave fills before the chip signs it. Solrouter puts a hash of its public key there. Two formulas exist. `GET /tee/attestation` pins the X25519 key alone. Per-reply quotes pin the X25519 key together with the ed25519 signing key. See [TDX attestation](/docs/under-the-hood/attestation). + +**RescueCipher.** The cipher (scrambling method) from Arcium that Solrouter uses to encrypt your prompt on your device. It works on numbers in a mathematical field instead of raw bytes. That is why the SDK packs 31 bytes into each number. See [RescueCipher and X25519](/docs/under-the-hood/encryption). + +**$ROUTER.** Solrouter's own token on Solana. You can pay for calls with it instead of USDC. Solrouter can buy it back and burn it with revenue; the configured ratios are not published. See [$ROUTER token](/docs/account/token). + +## S + +**SERV.** The older name for the guided reasoning feature now called BRAID. It is not the same as the "OpenServ" line in the token allocation table, which names a token drop to that community. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). + +**Skill graph.** A set of 44 linked notes with expert knowledge on Solana, DeFi, and research method. When your question matches a note's trigger words, the `/agent` endpoint adds that note to the model's instructions. It runs on the plaintext path only. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). + +**Solana wallet.** An app that holds your Solana keys and signs actions for you. Solrouter uses your wallet as your login. There is no email and no identity check. Phantom, Solflare, or a Privy embedded wallet all work. See [Get an API key](/docs/account/api-key). + +## T + +**tappd.** The small program inside a Phala dStack CVM that asks the Intel chip for a quote. Solrouter's enclave talks to it over a local socket. When the socket is missing, quote requests fail with `tdx_quote_unavailable`. See [TDX attestation](/docs/under-the-hood/attestation). + +**TEE (Trusted Execution Environment).** A sealed area of a computer where code and data are hidden from the machine's owner. Solrouter's TEE is an Intel TDX Confidential VM. Picture a sealed room: the landlord owns the building but cannot see inside. The analogy breaks here: the model runs on a separate GPU computer outside the room. See [What is a TEE?](/docs/what-is-a-tee). + +**TGE (token generation event).** The moment a token first goes live and can be traded. Vesting schedules count from this date. See [$ROUTER token](/docs/account/token). + +## U + +**Umbra.** The Solana privacy protocol whose mixer Solrouter uses for private swaps (Soon). The MCP tools that start with `umbra_` move real funds. See [Private swaps internals](/docs/under-the-hood/private-swaps). + +**USDC.** A digital dollar on Solana. One USDC is meant to stay worth one US dollar. Solrouter prices calls in USDC and accepts it for top-ups and pay-per-call payments. See [Pricing and balance](/docs/account/pricing). + +## V + +**Vesting (cliff and linear).** Rules for when locked tokens become spendable. A cliff is a waiting period with no release. Linear vesting releases an equal amount at each step after that. See [$ROUTER token](/docs/account/token). + +## W + +**Wallet address.** The public name of a wallet, a long string of letters and numbers. You can share it to receive funds. It reveals nothing secret, but everything sent to it is visible on the public ledger. See [Get an API key](/docs/account/api-key). + +## X + +**X25519.** A method for two parties to agree on a shared secret key without ever sending it. Your device makes a fresh, single-use keypair per session and combines it with the enclave's public key. The enclave's key is made at boot and changes on every restart. The enclave's X25519 key is also called the sealing key. See [RescueCipher and X25519](/docs/under-the-hood/encryption). + +**x402.** A way to pay for one web request at the moment you make it. It uses the HTTP status code 402 ("payment required"). The server answers with a price, your agent pays in USDC, and the request goes through. No account and no API key is needed. Encrypted x402 inference costs 0.005 USDC per call (Live). See [x402 payments](/docs/under-the-hood/x402). diff --git a/content/docs/meta.json b/content/docs/meta.json index 7d0ce3f..46c065d 100644 --- a/content/docs/meta.json +++ b/content/docs/meta.json @@ -1,12 +1,18 @@ { "pages": [ - "---Get Started---", + "---Start here---", "index", + "what-is-a-tee", + "what-is-private", + "verify-a-reply", + "use-cases", "quickstart", + "glossary", "chat-app", "concepts", "develop", "payments", + "under-the-hood", "api-reference" ] } diff --git a/content/docs/under-the-hood/agent-reasoning.mdx b/content/docs/under-the-hood/agent-reasoning.mdx new file mode 100644 index 0000000..d21d5cc --- /dev/null +++ b/content/docs/under-the-hood/agent-reasoning.mdx @@ -0,0 +1,50 @@ +--- +title: "Agent endpoint and guided reasoning" +icon: BrainCircuit +description: "The three ways a request can run through POST /agent, and the 44-node skill graph that shapes the answer." +status: live +checked: "2026-08-26" +statusNote: "The plaintext tool loop and the BRAID path are Live. The encrypted agent path is Live for REST callers and Soon for the SDK." +--- + +import { SkillGraphMap } from '@/components/diagrams/skill-graph-map'; + +`POST /agent` has three paths. The request body picks the path. Older material calls the guided path SERV; the code, the SDK option, and the API value call it BRAID. + +## Three paths + +
+ +| Path | Trigger | Where it runs | Tools | Model calls | Response envelope | SDK support | +| --- | --- | --- | --- | --- | --- | --- | +| Tool loop (default) | `useTools: true` | Backend | 18 tools | Up to 8 (`MAX_ITERATIONS = 8`) | `{ success, reply, toolCalls, usage, iterations, model, provider, billing, freeMessagesRemaining }` | `client.agent()` | +| BRAID guided reasoning | `reasoning: 'braid'` | Backend | Fixed order from one of six Guided Reasoning Diagrams | One synthesis call | `{ success, reply, reasoning: 'braid', braidTrace, usage, iterations, ... }` | `client.chat(prompt, { reasoning: 'braid' })`, plaintext | +| Encrypted agent mode | `encryptedPrompt` | Inside the CVM | 5-tool allowlist (web_search through SearXNG, token_price, trending_tokens, swap_quote, solana_balance) | Loop inside the enclave | Encrypted reply plus attestation | REST only today | + +
+ +Source: `be:routes/agent.js:273-291,296-348,375,414-420,561-572`; `be:lib/agentService.js:91-418,1092,1196`; `be:tee-service/src/tools.js:28-34`; `be:openserv-agent/src/braid/grds/`. + +## The skill graph + +Before the synthesis call, the engine matches your query against 44 knowledge nodes. Matched nodes and their neighbours add domain notes to the system prompt. The response does not include the walked path. Simple queries skip the graph. + +Click a node to see its edges and its line in the engine. The highlighted example is the traversal for a DeFi protocol comparison. + + + +**In words** + +- Research and analysis (15): research-core, source-eval, defi-analysis, liquidity-risk, token-economics, market-analysis, on-chain-analysis, wallet-analysis, privacy-research, risk-assessment, smart-contract-risk, comparative-analysis, data-synthesis, colosseum-research, colosseum-archives. +- Ecosystem (2): arcium-mpc, solana-ecosystem. +- DeFi protocols (10): jupiter-defi, raydium-defi, orca-defi, meteora-defi, kamino-defi, sanctum-staking, pump-fun, lulo-lending, ranger-perps, prediction-markets. +- Infrastructure and oracles (8): helius-infra, light-protocol-zk, metaplex-nfts, pyth-oracle, switchboard-oracle, squads-multisig, debridge-cross-chain, coingecko-analytics. +- Solana development (9): solana-kit-dev, anchor-dev, pinocchio-dev, framework-kit-frontend, solana-testing, solana-security-audit, token2022-extensions, quicknode-infra, magicblock-gaming. +- Edges are the `edges` arrays in the engine, 136 directed links. Example traversal: defi-analysis, then liquidity-risk, then comparative-analysis. The engine has no direct edge from liquidity-risk to comparative-analysis; the traversal reaches it through defi-analysis. + +Source: `be:lib/skillGraphEngine.js` (44 `id:` entries, `edges` arrays at lines 56, 76, 95, 117 and onward). + +## Read more + +- [SERV Reasoning](/docs/concepts/serv-reasoning) and [Skill Graphs](/docs/concepts/skill-graphs): the current concept pages. They merge into this page in a later update. +- [POST /agent](/docs/api-reference/agent): request and response fields. diff --git a/content/docs/under-the-hood/index.mdx b/content/docs/under-the-hood/index.mdx new file mode 100644 index 0000000..13b44c7 --- /dev/null +++ b/content/docs/under-the-hood/index.mdx @@ -0,0 +1,41 @@ +--- +title: "Architecture map" +icon: Cpu +description: "Every part of Solrouter on one interactive map: what each part holds, what it can see, and where the code lives." +status: mixed +checked: "2026-08-26" +statusNote: "Every node carries its own status. Private swaps are Soon. The rest is Live." +--- + +import { Cards, Card } from 'fumadocs-ui/components/card'; +import { ArchitectureMap } from '@/components/diagrams/architecture-map'; + +This section is for engineers and auditors. Every page here cites the code that backs each claim. + +Click a node to see what it holds, what it can see, and the source file. Pan by dragging. Pinch to zoom. + + + +**In words** + +- Chat app (solrouter.com/chat): holds your wallet session and, in Maximum Privacy Mode, encrypts each prompt in the browser. In the default mode it sends plaintext to the backend. +- `@solrouter/sdk`: holds your API key and encrypts by default. Sends the ciphertext bundle plus the API key, model id, and chat id in plaintext. +- `@solrouter/mcp-server` (your machine): holds `SOLROUTER_API_KEY`, `SOLROUTER_API_URL`, and `BRAVE_API_KEY`. Four tools use the encrypted path for the model step. Search and market lookups go to third parties in plaintext. +- REST and x402 clients: send whatever they build. `POST /api/v1/chat/completions` and `/tee/process` require `encryptedPrompt`; `/agent` accepts plaintext or `encryptedPrompt`. +- Solrouter backend (Render, behind api.solrouter.com): checks the key, bills, runs the x402 paywall, relays ciphertext to the CVM, and commits receipts with its deployer wallet. It holds no decryption key on the encrypted path. +- Intel TDX CVM on Phala dStack: generates an X25519 sealing key and an ed25519 signing key at boot, decrypts with RescueCipher, requests TDX quotes from the tappd agent, runs a 5-tool allowlist for encrypted agent mode, and hosts SearXNG in the same CVM. +- Nosana GPU node, one per model: runs the open-weight model in Ollama and sees the prompt and reply during inference. Solrouter does not control that hardware. +- Solana: holds one Light Protocol compressed receipt per private inference under program `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`. Cluster: mainnet by the code comments and explorer links, not re-verified with an on-chain read. +- Umbra mixer plus Jupiter (Soon): the private-swap path. The backend orchestrates it; no mainnet run is confirmed. +- x402 facilitator: the live manifest advertises Coinbase. Which facilitator settles a payment is set on the server and is not visible from outside. The agent never talks to the facilitator. + +## Deep dives + + + + + + + + + diff --git a/content/docs/under-the-hood/meta.json b/content/docs/under-the-hood/meta.json new file mode 100644 index 0000000..79ce64e --- /dev/null +++ b/content/docs/under-the-hood/meta.json @@ -0,0 +1,5 @@ +{ + "title": "Under the hood", + "icon": "Cpu", + "pages": ["index", "request-flow", "agent-reasoning"] +} diff --git a/content/docs/under-the-hood/request-flow.mdx b/content/docs/under-the-hood/request-flow.mdx new file mode 100644 index 0000000..cd2dcbf --- /dev/null +++ b/content/docs/under-the-hood/request-flow.mdx @@ -0,0 +1,44 @@ +--- +title: "The TEE request flow" +icon: Workflow +description: "What happens to one encrypted request, hop by hop, with the exact payload at each step." +status: live +checked: "2026-08-26" +--- + +import { RequestFlowStepper } from '@/components/diagrams/request-flow-stepper'; +import { EncryptionFlow } from '@/components/diagrams/encryption-flow'; + +This page follows one `client.chat()` call through the SDK, the Solrouter backend, the Intel TDX enclave, the Nosana GPU node, and back. Use the step list to walk the map. Each step names its payload and source file. + + + +**In words** + +1. `GET /tee/public-key` returns `{publicKey, publicKeySha256, algorithm, teeType}`. The SDK caches it for the life of the process (`be:packages/sdk/src/encryption.ts:55-75`). +2. The SDK makes an ephemeral X25519 keypair and derives the shared secret with the enclave key. +3. RescueCipher encrypts the prompt in packed-31 form. The bundle is `{ciphertext, nonce, publicKey, version: '2.0-packed31'}`. +4. `POST /tee/process` with a Bearer key. What leaves the machine: the ciphertext bundle plus, in plaintext, the API key, model id, `chatId`, and the optional `systemPrompt`, `useRAG`, `ragCollection`, `useLiveSearch` (`be:packages/sdk/src/client.ts:180-193`). +5. The backend forwards `{encryptedPrompt, model, privacyAttestationId}` unchanged (`be:routes/tee.js:44-53`). +6. The CVM derives the shared secret with its X25519 private key and decrypts. +7. The CVM calls the configured Nosana endpoint at `/v1/chat/completions` with the plaintext prompt. HTTPS per the documented node URL, not re-verified (`be:tee-service/src/index.js:461-473`). +8. The CVM encrypts the reply to your key, signs the `SOLR-ATTEST-v2` tuple, and requests a tappd quote with `report_data = sha256(x25519 || ed25519)`. +9. The backend commits the compressed receipt and returns `{success, encryptedResponse, attestation, encryptionProof, requestId, metadata, backendRole: 'BLIND_RELAY', onchainAttestation, privacyProof}` (`be:routes/tee.js:84-101`). +10. The SDK decrypts `encryptedResponse` with the session private key. + +## The short picture + + + +**In words** + +- Your device encrypts. The backend relays ciphertext. The enclave decrypts. The Nosana node runs the model in plaintext. The reply returns encrypted. + +## Key custody + +- Your device: an ephemeral X25519 private key per session. Never sent. +- Solrouter backend: no key on this path. +- Enclave: an X25519 sealing key and an ed25519 signing key, generated at boot and never exported (`be:tee-service/src/index.js:76-91`). +- Nosana node: no key. It receives plaintext from the enclave. + +Source: `be:routes/tee.js:33-101`, `be:tee-service/src/index.js:76-101,217-238,461-473`, `be:packages/sdk/src/encryption.ts:52-142`. diff --git a/content/docs/use-cases.mdx b/content/docs/use-cases.mdx new file mode 100644 index 0000000..3c11049 --- /dev/null +++ b/content/docs/use-cases.mdx @@ -0,0 +1,234 @@ +--- +title: "Use cases" +icon: Compass +description: "What people, developers, agents, traders, and teams do with Solrouter today, and what is still on the way." +status: mixed +checked: "2026-08-26" +statusNote: "Each card carries its own Status word. The Live cards for privacy modes, memory, guest mode, and team accounts depend on the owner's browser check in PR 5 (design decisions 12 and 13)." +--- + +import { Cards, Card } from 'fumadocs-ui/components/card'; +import { Callout } from 'fumadocs-ui/components/callout'; +import { + FileText, + Code, + PenLine, + History, + Brain, + UserX, + Paperclip, + BadgeCheck, + Shield, + Plug, + Route, + Lock, + Wallet, + Coins, + ArrowLeftRight, + Repeat, + Globe, + FolderOpen, + Users, +} from 'lucide-react'; + +Every card below starts with one Status word that we checked against the code +on 2026-08-26. **Live** works today, **Soon** exists in code but is not switched +on or not confirmed end to end, and **Archived** was removed. + +Many cards mention a sealed machine. That is a TEE (Trusted Execution +Environment): a computer that its own operator cannot look inside while it +runs. [What is a TEE?](/docs/what-is-a-tee) explains it with a picture. Other +terms, such as [wallet](/docs/glossary), [x402](/docs/glossary), and +[mixer](/docs/glossary), are in the [Glossary](/docs/glossary). + + + When encryption is on, Solrouter's own servers never see your words in + readable form. The GPU computer that runs the AI model does see them while it + writes the answer. Solrouter does not own that computer. Read + [What is private here](/docs/what-is-private) for the full picture. + + +## For people + +These are for anyone who uses the chat app at solrouter.com. Encryption in the +chat app is a switch called Maximum Privacy mode. It is off when you first +open the app. While it is on, the app keeps no history. + + + } title="Summarise a contract off the record" href="/docs/products/chat-app"> + **Live.** You want a summary of a contract, but you do not want an AI + company to keep a copy. Turn on Maximum Privacy mode and paste the text, + because an attached file is not encrypted. + + } title="Review code without sharing it" href="/docs/products/chat-app"> + **Live.** You want feedback on code that is not public. Turn on Maximum + Privacy mode and paste the code, and Solrouter's servers never see it in + readable form. + + } title="Draft a reply nobody else gets to read" href="/docs/products/chat-app"> + **Live.** You want help with a sensitive message, such as a letter to a + doctor or a lawyer. Turn on Maximum Privacy mode, write the draft, and + close the tab, because nothing is saved. + + } title="Keep your history, or keep nothing" href="/docs/products/chat-app"> + **Live.** Some chats should survive a reload and others should leave no + trace. Persistent mode saves history encrypted with a key that Solrouter + holds, and Maximum Privacy mode saves nothing at all. + + } title="Memory that only your wallet can unlock" href="/docs/products/chat-app"> + **Live.** You want the assistant to remember facts across chats without + Solrouter holding a readable profile of you. Your wallet signs a fixed + message to make the key, so Solrouter stores only a locked blob. + + } title="Try it with no wallet and no account" href="/docs/products/chat-app"> + **Live.** You will not connect a wallet before you have seen the product + work. Open the guest page and send up to five free messages a day. + + } title="Attach a file to a chat" href="/docs/products/chat-app"> + **Live.** You want to ask about a PDF, a spreadsheet, or a screenshot. You + can attach it, but attachments are not encrypted, and documents are stored + in readable form on a file server. + + } title="Check that a reply came from the sealed machine" href="/docs/verify-a-reply"> + **Live.** A privacy promise on a web page is not proof. Click the lock link + under a reply, or paste it into the checker, and see the receipt on the + Solana blockchain. + + + +**Archived.** Image and video generation was switched off in the chat app, and +the services that made them were removed. + +## For developers + +These are for people who write code and want to add private AI to their own +app, editor, or backend. + + + } title="Encrypted chat from your own app" href="/docs/products/privacy-sdk"> + **Live.** Every mainstream AI service can read what your app sends it, so + you cannot promise your users privacy. Install `@solrouter/sdk`, call + `client.chat()`, and the prompt is encrypted on your machine before it + leaves. + + } title="Private research inside Claude Desktop or Cursor" href="/docs/products/mcp-server"> + **Live.** Research done through a normal assistant shows the model provider + which tokens and wallets you look at. Paste one config block, and only + the synthesis step runs encrypted; web searches and price lookups stay + readable. + + } title="Research with fixed steps instead of a free-running agent" href="/docs/under-the-hood/agent-reasoning"> + **Live.** A normal agent asks the model what to do at every step, which is + slow and hard to predict. Send `reasoning: 'braid'` and the agent walks a + fixed diagram of steps, but on this path your prompt travels in readable + form. + + } title="Run the whole agent loop inside the sealed machine" href="/docs/api-reference/agent"> + **Live** over the REST endpoint, and Soon in the SDK. An agent that searches + the web and reads prices normally shows every question to the server. Send + an encrypted prompt to `POST /agent`, and the loop runs inside the sealed + machine with five approved tools. + + } title="Pay per call with no email, card, or KYC" href="/docs/account/api-key"> + **Live.** Signing up for an AI service means handing over your identity and + a card. Connect a Solana wallet, make an API key, and top up a balance in + USDC or $ROUTER. + + + +## For agents + +These are for software agents that hold a Solana wallet and act on their own, +with no person to manage keys. + + + } title="Pay per call with no API key at all (x402)" href="/docs/products/agent-privacy-api"> + **Live.** An agent that starts on demand has no person to sign up or hold a + key. The first call gets a price back, the agent pays a small amount of + USDC, and the call goes through. + + } title="One private swap from the agent's own wallet" href="/docs/products/agent-privacy-api"> + **Soon.** On Solana every transfer is public, so anyone can link the paying + wallet to where the money went. The agent signs one funding step, and + Solrouter routes the swap through a mixer so the two ends are not linked. + + } title="Repeated private swaps from a managed wallet" href="/docs/products/agent-privacy-api"> + **Soon.** A long-running agent should not sign a fresh funding step for + every trade. Solrouter creates a wallet for the agent, keeps its key locked + on the server, and runs each swap on request. + + } title="Plain HTTP calls and self-discovery" href="/docs/api-reference/inference"> + **Live.** Not every language has an SDK, and some agents find services on + their own. Call the OpenAI-style endpoint directly, and read the discovery + documents that describe every route and price. + + + +## For traders + +These are for people who trade on Solana and want their intent and their wallet +links kept out of provider logs. + + + } title="Swap privately from inside the chat" href="/docs/products/chat-app"> + **Soon.** A trader wants to swap without the public ledger linking their + main wallet to the destination. Type the swap in chat, and a widget walks + you through a mixer step and the swap with your own wallet signing. + + + +**Soon.** Phoenix Copilot: describe a trading strategy in plain English and +test it against past prices inside the sealed machine. + +**Soon.** RouterChan: trade from a Telegram app with a managed wallet and hard +spending limits. + +## For teams + +These are for small companies that want private AI over their own documents +with shared billing. + + + } title="Ask questions over your own documents" href="/docs/products/chat-app"> + **Live.** You want answers grounded in your contracts, specs, or research + instead of the model's general knowledge. Upload files to a knowledge base, + but know that the stored pieces are not encrypted at rest. + + } title="Team accounts with invites and shared funds" href="/docs/products/chat-app"> + **Live.** A company cannot run private AI on one person's wallet. Create an + organization, invite members by link, and see usage per member on one + shared balance. + + + +## All use cases at a glance + +
+ +| Use case | Who | Status | Page | +| --- | --- | --- | --- | +| Summarise a contract off the record | People | Live | [Chat app](/docs/products/chat-app) | +| Review code without sharing it | People | Live | [Chat app](/docs/products/chat-app) | +| Draft a reply nobody else gets to read | People | Live | [Chat app](/docs/products/chat-app) | +| Keep your history, or keep nothing | People | Live | [Chat app](/docs/products/chat-app) | +| Memory that only your wallet can unlock | People | Live | [Chat app](/docs/products/chat-app) | +| Try it with no wallet and no account | People | Live | [Chat app](/docs/products/chat-app) | +| Attach a file to a chat | People | Live, not encrypted | [Chat app](/docs/products/chat-app) | +| Check that a reply came from the sealed machine | People | Live | [Check a reply yourself](/docs/verify-a-reply) | +| Image and video generation | People | Archived | none | +| Encrypted chat from your own app | Developers | Live | [Privacy SDK](/docs/products/privacy-sdk) | +| Private research inside Claude Desktop or Cursor | Developers | Live | [MCP server](/docs/products/mcp-server) | +| Research with fixed steps (BRAID) | Developers | Live, readable prompt | [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning) | +| Run the whole agent loop inside the sealed machine | Developers | Live over REST, Soon in the SDK | [POST /agent](/docs/api-reference/agent) | +| Pay per call with no email, card, or KYC | Developers | Live | [Get an API key](/docs/account/api-key), [Pricing and balance](/docs/account/pricing) | +| Pay per call with no API key at all (x402) | Agents | Live | [Agent Privacy API](/docs/products/agent-privacy-api) | +| One private swap from the agent's own wallet | Agents | Soon | [Agent Privacy API](/docs/products/agent-privacy-api) | +| Repeated private swaps from a managed wallet | Agents | Soon | [Agent Privacy API](/docs/products/agent-privacy-api) | +| Plain HTTP calls and self-discovery | Agents | Live | [Inference endpoints](/docs/api-reference/inference), [Discovery documents](/docs/api-reference/discovery) | +| Swap privately from inside the chat | Traders | Soon | [Chat app](/docs/products/chat-app) | +| Phoenix Copilot | Traders | Soon | none yet | +| RouterChan Telegram app | Traders | Soon | none yet | +| Ask questions over your own documents | Teams | Live, not encrypted at rest | [Chat app](/docs/products/chat-app) | +| Team accounts with invites and shared funds | Teams | Live | [Chat app](/docs/products/chat-app) | + +
diff --git a/content/docs/verify-a-reply.mdx b/content/docs/verify-a-reply.mdx new file mode 100644 index 0000000..767c5f0 --- /dev/null +++ b/content/docs/verify-a-reply.mdx @@ -0,0 +1,106 @@ +--- +title: "Check a reply yourself" +icon: BadgeCheck +description: "Paste the lock link from a private chat reply and watch the check pass in your browser. Then compare the live enclave key with the signed hardware note." +status: live +checked: "2026-08-26" +statusNote: "The receipt check and the live key check run against api.solrouter.com today. Comparing the enclave against published reference measurements is Soon." +--- + +import { Callout } from 'fumadocs-ui/components/callout'; +import { Step, Steps } from 'fumadocs-ui/components/steps'; +import { Cards, Card } from 'fumadocs-ui/components/card'; +import { EncryptionProofVerifier } from '@/components/verify/encryption-proof-verifier'; +import { KeyQuoteInspector } from '@/components/verify/key-quote-inspector'; + +## The question + +"Solrouter says my message was handled inside a sealed computer. How do I know that is true?" + +You do not have to trust the claim. Every private reply comes with a receipt. The receipt lives on the Solana blockchain, and your browser can check it. This page has two checks. The first takes ten seconds. The second takes one click. + +## Check 1: the receipt for one reply + +When you send a message with Maximum Privacy Mode on, or through the Privacy SDK, the sealed computer (the enclave) signs a short receipt. The receipt says: "I received this exact scrambled message." Solrouter then writes the receipt to Solana. Solrouter cannot write a valid receipt on its own, because the signing key exists only inside the enclave. + +In the chat app, every private reply shows a lock icon. Click it and copy the link. Paste the link below. You can also paste the receipt address, the transaction signature, or the 64-character hash of your scrambled prompt. + + + +What a passing check means: + +- A real Intel TDX enclave received your exact scrambled message and signed for it. +- The receipt is on Solana, so nobody can quietly edit it later. +- The signing key is named inside the enclave's hardware note (the attestation), so an impostor cannot sign in its place. + +What it does not mean: + +- It does not prove that the Solana program checked the signature. The program stores the receipt fields; your browser checks the signature. +- It does not verify Intel's full signature chain on the hardware note. Check 3 below covers that. + +## Check 2: the live enclave key + +Your device encrypts every private message to the enclave's public key. This check confirms that the published key is the one named in the live hardware note. + + + +**In words** + +- Your browser fetches the published public key and the live hardware note from `api.solrouter.com`. +- It computes the fingerprint (SHA-256) of the key. +- It compares that fingerprint with the `report_data` field inside the note. +- A match means: the key you encrypt to belongs to this exact enclave, not to a machine in the middle. + + +The enclave makes a fresh key pair each time it starts. The private half never leaves the enclave. If you run this check on two days and see two keys, that is expected. + + +## Check 3: the deep checks + +These steps are for auditors and engineers. They use the same two endpoints plus Intel's tools. + + + +### Fetch the note and the key + +```bash +curl https://api.solrouter.com/tee/public-key +curl https://api.solrouter.com/tee/attestation +``` + +The first call returns `publicKey`, `publicKeySha256`, `algorithm`, and `teeType`. The second returns `teePublicKey`, `teePublicKeySha256`, `reportDataHex`, `tdxQuote`, and `generatedAt`. Both answered on 2026-08-26 with `teeType: "INTEL-TDX-PHALA"`. + + + +### Verify the quote signature + +Run the `tdxQuote.quote` bytes through Intel DCAP tools or a compatible TDX verifier. This proves the note came from real Intel TDX hardware, not from a simulator. + + + +### Check the report_data binding + +For `GET /tee/attestation`, `report_data` equals `sha256(X25519 public key)`. For the per-request note inside a `/tee/process` response, `report_data` equals `sha256(X25519 public key || ed25519 signing key)`. The two formulas are different on purpose: the second one also pins the key that signs receipts. + + + +### Verify one receipt by hand + +Read the receipt with `GET /attestation/by-tx/`. Rebuild the signed bytes: `"SOLR-ATTEST-v2"`, the SHA-256 of the scrambled prompt, the enclave sealing key, the nonce, your ephemeral key, then the model and provider names with one length byte each. Verify the 64-byte signature (`enclaveSigR` plus `enclaveSigS`) against `enclavePubkey` with any Ed25519 library. + + + +### Compare the program measurement (Soon) + +A full audit compares the measurements inside the note with published reference values for Solrouter's enclave image. Solrouter does not publish those values yet. Until it does, these checks prove real hardware and correct key binding, but not which image is running. + + + +## Read more + + + + + + + diff --git a/content/docs/what-is-a-tee.mdx b/content/docs/what-is-a-tee.mdx new file mode 100644 index 0000000..700223a --- /dev/null +++ b/content/docs/what-is-a-tee.mdx @@ -0,0 +1,81 @@ +--- +title: "What is a TEE?" +icon: ShieldCheck +description: "A TEE is a sealed part of a computer that the cloud operator cannot look into, and this page explains what that means for your prompt." +status: live +checked: "2026-08-26" +statusNote: "Describes the Intel TDX enclave on Phala Cloud that answers at api.solrouter.com/tee/public-key today." +--- + +import { Callout } from 'fumadocs-ui/components/callout'; +import { Card, Cards } from 'fumadocs-ui/components/card'; +import { SealedRoom } from '@/components/diagrams/sealed-room'; + +## The question + +You type a prompt. A computer somewhere works on it. Who can read the prompt while that happens? + +With a normal AI service, the answer is "the company that runs the server". Their staff can read it. Their logs can store it. A TEE changes that answer for one part of the path. + +TEE is short for Trusted Execution Environment. In plain words: a sealed part of a computer. The program inside can work on your data. The people who own the computer cannot look in. See the [glossary](/docs/glossary) for the short form of every term on this page. + +## The sealed room + + + +**In words** + +- A data center holds many computers. One of them runs Solrouter's private inference program. +- That program lives in a sealed room called a Confidential VM. A VM is a virtual machine: one computer pretending to be a separate, smaller computer. +- The room has one locked slot. Only data sealed to the room's public key can go in. Your device seals your prompt to that key before it leaves your machine. +- The room has one window. Through it, anyone can read a signed note that says which program is running inside. That note is the attestation. +- The operator of the data center stands outside. The CPU encrypts everything in the room's memory, so the operator cannot open the door. + +Where the analogy breaks: a real sealed room keeps everything inside. A TEE does not. The program inside can still send data out to other computers. The next sections say where Solrouter's program does that. + +## Two layers + +A TEE gives you two separate promises. + +### Layer 1: isolation + +The CPU chip encrypts the memory of the Confidential VM. The cloud operator, the host operating system, and any other program on the same machine see only scrambled bytes. This is why Solrouter can say its own backend and its cloud host never see your prompt in plain text. + +### Layer 2: attestation + +A sealed room could still hold the wrong program, one that copies your prompt somewhere. Attestation closes that gap. + +The chip signs a short note. The note says: "This exact program is running in this room right now." Anyone can fetch the note and check the signature against Intel's public records. Solrouter also puts a fingerprint of the room's public key inside the note. When you check the note, you also confirm the key belongs to this room. An impostor cannot fake that. + +Think of a tamper-evident seal on a package. The seal shows the package was not opened. It does not tell you what is inside. Attestation proves which program runs. It does not by itself prove the program is a good one. For that you need to compare the note against known reference values. Solrouter does not publish those values yet. Reference measurements: Soon. + +## What a TEE does not do + +A TEE stops outsiders from looking in. It does not stop the program inside from talking out. + +Solrouter's program inside the enclave decrypts your prompt. It then sends the plain-text prompt to a Nosana GPU node, a separate computer that runs the language model. That node is outside the sealed room. The node can see your prompt and the reply during inference. Solrouter does not control that hardware. The request is not linked to your identity on the node. + +Solrouter's own deployment file states the same limit. It claims "Solrouter never sees your prompt or searches" and adds "NOT no one sees them". + + +Solrouter's backend and its cloud host cannot read your prompt. The Nosana GPU node that runs the model can, while it works on it. + + +The page [What is private here](/docs/what-is-private) has the full table of who can see what. + +## How Solrouter uses one + +Solrouter runs its enclave as an Intel TDX Confidential VM on Phala Cloud. TDX is Intel's name for this kind of sealed VM. Phala Cloud is the hosting service that provides the TDX machines. + +When the enclave boots, it makes a fresh key pair inside the sealed room. The private half never leaves. The public half is published at `GET https://api.solrouter.com/tee/public-key`. That reply names the enclave type as `INTEL-TDX-PHALA`. The key changes on every reboot, so a copied key from last week is useless. + +The signed note comes from `GET https://api.solrouter.com/tee/attestation`. Solrouter's program asks the Phala guest agent inside the same Confidential VM for it, then passes it to you unchanged. + +## Check it yourself + +You do not have to take Solrouter's word for any of this. + + + + + diff --git a/content/docs/what-is-private.mdx b/content/docs/what-is-private.mdx new file mode 100644 index 0000000..40dfd93 --- /dev/null +++ b/content/docs/what-is-private.mdx @@ -0,0 +1,195 @@ +--- +title: "What is private here" +icon: EyeOff +description: "Who can read your prompt, your files, and your history, and what Solrouter keeps on its servers." +status: mixed +checked: "2026-08-26" +statusNote: "Each row states its own Live or Soon status. Cells were checked against the product code and the live API on 2026-08-26." +--- + +import { Callout } from 'fumadocs-ui/components/callout'; +import { Cards, Card } from 'fumadocs-ui/components/card'; +import { Lock, ShieldCheck, BookOpen, MessageSquare } from 'lucide-react'; +import { PlaintextZones } from '@/components/diagrams/plaintext-zones'; + +## The question + +"If I type something private into Solrouter, who can read it?" + +With encryption on, Solrouter's own servers cannot read your prompt or the reply. The prompt is opened only inside a sealed computer (a TEE) and on the rented GPU machine that runs the AI model. Your attached files, your knowledge base, and your saved chat history do not get that protection, and the tables below show exactly where each one is readable. + +Encryption is a toggle in the chat app. It is off by default. The Privacy SDK encrypts by default. The page [Chat app](/docs/products/chat-app) explains the toggle. This page explains what each setting exposes. + +## Who is who + +The tables use six parties. Here is each one in plain words. + +- **You.** Your browser, or the program that uses the SDK. +- **Network observer.** Anyone who watches the connection between you and Solrouter. For example, your internet provider or the owner of a public Wi-Fi. +- **Solrouter backend.** Solrouter's own servers. They check your login, take payment, store your history, and pass messages along. +- **CVM cloud host (Phala).** The company that owns the physical machine where the sealed computer runs. The sealed computer is a Confidential Virtual Machine (CVM). The processor encrypts its memory, so the machine owner cannot read it. See [What is a TEE?](/docs/what-is-a-tee). +- **Nosana GPU host.** The operator of the graphics-card machine that runs the AI model. Solrouter rents it from the Nosana network. Solrouter does not control that hardware. +- **Solana observer.** Anyone who reads the public Solana blockchain. Solrouter posts a receipt there for each encrypted request. + +Each cell uses one of these words. + +- **plaintext.** This party can read it as you wrote it. +- **ciphertext.** This party sees it only in scrambled form and has no key to unscramble it. +- **encrypted at rest with a Solrouter key.** Stored scrambled, but Solrouter holds the key and can unscramble it. Think of a locked cabinet in Solrouter's office. A thief who steals the cabinet gets nothing. Solrouter has the key. Where the picture breaks: the key is a text value in the server settings, so the same servers that hold the cabinet also hold the key. +- **hash only.** A fixed-length fingerprint of the scrambled data. It cannot be turned back into your words. Think of a wax seal: it shows the same blob was seen, not what the blob says. Where the picture breaks: a seal can be forged, a hash of a different blob never matches. +- **metadata only.** Facts about the message, such as size, time, or destination, but not the content. +- **nothing.** It never reaches this party. +- **not determined.** We could not confirm this from the code. + +Footnotes point at lines in the product code. `be:` means the backend folder, `fe:` means the web app folder. The code is private, so the references exist so the Solrouter team can re-check each cell in one step. + +## Who can see what + +Rows for prompt text, reply text, memory, and web searches describe the encrypted path. That path is the Privacy SDK with its default settings, and the chat app with Maximum Privacy Mode on. The two history rows describe each chat mode by name. Every row here is Live except where a footnote says Soon. + +
+ +| What | You | Network observer | Solrouter backend | CVM cloud host (Phala) | Nosana GPU host | Solana observer | +| --- | --- | --- | --- | --- | --- | --- | +| Prompt text | plaintext | ciphertext (1) | ciphertext (2) | ciphertext (3) | plaintext (4) | hash only (5) | +| Reply text | plaintext | ciphertext (1) | ciphertext (6) | ciphertext (3) | plaintext (4) | nothing | +| Attached files | plaintext | nothing (7) | nothing (7) | nothing | nothing | nothing | +| Knowledge-base documents | plaintext | ciphertext (1) | plaintext (8) | nothing | nothing (9) | nothing | +| Chat history (Persistent mode) | plaintext | ciphertext (1) | encrypted at rest with a Solrouter key (10) | nothing | plaintext (11) | nothing | +| Chat history (Maximum Privacy mode) | plaintext (12) | nothing | nothing (12) | nothing | nothing | nothing | +| Memory | plaintext | ciphertext (13) | ciphertext (13) | ciphertext (14) | plaintext (14) | nothing | +| Wallet address | plaintext | nothing (1) | plaintext (15) | nothing (16) | nothing (16) | nothing (17) | +| Model name | plaintext | nothing (1) | plaintext (18) | metadata only (19) | plaintext (4) | plaintext (20) | +| That you used Solrouter and when | plaintext | metadata only (21) | plaintext (15) | metadata only (22) | metadata only (22) | metadata only (20) | +| Encrypted-prompt hash | hash only (23) | nothing (1) | hash only (23) | nothing | nothing | hash only (5) | +| Web searches in agent mode | plaintext | ciphertext (24) | ciphertext (24) | metadata only (25) | plaintext (26) | nothing (27) | + +
+ +Footnotes: + +1. The connection to `api.solrouter.com` uses HTTPS. A watcher sees scrambled traffic plus its size and timing. On the encrypted path your browser or program scrambles the prompt a second time before it leaves. Source: live `https://api.solrouter.com/tee/public-key` answered on 2026-08-26; `fe:src/utils/arciumClient.ts:181-189`; `be:packages/sdk/src/client.ts:180-192`. +2. The backend receives the field `encryptedPrompt` and forwards it unchanged. It holds no key. The private key is made inside the CVM at boot and never leaves it. Source: `be:routes/tee.js:44-53`; `be:tee-service/src/index.js:77-86`. +3. The processor encrypts the memory of the CVM. The machine owner sees encrypted memory and encrypted network traffic. The owner can see where the CVM sends traffic and when. Source: `be:tee-service/docker-compose.yml:1,15-21`. +4. The CVM sends the unscrambled prompt to the model node as normal text and receives the reply as normal text. The node runs the model outside the CVM. The node operator could read both at that moment. Solrouter does not control that hardware. The request is not linked to your identity on the node. Source: `be:tee-service/src/index.js:461-475`; `be:tee-service/docker-compose.yml:26-27`. +5. Solrouter posts a receipt on Solana with the SHA-256 hash of the scrambled blob, not of your words. Source: `be:services/lightAttestation.js:208-221,310`; `be:routes/tee.js:57-73`. +6. The reply returns to the backend scrambled with your session key. One caveat: the enclave writes the first 50 characters of each reply to its own log. Who can read that log: not determined. Source: `be:tee-service/src/index.js:577,586-589`; `be:routes/tee.js:84-86`. +7. The encrypted path sends the text prompt only. Attachments do not travel on it. The default-chat table below shows where attachments go. Source: `fe:src/components/chat/ChatArea.tsx:569`. +8. Knowledge-base files are split into text chunks on Solrouter's server and stored there as plain text, one JSON file per collection. The embeddings are computed on the same server, so no outside embedding service sees them. The code does not tie a collection to your account. Whether the deployed app isolates collections per user: not determined. Source: `be:lib/ragService.js:16-46`; `be:lib/embeddingService.js:92-115`; `be:routes/rag.js:101-111,235-257`. +9. On the encrypted path the CVM never sees the knowledge base. The backend forwards only the scrambled prompt, the model name, and an attestation id. Source: `be:routes/tee.js:50-54`. +10. Persistent mode is the chat app with the toggle off. Each stored message is scrambled with AES-256-GCM under a key derived from a secret in the backend settings. This protects against a stolen database copy. It does not protect against Solrouter, which holds the key. On this path the backend also reads the prompt in plaintext on the way in. Source: `be:lib/chatCrypto.js:1-15,29-35`; `be:routes/router.js:409-413`. +11. In Persistent mode the backend sends earlier messages of the chat to the model node as context. Source: `be:routes/nosana.js:560`; `be:routes/router.js:305-317`. +12. In Maximum Privacy mode the messages live only in your browser tab. Nothing is stored. A refresh removes them. Source: `fe:src/components/chat/ChatArea.tsx:1127-1132`. +13. Memory is a list of facts you asked Solrouter to remember. Your browser scrambles it with a key derived from your wallet signature and sends only the scrambled blob, prefixed `umem:v1:`. Solrouter never has the key. Lose the wallet, lose the memory. Source: `fe:src/lib/memory.ts:1-18`; `be:routes/memory.js:3-8`. +14. When you send a message, your browser adds the remembered facts to the front of the prompt. On the encrypted path they are scrambled together with the prompt. So the CVM and the model node see them, and the backend does not. Source: `fe:src/components/chat/ChatArea.tsx:546-548`; `fe:src/lib/memory.ts:221-229`. +15. Your wallet is your login. The backend must know it to check your balance and bill you. It also logs the model and the chat id for each request. Source: `be:routes/tee.js:33-48`. +16. The backend does not forward your wallet address to the CVM, and the CVM does not send it to the model node. Source: `be:routes/tee.js:50-54`; `be:tee-service/src/index.js:469-475`. +17. The Solana receipt has a field for a user wallet. Neither the chat app nor the SDK fills it, so it holds all zeros. A raw API caller who sends `userPubkey` would put that address on the public chain. Source: `be:services/lightAttestation.js:253-259,313`; `fe:src/utils/arciumClient.ts:184-188`; `be:packages/sdk/src/client.ts:186-192`. +18. The backend logs the model name with the chat id. Source: `be:routes/tee.js:44-48`. +19. Each model has its own node address. The host can see which address the CVM talks to, and so which model you used, but not what you said. Source: `be:tee-service/docker-compose.yml:20-21,45-49`. +20. The Solana receipt stores the model name, the provider, and a timestamp. Solrouter's own wallet signs it, not yours. Source: `be:services/lightAttestation.js:136-166,310-311`; `be:routes/tee.js:57-59`. +21. A watcher sees your internet address talking to `api.solrouter.com`, the time, and the size. See note 1. +22. The CVM host and the model node see traffic timing from Solrouter's side, not from you. Source: `be:tee-service/docker-compose.yml:20-21,26-27`. +23. The backend computes the hash of the scrambled blob and returns it to you as `encryptedPromptHash`. Source: `be:routes/tee.js:90-97`; `be:services/lightAttestation.js:208-221`. +24. This row is the encrypted agent path: a REST call to `/agent` with `encryptedPrompt`. Live for REST. Soon for the SDK. The whole tool loop runs inside the CVM and the backend relays ciphertext. The chat app's agent mode does not use this path; see the default-chat table. Source: `be:routes/agent.js:295-318`; `be:tee-service/src/index.js:707-736`; `fe:src/components/chat/ChatArea.tsx:331-342,1114`. +25. Web search inside the CVM goes through SearXNG, a search relay that runs in the same CVM. SearXNG then asks public search engines, and those engines receive the search text. The host sees destinations and timing. Source: `be:tee-service/docker-compose.yml:17-21,54-58`; `be:tee-service/src/tools.js:25-34`. +26. The CVM sends the prompt and the search results to the model node as normal text. Source: `be:tee-service/src/index.js:461-475,731-736`. +27. No Solana receipt is written for the encrypted agent path. Source: `be:routes/agent.js:308-347`. + +## Default chat (toggle off) + +This is the chat app with the Privacy Mode toggle off, the SDK with `encrypted: false`, and guest chat. There is no second layer of encryption. Only rows that change are listed. Status: Live. + +
+ +| What | You | Network observer | Solrouter backend | CVM cloud host (Phala) | Nosana GPU host | Solana observer | +| --- | --- | --- | --- | --- | --- | --- | +| Prompt text | plaintext | ciphertext (a) | plaintext (b) | nothing (c) | plaintext (d) | nothing (e) | +| Reply text | plaintext | ciphertext (a) | plaintext (b) | nothing (c) | plaintext (d) | nothing (e) | +| Attached files | plaintext | ciphertext (a) | plaintext (f) | nothing | plaintext (f) | nothing | +| Knowledge-base documents (retrieved parts) | plaintext | ciphertext (a) | plaintext (g) | nothing | plaintext (g) | nothing | +| Memory (facts added to the prompt) | plaintext | ciphertext (a) | plaintext (h) | nothing | plaintext (h) | nothing | +| Web searches (agent mode and live search) | plaintext | ciphertext (a) | plaintext (i) | nothing | plaintext (i) | nothing | + +
+ +Footnotes: + +- (a) HTTPS only. A watcher sees scrambled traffic plus size and timing. See note 1 above. +- (b) The backend reads the prompt, builds the final text for the model, and stores your message and the reply scrambled with its own key (note 10). The encrypted path needs the toggle on. Source: `be:routes/router.js:277-296,409-413`; `fe:src/components/chat/ChatArea.tsx:1114`. +- (c) The default path does not use the CVM. The backend talks to the model node itself. Source: `be:routes/router.js:203-206,885-898`; `be:routes/nosana.js:560`. +- (d) Same as note 4. The request comes from Solrouter's backend instead of the CVM. +- (e) No Solana receipt is written on the default path. Source: `be:routes/nosana.js:405,433` pass an id through and call nothing else. +- (f) Images become data URLs inside the request. Documents upload to Cloudflare R2, a file store run by Cloudflare, through a short-lived upload link. The backend then downloads the file, extracts the text, and adds it to the prompt. The file stays in R2; see What we keep. Source: `fe:src/lib/r2Upload.ts:36-52`; `be:services/r2Storage.js:22-46`; `be:routes/nosana.js:298-326`. +- (g) The backend finds the best-matching chunks and pastes them into the prompt. Source: `be:routes/router.js:751-765`. +- (h) On this path the remembered facts travel as normal text inside the prompt. The stored memory blob stays scrambled. Source: `fe:src/components/chat/ChatArea.tsx:314,407`. +- (i) Agent mode in the chat app calls `/agent` with the plain prompt. Live search and the agent's `web_search` tool use Brave Search, with DuckDuckGo and Wikipedia as fallbacks. Those services receive the search text. Source: `fe:src/components/chat/ChatArea.tsx:331-342`; `be:lib/agentService.js:91,538-539`; `be:lib/liveDataService.js:58-63,124,167,206`. + +## Where your words are readable + +The strip below follows one encrypted request from your device to the Solana receipt. Green zones hold your words in readable form. Grey zones hold only ciphertext or a hash. The amber zone is the rented GPU machine. + + + +**In words** + +- Your device: your words are readable here. Your browser or program scrambles them before they leave. +- Network: ciphertext only. A watcher sees size and timing. +- Solrouter backend: ciphertext only. It checks your login, bills you, and passes the blob along. +- TDX CVM: your words are readable here, inside memory that the processor encrypts. The machine owner cannot open it. +- Nosana GPU node: your words are readable here while the model runs. Solrouter does not control this machine. The request is not linked to you. +- Solana: hash only. A receipt proves a request happened and names the model. It does not hold your words. + + + Solrouter does not run fully homomorphic encryption (FHE) inference. FHE means a computer works on scrambled data without ever unscrambling it. No production system runs AI models of this size under FHE in 2026. The compute cost is many orders of magnitude away from usable speed. Anyone who claims "FHE LLM inference" in production is overclaiming. + + What Solrouter provides is encryption on your device, a hardware-isolated CVM that unscrambles the prompt, a model that runs on a rented Nosana GPU node, and a receipt on Solana for each encrypted request. That is a real and checkable guarantee. It is not FHE, and we will not claim otherwise. + + +## What we keep + +Retention periods are not published. Each row states what is stored, in what form, who holds the key, and how to remove it. Rows are Live unless marked. + +
+ +| What | Where | Format | Key holder | How to delete | Retention | +| --- | --- | --- | --- | --- | --- | +| Chat rows (`enc:v1:`) | Solrouter's database, tables `chat_messages` and `chats` (message text, chat title, search and knowledge-base context, pitch-deck cards) | Scrambled with AES-256-GCM, stored as text with the prefix `enc:v1:` | Solrouter backend, from `CHAT_CONTENT_KEK` or `WALLET_VAULT_KEK` in the server settings | Deleting a chat in the app marks it archived. The rows stay in the database. A hard delete path: not determined | not published | +| Memory envelope (`umem:v1:`) | Solrouter's database, table `user_memory`, one row per user | Scrambled in your browser with AES-256-GCM, stored as text with the prefix `umem:v1:`. Solrouter cannot read it | You. The key comes from your wallet signature and is never stored | "Forget all" in the app removes the row (`DELETE /memory`) | not published | +| Knowledge-base chunks | Files on the backend server disk, one JSON file per collection under `data/vectors/` | Plain text chunks plus embedding vectors. Not encrypted | none | Delete the whole collection (`DELETE /rag/collections/:name`). Delete of one document: not determined | not published | +| Uploaded files (R2) | A Cloudflare R2 bucket, key `documents/` plus a timestamp and a random id | The original file. Not encrypted by Solrouter | none | not determined. The code has upload and download, no delete | not published | +| Usage log | Solrouter's database, table `api_usage` | Plain rows: key id, user id, model, token counts, cost, request id, time. No prompt text | none | not determined | not published | +| Swap session rows (Soon) | Solrouter's database, table `agent_swap_sessions` | Plain rows: mode, state, tokens, amount, destination address, transaction ids, payer id, webhook URL. The one-shot signer key is scrambled and set to null when the swap ends | Solrouter backend, `WALLET_VAULT_KEK`, for the signer key only | not determined. The API has read and webhook routes, no delete | not published. Pending sessions expire after 7 days | +| Guest per-IP counter | Backend process memory, not a database | Your internet address, a message count, and a reset time | none | No route. The entry resets 24 hours after first use and vanishes when the server restarts | not published | + +
+ +Sources, row by row: chat rows `be:lib/chatCrypto.js:9-15,29,76-78`, `be:routes/router.js:409-413`, `be:routes/chats.js:149,215,252-270`; memory `be:migrations/027_user_memory.sql:3-9,27-36`, `fe:src/lib/memory.ts:3-10,207-212`, `be:routes/memory.js:337-344`; knowledge-base chunks `be:lib/embeddingService.js:92-115,202-207`, `be:lib/ragService.js:32-46`, `be:routes/rag.js:131-147,172`; uploaded files `be:services/r2Storage.js:2,22-46`; usage log `be:migrations/add_api_keys.sql:23-33`, `be:routes/private-ai-api.js:257-266`, `be:routes/private-ai-x402.js:61-70`; swap sessions `be:migrations/009_agent_swap_sessions.sql:7-47`, `be:routes/agents/sessions.js:11-37`, design decision 9 for Soon; guest counter `be:routes/router.js:197-198,230-237`. + +Guest chat sends your prompt in plaintext to the backend and then to the model node. It stores no chat rows. Source: `be:routes/router.js:228-271`. + +## Short answers + +- "Can Solrouter read my prompt?" With the toggle on, or with the SDK default, no. With the toggle off, yes. +- "Can Solrouter read my history?" In Persistent mode, yes. It holds the key. In Maximum Privacy mode there is no history. +- "Can Solrouter read my memory?" No. Only your wallet can unlock it. +- "Can Solrouter read my uploaded documents?" Yes. They are stored as plain text and plain files. +- "Can anyone else read my prompt?" The operator of the Nosana GPU node could, while the model runs. No one else. +- "Is my wallet address public?" It is not on the Solana receipt. Solrouter's backend knows it. + +## Next + + + } href="/docs/products/chat-app"> + Where the Privacy Mode toggle is and what each mode keeps. + + } href="/docs/verify-a-reply"> + Paste the lock link from a reply and see the receipt check pass. + + } href="/docs/what-is-a-tee"> + The sealed-room picture and where it breaks. + + } href="/docs/glossary"> + Every term on this page in one line each. + + diff --git a/src/app/global.css b/src/app/global.css index f3e1c86..1191376 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -94,3 +94,27 @@ html > body[data-scroll-locked] { scroll-behavior: auto !important; } } + +.react-flow { + --xy-edge-stroke-default: var(--color-fd-muted-foreground); + --xy-edge-stroke-selected-default: var(--color-fd-primary); + --xy-edge-stroke-width-default: 1.5; + --xy-node-background-color-default: var(--color-fd-card); + --xy-node-border-default: 1px solid var(--color-fd-border); + --xy-node-color-default: var(--color-fd-foreground); + --xy-controls-button-background-color-default: var(--color-fd-card); + --xy-controls-button-background-color-hover-default: var(--color-fd-accent); + --xy-controls-button-color-default: var(--color-fd-foreground); + --xy-controls-button-border-color-default: var(--color-fd-border); + --xy-attribution-background-color-default: transparent; +} + +/* The stepper thickens one edge. Animate it, unless the reader asked for reduced motion. */ +.react-flow__edge-path { + transition: + stroke-width 150ms ease, + stroke 150ms ease; +} +.flow-reduced-motion .react-flow__edge-path { + transition: none; +} diff --git a/src/components/diagrams/architecture-map.tsx b/src/components/diagrams/architecture-map.tsx new file mode 100644 index 0000000..b294a47 --- /dev/null +++ b/src/components/diagrams/architecture-map.tsx @@ -0,0 +1,14 @@ +'use client'; + +import { systemMapEdges, systemMapNodes } from '@/components/flow/data/system-map'; +import { FlowFigure } from '@/components/flow/flow-figure'; + +export function ArchitectureMap() { + return ( + + ); +} diff --git a/src/components/diagrams/plaintext-zones.tsx b/src/components/diagrams/plaintext-zones.tsx new file mode 100644 index 0000000..e1b99a9 --- /dev/null +++ b/src/components/diagrams/plaintext-zones.tsx @@ -0,0 +1,106 @@ +import { Cpu, Database, Link2, Server, User, Zap, type LucideIcon } from 'lucide-react'; + +type Zone = { + icon: LucideIcon; + title: string; + state: string; + plaintext: boolean; + attacker: string; +}; + +const ZONES: Zone[] = [ + { + icon: User, + title: 'Your device', + state: 'Plaintext', + plaintext: true, + attacker: 'your prompt and the reply', + }, + { + icon: Link2, + title: 'Network', + state: 'Ciphertext', + plaintext: false, + attacker: 'ciphertext and traffic timing', + }, + { + icon: Server, + title: 'Solrouter backend', + state: 'Ciphertext', + plaintext: false, + attacker: 'ciphertext, your wallet or key, the model name', + }, + { + icon: Cpu, + title: 'TDX enclave', + state: 'Plaintext in CPU-encrypted memory', + plaintext: true, + attacker: 'encrypted memory, not the text', + }, + { + icon: Zap, + title: 'Nosana GPU node', + state: 'Plaintext in the Ollama process, TLS in transit', + plaintext: true, + attacker: 'your prompt and the reply, not who you are', + }, + { + icon: Database, + title: 'Solana', + state: 'Hash only', + plaintext: false, + attacker: 'a hash of the ciphertext and the model name', + }, +]; + +/** + * Where the prompt exists as readable text on the encrypted path. + * Plaintext zones use the primary tint; ciphertext and hash-only zones use + * the muted tint. Each zone carries a one-line "an attacker here sees" label. + */ +export function PlaintextZones() { + return ( +
+
+ {ZONES.map((zone) => ( +
+
+ An attacker here sees: {zone.attacker} +
+
+
+ +
+
{zone.title}
+
+ {zone.state} +
+
+
+ ))} +
+
+ + + Readable text exists here + + + + Only ciphertext or a hash exists here + +
+
+ ); +} diff --git a/src/components/diagrams/request-flow-stepper.tsx b/src/components/diagrams/request-flow-stepper.tsx new file mode 100644 index 0000000..2680513 --- /dev/null +++ b/src/components/diagrams/request-flow-stepper.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { + requestFlowEdges, + requestFlowNodes, + requestFlowSteps, +} from '@/components/flow/data/request-flow'; +import { FlowFigure } from '@/components/flow/flow-figure'; + +export function RequestFlowStepper() { + return ( + + ); +} diff --git a/src/components/diagrams/sealed-room.tsx b/src/components/diagrams/sealed-room.tsx new file mode 100644 index 0000000..6c4c973 --- /dev/null +++ b/src/components/diagrams/sealed-room.tsx @@ -0,0 +1,98 @@ +import { + Building2, + Cpu, + DoorClosed, + EyeOff, + Lock, + ShieldCheck, + User, + Zap, + type LucideIcon, +} from 'lucide-react'; + +function Feature({ icon: Icon, title, sub }: { icon: LucideIcon; title: string; sub: string }) { + return ( +
+
+ +
+
+
{title}
+
{sub}
+
+
+ ); +} + +/** + * The sealed-room picture of a Confidential VM (Intel TDX on Phala dStack). + * Two layers: isolation (the operator cannot open the door) and attestation + * (the window shows which program runs inside). The Nosana GPU node sits + * outside the room and sees the prompt while it runs the model. + */ +export function SealedRoom() { + return ( +
+
+
+ + Data center (cloud host) +
+
+
+
+ + +
+
Operator
+
+ Cannot open the door: memory is encrypted by the CPU +
+
+
+
+ + Confidential VM (the sealed room) + +
+ + +
+
+
+
+
+ +
+
+
Nosana GPU node (outside the room)
+
+ Runs the model. Sees the prompt while it works. Does not know who you are. +
+
+
+
+
+ + Isolation. The CPU encrypts the room's memory. The host and the operator cannot read it. +
+
+ + Attestation. The CPU signs a note that names the program inside. You can check that note. +
+
+
+ ); +} diff --git a/src/components/diagrams/skill-graph-map.tsx b/src/components/diagrams/skill-graph-map.tsx new file mode 100644 index 0000000..7c85b8a --- /dev/null +++ b/src/components/diagrams/skill-graph-map.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { + skillGraphEdges, + skillGraphNodes, + skillGraphSteps, +} from '@/components/flow/data/skill-graph'; +import { FlowFigure } from '@/components/flow/flow-figure'; + +export function SkillGraphMap() { + return ( + + ); +} diff --git a/src/components/flow/box-node.tsx b/src/components/flow/box-node.tsx new file mode 100644 index 0000000..25386ae --- /dev/null +++ b/src/components/flow/box-node.tsx @@ -0,0 +1,76 @@ +'use client'; + +import { Handle, Position, type NodeProps } from '@xyflow/react'; +import { + Code, + Coins, + Cpu, + Database, + Globe, + Landmark, + Link2, + MessageSquare, + Plug, + Server, + ShieldCheck, + Shuffle, + User, + Zap, + type LucideIcon, +} from 'lucide-react'; +import type { BoxNode, IconName } from './data/types'; + +const ICONS: Record = { + User, + Server, + Cpu, + Zap, + Link2, + Coins, + Plug, + MessageSquare, + Code, + Shuffle, + Landmark, + Database, + Globe, + ShieldCheck, +}; + +/* The one custom node type. Size must match BOX_WIDTH and BOX_HEIGHT in data/types.ts. */ +export function BoxNodeCard({ data, selected }: NodeProps) { + const Icon = ICONS[data.icon]; + const ring = data.active + ? 'ring-2 ring-fd-primary' + : selected + ? 'ring-2 ring-fd-ring/60' + : ''; + return ( +
+ +
+ +
+
+
{data.title}
+
+ {data.sub} +
+ {data.status ? ( +
+ {data.status} +
+ ) : null} +
+ +
+ ); +} diff --git a/src/components/flow/data/request-flow.ts b/src/components/flow/data/request-flow.ts new file mode 100644 index 0000000..8005292 --- /dev/null +++ b/src/components/flow/data/request-flow.ts @@ -0,0 +1,104 @@ +/* + * Request-flow stepper data (design section 6, row 26). + * Node subset of system-map.ts. Step sources were read in the monorepo on 2026-08-26. + */ +import { flowEdge, type BoxNode, type FlowEdge, type FlowStep } from './types'; +import { systemMapNodes } from './system-map'; + +const POSITIONS: Record = { + sdk: { x: 0, y: 110 }, + backend: { x: 330, y: 110 }, + cvm: { x: 660, y: 0 }, + nosana: { x: 990, y: 0 }, + solana: { x: 660, y: 220 }, +}; + +/* Reuse the system-map nodes so both diagrams say the same thing about each box. */ +export const requestFlowNodes: BoxNode[] = systemMapNodes + .filter((n) => n.id in POSITIONS) + .map((n) => ({ ...n, position: POSITIONS[n.id] })); + +export const requestFlowEdges: FlowEdge[] = [ + flowEdge('sdk', 'backend', 'ciphertext'), + flowEdge('backend', 'cvm', 'ciphertext'), + flowEdge('cvm', 'nosana', 'plaintext over TLS'), + flowEdge('backend', 'solana', 'receipt commit'), +]; + +export const requestFlowSteps: FlowStep[] = [ + { + id: 'key', + label: 'Fetch the enclave key', + nodeId: 'sdk', + edgeId: 'sdk__backend', + payload: 'GET /tee/public-key -> {publicKey, publicKeySha256, algorithm, teeType}. Cached per process.', + source: 'be:packages/sdk/src/encryption.ts:73-95; be:tee-service/src/index.js:805-812', + }, + { + id: 'keypair', + label: 'Ephemeral keypair', + nodeId: 'sdk', + payload: 'Ephemeral X25519 keypair, then the shared secret with the enclave key.', + source: 'be:packages/sdk/src/encryption.ts:61-68,116', + }, + { + id: 'encrypt', + label: 'Encrypt', + nodeId: 'sdk', + payload: "RescueCipher, packed-31 -> {ciphertext, nonce, publicKey, version: '2.0-packed31'}", + source: 'be:packages/sdk/src/encryption.ts:17-18,111-143', + }, + { + id: 'post', + label: 'POST /tee/process', + nodeId: 'sdk', + edgeId: 'sdk__backend', + payload: 'POST /tee/process with a Bearer key. What leaves the machine: the ciphertext bundle {ciphertext, nonce, publicKey, version} plus, in plaintext, the API key, model id, chatId, and the optional systemPrompt, useRAG, ragCollection, useLiveSearch.', + source: 'be:packages/sdk/src/client.ts:180-193; be:routes/tee.js:33-46', + }, + { + id: 'relay', + label: 'Blind relay', + nodeId: 'backend', + edgeId: 'backend__cvm', + payload: 'Backend forwards {encryptedPrompt, model, privacyAttestationId} unchanged.', + source: 'be:routes/tee.js:50-54', + }, + { + id: 'decrypt', + label: 'CVM decrypts', + nodeId: 'cvm', + payload: 'CVM derives the shared secret with its X25519 private key and decrypts.', + source: 'be:tee-service/src/index.js:321-329', + }, + { + id: 'infer', + label: 'Model call', + nodeId: 'cvm', + edgeId: 'cvm__nosana', + payload: 'CVM calls the configured Nosana endpoint URL at /v1/chat/completions with the plaintext (HTTPS per the documented node URL, not re-verified).', + source: 'be:tee-service/src/index.js:456-474', + }, + { + id: 'seal', + label: 'Seal and sign', + nodeId: 'cvm', + payload: 'CVM encrypts the reply to your key, signs the SOLR-ATTEST-v2 tuple, requests the tappd quote with report_data = sha256(x25519 || ed25519).', + source: 'be:tee-service/src/index.js:382-389,107-119,96-101,217-221', + }, + { + id: 'commit', + label: 'Receipt and reply', + nodeId: 'backend', + edgeId: 'backend__solana', + payload: "Backend commits the compressed account and returns {success, encryptedResponse, attestation (with the per-request tdxQuote or tdxQuoteError), encryptionProof, requestId, metadata, backendRole: 'BLIND_RELAY', onchainAttestation {type, address, encryptedPromptHash, signature, explorerUrl, alreadyExists} | null, privacyProof {backendSawPlaintext: false, decryptionLocation: 'PHALA_TDX_CVM', attestationVerifiable}}.", + source: 'be:routes/tee.js:57-73,84-101', + }, + { + id: 'read', + label: 'SDK decrypts', + nodeId: 'sdk', + payload: 'SDK decrypts encryptedResponse with the session private key.', + source: 'be:packages/sdk/src/client.ts:215-216', + }, +]; diff --git a/src/components/flow/data/skill-graph.ts b/src/components/flow/data/skill-graph.ts new file mode 100644 index 0000000..2f8e962 --- /dev/null +++ b/src/components/flow/data/skill-graph.ts @@ -0,0 +1,675 @@ +/* + * Skill graph data (design section 6, row 38). + * Ids, names, groups, and edges arrays copied from + * SolRouter/dev/backend/lib/skillGraphEngine.js (BASE_SKILL_NODES) on 2026-08-26. + * Generated by a script that parsed the file: 44 nodes, 136 directed edges, 0 missing targets. + * Family = the engine's section comment, folded into the five design families: + * Core, DeFi Research, Market Research, Privacy & Security Research, + * Risk & Comparative, Colosseum -> research and analysis; + * Solana Ecosystem, Arcium -> ecosystem; DeFi Protocol Skills -> DeFi protocols; + * Infrastructure Skills -> infrastructure and oracles; + * Development & Tooling Skills -> Solana development. + * Positions are fixed numbers. No layout library. No invented links. + */ +import { boxNode, flowEdge, type BoxNode, type FlowEdge, type FlowStep } from './types'; + +const SOURCE = 'be:lib/skillGraphEngine.js'; + +export const skillGraphNodes: BoxNode[] = [ + boxNode('research-core', 0, 0, { + icon: 'Globe', + title: 'research-core', + sub: 'Research Framework', + accent: false, + detail: { + holds: 'Edges: source-eval, comparative-analysis, risk-assessment, data-synthesis.', + sees: 'Family: research and analysis. Engine group: core.', + source: `${SOURCE}:51`, + }, + }), + boxNode('source-eval', 260, 0, { + icon: 'Globe', + title: 'source-eval', + sub: 'Source Evaluation', + accent: false, + detail: { + holds: 'Edges: research-core.', + sees: 'Family: research and analysis. Engine group: research.', + source: `${SOURCE}:71`, + }, + }), + boxNode('defi-analysis', 520, 0, { + icon: 'Globe', + title: 'defi-analysis', + sub: 'DeFi Protocol Analysis', + accent: false, + detail: { + holds: 'Edges: liquidity-risk, token-economics, smart-contract-risk, research-core.', + sees: 'Family: research and analysis. Engine group: defi.', + source: `${SOURCE}:90`, + }, + }), + boxNode('liquidity-risk', 0, 110, { + icon: 'Globe', + title: 'liquidity-risk', + sub: 'Liquidity Risk Assessment', + accent: false, + detail: { + holds: 'Edges: defi-analysis, risk-assessment.', + sees: 'Family: research and analysis. Engine group: defi.', + source: `${SOURCE}:112`, + }, + }), + boxNode('token-economics', 260, 110, { + icon: 'Globe', + title: 'token-economics', + sub: 'Token Economics', + accent: false, + detail: { + holds: 'Edges: defi-analysis, market-analysis.', + sees: 'Family: research and analysis. Engine group: market.', + source: `${SOURCE}:137`, + }, + }), + boxNode('market-analysis', 520, 110, { + icon: 'Globe', + title: 'market-analysis', + sub: 'Market Analysis', + accent: false, + detail: { + holds: 'Edges: token-economics, on-chain-analysis, research-core.', + sees: 'Family: research and analysis. Engine group: market.', + source: `${SOURCE}:166`, + }, + }), + boxNode('on-chain-analysis', 0, 220, { + icon: 'Globe', + title: 'on-chain-analysis', + sub: 'On-Chain Analysis', + accent: false, + detail: { + holds: 'Edges: market-analysis, wallet-analysis.', + sees: 'Family: research and analysis. Engine group: market.', + source: `${SOURCE}:187`, + }, + }), + boxNode('wallet-analysis', 260, 220, { + icon: 'Globe', + title: 'wallet-analysis', + sub: 'Wallet Analysis', + accent: false, + detail: { + holds: 'Edges: on-chain-analysis, risk-assessment.', + sees: 'Family: research and analysis. Engine group: wallet.', + source: `${SOURCE}:209`, + }, + }), + boxNode('privacy-research', 520, 220, { + icon: 'Globe', + title: 'privacy-research', + sub: 'Privacy Technology Research', + accent: false, + detail: { + holds: 'Edges: smart-contract-risk, research-core, arcium-mpc.', + sees: 'Family: research and analysis. Engine group: privacy.', + source: `${SOURCE}:230`, + }, + }), + boxNode('risk-assessment', 0, 330, { + icon: 'Globe', + title: 'risk-assessment', + sub: 'Risk Assessment', + accent: false, + detail: { + holds: 'Edges: smart-contract-risk, liquidity-risk, research-core.', + sees: 'Family: research and analysis. Engine group: meta.', + source: `${SOURCE}:258`, + }, + }), + boxNode('smart-contract-risk', 260, 330, { + icon: 'Globe', + title: 'smart-contract-risk', + sub: 'Smart Contract Risk', + accent: false, + detail: { + holds: 'Edges: risk-assessment, defi-analysis.', + sees: 'Family: research and analysis. Engine group: meta.', + source: `${SOURCE}:284`, + }, + }), + boxNode('comparative-analysis', 520, 330, { + icon: 'Globe', + title: 'comparative-analysis', + sub: 'Comparative Analysis', + accent: false, + detail: { + holds: 'Edges: research-core, data-synthesis.', + sees: 'Family: research and analysis. Engine group: research.', + source: `${SOURCE}:309`, + }, + }), + boxNode('data-synthesis', 0, 440, { + icon: 'Globe', + title: 'data-synthesis', + sub: 'Data Synthesis', + accent: false, + detail: { + holds: 'Edges: research-core, source-eval.', + sees: 'Family: research and analysis. Engine group: research.', + source: `${SOURCE}:329`, + }, + }), + boxNode('colosseum-research', 260, 440, { + icon: 'Globe', + title: 'colosseum-research', + sub: 'Colosseum Hackathon Intelligence', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, market-analysis, research-core, colosseum-archives.', + sees: 'Family: research and analysis. Engine group: research.', + source: `${SOURCE}:350`, + }, + }), + boxNode('colosseum-archives', 520, 440, { + icon: 'Globe', + title: 'colosseum-archives', + sub: 'Crypto Archive Research', + accent: false, + detail: { + holds: 'Edges: colosseum-research, research-core, privacy-research.', + sees: 'Family: research and analysis. Engine group: research.', + source: `${SOURCE}:376`, + }, + }), + boxNode('arcium-mpc', 910, 165, { + icon: 'Link2', + title: 'arcium-mpc', + sub: 'Arcium Confidential Computing', + accent: false, + detail: { + holds: 'Edges: privacy-research, solana-ecosystem, anchor-dev, smart-contract-risk.', + sees: 'Family: ecosystem. Engine group: privacy.', + source: `${SOURCE}:401`, + }, + }), + boxNode('solana-ecosystem', 910, 275, { + icon: 'Link2', + title: 'solana-ecosystem', + sub: 'Solana Ecosystem', + accent: true, + detail: { + holds: 'Edges: defi-analysis, on-chain-analysis, colosseum-research, jupiter-defi, raydium-defi, orca-defi, meteora-defi, kamino-defi, sanctum-staking, helius-infra, metaplex-nfts, solana-kit-dev, anchor-dev, arcium-mpc.', + sees: 'Family: ecosystem. Engine group: defi.', + source: `${SOURCE}:448`, + }, + }), + boxNode('jupiter-defi', 1300, 0, { + icon: 'Coins', + title: 'jupiter-defi', + sub: 'Jupiter DeFi', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, defi-analysis, liquidity-risk, raydium-defi, orca-defi.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:481`, + }, + }), + boxNode('raydium-defi', 1560, 0, { + icon: 'Coins', + title: 'raydium-defi', + sub: 'Raydium DeFi', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, defi-analysis, liquidity-risk, jupiter-defi.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:509`, + }, + }), + boxNode('orca-defi', 1300, 110, { + icon: 'Coins', + title: 'orca-defi', + sub: 'Orca Whirlpools', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, defi-analysis, liquidity-risk, jupiter-defi.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:533`, + }, + }), + boxNode('meteora-defi', 1560, 110, { + icon: 'Coins', + title: 'meteora-defi', + sub: 'Meteora DeFi', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, defi-analysis, liquidity-risk, pump-fun.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:560`, + }, + }), + boxNode('kamino-defi', 1300, 220, { + icon: 'Coins', + title: 'kamino-defi', + sub: 'Kamino Finance', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, defi-analysis, liquidity-risk, sanctum-staking.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:587`, + }, + }), + boxNode('sanctum-staking', 1560, 220, { + icon: 'Coins', + title: 'sanctum-staking', + sub: 'Sanctum Liquid Staking', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, defi-analysis, kamino-defi.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:616`, + }, + }), + boxNode('pump-fun', 1300, 330, { + icon: 'Coins', + title: 'pump-fun', + sub: 'PumpFun Token Launches', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, meteora-defi, token-economics.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:646`, + }, + }), + boxNode('lulo-lending', 1560, 330, { + icon: 'Coins', + title: 'lulo-lending', + sub: 'Lulo Lending Aggregator', + accent: false, + detail: { + holds: 'Edges: defi-analysis, kamino-defi, solana-ecosystem.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:678`, + }, + }), + boxNode('ranger-perps', 1300, 440, { + icon: 'Coins', + title: 'ranger-perps', + sub: 'Ranger Finance Perps', + accent: false, + detail: { + holds: 'Edges: jupiter-defi, defi-analysis, solana-ecosystem.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:698`, + }, + }), + boxNode('prediction-markets', 1560, 440, { + icon: 'Coins', + title: 'prediction-markets', + sub: 'Prediction Markets', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, defi-analysis.', + sees: 'Family: DeFi protocols. Engine group: defi.', + source: `${SOURCE}:714`, + }, + }), + boxNode('helius-infra', 0, 640, { + icon: 'Database', + title: 'helius-infra', + sub: 'Helius Infrastructure', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, metaplex-nfts, on-chain-analysis.', + sees: 'Family: infrastructure and oracles. Engine group: defi.', + source: `${SOURCE}:739`, + }, + }), + boxNode('light-protocol-zk', 260, 640, { + icon: 'Database', + title: 'light-protocol-zk', + sub: 'Light Protocol ZK Compression', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, privacy-research.', + sees: 'Family: infrastructure and oracles. Engine group: defi.', + source: `${SOURCE}:772`, + }, + }), + boxNode('metaplex-nfts', 520, 640, { + icon: 'Database', + title: 'metaplex-nfts', + sub: 'Metaplex NFTs', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, helius-infra.', + sees: 'Family: infrastructure and oracles. Engine group: defi.', + source: `${SOURCE}:806`, + }, + }), + boxNode('pyth-oracle', 780, 640, { + icon: 'Database', + title: 'pyth-oracle', + sub: 'Pyth Price Feeds', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, defi-analysis, switchboard-oracle.', + sees: 'Family: infrastructure and oracles. Engine group: defi.', + source: `${SOURCE}:834`, + }, + }), + boxNode('switchboard-oracle', 0, 750, { + icon: 'Database', + title: 'switchboard-oracle', + sub: 'Switchboard Oracle', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, pyth-oracle, defi-analysis.', + sees: 'Family: infrastructure and oracles. Engine group: defi.', + source: `${SOURCE}:862`, + }, + }), + boxNode('squads-multisig', 260, 750, { + icon: 'Database', + title: 'squads-multisig', + sub: 'Squads Multisig', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, smart-contract-risk.', + sees: 'Family: infrastructure and oracles. Engine group: defi.', + source: `${SOURCE}:891`, + }, + }), + boxNode('debridge-cross-chain', 520, 750, { + icon: 'Database', + title: 'debridge-cross-chain', + sub: 'deBridge Cross-Chain', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, defi-analysis.', + sees: 'Family: infrastructure and oracles. Engine group: defi.', + source: `${SOURCE}:917`, + }, + }), + boxNode('coingecko-analytics', 780, 750, { + icon: 'Database', + title: 'coingecko-analytics', + sub: 'CoinGecko Analytics', + accent: false, + detail: { + holds: 'Edges: market-analysis, token-economics, on-chain-analysis.', + sees: 'Family: infrastructure and oracles. Engine group: market.', + source: `${SOURCE}:941`, + }, + }), + boxNode('solana-kit-dev', 1040, 640, { + icon: 'Code', + title: 'solana-kit-dev', + sub: 'Solana Kit SDK', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, anchor-dev, framework-kit-frontend.', + sees: 'Family: Solana development. Engine group: core.', + source: `${SOURCE}:969`, + }, + }), + boxNode('anchor-dev', 1300, 640, { + icon: 'Code', + title: 'anchor-dev', + sub: 'Anchor Development', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, solana-kit-dev, smart-contract-risk, pinocchio-dev, solana-testing.', + sees: 'Family: Solana development. Engine group: core.', + source: `${SOURCE}:1000`, + }, + }), + boxNode('pinocchio-dev', 1560, 640, { + icon: 'Code', + title: 'pinocchio-dev', + sub: 'Pinocchio Framework', + accent: false, + detail: { + holds: 'Edges: anchor-dev, solana-ecosystem.', + sees: 'Family: Solana development. Engine group: core.', + source: `${SOURCE}:1040`, + }, + }), + boxNode('framework-kit-frontend', 1040, 750, { + icon: 'Code', + title: 'framework-kit-frontend', + sub: 'Frontend with Framework Kit', + accent: false, + detail: { + holds: 'Edges: solana-kit-dev, solana-ecosystem.', + sees: 'Family: Solana development. Engine group: core.', + source: `${SOURCE}:1067`, + }, + }), + boxNode('solana-testing', 1300, 750, { + icon: 'Code', + title: 'solana-testing', + sub: 'Solana Testing Strategy', + accent: false, + detail: { + holds: 'Edges: anchor-dev, solana-ecosystem.', + sees: 'Family: Solana development. Engine group: core.', + source: `${SOURCE}:1100`, + }, + }), + boxNode('solana-security-audit', 1560, 750, { + icon: 'Code', + title: 'solana-security-audit', + sub: 'Solana Security Checklist', + accent: false, + detail: { + holds: 'Edges: smart-contract-risk, anchor-dev, solana-ecosystem.', + sees: 'Family: Solana development. Engine group: meta.', + source: `${SOURCE}:1137`, + }, + }), + boxNode('token2022-extensions', 1040, 860, { + icon: 'Code', + title: 'token2022-extensions', + sub: 'Token-2022 Extensions', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, privacy-research, solana-security-audit.', + sees: 'Family: Solana development. Engine group: core.', + source: `${SOURCE}:1175`, + }, + }), + boxNode('quicknode-infra', 1300, 860, { + icon: 'Code', + title: 'quicknode-infra', + sub: 'QuickNode Infrastructure', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, helius-infra.', + sees: 'Family: Solana development. Engine group: defi.', + source: `${SOURCE}:1205`, + }, + }), + boxNode('magicblock-gaming', 1560, 860, { + icon: 'Code', + title: 'magicblock-gaming', + sub: 'MagicBlock Gaming', + accent: false, + detail: { + holds: 'Edges: solana-ecosystem, switchboard-oracle.', + sees: 'Family: Solana development. Engine group: defi.', + source: `${SOURCE}:1230`, + }, + }), +]; + +/* One edge per entry in each node's edges array, in file order. Direction kept. */ +export const skillGraphEdges: FlowEdge[] = [ + flowEdge('research-core', 'source-eval'), + flowEdge('research-core', 'comparative-analysis'), + flowEdge('research-core', 'risk-assessment'), + flowEdge('research-core', 'data-synthesis'), + flowEdge('source-eval', 'research-core'), + flowEdge('defi-analysis', 'liquidity-risk'), + flowEdge('defi-analysis', 'token-economics'), + flowEdge('defi-analysis', 'smart-contract-risk'), + flowEdge('defi-analysis', 'research-core'), + flowEdge('liquidity-risk', 'defi-analysis'), + flowEdge('liquidity-risk', 'risk-assessment'), + flowEdge('token-economics', 'defi-analysis'), + flowEdge('token-economics', 'market-analysis'), + flowEdge('market-analysis', 'token-economics'), + flowEdge('market-analysis', 'on-chain-analysis'), + flowEdge('market-analysis', 'research-core'), + flowEdge('on-chain-analysis', 'market-analysis'), + flowEdge('on-chain-analysis', 'wallet-analysis'), + flowEdge('wallet-analysis', 'on-chain-analysis'), + flowEdge('wallet-analysis', 'risk-assessment'), + flowEdge('privacy-research', 'smart-contract-risk'), + flowEdge('privacy-research', 'research-core'), + flowEdge('privacy-research', 'arcium-mpc'), + flowEdge('risk-assessment', 'smart-contract-risk'), + flowEdge('risk-assessment', 'liquidity-risk'), + flowEdge('risk-assessment', 'research-core'), + flowEdge('smart-contract-risk', 'risk-assessment'), + flowEdge('smart-contract-risk', 'defi-analysis'), + flowEdge('comparative-analysis', 'research-core'), + flowEdge('comparative-analysis', 'data-synthesis'), + flowEdge('data-synthesis', 'research-core'), + flowEdge('data-synthesis', 'source-eval'), + flowEdge('colosseum-research', 'solana-ecosystem'), + flowEdge('colosseum-research', 'market-analysis'), + flowEdge('colosseum-research', 'research-core'), + flowEdge('colosseum-research', 'colosseum-archives'), + flowEdge('colosseum-archives', 'colosseum-research'), + flowEdge('colosseum-archives', 'research-core'), + flowEdge('colosseum-archives', 'privacy-research'), + flowEdge('arcium-mpc', 'privacy-research'), + flowEdge('arcium-mpc', 'solana-ecosystem'), + flowEdge('arcium-mpc', 'anchor-dev'), + flowEdge('arcium-mpc', 'smart-contract-risk'), + flowEdge('solana-ecosystem', 'defi-analysis'), + flowEdge('solana-ecosystem', 'on-chain-analysis'), + flowEdge('solana-ecosystem', 'colosseum-research'), + flowEdge('solana-ecosystem', 'jupiter-defi'), + flowEdge('solana-ecosystem', 'raydium-defi'), + flowEdge('solana-ecosystem', 'orca-defi'), + flowEdge('solana-ecosystem', 'meteora-defi'), + flowEdge('solana-ecosystem', 'kamino-defi'), + flowEdge('solana-ecosystem', 'sanctum-staking'), + flowEdge('solana-ecosystem', 'helius-infra'), + flowEdge('solana-ecosystem', 'metaplex-nfts'), + flowEdge('solana-ecosystem', 'solana-kit-dev'), + flowEdge('solana-ecosystem', 'anchor-dev'), + flowEdge('solana-ecosystem', 'arcium-mpc'), + flowEdge('jupiter-defi', 'solana-ecosystem'), + flowEdge('jupiter-defi', 'defi-analysis'), + flowEdge('jupiter-defi', 'liquidity-risk'), + flowEdge('jupiter-defi', 'raydium-defi'), + flowEdge('jupiter-defi', 'orca-defi'), + flowEdge('raydium-defi', 'solana-ecosystem'), + flowEdge('raydium-defi', 'defi-analysis'), + flowEdge('raydium-defi', 'liquidity-risk'), + flowEdge('raydium-defi', 'jupiter-defi'), + flowEdge('orca-defi', 'solana-ecosystem'), + flowEdge('orca-defi', 'defi-analysis'), + flowEdge('orca-defi', 'liquidity-risk'), + flowEdge('orca-defi', 'jupiter-defi'), + flowEdge('meteora-defi', 'solana-ecosystem'), + flowEdge('meteora-defi', 'defi-analysis'), + flowEdge('meteora-defi', 'liquidity-risk'), + flowEdge('meteora-defi', 'pump-fun'), + flowEdge('kamino-defi', 'solana-ecosystem'), + flowEdge('kamino-defi', 'defi-analysis'), + flowEdge('kamino-defi', 'liquidity-risk'), + flowEdge('kamino-defi', 'sanctum-staking'), + flowEdge('sanctum-staking', 'solana-ecosystem'), + flowEdge('sanctum-staking', 'defi-analysis'), + flowEdge('sanctum-staking', 'kamino-defi'), + flowEdge('pump-fun', 'solana-ecosystem'), + flowEdge('pump-fun', 'meteora-defi'), + flowEdge('pump-fun', 'token-economics'), + flowEdge('lulo-lending', 'defi-analysis'), + flowEdge('lulo-lending', 'kamino-defi'), + flowEdge('lulo-lending', 'solana-ecosystem'), + flowEdge('ranger-perps', 'jupiter-defi'), + flowEdge('ranger-perps', 'defi-analysis'), + flowEdge('ranger-perps', 'solana-ecosystem'), + flowEdge('prediction-markets', 'solana-ecosystem'), + flowEdge('prediction-markets', 'defi-analysis'), + flowEdge('helius-infra', 'solana-ecosystem'), + flowEdge('helius-infra', 'metaplex-nfts'), + flowEdge('helius-infra', 'on-chain-analysis'), + flowEdge('light-protocol-zk', 'solana-ecosystem'), + flowEdge('light-protocol-zk', 'privacy-research'), + flowEdge('metaplex-nfts', 'solana-ecosystem'), + flowEdge('metaplex-nfts', 'helius-infra'), + flowEdge('pyth-oracle', 'solana-ecosystem'), + flowEdge('pyth-oracle', 'defi-analysis'), + flowEdge('pyth-oracle', 'switchboard-oracle'), + flowEdge('switchboard-oracle', 'solana-ecosystem'), + flowEdge('switchboard-oracle', 'pyth-oracle'), + flowEdge('switchboard-oracle', 'defi-analysis'), + flowEdge('squads-multisig', 'solana-ecosystem'), + flowEdge('squads-multisig', 'smart-contract-risk'), + flowEdge('debridge-cross-chain', 'solana-ecosystem'), + flowEdge('debridge-cross-chain', 'defi-analysis'), + flowEdge('coingecko-analytics', 'market-analysis'), + flowEdge('coingecko-analytics', 'token-economics'), + flowEdge('coingecko-analytics', 'on-chain-analysis'), + flowEdge('solana-kit-dev', 'solana-ecosystem'), + flowEdge('solana-kit-dev', 'anchor-dev'), + flowEdge('solana-kit-dev', 'framework-kit-frontend'), + flowEdge('anchor-dev', 'solana-ecosystem'), + flowEdge('anchor-dev', 'solana-kit-dev'), + flowEdge('anchor-dev', 'smart-contract-risk'), + flowEdge('anchor-dev', 'pinocchio-dev'), + flowEdge('anchor-dev', 'solana-testing'), + flowEdge('pinocchio-dev', 'anchor-dev'), + flowEdge('pinocchio-dev', 'solana-ecosystem'), + flowEdge('framework-kit-frontend', 'solana-kit-dev'), + flowEdge('framework-kit-frontend', 'solana-ecosystem'), + flowEdge('solana-testing', 'anchor-dev'), + flowEdge('solana-testing', 'solana-ecosystem'), + flowEdge('solana-security-audit', 'smart-contract-risk'), + flowEdge('solana-security-audit', 'anchor-dev'), + flowEdge('solana-security-audit', 'solana-ecosystem'), + flowEdge('token2022-extensions', 'solana-ecosystem'), + flowEdge('token2022-extensions', 'privacy-research'), + flowEdge('token2022-extensions', 'solana-security-audit'), + flowEdge('quicknode-infra', 'solana-ecosystem'), + flowEdge('quicknode-infra', 'helius-infra'), + flowEdge('magicblock-gaming', 'solana-ecosystem'), + flowEdge('magicblock-gaming', 'switchboard-oracle'), +]; + +/* + * Example traversal from the design: defi-analysis -> liquidity-risk -> comparative-analysis. + * The engine seeds BFS with the top 3 scored nodes and follows edges arrays + * (be:lib/skillGraphEngine.js:1451-1468). liquidity-risk has no edge to + * comparative-analysis, so the third step names no edge. Nothing is drawn that + * the engine does not define. + */ +export const skillGraphSteps: FlowStep[] = [ + { + id: 'seed', + label: 'defi-analysis', + nodeId: 'defi-analysis', + edgeId: 'defi-analysis__liquidity-risk', + payload: 'Seed node. BFS follows its edges array: liquidity-risk, token-economics, smart-contract-risk, research-core.', + source: `${SOURCE}:95,1451-1468`, + }, + { + id: 'hop', + label: 'liquidity-risk', + nodeId: 'liquidity-risk', + payload: 'Reached through the edge from defi-analysis. Its own edges: defi-analysis, risk-assessment.', + source: `${SOURCE}:117`, + }, + { + id: 'end', + label: 'comparative-analysis', + nodeId: 'comparative-analysis', + payload: 'No edge from liquidity-risk. Reached as another seed, or through research-core.', + source: `${SOURCE}:314`, + }, +]; diff --git a/src/components/flow/data/system-map.ts b/src/components/flow/data/system-map.ts new file mode 100644 index 0000000..bf2a2f8 --- /dev/null +++ b/src/components/flow/data/system-map.ts @@ -0,0 +1,148 @@ +/* + * Architecture map data (design section 6, row 25). + * Sources are monorepo file:line from the design doc dated 2026-08-26. + * be: = SolRouter/dev/backend, fe: = SolRouter frontend. + */ +import { boxNode, flowEdge, type BoxNode, type FlowEdge } from './types'; + +const PROGRAM_ID = 'ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb'; + +export const systemMapNodes: BoxNode[] = [ + boxNode('chat', 0, 0, { + icon: 'MessageSquare', + title: 'Chat app', + sub: 'solrouter.com; Maximum Privacy Mode encrypts in the browser', + accent: true, + detail: { + holds: 'Your ephemeral X25519 key and the plaintext prompt, in the browser tab.', + sees: 'Plaintext prompt and reply. Maximum Privacy Mode (toggle on) encrypts the text prompt for /tee/process. Persistent Privacy Mode (the default) sends the prompt in plaintext to the backend /nosana or /router route. Attachments are not encrypted client-side.', + status: 'Live', + source: 'fe:src/components/chat/ChatArea.tsx:542-569,1114; fe:src/lib/r2Upload.ts:36-47', + href: '/docs/products/chat-app', + }, + }), + boxNode('sdk', 0, 110, { + icon: 'Code', + title: '@solrouter/sdk', + sub: 'Your process, encrypts before send', + accent: true, + detail: { + holds: 'One session X25519 keypair per SDK instance and the cached enclave public key.', + sees: 'Plaintext prompt and reply on your machine only.', + status: 'Live', + source: 'be:packages/sdk/src/encryption.ts:52-68,73-95; be:packages/sdk/src/client.ts:177-184', + href: '/docs/products/privacy-sdk', + }, + }), + boxNode('mcp', 0, 220, { + icon: 'Plug', + title: '@solrouter/mcp-server', + sub: 'Your machine, 22 tools', + accent: true, + detail: { + holds: 'SOLROUTER_API_KEY, SOLROUTER_API_URL, and BRAVE_API_KEY on your machine; HELIUS_RPC_URL optional. Four tools use the encrypted /tee/process path for the model step: encrypted_chat, and the synthesis step of private_research, private_token_analysis, and private_wallet_audit.', + sees: 'Plaintext locally. The data-gathering steps call Brave, DexScreener, CoinGecko, and the Helius or public Solana RPC directly, in plaintext, from your machine.', + status: 'Live', + source: 'be:packages/mcp-server/src/index.ts:28,60,71,114,122', + href: '/docs/products/mcp-server', + }, + }), + boxNode('rest', 0, 330, { + icon: 'Globe', + title: 'REST and x402 clients', + sub: 'API key or wallet, you encrypt', + detail: { + holds: 'An API key, or a wallet key for x402. x402 removes the API key, not the wallet key.', + sees: 'Plaintext on your side. POST /api/v1/chat/completions requires encryptedPrompt and model.', + status: 'Live', + source: 'be:routes/private-ai-api.js:75-98; be:routes/private-ai-x402.js:74-79', + href: '/docs/api-reference/inference', + }, + }), + boxNode('backend', 330, 165, { + icon: 'Server', + title: 'Solrouter backend', + sub: 'Render: blind relay, keys, billing', + detail: { + holds: 'API keys, balances, the x402 paywall, and the deployer wallet that commits receipts. No decryption key.', + sees: 'Ciphertext, your API key or wallet address, model name, and usage. Never the plaintext.', + status: 'Live', + source: 'be:routes/tee.js:33-54,57-73,84-103; be:lib/x402Middleware.js:27-46', + href: '/docs/under-the-hood/request-flow', + }, + }), + boxNode('cvm', 660, 40, { + icon: 'ShieldCheck', + title: 'Intel TDX CVM', + sub: 'Phala dStack, tee-service', + accent: true, + detail: { + holds: 'X25519 sealing key and ed25519 signing key, generated at boot, never exported. RescueCipher, tappd quote, 5-tool allowlist, SearXNG in the same CVM.', + sees: 'Plaintext inside CPU-encrypted memory, for the length of one request.', + status: 'Live', + source: 'be:tee-service/src/index.js:76-101,107-119,217-238,321-329; be:tee-service/src/tools.js:28-34', + href: '/docs/under-the-hood/attestation', + }, + }), + boxNode('nosana', 990, 40, { + icon: 'Zap', + title: 'Nosana GPU node', + sub: 'One job per model, Ollama', + detail: { + holds: 'The model weights and the Ollama process.', + sees: 'Plaintext prompt and reply in the Ollama process, over TLS from the CVM. Not linked to your identity.', + status: 'Live', + source: 'be:tee-service/src/index.js:456-474; be:lib/nosanaEndpoints.js:14-32', + href: '/docs/under-the-hood/models', + }, + }), + boxNode('solana', 660, 290, { + icon: 'Landmark', + title: 'Solana', + sub: `program ${PROGRAM_ID}, Light compressed receipts, Photon`, + detail: { + holds: `One Light compressed receipt per inference under program ${PROGRAM_ID}, read through Photon. Cluster: mainnet by the explorer link and code comments (be:services/lightAttestation.js:81-82); not re-verified with getAccountInfo.`, + sees: 'sha256 of the ciphertext, model, provider, and the enclave signature. No plaintext. Cluster: mainnet by the explorer link and code comments (be:services/lightAttestation.js:81-82); not re-verified with getAccountInfo.', + status: 'Live', + source: 'be:services/lightAttestation.js:81-82,359-363; be:routes/verifyAttestation.js:7-10', + href: '/docs/under-the-hood/encryption-proof', + }, + }), + boxNode('umbra', 990, 290, { + icon: 'Shuffle', + title: 'Umbra mixer plus Jupiter', + sub: 'Private swaps', + status: 'Soon', + detail: { + holds: 'Mixer pool deposits and the Jupiter swap route.', + sees: 'That you used Umbra is public. Deposit and claim amounts at the pool boundary are public.', + status: 'Soon', + source: 'be:UMBRA_PRIVATE_SWAP_ARCHITECTURE.md:5,17,47-70; be:lib/agentSwapWorker.js:4-7,25', + href: '/docs/under-the-hood/private-swaps', + }, + }), + boxNode('facilitator', 330, 400, { + icon: 'Coins', + title: 'Coinbase x402 facilitator', + sub: 'advertised by the live manifest; which facilitator settles is not determined', + detail: { + holds: 'Nothing of yours. Verifies and settles the USDC payment that Solrouter\'s server sends it. The agent never talks to the facilitator.', + sees: 'The signed payment payload and your wallet address.', + status: 'Live', + source: 'be:routes/wellKnown.js:73; be:lib/x402Middleware.js:29,52-63', + href: '/docs/under-the-hood/x402', + }, + }), +]; + +export const systemMapEdges: FlowEdge[] = [ + flowEdge('chat', 'backend', 'ciphertext (Maximum Privacy Mode) or plaintext (default)'), + flowEdge('sdk', 'backend', 'ciphertext'), + flowEdge('mcp', 'backend', 'ciphertext'), + flowEdge('rest', 'backend', 'ciphertext'), + flowEdge('backend', 'cvm', 'ciphertext'), + flowEdge('cvm', 'nosana', 'plaintext over TLS'), + flowEdge('backend', 'solana', 'receipt commit'), + flowEdge('backend', 'facilitator', 'USDC'), + flowEdge('backend', 'umbra'), +]; diff --git a/src/components/flow/data/types.ts b/src/components/flow/data/types.ts new file mode 100644 index 0000000..ba35999 --- /dev/null +++ b/src/components/flow/data/types.ts @@ -0,0 +1,76 @@ +import { Position, type Edge, type Node } from '@xyflow/react'; + +/* Icon keys the box node can draw. A string keeps node data serialisable. */ +export type IconName = + | 'User' + | 'Server' + | 'Cpu' + | 'Zap' + | 'Link2' + | 'Coins' + | 'Plug' + | 'MessageSquare' + | 'Code' + | 'Shuffle' + | 'Landmark' + | 'Database' + | 'Globe' + | 'ShieldCheck'; + +/* What the detail panel shows when a node is selected. */ +export type NodeDetail = { + holds: string; + sees: string; + status?: string; + /* Monorepo file:line, or "not determined". */ + source: string; + /* Deep-dive page for this node. */ + href?: string; +}; + +export type BoxNodeData = { + icon: IconName; + title: string; + sub: string; + status?: string; + accent?: boolean; + active?: boolean; + detail: NodeDetail; +}; + +export type BoxNode = Node; +export type FlowEdge = Edge; + +/* One hop in a stepper. edgeId is absent when the hop stays inside one node. */ +export type FlowStep = { + id: string; + label: string; + nodeId: string; + edgeId?: string; + payload: string; + source: string; +}; + +export const BOX_WIDTH = 220; +export const BOX_HEIGHT = 84; + +/* Build one box node. Size and handles are fixed so the server can draw edges. */ +export function boxNode(id: string, x: number, y: number, data: BoxNodeData): BoxNode { + return { + id, + type: 'box', + position: { x, y }, + width: BOX_WIDTH, + height: BOX_HEIGHT, + handles: [ + { type: 'target', position: Position.Left, x: 0, y: BOX_HEIGHT / 2 }, + { type: 'source', position: Position.Right, x: BOX_WIDTH, y: BOX_HEIGHT / 2 }, + ], + data, + }; +} + +/* Edge ids use two underscores so they never collide with a node id. */ +export function flowEdge(source: string, target: string, label?: string): FlowEdge { + return { id: `${source}__${target}`, source, target, label }; +} diff --git a/src/components/flow/flow-canvas.tsx b/src/components/flow/flow-canvas.tsx new file mode 100644 index 0000000..f4f0a1f --- /dev/null +++ b/src/components/flow/flow-canvas.tsx @@ -0,0 +1,113 @@ +'use client'; + +import { + Controls, + ReactFlow, + ReactFlowProvider, + type FitViewOptions, + type NodeMouseHandler, + type NodeTypes, + type OnNodesChange, +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { useCallback, useMemo } from 'react'; +import { BoxNodeCard } from './box-node'; +import type { BoxNode, FlowEdge } from './data/types'; + +/* Module scope on purpose: a new object per render makes React Flow remount every node. */ +const nodeTypes: NodeTypes = { box: BoxNodeCard }; + +const EDGE_LABEL_STYLE = { fill: 'var(--color-fd-muted-foreground)' }; +const EDGE_LABEL_BG_STYLE = { fill: 'var(--color-fd-card)' }; +const ACTIVE_EDGE_STYLE = { stroke: 'var(--color-fd-primary)', strokeWidth: 3 }; +const PRO_OPTIONS = { hideAttribution: false }; + +export type FlowCanvasProps = { + nodes: BoxNode[]; + edges: FlowEdge[]; + /* Canvas size in px. React Flow needs both to run fitView on the server. */ + width: number; + height: number; + selectedId: string | null; + onSelect: (id: string | null) => void; + activeEdgeId?: string | null; + fitViewOptions?: FitViewOptions; +}; + +/* The only file that imports @xyflow/react at runtime, with its stylesheet. */ +export function FlowCanvas({ + nodes, + edges, + width, + height, + selectedId, + onSelect, + activeEdgeId = null, + fitViewOptions, +}: FlowCanvasProps) { + const viewNodes = useMemo( + () => nodes.map((n) => ({ ...n, selected: n.id === selectedId })), + [nodes, selectedId], + ); + const viewEdges = useMemo( + () => + edges.map((e) => ({ + ...e, + labelStyle: EDGE_LABEL_STYLE, + labelBgStyle: EDGE_LABEL_BG_STYLE, + style: e.id === activeEdgeId ? ACTIVE_EDGE_STYLE : undefined, + })), + [edges, activeEdgeId], + ); + + const onNodeClick: NodeMouseHandler = useCallback( + (_event, node) => onSelect(node.id), + [onSelect], + ); + const onPaneClick = useCallback(() => onSelect(null), [onSelect]); + /* Enter on a focused node arrives as a select change, not as a click. */ + const onNodesChange: OnNodesChange = useCallback( + (changes) => { + for (const change of changes) { + if (change.type === 'select' && change.selected) onSelect(change.id); + } + }, + [onSelect], + ); + + return ( + + + + + + ); +} diff --git a/src/components/flow/flow-figure.tsx b/src/components/flow/flow-figure.tsx new file mode 100644 index 0000000..399f85d --- /dev/null +++ b/src/components/flow/flow-figure.tsx @@ -0,0 +1,190 @@ +'use client'; + +import { + type KeyboardEvent, + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import { FlowCanvas } from './flow-canvas'; +import type { BoxNode, FlowEdge, FlowStep } from './data/types'; + +export type FlowFigureProps = { + /* One sentence that describes the whole diagram. */ + ariaLabel: string; + nodes: BoxNode[]; + edges: FlowEdge[]; + width?: number; + height?: number; + /* When given, a step list drives the active node and edge. Never auto-plays. */ + steps?: FlowStep[]; +}; + +const NO_MOTION = { duration: 0 }; + +function clamp(i: number, max: number): number { + return Math.max(0, Math.min(max, i)); +} + +/* MDX wrapper: canvas, aria-live detail panel, optional step list. */ +export function FlowFigure({ + ariaLabel, + nodes, + edges, + width = 880, + height = 440, + steps, +}: FlowFigureProps) { + const [selectedId, setSelectedId] = useState(null); + const [stepIndex, setStepIndex] = useState(0); + const [reducedMotion, setReducedMotion] = useState(false); + const listRef = useRef(null); + + useEffect(() => { + const query = window.matchMedia('(prefers-reduced-motion: reduce)'); + const apply = () => setReducedMotion(query.matches); + apply(); + query.addEventListener('change', apply); + return () => query.removeEventListener('change', apply); + }, []); + + const step = steps?.[stepIndex]; + + const viewNodes = useMemo( + () => + step + ? nodes.map((n) => + n.id === step.nodeId ? { ...n, data: { ...n.data, active: true } } : n, + ) + : nodes, + [nodes, step], + ); + + const selected = selectedId ? nodes.find((n) => n.id === selectedId) : undefined; + + const goTo = useCallback( + (i: number) => { + if (!steps || steps.length === 0) return; + const next = clamp(i, steps.length - 1); + setStepIndex(next); + setSelectedId(steps[next].nodeId); + listRef.current?.querySelectorAll('button')[next]?.focus(); + }, + [steps], + ); + + const onListKeyDown = useCallback( + (event: KeyboardEvent) => { + if (event.key === 'ArrowRight') { + event.preventDefault(); + goTo(stepIndex + 1); + } else if (event.key === 'ArrowLeft') { + event.preventDefault(); + goTo(stepIndex - 1); + } + }, + [goTo, stepIndex], + ); + + return ( +
+ {/* role="img" sits on the canvas only, so the buttons and the panel below stay in the accessibility tree. */} +
+ +
+ +
+
+ {selected ? ( +
+
Node
+
{selected.data.title}
+
Holds
+
{selected.data.detail.holds}
+
Sees
+
{selected.data.detail.sees}
+ {selected.data.detail.status ? ( + <> +
Status
+
{selected.data.detail.status}
+ + ) : null} +
Source
+
+ {selected.data.detail.source} +
+ {selected.data.detail.href ? ( + <> +
Deep dive
+
+ {selected.data.detail.href} +
+ + ) : null} +
+ ) : ( +

+ Select a node to see what it holds and what it sees. +

+ )} +
+ + {step ? ( +
+
+ Step {stepIndex + 1} of {steps?.length}: {step.label} +
+ {step.payload} +
+ Source: {step.source} +
+
+ ) : null} +
+ + {steps && steps.length > 0 ? ( +
    + {steps.map((s, i) => ( +
  1. + +
  2. + ))} +
+ ) : null} +
+ ); +} diff --git a/src/components/verify/key-quote-inspector.tsx b/src/components/verify/key-quote-inspector.tsx new file mode 100644 index 0000000..74e8911 --- /dev/null +++ b/src/components/verify/key-quote-inspector.tsx @@ -0,0 +1,150 @@ +'use client'; + +import { BadgeCheck, CircleAlert, Loader2, RefreshCw } from 'lucide-react'; +import { useState } from 'react'; + +// Live, CORS-enabled read endpoints (checked 2026-08-26 with an Origin header): +// GET /tee/public-key -> { publicKey, publicKeySha256, algorithm, teeType } +// GET /tee/attestation -> { teeType, teePublicKey, teePublicKeySha256, reportDataHex, tdxQuote, generatedAt } +// The GET quote pins report_data = sha256(X25519 public key). This widget recomputes +// that hash in the browser and compares it with reportDataHex. Override the host +// with NEXT_PUBLIC_API_BASE for local testing. +const API_BASE = process.env.NEXT_PUBLIC_API_BASE || 'https://api.solrouter.com'; + +type PublicKey = { publicKey: string; publicKeySha256?: string; algorithm?: string; teeType?: string }; +type Attestation = { + teeType?: string; + teePublicKey?: string; + teePublicKeySha256?: string; + reportDataHex?: string; + tdxQuote?: unknown; + tdxQuoteError?: string; + generatedAt?: number; +}; + +type Result = { + teeType: string; + keyMatches: boolean; + hashMatches: boolean; + quotePresent: boolean; + quoteError?: string; + computedSha256: string; + reportDataHex: string; + generatedAt?: string; +}; + +function base64ToBytes(b64: string): Uint8Array { + const bin = atob(b64); + const out = new Uint8Array(bin.length); + for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i); + return out; +} + +async function sha256Hex(bytes: Uint8Array): Promise { + const digest = await crypto.subtle.digest('SHA-256', bytes as BufferSource); + return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join(''); +} + +export function KeyQuoteInspector() { + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const [result, setResult] = useState(null); + + async function run() { + setBusy(true); + setError(null); + setResult(null); + try { + const [keyRes, attRes] = await Promise.all([ + fetch(`${API_BASE}/tee/public-key`), + fetch(`${API_BASE}/tee/attestation`), + ]); + if (!keyRes.ok) throw new Error(`GET /tee/public-key returned ${keyRes.status}`); + if (!attRes.ok) throw new Error(`GET /tee/attestation returned ${attRes.status}`); + const key = (await keyRes.json()) as PublicKey; + const att = (await attRes.json()) as Attestation; + const computed = await sha256Hex(base64ToBytes(key.publicKey)); + const reportDataHex = (att.reportDataHex || '').toLowerCase(); + setResult({ + teeType: att.teeType || key.teeType || 'unknown', + keyMatches: !!att.teePublicKey && att.teePublicKey === key.publicKey, + hashMatches: reportDataHex.startsWith(computed), + quotePresent: att.tdxQuote !== null && att.tdxQuote !== undefined, + quoteError: att.tdxQuoteError, + computedSha256: computed, + reportDataHex, + generatedAt: att.generatedAt ? new Date(att.generatedAt).toISOString() : undefined, + }); + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + setBusy(false); + } + } + + const ok = result && result.keyMatches && result.hashMatches && result.quotePresent; + + return ( +
+
+ + + Fetches /tee/public-key and /tee/attestation from your browser. + +
+ +
+ {error ? ( +

+ + {error} +

+ ) : null} + {result ? ( +
+

+ {ok ? : } + {ok ? 'The published key is bound into the live TDX quote.' : 'Something did not match. Read the rows below.'} +

+
+ + + + + + + + + {result.generatedAt ? : null} + +
+
+

+ This check proves the key you encrypt to is the key the quote names. It does not verify the Intel + signature chain. For that, run the quote through Intel DCAP tools. +

+
+ ) : null} +
+
+ ); +} + +function Row({ label, value, mono = false }: { label: string; value: string; mono?: boolean }) { + return ( + + + {label} + + {value} + + ); +} From f53e0ebf97dfb24edf7d07248e98b5435c0161f9 Mon Sep 17 00:00:00 2001 From: sarthib7 Date: Thu, 27 Aug 2026 13:29:50 +0200 Subject: [PATCH 4/7] feat(docs): react-flow diagram, privacy matrix, card polish, cleaner map panel --- src/app/global.css | 22 +++ src/components/diagrams/privacy-matrix.tsx | 89 +++++++++ .../diagrams/typical-vs-solrouter.tsx | 184 ++++++++++++------ src/components/flow/data/system-map.ts | 34 ++-- src/components/flow/flow-figure.tsx | 13 +- 5 files changed, 254 insertions(+), 88 deletions(-) create mode 100644 src/components/diagrams/privacy-matrix.tsx diff --git a/src/app/global.css b/src/app/global.css index 1191376..c195f68 100644 --- a/src/app/global.css +++ b/src/app/global.css @@ -118,3 +118,25 @@ html > body[data-scroll-locked] { .flow-reduced-motion .react-flow__edge-path { transition: none; } + +/* --- Card polish -------------------------------------------------------- */ +/* Fumadocs Card titles are

, so the global serif-heading rule rendered + them as tiny 14px display serif with no hierarchy. Give cards a body-font + title, real weight, and more breathing room. */ +a.bg-fd-card { + padding: 1.25rem; +} +a.bg-fd-card h3 { + font-family: var(--font-sans); + font-size: 1rem; + line-height: 1.35; + font-weight: 600; + margin-bottom: 0.4rem; +} +a.bg-fd-card > div:first-child { + margin-bottom: 0.85rem; +} +/* More air between cards in a grid. */ +div:has(> a.bg-fd-card) { + gap: 1.25rem; +} diff --git a/src/components/diagrams/privacy-matrix.tsx b/src/components/diagrams/privacy-matrix.tsx new file mode 100644 index 0000000..5a29e4a --- /dev/null +++ b/src/components/diagrams/privacy-matrix.tsx @@ -0,0 +1,89 @@ +import type { ReactNode } from 'react'; + +/** Cell tokens. `pt` (readable) is the only one that means a party can read your words. */ +export type Cell = 'pt' | 'ct' | 'no' | 'ha' | 're' | 'me' | 'nd'; + +const STYLE: Record = { + pt: { label: 'readable', cls: 'bg-amber-500/15 text-amber-600 dark:text-amber-300 border border-amber-500/40' }, + ct: { label: 'encrypted', cls: 'bg-emerald-500/15 text-emerald-600 dark:text-emerald-300 border border-emerald-500/40' }, + no: { label: 'nothing', cls: 'bg-fd-muted text-fd-muted-foreground border border-fd-border' }, + ha: { label: 'hash only', cls: 'bg-sky-500/15 text-sky-600 dark:text-sky-300 border border-sky-500/40' }, + re: { label: 'at rest*', cls: 'bg-amber-500/10 text-amber-600 dark:text-amber-200 border border-amber-500/30' }, + me: { label: 'metadata', cls: 'bg-fd-muted text-fd-muted-foreground border border-fd-border' }, + nd: { label: 'unknown', cls: 'text-fd-muted-foreground border border-dashed border-fd-border' }, +}; + +const LEGEND: { token: Cell; text: string }[] = [ + { token: 'pt', text: 'readable: this party can read your words' }, + { token: 'ct', text: 'encrypted: sees only ciphertext, holds no key' }, + { token: 'no', text: 'nothing: never reaches this party' }, + { token: 'ha', text: 'hash only: a fingerprint, not your words' }, + { token: 're', text: 'at rest: stored encrypted under a key Solrouter holds' }, + { token: 'me', text: 'metadata: size or timing, not content' }, +]; + +function Pill({ token }: { token: Cell }) { + const s = STYLE[token]; + return ( + + {s.label} + + ); +} + +export type MatrixRow = { what: ReactNode; cells: Cell[] }; + +/** + * Threat-model matrix. Each row is a kind of data; each column is a party; + * each cell says what that party can see. Amber cells are the only exposure. + */ +export function PrivacyMatrix({ + parties, + rows, + legend = true, +}: { + parties: string[]; + rows: MatrixRow[]; + legend?: boolean; +}) { + return ( +
+
+ + + + + {parties.map((p) => ( + + ))} + + + + {rows.map((row, i) => ( + + + {row.cells.map((c, j) => ( + + ))} + + ))} + +
What + {p} +
{row.what} + +
+
+ {legend ? ( +
+ {LEGEND.map((l) => ( + + + {l.text.split(':')[1].trim()} + + ))} +
+ ) : null} +
+ ); +} diff --git a/src/components/diagrams/typical-vs-solrouter.tsx b/src/components/diagrams/typical-vs-solrouter.tsx index dbb9d34..8ebcb45 100644 --- a/src/components/diagrams/typical-vs-solrouter.tsx +++ b/src/components/diagrams/typical-vs-solrouter.tsx @@ -1,87 +1,149 @@ +'use client'; + +import { + Handle, + MarkerType, + Position, + ReactFlow, + ReactFlowProvider, + type Edge, + type Node, + type NodeProps, +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; import { Eye, EyeOff, KeyRound, Lock, Server, User, Zap, type LucideIcon } from 'lucide-react'; -import type { ReactNode } from 'react'; -function Stage({ - icon: Icon, - title, - sub, - accent = false, -}: { - icon: LucideIcon; - title: string; - sub: string; +type ChipData = { + icon: keyof typeof ICONS; + label: string; accent?: boolean; -}) { - return ( -
-
- -
-
{title}
-
{sub}
-
- ); -} + danger?: boolean; +}; +type LaneData = { label: string }; + +const ICONS = { User, Eye, EyeOff, Server, Lock, KeyRound, Zap } satisfies Record; + +const CHIP_W = 150; +const CHIP_H = 52; -function Wire({ label, open = false }: { label: string; open?: boolean }) { - const Icon = open ? Eye : Lock; +function ChipNode({ data }: NodeProps>) { + const Icon = ICONS[data.icon]; + const tone = data.accent + ? 'border-fd-primary/40 bg-fd-primary/10' + : data.danger + ? 'border-amber-500/40 bg-amber-500/10' + : 'border-fd-border bg-fd-card'; + const iconTone = data.accent + ? 'text-fd-primary' + : data.danger + ? 'text-amber-600 dark:text-amber-300' + : 'text-fd-muted-foreground'; return (
- - {label} + + + {data.label} +
); } -function Row({ title, children }: { title: string; children: ReactNode }) { +function LaneNode({ data }: NodeProps>) { return ( -
-
{title}
-
{children}
+
+ {data.label}
); } +const nodeTypes = { chip: ChipNode, lane: LaneNode }; + +function chip(id: string, x: number, y: number, data: ChipData): Node { + return { id, type: 'chip', position: { x, y }, data, width: CHIP_W, height: CHIP_H, draggable: false, selectable: false }; +} +function lane(id: string, x: number, y: number, label: string): Node { + return { id, type: 'lane', position: { x, y }, data: { label }, draggable: false, selectable: false }; +} + +const AMBER = '#d97706'; +const GREEN = '#059669'; + +function wire(id: string, source: string, target: string, label: string, safe: boolean): Edge { + const color = safe ? GREEN : AMBER; + return { + id, + source, + target, + label, + type: 'smoothstep', + style: { stroke: color, strokeWidth: 1.5 }, + labelStyle: { fill: color, fontSize: 11, fontWeight: 600 }, + labelBgStyle: { fill: 'var(--color-fd-background)' }, + labelBgPadding: [4, 2], + labelBgBorderRadius: 4, + markerEnd: { type: MarkerType.ArrowClosed, color, width: 16, height: 16 }, + }; +} + +const nodes: Node[] = [ + lane('lane1', 0, -42, 'Typical AI API'), + chip('you1', 0, 0, { icon: 'User', label: 'You' }), + chip('prov', 240, 0, { icon: 'Eye', label: 'Provider server', danger: true }), + chip('model1', 480, 0, { icon: 'Server', label: 'Model' }), + + lane('lane2', 0, 108, 'Solrouter, encryption on'), + chip('you2', 0, 150, { icon: 'Lock', label: 'You', accent: true }), + chip('backend', 220, 150, { icon: 'EyeOff', label: 'Solrouter backend' }), + chip('enclave', 440, 150, { icon: 'KeyRound', label: 'TDX enclave', accent: true }), + chip('gpu', 660, 150, { icon: 'Zap', label: 'Nosana GPU' }), +]; + +const edges: Edge[] = [ + wire('e1', 'you1', 'prov', 'plaintext', false), + wire('e2', 'prov', 'model1', 'plaintext', false), + wire('e3', 'you2', 'backend', 'ciphertext', true), + wire('e4', 'backend', 'enclave', 'ciphertext', true), + wire('e5', 'enclave', 'gpu', 'plaintext / TLS', false), +]; + /** - * Before and after picture. Row 1: a typical AI API, where the provider's - * server reads the prompt. Row 2: Solrouter with encryption on, where the - * backend relays ciphertext and only the TDX enclave opens it. + * Before and after, as a React Flow graph. Row 1: a typical AI API reads your + * prompt in plaintext. Row 2: Solrouter relays ciphertext and only the enclave + * opens it. The plaintext/ciphertext state lives on the edges, not in the boxes. */ export function TypicalVsSolrouter() { return (
- - - - - - - - - - - - - - - - - - -
- The reply is encrypted inside the enclave before it travels back. The backend never sees the text. +
+ + + +
+
+ The reply returns encrypted from the enclave. The backend never sees the text.
); diff --git a/src/components/flow/data/system-map.ts b/src/components/flow/data/system-map.ts index bf2a2f8..7a3d850 100644 --- a/src/components/flow/data/system-map.ts +++ b/src/components/flow/data/system-map.ts @@ -11,27 +11,27 @@ export const systemMapNodes: BoxNode[] = [ boxNode('chat', 0, 0, { icon: 'MessageSquare', title: 'Chat app', - sub: 'solrouter.com; Maximum Privacy Mode encrypts in the browser', + sub: 'Encrypts in the browser', accent: true, detail: { holds: 'Your ephemeral X25519 key and the plaintext prompt, in the browser tab.', sees: 'Plaintext prompt and reply. Maximum Privacy Mode (toggle on) encrypts the text prompt for /tee/process. Persistent Privacy Mode (the default) sends the prompt in plaintext to the backend /nosana or /router route. Attachments are not encrypted client-side.', status: 'Live', source: 'fe:src/components/chat/ChatArea.tsx:542-569,1114; fe:src/lib/r2Upload.ts:36-47', - href: '/docs/products/chat-app', + href: '/docs/use/chat-app', }, }), boxNode('sdk', 0, 110, { icon: 'Code', title: '@solrouter/sdk', - sub: 'Your process, encrypts before send', + sub: 'Encrypts before send', accent: true, detail: { holds: 'One session X25519 keypair per SDK instance and the cached enclave public key.', sees: 'Plaintext prompt and reply on your machine only.', status: 'Live', source: 'be:packages/sdk/src/encryption.ts:52-68,73-95; be:packages/sdk/src/client.ts:177-184', - href: '/docs/products/privacy-sdk', + href: '/docs/build/privacy-sdk', }, }), boxNode('mcp', 0, 220, { @@ -44,7 +44,7 @@ export const systemMapNodes: BoxNode[] = [ sees: 'Plaintext locally. The data-gathering steps call Brave, DexScreener, CoinGecko, and the Helius or public Solana RPC directly, in plaintext, from your machine.', status: 'Live', source: 'be:packages/mcp-server/src/index.ts:28,60,71,114,122', - href: '/docs/products/mcp-server', + href: '/docs/build/mcp-server', }, }), boxNode('rest', 0, 330, { @@ -56,19 +56,19 @@ export const systemMapNodes: BoxNode[] = [ sees: 'Plaintext on your side. POST /api/v1/chat/completions requires encryptedPrompt and model.', status: 'Live', source: 'be:routes/private-ai-api.js:75-98; be:routes/private-ai-x402.js:74-79', - href: '/docs/api-reference/inference', + href: '/docs/api-reference/overview', }, }), boxNode('backend', 330, 165, { icon: 'Server', title: 'Solrouter backend', - sub: 'Render: blind relay, keys, billing', + sub: 'Blind relay, keys, billing', detail: { holds: 'API keys, balances, the x402 paywall, and the deployer wallet that commits receipts. No decryption key.', sees: 'Ciphertext, your API key or wallet address, model name, and usage. Never the plaintext.', status: 'Live', source: 'be:routes/tee.js:33-54,57-73,84-103; be:lib/x402Middleware.js:27-46', - href: '/docs/under-the-hood/request-flow', + href: '/docs/how-it-works/request-flow', }, }), boxNode('cvm', 660, 40, { @@ -81,7 +81,7 @@ export const systemMapNodes: BoxNode[] = [ sees: 'Plaintext inside CPU-encrypted memory, for the length of one request.', status: 'Live', source: 'be:tee-service/src/index.js:76-101,107-119,217-238,321-329; be:tee-service/src/tools.js:28-34', - href: '/docs/under-the-hood/attestation', + href: '/docs/how-it-works/attestation', }, }), boxNode('nosana', 990, 40, { @@ -93,19 +93,19 @@ export const systemMapNodes: BoxNode[] = [ sees: 'Plaintext prompt and reply in the Ollama process, over TLS from the CVM. Not linked to your identity.', status: 'Live', source: 'be:tee-service/src/index.js:456-474; be:lib/nosanaEndpoints.js:14-32', - href: '/docs/under-the-hood/models', + href: '/docs/how-it-works/models', }, }), boxNode('solana', 660, 290, { icon: 'Landmark', title: 'Solana', - sub: `program ${PROGRAM_ID}, Light compressed receipts, Photon`, + sub: 'Light compressed receipts', detail: { - holds: `One Light compressed receipt per inference under program ${PROGRAM_ID}, read through Photon. Cluster: mainnet by the explorer link and code comments (be:services/lightAttestation.js:81-82); not re-verified with getAccountInfo.`, - sees: 'sha256 of the ciphertext, model, provider, and the enclave signature. No plaintext. Cluster: mainnet by the explorer link and code comments (be:services/lightAttestation.js:81-82); not re-verified with getAccountInfo.', + holds: `One Light Protocol compressed receipt per inference, under program ${PROGRAM_ID}, read through a Photon indexer.`, + sees: 'The sha256 of your ciphertext, plus the model, provider, and enclave signature. No plaintext.', status: 'Live', source: 'be:services/lightAttestation.js:81-82,359-363; be:routes/verifyAttestation.js:7-10', - href: '/docs/under-the-hood/encryption-proof', + href: '/docs/how-it-works/proof', }, }), boxNode('umbra', 990, 290, { @@ -118,19 +118,19 @@ export const systemMapNodes: BoxNode[] = [ sees: 'That you used Umbra is public. Deposit and claim amounts at the pool boundary are public.', status: 'Soon', source: 'be:UMBRA_PRIVATE_SWAP_ARCHITECTURE.md:5,17,47-70; be:lib/agentSwapWorker.js:4-7,25', - href: '/docs/under-the-hood/private-swaps', + href: '/docs/build/agent-privacy-api', }, }), boxNode('facilitator', 330, 400, { icon: 'Coins', title: 'Coinbase x402 facilitator', - sub: 'advertised by the live manifest; which facilitator settles is not determined', + sub: 'From the live manifest', detail: { holds: 'Nothing of yours. Verifies and settles the USDC payment that Solrouter\'s server sends it. The agent never talks to the facilitator.', sees: 'The signed payment payload and your wallet address.', status: 'Live', source: 'be:routes/wellKnown.js:73; be:lib/x402Middleware.js:29,52-63', - href: '/docs/under-the-hood/x402', + href: '/docs/build/api-key', }, }), ]; diff --git a/src/components/flow/flow-figure.tsx b/src/components/flow/flow-figure.tsx index 399f85d..5698776 100644 --- a/src/components/flow/flow-figure.tsx +++ b/src/components/flow/flow-figure.tsx @@ -34,7 +34,7 @@ export function FlowFigure({ nodes, edges, width = 880, - height = 440, + height = 480, steps, }: FlowFigureProps) { const [selectedId, setSelectedId] = useState(null); @@ -97,7 +97,7 @@ export function FlowFigure({
{selected.data.detail.status} ) : null} -
Source
-
- {selected.data.detail.source} -
{selected.data.detail.href ? ( <>
Deep dive
- {selected.data.detail.href} + Open page →
) : null} @@ -153,9 +149,6 @@ export function FlowFigure({ Step {stepIndex + 1} of {steps?.length}: {step.label}
{step.payload} -
- Source: {step.source} -
) : null}

From 3d3e67b7bf700283ce1c052736df9814def7a267 Mon Sep 17 00:00:00 2001 From: sarthib7 Date: Thu, 27 Aug 2026 13:29:58 +0200 Subject: [PATCH 5/7] feat(docs): slug redirects and a quieter per-page status line --- next.config.mjs | 32 +++++++++++++++++++++++++++++++- src/lib/status.ts | 11 +++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/next.config.mjs b/next.config.mjs index d501d28..7d237dc 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -4,7 +4,37 @@ const withMDX = createMDX(); // Old slug -> new slug. Each row also redirects its `.md` twin, which proxy.ts // rewrites for text readers. Add a row whenever a page moves. -const moves = []; +const moves = [ + // Use Solrouter + ['/docs/chat-app', '/docs/use/chat-app'], + ['/docs/what-is-private', '/docs/use/what-is-private'], + ['/docs/verify-a-reply', '/docs/verify'], + ['/docs/use/verify-a-reply', '/docs/verify'], + ['/docs/payments/overview', '/docs/use/pricing'], + // Build on Solrouter + ['/docs/quickstart', '/docs/build/quickstart'], + ['/docs/develop/privacy-sdk', '/docs/build/privacy-sdk'], + ['/docs/develop/mcp-server', '/docs/build/mcp-server'], + ['/docs/develop/private-swaps', '/docs/build/agent-privacy-api'], + ['/docs/develop/agent-tools-sdk', '/docs/build/agent-tools-sdk'], + ['/docs/develop/authentication', '/docs/build/api-key'], + // How it works + ['/docs/what-is-a-tee', '/docs/how-it-works/what-is-a-tee'], + ['/docs/under-the-hood', '/docs/how-it-works'], + ['/docs/under-the-hood/request-flow', '/docs/how-it-works/request-flow'], + ['/docs/under-the-hood/agent-reasoning', '/docs/how-it-works/agent-reasoning'], + ['/docs/concepts/how-it-works', '/docs/how-it-works'], + ['/docs/concepts/encryption', '/docs/how-it-works/encryption'], + ['/docs/concepts/attestation', '/docs/how-it-works/attestation'], + ['/docs/concepts/encryption-proof', '/docs/how-it-works/proof'], + ['/docs/concepts/supported-models', '/docs/how-it-works/models'], + ['/docs/concepts/agent-framework', '/docs/how-it-works/agent-reasoning'], + ['/docs/concepts/serv-reasoning', '/docs/how-it-works/agent-reasoning'], + ['/docs/concepts/skill-graphs', '/docs/how-it-works/agent-reasoning'], + // Reference + ['/docs/payments/tokenomics', '/docs/token'], + ['/docs/api-reference/authentication', '/docs/build/api-key'], +]; /** @type {import('next').NextConfig} */ const config = { diff --git a/src/lib/status.ts b/src/lib/status.ts index 0f2d75b..9c2d88a 100644 --- a/src/lib/status.ts +++ b/src/lib/status.ts @@ -18,15 +18,14 @@ export function statusLabel(status: PageStatus): string { return LABEL[status]; } -/** One or two plain sentences that appear under the page description and in llms output. */ +/** A short note under the description, shown only when a page is not fully Live. */ export function statusSentence(input: { status?: PageStatus; checked?: string; statusNote?: string; }): string | null { - if (!input.status) return null; - const parts = [`Status: ${LABEL[input.status]}.`]; - parts.push(input.statusNote ? input.statusNote.trim().replace(/\.?$/, '.') : MEANING[input.status]); - if (input.checked) parts.push(`Checked against code and api.solrouter.com on ${input.checked}.`); - return parts.join(' '); + if (!input.status || input.status === 'live') return null; + return input.statusNote + ? input.statusNote.trim().replace(/\.?$/, '.') + : MEANING[input.status]; } From cdd3f08ebadc8686e4a4ba4f907a0494da7b00ae Mon Sep 17 00:00:00 2001 From: sarthib7 Date: Thu, 27 Aug 2026 13:30:06 +0200 Subject: [PATCH 6/7] refactor(docs): restructure into audience-first IA (Diataxis) and de-slop pages --- .../agents/v1/wallets/post.mdx | 4 +- .../agents/v1/wallets/id/shield/post.mdx | 2 +- .../swaps/agents/v1/anonymity-set/get.mdx | 4 +- .../swaps/agents/v1/quote/get.mdx | 4 +- .../swaps/agents/v1/swaps/oneshot/post.mdx | 8 +- content/docs/api-reference/agent.mdx | 2 +- content/docs/api-reference/authentication.mdx | 71 ------- content/docs/api-reference/errors.mdx | 24 +++ content/docs/api-reference/meta.json | 4 +- content/docs/api-reference/overview.mdx | 10 +- content/docs/api-reference/tee/public-key.mdx | 2 +- .../agent-privacy-api.mdx} | 52 ++--- .../{develop => build}/agent-tools-sdk.mdx | 4 +- .../authentication.mdx => build/api-key.mdx} | 2 +- .../docs/{develop => build}/mcp-server.mdx | 0 content/docs/{develop => build}/meta.json | 9 +- .../docs/{develop => build}/privacy-sdk.mdx | 2 +- content/docs/{ => build}/quickstart.mdx | 8 +- content/docs/concepts/agent-framework.mdx | 110 ---------- content/docs/concepts/encryption-proof.mdx | 132 ------------ content/docs/concepts/how-it-works.mdx | 81 -------- content/docs/concepts/meta.json | 14 -- content/docs/concepts/serv-reasoning.mdx | 97 --------- content/docs/concepts/skill-graphs.mdx | 37 ---- content/docs/glossary.mdx | 172 ++++++--------- content/docs/how-it-works/agent-reasoning.mdx | 69 +++++++ .../attestation.mdx | 42 +--- .../{concepts => how-it-works}/encryption.mdx | 4 +- content/docs/how-it-works/index.mdx | 42 ++++ content/docs/how-it-works/meta.json | 14 ++ .../models.mdx} | 0 content/docs/how-it-works/proof.mdx | 52 +++++ .../request-flow.mdx | 14 +- .../docs/{ => how-it-works}/what-is-a-tee.mdx | 6 +- content/docs/index.mdx | 141 ++----------- content/docs/meta.json | 18 +- content/docs/payments/meta.json | 5 - .../{payments/tokenomics.mdx => token.mdx} | 2 +- .../docs/under-the-hood/agent-reasoning.mdx | 50 ----- content/docs/under-the-hood/index.mdx | 41 ---- content/docs/under-the-hood/meta.json | 5 - content/docs/use-cases.mdx | 88 +++----- content/docs/{ => use}/chat-app.mdx | 29 ++- content/docs/use/meta.json | 9 + .../overview.mdx => use/pricing.mdx} | 2 +- content/docs/use/what-is-private.mdx | 152 ++++++++++++++ .../docs/{verify-a-reply.mdx => verify.mdx} | 10 +- content/docs/what-is-private.mdx | 195 ------------------ openapi/agent-privacy.json | 12 +- 49 files changed, 571 insertions(+), 1286 deletions(-) delete mode 100644 content/docs/api-reference/authentication.mdx create mode 100644 content/docs/api-reference/errors.mdx rename content/docs/{develop/private-swaps.mdx => build/agent-privacy-api.mdx} (59%) rename content/docs/{develop => build}/agent-tools-sdk.mdx (97%) rename content/docs/{develop/authentication.mdx => build/api-key.mdx} (99%) rename content/docs/{develop => build}/mcp-server.mdx (100%) rename content/docs/{develop => build}/meta.json (52%) rename content/docs/{develop => build}/privacy-sdk.mdx (93%) rename content/docs/{ => build}/quickstart.mdx (95%) delete mode 100644 content/docs/concepts/agent-framework.mdx delete mode 100644 content/docs/concepts/encryption-proof.mdx delete mode 100644 content/docs/concepts/how-it-works.mdx delete mode 100644 content/docs/concepts/meta.json delete mode 100644 content/docs/concepts/serv-reasoning.mdx delete mode 100644 content/docs/concepts/skill-graphs.mdx create mode 100644 content/docs/how-it-works/agent-reasoning.mdx rename content/docs/{concepts => how-it-works}/attestation.mdx (81%) rename content/docs/{concepts => how-it-works}/encryption.mdx (98%) create mode 100644 content/docs/how-it-works/index.mdx create mode 100644 content/docs/how-it-works/meta.json rename content/docs/{concepts/supported-models.mdx => how-it-works/models.mdx} (100%) create mode 100644 content/docs/how-it-works/proof.mdx rename content/docs/{under-the-hood => how-it-works}/request-flow.mdx (82%) rename content/docs/{ => how-it-works}/what-is-a-tee.mdx (91%) delete mode 100644 content/docs/payments/meta.json rename content/docs/{payments/tokenomics.mdx => token.mdx} (99%) delete mode 100644 content/docs/under-the-hood/agent-reasoning.mdx delete mode 100644 content/docs/under-the-hood/index.mdx delete mode 100644 content/docs/under-the-hood/meta.json rename content/docs/{ => use}/chat-app.mdx (69%) create mode 100644 content/docs/use/meta.json rename content/docs/{payments/overview.mdx => use/pricing.mdx} (98%) create mode 100644 content/docs/use/what-is-private.mdx rename content/docs/{verify-a-reply.mdx => verify.mdx} (88%) delete mode 100644 content/docs/what-is-private.mdx diff --git a/content/docs/api-reference/agent-privacy/managed-wallets/agents/v1/wallets/post.mdx b/content/docs/api-reference/agent-privacy/managed-wallets/agents/v1/wallets/post.mdx index 500b9ea..6e8926a 100644 --- a/content/docs/api-reference/agent-privacy/managed-wallets/agents/v1/wallets/post.mdx +++ b/content/docs/api-reference/agent-privacy/managed-wallets/agents/v1/wallets/post.mdx @@ -2,7 +2,7 @@ title: Provision a managed Umbra wallet (Mode A) description: >- Create a Solrouter-custodied Umbra wallet. The keypair is generated - server-side and envelope-encrypted (AES-256-GCM) — the agent never holds + server-side and envelope-encrypted (AES-256-GCM), so the agent never holds private keys. Fund the returned `umbraAddress`, then run swaps and encrypted-balance operations against the wallet id. full: true @@ -15,7 +15,7 @@ _openapi: contents: - content: >- Create a Solrouter-custodied Umbra wallet. The keypair is generated - server-side and envelope-encrypted (AES-256-GCM) — the agent never + server-side and envelope-encrypted (AES-256-GCM), so the agent never holds private keys. Fund the returned `umbraAddress`, then run swaps and encrypted-balance operations against the wallet id. --- diff --git a/content/docs/api-reference/agent-privacy/private-balance/agents/v1/wallets/id/shield/post.mdx b/content/docs/api-reference/agent-privacy/private-balance/agents/v1/wallets/id/shield/post.mdx index 8b1b5be..3041dd4 100644 --- a/content/docs/api-reference/agent-privacy/private-balance/agents/v1/wallets/id/shield/post.mdx +++ b/content/docs/api-reference/agent-privacy/private-balance/agents/v1/wallets/id/shield/post.mdx @@ -1,5 +1,5 @@ --- -title: Shield + unlink — mixer round-trip to a fresh address +title: 'Shield + unlink: mixer round-trip to a fresh address' description: >- Four txs, ~60s. Public balance enters the Umbra mixer, a claim breaks the on-chain link, then withdraw + transfer delivers to a FRESH destination. diff --git a/content/docs/api-reference/agent-privacy/swaps/agents/v1/anonymity-set/get.mdx b/content/docs/api-reference/agent-privacy/swaps/agents/v1/anonymity-set/get.mdx index 9f9cef4..4de1296 100644 --- a/content/docs/api-reference/agent-privacy/swaps/agents/v1/anonymity-set/get.mdx +++ b/content/docs/api-reference/agent-privacy/swaps/agents/v1/anonymity-set/get.mdx @@ -1,7 +1,7 @@ --- title: Pool depth for a denomination bucket description: >- - Inspect the anonymity set for a standard denomination bucket before swapping — + Inspect the anonymity set for a standard denomination bucket before swapping: how many recent deposits share that bucket. A deeper pool means a larger crowd to hide in. full: true @@ -14,7 +14,7 @@ _openapi: contents: - content: >- Inspect the anonymity set for a standard denomination bucket before - swapping — how many recent deposits share that bucket. A deeper pool + swapping: how many recent deposits share that bucket. A deeper pool means a larger crowd to hide in. --- diff --git a/content/docs/api-reference/agent-privacy/swaps/agents/v1/quote/get.mdx b/content/docs/api-reference/agent-privacy/swaps/agents/v1/quote/get.mdx index 645fffc..4a0653d 100644 --- a/content/docs/api-reference/agent-privacy/swaps/agents/v1/quote/get.mdx +++ b/content/docs/api-reference/agent-privacy/swaps/agents/v1/quote/get.mdx @@ -2,7 +2,7 @@ title: Quote a private swap description: >- Price a private swap before executing it. Returns the expected output after - Solrouter's spread plus anonymity-set guidance — whether the amount snaps to a + Solrouter's spread plus anonymity-set guidance: whether the amount snaps to a standard denomination bucket (strong privacy) or is an off-bucket amount with a unique on-chain fingerprint. full: true @@ -15,7 +15,7 @@ _openapi: contents: - content: >- Price a private swap before executing it. Returns the expected output - after Solrouter's spread plus anonymity-set guidance — whether the + after Solrouter's spread plus anonymity-set guidance: whether the amount snaps to a standard denomination bucket (strong privacy) or is an off-bucket amount with a unique on-chain fingerprint. --- diff --git a/content/docs/api-reference/agent-privacy/swaps/agents/v1/swaps/oneshot/post.mdx b/content/docs/api-reference/agent-privacy/swaps/agents/v1/swaps/oneshot/post.mdx index a18b97f..45edda0 100644 --- a/content/docs/api-reference/agent-privacy/swaps/agents/v1/swaps/oneshot/post.mdx +++ b/content/docs/api-reference/agent-privacy/swaps/agents/v1/swaps/oneshot/post.mdx @@ -1,7 +1,7 @@ --- title: Begin a one-shot ephemeral-wallet private swap (Mode B) description: >- - Start a private swap that runs through a throwaway ephemeral wallet — no + Start a private swap that runs through a throwaway ephemeral wallet, with no managed custody. Solrouter returns an unsigned `fundingTx`; the agent signs and broadcasts it, then calls `POST /swaps/oneshot/{id}/execute` to kick off the swap orchestrator. @@ -14,9 +14,9 @@ _openapi: headings: [] contents: - content: >- - Start a private swap that runs through a throwaway ephemeral wallet — - no managed custody. Solrouter returns an unsigned `fundingTx`; the - agent signs and broadcasts it, then calls `POST + Start a private swap that runs through a throwaway ephemeral wallet, + with no managed custody. Solrouter returns an unsigned `fundingTx`; + the agent signs and broadcasts it, then calls `POST /swaps/oneshot/{id}/execute` to kick off the swap orchestrator. --- diff --git a/content/docs/api-reference/agent.mdx b/content/docs/api-reference/agent.mdx index bbc842d..24a6fc1 100644 --- a/content/docs/api-reference/agent.mdx +++ b/content/docs/api-reference/agent.mdx @@ -130,7 +130,7 @@ The reply is encrypted to the `publicKey` inside `encryptedPrompt`. Any enclave ## Available tools -The default path registers 18 tools. They are listed on the [Agent Framework](/docs/concepts/agent-framework) page: `web_search`, `scrape_url`, `crawl_url`, `solana_balance`, `token_price`, `swap_quote`, `trending_tokens`, `deepwiki`, `colosseum_search`, `colosseum_archives`, `paysh_search_apis`, `paysh_call_api`, `github_list_repos`, `github_issues`, `github_read_file`, `notion_search`, `notion_get_page`, and `notion_query_database`. The model picks the tools at each step of the loop. +The default path registers 18 tools. They are listed on the [Agent Framework](/docs/how-it-works/agent-reasoning) page: `web_search`, `scrape_url`, `crawl_url`, `solana_balance`, `token_price`, `swap_quote`, `trending_tokens`, `deepwiki`, `colosseum_search`, `colosseum_archives`, `paysh_search_apis`, `paysh_call_api`, `github_list_repos`, `github_issues`, `github_read_file`, `notion_search`, `notion_get_page`, and `notion_query_database`. The model picks the tools at each step of the loop. Encrypted mode allows 5 tools inside the CVM: `web_search` (SearXNG inside the CVM), `token_price`, `trending_tokens`, `swap_quote`, and `solana_balance`. diff --git a/content/docs/api-reference/authentication.mdx b/content/docs/api-reference/authentication.mdx deleted file mode 100644 index 7fd76ab..0000000 --- a/content/docs/api-reference/authentication.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: "Authentication" -icon: KeyRound -description: "Pass your Solrouter API key as a Bearer token in the Authorization header. Generate keys at solrouter.com/sdk. No email or credit card required." -status: live -checked: "2026-08-26" ---- - -import { Callout } from 'fumadocs-ui/components/callout'; - -Solrouter authenticates REST API requests using Bearer tokens. Every request you make must include your API key in the `Authorization` header. Keys are tied to a prepaid balance denominated in USDC or `$ROUTER`, and usage is metered per call. There are no subscriptions or seat fees. - -## Request Format - -Include your API key as a Bearer token in every request: - -```bash -Authorization: Bearer sk_solrouter_... -``` - -## Getting an API Key - -1. Go to [solrouter.com/sdk](https://solrouter.com/sdk). -2. Connect your Solana wallet. No email or credit card is required. -3. Copy the generated API key. All keys follow the format `sk_solrouter_...`. -4. Top up your balance in **USDC** or **`$ROUTER`** to start making calls. - -## Full Request Example - -The following `curl` command shows a complete authenticated request to the `/agent` endpoint: - -```bash -curl -X POST "https://api.solrouter.com/agent" \ - -H "Authorization: Bearer sk_solrouter_..." \ - -H "Content-Type: application/json" \ - -d '{"prompt": "Hello", "model": "gpt-oss:20b", "useTools": false}' -``` - -## x402 Keyless Authentication - -If you are building an agent that does not hold an API key, Solrouter supports **x402 per-call USDC payment**. Instead of a long-lived API key, each request is settled individually on Solana mainnet. No account or prepaid balance is required. - -x402 is the standard. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. It does not show which facilitator settles payments. - -To discover the x402 payment manifest and per-call pricing, send a request to the API host: - -``` -GET https://api.solrouter.com/.well-known/x402 -``` - -Any x402-aware agent can use this manifest to self-fund calls autonomously. The x402 inference endpoint (`POST /api/v1/x402/chat/completions`) takes an Arcium-encrypted prompt and is priced at \$0.005 per call. - -For agent-to-agent interoperability, Solrouter also publishes an A2A protocol v1.0 discovery card at: - -``` -GET https://api.solrouter.com/.well-known/agent-card.json -``` - -This card describes the full skill list available to agents integrating with the Solrouter Agent Privacy API. - -## Error Responses - -| Status | Meaning | -| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| 401 Unauthorized | Missing or invalid API key. Check that your `Authorization` header is present and that the key begins with `sk_solrouter_`. | -| 402 Payment Required | x402 payment is required, or your prepaid balance is insufficient. Top up at [solrouter.com/sdk](https://solrouter.com/sdk) or use x402 per-call settlement. | -| 403 Forbidden | Which endpoints return 403, and when, is not determined. The API key check itself returns 401, not 403. | - - - Never expose your API key in client-side code, public repositories, or browser bundles. If your key is compromised, anyone can drain your prepaid balance. Rotate it immediately at [solrouter.com/sdk](https://solrouter.com/sdk) and treat the new key as a secret environment variable on your server or in a secrets manager. - diff --git a/content/docs/api-reference/errors.mdx b/content/docs/api-reference/errors.mdx new file mode 100644 index 0000000..ed9f548 --- /dev/null +++ b/content/docs/api-reference/errors.mdx @@ -0,0 +1,24 @@ +--- +title: "Errors" +icon: TriangleAlert +description: "The HTTP status codes the Solrouter API returns, what each one means, and how to fix it." +status: live +checked: "2026-08-26" +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + +Every endpoint returns standard HTTP status codes. The common ones are below. Endpoint-specific errors are listed on each endpoint's reference page. + +| Status | Meaning | Fix | +| --- | --- | --- | +| `400` Bad Request | A required field is missing. The x402 and `/api/v1/chat/completions` routes require `encryptedPrompt` and `model`. | Send every required field. | +| `401` Unauthorized | The `Authorization` header is missing, or the key does not begin with `sk_solrouter_`. | Send `Authorization: Bearer sk_solrouter_...`. | +| `402` Payment Required | Your prepaid balance is empty, or the route is x402-paywalled and no payment was attached. | Top up at [solrouter.com/sdk](https://solrouter.com/sdk), or pay per call with x402 and retry. | +| `403` Forbidden | Which endpoints return 403, and when, is not determined. The API-key check itself returns 401, not 403. | Not applicable. | +| `502` TEE unreachable | The backend could not reach the enclave. | Retry. If it persists, the enclave is down. | +| `503` TDX quote unavailable | The enclave is up but could not produce an attestation quote. | Retry. The reply path still works; only the quote is missing. | + + + Never expose your API key in client-side code or public repositories. Anyone with your key can spend your prepaid balance. If a key leaks, rotate it at [solrouter.com/sdk](https://solrouter.com/sdk). + diff --git a/content/docs/api-reference/meta.json b/content/docs/api-reference/meta.json index 9dbaa4c..f193a78 100644 --- a/content/docs/api-reference/meta.json +++ b/content/docs/api-reference/meta.json @@ -1,11 +1,11 @@ { - "title": "API Reference", + "title": "API", "icon": "Terminal", "pages": [ "overview", - "authentication", "agent", "tee", + "errors", "---Agent Privacy API---", "agent-privacy/swaps", "agent-privacy/managed-wallets", diff --git a/content/docs/api-reference/overview.mdx b/content/docs/api-reference/overview.mdx index f7455f1..2675246 100644 --- a/content/docs/api-reference/overview.mdx +++ b/content/docs/api-reference/overview.mdx @@ -33,12 +33,12 @@ All requests require a Bearer token in the `Authorization` header. You generate Authorization: Bearer sk_solrouter_... ``` -For agents that do not hold an API key, Solrouter also supports **x402 per-call USDC payment** on Solana mainnet. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest does not show which facilitator settles payments. See the [Authentication](/docs/api-reference/authentication) page for full details on both methods. +For agents that do not hold an API key, Solrouter also supports **x402 per-call USDC payment** on Solana mainnet. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest does not show which facilitator settles payments. See the [Authentication](/docs/build/api-key) page for full details on both methods. ## Endpoints - } href="/docs/develop/privacy-sdk"> + } href="/docs/build/privacy-sdk"> Status: Live. **POST /api/v1/chat/completions** takes an API key and requires `encryptedPrompt` and `model`; it returns 400 `bad_request` without them. `GET /api/v1/models`, `/api/v1/balance`, and `/api/v1/usage` sit beside it. `@solrouter/sdk` calls **POST /tee/process** and encrypts client-side. Keyless agents pay per call on `POST /api/v1/x402/chat/completions`. @@ -47,10 +47,10 @@ For agents that do not hold an API key, Solrouter also supports **x402 per-call } href="/docs/api-reference/tee/public-key"> - Status: Live. **GET /tee/public-key** returns the enclave's live X25519 public key. **GET /tee/attestation** returns the TDX quote when the CVM can reach the dStack agent; otherwise `tdxQuote` is null and `tdxQuoteError` says why. See the [attestation guide](/docs/concepts/attestation) for the verification flow. + Status: Live. **GET /tee/public-key** returns the enclave's live X25519 public key. **GET /tee/attestation** returns the TDX quote when the CVM can reach the dStack agent; otherwise `tdxQuote` is null and `tdxQuoteError` says why. See the [attestation guide](/docs/how-it-works/attestation) for the verification flow. - } href="/docs/develop/private-swaps"> + } href="/docs/build/agent-privacy-api"> **/agents/v1/**\* is the agent-first surface for private on-chain swaps, managed wallets, and x402-paywalled encrypted inference. Discovery, quote, and anonymity-set reads are Live. Swap execution is Soon. Full OpenAPI spec at `/agents/v1/openapi.json`. @@ -81,4 +81,4 @@ All endpoints return JSON. A top-level `success` field exists on `POST /agent` a The TEE key and attestation reads, x402 inference, the discovery documents, and `/agents/v1` responses have no `success` field. They return the result object directly, or an `error` field with a non-2xx status. -For error codes on authentication, see the [Authentication](/docs/api-reference/authentication) page, which covers `401`, `402`, and `403` responses. +For error codes on authentication, see the [Authentication](/docs/build/api-key) page, which covers `401`, `402`, and `403` responses. diff --git a/content/docs/api-reference/tee/public-key.mdx b/content/docs/api-reference/tee/public-key.mdx index 9345d96..5016796 100644 --- a/content/docs/api-reference/tee/public-key.mdx +++ b/content/docs/api-reference/tee/public-key.mdx @@ -71,5 +71,5 @@ In most cases you do not need to call this endpoint yourself. The enclave generates a new X25519 keypair on every CVM boot. The SDK fetches the key once per process and keeps it until you call `clearSession()`. A long-lived process can hold a stale key after the enclave restarts. When a request fails with `tee_unreachable`, or a reply fails to decrypt, call `clearSession()` and retry. The next request fetches the current key. Custom clients should do the same: cache the key, and fetch it again after a failure. - The X25519 keypair is generated inside the Confidential VM at boot. The private key never leaves the enclave: not to the Solrouter backend, not to any host process, and not to Solrouter staff. You can check this claim yourself. `GET /tee/attestation` returns a TDX quote whose `report_data` equals `sha256(publicKey)`. See the [attestation guide](/docs/concepts/attestation). + The X25519 keypair is generated inside the Confidential VM at boot. The private key never leaves the enclave: not to the Solrouter backend, not to any host process, and not to Solrouter staff. You can check this claim yourself. `GET /tee/attestation` returns a TDX quote whose `report_data` equals `sha256(publicKey)`. See the [attestation guide](/docs/how-it-works/attestation). diff --git a/content/docs/develop/private-swaps.mdx b/content/docs/build/agent-privacy-api.mdx similarity index 59% rename from content/docs/develop/private-swaps.mdx rename to content/docs/build/agent-privacy-api.mdx index 0e4c72c..0e9501b 100644 --- a/content/docs/develop/private-swaps.mdx +++ b/content/docs/build/agent-privacy-api.mdx @@ -1,5 +1,5 @@ --- -title: "Private Swaps" +title: "Agent Privacy API" icon: ArrowRightLeft description: "The Agent Privacy API (/agents/v1) gives AI agents private token swaps on Solana in two modes, plus pay-per-call encrypted inference over x402." status: mixed @@ -10,15 +10,9 @@ statusNote: "Swap execution is Soon: no mainnet swap run is on record. Discovery import { Callout } from 'fumadocs-ui/components/callout'; import { Tab, Tabs } from 'fumadocs-ui/components/tabs'; -When an autonomous agent moves tokens on Solana, the transaction graph exposes who paid whom. Anyone can trace the link between the funding source and the destination. The Agent Privacy API (`/agents/v1`) exists to break that link. +When an agent moves tokens on Solana, the transaction graph shows who paid whom. Anyone can trace the funding source to the destination. The Agent Privacy API (`/agents/v1`) breaks that link. It serves two agent needs: private on-chain swaps and encrypted inference. The main Solrouter SDK covers encrypted chat and research. One agent type swaps tokens privately. The other has no API key and pays per inference call with x402, a pay-per-request HTTP standard. The Solrouter backend and a swap worker run the swaps. The TEE (trusted execution environment, a hardware-isolated enclave) does not. Only the x402 inference endpoint on this page uses the encrypted TEE path. -This is a dedicated, agent-first surface for two things: privacy-preserving on-chain actions and encrypted inference. The main Solrouter SDK covers encrypted chat and research. This API serves two kinds of agent. One needs to swap tokens privately. The other has no API key and pays per inference call with x402, a pay-per-request HTTP standard. - -The Solrouter backend and a swap worker run the swaps. The TEE (trusted execution environment, a hardware-isolated enclave) does not. Only the x402 inference endpoint on this page uses the encrypted TEE path. - - - No sanctions screening runs today. Agents are responsible for their own compliance. - +No sanctions screening runs today. Agents are responsible for their own compliance. ## Feature status @@ -33,26 +27,20 @@ The Solrouter backend and a swap worker run the swaps. The TEE (trusted executio ## Execution modes -How you run a private swap depends on one question: does your agent keep its own funded wallet with Solrouter, or does it sign each operation on the fly? The API supports both. Pick the mode that matches how your agent already works. +Pick a mode by one question: does your agent keep a funded wallet with Solrouter, or sign each operation on the fly? The API supports both. - - Both swap modes are Soon. The code path exists, but no mainnet swap run is on record as of 2026-08-26. The samples below show the request shapes the routes accept today. - +Both swap modes are Soon. The code path exists, but no mainnet swap run is on record as of 2026-08-26. The samples below show the request shapes the routes accept today. - Use this mode when you want to fund once and forget the setup. Your agent provisions a long-lived managed Umbra wallet through the API, funds it a single time, then runs as many private swaps as it needs. No repeated wallet provisioning. + Fund once and forget the setup. Your agent provisions a long-lived managed Umbra wallet through the API, funds it once, then runs as many private swaps as it needs. Use it for agents that swap often, accumulate balance, or run on a schedule. Wallet routes require an API key. **How custody works:** * Solrouter holds the wallet keypair. The per-wallet Data Encryption Key (DEK) is wrapped with a Key Encryption Key (KEK) that the backend reads from `WALLET_VAULT_KEK`. - * The DEK is unwrapped inside the backend API and the swap worker for the duration of an operation, then wiped. + * The DEK is unwrapped inside the backend API and the swap worker for one operation, then wiped. * The wallet vault runs in the backend process, not inside a TEE. - **When to use it:** agents that run frequent swaps, need to accumulate balance over time, or operate on a recurring schedule. The managed wallet removes the overhead of signing a new funding transaction on every operation. - - Wallet routes require an API key. - ```bash # Provision a managed wallet for this agent curl -X POST "https://api.solrouter.com/agents/v1/wallets" \ @@ -76,17 +64,17 @@ How you run a private swap depends on one question: does your agent keep its own - Use this mode when your agent already has its own wallet and you would rather not hold a balance with Solrouter. Nothing is provisioned: your agent receives an unsigned funding transaction, signs it with its own wallet, submits the signature, and the worker handles the rest. + Use this mode when your agent has its own wallet and holds no balance with Solrouter. Nothing is provisioned. Your agent gets an unsigned funding transaction, signs it, submits the signature, and the worker handles the rest. Good for stateless agents and single-operation workflows. **The 7-step pipeline:** - 1. Agent requests a one-shot session, providing payer pubkey, mints, amount, and destination + 1. Agent requests a one-shot session with payer pubkey, mints, amount, and destination 2. API returns an unsigned funding transaction 3. Agent signs the transaction with its own wallet and broadcasts it 4. Agent submits the transaction signature to the API to start execution 5. Worker runs the Umbra mixer round-trip to break the on-chain link 6. Jupiter aggregator executes the swap - 7. Worker forwards the proceeds to the destination address, with no on-chain connection to the original payer + 7. Worker forwards the proceeds to the destination, with no on-chain link to the payer ```bash # 1. Create the session @@ -110,16 +98,14 @@ How you run a private swap depends on one question: does your agent keep its own # 4. Poll until state is settled curl "https://api.solrouter.com/agents/v1/sessions/SESSION_ID" ``` - - **When to use it:** stateless agents, single-operation workflows, or any agent that already manages its own wallet and prefers not to maintain a separate funded balance with Solrouter. -**What Solrouter stores per session:** `from_mint`, `to_mint`, `amount_base_units`, `destination_pubkey`, `payer_user_id`, `ephemeral_pubkey`, the wrapped ephemeral key, `final_tx_sig`, and `actual_out`. Solrouter's backend can read every one of these columns. Retention period: not published. +**What Solrouter stores per session:** `from_mint`, `to_mint`, `amount_base_units`, `destination_pubkey`, `payer_user_id`, `ephemeral_pubkey`, the wrapped ephemeral key, `final_tx_sig`, and `actual_out`. Solrouter's backend can read every column. Retention period: not published. ## Discovery endpoints -So your agent does not have to hardcode URLs, the API publishes its own configuration. A2A-compatible agents (the Agent-to-Agent interop protocol) and x402-aware runtimes read these endpoints to self-configure at runtime. +So your agent does not hardcode URLs, the API publishes its own configuration. A2A-compatible agents (the Agent-to-Agent interop protocol) and x402-aware runtimes read these endpoints to self-configure at runtime. | Endpoint | Description | Status | | ------------------------------ | ----------------------------------------------------------------- | ------ | @@ -132,14 +118,12 @@ The manifest is served by the API host: `https://api.solrouter.com/.well-known/x ## x402 encrypted inference -Not every agent has an API key, and account creation is friction you may not want. For those cases Solrouter exposes a pay-per-call encrypted inference endpoint. Your agent pays in USDC on Solana mainnet, with no account and no key management. - -x402 is the standard. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest shows `X402_FACILITATOR_URL` when it is set, or a built-in default when it is not. It does not show which facilitator settles payments. pay.sh is a catalog that lists the endpoint. +Not every agent has an API key, and account creation is friction. For those cases Solrouter exposes a pay-per-call encrypted inference endpoint. Your agent pays in USDC on Solana mainnet, with no account and no key management. x402 is the standard. The live manifest advertises Coinbase (`api.cdp.coinbase.com/x402`) as the facilitator. The manifest shows `X402_FACILITATOR_URL` when set, or a built-in default when not. It does not show which facilitator settles payments. pay.sh is a catalog that lists the endpoint. * **Endpoint:** `POST /api/v1/x402/chat/completions` * **Pricing:** \$0.005 per call, settled via x402 USDC on Solana mainnet * **Encryption:** Arcium-encrypted prompt in, encrypted response out. The same TEE path as the SDK. -* **Discovery:** `/.well-known/x402`. Any x402-aware agent runtime can auto-discover pricing and payment instructions. +* **Discovery:** `/.well-known/x402`. Any x402-aware runtime can auto-discover pricing and payment instructions. ```bash # x402 paywalled encrypted inference. No API key needed. @@ -153,10 +137,6 @@ curl -X POST "https://api.solrouter.com/api/v1/x402/chat/completions" \ Call the endpoint without payment and the server replies `402 Payment Required` with the price, the network, and the `payTo` address. An x402-aware runtime signs a USDC payment payload with the agent's wallet key and retries with the `X-PAYMENT` header. Solrouter's server sends that payload to its facilitator to verify and settle the transfer, then returns 200. The agent never talks to the facilitator. - - The paywall charges \$0.005 per call. The response body of this endpoint currently reports `paid.amount: 0.02`. This is a backend follow-up; the manifest price is the one charged. - +The paywall charges \$0.005 per call. The response body currently reports `paid.amount: 0.02`. This is a backend follow-up; the manifest price is the one charged. - - This endpoint returns the encrypted reply only. It does not commit an on-chain receipt. Receipts are created for `POST /tee/process`, the route the SDK uses. See [Encryption Proof](/docs/concepts/encryption-proof). - +This endpoint returns the encrypted reply only. It does not commit an on-chain receipt. Receipts are created for `POST /tee/process`, the route the SDK uses. See [Encryption Proof](/docs/how-it-works/proof). diff --git a/content/docs/develop/agent-tools-sdk.mdx b/content/docs/build/agent-tools-sdk.mdx similarity index 97% rename from content/docs/develop/agent-tools-sdk.mdx rename to content/docs/build/agent-tools-sdk.mdx index 5c06b75..ff16267 100644 --- a/content/docs/develop/agent-tools-sdk.mdx +++ b/content/docs/build/agent-tools-sdk.mdx @@ -10,7 +10,7 @@ statusNote: "The @solrouter/agent-tools package is not on npm yet." import { Callout } from 'fumadocs-ui/components/callout'; - `@solrouter/agent-tools` is not published on npm. The package exists in the Solrouter repository at version 1.0.0, but you cannot install it today. The code samples on this page show the planned API. To use the Agent Privacy API now, call `POST /agents/v1/*` over HTTP or use the `umbra_*` tools in the [MCP server](/docs/develop/mcp-server). + `@solrouter/agent-tools` is not published on npm. The package exists in the Solrouter repository at version 1.0.0, but you cannot install it today. The code samples on this page show the planned API. To use the Agent Privacy API now, call `POST /agents/v1/*` over HTTP or use the `umbra_*` tools in the [MCP server](/docs/build/mcp-server). An AI agent that swaps tokens on Solana leaves a link from payer to destination. To break that link you normally wire up quoting, signing, mixing, and settlement yourself. `@solrouter/agent-tools` will do that work. It gives your agent typed tools for the Agent Privacy API (`/agents/v1`). The agent can quote, run, and settle private swaps, and call encrypted inference. @@ -20,7 +20,7 @@ It ships with a Vercel AI SDK adapter, so you can drop it into an agent without ## What to use today - **HTTP.** Call the Agent Privacy API directly. Quotes and anonymity-set reads are live at `GET /agents/v1/quote` and `GET /agents/v1/anonymity-set`. Swap execution routes are Soon. -- **MCP.** The [MCP server](/docs/develop/mcp-server) wraps the same routes as `umbra_*` tools for Claude Desktop and Cursor. +- **MCP.** The [MCP server](/docs/build/mcp-server) wraps the same routes as `umbra_*` tools for Claude Desktop and Cursor. ## Vercel AI SDK quickstart (Soon) diff --git a/content/docs/develop/authentication.mdx b/content/docs/build/api-key.mdx similarity index 99% rename from content/docs/develop/authentication.mdx rename to content/docs/build/api-key.mdx index dbd7ffd..2b0f9c5 100644 --- a/content/docs/develop/authentication.mdx +++ b/content/docs/build/api-key.mdx @@ -1,5 +1,5 @@ --- -title: "Authentication" +title: "Get an API key" icon: KeyRound description: "Get an API key by connecting a Solana wallet at solrouter.com/sdk. No email, no KYC. Send the key as a bearer token, or pay per call with x402." status: live diff --git a/content/docs/develop/mcp-server.mdx b/content/docs/build/mcp-server.mdx similarity index 100% rename from content/docs/develop/mcp-server.mdx rename to content/docs/build/mcp-server.mdx diff --git a/content/docs/develop/meta.json b/content/docs/build/meta.json similarity index 52% rename from content/docs/develop/meta.json rename to content/docs/build/meta.json index 899ac37..6e67398 100644 --- a/content/docs/develop/meta.json +++ b/content/docs/build/meta.json @@ -1,11 +1,12 @@ { - "title": "Development", + "title": "Build on Solrouter", "icon": "Code", "pages": [ - "authentication", + "quickstart", "privacy-sdk", - "agent-tools-sdk", "mcp-server", - "private-swaps" + "agent-privacy-api", + "agent-tools-sdk", + "api-key" ] } diff --git a/content/docs/develop/privacy-sdk.mdx b/content/docs/build/privacy-sdk.mdx similarity index 93% rename from content/docs/develop/privacy-sdk.mdx rename to content/docs/build/privacy-sdk.mdx index c42edf2..b0d5f3e 100644 --- a/content/docs/develop/privacy-sdk.mdx +++ b/content/docs/build/privacy-sdk.mdx @@ -13,7 +13,7 @@ import { EncryptionFlow } from '@/components/diagrams/encryption-flow'; Most AI APIs read your prompts in the clear. The Solrouter Privacy SDK encrypts your prompt before it leaves your machine, so the Solrouter backend cannot read it. The SDK handles the cryptography for you. -Here is what happens when you call `client.chat()`. The SDK fetches the enclave's X25519 public key from `GET /tee/public-key`. It does not fetch or verify the attestation quote. That check is a manual step. See the [attestation guide](/docs/concepts/attestation). The SDK then encrypts your prompt on your machine with Arcium's RescueCipher. It sends the encrypted blob through the Solrouter backend, which forwards it without decrypting it. The TEE (Trusted Execution Environment, a CPU-isolated confidential VM) decrypts the prompt and calls the model on a Nosana GPU node. The node runs the model outside the enclave, so it sees the prompt and the reply in plaintext during inference. The reply comes back encrypted, and the SDK decrypts it with your session key. +Here is what happens when you call `client.chat()`. The SDK fetches the enclave's X25519 public key from `GET /tee/public-key`. It does not fetch or verify the attestation quote. That check is a manual step. See the [attestation guide](/docs/how-it-works/attestation). The SDK then encrypts your prompt on your machine with Arcium's RescueCipher. It sends the encrypted blob through the Solrouter backend, which forwards it without decrypting it. The TEE (Trusted Execution Environment, a CPU-isolated confidential VM) decrypts the prompt and calls the model on a Nosana GPU node. The node runs the model outside the enclave, so it sees the prompt and the reply in plaintext during inference. The reply comes back encrypted, and the SDK decrypts it with your session key. ## Installation diff --git a/content/docs/quickstart.mdx b/content/docs/build/quickstart.mdx similarity index 95% rename from content/docs/quickstart.mdx rename to content/docs/build/quickstart.mdx index 4068403..1e02f58 100644 --- a/content/docs/quickstart.mdx +++ b/content/docs/build/quickstart.mdx @@ -58,7 +58,7 @@ Most AI APIs can read every prompt you send them. Solrouter does not: your messa One call gets you a configured client. Pass your API key and the API base URL. - The SDK fetches the enclave's published public key from `GET /tee/public-key` before your first encrypted request. That key is the encryption target for your prompts. The SDK does not fetch or verify the attestation quote. To check the enclave yourself, see [Attestation](/docs/concepts/attestation). + The SDK fetches the enclave's published public key from `GET /tee/public-key` before your first encrypted request. That key is the encryption target for your prompts. The SDK does not fetch or verify the attestation quote. To check the enclave yourself, see [Attestation](/docs/how-it-works/attestation). ```typescript import { SolRouter } from '@solrouter/sdk'; @@ -81,7 +81,7 @@ Most AI APIs can read every prompt you send them. Solrouter does not: your messa Call `client.chat()` to send a message. The SDK encrypts it client-side with Arcium's RescueCipher. It sends the encrypted blob through the Solrouter backend, which cannot read it. Then it decrypts the response for you. - With the current SDK, use the default model `gpt-oss-20b`. Other catalog ids pass through with a type cast; see [Models](/docs/concepts/supported-models). + With the current SDK, use the default model `gpt-oss-20b`. Other catalog ids pass through with a type cast; see [Models](/docs/how-it-works/models). ```typescript // Encrypted end to end: the Solrouter backend never sees plaintext @@ -111,11 +111,11 @@ Most AI APIs can read every prompt you send them. Solrouter does not: your messa You have sent an encrypted request and checked your balance. That is the core loop. Where you go next depends on what you build: the full SDK surface, keyless payments, or direct HTTP access. - } href="/docs/develop/privacy-sdk"> + } href="/docs/build/privacy-sdk"> Full `@solrouter/sdk` documentation: model selection, plaintext mode (`encrypted: false`), BRAID reasoning (`reasoning: 'braid'`), and more. - } href="/docs/develop/authentication"> + } href="/docs/build/api-key"> Learn about API key auth, x402 keyless payments, and keeping your credentials safe. diff --git a/content/docs/concepts/agent-framework.mdx b/content/docs/concepts/agent-framework.mdx deleted file mode 100644 index 43c0fc8..0000000 --- a/content/docs/concepts/agent-framework.mdx +++ /dev/null @@ -1,110 +0,0 @@ ---- -title: "Agent Framework" -icon: LayoutDashboard -description: "The /agent endpoint runs a tool loop by default, a guided reasoning path (BRAID) on request, and a skill graph that adds domain knowledge to the answer." -status: live -checked: "2026-08-26" ---- - -import { Cards, Card } from 'fumadocs-ui/components/card'; -import { Callout } from 'fumadocs-ui/components/callout'; -import { Workflow, Share2 } from 'lucide-react'; - -The `/agent` endpoint has three paths. The request body selects the path. - -The default path is a standard tool loop. The model picks a tool, the backend runs it, and the model reads the result. The loop runs up to 8 model calls (`MAX_ITERATIONS = 8`). The last call has no tools, so the model must write the answer. - -The guided reasoning path (BRAID) runs only when the request has `reasoning: 'braid'`. It walks a fixed Guided Reasoning Diagram (GRD) and calls the model once at the end to write the answer. - -The encrypted path runs when the request has `encryptedPrompt`. The tool loop then runs inside the CVM (a confidential virtual machine, which is a TEE, trusted execution environment) with a 5-tool allowlist. - -A skill graph adds domain knowledge (DeFi, on-chain data, market analysis, and more) to the system prompt on the plaintext paths. It fires only when the prompt matches the trigger words of a node. - -Older material calls the guided reasoning path SERV. The code, the SDK option, and the API value call it BRAID. - -## Core components - - - } href="/docs/concepts/serv-reasoning"> - A fixed execution graph collects data, then one synthesis call writes the answer. Runs when the request has `reasoning: 'braid'`. Status: Live. - - - } href="/docs/concepts/skill-graphs"> - Domain knowledge from 44 nodes, added to the system prompt only when the prompt matches a node. Status: Live. - - - -## Three /agent paths - -| Path | Request field | Where it runs | Tools | Status | -| --- | --- | --- | --- | --- | -| Tool loop | `useTools: true` (default) | Backend | 18 built-in tools | Live | -| Guided reasoning (BRAID) | `reasoning: 'braid'` | Backend | Tools in the order the GRD sets | Live | -| Encrypted | `encryptedPrompt` | Inside the CVM | 5-tool allowlist | Live for REST callers who send `encryptedPrompt`. Soon for the SDK. Not used by the chat app, whose agent mode runs the plaintext tool loop. | - -## Built-in tools - -The plaintext `/agent` path registers 18 tools. The model picks which tools to call at each step of the loop. - -| Tool | Description | -| --- | --- | -| `web_search` | Search the web for current information. | -| `scrape_url` | Extract the main content from a URL. | -| `crawl_url` | Crawl a website and return its content as clean text. Uses Cloudflare Browser Rendering. | -| `solana_balance` | Get SOL and token balances for a Solana wallet address. | -| `token_price` | Get the current USD price, volume, liquidity, and market cap of a token. | -| `swap_quote` | Get a swap quote from the Jupiter DEX aggregator. | -| `trending_tokens` | List trending or boosted tokens from DexScreener with price data. | -| `deepwiki` | Ask questions about a GitHub repository through DeepWiki. | -| `colosseum_search` | Search Colosseum hackathon project submissions. | -| `colosseum_archives` | Search Colosseum's curated crypto archives. | -| `paysh_search_apis` | Find paid third-party APIs on pay.sh with their per-call USDC price. | -| `paysh_call_api` | Call a pay.sh endpoint. The call is paid from your USDC balance. | -| `github_list_repos` | List your GitHub repositories. Needs a connected GitHub account. | -| `github_issues` | List issues for a GitHub repository. | -| `github_read_file` | Read a file or list a directory in a GitHub repository. | -| `notion_search` | Search your connected Notion workspace. | -| `notion_get_page` | Read the text of a Notion page. | -| `notion_query_database` | List the rows of a Notion database. | - -The encrypted path runs a 5-tool allowlist inside the CVM: `web_search` (SearXNG inside the CVM), `token_price`, `trending_tokens`, `swap_quote`, and `solana_balance`. Every other tool fails closed in that mode. - -## Quick example - -This is the smallest request that runs the default tool loop. `useTools` defaults to `true`, so you can omit it. - -```bash -curl -X POST "https://api.solrouter.com/agent" \ - -H "Authorization: Bearer YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "prompt": "Compare Marginfi vs Kamino lending on Solana", - "model": "gpt-oss:20b", - "useTools": true - }' -``` - -The response holds the reply, every tool call with its result, the token usage, and the number of model calls. - -```json -{ - "success": true, - "reply": "## Marginfi vs Kamino Lending Comparison\n\n...", - "toolCalls": [ - { "tool": "web_search", "args": { "query": "Marginfi vs Kamino lending Solana" }, "result": { "...": "..." } }, - { "tool": "token_price", "args": { "token": "MNDE" }, "result": { "...": "..." } } - ], - "usage": { "promptTokens": 0, "completionTokens": 0, "totalTokens": 0 }, - "iterations": 4, - "model": "gpt-oss:20b", - "provider": "nosana", - "billing": null, - "freeMessagesRemaining": 0 -} -``` - -The skill graph runs on this path, but the response does not include a `skillGraph` field. The traversal only shapes the system prompt. - - - `useTools: true` is the default. It runs the standard tool loop with up to 8 model calls. It does not turn on guided reasoning. To use the BRAID path, send `reasoning: 'braid'`. See [POST /agent](/docs/api-reference/agent) for every field. - diff --git a/content/docs/concepts/encryption-proof.mdx b/content/docs/concepts/encryption-proof.mdx deleted file mode 100644 index e855b93..0000000 --- a/content/docs/concepts/encryption-proof.mdx +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: "Encryption Proof" -icon: Stamp -description: "Each inference sent through POST /tee/process writes an on-chain receipt signed inside the Intel TDX enclave. Paste the lock-icon link from any chat message (or an address, hash, or tx) and verify it yourself." -status: live -checked: "2026-08-26" ---- - -import { EncryptionProofVerifier } from '@/components/verify/encryption-proof-verifier'; -import { Callout } from 'fumadocs-ui/components/callout'; -import { Step, Steps } from 'fumadocs-ui/components/steps'; - -[Attestation](/docs/concepts/attestation) proves the enclave is genuine. The **encryption proof** ties *your specific request* to that enclave. A TDX-attested enclave (a TEE, a trusted execution environment: a hardware-isolated virtual machine) signs your exact ciphertext hash with a key that exists only inside the enclave, and the receipt goes to Solana. The backend relays it. It cannot change it without breaking the signature. A verifier who also checks the per-request quote can detect a forged record. - -Each receipt is a **Light Protocol compressed account**: about **0.000005 SOL each, about 400 times cheaper than a normal Solana PDA**. That cost gap is why a proof *per message* is viable. A standard PDA for every inference would cost too much at scale. Compression makes it routine. - -## Verify a proof - -In chat, every private-mode reply has a **🔒 next to it. Click it, copy the link, and paste it here.** You can also paste a Light attestation address, the commit-transaction signature, or the 64-character encrypted-prompt hash. The widget fetches the record through `api.solrouter.com/attestation/*` and checks the enclave's ed25519 signature **in your browser**. Solrouter serves the record. The signature check proves the signer holds the key in `enclave_pubkey`. To prove that key lives in the enclave, do Step 4 below with the per-request quote. - - - -## Verifying from an agent or the SDK - -Chat hands you a clickable lock link. The REST endpoint `POST /tee/process` hands you the proof in the response payload instead. Every private inference that commits a receipt returns an `onchainAttestation` object: - -```json -{ - "type": "light-compressed", - "address": "12Qenx1LK3ddTX5F3Apm6HYFFjswL3acSYNYXqUHTAVn", // Light compressed account - "encryptedPromptHash": "5b17ccd7…", // sha256(ciphertext) - "signature": "4RFJVwSC…", // the commit transaction - "explorerUrl": "https://solscan.io/tx/4RFJVwSC…" -} -``` - -The field is `null` when the commit failed. The `@solrouter/sdk` `chat()` response does not pass this object through today. It exposes `privacyAttestationId` only. To get the full object, call `POST /tee/process` over HTTP. - -Verify it three equivalent ways. Paste any of these into the widget above, or call the public endpoints directly: - -```bash -# by the commit-transaction signature (what `signature` / the 🔒 link points to) -curl https://api.solrouter.com/attestation/by-tx/ - -# by the attestation address -curl https://api.solrouter.com/attestation/
- -# by the encrypted-prompt hash -curl https://api.solrouter.com/attestation/by-hash/ -``` - -Each returns the full record with `version: "v2"` and every proof field below. You can check it in your own code against the enclave's signature. - - - The lock link is a transaction link (a compressed account can't be browsed on - Solscan). The `by-tx` endpoint reads the commit transaction, pulls the - encrypted-prompt hash out of its instruction data, and re-derives the - attestation address. So the link you already have is enough. - - -## What the record stores, and what gets signed - -A v2 attestation lives under the Solrouter program `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`. Its address is **deterministic**: `deriveAddressV2(["attestation_v2", sha256(ciphertext)], …)`. That is why a bare hash is enough to find it. Alongside the basics (`model`, `provider`, `timestamp`, `backend_saw_plaintext`, `tee_processed`) it stores the proof: - -| Field | Meaning | -| --- | --- | -| `client_pubkey` | Your ephemeral X25519 key from the request | -| `tee_pubkey` | The enclave's X25519 sealing key your ciphertext was sealed to | -| `nonce` | The RescueCipher nonce | -| `enclave_pubkey` | The enclave's ed25519 signing key, **bound inside the per-request TDX quote** | -| `enclave_sig_r` / `enclave_sig_s` | The two halves of the ed25519 signature | -| `tdx_quote_hash` | `sha256` of the TDX quote that attests `enclave_pubkey` | - -Inside the Confidential VM, the enclave signs this exact byte tuple: - -``` -"SOLR-ATTEST-v2" - ‖ sha256(ciphertext) // 32 your encrypted prompt - ‖ tee_pubkey // 32 the sealing key - ‖ nonce // 16 - ‖ client_pubkey // 32 your request key - ‖ len(model) ‖ model - ‖ len(provider) ‖ provider -``` - -## Verify the signature yourself, by hand - - - -### Read the record - -Use any of the `curl` calls above. You get back `version: "v2"` plus every field: `encryptedPromptHash` (hex); `teePubkey`, `nonce`, `clientPubkey`, `enclavePubkey`, `enclaveSigR`, `enclaveSigS` (base64). - - - -### Rebuild the signed message - -Concatenate the tuple above from the record bytes (`encryptedPromptHash` decoded from hex; the keys/nonce decoded from base64). - - - -### Check the signature - -Reassemble the 64-byte signature as `enclaveSigR ‖ enclaveSigS` and verify it over your message against `enclavePubkey` with any Ed25519 library. If a single byte was tampered (different ciphertext, swapped key, forged signer), it fails. - - - -### (Optional) Anchor the signing key in hardware - -Two quotes exist, and they pin different values in `report_data`: - -| Quote | Where you get it | `report_data` | -| --- | --- | --- | -| Service quote | `GET /tee/attestation` | `sha256(tee_pubkey)` | -| Per-request quote | `attestation.tdxQuote` in a `POST /tee/process` response | `sha256(tee_pubkey ‖ enclave_pubkey)` | - -To confirm `enclave_pubkey` is bound to hardware, use the per-request quote. Compute `sha256(teePubkey ‖ enclavePubkey)` from the record and compare it with the quote's `report_data`. `GET /tee/attestation` proves only that the enclave owns the X25519 sealing key. The record's `tdx_quote_hash` is the sha256 of the per-request quote's JSON. - - - -## Why a forged receipt is detectable - -The ed25519 signing key is generated **inside** the enclave at boot and never leaves it. The backend signs and pays for the Solana transaction with the deployer wallet. It could commit a record that carries any ed25519 key and signature. The on-chain program stores those fields and does not check them. The browser widget checks the signature against the `enclave_pubkey` in the record. That check alone does not prove the signer is the enclave. To detect a forged record, also check that `enclave_pubkey` is bound into the per-request TDX quote (Step 4 above). A verifier who skips the quote check cannot tell a real enclave key from another key. Step 4 closes that gap. - - - A passing check proves the key named in the record signed *your exact - ciphertext*, and that the record is committed on-chain. The per-request - quote check (Step 4) proves that key belongs to an attested TDX enclave. For the deepest level, - verifying Intel's full DCAP signature chain on the raw TDX quote, fetch the - live quote from `GET /tee/attestation` and run it through Intel's DCAP - libraries. The on-chain record stores the quote's hash, not the full quote. - diff --git a/content/docs/concepts/how-it-works.mdx b/content/docs/concepts/how-it-works.mdx deleted file mode 100644 index 6b5113b..0000000 --- a/content/docs/concepts/how-it-works.mdx +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: "How It Works" -icon: Workflow -description: "Your prompt is encrypted on your device. Solrouter's backend relays ciphertext it cannot read. An Intel TDX enclave decrypts it and runs the model on a Nosana GPU node." -status: live -checked: "2026-08-26" ---- - -import { Callout } from 'fumadocs-ui/components/callout'; -import { EncryptionFlow } from '@/components/diagrams/encryption-flow'; - -Most AI providers see every word you send. Solrouter routes your traffic, but its backend cannot read it. - -When you send a message, your prompt is encrypted on your device before it leaves your application. The Solrouter backend never sees plaintext. It forwards an opaque encrypted blob to an Intel TDX Confidential VM (a Trusted Execution Environment, or TEE: hardware that isolates code and data even from the machine's owner). Only the enclave holds the key that can decrypt your request. - -The enclave decrypts the prompt and calls the model on a Nosana GPU node. The enclave then encrypts the reply and returns it for you to decrypt locally. Plaintext exists in two places: inside the enclave, and on the Nosana GPU node during inference. Solrouter's backend is never one of them. - -## Encryption Stack - -This section walks through the four layers of the pipeline. A breach of Solrouter's own backend does not expose your prompt or the reply on the encrypted path. - -**Client-side encryption** - -Your prompt is encrypted before it leaves your device using Arcium's **RescueCipher**, a field-element symmetric cipher. The session key comes from an **X25519 key exchange** with the TEE's public key, which the SDK fetches from `GET /tee/public-key`. The TEE generates its X25519 keypair inside the Confidential VM at boot. The private key never leaves the enclave, so only the enclave can derive the shared session secret. No one in the middle can. - -**Inference isolation** - -The Solrouter backend acts as a blind relay. It takes the encrypted blob and forwards it to an **Intel TDX Confidential VM**, a hardware-level Trusted Execution Environment. The host operating system and hypervisor cannot read enclave memory. Decryption and response encryption happen inside that isolated boundary. - -The model itself runs on a Nosana GPU node, not inside the enclave. The enclave sends the decrypted prompt to that node at the configured Nosana endpoint URL (HTTPS per the documented node URL, not re-verified) and receives the reply. The prompt and reply exist in plaintext in that node's memory during inference. The node operator could read the prompt at that moment. Solrouter does not control that hardware. The request is not linked to your identity on the node. - -**Transport** - -The channel between your client and the enclave is encrypted end-to-end. The backend never sees your prompt or the reply. It does see your API key, the model id, `chatId`, and any `systemPrompt` you set, in plaintext. - -**Verifiable attestation** - -You should not have to take our word that the right code is running. The enclave publishes an **Intel-signed TDX quote** that binds its X25519 public key to the code measurement of the running image. Fetch the quote from `GET /tee/attestation`. Verify Intel's signature chain and confirm that the key you encrypt to belongs to that enclave. Reference measurements to compare against: Soon. The product repository is private, so no reference values are published today. - -## Request Flow - -Here is the complete path a privacy-mode request travels, and where your data is encrypted at each hop. - - - -In words: - -1. Your device encrypts the prompt with RescueCipher. The key comes from an X25519 exchange with the TEE public key. -2. The Solrouter backend receives the ciphertext. It cannot decrypt it. It forwards the blob to the Intel TDX enclave. -3. The enclave decrypts the prompt inside hardware-isolated memory. -4. The enclave calls the model on a Nosana GPU node. The prompt is plaintext on that node during inference. -5. The enclave encrypts the reply with your session's ephemeral key. -6. The Solrouter backend relays the encrypted reply. It still cannot read it. -7. Your device decrypts the reply with your ephemeral private key. - -## What Is Not (Yet) Encrypted - -Privacy claims are easy to inflate, so here is exactly where the guarantee ends today. - - - Solrouter is **not** running pure fully homomorphic encryption (FHE) inference - today. No production system runs LLM-scale inference under FHE in 2026. The - compute overhead is many orders of magnitude away from viable latency. Anyone - claiming "FHE LLM inference" in production is overclaiming. - - What Solrouter provides is Arcium-encrypted transport, Intel TDX hardware - isolation for decryption, and a Light Protocol compressed account on Solana - as a receipt for each inference sent through `POST /tee/process`. That is a meaningful and - verifiable privacy guarantee. It is not the same as FHE inference, and we - will not claim otherwise. - - -The honest summary: your prompt is encrypted in transit and decrypted inside a hardware-enforced enclave. The model runs on plaintext on a Nosana GPU node. That plaintext is inaccessible to Solrouter's backend and to anyone watching the network. The Nosana node operator could read it during inference. It is not processed under FHE. - -## Roadmap - -The current TEE pipeline is the first step. Here is where the privacy model is headed and why your integration will not have to change to follow it. - -Solrouter is built on Arcium's MXE (Multiparty eXecution Environment) substrate, which combines MPC, FHE, and ZK primitives. Choosing **RescueCipher**, a field-element cipher, was deliberate. As Arcium's network matures, the same encrypted payloads that flow through today's TEE pipeline can later run under MPC, FHE, or ZK circuits. The client encryption layer does not change. - -As the MXE network ships in production, more of the inference pipeline moves from TEE-isolated plaintext into cryptographic compute. MPC comes first. FHE and ZK follow where latency and cost allow. When that shift happens, your integration stays the same. The encryption layer you use today is already compatible. diff --git a/content/docs/concepts/meta.json b/content/docs/concepts/meta.json deleted file mode 100644 index 2e40a6c..0000000 --- a/content/docs/concepts/meta.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "title": "Core Concepts", - "icon": "BookOpen", - "pages": [ - "how-it-works", - "encryption", - "attestation", - "encryption-proof", - "supported-models", - "agent-framework", - "serv-reasoning", - "skill-graphs" - ] -} diff --git a/content/docs/concepts/serv-reasoning.mdx b/content/docs/concepts/serv-reasoning.mdx deleted file mode 100644 index 9041a6f..0000000 --- a/content/docs/concepts/serv-reasoning.mdx +++ /dev/null @@ -1,97 +0,0 @@ ---- -title: "Guided reasoning (BRAID)" -icon: BrainCircuit -description: "BRAID walks a fixed Guided Reasoning Diagram to collect data, then calls the model once to write the answer. Older material calls it SERV." -status: live -checked: "2026-08-26" ---- - -import { Callout } from 'fumadocs-ui/components/callout'; -import { Step, Steps } from 'fumadocs-ui/components/steps'; - -A standard agent loop asks the model what to do at every step. Each step is one model call. BRAID separates the two jobs an agent does: deciding what data to gather, and writing the answer. - -BRAID walks a Guided Reasoning Diagram (GRD). A GRD is a fixed execution graph. It sets which tools run and in what order. The model is called once at the end to turn the collected data into a reply. - -The code, the SDK option, and the API value call this path BRAID. Older material calls it SERV. - -Six GRDs exist: `comparison`, `defi-analysis`, `general-research`, `market-overview`, `token-research`, and `wallet-analysis`. - -## What the split buys you - -The tool loop spends one model call per step. BRAID spends one synthesis call at the end, plus a short model call only for a branch that no rule can decide. That is the whole mechanism behind the cost and latency claim. No benchmark numbers are published. - -## How BRAID works - -A query passes through four stages. - - - - ### Intent detection - - Keyword rules map the prompt to one of six intents, for example wallet analysis, comparison, or DeFi analysis. Each intent maps to one GRD. No model call is made here. - - - - ### GRD execution - - BRAID walks the diagram node by node. Tool nodes run in the order the GRD sets. Branch nodes use a rule first. When no rule applies, BRAID asks the model a short one-word question to pick the branch. - - - - ### Skill graph injection - - If the prompt matches skill nodes, the skill graph adds domain context to the synthesis prompt. See [Skill Graphs](/docs/concepts/skill-graphs). - - - - ### Synthesis - - After the walk, BRAID calls the model once. The model turns the collected data into a reply. - - - -Two prompt shapes skip the GRD walk. A swap prompt with two known tokens, or a prompt with three or more known tokens, calls the tools directly and then runs the same single synthesis call. - -## Use BRAID in the SDK - -Pass `reasoning: 'braid'` to `chat()`. The SDK then sends the request to `/agent`. - -```typescript -import { SolRouter } from '@solrouter/sdk'; - -const client = new SolRouter({ - apiKey: 'sk_solrouter_...', - baseUrl: 'https://api.solrouter.com', -}); - -const response = await client.chat('Compare Marginfi vs Kamino lending on Solana', { - reasoning: 'braid', -}); - -console.log(response.message); -``` - -The SDK sends the prompt in plaintext on this path. The response has `encrypted: false`. Client-side encryption is not available for BRAID in SDK 1.1.0. - -## Use BRAID over HTTP - -Call `/agent` directly and set `reasoning: 'braid'`. `useTools: true` alone does not select BRAID. It runs the standard tool loop. - -```bash -curl -X POST "https://api.solrouter.com/agent" \ - -H "Authorization: Bearer YOUR_API_KEY" \ - -H "Content-Type: application/json" \ - -d '{ - "prompt": "Compare Marginfi vs Kamino lending on Solana", - "model": "gpt-oss:20b", - "reasoning": "braid", - "braidOptions": { "includeTrace": true } - }' -``` - -The response has `reasoning: 'braid'` and an `iterations` field. On this path `iterations` counts GRD nodes, not model calls. When `braidOptions.includeTrace` is `true`, the response also has a `braidTrace` object with the GRD id, the intent, and one entry per node. See [POST /agent](/docs/api-reference/agent) for the full envelope. - - - BRAID fits multi-step research prompts: protocol comparisons, wallet audits, market overviews. For a single token price or swap quote, a direct tool call is faster. - diff --git a/content/docs/concepts/skill-graphs.mdx b/content/docs/concepts/skill-graphs.mdx deleted file mode 100644 index a91207f..0000000 --- a/content/docs/concepts/skill-graphs.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: "Skill Graphs" -icon: Network -description: "Skill graphs add domain knowledge for DeFi, on-chain data, and market analysis to the agent's system prompt when the prompt matches a node." -status: live -checked: "2026-08-26" ---- - -import { Callout } from 'fumadocs-ui/components/callout'; - -A model asked about a DeFi protocol has to rebuild specialist knowledge (risk frameworks, liquidity heuristics, evaluation criteria) from its training data. Skill graphs hand the model that knowledge as structured text before it starts to answer. - -A skill graph is Solrouter's domain-knowledge layer. The engine runs on the plaintext `/agent` paths. When the prompt matches the trigger words of one or more nodes, the engine walks the connected graph and adds the node content to the system prompt. - -The knowledge is added only when it applies. A prompt that matches no node gets no extra tokens. - -## Knowledge nodes - -The engine defines 44 node ids. Each node has a name, trigger words, an `edges` list of connected nodes, and a Markdown body with heuristics, definitions, and evaluation criteria. - -The node ids are: `research-core`, `source-eval`, `defi-analysis`, `liquidity-risk`, `token-economics`, `market-analysis`, `on-chain-analysis`, `wallet-analysis`, `privacy-research`, `risk-assessment`, `smart-contract-risk`, `comparative-analysis`, `data-synthesis`, `colosseum-research`, `colosseum-archives`, `arcium-mpc`, `solana-ecosystem`, `jupiter-defi`, `raydium-defi`, `orca-defi`, `meteora-defi`, `kamino-defi`, `sanctum-staking`, `pump-fun`, `lulo-lending`, `ranger-perps`, `prediction-markets`, `helius-infra`, `light-protocol-zk`, `metaplex-nfts`, `pyth-oracle`, `switchboard-oracle`, `squads-multisig`, `debridge-cross-chain`, `coingecko-analytics`, `solana-kit-dev`, `anchor-dev`, `pinocchio-dev`, `framework-kit-frontend`, `solana-testing`, `solana-security-audit`, `token2022-extensions`, `quicknode-infra`, and `magicblock-gaming`. - -## Selective activation - -The engine scores every node against the prompt. A node scores when the prompt contains one of its trigger words. The engine then walks the graph from the top three scoring nodes. It follows `edges` to a depth of 2 and stops at 5 nodes. - - - A prompt that matches no trigger word gets no skill-graph text and no extra tokens. The graph fires when it matches and stays silent when it does not. - - -When a walk happens, the engine adds only the nodes it reached. A DeFi protocol comparison might walk `defi-analysis`, `liquidity-risk`, and `comparative-analysis` and leave the wallet and privacy nodes alone. - -## Skill graph in API responses - -The `/agent` response does not include a `skillGraph` field. The engine computes the walked path and a relevance score, but the route does not send them. The traversal only shapes the system prompt. - -On the BRAID path, `braidOptions.includeTrace: true` returns a `braidTrace` object. That trace lists GRD nodes, not skill-graph nodes. See [POST /agent](/docs/api-reference/agent) for the response envelopes. diff --git a/content/docs/glossary.mdx b/content/docs/glossary.mdx index 6ec8537..09a86d8 100644 --- a/content/docs/glossary.mdx +++ b/content/docs/glossary.mdx @@ -4,175 +4,129 @@ icon: BookA description: "Plain-language definitions of every term used in these docs, in alphabetical order, each with a link to the page that explains it." status: live checked: "2026-08-26" -statusNote: "Definitions match the code and the live API on the checked date. Where a term names a feature, its own status (Live, Soon, Archived) is given in the entry." +statusNote: "Definitions match the code and live API on the checked date. Where a term names a feature, its status (Live, Soon, Archived) is in the entry." --- -This page defines each term in plain words first. The technical name comes after. Every entry links to the page that explains the idea in full. - -Some entries use an analogy. Each analogy also says where it stops being accurate. - +This page defines each term in plain words first, then the technical name. Every entry links to the page that explains it in full. Some entries use an analogy and say where it stops being accurate. ## A +**A2A agent card.** A small public file that lists what an AI agent can do, in a format other agents read. Solrouter serves one at `/.well-known/agent-card.json` (Live). See [Discovery documents](/docs/api-reference/overview). -**A2A agent card.** A small public file that describes what an AI agent can do, in a format other agents can read. Solrouter serves one at `/.well-known/agent-card.json` (Live). See [Discovery documents](/docs/api-reference/discovery). - -**/agent endpoint.** The address (`POST /agent`) where a program sends a research question and gets back an answer built with tools. By default it runs a tool loop. The model decides what to look up, runs a tool, and repeats up to 8 times. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). +**/agent endpoint.** The address (`POST /agent`) where a program sends a research question and gets a tool-built answer. By default it runs a tool loop: the model picks a tool, runs it, and repeats up to 8 times. See [Agent endpoint and guided reasoning](/docs/how-it-works/agent-reasoning). -**Agent Privacy API.** A separate set of endpoints under `/agents/v1` built for autonomous software agents. It covers private token swaps (Soon) and pay-per-call encrypted answers with no account (Live). It is not the same thing as the `/agent` endpoint above. See [Agent Privacy API](/docs/products/agent-privacy-api). +**Agent Privacy API.** A separate set of endpoints under `/agents/v1` for autonomous software agents. It covers private token swaps (Soon) and pay-per-call encrypted answers with no account (Live). It is not the `/agent` endpoint above. See [Agent Privacy API](/docs/build/agent-privacy-api). -**Agent Tools SDK.** A planned code package, `@solrouter/agent-tools`, that would wrap the Agent Privacy API for developers. It is not published on npm yet (Soon). See [Agent Tools SDK](/docs/products/agent-tools-sdk). +**Agent Tools SDK.** A planned code package, `@solrouter/agent-tools`, that would wrap the Agent Privacy API. It is not on npm yet (Soon). See [Agent Tools SDK](/docs/build/agent-tools-sdk). -**Anonymity set.** The group of deposits a mixer cannot tell apart from yours. A bigger group gives more privacy. `GET /agents/v1/anonymity-set` reports the size for a given amount bucket (Live). See [Private swaps internals](/docs/under-the-hood/private-swaps). +**Anonymity set.** The group of deposits a mixer cannot tell apart from yours. A bigger group gives more privacy. `GET /agents/v1/anonymity-set` reports the size for an amount bucket (Live). See [Private swaps internals](/docs/build/agent-privacy-api). -**API key.** A secret string that starts with `sk_solrouter_`. You send it with a request so Solrouter knows which prepaid balance to charge. Treat it like a password. See [Get an API key](/docs/account/api-key). +**API key.** A secret string that starts with `sk_solrouter_`. Send it with a request so Solrouter knows which prepaid balance to charge. Treat it like a password. See [Get an API key](/docs/build/api-key). -**Arcium.** The company whose software library Solrouter uses to encrypt prompts on your device. The chat app labels this "encrypted with Arcium". Arcium also runs a network for computing on encrypted data, which Solrouter does not use for inference today. See [RescueCipher and X25519](/docs/under-the-hood/encryption). - -**Attestation.** A signed statement from the computer chip itself. It says two things. A real Intel chip runs this sealed program, and the program owns this public key. Think of it as a tamper-evident seal on a package. The analogy breaks here. The seal proves the hardware and the key. Solrouter has not published reference values, so you cannot yet prove which program image is inside. See [What is a TEE?](/docs/what-is-a-tee) and [TDX attestation](/docs/under-the-hood/attestation). +**Arcium.** The company whose software library Solrouter uses to encrypt prompts on your device. The chat app labels this "encrypted with Arcium". Arcium also runs a network for computing on encrypted data, which Solrouter does not use for inference today. See [RescueCipher and X25519](/docs/how-it-works/encryption). +**Attestation.** A signed statement from the computer chip. It says a real Intel chip runs this sealed program, and the program owns this public key. Think of it as a tamper-evident seal. The seal proves the hardware and the key. Solrouter has not published reference values, so you cannot yet prove which program image is inside. See [What is a TEE?](/docs/how-it-works/what-is-a-tee) and [TDX attestation](/docs/how-it-works/attestation). ## B +**Backend.** The ordinary Solrouter servers that receive your request, charge your balance, and forward the encrypted message to the enclave. On the encrypted path the backend holds no key and cannot read your prompt. It is a blind courier. See [What is private here](/docs/use/what-is-private). -**Backend.** The ordinary Solrouter servers that receive your request, charge your balance, and forward the encrypted message to the enclave. On the encrypted path the backend holds no key and cannot read your prompt. It is a blind courier. See [What is private here](/docs/what-is-private). - -**BRAID.** Solrouter's guided reasoning feature. A normal agent asks the model what to do at each step. BRAID instead follows a fixed plan (a GRD) and gathers data with tools. Then it calls the model once to write the answer. You request it with `reasoning: 'braid'`. Older material calls this SERV. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). - +**BRAID.** Solrouter's guided reasoning feature. A normal agent asks the model what to do at each step. BRAID instead follows a fixed plan (a GRD), gathers data with tools, then calls the model once to write the answer. Request it with `reasoning: 'braid'`. Older material calls this SERV. See [Agent endpoint and guided reasoning](/docs/how-it-works/agent-reasoning). ## C +**Ciphertext and plaintext.** Plaintext is text anyone can read. Ciphertext is the scrambled form that only a key holder can turn back into text. On the encrypted path your prompt leaves your device as ciphertext. See [What is private here](/docs/use/what-is-private). -**Ciphertext and plaintext.** Plaintext is text anyone can read. Ciphertext is the scrambled form that only a key holder can turn back into text. On the encrypted path your prompt leaves your device as ciphertext. See [What is private here](/docs/what-is-private). - -**Confidential VM (CVM).** A virtual computer whose memory the chip encrypts. The owner of the physical machine cannot look inside it. Solrouter's enclave is a CVM on Intel TDX hardware hosted by Phala. See [What is a TEE?](/docs/what-is-a-tee). - +**Confidential VM (CVM).** A virtual computer whose memory the chip encrypts, so the owner of the physical machine cannot look inside. Solrouter's enclave is a CVM on Intel TDX hardware hosted by Phala. See [What is a TEE?](/docs/how-it-works/what-is-a-tee). ## D +**DEK and KEK.** Two keys for managed swap wallets. The DEK (data encryption key) locks one wallet's secret. The KEK (key encryption key) is a wrapping key the backend holds, and it locks the DEK. Both are handled in the backend process, not the enclave. See [Private swaps internals](/docs/build/agent-privacy-api). -**DEK and KEK.** Two keys used for managed swap wallets. The DEK (data encryption key) locks one wallet's secret. The KEK (key encryption key) is a wrapping key the backend holds, and it locks the DEK. Both are handled in the backend process, not in the enclave. See [Private swaps internals](/docs/under-the-hood/private-swaps). - -**Discovery documents.** Public files a program can fetch to learn what Solrouter offers and what each call costs, without reading these docs. They include the A2A agent card, the x402 manifest, an OpenAPI file (a machine-readable list of endpoints), and `/agents/v1/capabilities`. All are Live. See [Discovery documents](/docs/api-reference/discovery). - +**Discovery documents.** Public files a program can fetch to learn what Solrouter offers and what each call costs. They include the A2A agent card, the x402 manifest, an OpenAPI file (a machine-readable list of endpoints), and `/agents/v1/capabilities`. All are Live. See [Discovery documents](/docs/api-reference/overview). ## E +**ed25519 signature.** A digital signature scheme. The enclave creates an ed25519 signing key at boot and signs the encryption proof for each private reply. The signature lets anyone check that the enclave, not the backend, produced the receipt. See [On-chain encryption proof](/docs/how-it-works/proof). -**ed25519 signature.** A digital signature scheme. The enclave creates an ed25519 signing key at boot and uses it to sign the encryption proof for each private reply. The signature lets anyone check that the enclave, and not the backend, produced the receipt. See [On-chain encryption proof](/docs/under-the-hood/encryption-proof). - -**Enclave.** The sealed program that decrypts your prompt. In these docs "enclave" and "Confidential VM" mean the same running service. Picture a locked room with one mail slot: encrypted letters go in, encrypted replies come out. The analogy breaks here: the enclave sends your decrypted prompt to a GPU computer outside the room to run the model. See [What is a TEE?](/docs/what-is-a-tee). - -**Encryption proof.** A receipt for one private reply, written to the Solana blockchain. The enclave signs a summary of your encrypted prompt, and Solrouter stores it in a compressed account. Anyone with the lock link can check it. It proves the enclave handled that exact ciphertext. It does not prove what the model said. See [Check a reply yourself](/docs/verify-a-reply). +**Enclave.** The sealed program that decrypts your prompt. In these docs "enclave" and "Confidential VM" mean the same running service. Picture a locked room with one mail slot: encrypted letters in, encrypted replies out. The analogy breaks here: the enclave sends your decrypted prompt to a GPU computer outside the room to run the model. See [What is a TEE?](/docs/how-it-works/what-is-a-tee). +**Encryption proof.** A receipt for one private reply, written to the Solana blockchain. The enclave signs a summary of your encrypted prompt, and Solrouter stores it in a compressed account. Anyone with the lock link can check it. It proves the enclave handled that exact ciphertext, not what the model said. See [Check a reply yourself](/docs/verify). ## F +**Facilitator (x402).** The third-party service that checks and settles a pay-per-call payment. In production the manifest names Coinbase's facilitator. See [x402 payments](/docs/build/api-key). -**Facilitator (x402).** The third-party service that checks and settles a pay-per-call payment. In production the manifest names Coinbase's facilitator. See [x402 payments](/docs/under-the-hood/x402). - -**FDV (fully diluted valuation).** The value of every token that will ever exist, at today's price. Solrouter's fundraising sells tokens in steps tied to FDV bands. See [$ROUTER token](/docs/account/token). - -**FHE, MPC, and ZK.** Three families of maths for working with data while it stays encrypted. Solrouter does not use any of them to run the model today. The cipher it uses was chosen so a future move in that direction would not change the client side. See [RescueCipher and X25519](/docs/under-the-hood/encryption). +**FDV (fully diluted valuation).** The value of every token that will ever exist, at today's price. Solrouter's fundraising sells tokens in steps tied to FDV bands. See [$ROUTER token](/docs/token). +**FHE, MPC, and ZK.** Three families of maths for working with data while it stays encrypted. Solrouter does not use any of them to run the model today. It chose the cipher so a future move in that direction would not change the client side. See [RescueCipher and X25519](/docs/how-it-works/encryption). ## G +**GRD (Guided Reasoning Diagram).** A fixed plan for one kind of question. It tells BRAID which tools to run and in what order. Six exist: comparison, DeFi analysis, general research, market overview, token research, and wallet analysis. See [Agent endpoint and guided reasoning](/docs/how-it-works/agent-reasoning). -**GRD (Guided Reasoning Diagram).** A fixed plan for one kind of question. It tells BRAID which tools to run and in what order. Six exist: comparison, DeFi analysis, general research, market overview, token research, and wallet analysis. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). - -**Guest mode.** Using the chat app without a wallet. Guests get 5 free messages per day per network address. See [Chat app](/docs/products/chat-app). - +**Guest mode.** Using the chat app without a wallet. Guests get 5 free messages per day per network address. See [Chat app](/docs/use/chat-app). ## I +**Intel DCAP.** Intel's free software for checking that a TDX quote came from real Intel hardware. A security researcher can run it against the quote Solrouter returns. See [Check a reply yourself](/docs/verify). -**Intel DCAP.** Intel's free software for checking that a TDX quote came from real Intel hardware. A security researcher can run it against the quote Solrouter returns. See [Check a reply yourself](/docs/verify-a-reply). - -**Intel TDX.** The Intel chip feature that creates Confidential VMs and signs attestation quotes. TDX stands for Trust Domain Extensions. Solrouter's enclave reports its type as `INTEL-TDX-PHALA`. See [What is a TEE?](/docs/what-is-a-tee). - +**Intel TDX.** The Intel chip feature that creates Confidential VMs and signs attestation quotes. TDX stands for Trust Domain Extensions. Solrouter's enclave reports its type as `INTEL-TDX-PHALA`. See [What is a TEE?](/docs/how-it-works/what-is-a-tee). ## J - -**Jupiter.** A Solana service that finds the best price across many exchanges for a token swap. The private swap worker uses Jupiter for the swap step (Soon). See [Private swaps internals](/docs/under-the-hood/private-swaps). - +**Jupiter.** A Solana service that finds the best price across many exchanges for a token swap. The private swap worker uses Jupiter for the swap step (Soon). See [Private swaps internals](/docs/build/agent-privacy-api). ## L +**Lamports.** The smallest unit of SOL, Solana's native coin. One SOL is one billion lamports. API amounts use these base units, so `10000000` means 0.01 SOL. See [Agent Privacy API](/docs/build/agent-privacy-api). -**Lamports.** The smallest unit of SOL, Solana's native coin. One SOL is one billion lamports. API amounts are given in these base units, so `10000000` means 0.01 SOL. See [Agent Privacy API](/docs/products/agent-privacy-api). - -**Light Protocol compressed account.** A cheap kind of record on the Solana blockchain. Solrouter stores each encryption proof in one. Older Solrouter material called this record a "PDA". The current record is a compressed account. Its address is derived from the hash of your ciphertext. See [On-chain encryption proof](/docs/under-the-hood/encryption-proof). - -**Liquidity pool.** A shared pot of two tokens on an exchange that lets people trade one for the other at any time. Part of the $ROUTER supply is placed in one at launch. See [$ROUTER token](/docs/account/token). +**Light Protocol compressed account.** A cheap record on the Solana blockchain. Solrouter stores each encryption proof in one. Older material called this record a "PDA"; the current record is a compressed account. Its address comes from the hash of your ciphertext. See [On-chain encryption proof](/docs/how-it-works/proof). +**Liquidity pool.** A shared pot of two tokens on an exchange that lets people trade one for the other at any time. Part of the $ROUTER supply is placed in one at launch. See [$ROUTER token](/docs/token). ## M +**Managed wallet (Mode A).** A swap mode where Solrouter creates and holds a wallet for your agent. You fund it once and run swaps from it. Solrouter holds the key, so this is custody, not self-custody. Swap execution is Soon. See [Agent Privacy API](/docs/build/agent-privacy-api). -**Managed wallet (Mode A).** A swap mode where Solrouter creates and holds a wallet for your agent. You fund it once and run swaps from it. Solrouter holds the key, so this is custody, not self-custody. Swap execution is Soon. See [Agent Privacy API](/docs/products/agent-privacy-api). - -**Maximum Privacy Mode.** A chat app setting. When it is on, your messages are encrypted on your device and never stored. Refresh the page and the conversation is gone. It is off by default. See [Chat app](/docs/products/chat-app). +**Maximum Privacy Mode.** A chat app setting. When on, your messages are encrypted on your device and never stored. Refresh the page and the conversation is gone. It is off by default. See [Chat app](/docs/use/chat-app). -**MCP (Model Context Protocol).** A standard that lets desktop AI apps such as Claude Desktop or Cursor call outside tools. Solrouter's MCP server adds its tools to those apps (Live). Only some of those tools use the encrypted path. See [MCP server](/docs/products/mcp-server). +**MCP (Model Context Protocol).** A standard that lets desktop AI apps such as Claude Desktop or Cursor call outside tools. Solrouter's MCP server adds its tools to those apps (Live). Only some of those tools use the encrypted path. See [MCP server](/docs/build/mcp-server). -**Memory (wallet-encrypted).** A chat app feature that remembers facts across conversations. The facts are encrypted with a key made from your wallet's signature, so only your wallet can unlock them. The backend stores only the sealed form. See [Chat app](/docs/products/chat-app). +**Memory (wallet-encrypted).** A chat app feature that remembers facts across conversations. The facts are encrypted with a key made from your wallet's signature, so only your wallet can unlock them. The backend stores only the sealed form. See [Chat app](/docs/use/chat-app). -**Mint address.** The unique on-chain address that identifies one token type on Solana, such as USDC or $ROUTER. Swap requests name tokens by mint address. See [Agent Privacy API](/docs/products/agent-privacy-api). - -**Mixer.** A shared on-chain pool. It breaks the link between the wallet that puts money in and the wallet that takes it out. Picture many people dropping same-size envelopes into one box, then each taking one out. The analogy breaks here. The fact that you used the box is public. Amounts at the edges of the pool are visible. Solrouter uses the Umbra mixer (Soon). See [Private swaps internals](/docs/under-the-hood/private-swaps). +**Mint address.** The unique on-chain address that identifies one token type on Solana, such as USDC or $ROUTER. Swap requests name tokens by mint address. See [Agent Privacy API](/docs/build/agent-privacy-api). +**Mixer.** A shared on-chain pool that breaks the link between the wallet that puts money in and the one that takes it out. Picture people dropping same-size envelopes into one box, then each taking one out. The analogy breaks here: using the box is public, and amounts at the edges of the pool are visible. Solrouter uses the Umbra mixer (Soon). See [Private swaps internals](/docs/build/agent-privacy-api). ## N +**Nonce.** A random number used once per encrypted message, so two identical prompts never make the same ciphertext. The nonce is stored in the encryption proof. See [RescueCipher and X25519](/docs/how-it-works/encryption). -**Nonce.** A random number used once per encrypted message so two identical prompts never produce the same ciphertext. The nonce is stored in the encryption proof. See [RescueCipher and X25519](/docs/under-the-hood/encryption). - -**Nosana GPU node.** A rented computer with a graphics card on the Nosana network. It runs the AI model. The enclave sends it your decrypted prompt over an encrypted connection. The model runs outside the enclave, so the node operator could read the prompt during that moment. Solrouter does not control that hardware, and the request is not tied to your identity there. See [What is private here](/docs/what-is-private). - -**Nosana job.** One running task on the Nosana network. Solrouter runs each model as its own job, so each model has its own node and address. After idle time a node can answer "Nosana GPU node is warming up"; wait and retry. See [Models and Nosana nodes](/docs/under-the-hood/models). +**Nosana GPU node.** A rented computer with a graphics card on the Nosana network. It runs the AI model. The enclave sends it your decrypted prompt over an encrypted connection. The model runs outside the enclave, so the node operator could read the prompt at that moment. Solrouter does not control that hardware, and the request is not tied to your identity there. See [What is private here](/docs/use/what-is-private). +**Nosana job.** One running task on the Nosana network. Solrouter runs each model as its own job, so each model has its own node and address. After idle time a node can answer "Nosana GPU node is warming up"; wait and retry. See [Models and Nosana nodes](/docs/how-it-works/models). ## O +**Ollama.** Free software that runs open-weight models and answers requests in the common OpenAI format. Each Nosana node runs Ollama to serve its model. See [Models and Nosana nodes](/docs/how-it-works/models). -**Ollama.** Free software that runs open-weight models on a computer and answers requests in the common OpenAI format. Each Nosana node runs Ollama to serve its model. See [Models and Nosana nodes](/docs/under-the-hood/models). - -**One-shot swap (Mode B).** A swap mode where your agent keeps its own wallet. Solrouter returns an unsigned funding transaction, your agent signs it, and a worker does the rest. Swap execution is Soon. See [Agent Privacy API](/docs/products/agent-privacy-api). - -**Open-weight model.** An AI model whose files are public, so anyone can download and run it on their own hardware. This is what makes private hosting possible. Solrouter runs `gpt-oss:20b` (Live), `qwen3.8:27b` (Live), and `gemma4:31b` (Soon). See [Models and Nosana nodes](/docs/under-the-hood/models). +**One-shot swap (Mode B).** A swap mode where your agent keeps its own wallet. Solrouter returns an unsigned funding transaction, your agent signs it, and a worker does the rest. Swap execution is Soon. See [Agent Privacy API](/docs/build/agent-privacy-api). +**Open-weight model.** An AI model whose files are public, so anyone can download and run it on their own hardware. This is what makes private hosting possible. Solrouter runs `gpt-oss:20b` (Live), `qwen3.8:27b` (Live), and `gemma4:31b` (Soon). See [Models and Nosana nodes](/docs/how-it-works/models). ## P +**Persistent Privacy Mode.** The chat app default. Messages are encrypted for transport, then saved so your history survives a reload. Saved history is encrypted at rest with a key the backend holds. That protects against a stolen database copy, but does not hide history from Solrouter. See [Chat app](/docs/use/chat-app). -**Persistent Privacy Mode.** The chat app default. Messages are encrypted for transport, then saved so your history survives a reload. Saved history is encrypted at rest with a key the backend holds. That protects against a stolen database copy. It does not hide history from Solrouter. See [Chat app](/docs/products/chat-app). - -**Phala dStack.** The hosting platform that runs Solrouter's Confidential VM on Intel TDX hardware. It also provides the small service (tappd) that hands out attestation quotes. See [TDX attestation](/docs/under-the-hood/attestation). +**Phala dStack.** The hosting platform that runs Solrouter's Confidential VM on Intel TDX hardware. It also provides the small service (tappd) that hands out attestation quotes. See [TDX attestation](/docs/how-it-works/attestation). -**Plaintext mode.** Sending a prompt with `encrypted: false` in the SDK. The prompt travels unencrypted through Solrouter's backend to the same models. You give up every privacy guarantee. It does not unlock any other model. See [Privacy SDK](/docs/products/privacy-sdk). - -**Prepaid balance.** Money you add to your Solrouter account before use, in USDC or $ROUTER. Each call deducts from it. Adding money is called a top-up. See [Pricing and balance](/docs/account/pricing). +**Plaintext mode.** Sending a prompt with `encrypted: false` in the SDK. The prompt travels unencrypted through the backend to the same models. You give up every privacy guarantee, and it unlocks no other model. See [Privacy SDK](/docs/build/privacy-sdk). +**Prepaid balance.** Money you add to your Solrouter account before use, in USDC or $ROUTER. Each call deducts from it. Adding money is called a top-up. See [Pricing and balance](/docs/use/pricing). ## Q - -**Quote (TDX quote).** The signed attestation document produced by the Intel chip through Phala's dStack service. It carries a `report_data` field that pins the enclave's public key. `GET /tee/attestation` returns one (Live). A reply's quote is `null` with a `tdxQuoteError` when the enclave cannot reach dStack. See [TDX attestation](/docs/under-the-hood/attestation). - +**Quote (TDX quote).** The signed attestation document produced by the Intel chip through Phala's dStack service. Its `report_data` field pins the enclave's public key. `GET /tee/attestation` returns one (Live). A reply's quote is `null` with a `tdxQuoteError` when the enclave cannot reach dStack. See [TDX attestation](/docs/how-it-works/attestation). ## R +**RAG (retrieval-augmented generation).** Asking questions over your own uploaded documents. The chat app splits documents into pieces and finds the relevant pieces before the model answers. Those pieces are stored unencrypted on the backend. See [Chat app](/docs/use/chat-app) and [Data at rest](/docs/use/what-is-private). -**RAG (retrieval-augmented generation).** Asking questions over your own uploaded documents. The chat app splits documents into pieces and finds the relevant pieces before the model answers. Those pieces are stored unencrypted on the backend. See [Chat app](/docs/products/chat-app) and [Data at rest](/docs/under-the-hood/data-at-rest). - -**report_data.** A 64-byte field inside a TDX quote that the enclave fills before the chip signs it. Solrouter puts a hash of its public key there. Two formulas exist. `GET /tee/attestation` pins the X25519 key alone. Per-reply quotes pin the X25519 key together with the ed25519 signing key. See [TDX attestation](/docs/under-the-hood/attestation). - -**RescueCipher.** The cipher (scrambling method) from Arcium that Solrouter uses to encrypt your prompt on your device. It works on numbers in a mathematical field instead of raw bytes. That is why the SDK packs 31 bytes into each number. See [RescueCipher and X25519](/docs/under-the-hood/encryption). +**report_data.** A 64-byte field inside a TDX quote that the enclave fills before the chip signs it. Solrouter puts a hash of its public key there. Two formulas exist: `GET /tee/attestation` pins the X25519 key alone, and per-reply quotes pin the X25519 key with the ed25519 signing key. See [TDX attestation](/docs/how-it-works/attestation). -**$ROUTER.** Solrouter's own token on Solana. You can pay for calls with it instead of USDC. Solrouter can buy it back and burn it with revenue; the configured ratios are not published. See [$ROUTER token](/docs/account/token). +**RescueCipher.** The cipher (scrambling method) from Arcium that Solrouter uses to encrypt your prompt on your device. It works on numbers in a mathematical field instead of raw bytes, so the SDK packs 31 bytes into each number. See [RescueCipher and X25519](/docs/how-it-works/encryption). +**$ROUTER.** Solrouter's own token on Solana. You can pay for calls with it instead of USDC. Solrouter can buy it back and burn it with revenue; the configured ratios are not published. See [$ROUTER token](/docs/token). ## S +**SERV.** The older name for the guided reasoning feature now called BRAID. It is not the "OpenServ" line in the token allocation table, which names a token drop to that community. See [Agent endpoint and guided reasoning](/docs/how-it-works/agent-reasoning). -**SERV.** The older name for the guided reasoning feature now called BRAID. It is not the same as the "OpenServ" line in the token allocation table, which names a token drop to that community. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). - -**Skill graph.** A set of 44 linked notes with expert knowledge on Solana, DeFi, and research method. When your question matches a note's trigger words, the `/agent` endpoint adds that note to the model's instructions. It runs on the plaintext path only. See [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning). - -**Solana wallet.** An app that holds your Solana keys and signs actions for you. Solrouter uses your wallet as your login. There is no email and no identity check. Phantom, Solflare, or a Privy embedded wallet all work. See [Get an API key](/docs/account/api-key). +**Skill graph.** A set of 44 linked notes with expert knowledge on Solana, DeFi, and research method. When your question matches a note's trigger words, the `/agent` endpoint adds that note to the model's instructions. It runs on the plaintext path only. See [Agent endpoint and guided reasoning](/docs/how-it-works/agent-reasoning). +**Solana wallet.** An app that holds your Solana keys and signs actions for you. Solrouter uses your wallet as your login, with no email and no identity check. Phantom, Solflare, or a Privy embedded wallet all work. See [Get an API key](/docs/build/api-key). ## T +**tappd.** The small program inside a Phala dStack CVM that asks the Intel chip for a quote. Solrouter's enclave talks to it over a local socket. When the socket is missing, quote requests fail with `tdx_quote_unavailable`. See [TDX attestation](/docs/how-it-works/attestation). -**tappd.** The small program inside a Phala dStack CVM that asks the Intel chip for a quote. Solrouter's enclave talks to it over a local socket. When the socket is missing, quote requests fail with `tdx_quote_unavailable`. See [TDX attestation](/docs/under-the-hood/attestation). - -**TEE (Trusted Execution Environment).** A sealed area of a computer where code and data are hidden from the machine's owner. Solrouter's TEE is an Intel TDX Confidential VM. Picture a sealed room: the landlord owns the building but cannot see inside. The analogy breaks here: the model runs on a separate GPU computer outside the room. See [What is a TEE?](/docs/what-is-a-tee). - -**TGE (token generation event).** The moment a token first goes live and can be traded. Vesting schedules count from this date. See [$ROUTER token](/docs/account/token). +**TEE (Trusted Execution Environment).** A sealed area of a computer where code and data are hidden from the machine's owner. Solrouter's TEE is an Intel TDX Confidential VM. Picture a sealed room: the landlord owns the building but cannot see inside. The analogy breaks here: the model runs on a separate GPU computer outside the room. See [What is a TEE?](/docs/how-it-works/what-is-a-tee). +**TGE (token generation event).** The moment a token first goes live and can be traded. Vesting schedules count from this date. See [$ROUTER token](/docs/token). ## U +**Umbra.** The Solana privacy protocol whose mixer Solrouter uses for private swaps (Soon). The MCP tools that start with `umbra_` move real funds. See [Private swaps internals](/docs/build/agent-privacy-api). -**Umbra.** The Solana privacy protocol whose mixer Solrouter uses for private swaps (Soon). The MCP tools that start with `umbra_` move real funds. See [Private swaps internals](/docs/under-the-hood/private-swaps). - -**USDC.** A digital dollar on Solana. One USDC is meant to stay worth one US dollar. Solrouter prices calls in USDC and accepts it for top-ups and pay-per-call payments. See [Pricing and balance](/docs/account/pricing). - +**USDC.** A digital dollar on Solana, meant to stay worth one US dollar. Solrouter prices calls in USDC and accepts it for top-ups and pay-per-call payments. See [Pricing and balance](/docs/use/pricing). ## V - -**Vesting (cliff and linear).** Rules for when locked tokens become spendable. A cliff is a waiting period with no release. Linear vesting releases an equal amount at each step after that. See [$ROUTER token](/docs/account/token). - +**Vesting (cliff and linear).** Rules for when locked tokens become spendable. A cliff is a waiting period with no release. Linear vesting then releases an equal amount at each step. See [$ROUTER token](/docs/token). ## W - -**Wallet address.** The public name of a wallet, a long string of letters and numbers. You can share it to receive funds. It reveals nothing secret, but everything sent to it is visible on the public ledger. See [Get an API key](/docs/account/api-key). - +**Wallet address.** The public name of a wallet, a long string of letters and numbers. You can share it to receive funds. It reveals nothing secret, but everything sent to it is visible on the public ledger. See [Get an API key](/docs/build/api-key). ## X +**X25519.** A method for two parties to agree on a shared secret key without ever sending it. Your device makes a fresh, single-use keypair per session and combines it with the enclave's public key. The enclave's key is made at boot and changes on every restart. It is also called the sealing key. See [RescueCipher and X25519](/docs/how-it-works/encryption). -**X25519.** A method for two parties to agree on a shared secret key without ever sending it. Your device makes a fresh, single-use keypair per session and combines it with the enclave's public key. The enclave's key is made at boot and changes on every restart. The enclave's X25519 key is also called the sealing key. See [RescueCipher and X25519](/docs/under-the-hood/encryption). - -**x402.** A way to pay for one web request at the moment you make it. It uses the HTTP status code 402 ("payment required"). The server answers with a price, your agent pays in USDC, and the request goes through. No account and no API key is needed. Encrypted x402 inference costs 0.005 USDC per call (Live). See [x402 payments](/docs/under-the-hood/x402). +**x402.** A way to pay for one web request at the moment you make it, using HTTP status code 402 ("payment required"). The server answers with a price, your agent pays in USDC, and the request goes through. No account or API key is needed. Encrypted x402 inference costs 0.005 USDC per call (Live). See [x402 payments](/docs/build/api-key). diff --git a/content/docs/how-it-works/agent-reasoning.mdx b/content/docs/how-it-works/agent-reasoning.mdx new file mode 100644 index 0000000..910ff9d --- /dev/null +++ b/content/docs/how-it-works/agent-reasoning.mdx @@ -0,0 +1,69 @@ +--- +title: "Agent reasoning" +icon: BrainCircuit +description: "The three ways a request runs through POST /agent, the guided-reasoning path, and the skill graph that shapes the answer." +status: live +checked: "2026-08-26" +--- + +import { SkillGraphMap } from '@/components/diagrams/skill-graph-map'; +import { Callout } from 'fumadocs-ui/components/callout'; + +`POST /agent` has three paths. The request body picks the path. Older material calls the guided path SERV; the code, the SDK option, and the API value call it BRAID. + +## Three paths + +| Path | Trigger | Where it runs | Model calls | +| --- | --- | --- | --- | +| Tool loop (default) | `useTools: true` | Backend | Up to 8 (`MAX_ITERATIONS = 8`) | +| Guided reasoning (BRAID) | `reasoning: 'braid'` | Backend | One synthesis call | +| Encrypted agent mode | `encryptedPrompt` | Inside the enclave | Loop inside the enclave | + +The encrypted path is Live for REST callers who send `encryptedPrompt`, and Soon for the SDK. The chat app's agent mode runs the plaintext tool loop. + +## The tool loop + +The default path is a standard loop. The model picks a tool, the backend runs it, the model reads the result, and it repeats up to eight times. The last call has no tools, so the model must write the answer. The backend registers 18 tools: + +- **Web:** `web_search`, `scrape_url`, `crawl_url` +- **On-chain and markets:** `solana_balance`, `token_price`, `swap_quote`, `trending_tokens` +- **Research:** `deepwiki`, `colosseum_search`, `colosseum_archives` +- **Paid APIs:** `paysh_search_apis`, `paysh_call_api` +- **Connected accounts:** `github_list_repos`, `github_issues`, `github_read_file`, `notion_search`, `notion_get_page`, `notion_query_database` + +The encrypted path runs a 5-tool allowlist inside the enclave: `web_search` (SearXNG in the same enclave), `token_price`, `trending_tokens`, `swap_quote`, and `solana_balance`. Every other tool fails closed in that mode. + +## Guided reasoning (BRAID) + +A standard loop asks the model what to do at every step, one model call per step. BRAID splits the two jobs an agent does: deciding what data to gather, and writing the answer. It walks a Guided Reasoning Diagram (GRD), a fixed graph that sets which tools run and in what order, then calls the model once at the end to write the reply. + +A query passes through four stages: + +1. **Intent detection.** Keyword rules map the prompt to one of six GRDs (`comparison`, `defi-analysis`, `general-research`, `market-overview`, `token-research`, `wallet-analysis`). No model call. +2. **GRD execution.** BRAID walks the graph node by node. Branch nodes use a rule first; when no rule applies, the model answers a short one-word question to pick the branch. +3. **Skill-graph injection.** If the prompt matches skill nodes, their notes are added to the synthesis prompt. +4. **Synthesis.** BRAID calls the model once to turn the collected data into a reply. + +A swap prompt with two known tokens, or a prompt with three or more known tokens, skips the walk and calls the tools directly before the same single synthesis call. + +BRAID spends one synthesis call plus the occasional one-word branch call, instead of one model call per step. That is the whole mechanism behind the cost and latency claim. No benchmark numbers are published. + + + Send `reasoning: 'braid'` to `POST /agent`, or `client.chat(prompt, { reasoning: 'braid' })` in the SDK. The SDK path is plaintext today (`encrypted: false`). See [POST /agent](/docs/api-reference/agent) for the `braidTrace` envelope. + + +## The skill graph + +Before the synthesis call, the engine matches your query against 44 knowledge nodes. It scores every node against the prompt, walks the graph from the top three scoring nodes, follows edges to a depth of 2, and stops at 5 nodes. The reached nodes add domain notes to the system prompt. A prompt that matches no node gets no extra tokens, and the response never returns the walked path. + +Click a node to see its edges. The highlighted path is a DeFi protocol comparison. + + + +**The 44 nodes, by cluster** + +- **Research and analysis (15):** research-core, source-eval, defi-analysis, liquidity-risk, token-economics, market-analysis, on-chain-analysis, wallet-analysis, privacy-research, risk-assessment, smart-contract-risk, comparative-analysis, data-synthesis, colosseum-research, colosseum-archives. +- **Ecosystem (2):** arcium-mpc, solana-ecosystem. +- **DeFi protocols (10):** jupiter-defi, raydium-defi, orca-defi, meteora-defi, kamino-defi, sanctum-staking, pump-fun, lulo-lending, ranger-perps, prediction-markets. +- **Infrastructure and oracles (8):** helius-infra, light-protocol-zk, metaplex-nfts, pyth-oracle, switchboard-oracle, squads-multisig, debridge-cross-chain, coingecko-analytics. +- **Solana development (9):** solana-kit-dev, anchor-dev, pinocchio-dev, framework-kit-frontend, solana-testing, solana-security-audit, token2022-extensions, quicknode-infra, magicblock-gaming. diff --git a/content/docs/concepts/attestation.mdx b/content/docs/how-it-works/attestation.mdx similarity index 81% rename from content/docs/concepts/attestation.mdx rename to content/docs/how-it-works/attestation.mdx index a8d7dd3..70ee855 100644 --- a/content/docs/concepts/attestation.mdx +++ b/content/docs/how-it-works/attestation.mdx @@ -93,44 +93,6 @@ POST https://api.solrouter.com/attestation/derive The `umbra_attestation` MCP tool is a different thing. It returns the settlement record of a private-swap session from `GET /agents/v1/attestations/:sessionId`. It does not read inference receipts. -## Verification flow +## Verify it yourself -Here is the end-to-end check, from fetching the key to confirming the on-chain anchor. Each step builds on the last, and the final one is optional. - - - -### Fetch the public key - -Call `GET https://api.solrouter.com/tee/public-key` and store the returned X25519 public key. - - - -### Fetch the attestation quote - -Call `GET https://api.solrouter.com/tee/attestation` to retrieve the Intel TDX quote. - - - -### Verify the quote signature - -Use Intel DCAP or a compatible TEE verification library to validate the quote's signature chain back to Intel's root certificate. This proves the hardware is real. - - - -### Check report_data - -Decode `publicKey` from Step 1 from base64 and hash the 32 raw bytes with sha256. The hex digest must equal `reportDataHex` and `publicKeySha256`. This binds the public key to the verified enclave, so you know you encrypted to the right key. - - - -### Compare code measurements (Soon) - -This step needs published reference measurements. Solrouter does not publish them yet. Until then, you can prove genuine hardware and key binding, but not which image is running. - - - -### Check the on-chain anchor (optional) - -Take `onchainAttestation.signature` from your `/tee/process` response and call `GET https://api.solrouter.com/attestation/by-tx/:sig`. You can also look up `onchainAttestation.address` directly on a Solana explorer. - - +The full check, from the live key through the Intel DCAP signature chain to the on-chain anchor, runs in your browser on [Check a reply yourself](/docs/verify). Auditors will find the by-hand steps there too. diff --git a/content/docs/concepts/encryption.mdx b/content/docs/how-it-works/encryption.mdx similarity index 98% rename from content/docs/concepts/encryption.mdx rename to content/docs/how-it-works/encryption.mdx index 1771032..fc0b756 100644 --- a/content/docs/concepts/encryption.mdx +++ b/content/docs/how-it-works/encryption.mdx @@ -22,7 +22,7 @@ Here are the three building blocks that make the guarantee work, and what each o The first line of defense is simple: encrypt before you transmit. The SDK encrypts your prompt in the browser or in your server process before it sends anything. * **`RescueCipher`**: Arcium's field-element symmetric cipher. Arcium chose it for compatibility with MPC, FHE, and ZK computation, so the same encrypted payload can be processed under any of those paradigms as Arcium's network matures. -* **`X25519` key exchange**: your SDK session generates an ephemeral (single-use, per-session) X25519 keypair. The SDK derives the shared secret from your ephemeral private key and the TEE public key. The SDK fetches that key from `GET /tee/public-key`. It does not fetch or verify the attestation quote. Verification is a manual step; see [Attestation](/docs/concepts/attestation). +* **`X25519` key exchange**: your SDK session generates an ephemeral (single-use, per-session) X25519 keypair. The SDK derives the shared secret from your ephemeral private key and the TEE public key. The SDK fetches that key from `GET /tee/public-key`. It does not fetch or verify the attestation quote. Verification is a manual step; see [Attestation](/docs/how-it-works/attestation). * **TEE-generated keypair**: the TEE's own X25519 keypair is generated inside the Confidential VM at boot time. The private key never leaves the enclave, not even to Solrouter's own infrastructure. ### Inference isolation @@ -40,7 +40,7 @@ Encryption only helps if there is no gap where plaintext leaks in transit betwee * Your prompt and the reply are encrypted end-to-end between your client and the enclave. The request metadata (API key, model id, `chatId`, and any `systemPrompt`) reaches the backend in plaintext. * The Solrouter backend is a **blind relay**. It forwards encrypted blobs without being able to decrypt them. It never has the keys. -The full request path, hop by hop, is on [How It Works](/docs/concepts/how-it-works#request-flow). +The full request path, hop by hop, is on [How It Works](/docs/how-it-works/request-flow). ## Why RescueCipher? diff --git a/content/docs/how-it-works/index.mdx b/content/docs/how-it-works/index.mdx new file mode 100644 index 0000000..70b42f3 --- /dev/null +++ b/content/docs/how-it-works/index.mdx @@ -0,0 +1,42 @@ +--- +title: "Architecture" +icon: Layers +description: "Every part of Solrouter on one interactive map: what each part holds, what it can see, and where the code lives." +status: mixed +checked: "2026-08-26" +statusNote: "Private swaps are Soon. Every other part of the map is Live." +--- + +import { Cards, Card } from 'fumadocs-ui/components/card'; +import { ArchitectureMap } from '@/components/diagrams/architecture-map'; + +Solrouter has a handful of moving parts. This map shows all of them at once: what each part holds, what it can see, and where the code lives. Each page in this section zooms into one piece. It gets technical, and every claim points at the code. + +Click a node to open its details. Drag to pan. Pinch to zoom. + + + +**In words** + +- Chat app (solrouter.com/chat): holds your wallet session and, in Maximum Privacy Mode, encrypts each prompt in the browser. In the default mode it sends plaintext to the backend. +- `@solrouter/sdk`: holds your API key and encrypts by default. Sends the ciphertext bundle plus the API key, model id, and chat id in plaintext. +- `@solrouter/mcp-server` (your machine): holds `SOLROUTER_API_KEY`, `SOLROUTER_API_URL`, and `BRAVE_API_KEY`. Four tools use the encrypted path for the model step. Search and market lookups go to third parties in plaintext. +- REST and x402 clients: send whatever they build. `POST /api/v1/chat/completions` and `/tee/process` require `encryptedPrompt`; `/agent` accepts plaintext or `encryptedPrompt`. +- Solrouter backend (behind api.solrouter.com): checks the key, bills, runs the x402 paywall, relays ciphertext to the enclave, and commits receipts with its deployer wallet. It holds no decryption key on the encrypted path. +- Intel TDX enclave on Phala dStack: generates an X25519 sealing key and an ed25519 signing key at boot, decrypts with RescueCipher, requests TDX quotes from the tappd agent, runs a 5-tool allowlist for encrypted agent mode, and hosts SearXNG in the same enclave. +- Nosana GPU node, one per model: runs the open-weight model in Ollama and sees the prompt and reply during inference. Solrouter does not control that hardware. +- Solana: holds one Light Protocol compressed receipt per private inference, under program `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`. +- Umbra mixer plus Jupiter (Soon): the private-swap path. The backend orchestrates it; no mainnet run is confirmed. +- x402 facilitator: the live manifest advertises Coinbase. Which facilitator settles a payment is set on the server and is not visible from outside. The agent never talks to the facilitator. + +## Zoom in + + + + + + + + + + diff --git a/content/docs/how-it-works/meta.json b/content/docs/how-it-works/meta.json new file mode 100644 index 0000000..5ad913b --- /dev/null +++ b/content/docs/how-it-works/meta.json @@ -0,0 +1,14 @@ +{ + "title": "How it works", + "icon": "Layers", + "pages": [ + "index", + "what-is-a-tee", + "request-flow", + "encryption", + "attestation", + "proof", + "agent-reasoning", + "models" + ] +} diff --git a/content/docs/concepts/supported-models.mdx b/content/docs/how-it-works/models.mdx similarity index 100% rename from content/docs/concepts/supported-models.mdx rename to content/docs/how-it-works/models.mdx diff --git a/content/docs/how-it-works/proof.mdx b/content/docs/how-it-works/proof.mdx new file mode 100644 index 0000000..5da31c0 --- /dev/null +++ b/content/docs/how-it-works/proof.mdx @@ -0,0 +1,52 @@ +--- +title: "On-chain proof" +icon: Stamp +description: "Each private inference writes a receipt to Solana, signed inside the enclave. This page explains what the receipt stores and why the backend cannot forge it." +status: live +checked: "2026-08-26" +--- + +import { Callout } from 'fumadocs-ui/components/callout'; + +[Attestation](/docs/how-it-works/attestation) proves the enclave is genuine. The **encryption proof** ties *your specific request* to that enclave. Inside the enclave, a key that exists nowhere else signs the hash of your exact ciphertext, and the receipt goes to Solana. The backend relays it and cannot change it without breaking the signature. + +Each receipt is a **Light Protocol compressed account**: about 0.000005 SOL each, roughly 400 times cheaper than a normal Solana PDA. That cost gap is why a proof per message is viable. + +## What the record stores + +A v2 attestation lives under the Solrouter program `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`. Its address is deterministic: `deriveAddressV2(["attestation_v2", sha256(ciphertext)])`, so a bare ciphertext hash is enough to find it. Alongside `model`, `provider`, `timestamp`, `backend_saw_plaintext`, and `tee_processed`, it stores the proof: + +| Field | Meaning | +| --- | --- | +| `client_pubkey` | Your ephemeral X25519 key from the request | +| `tee_pubkey` | The enclave's X25519 sealing key your ciphertext was sealed to | +| `nonce` | The RescueCipher nonce | +| `enclave_pubkey` | The enclave's ed25519 signing key, bound inside the per-request TDX quote | +| `enclave_sig_r` / `enclave_sig_s` | The two halves of the ed25519 signature | +| `tdx_quote_hash` | `sha256` of the TDX quote that attests `enclave_pubkey` | + +Inside the enclave, the signature covers this exact byte tuple: + +``` +"SOLR-ATTEST-v2" + ‖ sha256(ciphertext) // 32 your encrypted prompt + ‖ tee_pubkey // 32 the sealing key + ‖ nonce // 16 + ‖ client_pubkey // 32 your request key + ‖ len(model) ‖ model + ‖ len(provider) ‖ provider +``` + +A `POST /tee/process` reply returns this record under `onchainAttestation` (`address`, `signature`, `explorerUrl`), or `null` if the commit failed. The SDK exposes `privacyAttestationId` only; call the REST route for the full object. + +## Why a forged receipt is detectable + +The ed25519 signing key is generated inside the enclave at boot and never leaves it. The backend pays for the Solana transaction with its deployer wallet, so it could commit a record carrying any key and signature. The on-chain program stores those fields without checking them. The safeguard is the per-request TDX quote: its `report_data` equals `sha256(tee_pubkey ‖ enclave_pubkey)`, so `enclave_pubkey` is pinned to attested hardware. A verifier who checks that binding can tell a real enclave key from a forged one. + + + A passing signature check proves the key in the record signed your exact ciphertext, and that the record is on-chain. The per-request quote check proves that key belongs to an attested TDX enclave. The deepest level, Intel's full DCAP chain on the raw quote, needs the live quote from `GET /tee/attestation`. + + +## Check one yourself + +Paste a lock link, address, transaction, or ciphertext hash into the verifier on [Check a reply yourself](/docs/verify), or read a receipt directly through the [proof-lookup endpoints](/docs/how-it-works/attestation#on-chain-attestation-anchor). diff --git a/content/docs/under-the-hood/request-flow.mdx b/content/docs/how-it-works/request-flow.mdx similarity index 82% rename from content/docs/under-the-hood/request-flow.mdx rename to content/docs/how-it-works/request-flow.mdx index cd2dcbf..7f769ce 100644 --- a/content/docs/under-the-hood/request-flow.mdx +++ b/content/docs/how-it-works/request-flow.mdx @@ -15,15 +15,15 @@ This page follows one `client.chat()` call through the SDK, the Solrouter backen **In words** -1. `GET /tee/public-key` returns `{publicKey, publicKeySha256, algorithm, teeType}`. The SDK caches it for the life of the process (`be:packages/sdk/src/encryption.ts:55-75`). +1. `GET /tee/public-key` returns `{publicKey, publicKeySha256, algorithm, teeType}`. The SDK caches it for the life of the process. 2. The SDK makes an ephemeral X25519 keypair and derives the shared secret with the enclave key. 3. RescueCipher encrypts the prompt in packed-31 form. The bundle is `{ciphertext, nonce, publicKey, version: '2.0-packed31'}`. -4. `POST /tee/process` with a Bearer key. What leaves the machine: the ciphertext bundle plus, in plaintext, the API key, model id, `chatId`, and the optional `systemPrompt`, `useRAG`, `ragCollection`, `useLiveSearch` (`be:packages/sdk/src/client.ts:180-193`). -5. The backend forwards `{encryptedPrompt, model, privacyAttestationId}` unchanged (`be:routes/tee.js:44-53`). +4. `POST /tee/process` with a Bearer key. What leaves the machine: the ciphertext bundle plus, in plaintext, the API key, model id, `chatId`, and the optional `systemPrompt`, `useRAG`, `ragCollection`, `useLiveSearch`. +5. The backend forwards `{encryptedPrompt, model, privacyAttestationId}` unchanged. 6. The CVM derives the shared secret with its X25519 private key and decrypts. -7. The CVM calls the configured Nosana endpoint at `/v1/chat/completions` with the plaintext prompt. HTTPS per the documented node URL, not re-verified (`be:tee-service/src/index.js:461-473`). +7. The CVM calls the configured Nosana endpoint at `/v1/chat/completions` with the plaintext prompt. HTTPS per the documented node URL, not re-verified. 8. The CVM encrypts the reply to your key, signs the `SOLR-ATTEST-v2` tuple, and requests a tappd quote with `report_data = sha256(x25519 || ed25519)`. -9. The backend commits the compressed receipt and returns `{success, encryptedResponse, attestation, encryptionProof, requestId, metadata, backendRole: 'BLIND_RELAY', onchainAttestation, privacyProof}` (`be:routes/tee.js:84-101`). +9. The backend commits the compressed receipt and returns `{success, encryptedResponse, attestation, encryptionProof, requestId, metadata, backendRole: 'BLIND_RELAY', onchainAttestation, privacyProof}`. 10. The SDK decrypts `encryptedResponse` with the session private key. ## The short picture @@ -38,7 +38,5 @@ This page follows one `client.chat()` call through the SDK, the Solrouter backen - Your device: an ephemeral X25519 private key per session. Never sent. - Solrouter backend: no key on this path. -- Enclave: an X25519 sealing key and an ed25519 signing key, generated at boot and never exported (`be:tee-service/src/index.js:76-91`). +- Enclave: an X25519 sealing key and an ed25519 signing key, generated at boot and never exported. - Nosana node: no key. It receives plaintext from the enclave. - -Source: `be:routes/tee.js:33-101`, `be:tee-service/src/index.js:76-101,217-238,461-473`, `be:packages/sdk/src/encryption.ts:52-142`. diff --git a/content/docs/what-is-a-tee.mdx b/content/docs/how-it-works/what-is-a-tee.mdx similarity index 91% rename from content/docs/what-is-a-tee.mdx rename to content/docs/how-it-works/what-is-a-tee.mdx index 700223a..d1f7f63 100644 --- a/content/docs/what-is-a-tee.mdx +++ b/content/docs/how-it-works/what-is-a-tee.mdx @@ -61,7 +61,7 @@ Solrouter's own deployment file states the same limit. It claims "Solrouter neve Solrouter's backend and its cloud host cannot read your prompt. The Nosana GPU node that runs the model can, while it works on it. -The page [What is private here](/docs/what-is-private) has the full table of who can see what. +The page [What is private here](/docs/use/what-is-private) has the full table of who can see what. ## How Solrouter uses one @@ -76,6 +76,6 @@ The signed note comes from `GET https://api.solrouter.com/tee/attestation`. Solr You do not have to take Solrouter's word for any of this. - - + + diff --git a/content/docs/index.mdx b/content/docs/index.mdx index ae063b9..8ef65aa 100644 --- a/content/docs/index.mdx +++ b/content/docs/index.mdx @@ -1,150 +1,45 @@ --- title: "Introduction" icon: Sparkles -description: "Solrouter is a private AI layer for Solana developers. Your prompt is encrypted on your device, and the Solrouter backend never sees it in plaintext." -status: mixed +description: "Solrouter is a private AI layer for Solana. Your prompt is encrypted on your device, and the Solrouter backend never sees it in plaintext." +status: live checked: "2026-08-26" -statusNote: "The product cards carry their own status. The Agent Tools SDK is Soon because @solrouter/agent-tools is not on npm yet." --- import { Cards, Card } from 'fumadocs-ui/components/card'; import { Callout } from 'fumadocs-ui/components/callout'; import { TypicalVsSolrouter } from '@/components/diagrams/typical-vs-solrouter'; -import { - Shield, - Bot, - Plug, - MessageSquare, - Lock, - Cpu, - BadgeCheck, - UserX, - Box, - Link2, -} from 'lucide-react'; +import { MessageSquare, Code, Layers, ShieldCheck } from 'lucide-react'; -Solrouter is a private AI infrastructure layer for Solana developers. The idea -is simple: your prompts and responses should never exist in plaintext anywhere -we could read them. On the encrypted path, your prompt is encrypted on your -device. It stays encrypted through our backend and is decrypted only inside a -hardware-isolated enclave. The SDK uses this path by default. The chat app uses -it when you turn on Maximum Privacy Mode. - -## Why Solrouter? - -Start with the problem. Today, every AI request you send is something the -provider can read, store, and analyze. - -Mainstream providers can keep your prompts, ask for personal details at sign-up, -and back their privacy claims with a terms-of-service page. Nothing technical -stops them from reading what you send. You trust a policy. - -Solrouter removes the need for that trust on the encrypted path. We built the -system so that **our backend cannot read your prompt or the reply**. - -Here is how. Your prompt is encrypted on your device before it leaves your -browser or app. Our backend receives only an encrypted blob and forwards it to -an Intel TDX Confidential VM. This is a TEE (Trusted Execution Environment: -hardware that isolates code and data so even the machine's operator cannot -inspect it at runtime). The enclave decrypts your prompt and sends it to an -open-weight model on a Nosana GPU node. Solrouter's backend never sees the -plaintext, and the request is not linked to your identity on the node. +Solrouter is a private AI layer for Solana. On the encrypted path, your prompt is encrypted on your device, stays encrypted through the Solrouter backend, and is decrypted only inside a hardware-isolated enclave. The backend never sees it in plaintext. **In words** -- With a typical AI API, your prompt travels as readable text to the provider's server. That server can read it, store it, and train on it. -- With Solrouter and encryption on, your device encrypts the prompt before it leaves. -- The Solrouter backend passes the sealed message along. It sees the ciphertext and your wallet or key, never the text. -- Only the enclave holds the key that opens it. The enclave sends the readable text to a Nosana GPU node, which runs the model and sees the prompt while it works. -- The enclave encrypts the reply again, so only your device can read it. - -## Start here - - - The sealed room, in plain words, with a picture. - Who can see what, cell by cell. - Paste a lock link and watch the check pass. - What people, developers, agents, traders, and teams do with it. - +- A typical AI API reads your prompt as plain text on its own server. +- With Solrouter, your device encrypts the prompt first. The backend relays ciphertext and never sees the words. +- Only the enclave holds the key. It runs the model on a Nosana GPU node, then encrypts the reply so only your device can read it. - In privacy mode, Solrouter does not use OpenAI, Anthropic, Google, or any - other third-party model provider. All private inference runs on self-hosted, - open-weight models on the Nosana decentralized GPU network. The Nosana node - that runs the model can see your prompt during inference. Solrouter does not - control that hardware. + Private inference runs only on self-hosted, open-weight models on the Nosana GPU network. Solrouter does not send your prompt to OpenAI, Anthropic, or Google. The Nosana node that runs the model sees the prompt during inference, and Solrouter does not control that hardware. -## Products - -Pick the surface that fits how you work: an SDK to embed in your app, typed -agent tools, an MCP server for your editor, or the hosted chat app. They all run -on the same private backend. +## Choose your surface - } title="Privacy SDK" href="/docs/develop/privacy-sdk"> - **`@solrouter/sdk`**: add encrypted AI calls to your app. The SDK handles - key exchange, client-side encryption with Arcium RescueCipher, and response - decryption for you. No email or KYC: connect a Solana wallet and start - building. + } title="Use the chat app" href="/docs/use/chat-app"> + Chat in the browser. No code. Turn on Maximum Privacy Mode to encrypt each prompt. - } title="Agent Tools SDK" href="/docs/develop/agent-tools-sdk"> - Status: Soon. **`@solrouter/agent-tools`** is not on npm yet. It will give - you typed tools for the Agent Privacy API with a Vercel AI SDK adapter. - Today, call `POST /agents/v1/*` over HTTP or use the `umbra_*` MCP tools. + } title="Build on the API" href="/docs/build/quickstart"> + Add encrypted AI to your app or agent with the SDK, the MCP server, or the REST API. - } title="MCP Server" href="/docs/develop/mcp-server"> - **`@solrouter/mcp-server`**: use Solrouter from Claude Desktop, Cursor, or - any MCP-compatible client. Encrypted chat, token research, and wallet - analysis without writing any code. + } title="See how it works" href="/docs/how-it-works"> + The enclave, encryption, attestation, and the on-chain proof, each with the code. - } title="Chat App" href="https://solrouter.com/chat"> - Chat in the browser with two open-weight models, file attachments, and a - RAG knowledge base. Encryption is a toggle, off by default. No account - needed beyond a connected Solana wallet. + } title="Check a reply yourself" href="/docs/verify"> + Paste a lock link and watch the verification pass in your browser. -## Key Guarantees - -These properties hold for every encrypted request, whichever product you use. -In the SDK, encryption is on by default. In the chat app, you turn on Maximum -Privacy Mode. - - - } title="Client-Side Encryption"> - Your prompt is encrypted on your own device with Arcium's RescueCipher and - X25519 key exchange, before it leaves your browser or application. - - } title="TEE-Isolated Decryption"> - Your prompt is decrypted inside an Intel TDX Confidential VM. No host - process, including Solrouter's own backend, can read enclave memory at - runtime. The enclave then sends the prompt to an open-weight model on a - Nosana GPU node, where it exists in plaintext during inference. - - } title="Verifiable Attestation"> - Don't take our word for it. A TEE response carries an Intel TDX quote when - the CVM can reach the dStack agent. Otherwise `attestation.tdxQuote` is - null and `tdxQuoteError` says why. The quote lets you check the enclave's - public key. Comparing it against published enclave measurements: Soon. - - } title="No KYC or Email Required"> - Connect a Solana wallet, generate an API key, and start building. You pay - per call in USDC or `$ROUTER` from a prepaid balance. There is no signup - form and no personal data. - - } title="Open-Weight Models Only"> - All privacy-mode inference runs on self-hosted open-weight models on the - Nosana decentralized GPU network. Models: `gpt-oss:20b` (Live), - `qwen3.8:27b` (Live), `gemma4:31b` (Soon). `qwen3:8b` is retired - (Archived). - - } title="On-Chain Attestation Anchor"> - Each inference sent through `POST /tee/process` (the SDK path) gets a Light - Protocol compressed account on Solana. Solrouter's deployer wallet commits - it after the CVM signs the proof. This is automatic: you do not publish - anything. The REST and x402 chat routes do not create a receipt today. - - +New to the tech? Start with [What is a TEE?](/docs/how-it-works/what-is-a-tee) and [What is private here](/docs/use/what-is-private). diff --git a/content/docs/meta.json b/content/docs/meta.json index 46c065d..2dc9a03 100644 --- a/content/docs/meta.json +++ b/content/docs/meta.json @@ -1,18 +1,14 @@ { "pages": [ - "---Start here---", "index", - "what-is-a-tee", - "what-is-private", - "verify-a-reply", "use-cases", - "quickstart", + "verify", + "use", + "build", + "how-it-works", + "---Reference---", + "api-reference", "glossary", - "chat-app", - "concepts", - "develop", - "payments", - "under-the-hood", - "api-reference" + "token" ] } diff --git a/content/docs/payments/meta.json b/content/docs/payments/meta.json deleted file mode 100644 index fa2fedf..0000000 --- a/content/docs/payments/meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Payments", - "icon": "Coins", - "pages": ["overview", "tokenomics"] -} diff --git a/content/docs/payments/tokenomics.mdx b/content/docs/token.mdx similarity index 99% rename from content/docs/payments/tokenomics.mdx rename to content/docs/token.mdx index 7ffd524..b429dc5 100644 --- a/content/docs/payments/tokenomics.mdx +++ b/content/docs/token.mdx @@ -118,4 +118,4 @@ Configured ratios: not published. `GET /payments/buyback/log` returns the recent The model stays simple on purpose. No emissions mint new tokens, no staking curves add complexity, and no tiered discount programs fragment the tokenomics. -To pay for API calls with `$ROUTER`, see [Pricing](/docs/payments/overview). +To pay for API calls with `$ROUTER`, see [Pricing](/docs/use/pricing). diff --git a/content/docs/under-the-hood/agent-reasoning.mdx b/content/docs/under-the-hood/agent-reasoning.mdx deleted file mode 100644 index d21d5cc..0000000 --- a/content/docs/under-the-hood/agent-reasoning.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: "Agent endpoint and guided reasoning" -icon: BrainCircuit -description: "The three ways a request can run through POST /agent, and the 44-node skill graph that shapes the answer." -status: live -checked: "2026-08-26" -statusNote: "The plaintext tool loop and the BRAID path are Live. The encrypted agent path is Live for REST callers and Soon for the SDK." ---- - -import { SkillGraphMap } from '@/components/diagrams/skill-graph-map'; - -`POST /agent` has three paths. The request body picks the path. Older material calls the guided path SERV; the code, the SDK option, and the API value call it BRAID. - -## Three paths - -
- -| Path | Trigger | Where it runs | Tools | Model calls | Response envelope | SDK support | -| --- | --- | --- | --- | --- | --- | --- | -| Tool loop (default) | `useTools: true` | Backend | 18 tools | Up to 8 (`MAX_ITERATIONS = 8`) | `{ success, reply, toolCalls, usage, iterations, model, provider, billing, freeMessagesRemaining }` | `client.agent()` | -| BRAID guided reasoning | `reasoning: 'braid'` | Backend | Fixed order from one of six Guided Reasoning Diagrams | One synthesis call | `{ success, reply, reasoning: 'braid', braidTrace, usage, iterations, ... }` | `client.chat(prompt, { reasoning: 'braid' })`, plaintext | -| Encrypted agent mode | `encryptedPrompt` | Inside the CVM | 5-tool allowlist (web_search through SearXNG, token_price, trending_tokens, swap_quote, solana_balance) | Loop inside the enclave | Encrypted reply plus attestation | REST only today | - -
- -Source: `be:routes/agent.js:273-291,296-348,375,414-420,561-572`; `be:lib/agentService.js:91-418,1092,1196`; `be:tee-service/src/tools.js:28-34`; `be:openserv-agent/src/braid/grds/`. - -## The skill graph - -Before the synthesis call, the engine matches your query against 44 knowledge nodes. Matched nodes and their neighbours add domain notes to the system prompt. The response does not include the walked path. Simple queries skip the graph. - -Click a node to see its edges and its line in the engine. The highlighted example is the traversal for a DeFi protocol comparison. - - - -**In words** - -- Research and analysis (15): research-core, source-eval, defi-analysis, liquidity-risk, token-economics, market-analysis, on-chain-analysis, wallet-analysis, privacy-research, risk-assessment, smart-contract-risk, comparative-analysis, data-synthesis, colosseum-research, colosseum-archives. -- Ecosystem (2): arcium-mpc, solana-ecosystem. -- DeFi protocols (10): jupiter-defi, raydium-defi, orca-defi, meteora-defi, kamino-defi, sanctum-staking, pump-fun, lulo-lending, ranger-perps, prediction-markets. -- Infrastructure and oracles (8): helius-infra, light-protocol-zk, metaplex-nfts, pyth-oracle, switchboard-oracle, squads-multisig, debridge-cross-chain, coingecko-analytics. -- Solana development (9): solana-kit-dev, anchor-dev, pinocchio-dev, framework-kit-frontend, solana-testing, solana-security-audit, token2022-extensions, quicknode-infra, magicblock-gaming. -- Edges are the `edges` arrays in the engine, 136 directed links. Example traversal: defi-analysis, then liquidity-risk, then comparative-analysis. The engine has no direct edge from liquidity-risk to comparative-analysis; the traversal reaches it through defi-analysis. - -Source: `be:lib/skillGraphEngine.js` (44 `id:` entries, `edges` arrays at lines 56, 76, 95, 117 and onward). - -## Read more - -- [SERV Reasoning](/docs/concepts/serv-reasoning) and [Skill Graphs](/docs/concepts/skill-graphs): the current concept pages. They merge into this page in a later update. -- [POST /agent](/docs/api-reference/agent): request and response fields. diff --git a/content/docs/under-the-hood/index.mdx b/content/docs/under-the-hood/index.mdx deleted file mode 100644 index 13b44c7..0000000 --- a/content/docs/under-the-hood/index.mdx +++ /dev/null @@ -1,41 +0,0 @@ ---- -title: "Architecture map" -icon: Cpu -description: "Every part of Solrouter on one interactive map: what each part holds, what it can see, and where the code lives." -status: mixed -checked: "2026-08-26" -statusNote: "Every node carries its own status. Private swaps are Soon. The rest is Live." ---- - -import { Cards, Card } from 'fumadocs-ui/components/card'; -import { ArchitectureMap } from '@/components/diagrams/architecture-map'; - -This section is for engineers and auditors. Every page here cites the code that backs each claim. - -Click a node to see what it holds, what it can see, and the source file. Pan by dragging. Pinch to zoom. - - - -**In words** - -- Chat app (solrouter.com/chat): holds your wallet session and, in Maximum Privacy Mode, encrypts each prompt in the browser. In the default mode it sends plaintext to the backend. -- `@solrouter/sdk`: holds your API key and encrypts by default. Sends the ciphertext bundle plus the API key, model id, and chat id in plaintext. -- `@solrouter/mcp-server` (your machine): holds `SOLROUTER_API_KEY`, `SOLROUTER_API_URL`, and `BRAVE_API_KEY`. Four tools use the encrypted path for the model step. Search and market lookups go to third parties in plaintext. -- REST and x402 clients: send whatever they build. `POST /api/v1/chat/completions` and `/tee/process` require `encryptedPrompt`; `/agent` accepts plaintext or `encryptedPrompt`. -- Solrouter backend (Render, behind api.solrouter.com): checks the key, bills, runs the x402 paywall, relays ciphertext to the CVM, and commits receipts with its deployer wallet. It holds no decryption key on the encrypted path. -- Intel TDX CVM on Phala dStack: generates an X25519 sealing key and an ed25519 signing key at boot, decrypts with RescueCipher, requests TDX quotes from the tappd agent, runs a 5-tool allowlist for encrypted agent mode, and hosts SearXNG in the same CVM. -- Nosana GPU node, one per model: runs the open-weight model in Ollama and sees the prompt and reply during inference. Solrouter does not control that hardware. -- Solana: holds one Light Protocol compressed receipt per private inference under program `ATMRatMtsKX4bHax7U4FRdhbE4mjU4NKpDZGqZqAhBKb`. Cluster: mainnet by the code comments and explorer links, not re-verified with an on-chain read. -- Umbra mixer plus Jupiter (Soon): the private-swap path. The backend orchestrates it; no mainnet run is confirmed. -- x402 facilitator: the live manifest advertises Coinbase. Which facilitator settles a payment is set on the server and is not visible from outside. The agent never talks to the facilitator. - -## Deep dives - - - - - - - - - diff --git a/content/docs/under-the-hood/meta.json b/content/docs/under-the-hood/meta.json deleted file mode 100644 index 79ce64e..0000000 --- a/content/docs/under-the-hood/meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Under the hood", - "icon": "Cpu", - "pages": ["index", "request-flow", "agent-reasoning"] -} diff --git a/content/docs/use-cases.mdx b/content/docs/use-cases.mdx index 3c11049..bdfccae 100644 --- a/content/docs/use-cases.mdx +++ b/content/docs/use-cases.mdx @@ -4,7 +4,7 @@ icon: Compass description: "What people, developers, agents, traders, and teams do with Solrouter today, and what is still on the way." status: mixed checked: "2026-08-26" -statusNote: "Each card carries its own Status word. The Live cards for privacy modes, memory, guest mode, and team accounts depend on the owner's browser check in PR 5 (design decisions 12 and 13)." +statusNote: "Each card carries its own Status word. The Live cards for privacy modes, memory, guest mode, and team accounts depend on the owner's browser check in PR 5." --- import { Cards, Card } from 'fumadocs-ui/components/card'; @@ -31,21 +31,21 @@ import { Users, } from 'lucide-react'; -Every card below starts with one Status word that we checked against the code -on 2026-08-26. **Live** works today, **Soon** exists in code but is not switched -on or not confirmed end to end, and **Archived** was removed. +Every card below starts with one Status word. We checked each against the code +on 2026-08-26. **Live** works today. **Soon** exists in code but is not switched +on or not confirmed end to end. **Archived** was removed. Many cards mention a sealed machine. That is a TEE (Trusted Execution -Environment): a computer that its own operator cannot look inside while it -runs. [What is a TEE?](/docs/what-is-a-tee) explains it with a picture. Other -terms, such as [wallet](/docs/glossary), [x402](/docs/glossary), and -[mixer](/docs/glossary), are in the [Glossary](/docs/glossary). +Environment). Its own operator cannot look inside while it runs. +[What is a TEE?](/docs/how-it-works/what-is-a-tee) explains it with a picture. +Other terms are in the [Glossary](/docs/glossary): [wallet](/docs/glossary), +[x402](/docs/glossary), and [mixer](/docs/glossary). When encryption is on, Solrouter's own servers never see your words in readable form. The GPU computer that runs the AI model does see them while it writes the answer. Solrouter does not own that computer. Read - [What is private here](/docs/what-is-private) for the full picture. + [What is private here](/docs/use/what-is-private). ## For people @@ -55,41 +55,41 @@ chat app is a switch called Maximum Privacy mode. It is off when you first open the app. While it is on, the app keeps no history. - } title="Summarise a contract off the record" href="/docs/products/chat-app"> + } title="Summarise a contract off the record" href="/docs/use/chat-app"> **Live.** You want a summary of a contract, but you do not want an AI company to keep a copy. Turn on Maximum Privacy mode and paste the text, because an attached file is not encrypted. - } title="Review code without sharing it" href="/docs/products/chat-app"> + } title="Review code without sharing it" href="/docs/use/chat-app"> **Live.** You want feedback on code that is not public. Turn on Maximum Privacy mode and paste the code, and Solrouter's servers never see it in readable form. - } title="Draft a reply nobody else gets to read" href="/docs/products/chat-app"> + } title="Draft a reply nobody else gets to read" href="/docs/use/chat-app"> **Live.** You want help with a sensitive message, such as a letter to a doctor or a lawyer. Turn on Maximum Privacy mode, write the draft, and close the tab, because nothing is saved. - } title="Keep your history, or keep nothing" href="/docs/products/chat-app"> + } title="Keep your history, or keep nothing" href="/docs/use/chat-app"> **Live.** Some chats should survive a reload and others should leave no trace. Persistent mode saves history encrypted with a key that Solrouter holds, and Maximum Privacy mode saves nothing at all. - } title="Memory that only your wallet can unlock" href="/docs/products/chat-app"> + } title="Memory that only your wallet can unlock" href="/docs/use/chat-app"> **Live.** You want the assistant to remember facts across chats without Solrouter holding a readable profile of you. Your wallet signs a fixed message to make the key, so Solrouter stores only a locked blob. - } title="Try it with no wallet and no account" href="/docs/products/chat-app"> + } title="Try it with no wallet and no account" href="/docs/use/chat-app"> **Live.** You will not connect a wallet before you have seen the product work. Open the guest page and send up to five free messages a day. - } title="Attach a file to a chat" href="/docs/products/chat-app"> + } title="Attach a file to a chat" href="/docs/use/chat-app"> **Live.** You want to ask about a PDF, a spreadsheet, or a screenshot. You can attach it, but attachments are not encrypted, and documents are stored in readable form on a file server. - } title="Check that a reply came from the sealed machine" href="/docs/verify-a-reply"> + } title="Check that a reply came from the sealed machine" href="/docs/verify"> **Live.** A privacy promise on a web page is not proof. Click the lock link under a reply, or paste it into the checker, and see the receipt on the Solana blockchain. @@ -105,19 +105,19 @@ These are for people who write code and want to add private AI to their own app, editor, or backend. - } title="Encrypted chat from your own app" href="/docs/products/privacy-sdk"> + } title="Encrypted chat from your own app" href="/docs/build/privacy-sdk"> **Live.** Every mainstream AI service can read what your app sends it, so you cannot promise your users privacy. Install `@solrouter/sdk`, call `client.chat()`, and the prompt is encrypted on your machine before it leaves. - } title="Private research inside Claude Desktop or Cursor" href="/docs/products/mcp-server"> + } title="Private research inside Claude Desktop or Cursor" href="/docs/build/mcp-server"> **Live.** Research done through a normal assistant shows the model provider which tokens and wallets you look at. Paste one config block, and only the synthesis step runs encrypted; web searches and price lookups stay readable. - } title="Research with fixed steps instead of a free-running agent" href="/docs/under-the-hood/agent-reasoning"> + } title="Research with fixed steps instead of a free-running agent" href="/docs/how-it-works/agent-reasoning"> **Live.** A normal agent asks the model what to do at every step, which is slow and hard to predict. Send `reasoning: 'braid'` and the agent walks a fixed diagram of steps, but on this path your prompt travels in readable @@ -129,7 +129,7 @@ app, editor, or backend. an encrypted prompt to `POST /agent`, and the loop runs inside the sealed machine with five approved tools. - } title="Pay per call with no email, card, or KYC" href="/docs/account/api-key"> + } title="Pay per call with no email, card, or KYC" href="/docs/build/api-key"> **Live.** Signing up for an AI service means handing over your identity and a card. Connect a Solana wallet, make an API key, and top up a balance in USDC or $ROUTER. @@ -142,22 +142,22 @@ These are for software agents that hold a Solana wallet and act on their own, with no person to manage keys. - } title="Pay per call with no API key at all (x402)" href="/docs/products/agent-privacy-api"> + } title="Pay per call with no API key at all (x402)" href="/docs/build/agent-privacy-api"> **Live.** An agent that starts on demand has no person to sign up or hold a key. The first call gets a price back, the agent pays a small amount of USDC, and the call goes through. - } title="One private swap from the agent's own wallet" href="/docs/products/agent-privacy-api"> + } title="One private swap from the agent's own wallet" href="/docs/build/agent-privacy-api"> **Soon.** On Solana every transfer is public, so anyone can link the paying wallet to where the money went. The agent signs one funding step, and Solrouter routes the swap through a mixer so the two ends are not linked. - } title="Repeated private swaps from a managed wallet" href="/docs/products/agent-privacy-api"> + } title="Repeated private swaps from a managed wallet" href="/docs/build/agent-privacy-api"> **Soon.** A long-running agent should not sign a fresh funding step for every trade. Solrouter creates a wallet for the agent, keeps its key locked on the server, and runs each swap on request. - } title="Plain HTTP calls and self-discovery" href="/docs/api-reference/inference"> + } title="Plain HTTP calls and self-discovery" href="/docs/api-reference/overview"> **Live.** Not every language has an SDK, and some agents find services on their own. Call the OpenAI-style endpoint directly, and read the discovery documents that describe every route and price. @@ -170,7 +170,7 @@ These are for people who trade on Solana and want their intent and their wallet links kept out of provider logs. - } title="Swap privately from inside the chat" href="/docs/products/chat-app"> + } title="Swap privately from inside the chat" href="/docs/use/chat-app"> **Soon.** A trader wants to swap without the public ledger linking their main wallet to the destination. Type the swap in chat, and a widget walks you through a mixer step and the swap with your own wallet signing. @@ -189,46 +189,14 @@ These are for small companies that want private AI over their own documents with shared billing. - } title="Ask questions over your own documents" href="/docs/products/chat-app"> + } title="Ask questions over your own documents" href="/docs/use/chat-app"> **Live.** You want answers grounded in your contracts, specs, or research instead of the model's general knowledge. Upload files to a knowledge base, but know that the stored pieces are not encrypted at rest. - } title="Team accounts with invites and shared funds" href="/docs/products/chat-app"> + } title="Team accounts with invites and shared funds" href="/docs/use/chat-app"> **Live.** A company cannot run private AI on one person's wallet. Create an organization, invite members by link, and see usage per member on one shared balance. - -## All use cases at a glance - -
- -| Use case | Who | Status | Page | -| --- | --- | --- | --- | -| Summarise a contract off the record | People | Live | [Chat app](/docs/products/chat-app) | -| Review code without sharing it | People | Live | [Chat app](/docs/products/chat-app) | -| Draft a reply nobody else gets to read | People | Live | [Chat app](/docs/products/chat-app) | -| Keep your history, or keep nothing | People | Live | [Chat app](/docs/products/chat-app) | -| Memory that only your wallet can unlock | People | Live | [Chat app](/docs/products/chat-app) | -| Try it with no wallet and no account | People | Live | [Chat app](/docs/products/chat-app) | -| Attach a file to a chat | People | Live, not encrypted | [Chat app](/docs/products/chat-app) | -| Check that a reply came from the sealed machine | People | Live | [Check a reply yourself](/docs/verify-a-reply) | -| Image and video generation | People | Archived | none | -| Encrypted chat from your own app | Developers | Live | [Privacy SDK](/docs/products/privacy-sdk) | -| Private research inside Claude Desktop or Cursor | Developers | Live | [MCP server](/docs/products/mcp-server) | -| Research with fixed steps (BRAID) | Developers | Live, readable prompt | [Agent endpoint and guided reasoning](/docs/under-the-hood/agent-reasoning) | -| Run the whole agent loop inside the sealed machine | Developers | Live over REST, Soon in the SDK | [POST /agent](/docs/api-reference/agent) | -| Pay per call with no email, card, or KYC | Developers | Live | [Get an API key](/docs/account/api-key), [Pricing and balance](/docs/account/pricing) | -| Pay per call with no API key at all (x402) | Agents | Live | [Agent Privacy API](/docs/products/agent-privacy-api) | -| One private swap from the agent's own wallet | Agents | Soon | [Agent Privacy API](/docs/products/agent-privacy-api) | -| Repeated private swaps from a managed wallet | Agents | Soon | [Agent Privacy API](/docs/products/agent-privacy-api) | -| Plain HTTP calls and self-discovery | Agents | Live | [Inference endpoints](/docs/api-reference/inference), [Discovery documents](/docs/api-reference/discovery) | -| Swap privately from inside the chat | Traders | Soon | [Chat app](/docs/products/chat-app) | -| Phoenix Copilot | Traders | Soon | none yet | -| RouterChan Telegram app | Traders | Soon | none yet | -| Ask questions over your own documents | Teams | Live, not encrypted at rest | [Chat app](/docs/products/chat-app) | -| Team accounts with invites and shared funds | Teams | Live | [Chat app](/docs/products/chat-app) | - -
diff --git a/content/docs/chat-app.mdx b/content/docs/use/chat-app.mdx similarity index 69% rename from content/docs/chat-app.mdx rename to content/docs/use/chat-app.mdx index e236167..cbddce7 100644 --- a/content/docs/chat-app.mdx +++ b/content/docs/use/chat-app.mdx @@ -15,27 +15,34 @@ Most AI chat tools want your email, store your conversations in plaintext, and l You connect a Solana wallet, top up a prepaid balance, and pick one of two open-weight models. You also get file attachments and a RAG knowledge base. Encryption is a toggle. It is off by default. -## Features +## Two privacy modes + +Encryption is a toggle, off by default. Here is what each mode does with your prompt and your history. + +| | Persistent (default) | Maximum Privacy (on) | +| --- | --- | --- | +| Prompt to the backend | plaintext | encrypted in your browser | +| Opened where | backend, then the Nosana node | only the enclave, then the Nosana node | +| Chat history | stored, encrypted at rest under a key Solrouter holds | not stored, lost on refresh | +| Best for | everyday chats | sensitive prompts | + +For exactly who can read what, see [What is private here](/docs/use/what-is-private). -Here is what you get, and what each feature does with your data. +## Features }> - **Persistent Privacy Mode** (default): your prompt goes to the Solrouter backend in plaintext, then to a Nosana GPU node. Chat history is stored encrypted at rest with AES-256-GCM. The backend holds the key, so this protects against a database dump, not against Solrouter. - - **Maximum Privacy Mode** (toggle on): your prompt is encrypted in your browser. It is decrypted only inside the TEE (Trusted Execution Environment: hardware that isolates code and data from the machine's operator). Nothing is stored. History is lost on refresh. + Turn on Maximum Privacy Mode to encrypt each prompt before it leaves your browser. Off by default. - }> - Send images and documents with your question. Attachments are not encrypted in your browser. Images become data URLs. Documents upload to Cloudflare R2 through a presigned URL, and the backend parses them. Maximum Privacy Mode encrypts the text prompt only. + Send images and documents with your question. Attachments are not encrypted, only the text prompt is. - }> - RAG (Retrieval-Augmented Generation: answering from your own documents instead of only the model's training data) lets you upload files. You then query them in plain language. The backend chunks and embeds your documents in plaintext. Embeddings are computed on Solrouter's own servers, so no third-party embedding API sees them. Stored chunks are not encrypted. + Upload files and ask questions grounded in them. Stored chunks are not encrypted at rest. -Image and video generation: Archived. It is disabled in the chat app. +Image and video generation is Archived. It is disabled in the chat app. ## Getting started @@ -73,7 +80,7 @@ Four steps take you from a blank browser tab to your first message. The chat model picker lists two self-hosted open-weight models on the Nosana GPU network: `gpt-oss:20b` (Default, Live) and `qwen3.8:27b` (Uncensored, Live). No proprietary model is reachable in the chat app. Maximum Privacy Mode works with both models. -For model ids and their status, see the [Supported Models](/docs/concepts/supported-models) page. +For model ids and their status, see the [Supported Models](/docs/how-it-works/models) page. In Maximum Privacy Mode, Solrouter's backend cannot read your prompt. Plaintext exists on your device, inside the Intel TDX enclave, and on the Nosana GPU node that runs the model during inference. Solrouter does not control that node's hardware. diff --git a/content/docs/use/meta.json b/content/docs/use/meta.json new file mode 100644 index 0000000..0110f36 --- /dev/null +++ b/content/docs/use/meta.json @@ -0,0 +1,9 @@ +{ + "title": "Use Solrouter", + "icon": "MessageSquare", + "pages": [ + "chat-app", + "what-is-private", + "pricing" + ] +} diff --git a/content/docs/payments/overview.mdx b/content/docs/use/pricing.mdx similarity index 98% rename from content/docs/payments/overview.mdx rename to content/docs/use/pricing.mdx index 0117b57..5d95f6b 100644 --- a/content/docs/payments/overview.mdx +++ b/content/docs/use/pricing.mdx @@ -100,4 +100,4 @@ Status: Soon. The mechanism exists in code. The ratios are runtime settings, and * A configured share of the `$ROUTER` bought back is burned (`BURN_OUTPUT_TOKEN_BPS`). The rest stays in treasury. * A configured share of fees paid directly in `$ROUTER` is burned on receipt (`BURN_TOKEN_BPS`). The rest stays in treasury. -Configured ratios: not published. `GET /payments/buyback/log` returns the recent buyback worker ticks, including skipped ones. See [/docs/payments/tokenomics](/docs/payments/tokenomics) for the token supply schedule and vesting details. +Configured ratios: not published. `GET /payments/buyback/log` returns the recent buyback worker ticks, including skipped ones. See [/docs/token](/docs/token) for the token supply schedule and vesting details. diff --git a/content/docs/use/what-is-private.mdx b/content/docs/use/what-is-private.mdx new file mode 100644 index 0000000..739cff5 --- /dev/null +++ b/content/docs/use/what-is-private.mdx @@ -0,0 +1,152 @@ +--- +title: "What is private here" +icon: EyeOff +description: "Who can read your prompt, your files, and your history, and what Solrouter keeps on its servers." +status: mixed +checked: "2026-08-26" +statusNote: "Each row states its own Live or Soon status." +--- + +import { Callout } from 'fumadocs-ui/components/callout'; +import { Cards, Card } from 'fumadocs-ui/components/card'; +import { Lock, ShieldCheck, BookOpen, MessageSquare } from 'lucide-react'; +import { PlaintextZones } from '@/components/diagrams/plaintext-zones'; +import { PrivacyMatrix } from '@/components/diagrams/privacy-matrix'; + +## The question + +"If I type something private into Solrouter, who can read it?" + +With encryption on, Solrouter's own servers cannot read your prompt or the reply. The prompt is opened only inside a sealed computer (a TEE) and on the rented GPU machine that runs the AI model. Your attached files, your knowledge base, and your saved chat history do not get that protection, and the tables below show exactly where each one is readable. + +Encryption is a toggle in the chat app. It is off by default. The Privacy SDK encrypts by default. The page [Chat app](/docs/use/chat-app) explains the toggle. This page explains what each setting exposes. + +## Who is who + +The matrix shows five parties. You are the sixth: your own device always reads your own words. Here is each party in plain words. + +- **You.** Your browser, or the program that uses the SDK. +- **Network observer.** Anyone who watches the connection between you and Solrouter. For example, your internet provider or the owner of a public Wi-Fi. +- **Solrouter backend.** Solrouter's own servers. They check your login, take payment, store your history, and pass messages along. +- **CVM cloud host (Phala).** The company that owns the physical machine where the sealed computer runs. The sealed computer is a Confidential Virtual Machine (CVM). The processor encrypts its memory, so the machine owner cannot read it. See [What is a TEE?](/docs/how-it-works/what-is-a-tee). +- **Nosana GPU host.** The operator of the graphics-card machine that runs the AI model. Solrouter rents it from the Nosana network. Solrouter does not control that hardware. +- **Solana observer.** Anyone who reads the public Solana blockchain. Solrouter posts a receipt there for each encrypted request. + +Each cell says what that party can see. The legend under the matrix explains every word. + +## Who can see what + +Rows here describe the encrypted path: the Privacy SDK with its default settings, or the chat app with Maximum Privacy Mode on. Amber cells mark the only places a party can read your words. Every row is Live. + + + +**Notes** + +- **Nosana GPU host, readable.** The model runs there in plaintext for the length of one request. Solrouter rents the machine and does not control it. The request is not linked to your identity. +- **Chat history, at rest.** Persistent mode stores each message encrypted with AES-256-GCM under a key the backend holds. That protects against a stolen database copy, not against Solrouter. On this path the backend also reads the prompt in plaintext on the way in. +- **Knowledge-base documents.** Files are split and embedded on the server as plain text, not encrypted at rest. Whether the deployed app isolates collections per user is not determined. +- **Attached files.** The encrypted path sends the text prompt only. Attachments travel on the default path below. +- **Web searches** is the encrypted agent path: a REST call to `/agent` with `encryptedPrompt`, Live for REST and Soon for the SDK. Search runs through SearXNG inside the enclave, which then queries public engines. Those engines receive the search text. +- The enclave logs the first 50 characters of each reply. Who can read that log is not determined. + +## Default chat (toggle off) + +This is the chat app with the toggle off, the SDK with `encrypted: false`, and guest chat. There is no client-side encryption, so the backend and the model node read your words. Only the rows that change from the matrix above are shown. Status: Live. + + + +On this path attachments and knowledge-base files reach the backend and the model node in plaintext. Documents upload to Cloudflare R2 through a short-lived link, and the backend extracts their text. Live search and the `web_search` tool use Brave, with DuckDuckGo and Wikipedia as fallbacks, so those services receive the search text. + +## Where your words are readable + +The strip below follows one encrypted request from your device to the Solana receipt. Green zones hold your words in readable form. Grey zones hold only ciphertext or a hash. The amber zone is the rented GPU machine. + + + +**In words** + +- Your device: your words are readable here. Your browser or program scrambles them before they leave. +- Network: ciphertext only. A watcher sees size and timing. +- Solrouter backend: ciphertext only. It checks your login, bills you, and passes the blob along. +- TDX CVM: your words are readable here, inside memory that the processor encrypts. The machine owner cannot open it. +- Nosana GPU node: your words are readable here while the model runs. Solrouter does not control this machine. The request is not linked to you. +- Solana: hash only. A receipt proves a request happened and names the model. It does not hold your words. + + + Solrouter does not run fully homomorphic encryption (FHE) inference. FHE means a computer works on scrambled data without ever unscrambling it. No production system runs AI models of this size under FHE in 2026. The compute cost is many orders of magnitude away from usable speed. Anyone who claims "FHE LLM inference" in production is overclaiming. + + What Solrouter provides is encryption on your device, a hardware-isolated CVM that unscrambles the prompt, a model that runs on a rented Nosana GPU node, and a receipt on Solana for each encrypted request. That is a real and checkable guarantee. It is not FHE, and we will not claim otherwise. + + +## What we keep + +Retention periods are not published. Each row states what is stored, in what form, who holds the key, and how to remove it. Rows are Live unless marked. + +
+ +| What | Where | Format | Key holder | How to delete | Retention | +| --- | --- | --- | --- | --- | --- | +| Chat rows (`enc:v1:`) | Solrouter's database, tables `chat_messages` and `chats` (message text, chat title, search and knowledge-base context, pitch-deck cards) | Scrambled with AES-256-GCM, stored as text with the prefix `enc:v1:` | Solrouter backend, from `CHAT_CONTENT_KEK` or `WALLET_VAULT_KEK` in the server settings | Deleting a chat in the app marks it archived. The rows stay in the database. A hard delete path: not determined | not published | +| Memory envelope (`umem:v1:`) | Solrouter's database, table `user_memory`, one row per user | Scrambled in your browser with AES-256-GCM, stored as text with the prefix `umem:v1:`. Solrouter cannot read it | You. The key comes from your wallet signature and is never stored | "Forget all" in the app removes the row (`DELETE /memory`) | not published | +| Knowledge-base chunks | Files on the backend server disk, one JSON file per collection under `data/vectors/` | Plain text chunks plus embedding vectors. Not encrypted | none | Delete the whole collection (`DELETE /rag/collections/:name`). Delete of one document: not determined | not published | +| Uploaded files (R2) | A Cloudflare R2 bucket, key `documents/` plus a timestamp and a random id | The original file. Not encrypted by Solrouter | none | not determined. The code has upload and download, no delete | not published | +| Usage log | Solrouter's database, table `api_usage` | Plain rows: key id, user id, model, token counts, cost, request id, time. No prompt text | none | not determined | not published | +| Swap session rows (Soon) | Solrouter's database, table `agent_swap_sessions` | Plain rows: mode, state, tokens, amount, destination address, transaction ids, payer id, webhook URL. The one-shot signer key is scrambled and set to null when the swap ends | Solrouter backend, `WALLET_VAULT_KEK`, for the signer key only | not determined. The API has read and webhook routes, no delete | not published. Pending sessions expire after 7 days | +| Guest per-IP counter | Backend process memory, not a database | Your internet address, a message count, and a reset time | none | No route. The entry resets 24 hours after first use and vanishes when the server restarts | not published | + +
+ +Guest chat sends your prompt in plaintext to the backend and then to the model node. It stores no chat rows. + +## Short answers + +- "Can Solrouter read my prompt?" With the toggle on, or with the SDK default, no. With the toggle off, yes. +- "Can Solrouter read my history?" In Persistent mode, yes. It holds the key. In Maximum Privacy mode there is no history. +- "Can Solrouter read my memory?" No. Only your wallet can unlock it. +- "Can Solrouter read my uploaded documents?" Yes. They are stored as plain text and plain files. +- "Can anyone else read my prompt?" The operator of the Nosana GPU node could, while the model runs. No one else. +- "Is my wallet address public?" It is not on the Solana receipt. Solrouter's backend knows it. + +## Next + + + } href="/docs/use/chat-app"> + Where the Privacy Mode toggle is and what each mode keeps. + + } href="/docs/verify"> + Paste the lock link from a reply and see the receipt check pass. + + } href="/docs/how-it-works/what-is-a-tee"> + The sealed-room picture and where it breaks. + + } href="/docs/glossary"> + Every term on this page in one line each. + + diff --git a/content/docs/verify-a-reply.mdx b/content/docs/verify.mdx similarity index 88% rename from content/docs/verify-a-reply.mdx rename to content/docs/verify.mdx index 767c5f0..8dbdcf2 100644 --- a/content/docs/verify-a-reply.mdx +++ b/content/docs/verify.mdx @@ -1,5 +1,5 @@ --- -title: "Check a reply yourself" +title: "Verify an encryption proof" icon: BadgeCheck description: "Paste the lock link from a private chat reply and watch the check pass in your browser. Then compare the live enclave key with the signed hardware note." status: live @@ -99,8 +99,8 @@ A full audit compares the measurements inside the note with published reference ## Read more - - - - + + + + diff --git a/content/docs/what-is-private.mdx b/content/docs/what-is-private.mdx deleted file mode 100644 index 40dfd93..0000000 --- a/content/docs/what-is-private.mdx +++ /dev/null @@ -1,195 +0,0 @@ ---- -title: "What is private here" -icon: EyeOff -description: "Who can read your prompt, your files, and your history, and what Solrouter keeps on its servers." -status: mixed -checked: "2026-08-26" -statusNote: "Each row states its own Live or Soon status. Cells were checked against the product code and the live API on 2026-08-26." ---- - -import { Callout } from 'fumadocs-ui/components/callout'; -import { Cards, Card } from 'fumadocs-ui/components/card'; -import { Lock, ShieldCheck, BookOpen, MessageSquare } from 'lucide-react'; -import { PlaintextZones } from '@/components/diagrams/plaintext-zones'; - -## The question - -"If I type something private into Solrouter, who can read it?" - -With encryption on, Solrouter's own servers cannot read your prompt or the reply. The prompt is opened only inside a sealed computer (a TEE) and on the rented GPU machine that runs the AI model. Your attached files, your knowledge base, and your saved chat history do not get that protection, and the tables below show exactly where each one is readable. - -Encryption is a toggle in the chat app. It is off by default. The Privacy SDK encrypts by default. The page [Chat app](/docs/products/chat-app) explains the toggle. This page explains what each setting exposes. - -## Who is who - -The tables use six parties. Here is each one in plain words. - -- **You.** Your browser, or the program that uses the SDK. -- **Network observer.** Anyone who watches the connection between you and Solrouter. For example, your internet provider or the owner of a public Wi-Fi. -- **Solrouter backend.** Solrouter's own servers. They check your login, take payment, store your history, and pass messages along. -- **CVM cloud host (Phala).** The company that owns the physical machine where the sealed computer runs. The sealed computer is a Confidential Virtual Machine (CVM). The processor encrypts its memory, so the machine owner cannot read it. See [What is a TEE?](/docs/what-is-a-tee). -- **Nosana GPU host.** The operator of the graphics-card machine that runs the AI model. Solrouter rents it from the Nosana network. Solrouter does not control that hardware. -- **Solana observer.** Anyone who reads the public Solana blockchain. Solrouter posts a receipt there for each encrypted request. - -Each cell uses one of these words. - -- **plaintext.** This party can read it as you wrote it. -- **ciphertext.** This party sees it only in scrambled form and has no key to unscramble it. -- **encrypted at rest with a Solrouter key.** Stored scrambled, but Solrouter holds the key and can unscramble it. Think of a locked cabinet in Solrouter's office. A thief who steals the cabinet gets nothing. Solrouter has the key. Where the picture breaks: the key is a text value in the server settings, so the same servers that hold the cabinet also hold the key. -- **hash only.** A fixed-length fingerprint of the scrambled data. It cannot be turned back into your words. Think of a wax seal: it shows the same blob was seen, not what the blob says. Where the picture breaks: a seal can be forged, a hash of a different blob never matches. -- **metadata only.** Facts about the message, such as size, time, or destination, but not the content. -- **nothing.** It never reaches this party. -- **not determined.** We could not confirm this from the code. - -Footnotes point at lines in the product code. `be:` means the backend folder, `fe:` means the web app folder. The code is private, so the references exist so the Solrouter team can re-check each cell in one step. - -## Who can see what - -Rows for prompt text, reply text, memory, and web searches describe the encrypted path. That path is the Privacy SDK with its default settings, and the chat app with Maximum Privacy Mode on. The two history rows describe each chat mode by name. Every row here is Live except where a footnote says Soon. - -
- -| What | You | Network observer | Solrouter backend | CVM cloud host (Phala) | Nosana GPU host | Solana observer | -| --- | --- | --- | --- | --- | --- | --- | -| Prompt text | plaintext | ciphertext (1) | ciphertext (2) | ciphertext (3) | plaintext (4) | hash only (5) | -| Reply text | plaintext | ciphertext (1) | ciphertext (6) | ciphertext (3) | plaintext (4) | nothing | -| Attached files | plaintext | nothing (7) | nothing (7) | nothing | nothing | nothing | -| Knowledge-base documents | plaintext | ciphertext (1) | plaintext (8) | nothing | nothing (9) | nothing | -| Chat history (Persistent mode) | plaintext | ciphertext (1) | encrypted at rest with a Solrouter key (10) | nothing | plaintext (11) | nothing | -| Chat history (Maximum Privacy mode) | plaintext (12) | nothing | nothing (12) | nothing | nothing | nothing | -| Memory | plaintext | ciphertext (13) | ciphertext (13) | ciphertext (14) | plaintext (14) | nothing | -| Wallet address | plaintext | nothing (1) | plaintext (15) | nothing (16) | nothing (16) | nothing (17) | -| Model name | plaintext | nothing (1) | plaintext (18) | metadata only (19) | plaintext (4) | plaintext (20) | -| That you used Solrouter and when | plaintext | metadata only (21) | plaintext (15) | metadata only (22) | metadata only (22) | metadata only (20) | -| Encrypted-prompt hash | hash only (23) | nothing (1) | hash only (23) | nothing | nothing | hash only (5) | -| Web searches in agent mode | plaintext | ciphertext (24) | ciphertext (24) | metadata only (25) | plaintext (26) | nothing (27) | - -
- -Footnotes: - -1. The connection to `api.solrouter.com` uses HTTPS. A watcher sees scrambled traffic plus its size and timing. On the encrypted path your browser or program scrambles the prompt a second time before it leaves. Source: live `https://api.solrouter.com/tee/public-key` answered on 2026-08-26; `fe:src/utils/arciumClient.ts:181-189`; `be:packages/sdk/src/client.ts:180-192`. -2. The backend receives the field `encryptedPrompt` and forwards it unchanged. It holds no key. The private key is made inside the CVM at boot and never leaves it. Source: `be:routes/tee.js:44-53`; `be:tee-service/src/index.js:77-86`. -3. The processor encrypts the memory of the CVM. The machine owner sees encrypted memory and encrypted network traffic. The owner can see where the CVM sends traffic and when. Source: `be:tee-service/docker-compose.yml:1,15-21`. -4. The CVM sends the unscrambled prompt to the model node as normal text and receives the reply as normal text. The node runs the model outside the CVM. The node operator could read both at that moment. Solrouter does not control that hardware. The request is not linked to your identity on the node. Source: `be:tee-service/src/index.js:461-475`; `be:tee-service/docker-compose.yml:26-27`. -5. Solrouter posts a receipt on Solana with the SHA-256 hash of the scrambled blob, not of your words. Source: `be:services/lightAttestation.js:208-221,310`; `be:routes/tee.js:57-73`. -6. The reply returns to the backend scrambled with your session key. One caveat: the enclave writes the first 50 characters of each reply to its own log. Who can read that log: not determined. Source: `be:tee-service/src/index.js:577,586-589`; `be:routes/tee.js:84-86`. -7. The encrypted path sends the text prompt only. Attachments do not travel on it. The default-chat table below shows where attachments go. Source: `fe:src/components/chat/ChatArea.tsx:569`. -8. Knowledge-base files are split into text chunks on Solrouter's server and stored there as plain text, one JSON file per collection. The embeddings are computed on the same server, so no outside embedding service sees them. The code does not tie a collection to your account. Whether the deployed app isolates collections per user: not determined. Source: `be:lib/ragService.js:16-46`; `be:lib/embeddingService.js:92-115`; `be:routes/rag.js:101-111,235-257`. -9. On the encrypted path the CVM never sees the knowledge base. The backend forwards only the scrambled prompt, the model name, and an attestation id. Source: `be:routes/tee.js:50-54`. -10. Persistent mode is the chat app with the toggle off. Each stored message is scrambled with AES-256-GCM under a key derived from a secret in the backend settings. This protects against a stolen database copy. It does not protect against Solrouter, which holds the key. On this path the backend also reads the prompt in plaintext on the way in. Source: `be:lib/chatCrypto.js:1-15,29-35`; `be:routes/router.js:409-413`. -11. In Persistent mode the backend sends earlier messages of the chat to the model node as context. Source: `be:routes/nosana.js:560`; `be:routes/router.js:305-317`. -12. In Maximum Privacy mode the messages live only in your browser tab. Nothing is stored. A refresh removes them. Source: `fe:src/components/chat/ChatArea.tsx:1127-1132`. -13. Memory is a list of facts you asked Solrouter to remember. Your browser scrambles it with a key derived from your wallet signature and sends only the scrambled blob, prefixed `umem:v1:`. Solrouter never has the key. Lose the wallet, lose the memory. Source: `fe:src/lib/memory.ts:1-18`; `be:routes/memory.js:3-8`. -14. When you send a message, your browser adds the remembered facts to the front of the prompt. On the encrypted path they are scrambled together with the prompt. So the CVM and the model node see them, and the backend does not. Source: `fe:src/components/chat/ChatArea.tsx:546-548`; `fe:src/lib/memory.ts:221-229`. -15. Your wallet is your login. The backend must know it to check your balance and bill you. It also logs the model and the chat id for each request. Source: `be:routes/tee.js:33-48`. -16. The backend does not forward your wallet address to the CVM, and the CVM does not send it to the model node. Source: `be:routes/tee.js:50-54`; `be:tee-service/src/index.js:469-475`. -17. The Solana receipt has a field for a user wallet. Neither the chat app nor the SDK fills it, so it holds all zeros. A raw API caller who sends `userPubkey` would put that address on the public chain. Source: `be:services/lightAttestation.js:253-259,313`; `fe:src/utils/arciumClient.ts:184-188`; `be:packages/sdk/src/client.ts:186-192`. -18. The backend logs the model name with the chat id. Source: `be:routes/tee.js:44-48`. -19. Each model has its own node address. The host can see which address the CVM talks to, and so which model you used, but not what you said. Source: `be:tee-service/docker-compose.yml:20-21,45-49`. -20. The Solana receipt stores the model name, the provider, and a timestamp. Solrouter's own wallet signs it, not yours. Source: `be:services/lightAttestation.js:136-166,310-311`; `be:routes/tee.js:57-59`. -21. A watcher sees your internet address talking to `api.solrouter.com`, the time, and the size. See note 1. -22. The CVM host and the model node see traffic timing from Solrouter's side, not from you. Source: `be:tee-service/docker-compose.yml:20-21,26-27`. -23. The backend computes the hash of the scrambled blob and returns it to you as `encryptedPromptHash`. Source: `be:routes/tee.js:90-97`; `be:services/lightAttestation.js:208-221`. -24. This row is the encrypted agent path: a REST call to `/agent` with `encryptedPrompt`. Live for REST. Soon for the SDK. The whole tool loop runs inside the CVM and the backend relays ciphertext. The chat app's agent mode does not use this path; see the default-chat table. Source: `be:routes/agent.js:295-318`; `be:tee-service/src/index.js:707-736`; `fe:src/components/chat/ChatArea.tsx:331-342,1114`. -25. Web search inside the CVM goes through SearXNG, a search relay that runs in the same CVM. SearXNG then asks public search engines, and those engines receive the search text. The host sees destinations and timing. Source: `be:tee-service/docker-compose.yml:17-21,54-58`; `be:tee-service/src/tools.js:25-34`. -26. The CVM sends the prompt and the search results to the model node as normal text. Source: `be:tee-service/src/index.js:461-475,731-736`. -27. No Solana receipt is written for the encrypted agent path. Source: `be:routes/agent.js:308-347`. - -## Default chat (toggle off) - -This is the chat app with the Privacy Mode toggle off, the SDK with `encrypted: false`, and guest chat. There is no second layer of encryption. Only rows that change are listed. Status: Live. - -
- -| What | You | Network observer | Solrouter backend | CVM cloud host (Phala) | Nosana GPU host | Solana observer | -| --- | --- | --- | --- | --- | --- | --- | -| Prompt text | plaintext | ciphertext (a) | plaintext (b) | nothing (c) | plaintext (d) | nothing (e) | -| Reply text | plaintext | ciphertext (a) | plaintext (b) | nothing (c) | plaintext (d) | nothing (e) | -| Attached files | plaintext | ciphertext (a) | plaintext (f) | nothing | plaintext (f) | nothing | -| Knowledge-base documents (retrieved parts) | plaintext | ciphertext (a) | plaintext (g) | nothing | plaintext (g) | nothing | -| Memory (facts added to the prompt) | plaintext | ciphertext (a) | plaintext (h) | nothing | plaintext (h) | nothing | -| Web searches (agent mode and live search) | plaintext | ciphertext (a) | plaintext (i) | nothing | plaintext (i) | nothing | - -
- -Footnotes: - -- (a) HTTPS only. A watcher sees scrambled traffic plus size and timing. See note 1 above. -- (b) The backend reads the prompt, builds the final text for the model, and stores your message and the reply scrambled with its own key (note 10). The encrypted path needs the toggle on. Source: `be:routes/router.js:277-296,409-413`; `fe:src/components/chat/ChatArea.tsx:1114`. -- (c) The default path does not use the CVM. The backend talks to the model node itself. Source: `be:routes/router.js:203-206,885-898`; `be:routes/nosana.js:560`. -- (d) Same as note 4. The request comes from Solrouter's backend instead of the CVM. -- (e) No Solana receipt is written on the default path. Source: `be:routes/nosana.js:405,433` pass an id through and call nothing else. -- (f) Images become data URLs inside the request. Documents upload to Cloudflare R2, a file store run by Cloudflare, through a short-lived upload link. The backend then downloads the file, extracts the text, and adds it to the prompt. The file stays in R2; see What we keep. Source: `fe:src/lib/r2Upload.ts:36-52`; `be:services/r2Storage.js:22-46`; `be:routes/nosana.js:298-326`. -- (g) The backend finds the best-matching chunks and pastes them into the prompt. Source: `be:routes/router.js:751-765`. -- (h) On this path the remembered facts travel as normal text inside the prompt. The stored memory blob stays scrambled. Source: `fe:src/components/chat/ChatArea.tsx:314,407`. -- (i) Agent mode in the chat app calls `/agent` with the plain prompt. Live search and the agent's `web_search` tool use Brave Search, with DuckDuckGo and Wikipedia as fallbacks. Those services receive the search text. Source: `fe:src/components/chat/ChatArea.tsx:331-342`; `be:lib/agentService.js:91,538-539`; `be:lib/liveDataService.js:58-63,124,167,206`. - -## Where your words are readable - -The strip below follows one encrypted request from your device to the Solana receipt. Green zones hold your words in readable form. Grey zones hold only ciphertext or a hash. The amber zone is the rented GPU machine. - - - -**In words** - -- Your device: your words are readable here. Your browser or program scrambles them before they leave. -- Network: ciphertext only. A watcher sees size and timing. -- Solrouter backend: ciphertext only. It checks your login, bills you, and passes the blob along. -- TDX CVM: your words are readable here, inside memory that the processor encrypts. The machine owner cannot open it. -- Nosana GPU node: your words are readable here while the model runs. Solrouter does not control this machine. The request is not linked to you. -- Solana: hash only. A receipt proves a request happened and names the model. It does not hold your words. - - - Solrouter does not run fully homomorphic encryption (FHE) inference. FHE means a computer works on scrambled data without ever unscrambling it. No production system runs AI models of this size under FHE in 2026. The compute cost is many orders of magnitude away from usable speed. Anyone who claims "FHE LLM inference" in production is overclaiming. - - What Solrouter provides is encryption on your device, a hardware-isolated CVM that unscrambles the prompt, a model that runs on a rented Nosana GPU node, and a receipt on Solana for each encrypted request. That is a real and checkable guarantee. It is not FHE, and we will not claim otherwise. - - -## What we keep - -Retention periods are not published. Each row states what is stored, in what form, who holds the key, and how to remove it. Rows are Live unless marked. - -
- -| What | Where | Format | Key holder | How to delete | Retention | -| --- | --- | --- | --- | --- | --- | -| Chat rows (`enc:v1:`) | Solrouter's database, tables `chat_messages` and `chats` (message text, chat title, search and knowledge-base context, pitch-deck cards) | Scrambled with AES-256-GCM, stored as text with the prefix `enc:v1:` | Solrouter backend, from `CHAT_CONTENT_KEK` or `WALLET_VAULT_KEK` in the server settings | Deleting a chat in the app marks it archived. The rows stay in the database. A hard delete path: not determined | not published | -| Memory envelope (`umem:v1:`) | Solrouter's database, table `user_memory`, one row per user | Scrambled in your browser with AES-256-GCM, stored as text with the prefix `umem:v1:`. Solrouter cannot read it | You. The key comes from your wallet signature and is never stored | "Forget all" in the app removes the row (`DELETE /memory`) | not published | -| Knowledge-base chunks | Files on the backend server disk, one JSON file per collection under `data/vectors/` | Plain text chunks plus embedding vectors. Not encrypted | none | Delete the whole collection (`DELETE /rag/collections/:name`). Delete of one document: not determined | not published | -| Uploaded files (R2) | A Cloudflare R2 bucket, key `documents/` plus a timestamp and a random id | The original file. Not encrypted by Solrouter | none | not determined. The code has upload and download, no delete | not published | -| Usage log | Solrouter's database, table `api_usage` | Plain rows: key id, user id, model, token counts, cost, request id, time. No prompt text | none | not determined | not published | -| Swap session rows (Soon) | Solrouter's database, table `agent_swap_sessions` | Plain rows: mode, state, tokens, amount, destination address, transaction ids, payer id, webhook URL. The one-shot signer key is scrambled and set to null when the swap ends | Solrouter backend, `WALLET_VAULT_KEK`, for the signer key only | not determined. The API has read and webhook routes, no delete | not published. Pending sessions expire after 7 days | -| Guest per-IP counter | Backend process memory, not a database | Your internet address, a message count, and a reset time | none | No route. The entry resets 24 hours after first use and vanishes when the server restarts | not published | - -
- -Sources, row by row: chat rows `be:lib/chatCrypto.js:9-15,29,76-78`, `be:routes/router.js:409-413`, `be:routes/chats.js:149,215,252-270`; memory `be:migrations/027_user_memory.sql:3-9,27-36`, `fe:src/lib/memory.ts:3-10,207-212`, `be:routes/memory.js:337-344`; knowledge-base chunks `be:lib/embeddingService.js:92-115,202-207`, `be:lib/ragService.js:32-46`, `be:routes/rag.js:131-147,172`; uploaded files `be:services/r2Storage.js:2,22-46`; usage log `be:migrations/add_api_keys.sql:23-33`, `be:routes/private-ai-api.js:257-266`, `be:routes/private-ai-x402.js:61-70`; swap sessions `be:migrations/009_agent_swap_sessions.sql:7-47`, `be:routes/agents/sessions.js:11-37`, design decision 9 for Soon; guest counter `be:routes/router.js:197-198,230-237`. - -Guest chat sends your prompt in plaintext to the backend and then to the model node. It stores no chat rows. Source: `be:routes/router.js:228-271`. - -## Short answers - -- "Can Solrouter read my prompt?" With the toggle on, or with the SDK default, no. With the toggle off, yes. -- "Can Solrouter read my history?" In Persistent mode, yes. It holds the key. In Maximum Privacy mode there is no history. -- "Can Solrouter read my memory?" No. Only your wallet can unlock it. -- "Can Solrouter read my uploaded documents?" Yes. They are stored as plain text and plain files. -- "Can anyone else read my prompt?" The operator of the Nosana GPU node could, while the model runs. No one else. -- "Is my wallet address public?" It is not on the Solana receipt. Solrouter's backend knows it. - -## Next - - - } href="/docs/products/chat-app"> - Where the Privacy Mode toggle is and what each mode keeps. - - } href="/docs/verify-a-reply"> - Paste the lock link from a reply and see the receipt check pass. - - } href="/docs/what-is-a-tee"> - The sealed-room picture and where it breaks. - - } href="/docs/glossary"> - Every term on this page in one line each. - - diff --git a/openapi/agent-privacy.json b/openapi/agent-privacy.json index fbd4960..455225d 100644 --- a/openapi/agent-privacy.json +++ b/openapi/agent-privacy.json @@ -122,7 +122,7 @@ "bucketed": true, "bucketLabel": "USDC_1", "nearestBucket": "USDC_1", - "privacyNotes": "amount snaps to standard denomination — strong anonymity-set" + "privacyNotes": "amount snaps to a standard denomination, a strong anonymity set" } }, "Session": { @@ -252,7 +252,7 @@ "Swaps" ], "summary": "Quote a private swap", - "description": "Price a private swap before executing it. Returns the expected output after Solrouter's spread plus anonymity-set guidance — whether the amount snaps to a standard denomination bucket (strong privacy) or is an off-bucket amount with a unique on-chain fingerprint.", + "description": "Price a private swap before executing it. Returns the expected output after Solrouter's spread plus anonymity-set guidance: whether the amount snaps to a standard denomination bucket (strong privacy) or is an off-bucket amount with a unique on-chain fingerprint.", "parameters": [ { "name": "fromMint", @@ -333,7 +333,7 @@ "Swaps" ], "summary": "Pool depth for a denomination bucket", - "description": "Inspect the anonymity set for a standard denomination bucket before swapping — how many recent deposits share that bucket. A deeper pool means a larger crowd to hide in.", + "description": "Inspect the anonymity set for a standard denomination bucket before swapping: how many recent deposits share that bucket. A deeper pool means a larger crowd to hide in.", "parameters": [ { "name": "bucket", @@ -404,7 +404,7 @@ "Swaps" ], "summary": "Begin a one-shot ephemeral-wallet private swap (Mode B)", - "description": "Start a private swap that runs through a throwaway ephemeral wallet — no managed custody. Solrouter returns an unsigned `fundingTx`; the agent signs and broadcasts it, then calls `POST /swaps/oneshot/{id}/execute` to kick off the swap orchestrator.", + "description": "Start a private swap that runs through a throwaway ephemeral wallet, with no managed custody. Solrouter returns an unsigned `fundingTx`; the agent signs and broadcasts it, then calls `POST /swaps/oneshot/{id}/execute` to kick off the swap orchestrator.", "requestBody": { "required": true, "content": { @@ -623,7 +623,7 @@ "Managed Wallets" ], "summary": "Provision a managed Umbra wallet (Mode A)", - "description": "Create a Solrouter-custodied Umbra wallet. The keypair is generated server-side and envelope-encrypted (AES-256-GCM) — the agent never holds private keys. Fund the returned `umbraAddress`, then run swaps and encrypted-balance operations against the wallet id.", + "description": "Create a Solrouter-custodied Umbra wallet. The keypair is generated server-side and envelope-encrypted (AES-256-GCM), so the agent never holds private keys. Fund the returned `umbraAddress`, then run swaps and encrypted-balance operations against the wallet id.", "requestBody": { "content": { "application/json": { @@ -1095,7 +1095,7 @@ "tags": [ "Private Balance" ], - "summary": "Shield + unlink — mixer round-trip to a fresh address", + "summary": "Shield + unlink: mixer round-trip to a fresh address", "description": "Four txs, ~60s. Public balance enters the Umbra mixer, a claim breaks the on-chain link, then withdraw + transfer delivers to a FRESH destination. Source and destination both appear on-chain but with no link between them. Use when you want destination unlinkability without converting tokens.", "parameters": [ { From c867716ac8735dabf9c866afda6b38686a875120 Mon Sep 17 00:00:00 2001 From: sarthib7 Date: Thu, 27 Aug 2026 13:55:00 +0200 Subject: [PATCH 7/7] fix(deps): regenerate package-lock so npm ci passes Adds the @emnapi wasm32-wasi entries (incl. @emnapi/runtime@1.11.3) npm ci needs on Linux; transitives refreshed within semver. next/fumadocs/@xyflow unchanged. --- package-lock.json | 1651 ++++++++++++++++++++++++--------------------- 1 file changed, 883 insertions(+), 768 deletions(-) diff --git a/package-lock.json b/package-lock.json index b2f5a19..1017609 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,14 +105,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -225,13 +225,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -256,18 +256,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", + "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", + "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -275,9 +275,9 @@ } }, "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { @@ -288,10 +288,34 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.3.tgz", - "integrity": "sha512-ELEBe8PsLvvJ6QMr0zLt8ffvOHW/dc1m3CEzNMg7aJUv3bMaoDtw2TXyDAwkYBuroxxuHEwhRTLJSe5sya547g==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -300,9 +324,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", "cpu": [ "ppc64" ], @@ -316,9 +340,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", "cpu": [ "arm" ], @@ -332,9 +356,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", "cpu": [ "arm64" ], @@ -348,9 +372,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", "cpu": [ "x64" ], @@ -364,9 +388,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", "cpu": [ "arm64" ], @@ -380,9 +404,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", "cpu": [ "x64" ], @@ -396,9 +420,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", "cpu": [ "arm64" ], @@ -412,9 +436,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", "cpu": [ "x64" ], @@ -428,9 +452,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", "cpu": [ "arm" ], @@ -444,9 +468,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", "cpu": [ "arm64" ], @@ -460,9 +484,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", "cpu": [ "ia32" ], @@ -476,9 +500,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", "cpu": [ "loong64" ], @@ -492,9 +516,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", "cpu": [ "mips64el" ], @@ -508,9 +532,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", "cpu": [ "ppc64" ], @@ -524,9 +548,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", "cpu": [ "riscv64" ], @@ -540,9 +564,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", "cpu": [ "s390x" ], @@ -556,9 +580,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", "cpu": [ "x64" ], @@ -572,9 +596,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", "cpu": [ "arm64" ], @@ -588,9 +612,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", "cpu": [ "x64" ], @@ -604,9 +628,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", "cpu": [ "arm64" ], @@ -620,9 +644,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", "cpu": [ "x64" ], @@ -636,9 +660,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", "cpu": [ "arm64" ], @@ -652,9 +676,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", "cpu": [ "x64" ], @@ -668,9 +692,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", "cpu": [ "arm64" ], @@ -684,9 +708,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", "cpu": [ "ia32" ], @@ -700,9 +724,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", "cpu": [ "x64" ], @@ -716,9 +740,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { @@ -799,9 +823,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -811,7 +835,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -823,9 +847,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { @@ -860,31 +884,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", "license": "MIT", "dependencies": { - "@floating-ui/utils": "^0.2.11" + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", - "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.9.tgz", + "integrity": "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.6" + "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", @@ -892,9 +916,9 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, "node_modules/@fumadocs/tailwind": { @@ -1570,22 +1594,25 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@next/env": { @@ -1790,32 +1817,32 @@ } }, "node_modules/@radix-ui/number": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.2.tgz", - "integrity": "sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", + "integrity": "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==", "license": "MIT" }, "node_modules/@radix-ui/primitive": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.4.tgz", - "integrity": "sha512-7AdCK9PQyiljKoBDbN8OuctCbd/esdwZPQ8RtOE3SsyQtUpiPb+ND75q0jEhC1m1ecBI0MFNeLJvwIh9iKHRcQ==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz", + "integrity": "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==", "license": "MIT" }, "node_modules/@radix-ui/react-accordion": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.13.tgz", - "integrity": "sha512-xITxBB2p5m5tAe7M0F95kb4uAh7jSIKGlExMEm93HlW+XxZHV2eXFbPWLktd4JhRiwcnXNbO7iekcrbZy6ZCvA==", + "version": "1.2.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.20.tgz", + "integrity": "sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collapsible": "1.1.13", - "@radix-ui/react-collection": "1.1.9", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collapsible": "1.1.20", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -1833,12 +1860,12 @@ } }, "node_modules/@radix-ui/react-arrow": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.9.tgz", - "integrity": "sha512-yqHW5WQ/cTpU/un7dqqIKNy2iRU8BC0JB78PEzTfCCYvZu1U6W9KwObAniMk9nhSfyotKPQTYaUD/HB0f5muig==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.15.tgz", + "integrity": "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.5" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -1856,19 +1883,19 @@ } }, "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.13.tgz", - "integrity": "sha512-F0s8+p2XNpfc3k02zBfB0jPWbkHVG162+p7BdUMyJ2308QMqZ+oaclX+FAzKFovgL5OqRU+Rvy6f/vbdlJVaqA==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.20.tgz", + "integrity": "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -1886,15 +1913,15 @@ } }, "node_modules/@radix-ui/react-collection": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.9.tgz", - "integrity": "sha512-zuSVi7ziP7uQRqc+yGxsKJfNkdyHv3ZKDaHe0gzg4dRgws96TPKWIiz84tVHP4GEcEl8bC0mdt17NkcxaJHmaQ==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.15.tgz", + "integrity": "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-slot": "1.2.5" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -1912,9 +1939,9 @@ } }, "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.3.tgz", - "integrity": "sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.5.tgz", + "integrity": "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1927,9 +1954,9 @@ } }, "node_modules/@radix-ui/react-context": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.4.tgz", - "integrity": "sha512-QwH4PO5urrbO+FaGd5Aglg+YJgWTyyuZ3g/6mKvsqraLkglDdckw9JafgL5McL5VEJ6EPNduPaT3ZE9BttDAqg==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.2.2.tgz", + "integrity": "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1942,23 +1969,24 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.16.tgz", - "integrity": "sha512-l9ok83YBclEZhbjgzt76Hw733e6cvRKPNgO6GJ/IETlufXG9p+fRu2wlvpImQvR6xdJ8h7J8J2DBvsPEiEsKMw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.12", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.9", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-portal": "1.1.11", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-slot": "1.2.5", - "@radix-ui/react-use-controllable-state": "1.2.3", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.23.tgz", + "integrity": "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -1978,9 +2006,9 @@ } }, "node_modules/@radix-ui/react-direction": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.2.tgz", - "integrity": "sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.4.tgz", + "integrity": "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1993,16 +2021,16 @@ } }, "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.12.tgz", - "integrity": "sha512-MhoruH6xEzsbvOmo4TNgMfmtvRGyDZw4MDSdf4ybMHfezjqwzv6hyd4lsMzBp8K9Sn6sGzCF62x1I7BYUECXOg==", + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.19.tgz", + "integrity": "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-escape-keydown": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", @@ -2020,9 +2048,9 @@ } }, "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.4.tgz", - "integrity": "sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.6.tgz", + "integrity": "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2035,14 +2063,14 @@ } }, "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.9.tgz", - "integrity": "sha512-9Se8t+Zry+1rEOL7Y6l/4ANYU/TOtAtf8O2fKdwLltcaMcm6kOqYGbzO4tMFQ0bvzO920pRAoHpFZ4W85S3keQ==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.16.tgz", + "integrity": "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-use-callback-ref": "1.1.2" + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2060,12 +2088,12 @@ } }, "node_modules/@radix-ui/react-id": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.2.tgz", - "integrity": "sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.4.tgz", + "integrity": "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2078,25 +2106,25 @@ } }, "node_modules/@radix-ui/react-navigation-menu": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.15.tgz", - "integrity": "sha512-/fS8hKCcRt4DwCGa5QIB3juRXmfYSOk4a2AEe/BDIyy7Hm+eje2Y13oUx5zejl+wFt1owrM7E8NWlbaEl5EGpg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.9", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.12", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.5" + "version": "1.2.22", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.22.tgz", + "integrity": "sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", @@ -2114,24 +2142,24 @@ } }, "node_modules/@radix-ui/react-popover": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.16.tgz", - "integrity": "sha512-8brVpAU5Uq7Bh0c8EFc4ZTf2JJTYn0o+1L+CUJB3UYIOkTjKGMgoHvduylrahdmNlr3DfH0rFq2DrbNZXgaspw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-dismissable-layer": "1.1.12", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.9", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.0", - "@radix-ui/react-portal": "1.1.11", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-slot": "1.2.5", - "@radix-ui/react-use-controllable-state": "1.2.3", + "version": "1.1.23", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.23.tgz", + "integrity": "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-controllable-state": "1.2.6", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -2151,21 +2179,21 @@ } }, "node_modules/@radix-ui/react-popper": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.0.tgz", - "integrity": "sha512-9PB589e1aWZbrlFUHdz6WiPCL+xLZHQFX7oibqG/6Q0SwOkxDyQX9W/cyPa+sAPPKuC8cpLCpRczE5a/1DiwVQ==", + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.3.7.tgz", + "integrity": "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==", "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.9", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-rect": "1.1.2", - "@radix-ui/react-use-size": "1.1.2", - "@radix-ui/rect": "1.1.2" + "@radix-ui/react-arrow": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-rect": "1.1.4", + "@radix-ui/react-use-size": "1.1.4", + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -2183,13 +2211,13 @@ } }, "node_modules/@radix-ui/react-portal": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.11.tgz", - "integrity": "sha512-UEytdjgEh2tJGgD/gZK4FUx6t1rNIlM3U0DENhSrG7I75FGm1DnaDuVUWF1pWAWUwGmn1sCJ1VGHn8LhN1aTOw==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.17.tgz", + "integrity": "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2207,12 +2235,12 @@ } }, "node_modules/@radix-ui/react-presence": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.6.tgz", - "integrity": "sha512-zdTk4PlUO0E18HnZ3wYbW0KkJJxWCdiNYp6g6X1PtONFhxVkg01vliTJAmwIszU6mHiyBOoW9P0rAugl5/hULQ==", + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.10.tgz", + "integrity": "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2230,12 +2258,12 @@ } }, "node_modules/@radix-ui/react-primitive": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.5.tgz", - "integrity": "sha512-zifXeB8Y88qCYx8PLZ5oQb32KwZub+s925mMoZsBBq9KUQqWKkREubTfs6ASjRPPBe7Jt9O8OHH89+95VG+grA==", + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.10.tgz", + "integrity": "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==", "license": "MIT", "dependencies": { - "@radix-ui/react-slot": "1.2.5" + "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", @@ -2253,20 +2281,22 @@ } }, "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.12.tgz", - "integrity": "sha512-FvgPt1bRmg8Xt2QpF7NUZW3dE0ZQHGm41dAdgT2J2GJPoIXz+9Em3NobAxf4fupcxhgHu03E5CRiU2MWvObXyg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.9", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3" + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.19.tgz", + "integrity": "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-is-hydrated": "0.1.3", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2284,20 +2314,20 @@ } }, "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.11.tgz", - "integrity": "sha512-DS39ziOgea75U/TrXKU2/oKp0be2jrDHnzFLvahg/0iNAT1Zq16e4Uw0WXwyXvsK+mG3BRyMb7A3NRZMDuEXtQ==", + "version": "1.2.18", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.18.tgz", + "integrity": "sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==", "license": "MIT", "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2315,31 +2345,31 @@ } }, "node_modules/@radix-ui/react-select": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.0.tgz", - "integrity": "sha512-mENc7WpJvJcW8hlMpzfFcHcEhTvYS5JMBmi9HVC1Q00uhBwML086MHYUV8QQdQv6lcu0Wg8dzd1RB8AFADcG/g==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.2", - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-collection": "1.1.9", - "@radix-ui/react-compose-refs": "1.1.3", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.12", - "@radix-ui/react-focus-guards": "1.1.4", - "@radix-ui/react-focus-scope": "1.1.9", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-popper": "1.3.0", - "@radix-ui/react-portal": "1.1.11", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-slot": "1.2.5", - "@radix-ui/react-use-callback-ref": "1.1.2", - "@radix-ui/react-use-controllable-state": "1.2.3", - "@radix-ui/react-use-layout-effect": "1.1.2", - "@radix-ui/react-use-previous": "1.1.2", - "@radix-ui/react-visually-hidden": "1.2.5", + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.3.7.tgz", + "integrity": "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.3", + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-collection": "1.1.15", + "@radix-ui/react-compose-refs": "1.1.5", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-dismissable-layer": "1.1.19", + "@radix-ui/react-focus-guards": "1.1.6", + "@radix-ui/react-focus-scope": "1.1.16", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-popper": "1.3.7", + "@radix-ui/react-portal": "1.1.17", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-slot": "1.3.3", + "@radix-ui/react-use-callback-ref": "1.1.4", + "@radix-ui/react-use-controllable-state": "1.2.6", + "@radix-ui/react-use-layout-effect": "1.1.4", + "@radix-ui/react-use-previous": "1.1.4", + "@radix-ui/react-visually-hidden": "1.2.11", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, @@ -2359,12 +2389,12 @@ } }, "node_modules/@radix-ui/react-slot": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.5.tgz", - "integrity": "sha512-rCMO3QsIVKv5JTY5CVbo2MvO77SpEqqYc8AvRE7OWqRDOIqAKjsp+DrmnY9uc8NPdxB5E2z47HTYGeE2+NTptg==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.3.3.tgz", + "integrity": "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==", "license": "MIT", "dependencies": { - "@radix-ui/react-compose-refs": "1.1.3" + "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", @@ -2377,19 +2407,19 @@ } }, "node_modules/@radix-ui/react-tabs": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.14.tgz", - "integrity": "sha512-D5jwp9JNuwDeCw3CYD2Fz+sSHo0droQjC8u75dJHe4aWr5q6yBiXZU+hurXnKudRgEpUkD5TsI6bjHPo5ThUxA==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz", + "integrity": "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.4", - "@radix-ui/react-context": "1.1.4", - "@radix-ui/react-direction": "1.1.2", - "@radix-ui/react-id": "1.1.2", - "@radix-ui/react-presence": "1.1.6", - "@radix-ui/react-primitive": "2.1.5", - "@radix-ui/react-roving-focus": "1.1.12", - "@radix-ui/react-use-controllable-state": "1.2.3" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-context": "1.2.2", + "@radix-ui/react-direction": "1.1.4", + "@radix-ui/react-id": "1.1.4", + "@radix-ui/react-presence": "1.1.10", + "@radix-ui/react-primitive": "2.1.10", + "@radix-ui/react-roving-focus": "1.1.19", + "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", @@ -2407,9 +2437,9 @@ } }, "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.2.tgz", - "integrity": "sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.4.tgz", + "integrity": "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2422,13 +2452,14 @@ } }, "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.3.tgz", - "integrity": "sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.6.tgz", + "integrity": "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.3", - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/primitive": "1.1.7", + "@radix-ui/react-use-effect-event": "0.0.5", + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2441,12 +2472,12 @@ } }, "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.3.tgz", - "integrity": "sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==", + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.5.tgz", + "integrity": "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2458,14 +2489,11 @@ } } }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.2.tgz", - "integrity": "sha512-2uVLvLjgO7NZCWw01/FdqRwmA42J0BcjPMUCA+koFEOAb+zjqIP7SiFz/7zWPrKnVmSqr76Omq2ALyCuX4dhLw==", + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.3.tgz", + "integrity": "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==", "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.2" - }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" @@ -2477,9 +2505,9 @@ } }, "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.2.tgz", - "integrity": "sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.4.tgz", + "integrity": "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2492,9 +2520,9 @@ } }, "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.2.tgz", - "integrity": "sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.4.tgz", + "integrity": "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -2507,12 +2535,12 @@ } }, "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.2.tgz", - "integrity": "sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.4.tgz", + "integrity": "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==", "license": "MIT", "dependencies": { - "@radix-ui/rect": "1.1.2" + "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", @@ -2525,12 +2553,12 @@ } }, "node_modules/@radix-ui/react-use-size": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.2.tgz", - "integrity": "sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.4.tgz", + "integrity": "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==", "license": "MIT", "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.2" + "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", @@ -2543,12 +2571,12 @@ } }, "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.5.tgz", - "integrity": "sha512-tPcHNI3FajdDBFpl/Ez1m2WL0ufJqBKyHxMDBvKitopamK36WwBGOMicuMEZKkM5Wce41QxUyv6BsiqfrWBiGg==", + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.11.tgz", + "integrity": "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==", "license": "MIT", "dependencies": { - "@radix-ui/react-primitive": "2.1.5" + "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", @@ -2566,9 +2594,9 @@ } }, "node_modules/@radix-ui/rect": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.2.tgz", - "integrity": "sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.3.tgz", + "integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==", "license": "MIT" }, "node_modules/@rtsao/scc": { @@ -2579,15 +2607,15 @@ "license": "MIT" }, "node_modules/@shikijs/core": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.2.0.tgz", - "integrity": "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.3.tgz", + "integrity": "sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==", "license": "MIT", "dependencies": { - "@shikijs/primitive": "4.2.0", - "@shikijs/types": "4.2.0", + "@shikijs/primitive": "4.4.3", + "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", + "@types/hast": "^3.0.5", "hast-util-to-html": "^9.0.5" }, "engines": { @@ -2595,12 +2623,12 @@ } }, "node_modules/@shikijs/engine-javascript": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.2.0.tgz", - "integrity": "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.4.3.tgz", + "integrity": "sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0", + "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" }, @@ -2609,12 +2637,12 @@ } }, "node_modules/@shikijs/engine-oniguruma": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.2.0.tgz", - "integrity": "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.3.tgz", + "integrity": "sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0", + "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2" }, "engines": { @@ -2622,51 +2650,51 @@ } }, "node_modules/@shikijs/langs": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.2.0.tgz", - "integrity": "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.4.3.tgz", + "integrity": "sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0" + "@shikijs/types": "4.4.3" }, "engines": { "node": ">=20" } }, "node_modules/@shikijs/primitive": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.2.0.tgz", - "integrity": "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.4.3.tgz", + "integrity": "sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0", + "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" + "@types/hast": "^3.0.5" }, "engines": { "node": ">=20" } }, "node_modules/@shikijs/themes": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.2.0.tgz", - "integrity": "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.3.tgz", + "integrity": "sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==", "license": "MIT", "dependencies": { - "@shikijs/types": "4.2.0" + "@shikijs/types": "4.4.3" }, "engines": { "node": ">=20" } }, "node_modules/@shikijs/types": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz", - "integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.4.3.tgz", + "integrity": "sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" + "@types/hast": "^3.0.5" }, "engines": { "node": ">=20" @@ -2694,25 +2722,25 @@ } }, "node_modules/@tailwindcss/node": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", - "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz", + "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.21.0", - "jiti": "^2.6.1", + "enhanced-resolve": "^5.24.1", + "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.3.0" + "tailwindcss": "4.3.3" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", - "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz", + "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==", "devOptional": true, "license": "MIT", "peer": true, @@ -2720,24 +2748,24 @@ "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-arm64": "4.3.0", - "@tailwindcss/oxide-darwin-x64": "4.3.0", - "@tailwindcss/oxide-freebsd-x64": "4.3.0", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.0", - "@tailwindcss/oxide-linux-arm64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-arm64-musl": "4.3.0", - "@tailwindcss/oxide-linux-x64-gnu": "4.3.0", - "@tailwindcss/oxide-linux-x64-musl": "4.3.0", - "@tailwindcss/oxide-wasm32-wasi": "4.3.0", - "@tailwindcss/oxide-win32-arm64-msvc": "4.3.0", - "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" + "@tailwindcss/oxide-android-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-arm64": "4.3.3", + "@tailwindcss/oxide-darwin-x64": "4.3.3", + "@tailwindcss/oxide-freebsd-x64": "4.3.3", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.3", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.3", + "@tailwindcss/oxide-linux-x64-musl": "4.3.3", + "@tailwindcss/oxide-wasm32-wasi": "4.3.3", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.3" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", - "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz", + "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==", "cpu": [ "arm64" ], @@ -2752,9 +2780,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", - "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz", + "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==", "cpu": [ "arm64" ], @@ -2769,9 +2797,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", - "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz", + "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==", "cpu": [ "x64" ], @@ -2786,9 +2814,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", - "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz", + "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==", "cpu": [ "x64" ], @@ -2803,9 +2831,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", - "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz", + "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==", "cpu": [ "arm" ], @@ -2820,9 +2848,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", - "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz", + "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==", "cpu": [ "arm64" ], @@ -2837,9 +2865,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", - "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz", + "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==", "cpu": [ "arm64" ], @@ -2854,9 +2882,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", - "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz", + "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==", "cpu": [ "x64" ], @@ -2871,9 +2899,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", - "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz", + "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==", "cpu": [ "x64" ], @@ -2888,9 +2916,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", - "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz", + "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -2906,21 +2934,89 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.10.0", - "@emnapi/runtime": "^1.10.0", - "@emnapi/wasi-threads": "^1.2.1", + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", "@napi-rs/wasm-runtime": "^1.1.4", - "@tybys/wasm-util": "^0.10.1", + "@tybys/wasm-util": "^0.10.2", "tslib": "^2.8.1" }, "engines": { "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", - "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz", + "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==", "cpu": [ "arm64" ], @@ -2935,9 +3031,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", - "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz", + "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==", "cpu": [ "x64" ], @@ -2952,23 +3048,23 @@ } }, "node_modules/@tailwindcss/postcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.0.tgz", - "integrity": "sha512-Jm05Tjx+9yCLGv5qw1c+84Psds8MnyrEQYCB+FFk2lgGiUjlRqdxke4mVTuYrj2xnVZqKim2Apr5ySuQRYAw/w==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.3.tgz", + "integrity": "sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==", "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", - "@tailwindcss/node": "4.3.0", - "@tailwindcss/oxide": "4.3.0", - "postcss": "^8.5.10", - "tailwindcss": "4.3.0" + "@tailwindcss/node": "4.3.3", + "@tailwindcss/oxide": "4.3.3", + "postcss": "^8.5.16", + "tailwindcss": "4.3.3" } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, @@ -3050,9 +3146,9 @@ } }, "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -3096,9 +3192,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz", - "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==", + "version": "25.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.5.tgz", + "integrity": "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -3106,9 +3202,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", "devOptional": true, "license": "MIT", "peer": true, @@ -3117,9 +3213,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", "devOptional": true, "license": "MIT", "peer": true, @@ -3134,17 +3230,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.0.tgz", - "integrity": "sha512-bFNvl9ZczlVb+wR2Akszf3gHfKVj/8WanXaGJ3UstTA7brNKg0cNdk6X1Psu5V7MZ2oQtzZKOEzIUehaoxbDGw==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.68.0.tgz", + "integrity": "sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.61.0", - "@typescript-eslint/type-utils": "8.61.0", - "@typescript-eslint/utils": "8.61.0", - "@typescript-eslint/visitor-keys": "8.61.0", + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/type-utils": "8.68.0", + "@typescript-eslint/utils": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3157,15 +3253,15 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.61.0", + "@typescript-eslint/parser": "^8.68.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -3173,17 +3269,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.0.tgz", - "integrity": "sha512-5B7PfA2e1NQGCnDHd/0lW7W3gvp3d59Ryw54FYO8Uswxo9f6ikw3AZV+Xj/TvpImmpsiYyUqAfhC6kJID1jF6w==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.68.0.tgz", + "integrity": "sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.61.0", - "@typescript-eslint/types": "8.61.0", - "@typescript-eslint/typescript-estree": "8.61.0", - "@typescript-eslint/visitor-keys": "8.61.0", + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", "debug": "^4.4.3" }, "engines": { @@ -3199,14 +3295,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.0.tgz", - "integrity": "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.68.0.tgz", + "integrity": "sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.61.0", - "@typescript-eslint/types": "^8.61.0", + "@typescript-eslint/tsconfig-utils": "^8.68.0", + "@typescript-eslint/types": "^8.68.0", "debug": "^4.4.3" }, "engines": { @@ -3221,14 +3317,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.0.tgz", - "integrity": "sha512-IWdXFHFSb6mlC3HPc7QsLDm5zYEbUla6trDEHf32D3/dnuUyXd87plScSNXSbm0/RxMvObpI17sv/EDTGrGZkA==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.68.0.tgz", + "integrity": "sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.0", - "@typescript-eslint/visitor-keys": "8.61.0" + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3239,9 +3335,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.0.tgz", - "integrity": "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.68.0.tgz", + "integrity": "sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==", "dev": true, "license": "MIT", "engines": { @@ -3256,15 +3352,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.0.tgz", - "integrity": "sha512-TuBiQYIkd97yBfInHCTKVYMbX4kvEmpOEuixIuzCU9p8BGT1SfyyO0d0IfDMbPIHcjn/hWnusUX5e8v5Xg+X8A==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.68.0.tgz", + "integrity": "sha512-X77zqoY1EjeWGs/0JNxeaMfp5C5lIz4Tw8y66F1Ne8Faq6g424sBNYM6xBAqElfGZPLpWS+CZAp0DXyKDzWiHg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.0", - "@typescript-eslint/typescript-estree": "8.61.0", - "@typescript-eslint/utils": "8.61.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0", + "@typescript-eslint/utils": "8.68.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3281,9 +3377,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.0.tgz", - "integrity": "sha512-9QTQpZ5Iin4CdIodfbDQFSeiSJKidgYJYug1P9CC2xWgUTvlmixViqDZNciMjwLBZyJnG4tGmPl97rVAFb1AJg==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.68.0.tgz", + "integrity": "sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==", "devOptional": true, "license": "MIT", "peer": true, @@ -3296,16 +3392,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.0.tgz", - "integrity": "sha512-42zatd5qSvvcV1JdDBCLxYRznvP4eIHpPoZXdkPFnAmanA4FuZ5dibSnCBggY8hQnqajPpoGjXFdZ7fIJKQnlA==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.68.0.tgz", + "integrity": "sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.61.0", - "@typescript-eslint/tsconfig-utils": "8.61.0", - "@typescript-eslint/types": "8.61.0", - "@typescript-eslint/visitor-keys": "8.61.0", + "@typescript-eslint/project-service": "8.68.0", + "@typescript-eslint/tsconfig-utils": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/visitor-keys": "8.68.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3334,26 +3430,26 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -3363,9 +3459,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.3.tgz", - "integrity": "sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -3376,16 +3472,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.0.tgz", - "integrity": "sha512-3bzFt7ImFMW/jVYwJamDoe/dMOdFLSC6pom6rRjdh4SZJEYupyMzem8e7vKZLclLfpHjlwSAXOUxtKxGXUiLqA==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.68.0.tgz", + "integrity": "sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.61.0", - "@typescript-eslint/types": "8.61.0", - "@typescript-eslint/typescript-estree": "8.61.0" + "@typescript-eslint/scope-manager": "8.68.0", + "@typescript-eslint/types": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3400,13 +3496,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.0.tgz", - "integrity": "sha512-QVLZu3ZPQEE+HICQyAMZ2yLQhxf0meY/wx6Hx14YcTNj13JB3qHlX3lJ02L3fLGHgERRH71kvYDwiXIguT3AjQ==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.68.0.tgz", + "integrity": "sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.0", + "@typescript-eslint/types": "8.68.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3431,9 +3527,9 @@ } }, "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { @@ -3707,18 +3803,6 @@ "node": ">=14.0.0" } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", @@ -3730,17 +3814,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { "version": "1.12.2", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", @@ -3826,9 +3899,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "license": "MIT", "peer": true, "bin": { @@ -4111,9 +4184,9 @@ } }, "node_modules/axe-core": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.0.tgz", - "integrity": "sha512-FTavr/7Ba0IptwGOPxnQvdyW2tAsdLBMTBXz7rKH6xJ2skpyxpBxyHkDdBs4lf69yRqYpkqCdfhnwS8YULGOmg==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", + "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", "dev": true, "license": "MPL-2.0", "engines": { @@ -4148,9 +4221,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.34", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz", - "integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==", + "version": "2.11.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.19.tgz", + "integrity": "sha512-Grytf1xOxOEMTGRwx6rLGKkTabd4vMg3VrKdj/7joCmV0qgh4QwMMO6xh34YEXQqirAuUdgQGa5orJQQ+69RBw==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -4160,9 +4233,9 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", - "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -4184,9 +4257,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz", + "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==", "dev": true, "funding": [ { @@ -4205,11 +4278,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" + "baseline-browser-mapping": "^2.11.12", + "caniuse-lite": "^1.0.30001809", + "electron-to-chromium": "^1.5.402", + "node-releases": "^2.0.53", + "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" @@ -4279,9 +4352,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001797", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", - "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", "funding": [ { "type": "opencollective", @@ -4802,9 +4875,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.368", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz", - "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==", + "version": "1.5.415", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.415.tgz", + "integrity": "sha512-958V+Kbhtgz+SxXeEVKBjrlKRBIDAYvUJfwhjxMZ5S6ut9jAl7l9ZKBkBrvjyjZE36PabLUo2L8kEeV5O4vgJg==", "dev": true, "license": "ISC" }, @@ -4816,9 +4889,9 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.23.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.23.0.tgz", - "integrity": "sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==", + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", "dev": true, "license": "MIT", "dependencies": { @@ -4910,6 +4983,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -4931,9 +5023,9 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.2.tgz", - "integrity": "sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5001,15 +5093,18 @@ } }, "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", "dev": true, "license": "MIT", "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -5051,9 +5146,9 @@ } }, "node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -5063,32 +5158,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/escalade": { @@ -5115,9 +5210,10 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "peer": true, @@ -5127,8 +5223,8 @@ "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -5273,9 +5369,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.13.0.tgz", - "integrity": "sha512-bLohSkT6469rRs8czj0tLTD8vaeIS/whvPRJVjDr7IuoTT1k5DYDERlNycjDj/HkOlvQdYurmfZ/g3fG5bgeLQ==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", "dev": true, "license": "MIT", "dependencies": { @@ -5306,7 +5402,6 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -5490,9 +5585,9 @@ } }, "node_modules/esrap": { - "version": "2.2.11", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.11.tgz", - "integrity": "sha512-gPdx+I+BjYEinNMQaBXFjbaJVyoPMU4ZODg5mE+M4DqVG9VusAVHHjcBX+zqyITlI0DIARwDMMzZwAWj36dRoQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.3.6.tgz", + "integrity": "sha512-yc0OC12UjPqLoc+fe+v5GNs4TOjAigUw3sTikfC+xeBPGUw7gDRz3DtYaqEhxyMVJojcSWJw7jT0QWR+CbuE/A==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" @@ -5569,9 +5664,9 @@ } }, "node_modules/estree-util-scope": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", - "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.1.tgz", + "integrity": "sha512-B0np3dcdxqILX5e9nEi5/Fr4K7gL4oYFVPV1zRa2e9wRCbQoZZNWOZFYyoInvXUPJXBXjss+QXlWLJChDEHDkA==", "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -5784,9 +5879,9 @@ } }, "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -5807,12 +5902,12 @@ } }, "node_modules/framer-motion": { - "version": "12.40.0", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.40.0.tgz", - "integrity": "sha512-uaBd3qC1v3KQqBEjwTUd183K6PbS+j0yR9w9VmEOLWA/tnUcSn8Xa3uck7t4dgpDoUss8xQTcj8W2L07lrnLFg==", + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.43.0.tgz", + "integrity": "sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==", "license": "MIT", "dependencies": { - "motion-dom": "^12.40.0", + "motion-dom": "^12.43.0", "motion-utils": "^12.39.0", "tslib": "^2.4.0" }, @@ -6106,18 +6201,21 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6223,9 +6321,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", - "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", "dev": true, "license": "MIT", "dependencies": { @@ -6774,9 +6872,9 @@ } }, "node_modules/is-bun-module/node_modules/semver": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.3.tgz", - "integrity": "sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { @@ -6860,6 +6958,22 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7189,9 +7303,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "funding": [ { "type": "github", @@ -7635,9 +7749,9 @@ } }, "node_modules/lucide-react": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.17.0.tgz", - "integrity": "sha512-9FA9evdox/JQL5PT57fdA1x/yg8T7knJ98+zjTL3UfKza6pflQUUh3XtaQIHKvnsJw1lmsEyHVlt5jchYxOQ5w==", + "version": "1.34.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.34.0.tgz", + "integrity": "sha512-vnjGJNI7Htk5+oWW8gXGuaLgwgAb0T6/iZbBrp9JCfRFwdNWZ0YTm3eyxjOLgwN6r8iyAf3UA70zNmBRBNv7yg==", "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -8762,12 +8876,12 @@ } }, "node_modules/motion": { - "version": "12.40.0", - "resolved": "https://registry.npmjs.org/motion/-/motion-12.40.0.tgz", - "integrity": "sha512-yjrHUrBFW6kQvjJwRsoiPSAhC5tRwRqNGJWmiJ4CrGnbKp0V88AdzkhBmDoqIsIPfarOe0Uddd37Xq43/gIocA==", + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.43.0.tgz", + "integrity": "sha512-BQgQbSa9Hn3/mtbib0MK53y6JSANa+YKUKlaYnWzAVDH424RYQ5LVpV3pNiWH00BA2z4ojsSdMzqT7g2FQwjuQ==", "license": "MIT", "dependencies": { - "framer-motion": "^12.40.0", + "framer-motion": "^12.43.0", "tslib": "^2.4.0" }, "peerDependencies": { @@ -8788,9 +8902,9 @@ } }, "node_modules/motion-dom": { - "version": "12.40.0", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.40.0.tgz", - "integrity": "sha512-HxU3ZaBwNPVQUBQf1xxgq+7JrPNZvjLVxgbpEZL7RrWJnsxOf0/OM+yrHG9ogLQ31Do/r57Oz2gQWPK+6q62mg==", + "version": "12.43.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.43.0.tgz", + "integrity": "sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==", "license": "MIT", "dependencies": { "motion-utils": "^12.39.0" @@ -8809,9 +8923,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.12", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", - "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "funding": [ { "type": "github", @@ -8942,9 +9056,9 @@ } }, "node_modules/node-exports-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", - "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", "dev": true, "license": "MIT", "dependencies": { @@ -8961,9 +9075,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.47", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", - "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "version": "2.0.53", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", + "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==", "dev": true, "license": "MIT", "engines": { @@ -9129,13 +9243,14 @@ } }, "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.6", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" }, @@ -9262,9 +9377,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "license": "MIT", "engines": { "node": ">=12" @@ -9284,9 +9399,9 @@ } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -9304,7 +9419,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -9376,9 +9491,9 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", "license": "MIT", "peer": true, "engines": { @@ -9386,16 +9501,16 @@ } }, "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", "license": "MIT", "peer": true, "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.7" + "react": "^19.2.8" } }, "node_modules/react-is": { @@ -9475,9 +9590,9 @@ } }, "node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz", + "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==", "license": "MIT", "engines": { "node": ">= 20.19.0" @@ -10002,9 +10117,9 @@ } }, "node_modules/sharp/node_modules/semver": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.3.tgz", - "integrity": "sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "optional": true, "bin": { @@ -10038,19 +10153,19 @@ } }, "node_modules/shiki": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.2.0.tgz", - "integrity": "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.3.tgz", + "integrity": "sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==", "license": "MIT", "dependencies": { - "@shikijs/core": "4.2.0", - "@shikijs/engine-javascript": "4.2.0", - "@shikijs/engine-oniguruma": "4.2.0", - "@shikijs/langs": "4.2.0", - "@shikijs/themes": "4.2.0", - "@shikijs/types": "4.2.0", + "@shikijs/core": "4.4.3", + "@shikijs/engine-javascript": "4.4.3", + "@shikijs/engine-oniguruma": "4.4.3", + "@shikijs/langs": "4.4.3", + "@shikijs/themes": "4.4.3", + "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" + "@types/hast": "^3.0.5" }, "engines": { "node": ">=20" @@ -10410,9 +10525,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", - "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz", + "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==", "devOptional": true, "license": "MIT", "peer": true @@ -10432,9 +10547,9 @@ } }, "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "license": "MIT", "engines": { "node": ">=18" @@ -10641,16 +10756,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.61.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.0.tgz", - "integrity": "sha512-8y31Rd0eGTrDKqhy6vT0HtzhN+YLjQizwX3aA3hPXP/ynSfnrBXcQY5IzsP9/DM7+klX4IUncZZjkchP0z+rUw==", + "version": "8.68.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.68.0.tgz", + "integrity": "sha512-MHy0Y0ynqeEbx/S45+i/bBssdy3X6KNBfmJAP35GrgtNxu2TQ5K5xsFDhAnmsq1jvpdoZOPG1LGtJo0HWqYCrQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.61.0", - "@typescript-eslint/parser": "8.61.0", - "@typescript-eslint/typescript-estree": "8.61.0", - "@typescript-eslint/utils": "8.61.0" + "@typescript-eslint/eslint-plugin": "8.68.0", + "@typescript-eslint/parser": "8.68.0", + "@typescript-eslint/typescript-estree": "8.68.0", + "@typescript-eslint/utils": "8.68.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10843,9 +10958,9 @@ } }, "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==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz", + "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==", "dev": true, "funding": [ {