| name | nostr-dvms |
| description | Implement NIP-90 Data Vending Machine clients or services when the task involves DVM job requests, feedback, results, payments, chaining, encrypted params, or NIP-89 discovery. |
Nostr Data Vending Machines (NIP-90)
Overview
Build AI and compute services on Nostr using the Data Vending Machine protocol.
DVMs follow a simple pattern: customers publish job requests (kind 5000-5999),
service providers process them and return results (kind 6000-6999), with
optional feedback events (kind 7000) for status updates and payment negotiation.
When to Use
- The task involves NIP-90 Data Vending Machine request, feedback, result, discovery, or payment flows.
- The user is building a DVM client, a DVM service provider, or chaining DVM jobs together.
- The request includes DVM job kinds, encrypted params, payment-required states, or NIP-89 discovery events.
- The problem is DVM-specific orchestration, not generic Nostr publishing.
Do NOT use when:
- The task is generic event construction without DVM semantics.
- The work is plain Lightning or Cashu integration outside a DVM workflow.
- The problem is relay transport or subscription infrastructure rather than DVM behavior.
Response format
Always structure the final response with these top-level sections, in this order:
- Summary — state the task, scope, and main conclusion in 1-3 sentences.
- Decision / Approach — state the key classification, assumptions, or chosen path.
- Artifacts — provide the primary deliverable(s) for this skill. Use clear subheadings for multiple files, commands, JSON payloads, queries, or documents.
- Validation — state checks performed, important risks, caveats, or unresolved questions.
- Next steps — list concrete follow-up actions, or write
None if nothing remains.
Rules:
- Do not omit a section; write
None when a section does not apply.
- If files are produced, list each file path under Artifacts before its contents.
- If commands, JSON, SQL, YAML, or code are produced, put each artifact in fenced code blocks with the correct language tag when possible.
- Keep section names exactly as written above so output stays predictable across skills.
Workflow
1. Determine Your Role
Ask: "Am I building a service provider, a client, or both?"
| Role | Publishes | Subscribes To |
|---|
| Service Provider | kind:6xxx results | kind:5xxx requests |
| kind:7000 feedback | kind:5 cancellations |
| kind:31990 NIP-89 info | |
| Customer/Client | kind:5xxx requests | kind:6xxx results |
| kind:5 cancellations | kind:7000 feedback |
2. Choose the Job Kind
Each DVM operation has a specific kind number. The result kind is always request
kind + 1000.
| Request Kind | Result Kind | Description |
|---|
| 5000 | 6000 | Text extraction |
| 5001 | 6001 | Summarization |
| 5002 | 6002 | Translation |
| 5003 | 6003 | Text generation |
| 5005 | 6005 | Content discovery/recommendation |
| 5050 | 6050 | Text-to-speech |
| 5100 | 6100 | Image generation |
| 5250 | 6250 | Event publishing |
See references/dvm-kinds.md for the full registry.
3. Build the Job Request (Customer Side)
Construct a kind 5000-5999 event:
{
"kind": 5001,
"content": "",
"tags": [
["i", "<data>", "<input-type>", "<relay>", "<marker>"],
["output", "<mime-type>"],
["relays", "wss://relay.example.com"],
["bid", "<msat-amount>"],
["t", "<topic-tag>"],
["p", "<preferred-service-provider-pubkey>"]
]
}
Input types — the second element of the i tag:
| Type | Meaning | Example |
|---|
text | Raw text data, no resolution needed | ["i", "Hello world", "text"] |
url | URL to fetch content from | ["i", "https://example.com/doc.pdf", "url"] |
event | Reference to a Nostr event by ID | ["i", "<event-id>", "event", "wss://relay"] |
job | Output of a previous DVM job (for chaining) | ["i", "<job-event-id>", "job"] |
Optional tags:
output — requested MIME type for the result (e.g., text/plain)
param — key/value parameters: ["param", "lang", "es"]
bid — max millisats the customer will pay
relays — where service providers should publish responses
p — preferred service provider pubkey (others MAY still respond)
t — topic tags for categorization
4. Handle Job Feedback (Kind 7000)
Service providers send feedback events to communicate status:
{
"kind": 7000,
"content": "",
"tags": [
["status", "<status>", "<extra-info>"],
["amount", "<msat>", "<bolt11>"],
["e", "<job-request-id>", "<relay-hint>"],
["p", "<customer-pubkey>"]
]
}
Status values:
| Status | Meaning | Action Required |
|---|
payment-required | SP requires payment before continuing | Customer must pay |
processing | SP is actively working on the job | Wait for result |
error | SP could not process the job | Check extra-info for why |
success | SP completed the job | Result incoming |
partial | SP has partial results (content may have samples) | More results coming |
Critical: payment-required is a hard gate — the SP will NOT proceed until
paid. Other statuses are informational.
The content field MAY contain partial results (e.g., a sample of processed
output) for any feedback status.
5. Publish Job Results (Service Provider Side)
Result kind = request kind + 1000 (e.g., 5001 → 6001):
{
"kind": 6001,
"content": "<result-payload>",
"tags": [
["request", "<stringified-original-job-request-event>"],
["e", "<job-request-id>", "<relay-hint>"],
["i", "<original-input-data>"],
["p", "<customer-pubkey>"],
["amount", "<msat>", "<optional-bolt11>"]
]
}
Required tags:
request — the FULL original job request event as a stringified JSON string
e — references the job request event ID
p — the customer's pubkey (so they can find the result)
Important: The request tag value is the entire job request event
serialized as a JSON string, not just the event ID.
6. Handle Payments
The payment model is flexible by design:
- Customer bids: Include
["bid", "<msat>"] in the request
- SP quotes: Include
["amount", "<msat>", "<bolt11>"] in feedback/result
- Customer pays: Either pay the bolt11 invoice OR zap the result event
Customer Service Provider
| |
|-- kind:5001 (bid: 5000) ---->|
| |
|<-- kind:7000 ----------------| status: payment-required
| amount: 3000, bolt11:... |
| |
|-- pay bolt11 or zap -------->|
| |
|<-- kind:7000 ----------------| status: processing
| |
|<-- kind:6001 ----------------| result + amount tag
SPs MUST use payment-required feedback to block until paid. They SHOULD NOT
silently wait for payment without signaling.
7. Implement Job Chaining
Chain jobs by using the job input type — the output of one job becomes the
input of the next:
{
"kind": 5001,
"content": "",
"tags": [
["i", "<translation-job-event-id>", "job"],
["param", "lang", "en"]
]
}
The service provider for job #2 watches for the result of job #1, then processes
it. Payment timing is at the SP's discretion — they may wait for the customer to
zap job #1's result before starting job #2.
Chaining example — translate then summarize:
Step 1: Publish kind:5002 (translation)
["i", "https://article.com/post", "url"]
["param", "lang", "en"]
Step 2: Publish kind:5001 (summarization)
["i", "<step-1-event-id>", "job"]
8. Add Encrypted Parameters (Optional)
For privacy, encrypt i and param tags using NIP-04 with the service
provider's pubkey:
- Collect all
i and param tags into a JSON array
- Encrypt with NIP-04 (customer's private key + SP's public key)
- Put encrypted payload in
content field
- Add
["encrypted"] tag and ["p", "<sp-pubkey>"] tag
- Remove plaintext
i and param tags from the event
{
"kind": 5001,
"content": "<nip04-encrypted-payload>",
"tags": [
["p", "<service-provider-pubkey>"],
["encrypted"],
["output", "text/plain"],
["relays", "wss://relay.example.com"]
]
}
The SP decrypts the content to recover the input parameters. If the request was
encrypted, the result MUST also be encrypted and tagged ["encrypted"].
9. Publish Service Provider Discovery (NIP-89)
Advertise DVM capabilities with a kind:31990 handler announcement:
{
"kind": 31990,
"content": "{\"name\":\"My Summarizer DVM\",\"about\":\"AI-powered text summarization\"}",
"tags": [
["d", "<unique-identifier>"],
["k", "5001"],
["t", "summarization"],
["t", "ai"]
]
}
k tag: the job request kind this DVM handles (e.g., 5001)
t tags: topic tags for discoverability
content: JSON with name and about fields (like kind:0 metadata)
10. Handle Cancellation
Customers cancel jobs by publishing a kind:5 deletion request:
{
"kind": 5,
"tags": [
["e", "<job-request-event-id>"],
["k", "5001"]
],
"content": "No longer needed"
}
Service providers SHOULD monitor for kind:5 events tagging their active jobs and
stop processing if a cancellation is received.
Checklist
Example: Complete Summarization DVM Service Provider
Scenario: Build a service provider that handles kind:5001 summarization
requests.
Subscribe to job requests
const sub = relay.subscribe([{ kinds: [5001] }]);
Process a request
async function handleJobRequest(event: NostrEvent) {
const customerPubkey = event.pubkey;
const jobId = event.id;
await publishEvent({
kind: 7000,
content: "",
tags: [
["status", "processing", "Starting summarization"],
["e", jobId, "wss://relay.example.com"],
["p", customerPubkey],
],
});
const inputTag = event.tags.find((t) => t[0] === "i");
const inputType = inputTag[2];
let inputData: string;
if (inputType === "text") {
inputData = inputTag[1];
} else if (inputType === "url") {
inputData = await fetch(inputTag[1]).then((r) => r.text());
} else if (inputType === "event") {
inputData = await fetchNostrEvent(inputTag[1], inputTag[3]);
}
const summary = await summarize(inputData);
await publishEvent({
kind: 6001,
content: summary,
tags: [
["request", JSON.stringify(event)],
["e", jobId, "wss://relay.example.com"],
["i", inputTag[1], inputTag[2]],
["p", customerPubkey],
["amount", "1000", generateBolt11(1000)],
],
});
}
Common Mistakes
| Mistake | Why It Breaks | Fix |
|---|
| Result kind doesn't match request kind + 1000 | Clients can't correlate results to requests | kind:5001 → kind:6001, always add 1000 |
Missing request tag in result | Clients can't verify the result matches their request | Include full stringified job request event |
request tag contains event ID instead of full event | Spec requires the complete event JSON as a string | Use JSON.stringify(originalEvent) |
Using payment-required without amount tag | Customer has no way to pay | Always include ["amount", "<msat>", "<bolt11>"] |
Forgetting p tag in result/feedback | Customer can't find the result via subscription | Always tag the customer's pubkey |
| Encrypting request but not result | Leaks the output even though input was private | If request has ["encrypted"], result must too |
Using wrong input-type in i tag | SP can't resolve the input data | text=raw, url=fetch, event=nostr lookup, job=chain |
Chaining with event type instead of job | SP treats it as a static event, not a job output | Use ["i", "<id>", "job"] for chaining |
| Not monitoring for kind:5 cancellations | Wastes compute on cancelled jobs | Subscribe to kind:5 events tagging active jobs |
NIP-89 announcement missing k tag | Clients can't discover which kinds the DVM supports | Include ["k", "5001"] for each supported kind |
Key Principles
-
Result kind = request kind + 1000 — This is the fundamental mapping.
kind:5001 always produces kind:6001. No exceptions.
-
The request tag carries the full event — Not just the ID. The entire
original job request event must be stringified and included so clients can
verify the result matches their request without additional lookups.
-
Payment is flexible, signaling is not — SPs can choose when to require
payment, but they MUST use payment-required feedback to signal it. Silent
blocking creates a broken UX.
-
Encrypted in = encrypted out — If the job request uses encrypted params,
the result MUST also be encrypted. Partial encryption leaks data.
-
Job chaining uses the job input type — Not event. The job type
tells the SP to wait for and use the output of a previous DVM job, not just
read a static event.
Optimization Notes
- Preserve the user's requested output shape exactly and do not substitute generic advice for concrete artifacts.
- Include exact commands, code structures, protocol fields, tags, parameters, file paths, or deliverable sections when the task asks for them.
- Make safety gates explicit before irreversible, destructive, externally visible, or compliance-sensitive actions.
- For multi-step work, present steps in execution order and include validation or rollback checks where relevant.
- Avoid overfitting to a single eval example: express lessons as reusable rules, not as task-specific answers.