diff --git a/pages/_meta.ts b/pages/_meta.ts index b700a1c..f9d9f81 100644 --- a/pages/_meta.ts +++ b/pages/_meta.ts @@ -26,6 +26,10 @@ const meta: Meta = { title: "Runtime", type: "page", }, + blueprints: { + title: "Blueprints", + type: "page", + }, protocol: { type: "menu", title: "Protocol", @@ -68,6 +72,10 @@ const meta: Meta = { title: "Protocol", type: "page", }, + "release-notes": { + title: "Release Notes", + type: "page", + }, }; export default meta; diff --git a/pages/blueprints/_meta.ts b/pages/blueprints/_meta.ts new file mode 100644 index 0000000..22f1922 --- /dev/null +++ b/pages/blueprints/_meta.ts @@ -0,0 +1,25 @@ +import type { Meta } from "nextra"; + +const meta: Meta = { + index: "Overview", + "protocol-model": "Protocol Model", + "operator-matrix": "Operator Matrix", + "dapp-integration": "Dapp Integration", + "-- sandbox": { + type: "separator", + title: "AI Agent Sandbox", + }, + "ai-agent-sandbox": "AI Agent Sandbox", + "-- trading": { + type: "separator", + title: "AI Trading", + }, + "ai-trading": "AI Trading", + "-- surplus": { + type: "separator", + title: "Surplus Market", + }, + "surplus-market": "Surplus Market", +}; + +export default meta; diff --git a/pages/blueprints/ai-agent-sandbox/_meta.ts b/pages/blueprints/ai-agent-sandbox/_meta.ts new file mode 100644 index 0000000..9553c4f --- /dev/null +++ b/pages/blueprints/ai-agent-sandbox/_meta.ts @@ -0,0 +1,10 @@ +import type { Meta } from "nextra"; + +const meta: Meta = { + index: "Overview", + "operator-requirements": "Operator Requirements", + "runtime-and-harnesses": "Runtime and Harnesses", + "dapp-and-indexer": "Dapp and Indexer", +}; + +export default meta; diff --git a/pages/blueprints/ai-agent-sandbox/dapp-and-indexer.mdx b/pages/blueprints/ai-agent-sandbox/dapp-and-indexer.mdx new file mode 100644 index 0000000..2b65fce --- /dev/null +++ b/pages/blueprints/ai-agent-sandbox/dapp-and-indexer.mdx @@ -0,0 +1,41 @@ +--- +title: AI Agent Sandbox Dapp and Indexer +description: Dapp metadata, iframe policy, and indexer requirements for the AI Agent Sandbox blueprint. +--- + +# Dapp and Indexer + +The dapp should resolve the sandbox blueprint from first-party metadata, then render the hosted sandbox app through the trusted iframe path. + +## Metadata Contract + +| Field | Value | +| --------------------------------- | ----------------------------------------------- | +| `blueprintUi.displayName` | `AI Agent Sandbox` | +| `blueprintUi.requestedSlug` | `ai-agent-sandbox` | +| `blueprintUi.publisher.namespace` | `tangle` | +| `resources.serviceNoun` | `sandbox fleet` | +| `resources.resourceNoun` | `agent` | +| `externalApp.url` | `https://agent-sandbox.blueprint.tangle.tools/` | +| `externalApp.mode` | `iframe` | + +The metadata defines three modes: cloud, dedicated instance, and TEE instance. The dapp can use those mode records to keep the app surface stable while routing service creation to the right blueprint ID. + +## Iframe Policy + +The sandbox app requests read account access, chain switching, and message/contract permissions for local and Base Sepolia deployments. Popups are disabled. The dapp should still enforce the host allowlist and iframe app policy before rendering. + +## Indexed State + +The dapp needs indexed state for: + +| State | Why it matters | +| ------------------------------ | -------------------------------------------------------- | +| Blueprint creation and updates | Finds the sandbox blueprint and reads current metadata. | +| Operator registration | Shows available operators and capacity choices. | +| Service creation and approval | Identifies active sandbox fleets or dedicated instances. | +| Source acknowledgement | Confirms the operator accepted the runnable artifact. | +| Job events | Shows sandbox create/delete and workflow activity. | +| Quality-of-service heartbeats | Shows liveness and operator reliability. | + +The indexer should preserve enough service-instance and operator history for the app to distinguish "not provisioned yet" from "operator failed to report readiness." Live sandbox health still comes from the operator API and runtime checks. diff --git a/pages/blueprints/ai-agent-sandbox/index.mdx b/pages/blueprints/ai-agent-sandbox/index.mdx new file mode 100644 index 0000000..43a0a98 --- /dev/null +++ b/pages/blueprints/ai-agent-sandbox/index.mdx @@ -0,0 +1,44 @@ +--- +title: AI Agent Sandbox Blueprint +description: First-party Tangle blueprint for managed AI agent sandboxes. +--- + +# AI Agent Sandbox Blueprint + +The AI Agent Sandbox blueprint defines the template for isolated agent sandbox service instances on Tangle. Operators run the sandbox runtime and sidecar containers. Users create sandboxes, trigger workflows, attach terminals, inject secrets, and inspect execution through the hosted app. + +Source repo: https://github.com/tangle-network/ai-agent-sandbox-blueprint + +## Product Shape + +| Area | Source-backed behavior | +| ----------------- | --------------------------------------------------------------------------------------------------------- | +| Cloud mode | Multi-tenant fleet; callers create and delete sandboxes on demand through on-chain jobs. | +| Instance mode | One sandbox per service instance; the operator auto-provisions on startup and reports lifecycle directly. | +| TEE instance mode | Dedicated instance with TEE attestation and sealed secrets. | +| Hosted app | `https://agent-sandbox.blueprint.tangle.tools/` | +| Metadata identity | `publisher.namespace = "tangle"`, `requestedSlug = "ai-agent-sandbox"` | + +## On-Chain Jobs + +| ID | Job | Mode | What it does | +| --- | ------------------ | ------------------ | ------------------------------------------------------------ | +| `0` | `SANDBOX_CREATE` | Cloud | Creates a sandbox container, microVM, or TEE-backed sandbox. | +| `1` | `SANDBOX_DELETE` | Cloud | Deletes the sandbox and releases runtime resources. | +| `2` | `WORKFLOW_CREATE` | Cloud and instance | Registers a workflow template. | +| `3` | `WORKFLOW_TRIGGER` | Cloud and instance | Starts a registered workflow. | +| `4` | `WORKFLOW_CANCEL` | Cloud and instance | Cancels active workflow execution. | + +`JOB_WORKFLOW_TICK` is internal cron-driven workflow progress. It is not a public on-chain job. + +## Read Next + +| Page | Use it for | +| --------------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| [Operator Requirements](/blueprints/ai-agent-sandbox/operator-requirements) | Host sizing, env vars, Docker, Firecracker, TEE, ports. | +| [Runtime and Harnesses](/blueprints/ai-agent-sandbox/runtime-and-harnesses) | Sidecar capabilities, harness discovery, secrets, auth, runtime safety. | +| [Dapp and Indexer](/blueprints/ai-agent-sandbox/dapp-and-indexer) | Metadata, iframe policy, indexed events, service state. | + +## Source Material + +This page is based on the blueprint repo's `README.md`, `docs/runbook.md`, `metadata/blueprint-metadata.json`, `ai-agent-sandbox-blueprint-lib/src/lib.rs`, `sandbox-runtime/src/operator_api.rs`, and `TEE-GUIDE.md`. diff --git a/pages/blueprints/ai-agent-sandbox/operator-requirements.mdx b/pages/blueprints/ai-agent-sandbox/operator-requirements.mdx new file mode 100644 index 0000000..ab69dc4 --- /dev/null +++ b/pages/blueprints/ai-agent-sandbox/operator-requirements.mdx @@ -0,0 +1,50 @@ +--- +title: AI Agent Sandbox Operator Requirements +description: Host, runtime, and environment requirements for AI Agent Sandbox operators. +--- + +# Operator Requirements + +Operators run one of three binaries: sandbox cloud, dedicated instance, or TEE instance. All variants need a keystore, chain RPC, persistent state, and an authenticated operator API. + +## Common Environment + +| Variable | Purpose | +| ------------------------------------------------- | ------------------------------------------------------------------ | +| `KEYSTORE_URI` | Operator keystore path or URI. | +| `HTTP_RPC_ENDPOINT` or `RPC_URL` | Tangle EVM HTTP RPC endpoint. | +| `TANGLE_WS_URL` | WebSocket endpoint for event subscriptions. | +| `BLUEPRINT_STATE_DIR` | Persistent state for sandbox records, sessions, and metadata. | +| `SESSION_AUTH_SECRET` | 32+ byte secret for PASETO sessions and at-rest secret encryption. | +| `SANDBOX_UI_AUTH_MODE`, `SANDBOX_UI_BEARER_TOKEN` | Browser-facing UI ingress auth. | + +## Sandbox Mode + +| Requirement | Notes | +| ------------------- | ------------------------------------------------------------------------------ | +| Docker | Required for standard sidecar containers. | +| `SIDECAR_IMAGE` | Defaults to a sidecar-compatible all-harness image. | +| `OPERATOR_API_PORT` | Default `9100`. | +| `PUBLIC_HOST` | Set explicitly behind NAT or VPN; auto-detect is available for Tailscale IPv4. | + +## Firecracker Mode + +Firecracker runs through the in-process `microvm-runtime` driver. The operator binary is the Firecracker host; there is no separate host-agent service. + +| Variable | Purpose | +| -------------------------------- | -------------------------- | +| `MICROVM_FIRECRACKER_BIN` | Firecracker binary path. | +| `MICROVM_FIRECRACKER_KERNEL` | Linux kernel image. | +| `MICROVM_FIRECRACKER_ROOTFS` | Rootfs ext4 image. | +| `MICROVM_FIRECRACKER_SOCKET_DIR` | Per-VM API socket parent. | +| `MICROVM_FIRECRACKER_STATE_DIR` | Per-VM state directory. | +| `MICROVM_FIRECRACKER_VCPU` | Default vCPU count per VM. | +| `MICROVM_FIRECRACKER_MEM_MIB` | Default memory per VM. | + +The Firecracker path allocates TAP, vsock, per-VM rootfs clones, and host port forwarding. Operators must bake the guest metadata daemon into the rootfs so environment and sidecar auth tokens can be injected over vsock. + +## TEE Mode + +TEE instance mode supports provider-backed confidential runtimes such as Phala, AWS Nitro, GCP Confidential Space, Azure SKR, or direct operator-managed hardware. Configure the provider-specific credentials and set `TEE_BACKEND`. + +Current TEE verification should be treated as structural unless the deployment also pins quote-signature verification, expected sidecar measurements, and fresh client nonces. diff --git a/pages/blueprints/ai-agent-sandbox/runtime-and-harnesses.mdx b/pages/blueprints/ai-agent-sandbox/runtime-and-harnesses.mdx new file mode 100644 index 0000000..12f7d51 --- /dev/null +++ b/pages/blueprints/ai-agent-sandbox/runtime-and-harnesses.mdx @@ -0,0 +1,54 @@ +--- +title: AI Agent Sandbox Runtime and Harnesses +description: Sidecar capability and harness model for the AI Agent Sandbox blueprint. +--- + +# Runtime and Harnesses + +The sandbox blueprint is not one agent integration. The product contract is a sidecar-compatible sandbox plus runtime capabilities advertised by the operator. + +## Runtime Backends + +Sandbox creation selects a backend through `metadata_json.runtime_backend`. + +| Backend | Behavior | +| ------------- | --------------------------------------------------------------------------------------------- | +| `docker` | Default managed sidecar container path. | +| `firecracker` | MicroVM path; installs per-VM networking, rootfs, env injection, and sidecar token injection. | +| `tee` | Forces TEE provisioning and `tee_required=true`. | + +Firecracker and TEE are separate choices in the current release. Selecting `firecracker` forces `tee_required=false`. + +## Sidecar Capabilities + +Provisioning accepts `capabilities_json`, a JSON-encoded string array. Accepted values are injected into the sandbox as `SIDECAR_CAPABILITIES`. + +| Capability | Behavior | +| -------------- | -------------------------------------------------------------------------------------------------------- | +| `computer_use` | Starts browser/computer-use sidecar services for mouse, keyboard, screenshots, and related MCP surfaces. | +| `all_harness` | Requests the multi-harness sidecar image path. | + +The exact harness matrix comes from `GET /api/capabilities`. The current default all-harness image advertises multiple CLI agent harnesses. Operators should rely on capability discovery instead of treating any one harness as the integration. + +The current all-harness path is source-backed for Claude Code, Codex, OpenCode, Kimi, and Gemini. That is a sidecar runtime capability, not the blueprint's product identity. The blueprint identity is the sandbox service instance plus its operator API, lifecycle jobs, workflow state, and runtime isolation. + +## Operator API + +All data endpoints use EIP-191 challenge-response auth that issues PASETO v4.local session tokens. + +| Endpoint group | Examples | +| -------------------- | -------------------------------------------------------------------------------------------- | +| Auth | `POST /api/auth/challenge`, `POST /api/auth/session`, `DELETE /api/auth/session` | +| Sandbox operations | `exec`, `prompt`, `task`, `stop`, `resume`, `snapshot`, `ssh`, `secrets`, port proxy | +| Instance operations | Same control plane under `/api/sandbox/...` for singleton service instances | +| Health and discovery | `GET /health`, `GET /readyz`, `GET /metrics`, `GET /api/provisions`, `GET /api/capabilities` | + +## Secrets and Model Credentials + +Sandbox creation can succeed without model credentials. Agent prompts, tasks, and workflow steps fail only when the selected harness or model provider lacks its required secret. + +Operators should inject provider credentials as sandbox secrets or environment variables scoped to the service instance. The blueprint ABI stays stable as new sidecar images and sandbox SDK harnesses add support for more providers. + +## Runtime Safety + +The runtime enforces challenge-response auth, encrypted stored secrets, container hardening, rate limits, circuit breakers, session caps, and snapshot destination validation. Container hardening includes dropped capabilities, `no-new-privileges`, PID limits, localhost-bound ports, and restricted writable state. diff --git a/pages/blueprints/ai-trading/_meta.ts b/pages/blueprints/ai-trading/_meta.ts new file mode 100644 index 0000000..6c39eec --- /dev/null +++ b/pages/blueprints/ai-trading/_meta.ts @@ -0,0 +1,10 @@ +import type { Meta } from "nextra"; + +const meta: Meta = { + index: "Overview", + "operator-requirements": "Operator Requirements", + "runtime-and-risk": "Runtime and Risk", + "dapp-and-indexer": "Dapp and Indexer", +}; + +export default meta; diff --git a/pages/blueprints/ai-trading/dapp-and-indexer.mdx b/pages/blueprints/ai-trading/dapp-and-indexer.mdx new file mode 100644 index 0000000..c2ef547 --- /dev/null +++ b/pages/blueprints/ai-trading/dapp-and-indexer.mdx @@ -0,0 +1,46 @@ +--- +title: AI Trading Dapp and Indexer +description: Arena metadata, iframe policy, and indexed state for the AI Trading blueprint. +--- + +# Dapp and Indexer + +The dapp should resolve the AI Trading blueprint from first-party metadata and render the Arena app through the trusted iframe path. + +## Metadata Contract + +| Field | Value | +| --------------------------------- | ----------------------------------------------- | +| `blueprintUi.displayName` | `AI Trading Desk` | +| `blueprintUi.requestedSlug` | `ai-trading` | +| `blueprintUi.publisher.namespace` | `tangle` | +| `resources.serviceNoun` | `trading instance` | +| `resources.resourceNoun` | `bot` | +| `externalApp.url` | `https://trading-arena.blueprint.tangle.tools/` | +| `externalApp.mode` | `iframe` | + +The metadata includes overview cards for the risk gate, venue capabilities, and protocol adapters. It also declares actions for bot activation and pausing. + +## Dapp Behavior + +| Surface | Behavior | +| ----------------- | ----------------------------------------------------------------------------------------- | +| Blueprint route | Claim metadata with `requestedSlug = "ai-trading"` under the `tangle` namespace. | +| External app | Render the Arena iframe only after host and iframe policy checks pass. | +| Chain switching | Allow only the chain IDs declared in iframe policy. | +| Protocol fallback | Keep the generic blueprint detail route available for raw on-chain service-instance data. | + +## Indexed State + +The Arena and Tangle Cloud need: + +| State | Why it matters | +| -------------------------------------- | ------------------------------------------------------------------------ | +| Blueprint and source updates | Finds current binaries and metadata. | +| Operator registration and endpoint URL | Discovers live operators and their APIs. | +| Service instances | Shows bot fleets and dedicated trading instances. | +| Job events | Tracks provision, configure, start, stop, status, and workflow activity. | +| Pricing state | Shows operator quote surfaces and service-instance cost. | +| QoS heartbeats | Ranks and filters operators by liveness. | + +Because operator endpoint URLs are self-reported at registration time, the dapp should treat the indexer as discovery, not proof that an endpoint is honest or healthy. diff --git a/pages/blueprints/ai-trading/index.mdx b/pages/blueprints/ai-trading/index.mdx new file mode 100644 index 0000000..78ffd6f --- /dev/null +++ b/pages/blueprints/ai-trading/index.mdx @@ -0,0 +1,54 @@ +--- +title: AI Trading Blueprint +description: First-party Tangle blueprint for autonomous trading agents. +--- + +# AI Trading Blueprint + +The AI Trading blueprint defines the template for autonomous trading service instances with strategy workers, vault policy, and decentralized risk validation. Operators host bot sandboxes and trading APIs. Users create and monitor trading service instances through the Arena app. + +Source repo: https://github.com/tangle-network/ai-trading-blueprint + +## Product Shape + +| Area | Source-backed behavior | +| ----------------- | ---------------------------------------------------------------- | +| Cloud fleet | Multi-bot operator process for shared capacity. | +| Instance | One dedicated bot per service instance. | +| TEE instance | Single bot with confidential runtime selection. | +| Hosted app | `https://trading-arena.blueprint.tangle.tools/` | +| Metadata identity | `publisher.namespace = "tangle"`, `requestedSlug = "ai-trading"` | + +## Protocol Coverage + +| Category | Supported adapters | +| ---------- | ------------------------------ | +| Perpetuals | Hyperliquid, GMX v2, Vertex | +| DEX | Uniswap V3, Aerodrome | +| Lending | Aave V3, Morpho | +| Prediction | Polymarket | +| Execution | TWAP and statistical arbitrage | + +## Strategy Packs + +| Pack | Default cadence | Max turns | +| ------------ | --------------- | --------- | +| `prediction` | 15 minutes | 20 | +| `dex` | 5 minutes | 12 | +| `yield` | 15 minutes | 10 | +| `perp` | 2 minutes | 15 | +| `volatility` | 10 minutes | 12 | +| `mm` | 1 minute | 15 | +| `multi` | 5 minutes | 20 | + +## Read Next + +| Page | Use it for | +| --------------------------------------------------------------------- | -------------------------------------------------------------- | +| [Operator Requirements](/blueprints/ai-trading/operator-requirements) | VPS sizing, ports, install flow, service IDs, env vars. | +| [Runtime and Risk](/blueprints/ai-trading/runtime-and-risk) | Runtime backends, paper mode, AI cost, validation trust modes. | +| [Dapp and Indexer](/blueprints/ai-trading/dapp-and-indexer) | Arena metadata, iframe policy, indexed service state. | + +## Source Material + +This page is based on the blueprint repo's `README.md`, `OPERATORS.md`, `docs/runbook.md`, `metadata/blueprint-metadata.json`, `settings.env.example`, and trading runtime contract/risk code. diff --git a/pages/blueprints/ai-trading/operator-requirements.mdx b/pages/blueprints/ai-trading/operator-requirements.mdx new file mode 100644 index 0000000..b80547b --- /dev/null +++ b/pages/blueprints/ai-trading/operator-requirements.mdx @@ -0,0 +1,52 @@ +--- +title: AI Trading Operator Requirements +description: Host and configuration requirements for AI Trading blueprint operators. +--- + +# Operator Requirements + +Trading operators run one of three binaries and expose an operator API that the Arena discovers from on-chain registration. + +## Operator Shapes + +| Blueprint ID | Binary | Shape | +| ------------ | -------------------------------- | --------------------------------------------- | +| `13` | `trading-blueprint` | Cloud fleet; many bots per operator instance. | +| `14` | `trading-instance-blueprint` | One bot per service instance. | +| `15` | `trading-tee-instance-blueprint` | TEE instance; requires TEE hardware. | + +## Host Requirements + +| Resource | Minimum | Recommended | +| -------- | ------------------------------- | ---------------------------------------- | +| CPU | 2 vCPU | 4 vCPU | +| RAM | 4 GB | 8 GB | +| Disk | 40 GB SSD | 80 GB SSD | +| OS | Ubuntu 22.04 or 24.04 | Ubuntu 24.04 | +| Arch | x86_64 | x86_64 | +| Runtime | Docker 24+ | Docker 24+ with sidecar image pre-pulled | +| Network | Public IPv4, inbound 22 and 443 | Public IPv4, TLS, low-latency RPC | + +The public sidecar image is multi-GB and bots accumulate state, so disk sizing should include image cache, bot workspaces, logs, and chain metadata. + +## Required Service Configuration + +| Variable | Purpose | +| --------------------------------------------------------------------------------------------- | ----------------------------------------------------- | +| `SERVICE_ID` | Existing service instance. The binary exits if unset. | +| `BLUEPRINT_ID` | Blueprint variant being served. | +| `CHAIN_ID` | Base Sepolia is `84532`. | +| `HTTP_RPC_URL`, `WS_RPC_URL` | Chain reads, writes, and event watching. | +| `BLUEPRINT_STATE_DIR` | Persistent operator state. | +| `OPERATOR_PRIVATE_KEY` | Operator signer. | +| `VAULT_FACTORY`, `TRADE_VALIDATOR`, `POLICY_ENGINE`, `FEE_DISTRIBUTOR`, `ASSET_TOKEN_ADDRESS` | Trading contract addresses. | + +## Ports and Discovery + +| Port | Purpose | +| ------ | -------------------------------------------- | +| `9200` | Operator API default. | +| `9100` | Trading API default. | +| `443` | TLS public endpoint used by Arena discovery. | + +Operators register their public API URL on-chain. The Arena reads that registration and calls the operator API for bot state, pricing, and service-instance controls. diff --git a/pages/blueprints/ai-trading/runtime-and-risk.mdx b/pages/blueprints/ai-trading/runtime-and-risk.mdx new file mode 100644 index 0000000..1eb4c51 --- /dev/null +++ b/pages/blueprints/ai-trading/runtime-and-risk.mdx @@ -0,0 +1,48 @@ +--- +title: AI Trading Runtime and Risk +description: Runtime backend, safety, and AI cost model for AI Trading operators. +--- + +# Runtime and Risk + +Trading service instances use sandboxed bots, explicit runtime backends, paper-first defaults, and validation modes that control when capital can move. + +## Runtime Backend Selection + +Trading provision requests use `strategy_config_json.runtime_backend`. + +| Value | Behavior | +| ------------- | ------------------------------------------------------------------------------------- | +| `docker` | Standard bot sandbox path. | +| `firecracker` | Maps to sandbox runtime metadata; current provider wiring must be enabled before use. | +| `tee` | Confidential runtime selection; TEE instance blueprints pin this by default. | + +## Safety Knobs + +| Setting | Default | Why it matters | +| ---------------------------------- | ---------------------------------- | -------------------------------------------------------------------- | +| `TRADING_REQUESTER_ACCESS_MODE` | `allowlist` | Keeps bot creation limited to allowed addresses. | +| `TRADING_REQUESTER_ALLOWLIST` | Operator plus configured addresses | Defines who can request bots in allowlist mode. | +| `OPERATOR_MAX_CAPACITY` | `10` in installer templates | Caps concurrent bots. Unset or `0` means unlimited. | +| `DEFAULT_PAPER_TRADE` | `true` in templates | New bots use live data and simulated fills, with no on-chain trades. | +| `TRADING_ENABLE_DIRECT_BOT_CREATE` | `false` | Prevents unauthenticated direct bot creation. | + +## AI Spend + +Deterministic strategy ticks can run without model credentials. Agentic activation and chat require provider credentials. If an operator sets model keys, inference is billed to that operator's account. + +The repo supports credentials such as `ZAI_API_KEY`, `ANTHROPIC_API_KEY`, and `TANGLE_API_KEY`. Treat these as provider secrets for the selected bot harness, not as the product integration boundary. + +There is no built-in per-bot, per-day, or total LLM spend cap. Operators should enforce spend limits at the provider account, admission allowlist, and capacity levels. + +The trading product boundary is a bot with strategy config, vault policy, risk limits, paper/live mode, and performance history. Agent harnesses are runtime backends for agentic work; deterministic ticks and risk checks continue without a model key. + +## Validation Trust + +| Mode | Behavior | Latency | +| -------------- | ---------------------------------------------------------------------- | ------------------- | +| `PerTrade` | Validator EIP-712 signatures required per trade. | About 5-30 seconds. | +| `Envelope` | Depositor-approved bounds allow instant execution inside the envelope. | Near-zero. | +| `SelfOperated` | Local policy only; envelope still enforced. | Near-zero. | + +On-chain guards enforce token allowlists, position caps, leverage limits, rate limits, and replay protection through the policy and validator contracts. diff --git a/pages/blueprints/dapp-integration.mdx b/pages/blueprints/dapp-integration.mdx new file mode 100644 index 0000000..aca47c1 --- /dev/null +++ b/pages/blueprints/dapp-integration.mdx @@ -0,0 +1,66 @@ +--- +title: Blueprint Dapp Integration +description: How first-party blueprint apps, Tangle Cloud routing, indexer state, and local release gates fit together. +--- + +# Blueprint Dapp Integration + +Tangle Cloud should treat first-party blueprint apps as product surfaces backed by protocol state, not as hand-written one-off pages. + +## Routing Contract + +| Field | Purpose | +| --------------------------------- | --------------------------------------------------------------------------------------------------------- | +| `blueprintUi.publisher.namespace` | Identifies the publisher namespace. First-party entries use `tangle`. | +| `blueprintUi.requestedSlug` | Stable app slug such as `ai-agent-sandbox`, `ai-trading`, or `surplus`. | +| `blueprintUi.externalApp` | Declares whether the app renders as a trusted iframe or a trusted link-out. | +| Curated registry entry | Temporary or first-party override when on-chain metadata is incomplete or needs a hardened iframe policy. | +| Protocol fallback route | Raw indexed blueprint and service-instance view for debugging, operators, and auditors. | + +Sandbox and Trading are iframe-first apps under `*.blueprint.tangle.tools`. Surplus is currently a trusted link-out to `surplus-market.pages.dev` until it publishes equivalent rich `blueprintUi` metadata and iframe hosting. + +## Current First-Party App Surfaces + +| App | Slug | Surface mode | Why | +| ---------------- | ------------------ | -------------- | ------------------------------------------------------------------------------------------------ | +| AI Agent Sandbox | `ai-agent-sandbox` | Trusted iframe | The hosted sandbox app owns workflow, terminal, file, port, and secret UX. | +| AI Trading | `ai-trading` | Trusted iframe | The Arena owns bot creation, monitoring, risk settings, and operator API controls. | +| Surplus Market | `surplus` | Trusted link | The market app is live at `surplus-market.pages.dev`; rich iframe metadata is not yet published. | + +## Indexer Coverage + +The dapp needs indexed protocol state for discovery and routing: + +| State | Required for | +| ----------------------------------------------------- | ------------------------------------------------------------------------------------ | +| Blueprint creation and metadata updates | Catalog, product cards, app slug resolution, and fallback pages. | +| Source acknowledgement | Showing which runnable artifact an operator accepted. | +| Operator registration and endpoint metadata | Operator selection, endpoint routing, and reliability screens. | +| Service request, approval, extension, and termination | Service-instance lifecycle and customer history. | +| Job events | Provision, configure, start, stop, status, workflow, and settlement-trigger history. | +| Pricing and payment events | Operator quote surfaces, service cost, and fee reconciliation. | +| Quality-of-service heartbeats | Liveness filtering, operator rankings, and incident investigation. | + +The indexer does not prove endpoint honesty. It tells the app what the protocol saw. The app still needs operator API probes, settlement evidence, TEE verification, or blueprint-specific checks before making stronger claims. + +## Local Release Gates + +Before shipping dapp changes that touch staking, blueprint routing, iframe policy, or local deployment wiring, run the local gates against an Anvil testnet: + +| Gate | What it proves | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `yarn test:staking:local` | Contract path for deposit, delegate, rewards distribution, claim, unstake, and withdraw works against local protocol contracts. | +| `yarn test:wallet-flows:staking:gate` | Browser wallet-flow reports contain the required staking flows with no blocker partials. | +| `yarn test:staking:local-release` | Runs the contract-path gate and the browser-flow release gate together. | + +These gates are local proof, not deployment proof. A production release still needs the hosted dapp URL, docs URL, and indexer endpoint verified after deploy. + +## Integration Rules + +| Rule | Reason | +| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | +| Prefer metadata and iframe policy over bespoke React modules. | Keeps every blueprint on the same host contract. | +| Keep protocol fallback pages available. | Operators and auditors need raw state even when a hosted app is down. | +| Keep link-out mode for apps without a trusted iframe host. | Prevents widening iframe permissions before the publisher app is ready. | +| Show unavailable, pending, failed, and healthy as distinct states. | Users need to know whether the chain, indexer, operator API, or hosted app is the failing layer. | +| Treat local release gates as required for staking changes. | Staking regressions are expensive to discover through a deployed browser app. | diff --git a/pages/blueprints/index.mdx b/pages/blueprints/index.mdx new file mode 100644 index 0000000..b5b155c --- /dev/null +++ b/pages/blueprints/index.mdx @@ -0,0 +1,42 @@ +--- +title: Production Blueprints +description: First-party Tangle blueprints available to operators and app builders. +--- + +# Production Blueprints + +These blueprints are first-party capability templates built on the Tangle protocol and runtime stack. Each one defines software operators can run, on-chain service-instance state the dapp can index, and an application surface users can open from Tangle Cloud. + +Start with the [protocol model](/blueprints/protocol-model) if you are mapping the moving parts. A blueprint defines the template, operators register to serve it, users request service instances, and apps combine indexed protocol state with operator APIs. + +An indexer is the service that reads contract events and turns them into queryable app state. These blueprints expect the indexer to track blueprint creation, operator registration, service-instance creation, service extension, source acknowledgement, job activity, pricing, and quality-of-service heartbeats. + +Use the [operator matrix](/blueprints/operator-matrix) for host sizing, AI credential planning, runtime isolation, and proof boundaries across all three products. Use [dapp integration](/blueprints/dapp-integration) for Tangle Cloud routing, iframe/link mode, indexer state, and local release gates. + +## Blueprint Catalog + +| Blueprint | Capability template | Runtime model | App surface | +| ------------------------------------------------ | ------------------------------------------------------------------------------------ | --------------------------------------------------------- | -------------------------------------- | +| [AI Agent Sandbox](/blueprints/ai-agent-sandbox) | Service instances for agent sessions, workflow runs, terminals, files, and tool use | Docker, Firecracker microVM, or TEE-backed instance | `agent-sandbox.blueprint.tangle.tools` | +| [AI Trading](/blueprints/ai-trading) | Service instances for trading bots, risk checks, vault policy, and strategy workers | Cloud fleet, per-service instance, or TEE-backed instance | `trading-arena.blueprint.tangle.tools` | +| [Surplus Market](/blueprints/surplus-market) | Service instances for inference credit markets, order books, settlement, and serving | Operator HTTP service plus real inference backend | `surplus-market.pages.dev` | + +## Operator Planning + +| Question | Where to decide | +| ------------------------------------ | ------------------------------------------------------------------------------------------------------------- | +| What machine size do I need? | Start with [operator sizing](/operators/manager/sizing), then use the blueprint-specific resource tables. | +| Which isolation mode should I run? | Use [sandboxing and safety](/infrastructure/sandboxing) for runtime policy, then follow the blueprint page. | +| Which secrets are required? | Each blueprint page lists the required chain keys, provider credentials, inference backends, and app secrets. | +| How does the dapp route users? | The dapp uses indexed blueprint metadata plus first-party registry entries for curated app routes. | +| What state should be on-chain? | Use jobs for service-instance commands and contract/indexer/operator APIs for reads. | +| How do I compare blueprint products? | Use the [operator matrix](/blueprints/operator-matrix) for side-by-side host, AI, and proof requirements. | +| How do I prove the dapp still works? | Use [dapp integration](/blueprints/dapp-integration) for local release gates and routing rules. | + +## Source Repositories + +| Blueprint | Repository | +| ---------------- | ------------------------------------------------------------ | +| AI Agent Sandbox | https://github.com/tangle-network/ai-agent-sandbox-blueprint | +| AI Trading | https://github.com/tangle-network/ai-trading-blueprint | +| Surplus Market | https://github.com/tangle-network/surplus | diff --git a/pages/blueprints/operator-matrix.mdx b/pages/blueprints/operator-matrix.mdx new file mode 100644 index 0000000..e966b45 --- /dev/null +++ b/pages/blueprints/operator-matrix.mdx @@ -0,0 +1,52 @@ +--- +title: Blueprint Operator Matrix +description: Cross-blueprint host, AI, runtime, and proof requirements for Tangle blueprint operators. +--- + +# Blueprint Operator Matrix + +Use this page to compare the first-party blueprint products before sizing hosts, buying GPUs or inference credits, or publishing an operator endpoint. + +## Product Comparison + +| Blueprint | Operator job | Customer-facing resource | Default app | Runtime isolation | +| ---------------- | -------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------- | ----------------------------------------------------------------------------- | +| AI Agent Sandbox | Run isolated agent sandboxes and authenticated sidecar APIs. | Sandbox, workflow, terminal, files, ports, secrets, and snapshots. | `agent-sandbox.blueprint.tangle.tools` | Docker, Firecracker microVM, or TEE instance. | +| AI Trading | Run trading bots, strategy workers, risk checks, and trading APIs. | Bot, vault policy, strategy config, trade log, and operator report. | `trading-arena.blueprint.tangle.tools` | Docker sidecar by default; instance and TEE variants available. | +| Surplus Market | Run an inference-credit venue, quote market, serve redemption, and settle fills. | Credit lot, order book, RFQ, redemption receipt, and settlement batch. | `surplus-market.pages.dev` | Operator HTTP service plus controlled inference backend; settlement on-chain. | + +## Minimum Host Plan + +| Blueprint | Minimum | Recommended | Ports | Notes | +| ---------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| AI Agent Sandbox | Docker host with Rust 1.88+, Foundry, Node 22+/pnpm for UI work. | Separate persistent state volume, TLS ingress, explicit public host, pre-pulled all-harness sidecar image. | Operator API defaults around `9100`; sidecar HTTP and SSH are container-internal by default. | Firecracker hosts also need kernel, rootfs, TAP/vsock support, and guest metadata daemon. | +| AI Trading | 2 vCPU, 4 GB RAM, 40 GB SSD, Docker 24+, public IPv4. | 4 vCPU, 8 GB RAM, 80 GB SSD, TLS on 443, low-latency RPC. | Operator API `9200`, trading API `9100`, public TLS on `443`. | The sidecar image is multi-GB and each bot accumulates state. | +| Surplus Market | Rust operator, chain RPC, persistent book/outbox state, settlement submitter key. | Dedicated venue host, supervised process, private settlement key handling, real inference backend. | HTTP venue default `9100`; sidecar default `9110` for market-making. | Bonded issuers must back lots with inference they run or control. | + +## AI And Secret Requirements + +| Blueprint | Can run without model keys? | When model keys are needed | Secrets operators must protect | +| ---------------- | ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | +| AI Agent Sandbox | Yes for sandbox lifecycle and non-model commands. | Prompt, task, and workflow execution need the selected harness or model provider secret. | `SESSION_AUTH_SECRET`, sidecar auth tokens, provider keys, sandbox secrets, TEE provider credentials. | +| AI Trading | Yes for deterministic strategy ticks and paper trading. | Agentic activation, chat, and model-driven strategy work need provider keys such as `ZAI_API_KEY`, `ANTHROPIC_API_KEY`, or `TANGLE_API_KEY`. | Operator key, bot secrets, provider keys, vault and trading contract config, admission allowlist. | +| Surplus Market | Yes for the order book and dry-run venue. | Bonded credit redemption needs an inference backend: managed vLLM, external OpenAI-compatible API, or controlled provider capacity. | Operator attester key, submitter key, inference API key, router or ShieldedCredits credentials, venue state. | + +## Harness Reality + +Model harnesses as runtime capabilities, not as the product boundary. + +The sandbox product advertises runtime capabilities through `GET /api/capabilities`. Its current all-harness sidecar path includes Claude Code, Codex, OpenCode, Kimi, and Gemini. Operators should treat that endpoint as the live contract because the harness list can grow without changing the blueprint ABI. + +AI Trading uses the same sidecar direction for agentic work, but the trading product boundary is the bot, vault policy, strategy config, and risk gate. A harness is one execution backend for the bot's agentic mode, not the product itself. + +Surplus can run deterministic market-making through `@surplus/mm-loop`. Agentic quoting is a sidecar mode that uses the same risk gate before quotes reach the venue. + +## Proof Requirements + +| Blueprint | What the indexer can prove | What still needs runtime or settlement proof | +| ---------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| AI Agent Sandbox | Blueprint, operator, service, job, source, and heartbeat state. | Endpoint health, sandbox readiness, prompt output, secret handling, and TEE quote validity. | +| AI Trading | Blueprint registration, service instances, operator endpoint metadata, jobs, pricing pointers, and heartbeats. | Bot health, market execution, paper/live mode, model spend, and trade policy enforcement. | +| Surplus Market | Blueprint, operator, service, job, endpoint, and heartbeat state. | Order-book correctness, fill settlement, credit redemption, attester quorum, SP1 batch proof, and inference delivery. | + +The indexer is discovery infrastructure. Product surfaces should still use operator APIs, contract reads, attestation records, settlement receipts, or proof systems for claims that are not visible in Tangle protocol events. diff --git a/pages/blueprints/protocol-model.mdx b/pages/blueprints/protocol-model.mdx new file mode 100644 index 0000000..4b9dc6f --- /dev/null +++ b/pages/blueprints/protocol-model.mdx @@ -0,0 +1,65 @@ +--- +title: Blueprint Protocol Model +description: How Tangle blueprints, operators, service instances, jobs, apps, and indexers fit together. +--- + +# Blueprint Protocol Model + +A Tangle blueprint is a reusable capability definition. It does not run user workloads by itself. Operators register for a blueprint, users request service instances from registered operators, and those service instances execute the concrete workload. + +## Core Terms + +| Term | Plain meaning | +| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| Blueprint | The template: metadata, binaries, job definitions, pricing rules, and optional blueprint-specific contracts. | +| Operator registration | An operator's on-chain statement that it can serve a blueprint, usually with endpoint metadata, pricing, capacity, and source acknowledgement. | +| Service request | A user asks one or more registered operators to instantiate the blueprint for a concrete purpose. | +| Service instance | The live unit created from a service request. It has selected operators, service state, jobs, and runtime-specific resources. | +| Job | A command sent to a service instance. Jobs change state or ask operators to perform work. They are not the read path for normal app queries. | +| Operator API | The off-chain HTTP or RPC surface exposed by an operator runtime. Apps use it for live product state, controls, files, books, terminals, and quotes. | +| Indexer | The service that reads chain events and contract state, then turns them into queryable app state. It discovers state; it does not prove honesty. | +| Hosted app | The product UI for users. It is not the blueprint itself, even when it is first-party and launched from Tangle Cloud. | +| BSM | Blueprint Service Manager contract logic. When present, it handles blueprint-specific on-chain rules such as validation, payment, or slashing hooks. | + +## Lifecycle + +| Step | What happens | Who cares most | +| ---- | -------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| `1` | A developer publishes a blueprint definition, binaries, metadata, and optional BSM contracts. | Developers and auditors. | +| `2` | Operators register for the blueprint and expose their runtime endpoint, capacity, and pricing. | Operators, dapps, and indexers. | +| `3` | A user requests a service instance and selects the registered operators that should serve it. | Users and operators. | +| `4` | Operators run the runtime for that service instance and submit acknowledgements, results, or jobs. | Operators, users, and dispute or settlement logic. | +| `5` | Apps read indexed chain state plus operator APIs to present the product experience. | Users, support teams, and product surfaces. | + +## Audience Map + +| Audience | What they need to know | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Consumer | They request a concrete service instance and use the app or API output. They should not need to understand operator internals. | +| Developer | They define the blueprint, job schema, metadata, app policy, contracts, and any BSM or settlement logic. | +| Operator | They register for the blueprint, keep the runtime online, expose the right endpoint, manage keys and provider credentials, and meet the resource profile. | +| App builder | They use indexed protocol state for discovery and operator APIs for live product behavior. They should keep protocol fallback pages available for raw state. | +| Indexer owner | They must preserve enough event and state history to distinguish unavailable data, pending provisioning, operator failure, and normal service termination. | + +## Jobs And Reads + +Jobs are for service-instance commands: create a sandbox, trigger a workflow, list an instrument, start a trading bot, or report status. Reads should use the cheapest accurate source: + +| Need | Preferred read path | +| ---------------------------- | ------------------------------------------------------------------------------------------------------- | +| Blueprint and operator lists | Indexer over blueprint creation, source updates, operator registration, pricing, and endpoint metadata. | +| Current contract state | Direct contract call or indexed state, depending on latency and UX needs. | +| Live runtime state | Operator API, because terminals, files, books, quotes, and bot status are off-chain runtime state. | +| Settlement evidence | Blueprint-specific settlement contracts, attestation records, proofs, or venue APIs. | + +The indexer is a discovery layer. It can show which operators registered, which endpoints they published, which jobs were emitted, and which heartbeats landed. Endpoint health, response quality, model behavior, trade execution, and inference redemption still need runtime checks, app-level validation, or blueprint-specific proof paths. + +## Layer Boundaries + +| Do not conflate | Correct distinction | +| ----------------------------- | -------------------------------------------------------------------------------------------------------- | +| Blueprint vs service instance | The blueprint is the template. The service instance is the live runtime unit created from that template. | +| Operator vs hosted app | Operators run the runtime for selected service instances. The hosted app is the user's UI. | +| Indexer vs proof | The indexer reports protocol-visible state. It does not prove an endpoint is honest or healthy. | +| Job vs query | A job requests work or state change. A query reads state from contracts, the indexer, or operator APIs. | +| Sidecar vs BSM | A sidecar is runtime software. A BSM is contract-side service-management logic. | diff --git a/pages/blueprints/surplus-market/_meta.ts b/pages/blueprints/surplus-market/_meta.ts new file mode 100644 index 0000000..fbaa916 --- /dev/null +++ b/pages/blueprints/surplus-market/_meta.ts @@ -0,0 +1,10 @@ +import type { Meta } from "nextra"; + +const meta: Meta = { + index: "Overview", + "operator-requirements": "Operator Requirements", + "settlement-and-inference": "Settlement and Inference", + "dapp-and-indexer": "Dapp and Indexer", +}; + +export default meta; diff --git a/pages/blueprints/surplus-market/dapp-and-indexer.mdx b/pages/blueprints/surplus-market/dapp-and-indexer.mdx new file mode 100644 index 0000000..650ea28 --- /dev/null +++ b/pages/blueprints/surplus-market/dapp-and-indexer.mdx @@ -0,0 +1,44 @@ +--- +title: Surplus Market Dapp and Indexer +description: Dapp routing and indexer requirements for the Surplus Market blueprint. +--- + +# Dapp and Indexer + +Surplus currently has a hosted market app at `https://surplus-market.pages.dev/`. Tangle Cloud should expose it as a first-party link-out app until a dedicated `blueprint.tangle.tools` iframe host and iframe policy are deployed. + +## Metadata Contract + +The deployed metadata currently declares: + +| Field | Value | +| ----------------- | ------------------------------------------- | +| `name` | `surplus` | +| `category` | `inference-market` | +| `author` | `Tangle` | +| `code_repository` | `https://github.com/tangle-network/surplus` | + +Unlike Sandbox and Trading, this metadata does not yet carry a rich `blueprintUi` object. The dapp should keep the first-party registry entry for `surplus` until the repo publishes equivalent metadata on-chain. + +## Dapp Behavior + +| Surface | Behavior | +| ------------------ | ----------------------------------------------------------------------------------------------------- | +| Tangle Cloud route | Reserve the `surplus` slug and match first-party Surplus metadata. | +| Hosted app | Open `https://surplus-market.pages.dev/` as a trusted link, not an iframe. | +| Protocol fallback | Preserve the raw indexed blueprint and service-instance route for operator and settlement inspection. | + +## Indexed State + +The indexer should expose: + +| State | Why it matters | +| ------------------------------------- | -------------------------------------------------------- | +| Blueprint creation and source updates | Finds the Surplus operator binary and metadata. | +| Operator registration | Discovers venues and issuer/attester operators. | +| Service creation | Distinguishes independent books from shared CLOB books. | +| Job events | Tracks `list_instrument`, `status`, and `workflow_tick`. | +| Operator endpoint metadata | Lets the app discover venue endpoints. | +| QoS heartbeats | Shows venue liveness and reliability. | + +Settlement-specific fills, lots, redemption receipts, and attestation state live in Surplus settlement contracts and venue APIs. The Tangle indexer should at least preserve pointers so product surfaces can reconcile protocol service instances with market state. Endpoint liveness and venue honesty still need venue checks, settlement evidence, or proof paths. diff --git a/pages/blueprints/surplus-market/index.mdx b/pages/blueprints/surplus-market/index.mdx new file mode 100644 index 0000000..c572f73 --- /dev/null +++ b/pages/blueprints/surplus-market/index.mdx @@ -0,0 +1,55 @@ +--- +title: Surplus Market Blueprint +description: First-party Tangle blueprint for an AI inference credit market. +--- + +# Surplus Market Blueprint + +The Surplus Market blueprint defines the template for a prepaid AI inference credit market. Buyers acquire discounted inference capacity through service instances. Operators make markets in credit lots, serve or back redemption, and settle fills on-chain. + +Source repo: https://github.com/tangle-network/surplus + +## Product Shape + +| Area | Source-backed behavior | +| ------------------ | ----------------------------------------------------------------------------------------------------- | +| Operator binary | `surplus-operator` for the full Tangle runner; `surplus-operator-lite` for HTTP-only local operation. | +| Blueprint category | `inference-market` | +| Default port | `9100` | +| Hosted app | `https://surplus-market.pages.dev/` | +| Settlement | `SurplusSettlement` with attested quorum or SP1 proof boundary. | + +## Jobs + +| ID | Job | Status | +| ---- | ----------------- | --------------------------------------- | +| `0` | `list_instrument` | Implemented. | +| `1` | `configure` | Reserved placeholder. | +| `2` | `start_making` | Reserved placeholder. | +| `3` | `stop_making` | Reserved placeholder. | +| `4` | `status` | Implemented. | +| `30` | `workflow_tick` | Implemented; drives market-maker ticks. | + +## Operator API + +| Route | Purpose | +| -------------------------------- | --------------------------------------------------- | +| `POST /order` | Place limit orders and emit settlement intents. | +| `POST /cancel` | Cancel a resting order. | +| `POST /book` | Read depth, reference price, and inventory. | +| `POST /mm-tick` | Pull risk-gated quotes and replace operator quotes. | +| `POST /rfq` and `POST /rfq/fill` | Quote and fill signed firm RFQs. | +| `GET /settlement/outbox` | Read settleable fills. | +| `POST /settlement/flush` | Submit fills when chain settlement is enabled. | + +## Read Next + +| Page | Use it for | +| ------------------------------------------------------------------------------- | ------------------------------------------------------------- | +| [Operator Requirements](/blueprints/surplus-market/operator-requirements) | Env vars, CLOB setup, privacy mode, app env. | +| [Settlement and Inference](/blueprints/surplus-market/settlement-and-inference) | Real inference backend, credit lots, redemption, quorum, SP1. | +| [Dapp and Indexer](/blueprints/surplus-market/dapp-and-indexer) | Dapp link-out, metadata gaps, indexed market state. | + +## Source Material + +This page is based on the Surplus repo's `README.md`, `BLUEPRINT.md`, `blueprint.toml`, `deploy/blueprint-definition.toml`, `deploy/blueprint-metadata.json`, `docs/testnet-release.md`, `operator/src/config.rs`, and `operator/src/inference.rs`. diff --git a/pages/blueprints/surplus-market/operator-requirements.mdx b/pages/blueprints/surplus-market/operator-requirements.mdx new file mode 100644 index 0000000..8c265f8 --- /dev/null +++ b/pages/blueprints/surplus-market/operator-requirements.mdx @@ -0,0 +1,52 @@ +--- +title: Surplus Market Operator Requirements +description: Host and configuration requirements for Surplus Market operators. +--- + +# Operator Requirements + +Surplus operators run a market venue and, when bonded as issuers, must back credit lots with real inference capacity. + +## Required Runtime + +| Requirement | Notes | +| ------------------- | ------------------------------------------------------------------- | +| `surplus-operator` | Full Tangle runner built with the `blueprint` feature. | +| HTTP venue | Default port `9100`. | +| Settlement contract | `SURPLUS_SETTLEMENT_ADDR` and chain RPC. | +| Operator key | Signs RFQ quotes and batch attestations. | +| Submitter key | Pays gas and submits settlement transactions. | +| Persistent state | Order book, settlement outbox, fills, lots, and redemption records. | + +## Environment + +| Variable | Purpose | +| ------------------------- | ----------------------------------------------------------------- | +| `SURPLUS_CHAIN_ID` | EIP-712 settlement chain ID. | +| `SURPLUS_SETTLEMENT_ADDR` | `SurplusSettlement` contract address. | +| `SURPLUS_RPC_URL` | RPC endpoint for settlement submission. | +| `SURPLUS_OPERATOR_KEY` | Attester or issuer signing key. | +| `SURPLUS_SUBMITTER_KEY` | Hot transaction key. | +| `SURPLUS_SIDECAR_URL` | Market-maker sidecar URL; default `http://127.0.0.1:9110`. | +| `SURPLUS_INSTRUMENT` | Boot market, defaulting to an Anthropic output-credit instrument. | +| `SURPLUS_MM_SIZE` | Quote size; minimum `1000`, default `50000`. | + +## Shared Books and Privacy + +| Mode | Variables | +| ------------- | ----------------------------------------------------------------------- | +| Shared CLOB | `SURPLUS_CLOB_OPERATORS`, `SURPLUS_CLOB_BOOK`, `SURPLUS_CLOB_THRESHOLD` | +| Attester-only | `SURPLUS_ATTESTER_ONLY=1` | +| Tor privacy | `PRIVACY_MODE=tor`, `SURPLUS_TOR_SOCKS`, `SURPLUS_ONION_FILE` | + +The privacy layer uses Arti/Tor for outbound calls and `.onion` operator services. It is not custom cryptography. + +## App Environment + +The hosted app needs: + +| Variable | Purpose | +| ------------------------------- | -------------------------------------------------------- | +| `VITE_SURPLUS_VENUE_URL` | Default venue URL. | +| `VITE_TANGLE_ROUTER_URL` | Tangle Router endpoint. | +| `VITE_WALLETCONNECT_PROJECT_ID` | WalletConnect project ID; do not ship a shared fallback. | diff --git a/pages/blueprints/surplus-market/settlement-and-inference.mdx b/pages/blueprints/surplus-market/settlement-and-inference.mdx new file mode 100644 index 0000000..7ddb505 --- /dev/null +++ b/pages/blueprints/surplus-market/settlement-and-inference.mdx @@ -0,0 +1,45 @@ +--- +title: Surplus Market Settlement and Inference +description: Settlement, inference backend, and redemption model for the Surplus Market blueprint. +--- + +# Settlement and Inference + +Surplus is a market for inference credits, so the operator must prove two things: fills settle correctly and credits redeem against real inference capacity. + +## Inference Backend Selection + +The operator uses an OpenAI-compatible chat-completions interface. Backend selection is environment-driven. + +| Priority | Backend | Variables | +| -------- | ---------------------- | -------------------------------------------------------------------------------- | +| 1 | Managed vLLM | `SURPLUS_VLLM_MODEL`, optional `SURPLUS_VLLM_PORT`, optional `SURPLUS_VLLM_ARGS` | +| 2 | External inference API | `SURPLUS_INFERENCE_URL`, optional `SURPLUS_INFERENCE_API_KEY` | +| 3 | Router fallback | `SURPLUS_ROUTER_URL`, `SURPLUS_ROUTER_API_KEY` | + +Router fallback is for non-bonded testing. A bonded issuer must not use router mode; the operator fails closed because a lot must be backed by inference it runs or controls. + +## Settlement Spine + +| Component | Purpose | +| ------------------- | ------------------------------------------------------------------------------- | +| `SurplusSettlement` | Clears signed fills atomically and mints collateral-backed credit lots. | +| Attester quorum | Compresses batch verification through operator signatures. | +| SP1 proof | Optional proof path for batch verification when volume warrants it. | +| Redemption receipt | Lets holders prove delivery or trigger collateral default. | +| `SurplusBSM` | Connects redemption defaults to Tangle slashing on top of settlement penalties. | + +## Verified Paths + +The testnet release docs require these checks before and after deploy: + +| Check | What it proves | +| ---------------------------------- | ------------------------------------------------------------------------------------------- | +| `scripts/settlement-e2e.sh` | Atomic fill, receipt redemption, collateral default, attested batch, proven batch. | +| `scripts/spend-e2e.sh` | API-key mint, OpenAI-compatible calls, streaming, on-chain billing, forged voucher refusal. | +| `scripts/prove-batch.sh execute` | SP1 guest public values match host recomputation. | +| `forge test` and Rust parity tests | Contract invariants and digest parity. | + +## Market-Making Loop + +The market-making sidecar can run deterministic Avellaneda-Stoikov quoting or a sandboxed agent strategy. The risk desk is fail-closed and validates quote size, inventory, deviation, spread, and drawdown before the operator places quotes. diff --git a/pages/developers/api/reference/IBlueprintServiceManager.mdx b/pages/developers/api/reference/IBlueprintServiceManager.mdx index 91a4cb6..6d206d4 100644 --- a/pages/developers/api/reference/IBlueprintServiceManager.mdx +++ b/pages/developers/api/reference/IBlueprintServiceManager.mdx @@ -186,6 +186,63 @@ _Defines minimum commitment and exit queue timing_ | exitQueueDuration | uint64 | Time between scheduling exit and completing it (seconds) | | forceExitAllowed | bool | Whether service owner can force-exit operators | +#### getNonPaymentTerminationPolicy + +```solidity +function getNonPaymentTerminationPolicy(uint64 serviceId) external view returns (bool useDefault, uint64 graceIntervals) +``` + +Get the non-payment termination policy for subscription services. + +Core computes eligibility as +`lastPaymentAt + subscriptionInterval + (subscriptionInterval * graceIntervals)`. +`graceIntervals = 0` means termination is eligible immediately at the first missed +billing tick. Implementations should return `useDefault = true` unless they need +custom grace behavior. Default implementation in `BlueprintServiceManagerBase` returns +`(true, 0)`. + +##### Parameters + +| Name | Type | Description | +| --------- | ------ | -------------- | +| serviceId | uint64 | The service ID | + +##### Return Values + +| Name | Type | Description | +| -------------- | ------ | ---------------------------------------------------------------- | +| useDefault | bool | True to use the protocol default policy | +| graceIntervals | uint64 | Additional full intervals to wait after the first missed payment | + +#### forceRemoveAllowsBelowMin + +```solidity +function forceRemoveAllowsBelowMin(uint64 serviceId) external view returns (bool ok) +``` + +Whether `forceRemoveOperator` may drop the service below `minOperators`. + +By default the protocol enforces `operatorCount > minOperators` even when a blueprint +manager calls `forceRemoveOperator`. A blueprint that genuinely needs +emergency-eviction-below-min must self-document by returning `true`. Reverting or +unimplemented => the protocol enforces the floor (fail-closed). The default +implementation in `BlueprintServiceManagerBase` returns `false`; custom managers that +do **not** inherit `BlueprintServiceManagerBase` MUST implement this hook explicitly +or `forceRemoveOperator` will revert as soon as the eviction would push the operator +count below `minOperators`. + +##### Parameters + +| Name | Type | Description | +| --------- | ------ | -------------- | +| serviceId | uint64 | The service ID | + +##### Return Values + +| Name | Type | Description | +| ---- | ---- | ------------------------------------------------------- | +| ok | bool | True to allow eviction below the minimum operator count | + #### onRequest ```solidity diff --git a/pages/developers/api/reference/IMBSMRegistry.mdx b/pages/developers/api/reference/IMBSMRegistry.mdx index 4d8e110..49859fd 100644 --- a/pages/developers/api/reference/IMBSMRegistry.mdx +++ b/pages/developers/api/reference/IMBSMRegistry.mdx @@ -89,7 +89,13 @@ Get the latest revision number registered in the registry function pinBlueprint(uint64 blueprintId, uint32 revision) external ``` -Pin a blueprint to a specific revision (0 disallowed) +Pin a blueprint to a specific revision (0 disallowed). + +Reverts if `revision` is currently inside the deprecation grace window. Pinning a blueprint +to a revision that is itself deprecated would defeat the deprecation flow: `getMBSM` would +return `address(0)` the moment `completeDeprecation` ran, breaking every BSM call for the +pinned blueprint. Choose a revision that is not scheduled for deprecation, or wait until +the deprecation has fully completed before pinning to a different revision. #### unpinBlueprint diff --git a/pages/developers/api/reference/ITangleSlashing.mdx b/pages/developers/api/reference/ITangleSlashing.mdx index 2b93591..efc9a65 100644 --- a/pages/developers/api/reference/ITangleSlashing.mdx +++ b/pages/developers/api/reference/ITangleSlashing.mdx @@ -11,12 +11,15 @@ Source: https://github.com/tangle-network/tnt-core/blob/main/src/interfaces/ITan Slashing interface for Tangle protocol +The event declarations on this interface mirror what the protocol actually emits from `SlashingLib`. +Off-chain consumers (Rust bindings, indexers) MUST decode against the shapes documented below. + #### Functions #### proposeSlash ```solidity -function proposeSlash(uint64 serviceId, address operator, uint256 amount, bytes32 evidence) external returns (uint64 slashId) +function proposeSlash(uint64 serviceId, address operator, uint16 slashBps, bytes32 evidence) external returns (uint64 slashId) ``` Propose a slash against an operator @@ -27,8 +30,8 @@ Propose a slash against an operator | --------- | ------- | ------------------------------------ | | serviceId | uint64 | The service where violation occurred | | operator | address | The operator to slash | -| amount | uint256 | Amount to slash | -| evidence | bytes32 | Evidence hash | +| slashBps | uint16 | Slash percentage in basis points | +| evidence | bytes32 | Evidence hash (must be non-zero) | ##### Return Values @@ -39,10 +42,14 @@ Propose a slash against an operator #### disputeSlash ```solidity -function disputeSlash(uint64 slashId, string reason) external +function disputeSlash(uint64 slashId, string reason) external payable ``` -Dispute a slash proposal +Dispute a slash proposal. + +`payable` because the implementation requires `msg.value == config.disputeBond` when the bond +is non-zero (and zero otherwise). Typed callers MUST use a payable reference so +`disputeSlash{value: bond}(...)` compiles. #### executeSlash @@ -79,11 +86,22 @@ Cancel a slash proposal #### setSlashConfig ```solidity -function setSlashConfig(uint64 disputeWindow, bool instantSlashEnabled, uint16 maxSlashBps) external +function setSlashConfig(uint64 disputeWindow, bool instantSlashEnabled, uint16 maxSlashBps, uint64 disputeResolutionDeadline, uint256 disputeBond, uint16 maxPendingSlashesPerOperator) external ``` Update slashing configuration +##### Parameters + +| Name | Type | Description | +| ---------------------------- | ------- | ---------------------------------------------------------------- | +| disputeWindow | uint64 | Time after `proposeSlash` during which the operator can dispute | +| instantSlashEnabled | bool | Reserved emergency toggle (no effect through the standard API) | +| maxSlashBps | uint16 | Hard cap on any single slash proposal | +| disputeResolutionDeadline | uint64 | Time `SLASH_ADMIN` has to resolve a dispute before it auto-fails | +| disputeBond | uint256 | Native asset bond required to dispute (0 = disabled) | +| maxPendingSlashesPerOperator | uint16 | Cap on concurrent pending slashes per operator (anti-spam) | + #### getSlashProposal ```solidity @@ -92,16 +110,93 @@ function getSlashProposal(uint64 slashId) external view returns (struct Slashing Get slash proposal details +#### getSlashConfig + +```solidity +function getSlashConfig() external view returns (struct SlashingLib.SlashConfig) +``` + +Get the current slashing configuration. Returns the live `SlashConfig` tuple containing +`disputeWindow`, `instantSlashEnabled`, `maxSlashBps`, `disputeResolutionDeadline`, +`disputeBond`, and `maxPendingSlashesPerOperator`. + #### Events #### SlashProposed ```solidity -event SlashProposed(uint64 serviceId, address operator, uint256 amount, bytes32 evidence) +event SlashProposed(uint64 indexed slashId, uint64 indexed serviceId, address indexed operator, address proposer, uint16 slashBps, uint16 effectiveSlashBps, bytes32 evidence, uint64 executeAfter) ``` +Emitted when a new slash proposal is created. + +##### Parameters + +| Name | Type | Description | +| ----------------- | ------- | ----------------------------------------------------------------------- | +| slashId | uint64 | The new slash ID (indexed) | +| serviceId | uint64 | The service where the violation occurred (indexed) | +| operator | address | The slashed operator (indexed) | +| proposer | address | The address that called `proposeSlash` | +| slashBps | uint16 | Requested slash percentage in basis points | +| effectiveSlashBps | uint16 | Slash percentage after exposure scaling (what will actually be applied) | +| evidence | bytes32 | Evidence hash (non-zero, enforced by `proposeSlash`) | +| executeAfter | uint64 | Earliest UNIX timestamp at which the slash can be executed | + +#### SlashDisputed + +```solidity +event SlashDisputed(uint64 indexed slashId, address indexed disputer, string reason) +``` + +Emitted when a slash proposal is disputed by the operator or by `SLASH_ADMIN_ROLE`. + +##### Parameters + +| Name | Type | Description | +| -------- | ------- | ------------------------------------------ | +| slashId | uint64 | The disputed slash ID (indexed) | +| disputer | address | The address that called `disputeSlash` | +| reason | string | Human-readable rationale (free-form input) | + #### SlashExecuted ```solidity -event SlashExecuted(uint64 serviceId, address operator, uint256 amount) +event SlashExecuted(uint64 indexed slashId, uint64 indexed serviceId, address indexed operator, uint256 actualSlashed) +``` + +Emitted when a slash is executed. + +##### Parameters + +| Name | Type | Description | +| ------------- | ------- | -------------------------------------------------- | +| slashId | uint64 | The executed slash ID (indexed) | +| serviceId | uint64 | The service the slash was applied to (indexed) | +| operator | address | The slashed operator (indexed) | +| actualSlashed | uint256 | Total stake actually burned in the underlying call | + +#### SlashCancelled + +```solidity +event SlashCancelled(uint64 indexed slashId, address indexed canceller, string reason) ``` + +Emitted when a slash proposal is cancelled by `SLASH_ADMIN_ROLE`. + +##### Parameters + +| Name | Type | Description | +| --------- | ------- | ------------------------------------------- | +| slashId | uint64 | The cancelled slash ID (indexed) | +| canceller | address | Address that called `cancelSlash` (indexed) | +| reason | string | Human-readable rationale (free-form input) | + +#### SlashConfigUpdated + +```solidity +event SlashConfigUpdated(uint64 disputeWindow, bool instantSlashEnabled, uint16 maxSlashBps, uint64 disputeResolutionDeadline, uint256 disputeBond, uint16 maxPendingSlashesPerOperator) +``` + +Emitted when `setSlashConfig` updates the slashing configuration. The full new +configuration is included in the event. diff --git a/pages/developers/api/reference/generated/IBlueprintServiceManager.mdx b/pages/developers/api/reference/generated/IBlueprintServiceManager.mdx index 91a4cb6..6d206d4 100644 --- a/pages/developers/api/reference/generated/IBlueprintServiceManager.mdx +++ b/pages/developers/api/reference/generated/IBlueprintServiceManager.mdx @@ -186,6 +186,63 @@ _Defines minimum commitment and exit queue timing_ | exitQueueDuration | uint64 | Time between scheduling exit and completing it (seconds) | | forceExitAllowed | bool | Whether service owner can force-exit operators | +#### getNonPaymentTerminationPolicy + +```solidity +function getNonPaymentTerminationPolicy(uint64 serviceId) external view returns (bool useDefault, uint64 graceIntervals) +``` + +Get the non-payment termination policy for subscription services. + +Core computes eligibility as +`lastPaymentAt + subscriptionInterval + (subscriptionInterval * graceIntervals)`. +`graceIntervals = 0` means termination is eligible immediately at the first missed +billing tick. Implementations should return `useDefault = true` unless they need +custom grace behavior. Default implementation in `BlueprintServiceManagerBase` returns +`(true, 0)`. + +##### Parameters + +| Name | Type | Description | +| --------- | ------ | -------------- | +| serviceId | uint64 | The service ID | + +##### Return Values + +| Name | Type | Description | +| -------------- | ------ | ---------------------------------------------------------------- | +| useDefault | bool | True to use the protocol default policy | +| graceIntervals | uint64 | Additional full intervals to wait after the first missed payment | + +#### forceRemoveAllowsBelowMin + +```solidity +function forceRemoveAllowsBelowMin(uint64 serviceId) external view returns (bool ok) +``` + +Whether `forceRemoveOperator` may drop the service below `minOperators`. + +By default the protocol enforces `operatorCount > minOperators` even when a blueprint +manager calls `forceRemoveOperator`. A blueprint that genuinely needs +emergency-eviction-below-min must self-document by returning `true`. Reverting or +unimplemented => the protocol enforces the floor (fail-closed). The default +implementation in `BlueprintServiceManagerBase` returns `false`; custom managers that +do **not** inherit `BlueprintServiceManagerBase` MUST implement this hook explicitly +or `forceRemoveOperator` will revert as soon as the eviction would push the operator +count below `minOperators`. + +##### Parameters + +| Name | Type | Description | +| --------- | ------ | -------------- | +| serviceId | uint64 | The service ID | + +##### Return Values + +| Name | Type | Description | +| ---- | ---- | ------------------------------------------------------- | +| ok | bool | True to allow eviction below the minimum operator count | + #### onRequest ```solidity diff --git a/pages/developers/api/reference/generated/IMBSMRegistry.mdx b/pages/developers/api/reference/generated/IMBSMRegistry.mdx index 4d8e110..49859fd 100644 --- a/pages/developers/api/reference/generated/IMBSMRegistry.mdx +++ b/pages/developers/api/reference/generated/IMBSMRegistry.mdx @@ -89,7 +89,13 @@ Get the latest revision number registered in the registry function pinBlueprint(uint64 blueprintId, uint32 revision) external ``` -Pin a blueprint to a specific revision (0 disallowed) +Pin a blueprint to a specific revision (0 disallowed). + +Reverts if `revision` is currently inside the deprecation grace window. Pinning a blueprint +to a revision that is itself deprecated would defeat the deprecation flow: `getMBSM` would +return `address(0)` the moment `completeDeprecation` ran, breaking every BSM call for the +pinned blueprint. Choose a revision that is not scheduled for deprecation, or wait until +the deprecation has fully completed before pinning to a different revision. #### unpinBlueprint diff --git a/pages/developers/api/reference/generated/ITangleSlashing.mdx b/pages/developers/api/reference/generated/ITangleSlashing.mdx index 2b93591..efc9a65 100644 --- a/pages/developers/api/reference/generated/ITangleSlashing.mdx +++ b/pages/developers/api/reference/generated/ITangleSlashing.mdx @@ -11,12 +11,15 @@ Source: https://github.com/tangle-network/tnt-core/blob/main/src/interfaces/ITan Slashing interface for Tangle protocol +The event declarations on this interface mirror what the protocol actually emits from `SlashingLib`. +Off-chain consumers (Rust bindings, indexers) MUST decode against the shapes documented below. + #### Functions #### proposeSlash ```solidity -function proposeSlash(uint64 serviceId, address operator, uint256 amount, bytes32 evidence) external returns (uint64 slashId) +function proposeSlash(uint64 serviceId, address operator, uint16 slashBps, bytes32 evidence) external returns (uint64 slashId) ``` Propose a slash against an operator @@ -27,8 +30,8 @@ Propose a slash against an operator | --------- | ------- | ------------------------------------ | | serviceId | uint64 | The service where violation occurred | | operator | address | The operator to slash | -| amount | uint256 | Amount to slash | -| evidence | bytes32 | Evidence hash | +| slashBps | uint16 | Slash percentage in basis points | +| evidence | bytes32 | Evidence hash (must be non-zero) | ##### Return Values @@ -39,10 +42,14 @@ Propose a slash against an operator #### disputeSlash ```solidity -function disputeSlash(uint64 slashId, string reason) external +function disputeSlash(uint64 slashId, string reason) external payable ``` -Dispute a slash proposal +Dispute a slash proposal. + +`payable` because the implementation requires `msg.value == config.disputeBond` when the bond +is non-zero (and zero otherwise). Typed callers MUST use a payable reference so +`disputeSlash{value: bond}(...)` compiles. #### executeSlash @@ -79,11 +86,22 @@ Cancel a slash proposal #### setSlashConfig ```solidity -function setSlashConfig(uint64 disputeWindow, bool instantSlashEnabled, uint16 maxSlashBps) external +function setSlashConfig(uint64 disputeWindow, bool instantSlashEnabled, uint16 maxSlashBps, uint64 disputeResolutionDeadline, uint256 disputeBond, uint16 maxPendingSlashesPerOperator) external ``` Update slashing configuration +##### Parameters + +| Name | Type | Description | +| ---------------------------- | ------- | ---------------------------------------------------------------- | +| disputeWindow | uint64 | Time after `proposeSlash` during which the operator can dispute | +| instantSlashEnabled | bool | Reserved emergency toggle (no effect through the standard API) | +| maxSlashBps | uint16 | Hard cap on any single slash proposal | +| disputeResolutionDeadline | uint64 | Time `SLASH_ADMIN` has to resolve a dispute before it auto-fails | +| disputeBond | uint256 | Native asset bond required to dispute (0 = disabled) | +| maxPendingSlashesPerOperator | uint16 | Cap on concurrent pending slashes per operator (anti-spam) | + #### getSlashProposal ```solidity @@ -92,16 +110,93 @@ function getSlashProposal(uint64 slashId) external view returns (struct Slashing Get slash proposal details +#### getSlashConfig + +```solidity +function getSlashConfig() external view returns (struct SlashingLib.SlashConfig) +``` + +Get the current slashing configuration. Returns the live `SlashConfig` tuple containing +`disputeWindow`, `instantSlashEnabled`, `maxSlashBps`, `disputeResolutionDeadline`, +`disputeBond`, and `maxPendingSlashesPerOperator`. + #### Events #### SlashProposed ```solidity -event SlashProposed(uint64 serviceId, address operator, uint256 amount, bytes32 evidence) +event SlashProposed(uint64 indexed slashId, uint64 indexed serviceId, address indexed operator, address proposer, uint16 slashBps, uint16 effectiveSlashBps, bytes32 evidence, uint64 executeAfter) ``` +Emitted when a new slash proposal is created. + +##### Parameters + +| Name | Type | Description | +| ----------------- | ------- | ----------------------------------------------------------------------- | +| slashId | uint64 | The new slash ID (indexed) | +| serviceId | uint64 | The service where the violation occurred (indexed) | +| operator | address | The slashed operator (indexed) | +| proposer | address | The address that called `proposeSlash` | +| slashBps | uint16 | Requested slash percentage in basis points | +| effectiveSlashBps | uint16 | Slash percentage after exposure scaling (what will actually be applied) | +| evidence | bytes32 | Evidence hash (non-zero, enforced by `proposeSlash`) | +| executeAfter | uint64 | Earliest UNIX timestamp at which the slash can be executed | + +#### SlashDisputed + +```solidity +event SlashDisputed(uint64 indexed slashId, address indexed disputer, string reason) +``` + +Emitted when a slash proposal is disputed by the operator or by `SLASH_ADMIN_ROLE`. + +##### Parameters + +| Name | Type | Description | +| -------- | ------- | ------------------------------------------ | +| slashId | uint64 | The disputed slash ID (indexed) | +| disputer | address | The address that called `disputeSlash` | +| reason | string | Human-readable rationale (free-form input) | + #### SlashExecuted ```solidity -event SlashExecuted(uint64 serviceId, address operator, uint256 amount) +event SlashExecuted(uint64 indexed slashId, uint64 indexed serviceId, address indexed operator, uint256 actualSlashed) +``` + +Emitted when a slash is executed. + +##### Parameters + +| Name | Type | Description | +| ------------- | ------- | -------------------------------------------------- | +| slashId | uint64 | The executed slash ID (indexed) | +| serviceId | uint64 | The service the slash was applied to (indexed) | +| operator | address | The slashed operator (indexed) | +| actualSlashed | uint256 | Total stake actually burned in the underlying call | + +#### SlashCancelled + +```solidity +event SlashCancelled(uint64 indexed slashId, address indexed canceller, string reason) ``` + +Emitted when a slash proposal is cancelled by `SLASH_ADMIN_ROLE`. + +##### Parameters + +| Name | Type | Description | +| --------- | ------- | ------------------------------------------- | +| slashId | uint64 | The cancelled slash ID (indexed) | +| canceller | address | Address that called `cancelSlash` (indexed) | +| reason | string | Human-readable rationale (free-form input) | + +#### SlashConfigUpdated + +```solidity +event SlashConfigUpdated(uint64 disputeWindow, bool instantSlashEnabled, uint16 maxSlashBps, uint64 disputeResolutionDeadline, uint256 disputeBond, uint16 maxPendingSlashesPerOperator) +``` + +Emitted when `setSlashConfig` updates the slashing configuration. The full new +configuration is included in the event. diff --git a/pages/developers/auth-surface.mdx b/pages/developers/auth-surface.mdx index bfb69d9..9643f08 100644 --- a/pages/developers/auth-surface.mdx +++ b/pages/developers/auth-surface.mdx @@ -66,14 +66,14 @@ Most admin setters in `Base.sol` are `whenNotPaused`. The two exceptions, both i ### Slashing -| Function | Caller | Source | -| ------------------------------------------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------ | -| `proposeSlash(serviceId, op, slashBps, evidence)` | Service owner, blueprint owner, or BSM-declared origin | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | -| `disputeSlash(slashId, reason)` `payable` | Slashed operator (must post bond), or `SLASH_ADMIN_ROLE` | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | -| `executeSlash(slashId)` | Anyone (gated by `isExecutable`) | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | -| `executeSlashBatch(ids)` | Anyone | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | -| `cancelSlash(slashId, reason)` | `SLASH_ADMIN_ROLE` | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | -| `setSlashConfig(...)` | `ADMIN_ROLE` | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | +| Function | Caller | Source | +| ------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `proposeSlash(serviceId, op, slashBps, evidence)` | Service owner, blueprint owner, or BSM-declared origin | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | +| `disputeSlash(slashId, reason)` `payable` | Slashed operator (must post bond), or `SLASH_ADMIN_ROLE` (admin cannot self-dispute their own proposal) | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | +| `executeSlash(slashId)` | Anyone (gated by `isExecutable`) | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | +| `executeSlashBatch(ids)` | Anyone | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | +| `cancelSlash(slashId, reason)` | `SLASH_ADMIN_ROLE` | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | +| `setSlashConfig(...)` | `ADMIN_ROLE` | [Slashing.sol](https://github.com/tangle-network/tnt-core/blob/main/src/core/Slashing.sol) | See [Slashing](/developers/slashing) for the full lifecycle and runbooks. @@ -104,8 +104,8 @@ See [Slashing](/developers/slashing) for the full lifecycle and runbooks. | `approveService(ApprovalParams)` | Operator listed in the request (request not expired) | | `rejectService(requestId)` | Operator listed in the request | | `expireServiceRequest(requestId)` | Anyone, after grace period (when not activated) | -| `terminateService(serviceId)` | Service owner | -| `forceRemoveOperator(serviceId, operator)` | Blueprint manager only | +| `terminateService(serviceId)` | Service owner, `nonReentrant` | +| `forceRemoveOperator(serviceId, operator)` | Blueprint manager only, `nonReentrant` | `approveService` is a single entrypoint. Optional capabilities are opt-in via empty/zero fields on `ApprovalParams`: empty `securityCommitments`, zero `blsPubkey`, or empty `teeCommitments` each means "skip this capability." See [the slashing doc](/developers/slashing) for the per-asset commitment contract. @@ -175,7 +175,7 @@ Staking and delegation are a separate proxy with its own role registry. Function | Function | Caller | | --------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `addVersion(mbsm)` | `MANAGER_ROLE`. Rejects EOA _targets_ (`mbsmAddress.code.length != 0`); the caller can be any holder of the role. | -| `pinBlueprint(blueprintId, revision)` | `MANAGER_ROLE` | +| `pinBlueprint(blueprintId, revision)` | `MANAGER_ROLE`. Reverts if `revision` is currently in the deprecation grace window. | | `unpinBlueprint(blueprintId)` | `MANAGER_ROLE` | | `initiateDeprecation(revision)` | `MANAGER_ROLE` | | `completeDeprecation(revision)` | `MANAGER_ROLE`, after grace period | @@ -198,12 +198,38 @@ Staking and delegation are a separate proxy with its own role registry. Function ### `L2SlashingReceiver` -| Function | Caller | -| -------------------------------------------------- | ------------------------------------------- | -| `receiveMessage(sourceChainId, sender, payload)` | The configured cross-chain messenger only | -| `setAuthorizedSender(chainId, sender, authorized)` | Owner; authorization is timelocked (2 days) | -| `activateAuthorizedSender(chainId, sender)` | Owner, after `SENDER_ACTIVATION_DELAY` | -| `setMessenger(addr)` | Owner | +| Function | Caller | +| -------------------------------------------------- | --------------------------------------------------------------------------------------- | +| `receiveMessage(sourceChainId, sender, payload)` | The configured cross-chain messenger only | +| `setAuthorizedSender(chainId, sender, authorized)` | Owner. Revocation is immediate; authorization is timelocked (`SENDER_ACTIVATION_DELAY`) | +| `activateAuthorizedSender(chainId, sender)` | Owner, after `SENDER_ACTIVATION_DELAY` (2 days) | +| `setMessenger(addr)` | Owner. Bootstrap (current = `address(0)`) is immediate; subsequent swaps are queued | +| `activateMessenger()` | Owner, after `SENDER_ACTIVATION_DELAY` | +| `setSlasher(addr)` | Owner. Bootstrap (current = `address(0)`) is immediate; subsequent swaps are queued | +| `activateSlasher()` | Owner, after `SENDER_ACTIVATION_DELAY` | + +**Two-step swap (v0.13.0).** `setMessenger` and `setSlasher` no longer take effect on the +write that calls them. The owner queues the new address and the activation timestamp; after +`SENDER_ACTIVATION_DELAY` (2 days) elapses, the same owner calls `activateMessenger()` / +`activateSlasher()` to flip the live pointer. The bootstrap exemption (when the current +value is `address(0)`) lets deploy scripts wire the bridge without a 2-day deadlock; the +exemption is consumed the first time the slot is set. A typical rotation in production +looks like: + +```solidity +// t = 0: queue the new messenger +receiver.setMessenger(newMessenger); +// ... 2 days later (`SENDER_ACTIVATION_DELAY` has elapsed) +receiver.activateMessenger(); +``` + +**Receiver enforcement (v0.13.0).** `receiveMessage` now reverts if the L2 slasher returns +`canSlash == false` for the target operator (e.g. paused, unknown operator) or if +`slashBps == 0`, **before** consuming the bridge nonce. Previously the nonce was marked +processed first and a transient failure silently dropped the slash. With the new ordering +the bridge keeps the message available for retry until the slash actually applies. Relayers +must distinguish "already processed" (revert) from "still pending" (revert with +`SlashingNotPossible`) and re-deliver the latter. ### `L2SlashingConnector` diff --git a/pages/developers/blueprints/execution-confidentiality.mdx b/pages/developers/blueprints/execution-confidentiality.mdx index 27d5074..0d48b76 100644 --- a/pages/developers/blueprints/execution-confidentiality.mdx +++ b/pages/developers/blueprints/execution-confidentiality.mdx @@ -96,6 +96,10 @@ Important consequences: - All quotes in a single RFQ service creation or extension must agree on confidentiality. - Service extensions preserve the active service's confidentiality mode. If you need to switch modes, create a new service. +- RFQ quotes are bound to a specific consumer via the `requester` field of `QuoteDetails` + (and `JobQuoteDetails` for per-job quotes). The contract enforces + `requester == msg.sender` and rejects wildcard `requester == address(0)`, so confidential + service quotes cannot be lifted from the mempool and redeemed by a different caller. ## Tooling Status diff --git a/pages/developers/blueprints/pricing-and-payments.mdx b/pages/developers/blueprints/pricing-and-payments.mdx index 35d4626..9010ce8 100644 --- a/pages/developers/blueprints/pricing-and-payments.mdx +++ b/pages/developers/blueprints/pricing-and-payments.mdx @@ -76,15 +76,50 @@ Use Job RFQ when the price should be negotiated per request (LLM model selection High-level flow: 1. User requests a per-job quote from one or more operators (off-chain). -2. Operators respond with an EIP-712 signed `JobQuoteDetails { serviceId, jobIndex, price, timestamp, expiry }`. +2. Operators respond with an EIP-712 signed + `JobQuoteDetails { requester, serviceId, jobIndex, price, timestamp, expiry, confidentiality }`. 3. User submits the job on-chain with the signed quote(s) via `submitJobFromQuote`. Key properties: +- **Quote binding (v0.13.0).** `requester` is the FIRST field of `JobQuoteDetails` and is part + of the EIP-712 typehash. The contract enforces `requester == msg.sender` and rejects + wildcard `requester == address(0)`. Operators MUST sign per-caller quotes and populate + `requester` with the address they expect to consume the quote; an attacker who lifts a + signed digest off the wire cannot redirect it to themselves. The new typehash string is: + `JobQuoteDetails(address requester,uint64 serviceId,uint8 jobIndex,uint256 price,uint64 timestamp,uint64 expiry,uint8 confidentiality)`. - `msg.value` must equal `sum(quote.price)` across quotes. - Quotes are replay-protected and expire (protocol also enforces `maxQuoteAge`). - Only quoted operators can submit results for RFQ jobs. +A minimal Solidity-side struct literal an off-chain signer should hash: + +```solidity +Types.JobQuoteDetails({ + requester: 0xCAFE..., // MUST match the on-chain msg.sender + serviceId: 42, + jobIndex: 0, + price: 1_000_000 gwei, + timestamp: uint64(block.timestamp), + expiry: uint64(block.timestamp + 5 minutes), + confidentiality: 0 // 0=Any, 1=Required, 2=Preferred +}); +``` + +The equivalent EIP-712 typed-data payload (`message`): + +```json +{ + "requester": "0xCAFE000000000000000000000000000000000000", + "serviceId": "42", + "jobIndex": 0, + "price": "1000000000000000", + "timestamp": "1746700000", + "expiry": "1746700300", + "confidentiality": 0 +} +``` + ## Off-Chain Settlement (x402, optional) Some services want an HTTP-first, stablecoin settlement path for paid job execution. The Blueprint SDK includes an @@ -104,12 +139,25 @@ operator-run gateway. See: Service RFQ lets a user create a service instantly using operator quotes: 1. User requests service quotes from operators for `(blueprintId, ttlBlocks, confidentiality, security requirements)`. -2. Operators sign EIP-712 `QuoteDetails { blueprintId, ttlBlocks, totalCost, timestamp, expiry, confidentiality, ... }`. +2. Operators sign EIP-712 + `QuoteDetails { requester, blueprintId, ttlBlocks, totalCost, timestamp, expiry, confidentiality, securityCommitments[], resourceCommitments[] }`. 3. User calls `createServiceFromQuotes(...)` (or `extendServiceFromQuotes(...)`) with quotes and payment. All quotes for a single RFQ service creation or extension must agree on confidentiality. Changing confidentiality mode is treated as a new service agreement, not an in-place extension. +**Quote binding (v0.12.0+).** `requester` is the FIRST field of `QuoteDetails` and is part of the +EIP-712 typehash. The contract enforces `requester == msg.sender` on +`createServiceFromQuotes` / `extendServiceFromQuotes`, and rejects wildcard +`requester == address(0)`. Off-chain signers MUST populate `requester` with the address they +expect to redeem the quote; previously the field existed on the struct but was excluded from +the typehash, so a mempool observer could rewrite `details.requester` and the operator's +signature still recovered. The new typehash string is: +`QuoteDetails(address requester,uint64 blueprintId,uint64 ttlBlocks,uint256 totalCost,uint64 timestamp,uint64 expiry,uint8 confidentiality,AssetSecurityCommitment[] securityCommitments,ResourceCommitment[] resourceCommitments)`. + +Pre-fix signatures (without `requester` in the typehash) are invalid against the new +typehash and will fail signature recovery. + This is separate from the request and approval service lifecycle flow that the CLI exposes under `cargo tangle blueprint service`. For renewal and expiry semantics (including limitations of `extendServiceFromQuotes` and migration recommendations), see: diff --git a/pages/developers/blueprints/pricing-engine.mdx b/pages/developers/blueprints/pricing-engine.mdx index fc67cf9..17ac3ad 100644 --- a/pages/developers/blueprints/pricing-engine.mdx +++ b/pages/developers/blueprints/pricing-engine.mdx @@ -88,6 +88,13 @@ Important environment variables: - `GetPrice` uses proof-of-work to rate-limit RPC abuse. - Quotes include TTL + expiry and are signed with the operator ECDSA key to prevent replay. +- **Quote requester binding (v0.13.0).** Both service quotes (`QuoteDetails`) and per-job + quotes (`JobQuoteDetails`) include `requester` as the FIRST field of the EIP-712 typed + data. The contract enforces `requester == msg.sender` and rejects wildcard + `requester == address(0)`. Operator software MUST populate `requester` with the address + the customer intends to redeem the quote with; signing wildcard quotes (or omitting the + field from the typehash) makes the quote unusable on-chain and exposes operators to + mempool front-running of the signed digest. ## x402 Settlement Options (Optional) @@ -113,3 +120,7 @@ gRPC server to return x402 settlement options, you need to construct the pricing 6. Keep quote details intact. Never modify quote payloads after they are signed. 7. Generate fresh proof-of-work per request if your deployment uses PoW gating. 8. Expect price variation across operators. +9. **Always populate `requester` with the on-chain caller you intend to redeem the quote + with.** The contract enforces `requester == msg.sender` and rejects `address(0)`. If + the redeemer is a smart contract (paymaster, multisig), `requester` MUST be that + contract's address. diff --git a/pages/developers/blueprints/service-lifecycle.mdx b/pages/developers/blueprints/service-lifecycle.mdx index cb7f5cb..5d942ad 100644 --- a/pages/developers/blueprints/service-lifecycle.mdx +++ b/pages/developers/blueprints/service-lifecycle.mdx @@ -72,6 +72,13 @@ Options depend on the blueprint membership model: Blueprint managers can also force-remove operators from a service for emergency response. Blueprint developers should treat this as a last-resort tool and design for the possibility that the operator set changes unexpectedly. +By default the protocol enforces `operatorCount > minOperators` even when a blueprint manager calls +`forceRemoveOperator`. A blueprint that genuinely needs emergency-eviction-below-min must opt in by overriding +the new `forceRemoveAllowsBelowMin(serviceId)` BSM hook to return `true` (default in +`BlueprintServiceManagerBase` is `false`). Custom managers that do not inherit `BlueprintServiceManagerBase` MUST +implement this hook explicitly; an unimplemented or reverting view fails closed and the eviction will revert as soon +as it would push the service below `minOperators`. + ## What Expiry Means Expiry is enforced by function-level checks. When a service is expired (`block.timestamp > createdAt + ttl`), critical @@ -80,6 +87,31 @@ paths like job submission and subscription billing revert. Expiry does not necessarily mean "terminated" in the protocol's service status. If you need explicit cleanup or refund behavior, ensure your operational tooling calls `terminateService(serviceId)` when a service should end. +## Exit Gates (v0.13.0) + +The protocol tightened lifecycle gating so a stale operator cannot continue to mutate a service whose +agreement is already over, and so a request cannot be rescued past its expiry grace window. + +- **Active-service gate on every exit entrypoint.** `scheduleExit`, `executeExit`, `forceExit`, + `leaveService`, and `forceRemoveOperator` all reject when `service.status != Active`. Previously + a stale operator could keep firing exit paths against a `Terminated` service, double-decrementing + counts and emitting `OperatorLeftService` for a service that no longer exists. +- **`terminateService` is `nonReentrant`.** State writes already preceded external calls + (CEI), but the entrypoint now matches the rest of the lifecycle on defense-in-depth. The + same guard applies to `terminateServiceForNonPayment`. +- **`approveService` rejects past-grace requests.** Once a request is past + `createdAt + requestExpiryGracePeriod`, operators can no longer activate it. This closes the + race where an operator could front-run the requester's `expireServiceRequest` cleanup tx and + quietly spawn a stale service. +- **`requestService*` rejects duplicate operators.** Submitting the same operator address more + than once now reverts. With duplicates `operatorCount` exceeded the unique approver count, + so `approvalCount == operatorCount` was unreachable and the request could only be cleaned up + via `expireServiceRequest`. +- **`forceRemoveOperator` respects `minOperators` by default.** A blueprint manager can no + longer evict honest operators below `minOperators` unless the BSM explicitly opts in via + the new `forceRemoveAllowsBelowMin(serviceId)` hook (default: `false`). Blueprints that + genuinely need emergency-eviction-below-min must override that hook to return `true`. + ## Safety Recommendations For Stateful Or Custodial Blueprints If your blueprint holds user funds, tokens, or state that must remain withdrawable, assume that: diff --git a/pages/developers/slashing.mdx b/pages/developers/slashing.mdx index 91bd87e..1530e7f 100644 --- a/pages/developers/slashing.mdx +++ b/pages/developers/slashing.mdx @@ -35,7 +35,7 @@ The slashing path is implemented in: Each transition has a single responsible caller and a single state effect. -### `proposeSlash(serviceId, operator, slashBps, evidence)` +### `proposeSlash(serviceId, operator, slashBps, evidence)` (`nonReentrant`) Anyone the protocol accepts as a slasher creates a `SlashProposal`. The proposal: @@ -47,18 +47,29 @@ Anyone the protocol accepts as a slasher creates a `SlashProposal`. The proposal If the operator already has `maxPendingSlashesPerOperator` pending, the call reverts. Default cap is 32. -### `disputeSlash(slashId, reason)` `payable` +`evidence` MUST be non-zero. The contract rejects `bytes32(0)` evidence so off-chain monitors keying off +non-zero evidence never observe silently-zero entries. + +### `disputeSlash(slashId, reason)` `payable` (`nonReentrant`) The operator (or `SLASH_ADMIN_ROLE`) contests the slash within the dispute window. The operator must post `config.disputeBond` in native asset. `SLASH_ADMIN` posts no bond. The dispute snapshots `config.disputeResolutionDeadline` onto the proposal (`disputeDeadline = block.timestamp + deadline`), so a later admin-driven shrink of the live config cannot retroactively shorten the operator's review window. +A `SLASH_ADMIN_ROLE` holder that is also the `proposer` of a slash CANNOT dispute their own +proposal. Without this, a single role-holder could propose, immediately self-dispute (no bond), +and freeze operator stake for the full `disputeResolutionDeadline` window — and (when +`treasury == admin`) capture the operator's bond on auto-execution. To dispute as +`SLASH_ADMIN`, route the dispute through a different admin-keyed account or the operator. + ### `executeSlash(slashId)` (permissionless) Anyone calls this. The proposal's `isExecutable` check accepts: - A `Pending` proposal whose `executeAfter + TIMESTAMP_BUFFER` has elapsed -- A `Disputed` proposal whose snapshotted `disputeDeadline` has elapsed +- A `Disputed` proposal whose `disputeDeadline + TIMESTAMP_BUFFER` has elapsed (symmetric 15s + buffer with the dispute window, so a sequencer with timestamp influence cannot sandwich the + deadline tick to land an `executeSlash` before the operator's dispute window closes) Execution routes through `_executeSlashOnStaking`: diff --git a/pages/infrastructure/protocol-deployment.mdx b/pages/infrastructure/protocol-deployment.mdx index 862f2cb..17290cf 100644 --- a/pages/infrastructure/protocol-deployment.mdx +++ b/pages/infrastructure/protocol-deployment.mdx @@ -36,7 +36,13 @@ If you operate a production environment, keep these items in your internal runbo - the `FULL_DEPLOY_CONFIG` JSON you used (and any env var overrides) - the manifest output path and the produced manifest JSON - role handoff targets (timelock, multisig, treasury) and whether bootstrap roles were revoked -- any cross-chain slashing wiring (Hyperlane or LayerZero) and the connector and receiver manifests +- any cross-chain slashing wiring (Hyperlane or LayerZero) and the connector and receiver manifests. On + `L2SlashingReceiver`, the bootstrap `setMessenger` / `setSlasher` calls (when the current value is + `address(0)`) take effect immediately. **Subsequent rotations are timelocked**: the owner queues the new + address with `setMessenger` / `setSlasher`, then calls `activateMessenger()` / + `activateSlasher()` after `SENDER_ACTIVATION_DELAY` (2 days) elapses. Document the queue/activate + pair as two transactions in your runbook so operators know to schedule the activation tx before the + delay window closes. ## Related Docs diff --git a/pages/network/metrics-and-scoring.mdx b/pages/network/metrics-and-scoring.mdx index dc0bb6c..a20ffb7 100644 --- a/pages/network/metrics-and-scoring.mdx +++ b/pages/network/metrics-and-scoring.mdx @@ -22,7 +22,11 @@ Depending on what is configured on-chain, the protocol can record: - **Service activity**: service creation/termination, job calls, job completion success rates. - **Payments**: total fees paid by customers. - **Operator liveness**: heartbeats for active services. -- **Slashing**: executed slash events (and the slashed amount). +- **Slashing**: the full slashing lifecycle is observable via `ITangleSlashing` events: + `SlashProposed`, `SlashDisputed`, `SlashExecuted`, `SlashCancelled`, and + `SlashConfigUpdated`. `TangleMetrics` records executed slashes (count and slashed amount) + for incentive accounting; indexers that need full lifecycle context (proposers, + dispute reasons, config history) should subscribe to all five events directly. ## Staker “Exposure” Scoring diff --git a/pages/operators/pricing/overview.mdx b/pages/operators/pricing/overview.mdx index 3226d60..980da19 100644 --- a/pages/operators/pricing/overview.mdx +++ b/pages/operators/pricing/overview.mdx @@ -44,6 +44,14 @@ These flows require operators to generate and sign EIP-712 quotes off-chain: If the customer requests a specific confidentiality mode for the service, that confidentiality intent is part of the service RFQ agreement and should be reflected in your quote and operating cost assumptions. +**Quote requester binding (v0.13.0).** Both `QuoteDetails` (service quotes) and +`JobQuoteDetails` (per-job quotes) now have `requester` as the FIRST field of the EIP-712 +typed data. The protocol enforces `requester == msg.sender` and rejects wildcard +`requester == address(0)`. Your quote server MUST sign per-caller quotes with `requester` +populated to the customer's address, otherwise the customer cannot redeem your quote +on-chain. This also defeats mempool front-running: another `permittedCaller` (or anyone +watching the mempool) can no longer lift your signed digest and consume it. + To participate in RFQ, you need: - a quote serving endpoint (typically the `pricing-engine` gRPC server) diff --git a/pages/release-notes/0.13.0.mdx b/pages/release-notes/0.13.0.mdx new file mode 100644 index 0000000..e82f70a --- /dev/null +++ b/pages/release-notes/0.13.0.mdx @@ -0,0 +1,204 @@ +--- +title: tnt-core v0.13.0 +description: Quote requester binding, slashing event reshape, BSM hooks, and L2 slashing-receiver timelocks. +--- + +# tnt-core v0.13.0 + +This release cuts in two breaking changes for any off-chain service that signs RFQ +quotes or decodes slashing events, plus a set of lifecycle and cross-chain hardening +fixes from the round-2 audit pass. Upstream PRs: +[tnt-core#124](https://github.com/tangle-network/tnt-core/pull/124), +[tnt-core#125](https://github.com/tangle-network/tnt-core/pull/125). + +If you maintain operator software that signs quotes, an indexer that decodes slash +events, a custom BSM, or an L2 slashing receiver, treat this as a required upgrade. + +## Breaking Changes + +### EIP-712 quote binding (`QuoteDetails`, `JobQuoteDetails`) + +Both quote structs now carry `address requester` as the **first** field of the EIP-712 +typed data. The contract enforces `requester == msg.sender` on +`createServiceFromQuotes` / `extendServiceFromQuotes` / `submitJobFromQuote` and rejects +wildcard `requester == address(0)`. + +The new typehash strings are: + +``` +QuoteDetails(address requester,uint64 blueprintId,uint64 ttlBlocks,uint256 totalCost,uint64 timestamp,uint64 expiry,uint8 confidentiality,AssetSecurityCommitment[] securityCommitments,ResourceCommitment[] resourceCommitments) + +JobQuoteDetails(address requester,uint64 serviceId,uint8 jobIndex,uint256 price,uint64 timestamp,uint64 expiry,uint8 confidentiality) +``` + +Previously `requester` lived on `QuoteDetails` but was excluded from the typehash, so a +mempool observer could rewrite `details.requester` to themselves and the operator's +signature still recovered. `JobQuoteDetails` had no `requester` at all and any +`permittedCaller` could lift another caller's signed digest. Both are now bound at the +typehash level. + +**Action for operator software:** + +- Add `requester` to the `QuoteDetails` typed-data hash as the first member. +- Add `requester` to the `JobQuoteDetails` typed-data hash as the first member. +- Sign per-caller quotes; do **not** emit wildcard quotes — they are rejected on-chain. +- Pre-fix signatures fail signature recovery against the new typehash and must be regenerated. + +See [pricing & payments](/developers/blueprints/pricing-and-payments) for the updated +struct shapes and a copyable typed-data example. + +### `Types.ServiceRequest.activated` reordered + +The `activated` flag was moved to the **end** of the `ServiceRequest` struct so a +hypothetical upgrade from a pre-`activated` storage layout cannot accidentally read a +non-zero byte from a different field as `activated == true`. ABI consumers regenerate +from the new bindings. + +### `ITangleSlashing` event shapes + +`ITangleSlashing` now declares the events the protocol actually emits from `SlashingLib`. +Before this release, Rust bindings and indexers wired to `ITangleSlashing` could not decode +any slash event because the interface declared smaller, legacy shapes. + +| Event | Old fields | New fields | +| -------------------- | -------------- | --------------------------------------------------------------------------------------------------------------- | +| `SlashProposed` | 4 | 8 — `slashId`, `serviceId`, `operator`, `proposer`, `slashBps`, `effectiveSlashBps`, `evidence`, `executeAfter` | +| `SlashExecuted` | 3 | 4 — `slashId`, `serviceId`, `operator`, `actualSlashed` | +| `SlashDisputed` | (not declared) | `slashId`, `disputer`, `reason` | +| `SlashCancelled` | (not declared) | `slashId`, `canceller`, `reason` | +| `SlashConfigUpdated` | (not declared) | full `SlashConfig` tuple (6 fields) | + +`getSlashConfig()` returns the full 6-field `SlashConfig`. `setSlashConfig` takes 6 args: +the existing `(disputeWindow, instantSlashEnabled, maxSlashBps)` plus +`(disputeResolutionDeadline, disputeBond, maxPendingSlashesPerOperator)`. + +`proposeSlash` parameter `uint256 amount` is now `uint16 slashBps` (basis points). +`disputeSlash` is `external payable` (so callers can pass `disputeBond` as native value). + +See [`ITangleSlashing`](/developers/api/reference/ITangleSlashing) and +[Slashing](/developers/slashing) for the updated lifecycle and config matrix. + +### BSM hook: `forceRemoveAllowsBelowMin(uint64) -> bool` + +`IBlueprintServiceManager` adds a new view: + +```solidity +function forceRemoveAllowsBelowMin(uint64 serviceId) external view returns (bool ok); +``` + +Default in `BlueprintServiceManagerBase` is `false` — the protocol enforces +`operatorCount > minOperators` on `forceRemoveOperator`. Custom BSMs that do **not** +inherit `BlueprintServiceManagerBase` MUST implement this hook explicitly; an +unimplemented or reverting view fails closed and the eviction reverts as soon as it +would push the service below `minOperators`. + +Without this gate, a malicious blueprint manager could evict honest operators below +the configured floor and bias the operator set toward sybils. + +## Hardening (non-breaking, behavior-changing) + +### Slashing + +- `proposeSlash` and `disputeSlash` now carry `nonReentrant`. Previously only + `executeSlash`, `executeSlashBatch`, and `cancelSlash` were guarded. +- `proposeSlash` rejects `bytes32(0)` evidence so off-chain monitors keying off + non-zero evidence don't see silently-zero entries. +- Disputed slashes use the same 15-second `TIMESTAMP_BUFFER` as Pending slashes. + `executeSlash` for a `Disputed` proposal now requires + `disputeDeadline + TIMESTAMP_BUFFER` to have elapsed. +- A `SLASH_ADMIN_ROLE` holder that is also the `proposer` of a slash CANNOT dispute + their own proposal. + +### Service lifecycle + +- Every operator-exit entrypoint (`scheduleExit`, `executeExit`, `forceExit`, + `leaveService`, `forceRemoveOperator`) reverts when the service is no longer + `Active`. +- `terminateService` and `terminateServiceForNonPayment` carry `nonReentrant`. +- `approveService` rejects requests past `createdAt + requestExpiryGracePeriod`. +- `requestService*` rejects duplicate operator entries. + +### MBSM registry + +- `MBSMRegistry.pinBlueprint` rejects revisions currently inside the deprecation + grace window. Pinning to a deprecated revision would break every BSM call for the + pinned blueprint the moment `completeDeprecation` ran. + +### L2 slashing receiver + +- `setMessenger` and `setSlasher` are timelock-gated for non-bootstrap rotations + (2-day `SENDER_ACTIVATION_DELAY`). The first write (when the current value is + `address(0)`) is a bootstrap exemption so deploy scripts can wire the bridge + without a 2-day deadlock. +- New `activateMessenger()` / `activateSlasher()` consume the queued swap after the + delay elapses. +- `receiveMessage` reverts when the L2 slasher returns `canSlash == false` or when + `slashBps == 0`, **before** consuming the bridge nonce. Previously the nonce was + marked processed first and a transient failure silently dropped the slash with no + retry path. With CEI fixed the bridge keeps the message available for retry. +- New `SlashingNotPossible(address operator)` error distinguishes the + retry-after-condition-clears case from a real misconfiguration. + +### Beacon SSZ encoding + +`BeaconChainProofs` `getEffectiveBalanceGwei`, `getActivationEpoch`, `getExitEpoch`, +`getWithdrawableEpoch`, and `_extractBalanceFromLeaf` now perform the canonical +little-endian byte-swap on SSZ-packed `uint64` fields. EigenPod-CLI fixtures decode +correctly out of the box; hand-rolled proof builders that pack values into the low 8 +bytes of the chunk (or use big-endian) will be rejected. Real EigenPod proofs would +silently mis-account every `uint64` field — every effective balance, exit epoch, and +validator balance — under the previous code. + +If you maintain a proof builder, regenerate fixtures with the canonical SSZ packing +and pin the 32-ETH leaf regression test that ships with v0.13.0. + +### Other fixes + +- `TNTLockFactory.getOrCreateLock` requires `msg.sender == beneficiary`. Without this + gate, a third party could front-run the victim's first interaction with a lock, + supply themselves as `delegatee`, and persistently capture the victim's voting + power for every future inbound TNT transfer to the deterministic lock address. +- `_distributePaymentWithEffectiveExposure` reverts (instead of silently retaining + funds) when there are zero active operators at billing time. +- `fundService`, `billSubscription`, and `billSubscriptionBatch` respect the global + pause. Reward / refund claim paths remain unguarded so users can always exit. +- `OperatorStatusRegistry.registerOperator` resets all per-`(serviceId, operator)` + heartbeat / metrics state on (re-)register. +- `LiquidDelegationVault.requestRedeem` rejects `controller == address(0)`. + +## Migration Checklist + +1. **Operator quote servers**: regenerate signatures with `requester` populated as the + first field of `QuoteDetails` and `JobQuoteDetails`. Stop signing wildcard quotes. +2. **Indexers / Rust binding consumers**: regenerate from + [`tnt-core-bindings` v0.13.0](https://crates.io/crates/tnt-core-bindings) and + re-decode slash events against the new shapes (`SlashProposed` 8 fields, + `SlashExecuted` 4 fields, plus `SlashDisputed`/`SlashCancelled`/`SlashConfigUpdated`). +3. **Custom BSMs not inheriting `BlueprintServiceManagerBase`**: implement + `forceRemoveAllowsBelowMin(uint64) -> bool` (return `false` unless you genuinely need + emergency-eviction-below-min). +4. **MBSM operators**: do not pin a blueprint to a revision that is in the deprecation + grace window — `pinBlueprint` will revert. Wait until the deprecation completes or + pick a different revision. +5. **L2 slashing receiver operators**: budget for two-step rotations of `messenger` + and `slasher`. Queue with `setMessenger` / `setSlasher`, then schedule + `activateMessenger()` / `activateSlasher()` 2 days later. +6. **Beacon proof builders**: regenerate SSZ fixtures with canonical + little-endian packing for `uint64` fields. +7. **`SLASH_ADMIN` operators**: a SLASH_ADMIN that is also the proposer can no longer + self-dispute their own slash. Route disputes through a different admin-keyed + account or the operator. + +## Reference + +- [`ITangleSlashing`](/developers/api/reference/ITangleSlashing) +- [`IBlueprintServiceManager`](/developers/api/reference/IBlueprintServiceManager) +- [`IMBSMRegistry`](/developers/api/reference/IMBSMRegistry) +- [Slashing](/developers/slashing) +- [Auth Surface](/developers/auth-surface) +- [Service Lifecycle](/developers/blueprints/service-lifecycle) +- [Pricing and Payments](/developers/blueprints/pricing-and-payments) +- tnt-core PR [#124](https://github.com/tangle-network/tnt-core/pull/124), + [#125](https://github.com/tangle-network/tnt-core/pull/125) +- Full bindings changelog: + [`tnt-core-bindings/CHANGELOG.md`](https://github.com/tangle-network/tnt-core/blob/main/bindings/CHANGELOG.md) diff --git a/pages/release-notes/_meta.ts b/pages/release-notes/_meta.ts new file mode 100644 index 0000000..4ddcc52 --- /dev/null +++ b/pages/release-notes/_meta.ts @@ -0,0 +1,7 @@ +import type { Meta } from "nextra"; + +const meta: Meta = { + "0.13.0": "v0.13.0", +}; + +export default meta;