{"id":"f01d433ee9a5d1588342d111d62f0fa5e590ba56b82b830d19f9837c06505422","pubkey":"da19f1cd34beca44be74da4b306d9d1dd86b6343cef94ce22c49c6f59816e5bd","created_at":1774618193,"kind":30817,"tags":[["title","NIP-MATCHING: Competitive Matching & Selection"],["k","30576"],["k","30577"],["d","nip-matching"]],"content":"NIP-MATCHING\n============\n\nCompetitive Matching & Selection\n----------------------------------\n\n`draft` `optional`\n\nTwo addressable event kinds for competitive offer-and-selection workflows on Nostr — multiple providers publish offers in response to a request, and the requester selects the best match.\n\n> **Design principle:** Matching events coordinate selection — they do not enforce exclusivity or payment. The consuming application decides what happens after selection (task creation, payment initiation, contract signing).\n\n> **Standalone.** This NIP works independently on any Nostr application.\n\n## Motivation\n\nNostr has NIP-99 for classified listings and NIP-15 for marketplace storefronts, but no standard mechanism for **competitive bidding and selection**. Many workflows require multiple parties to compete for a single opportunity:\n\n- **Job boards** — freelancers submit proposals, the client selects one\n- **Reverse auctions** — suppliers bid on a buyer's requirements\n- **Bounties** — developers compete to solve a posted bounty\n- **Service requests** — a requester posts a need and reviews offers from multiple providers\n\nWithout a standard, each application invents its own offer/selection scheme. NIP-MATCHING provides a minimal, composable primitive that works alongside NIP-99 listings, NIP-15 storefronts, or any other Nostr-based request mechanism.\n\n## Relationship to Existing NIPs\n\n- **NIP-15 (Marketplace) and NIP-99 (Classified Listings):** These NIPs model buyer-seeks-seller: one merchant publishes a listing, many buyers browse. NIP-MATCHING models the reverse: one requester publishes a need, many providers compete with offers. The offer author is the provider (not the requester), offers are addressable for revision, and the selection event records which provider was chosen. This reverse-auction pattern has distinct relay filter requirements (discover all offers for a given context) that listings do not serve.\n- **\"Why not kind 1 replies?\":** Kind 1 replies are not addressable (a provider cannot revise their offer by republishing with the same `d` tag), not relay-filterable by originating context, and carry no structured pricing, timeline, or qualification tags. Competitive bidding requires structured, revisable, filterable offers.\n\n### Community NIPs\n\n- **Catallax (kinds 33400/33401/3402):** Catallax defines a complete escrow-backed gig system where patrons publish task proposals (kind 33401), workers are assigned, and arbiters conclude tasks (kind 3402). The matching in Catallax is implicit — workers are assigned to tasks, but there is no competitive offer/selection phase. NIP-MATCHING provides the missing competitive bidding primitive: multiple providers publish structured, revisable offers in response to a need, and the requester selects the best match. A Catallax-style gig platform could compose NIP-MATCHING for the provider selection step before creating a Catallax task proposal with the selected worker.\n\n## Relationship to State Machine Protocols\n\nNIP-MATCHING is a **standalone** competitive matching primitive. It works independently of any lifecycle or state machine protocol.\n\nWhen used alongside a state machine protocol (such as [AtoB](https://git.nostrdev.com/delog/atob)), the boundary is:\n\n- **NIP-MATCHING (kinds 30576-30577):** Use for competitive bidding **outside** a state machine lifecycle — bulletin board offers, open tenders, reverse auctions where no task lifecycle exists yet.\n\n- **State machine transitions (e.g. AtoB kind 7501 with `offer` trigger):** Use for competitive offers **within** an active lifecycle — the offer is a state transition that the state machine tracks, guards, and audits.\n\nAn implementation MAY compose NIP-MATCHING with task lifecycle protocols. The Kind 30577 (Selection) event MAY reference an external lifecycle event via an `e` tag.\n\n## Kinds\n\n| kind  | description         |\n| ----- | ------------------- |\n| 30576 | Matching Offer      |\n| 30577 | Matching Selection  |\n\nBoth kinds are addressable events (NIP-01). The `d` tag format ensures each event occupies a unique slot, allowing updates via republication.\n\n---\n\n## Matching Offer (`kind:30576`)\n\nPublished by a provider to offer their services in response to a request or announcement. The `d` tag format allows one offer per provider per context — providers can update their offer by republishing.\n\n```json\n{\n    \"kind\": 30576,\n    \"pubkey\": \"<provider-hex-pubkey>\",\n    \"created_at\": 1698769000,\n    \"tags\": [\n        [\"d\", \"bounty_fix_login_bug:offer:<provider-hex-pubkey>\"],\n        [\"t\", \"matching-offer\"],\n        [\"alt\", \"Matching offer: 50000 SAT to fix login bug\"],\n        [\"p\", \"<requester-hex-pubkey>\"],\n        [\"amount\", \"50000\"],\n        [\"currency\", \"SAT\"],\n        [\"expiration\", \"1698855400\"],\n        [\"e\", \"<request-event-id>\", \"wss://relay.example.com\"],\n        [\"estimated_duration_seconds\", \"259200\"]\n    ],\n    \"content\": \"I can fix this login bug within 3 days. I've worked on similar auth flows before — see my recent commits at https://example.com/portfolio. Includes testing and documentation.\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\nTags:\n\n* `d` (REQUIRED): Format `<context_id>:offer:<provider_pubkey>`. One offer per provider per context.\n* `t` (REQUIRED): Protocol family marker. MUST be `\"matching-offer\"`.\n* `p` (REQUIRED): Requester's hex pubkey.\n* `amount` (RECOMMENDED): Proposed price in smallest currency unit (pence for GBP, cents for USD, satoshis for SAT).\n* `currency` (RECOMMENDED): Currency code (e.g. `GBP`, `USD`, `EUR`, `SAT`).\n* `expiration` (RECOMMENDED): Unix timestamp — offer validity deadline. Clients SHOULD use NIP-40 `expiration` for relay-level enforcement.\n* `e` (RECOMMENDED): Event ID of the request or announcement being responded to.\n* `eta_minutes` (OPTIONAL): Estimated time of arrival or start, in minutes. Uses minutes for human-readable scheduling contexts (e.g. \"provider arrives in 15 minutes\").\n* `estimated_duration_seconds` (OPTIONAL): Estimated time to complete the work, in seconds. Uses seconds for precision in programmatic duration calculations.\n* `trust_model` (OPTIONAL): Preferred payment trust model (e.g. `escrow`, `direct`, `milestone`).\n* `ref` (OPTIONAL): External reference (portfolio link, previous work ID).\n\n**Content:** Plain text or NIP-44 encrypted JSON with the offer details — scope description, qualifications, portfolio links, methodology.\n\n---\n\n## Matching Selection (`kind:30577`)\n\nPublished by the requester to select one of the received offers. One selection per context.\n\n```json\n{\n    \"kind\": 30577,\n    \"pubkey\": \"<requester-hex-pubkey>\",\n    \"created_at\": 1698770000,\n    \"tags\": [\n        [\"d\", \"bounty_fix_login_bug:selection\"],\n        [\"t\", \"matching-selection\"],\n        [\"alt\", \"Matching selection: provider selected for login bug fix\"],\n        [\"e\", \"<selected-offer-event-id>\", \"wss://relay.example.com\"],\n        [\"p\", \"<selected-provider-hex-pubkey>\"]\n    ],\n    \"content\": \"\",\n    \"id\": \"<32-bytes lowercase hex>\",\n    \"sig\": \"<64-bytes lowercase hex>\"\n}\n```\n\nTags:\n\n* `d` (REQUIRED): Format `<context_id>:selection`. One selection per context.\n* `t` (REQUIRED): Protocol family marker. MUST be `\"matching-selection\"`.\n* `e` (REQUIRED): Event ID of the chosen Kind 30576 offer.\n* `p` (REQUIRED): Hex pubkey of the selected provider.\n* `reason` (OPTIONAL): Brief rationale for the selection.\n\n**Content:** Empty string or NIP-44 encrypted JSON with selection details.\n\n---\n\n## Protocol Flow\n\n```\n  Requester                      Relay                     Providers\n      |                            |                            |\n      |  (Request or announcement  |                            |\n      |   published via NIP-99,    |                            |\n      |   NIP-15, or any event)    |                            |\n      |                            |                            |\n      |                            |<-- kind:30576 Offer -------| Provider 1\n      |                            |    (amount: 50000 SAT)     |\n      |                            |                            |\n      |                            |<-- kind:30576 Offer -------| Provider 2\n      |                            |    (amount: 45000 SAT)     |\n      |                            |                            |\n      |                            |<-- kind:30576 Offer -------| Provider 3\n      |                            |    (amount: 60000 SAT)     |\n      |                            |                            |\n      |<---- offers received ------|                            |\n      |                            |                            |\n      |-- kind:30577 Selection --->|                            |\n      |  (selected: Provider 2)    |------- notification ------>| Provider 2\n      |                            |                            |\n      |                            |  (Provider 1, 3: not       |\n      |                            |   selected — implicit)     |\n      |                            |                            |\n```\n\n1. **Request:** The requester publishes a request via any mechanism (NIP-99 classified listing, NIP-15 marketplace request, or any other event). The request event is referenced by offers via `e` tags.\n2. **Offers:** Providers discover the request and publish `kind:30576` offers. Each provider can update their offer by republishing (addressable event).\n3. **Review:** The requester reviews received offers — comparing price, qualifications, timeline, and reputation.\n4. **Selection:** The requester publishes `kind:30577` selecting one offer. The selection references the chosen offer's event ID and the selected provider's pubkey.\n5. **Next steps:** The consuming application handles what happens after selection — task creation, payment initiation, contract formation, etc.\n\nThe following diagram illustrates the competitive offer, review, and selection sequence:\n\n\n![Competitive Selection Flow](https://raw.githubusercontent.com/forgesworn/nip-drafts/main/images/matching-1.png)\n\n```mermaid\nsequenceDiagram\n    autonumber\n    participant RQ as Requester\n    participant RL as Relay\n    participant P1 as Provider 1\n    participant P2 as Provider 2\n    participant P3 as Provider 3\n\n    rect rgb(27, 45, 61)\n        Note over RQ,P3: Request Published (NIP-99 or any event)\n        RQ->>RL: Request / announcement event\n    end\n\n    rect rgb(45, 45, 27)\n        Note over RQ,P3: Offers — providers compete\n        P1->>RL: kind:30576 Offer<br/>50,000 SAT, 3 days\n        P2->>RL: kind:30576 Offer<br/>45,000 SAT, 5 days\n        P3->>RL: kind:30576 Offer<br/>60,000 SAT, 2 days\n\n        Note over P2: Revises offer (addressable republish)\n        P2->>RL: kind:30576 Offer (updated)<br/>42,000 SAT, 4 days\n    end\n\n    rect rgb(27, 45, 61)\n        Note over RQ,P3: Review — requester evaluates\n        RL-->>RQ: All current offers\n        Note over RQ: Compares: price, timeline,<br/>reputation, qualifications\n    end\n\n    rect rgb(27, 61, 45)\n        Note over RQ,P3: Selection\n        RQ->>RL: kind:30577 Selection<br/>selected: Provider 2\n        RL-->>P2: Notification: selected\n        Note over P1,P3: Unselected providers:<br/>no explicit rejection event\n    end\n```\n\n## Example Applications\n\n### Nostr Bounty Boards\n\nA Nostr-native bounty platform can use matching for developer bounties. The requester posts a bounty (via NIP-99 or a custom kind), developers submit `kind:30576` offers with their proposed approach and price, and the requester selects the best candidate. The offer's `content` field serves as a mini-proposal.\n\n### Freelance Marketplaces\n\nNostr freelance platforms can use matching for project bidding. Clients post project requirements, freelancers submit offers with portfolio links and pricing, and clients select their preferred freelancer. The `estimated_duration_seconds` and `amount` tags enable structured comparison.\n\n### Reverse Auctions & Procurement\n\nBuyers can post procurement requirements and receive competitive bids from suppliers. The `amount` tag enables price comparison, while the `content` field allows suppliers to differentiate on quality, delivery terms, or other factors beyond price.\n\n### Community Role Selection\n\nNostr communities can use matching for role assignments — selecting moderators, event organisers, or project leads. Candidates submit `kind:30576` offers outlining their qualifications, and the community administrator publishes `kind:30577` to formalise the selection.\n\n## Test Vectors\n\nAll examples use timestamps around `1709280000` (2024-03-01) and placeholder hex pubkeys.\n\n### Kind 30576 — Matching Offer\n\nA provider offering a logo design service at a quoted price in response to a request.\n\n```json\n{\n  \"kind\": 30576,\n  \"pubkey\": \"b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3\",\n  \"created_at\": 1709280000,\n  \"tags\": [\n    [\"d\", \"logo_design_req_42:offer:b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3\"],\n    [\"t\", \"matching-offer\"],\n    [\"alt\", \"Matching offer: 75000 SAT for logo design package\"],\n    [\"p\", \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\"],\n    [\"amount\", \"75000\"],\n    [\"currency\", \"SAT\"],\n    [\"expiration\", \"1709366400\"],\n    [\"e\", \"dddd4444eeee5555ffff6666aaaa1111bbbb2222cccc3333dddd4444eeee5555\", \"wss://relay.example.com\"],\n    [\"estimated_duration_seconds\", \"604800\"]\n  ],\n  \"content\": \"I can deliver a full logo package within 7 days. Includes 3 concepts, 2 revision rounds, and source files.\",\n  \"id\": \"<32-byte-hex>\",\n  \"sig\": \"<64-byte-hex>\"\n}\n```\n\n### Kind 30577 — Matching Selection\n\nThe requester selects the winning offer from the above provider.\n\n```json\n{\n  \"kind\": 30577,\n  \"pubkey\": \"a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2\",\n  \"created_at\": 1709283600,\n  \"tags\": [\n    [\"d\", \"logo_design_req_42:selection\"],\n    [\"t\", \"matching-selection\"],\n    [\"alt\", \"Matching selection: provider selected for logo design\"],\n    [\"e\", \"aaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaaa1111bbbb2222\", \"wss://relay.example.com\"],\n    [\"p\", \"b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3\"],\n    [\"reason\", \"Best portfolio and competitive pricing\"]\n  ],\n  \"content\": \"\",\n  \"id\": \"<32-byte-hex>\",\n  \"sig\": \"<64-byte-hex>\"\n}\n```\n\n## Security Considerations\n\n* **Offer authenticity.** Each `kind:30576` offer is signed by the provider's keypair, ensuring offers cannot be forged. Clients SHOULD verify that the `pubkey` on the offer matches the provider's known identity.\n* **Selection finality.** The `kind:30577` selection is addressable, meaning the requester can change their selection by republishing. Applications that require selection finality SHOULD treat the first valid selection as canonical or use an additional confirmation mechanism.\n* **Offer expiry.** Offers with an `expiration` tag SHOULD be considered expired after the deadline. Requesters MUST NOT select expired offers. Relays MAY enforce this via NIP-40.\n* **Privacy.** Offer amounts and details are public by default. When competitive pricing is sensitive, the `content` field and pricing tags SHOULD be NIP-44 encrypted to the requester's pubkey.\n* **Sybil offers.** A single entity may submit multiple offers from different keypairs. Applications SHOULD use reputation signals (NIP-02 web of trust, NIP-58 badges) to filter for genuine providers.\n\n## Dependencies\n\n* [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md): Basic protocol flow, addressable events\n* [NIP-15](https://github.com/nostr-protocol/nips/blob/master/15.md): Nostr Marketplace (request discovery)\n* [NIP-40](https://github.com/nostr-protocol/nips/blob/master/40.md): Expiration timestamps (offer validity)\n* [NIP-44](https://github.com/nostr-protocol/nips/blob/master/44.md): Versioned encrypted payloads (private offer details)\n* [NIP-99](https://github.com/nostr-protocol/nips/blob/master/99.md): Classified Listings (request publication)\n\n## Reference Implementation\n\nNo public reference implementation exists yet. Implementors SHOULD refer to the kind definitions above.\n\nA minimal implementation requires:\n\n1. A Nostr client that supports addressable event publishing.\n2. Offer discovery logic — subscribing to `kind:30576` events matching a specific context (via `e` tag or `d` tag prefix).\n3. Selection publishing and notification to the selected provider.","sig":"0aa7d7caa6890257666d81e14e04d5385b76c89cf9fefab0d03b3b241a21bb1eca66401a7b068cc98bfd4f14f03bbf1f7ff4fec3a21adfed4a6b89c61741c954"}