{"id":"836cbcbbe43c7f0a217f8375b6814a7ef2cd132f62bcc7bb78535844f10e2663","pubkey":"da19f1cd34beca44be74da4b306d9d1dd86b6343cef94ce22c49c6f59816e5bd","created_at":1774618322,"kind":30817,"tags":[["title","NIP-PAID-SERVICES: Paid API Service Announcements"],["k","31402"],["d","nip-paid-services"]],"content":"NIP-PAID-SERVICES\n==================\n\nPaid API Service Announcements\n---------------------------------\n\n`draft` `optional`\n\nThis NIP defines one addressable event kind for announcing paid API services on Nostr. It enables decentralised discovery of HTTP APIs gated by any payment mechanism (L402, x402, Cashu, or others), without reliance on a central registry.\n\n## Motivation\n\nPaid HTTP APIs already work. Middleware like L402 and x402 gates any endpoint behind Lightning, Cashu, or stablecoin payments using standard HTTP 402 challenge-response (RFC 9110 section 15.5.3). The payment flow requires no Nostr involvement: a client hits an endpoint, gets a 402, pays, retries with proof, and receives the response. This works today with zero relay dependency.\n\nWhat is missing is **discovery**. How does a client find paid APIs without a centralised directory? Operators currently list services on proprietary platforms or rely on word-of-mouth.\n\nNIP-PAID-SERVICES adds a decentralised discovery layer. Service operators publish a kind 31402 event on Nostr describing what their API does, how much it costs, and which payment methods it accepts. Clients (including AI agents) subscribe to relays with standard filters and discover services automatically. Nostr handles discovery; HTTP handles payment and consumption. The two are cleanly separated.\n\n## Relationship to Existing NIPs\n\n- **NIP-89 (Application Handlers):** App handlers announce Nostr-native applications that process specific event kinds. Paid service announcements declare HTTP API capabilities gated by payment. Different scope; no overlap.\n- **NIP-105 (PR #780, open since September 2023):** Also proposes kind 31402 for API service listings. Not merged. This NIP was developed independently with a different tag schema; see [Kind Allocation](#kind-allocation).\n- **NIP-90 (Data Vending Machines):** DVMs are Nostr-native; jobs and results flow through relays as kind 5xxx/6xxx events. Paid services are HTTP-native; Nostr handles discovery only, consumption uses standard REST. DVMs are intent-based (\"I want X done\"); paid services are offer-based (\"I provide X at Y price\"). DVMs keep computation on-relay for composability (job chaining); paid services keep computation off-relay for efficiency and privacy. They complement each other: a DVM MAY delegate to a paid service for the actual computation, publishing the result back to the relay.\n- **NIP-57 (Zaps):** Zaps are tips and donations. Paid service announcements declare static price listings for API capabilities. No overlap.\n\n### Why not NIP-99 (Classified Listings)?\n\nNIP-99 classifieds and kind 31402 paid service announcements serve different consumers with incompatible data models:\n\n| Aspect | NIP-99 Classified Listing | Kind 31402 Paid Service |\n|--------|--------------------------|------------------------|\n| Consumer | Humans browsing a marketplace | Machines (AI agents, API clients) |\n| Price model | Single price per listing | Per-capability pricing with multiple tiers |\n| Payment negotiation | Out-of-band | Machine-readable `pmi` tags with rail-specific parameters |\n| Endpoint declaration | None | `url` tags with multi-transport redundancy |\n| Request/response schema | None | JSON Schema for request and response bodies |\n| Content structure | Free-text description + images | Structured capability array with endpoint paths |\n| Subscription pattern | `{\"kinds\": [30402]}` returns goods, services, rentals, jobs | `{\"kinds\": [31402]}` returns only machine-consumable API endpoints |\n\nA client subscribing to NIP-99 to find \"APIs that accept Lightning\" would receive furniture listings, concert tickets, and apartment rentals in the same feed. The tag schemas are incompatible: NIP-99 uses `price` as a single amount; kind 31402 uses `price` as a per-capability tuple. NIP-99 has no concept of endpoint URLs, payment method identifiers, or request schemas.\n\nAdding an `api` tag to NIP-99 would not resolve this. The content model (free-text vs structured capabilities), the price model (per-listing vs per-capability), and the intended consumer (human vs machine) are fundamentally different.\n\n## Kind\n\n| Kind  | Description                     |\n| ----- | ------------------------------- |\n| 31402 | Paid API Service Announcement   |\n\nKind 31402 is an addressable event (NIP-01). The combination of `pubkey` + `d` tag uniquely identifies a listing. Publishing again with the same values replaces the existing announcement.\n\n---\n\n## Event Structure\n\n### Tags\n\n| Tag         | Required    | Repeatable | Description                                                      |\n| ----------- | ----------- | ---------- | ---------------------------------------------------------------- |\n| `d`         | REQUIRED    | no         | Unique service identifier. Max 256 characters.                   |\n| `name`      | REQUIRED    | no         | Human-readable service name. Max 256 characters.                 |\n| `url`       | REQUIRED    | yes (1-10) | HTTP endpoint URL. Multiple tags for multi-transport redundancy. Max 2048 characters each. |\n| `summary`   | RECOMMENDED | no         | Service description. Max 4096 characters.                        |\n| `pmi`       | REQUIRED    | yes (1-20) | Payment Method Identifier. See [PMI Values](#canonical-pmi-values). |\n| `price`     | RECOMMENDED | yes (1-100)| Capability pricing. Format below.                                |\n| `s`         | OPTIONAL    | no         | Underlying API endpoint URL that this service proxies. Enables clients to discover all providers proxying the same API (e.g. all proxies for `https://api.openai.com/v1/chat/completions`). When present, the `d` tag SHOULD match the `s` tag value. Max 2048 characters. |\n| `t`         | OPTIONAL    | yes (1-50) | Topic tag for discovery filtering. Max 64 characters each.       |\n| `picture`   | OPTIONAL    | no         | Icon URL (`http://` or `https://`). Max 2048 characters.         |\n| `alt`       | RECOMMENDED | no         | Short human-readable plaintext description of the event for clients that do not support kind 31402. |\n| `expiration`| OPTIONAL    | no         | NIP-40 expiration timestamp. Relays MAY discard the event after this time. |\n\n**Tag formats:**\n\n```\n[\"d\", \"<service-identifier>\"]\n[\"name\", \"<human-readable-name>\"]\n[\"url\", \"<endpoint-url>\"]\n[\"summary\", \"<description>\"]\n[\"pmi\", \"<rail>\", ...additional-elements]\n[\"price\", \"<capability_name>\", \"<amount>\", \"<currency>\"]\n[\"s\", \"<upstream-api-url>\"]\n[\"t\", \"<topic>\"]\n[\"alt\", \"<human-readable-description>\"]\n[\"picture\", \"<icon-url>\"]\n[\"expiration\", \"<unix-timestamp>\"]\n```\n\n**`d` tag:** Clients SHOULD use a stable, URL-safe string (e.g. `jokes-api`, `inference-v2`). The `d` tag MUST NOT be empty or whitespace-only.\n\n**`url` tag:** Each URL becomes a separate tag. Multiple URLs represent the same service accessible via different transports (clearnet, Tor .onion, Handshake .hns). Clients SHOULD try URLs in tag order and use whichever is reachable. Schemes `data:`, `file:`, `javascript:`, `blob:`, and `vbscript:` are prohibited. URLs MUST NOT contain control characters.\n\n**`pmi` tag:** A multi-element tag. The first element after the tag name identifies the payment rail. Additional elements carry rail-specific parameters. See [Canonical PMI Values](#canonical-pmi-values).\n\n**`price` tag:** Amount MUST be a non-negative integer in the smallest unit of the specified currency (satoshis for `sats`, cents for `usd`, pence for `gbp`). Capability names MUST NOT exceed 64 characters. Currency codes MUST NOT exceed 32 characters.\n\n### Content\n\nThe event content is a JSON string containing an object with optional fields:\n\n| Field          | Type     | Description                                                    |\n| -------------- | -------- | -------------------------------------------------------------- |\n| `capabilities` | array    | Detailed capability descriptions (max 100 entries).            |\n| `version`      | string   | Service version string (max 64 characters).                    |\n\nEach capability object:\n\n| Field          | Type   | Required | Description                                                  |\n| -------------- | ------ | -------- | ------------------------------------------------------------ |\n| `name`         | string | REQUIRED | Capability name (max 64 characters). MUST match a `price` tag capability name if pricing is declared. |\n| `description`  | string | REQUIRED | Human-readable description (max 4096 characters).            |\n| `endpoint`     | string | OPTIONAL | Endpoint path or full URL (e.g. `/api/joke` or `https://api.example.com/v1/chat`). Max 2048 characters. |\n| `schema`       | object | OPTIONAL | JSON Schema (draft 2020-12) for the POST request body. Enables AI agents to auto-generate type-safe API calls without documentation. |\n| `outputSchema` | object | OPTIONAL | JSON Schema (draft 2020-12) for the response body. Enables AI agents to auto-generate type-safe API calls without documentation. |\n\nThe content JSON MUST NOT exceed 64 KiB when serialised. Implementations SHOULD reject content with nesting depth exceeding 20 levels to prevent denial-of-service via deeply nested schemas.\n\nIf the content has no capabilities or version, it SHOULD be an empty JSON object (`{}`).\n\nTags are sufficient for discovery, filtering, and pricing. Content capabilities are an optimisation for programmatic consumers (AI agents, MCP clients) that need request/response schemas without fetching external documentation. Implementations that only need discovery MAY ignore content entirely.\n\n---\n\n## Canonical PMI Values\n\nThe `pmi` tag uses a multi-element format where the first element identifies the payment rail and subsequent elements carry rail-specific metadata.\n\n| Rail identifier | Additional elements             | Description                                                    |\n| --------------- | ------------------------------- | -------------------------------------------------------------- |\n| `l402`          | `lightning`                     | L402 (formerly LSAT). Lightning BOLT-11 invoice in the `WWW-Authenticate` header. |\n| `x402`          | `<network>`, `<token>`, `<receiver-address>` | x402 stablecoin payments. Network is the chain identifier (e.g. `base`). Token is the asset (e.g. `usdc`). |\n| `cashu`         | _(none)_                        | Cashu ecash. Generic; mint discovery is out of scope.          |\n| `xcashu`        | _(none)_                        | Cashu ecash via NUT-24 (X-Cashu HTTP header protocol).         |\n\n**Examples:**\n\n```\n[\"pmi\", \"l402\", \"lightning\"]\n[\"pmi\", \"x402\", \"base\", \"usdc\", \"0xAbC123...\"]\n[\"pmi\", \"cashu\"]\n[\"pmi\", \"xcashu\"]\n```\n\nImplementations MUST accept the four canonical rail identifiers listed above. Unrecognised rail identifiers SHOULD be ignored by clients that do not support them.\n\nFuture payment rails MAY be added by convention. Implementations SHOULD NOT reject events containing unrecognised `pmi` rails, as this allows forward compatibility.\n\n---\n\n## Examples\n\n### AI Inference Service (Lightning + Cashu)\n\n```json\n{\n  \"kind\": 31402,\n  \"pubkey\": \"<operator-hex-pubkey>\",\n  \"created_at\": 1711234567,\n  \"tags\": [\n    [\"d\", \"llm-inference-v1\"],\n    [\"name\", \"LLM Inference API\"],\n    [\"alt\", \"Paid API: LLM Inference API via Lightning and Cashu\"],\n    [\"url\", \"https://inference.example.com/v1\"],\n    [\"url\", \"http://inferencexyz123.onion/v1\"],\n    [\"summary\", \"GPT-4 class inference with Lightning and Cashu payment. Supports chat completions and embeddings.\"],\n    [\"pmi\", \"l402\", \"lightning\"],\n    [\"pmi\", \"cashu\"],\n    [\"price\", \"chat_completion\", \"100\", \"sats\"],\n    [\"price\", \"embedding\", \"10\", \"sats\"],\n    [\"t\", \"ai\"],\n    [\"t\", \"inference\"],\n    [\"t\", \"llm\"]\n  ],\n  \"content\": \"{\\\"capabilities\\\":[{\\\"name\\\":\\\"chat_completion\\\",\\\"description\\\":\\\"Chat completion with streaming support. Accepts OpenAI-compatible request format.\\\",\\\"endpoint\\\":\\\"/chat/completions\\\"},{\\\"name\\\":\\\"embedding\\\",\\\"description\\\":\\\"Text embedding generation. Returns 1536-dimensional vectors.\\\",\\\"endpoint\\\":\\\"/embeddings\\\"}],\\\"version\\\":\\\"2.1.0\\\"}\"\n}\n```\n\n### Data API (x402 Stablecoin)\n\n```json\n{\n  \"kind\": 31402,\n  \"pubkey\": \"<operator-hex-pubkey>\",\n  \"created_at\": 1711234567,\n  \"tags\": [\n    [\"d\", \"market-data-feed\"],\n    [\"name\", \"Real-Time Market Data\"],\n    [\"alt\", \"Paid API: Real-Time Market Data via x402 stablecoin\"],\n    [\"url\", \"https://data.example.com/api\"],\n    [\"summary\", \"Live and historical market data for equities and crypto. REST and WebSocket.\"],\n    [\"pmi\", \"x402\", \"base\", \"usdc\", \"0x1234567890abcdef1234567890abcdef12345678\"],\n    [\"price\", \"snapshot\", \"5\", \"usd\"],\n    [\"price\", \"historical_range\", \"50\", \"usd\"],\n    [\"price\", \"websocket_stream\", \"200\", \"usd\"],\n    [\"t\", \"data\"],\n    [\"t\", \"finance\"],\n    [\"t\", \"market-data\"]\n  ],\n  \"content\": \"{\\\"capabilities\\\":[{\\\"name\\\":\\\"snapshot\\\",\\\"description\\\":\\\"Current price snapshot for a given ticker.\\\",\\\"endpoint\\\":\\\"/snapshot\\\"},{\\\"name\\\":\\\"historical_range\\\",\\\"description\\\":\\\"OHLCV data for a date range.\\\",\\\"endpoint\\\":\\\"/historical\\\"},{\\\"name\\\":\\\"websocket_stream\\\",\\\"description\\\":\\\"Live price stream via WebSocket. Fee covers 1 hour of streaming.\\\",\\\"endpoint\\\":\\\"/ws\\\"}],\\\"version\\\":\\\"1.0.0\\\"}\"\n}\n```\n\n### Compute Service (Lightning + X-Cashu)\n\n```json\n{\n  \"kind\": 31402,\n  \"pubkey\": \"<operator-hex-pubkey>\",\n  \"created_at\": 1711234567,\n  \"tags\": [\n    [\"d\", \"gpu-compute\"],\n    [\"name\", \"GPU Compute on Demand\"],\n    [\"alt\", \"Paid API: GPU Compute on Demand via Lightning and X-Cashu\"],\n    [\"url\", \"https://compute.example.com\"],\n    [\"url\", \"https://compute.example.hns\"],\n    [\"summary\", \"On-demand GPU compute for ML training and rendering. A100 instances.\"],\n    [\"pmi\", \"l402\", \"lightning\"],\n    [\"pmi\", \"xcashu\"],\n    [\"price\", \"gpu_hour_a100\", \"50000\", \"sats\"],\n    [\"price\", \"gpu_minute_a100\", \"900\", \"sats\"],\n    [\"t\", \"compute\"],\n    [\"t\", \"gpu\"],\n    [\"t\", \"ml\"]\n  ],\n  \"content\": \"{\\\"capabilities\\\":[{\\\"name\\\":\\\"gpu_hour_a100\\\",\\\"description\\\":\\\"Reserve one A100 GPU for one hour. Returns SSH credentials on payment.\\\"},{\\\"name\\\":\\\"gpu_minute_a100\\\",\\\"description\\\":\\\"Reserve one A100 GPU for one minute. Suitable for short inference jobs.\\\"}],\\\"version\\\":\\\"0.9.0\\\"}\"\n}\n```\n\n---\n\n## Discovery\n\nClients discover services using standard NIP-01 `REQ` filters on kind 31402 events.\n\n### Filter by Topic\n\n```json\n[\"REQ\", \"sub1\", { \"kinds\": [31402], \"#t\": [\"ai\", \"inference\"] }]\n```\n\n### Filter by Payment Method\n\n```json\n[\"REQ\", \"sub2\", { \"kinds\": [31402], \"#pmi\": [\"l402\"] }]\n```\n\n### Filter by Specific Service\n\n```json\n[\"REQ\", \"sub3\", { \"kinds\": [31402], \"#d\": [\"llm-inference-v1\"], \"authors\": [\"<operator-pubkey>\"] }]\n```\n\n### All Services from an Operator\n\n```json\n[\"REQ\", \"sub4\", { \"kinds\": [31402], \"authors\": [\"<operator-pubkey>\"] }]\n```\n\n### All Announcements (broad crawl)\n\n```json\n[\"REQ\", \"sub5\", { \"kinds\": [31402], \"limit\": 500 }]\n```\n\nClients SHOULD filter by `#pmi` to discover only services whose payment method they support. Note: `pmi` is a multi-letter tag; relay support for `#pmi` filtering varies. Clients that cannot filter by `#pmi` at the relay SHOULD filter by kind 31402 and post-filter by `pmi` tag client-side. Relays that support NIP-50 (Search) MAY allow full-text search over `name` and `summary` tag values.\n\n---\n\n## Protocol Flow\n\n\n![Paid API Discovery and Payment Flow](https://raw.githubusercontent.com/forgesworn/nip-drafts/main/images/paid_services-1.png)\n\n```mermaid\nsequenceDiagram\n    participant Operator as Service Operator\n    participant Relay as Nostr Relay\n    participant Client as Client / AI Agent\n\n    Operator->>Relay: 1. Publish kind 31402 (signed event)\n    Client->>Relay: 2. REQ {kinds:[31402], #t, #pmi}\n    Relay-->>Client: 3. EVENT (kind 31402)\n    Client->>Client: 4. Parse tags: url, pmi, price, capabilities\n\n    Client->>Operator: 5. HTTP request to service URL\n    Operator-->>Client: 6. HTTP 402 + payment challenge (L402/x402/Cashu)\n    Client->>Operator: 7. HTTP request + payment proof (token/receipt)\n    Operator-->>Client: 8. HTTP 200 + response body\n```\n\n1. The service operator publishes a kind 31402 event to one or more relays.\n2. A client subscribes with filters matching its interests (topics, payment methods).\n3. The relay delivers matching announcements.\n4. The client parses the event to extract endpoint URLs, pricing, and payment methods.\n5. The client sends an HTTP request to the service URL.\n6. The service responds with HTTP 402 and a payment challenge appropriate to the declared `pmi`.\n7. The client completes payment and retries the request with a proof token.\n8. The service validates the proof and returns the response.\n\n---\n\n## Multiple URLs vs Multiple Events\n\nThis distinction matters for operators:\n\n**Multiple URLs in one event:** use multiple `url` tags when the URLs represent the **same service** on different transports (clearnet, Tor, Handshake). The pricing, credentials, and authentication are identical. Clients pick whichever URL they can reach. This is for censorship resistance and redundancy.\n\n**Separate kind 31402 events:** publish separate events (different `d` tags) when you have **genuinely different services**: different pricing tiers, different capabilities, or services that operate independently.\n\nIn short: same service, different network paths = one event with multiple `url` tags. Different services = separate events.\n\n---\n\n## Security Considerations\n\n### URL Validation\n\nClients MUST validate URLs before making HTTP requests. At minimum:\n\n- Reject `data:`, `file:`, `javascript:`, `blob:`, and `vbscript:` schemes.\n- Reject URLs containing control characters.\n- Apply a connection timeout (RECOMMENDED: 10 seconds).\n- Follow redirects cautiously; do not follow more than 3 redirects.\n\n### Rate Limiting\n\nRelays SHOULD apply standard rate limits to kind 31402 event publication. A single pubkey publishing hundreds of announcements per minute is likely abusive.\n\nClients SHOULD cache discovered announcements and avoid re-fetching on every query.\n\n### Trust Model\n\nKind 31402 events are **unverified claims**. The announcement says \"I offer X at Y price,\" but there is no on-chain or relay-enforced guarantee that the service exists, works correctly, or charges the stated price. Clients SHOULD:\n\n- Prefer services from operators with established Nostr reputations (NIP-65 relay lists, follower counts, NIP-32 labels from trusted curators).\n- Start with low-value requests to verify service quality before committing to expensive operations.\n- Maintain local blocklists of operators who deliver poor results.\n\n### Key Material\n\nImplementations that sign events programmatically MUST zeroise secret key byte buffers after signing. JavaScript string values are immutable and cannot be erased from memory; implementations SHOULD minimise the lifetime of secret key strings.\n\n## Privacy Considerations\n\n- Service operators reveal their Nostr pubkey and HTTP endpoint URLs. Operators who require anonymity SHOULD use a dedicated keypair and publish only Tor .onion URLs.\n- Clients querying relays for kind 31402 events reveal their interest in paid APIs to the relay operator. Clients concerned about metadata leakage SHOULD query via Tor or use multiple relays.\n- The `price` and `pmi` tags are public. Operators who wish to keep pricing private SHOULD omit `price` tags and negotiate pricing out-of-band after initial contact.\n\n---\n\n## Kind Allocation\n\nKind 31402 is an addressable event in the 30000-39999 range. The number is the natural choice for HTTP 402-related service discovery (31000 range + 402).\n\nThis NIP formalises a kind that has been in production use across 8 implementations (all from the same developer; see Reference Implementations). Independent implementations are encouraged.\n\nTwo other proposals also use kind 31402: NIP-105 (nostr-protocol/nips PR #780, open since September 2023, not merged) proposes API service marketplace listings; SARA (NostrHub, February 2026) proposes a revenue share offering registry. The intent is compatible; the tag schemas differ. This NIP stores pricing and endpoints as tags for relay-side filterability, supports multi-transport URLs, and defines structured payment method identifiers. We welcome collaboration with the NIP-105 author on a merged specification.\n\n---\n\n## Test Vectors\n\n### Minimal Valid Event\n\nREQUIRED tags only:\n\n```json\n{\n  \"kind\": 31402,\n  \"pubkey\": \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\",\n  \"created_at\": 1711234567,\n  \"tags\": [\n    [\"d\", \"test-service\"],\n    [\"name\", \"Test Service\"],\n    [\"url\", \"https://test.example.com\"],\n    [\"pmi\", \"l402\", \"lightning\"]\n  ],\n  \"content\": \"{}\"\n}\n```\n\nValidation rules for this event:\n\n- Kind MUST be 31402.\n- `d` tag MUST be present and non-empty.\n- `name` tag MUST be present and non-empty, max 256 characters.\n- At least one `url` tag MUST be present, max 10.\n- At least one `pmi` tag MUST be present, max 20.\n- `content` MUST be valid JSON.\n\n### Invalid Events\n\n**Missing `pmi` tag** (MUST be rejected):\n\n```json\n{\n  \"kind\": 31402,\n  \"tags\": [\n    [\"d\", \"no-payment\"],\n    [\"name\", \"No Payment Info\"],\n    [\"url\", \"https://test.example.com\"]\n  ],\n  \"content\": \"{}\"\n}\n```\n\n**URL with disallowed scheme** (MUST be rejected by publishers):\n\n```json\n[\"url\", \"javascript:alert(1)\"]\n```\n\n**Exceeds URL limit** (MUST be rejected by publishers): an event with 11 or more `url` tags.\n\n---\n\n## Dependencies\n\n- **NIP-01:** Event format, addressable events, relay communication, `REQ` filters.\n- **NIP-40:** `expiration` tag for time-limited announcements.\n- **RFC 9110 section 15.5.3:** HTTP 402 Payment Required status code. This NIP provides the discovery layer for services that use the 402 challenge-response pattern.\n\n## Reference Implementations\n\n| Implementation | Language | Purpose |\n| -------------- | -------- | ------- |\n| [402-announce](https://github.com/forgesworn/402-announce) | TypeScript | Build and publish kind 31402 events |\n| [toll-booth-announce](https://github.com/forgesworn/toll-booth-announce) | TypeScript | Bridge: toll-booth config to kind 31402 announcements |\n| [aperture-announce](https://github.com/forgesworn/aperture-announce) | Go | Aperture YAML config to kind 31402 announcements |\n| [402-pub](https://402.pub) | JavaScript | Live service directory (streams kind 31402 from relays) |\n| [402-mcp](https://github.com/forgesworn/402-mcp) | TypeScript | MCP server: AI agents discover, pay, and consume 402 APIs |\n| [402-indexer](https://github.com/forgesworn/402-indexer) | TypeScript | Nostr-native crawler for L402/x402 service discovery |\n| [toll-booth-dvm](https://github.com/forgesworn/toll-booth-dvm) | TypeScript | Bridge: expose toll-booth L402 APIs as NIP-90 DVMs (proves L402/DVM interop) |","sig":"1ee7dfd41a1e0200b98689f1b7eef3374a9303506c7a2c65e50db1cf05c5e63b1b8b87ea6704a41e51f19871a014ddd8f7dac0c725e82cd961944827137f69be"}