nostr-dvms
NostrのDVM(データ自動販売機)サービスやクライアントを構築し、AIや計算サービスを提供したり、支払い処理を行うクライアントを作成したり、ジョブを連携させたりする際に活用できるSkill。
📜 元の英語説明(参考)
Build Nostr Data Vending Machine (DVM) services and clients using NIP-90. Use when implementing AI/compute service providers (kinds 5000-5999 job requests, 6000-6999 job results, kind 7000 feedback), creating DVM job request clients with payment handling, chaining DVM jobs, handling encrypted DVM params, or publishing NIP-89 service provider discovery events for DVMs.
🇯🇵 日本人クリエイター向け解説
NostrのDVM(データ自動販売機)サービスやクライアントを構築し、AIや計算サービスを提供したり、支払い処理を行うクライアントを作成したり、ジョブを連携させたりする際に活用できるSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o nostr-dvms.zip https://jpskill.com/download/9134.zip && unzip -o nostr-dvms.zip && rm nostr-dvms.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/9134.zip -OutFile "$d\nostr-dvms.zip"; Expand-Archive "$d\nostr-dvms.zip" -DestinationPath $d -Force; ri "$d\nostr-dvms.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
nostr-dvms.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
nostr-dvmsフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-18
- 取得日時
- 2026-05-18
- 同梱ファイル
- 1
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
Nostr Data Vending Machines (NIP-90)
概要
Data Vending Machine プロトコルを使用して、Nostr 上で AI およびコンピューティングサービスを構築します。 DVM はシンプルなパターンに従います。顧客はジョブリクエスト(kind 5000-5999)を発行し、 サービスプロバイダーはそれらを処理して結果(kind 6000-6999)を返し、 オプションでステータス更新および支払い交渉のためのフィードバックイベント(kind 7000)を送信します。
どのような時に使うべきか
- ジョブリクエストを処理する DVM サービスプロバイダーを構築する
- DVM ジョブリクエストを発行し、結果を処理するクライアントを作成する
- DVM サービスの支払いフロー(bid、amount、bolt11)を実装する
- 複数の DVM ジョブを連鎖させる(あるジョブの出力が別のジョブへの入力となる)
- プライバシーのために暗号化されたパラメータを DVM リクエストに追加する
- DVM の発見可能性のために NIP-89 ハンドラーアナウンスを発行する
- DVM フィードバックの状態(processing、payment-required、error、partial)を処理する
使用すべきでない場合:
- 一般的な Nostr イベントを構築する場合(nostr-event-builder を使用)
- リレー WebSocket ロジックを実装する場合
- DVM コンテキスト外で Lightning 支払いを扱う場合
ワークフロー
1. 自分の役割を決定する
「サービスプロバイダー、クライアント、またはその両方を構築するのか?」と自問してください。
| 役割 | 発行するもの | サブスクライブするもの |
|---|---|---|
| サービスプロバイダー | kind:6xxx results | kind:5xxx requests |
| kind:7000 feedback | kind:5 cancellations | |
| kind:31990 NIP-89 info | ||
| 顧客/クライアント | kind:5xxx requests | kind:6xxx results |
| kind:5 cancellations | kind:7000 feedback |
2. ジョブの Kind を選択する
各 DVM 操作には特定の kind 番号があります。結果の kind は常にリクエストの kind + 1000 です。
| リクエスト Kind | 結果 Kind | 説明 |
|---|---|---|
| 5000 | 6000 | テキスト抽出 |
| 5001 | 6001 | 要約 |
| 5002 | 6002 | 翻訳 |
| 5003 | 6003 | テキスト生成 |
| 5005 | 6005 | コンテンツ発見/レコメンデーション |
| 5050 | 6050 | テキスト読み上げ |
| 5100 | 6100 | 画像生成 |
| 5250 | 6250 | イベント発行 |
完全なレジストリについては、references/dvm-kinds.md を参照してください。
3. ジョブリクエストを構築する(顧客側)
kind 5000-5999 のイベントを構築します。
{
"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>"]
]
}
入力タイプ — i タグの 2 番目の要素:
| タイプ | 意味 | 例 |
|---|---|---|
text |
生のテキストデータ、解決は不要 | ["i", "Hello world", "text"] |
url |
コンテンツをフェッチする URL | ["i", "https://example.com/doc.pdf", "url"] |
event |
ID による Nostr イベントへの参照 | ["i", "<event-id>", "event", "wss://relay"] |
job |
前の DVM ジョブの出力(連鎖用) | ["i", "<job-event-id>", "job"] |
オプションのタグ:
output— 結果に要求される MIME タイプ(例:text/plain)param— キー/値パラメータ:["param", "lang", "es"]bid— 顧客が支払う最大ミリサトrelays— サービスプロバイダーが応答を発行する場所p— 優先サービスプロバイダーの pubkey(他のプロバイダーも応答する可能性があります)t— 分類のためのトピックタグ
4. ジョブフィードバックを処理する(Kind 7000)
サービスプロバイダーは、ステータスを伝達するためにフィードバックイベントを送信します。
{
"kind": 7000,
"content": "",
"tags": [
["status", "<status>", "<extra-info>"],
["amount", "<msat>", "<bolt11>"],
["e", "<job-request-id>", "<relay-hint>"],
["p", "<customer-pubkey>"]
]
}
ステータス値:
| ステータス | 意味 | 必要なアクション |
|---|---|---|
payment-required |
SP は続行する前に支払いを要求します | 顧客は支払う必要があります |
processing |
SP はジョブを積極的に処理しています | 結果を待ちます |
error |
SP はジョブを処理できませんでした | 理由について extra-info を確認してください |
success |
SP はジョブを完了しました | 結果が届きます |
partial |
SP は部分的な結果を持っています(コンテンツにサンプルが含まれている可能性があります) | より多くの結果が届きます |
重要: payment-required はハードゲートです — SP は支払われるまで続行しません。
他のステータスは情報提供です。
content フィールドには、任意のフィードバックステータスについて、部分的な結果(例えば、処理された出力のサンプル)が含まれている可能性があります。
5. ジョブの結果を発行する(サービスプロバイダー側)
結果の kind = リクエストの kind + 1000(例: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>"]
]
}
必須タグ:
request— 文字列化された JSON 文字列としての完全な元のジョブリクエストイベントe— ジョブリクエストイベント ID を参照しますp— 顧客の pubkey(結果を見つけられるように)
重要: request タグの値は、イベント ID だけでなく、JSON 文字列としてシリアライズされたジョブリクエストイベント全体です。
6. 支払いを処理する
支払いモデルは
(原文がここで切り詰められています)
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
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
- Building a DVM service provider that processes job requests
- Creating a client that publishes DVM job requests and handles results
- Implementing payment flows for DVM services (bid, amount, bolt11)
- Chaining multiple DVM jobs (output of one feeds into another)
- Adding encrypted parameters to DVM requests for privacy
- Publishing NIP-89 handler announcements for DVM discoverability
- Handling DVM feedback states (processing, payment-required, error, partial)
Do NOT use when:
- Building general Nostr events (use nostr-event-builder)
- Implementing relay WebSocket logic
- Working with Lightning payments outside the DVM context
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 payrelays— where service providers should publish responsesp— 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 stringe— references the job request event IDp— 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
iandparamtags into a JSON array - Encrypt with NIP-04 (customer's private key + SP's public key)
- Put encrypted payload in
contentfield - Add
["encrypted"]tag and["p", "<sp-pubkey>"]tag - Remove plaintext
iandparamtags 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"]
]
}
ktag: the job request kind this DVM handles (e.g.,5001)ttags: topic tags for discoverabilitycontent: JSON withnameandaboutfields (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
- [ ] Job request uses correct kind (5000-5999) for the operation type
- [ ] Input
itags use valid input-type (text,url,event,job) - [ ] Job result kind = request kind + 1000
- [ ] Result includes
requesttag with full stringified job request event - [ ] Result includes
etag referencing the job request event ID - [ ] Result includes
ptag with customer's pubkey - [ ] Feedback events use kind:7000 with valid status values
- [ ]
payment-requiredfeedback includesamounttag with bolt11 - [ ] Encrypted requests have
["encrypted"]tag and encrypted content - [ ] Encrypted results also use
["encrypted"]tag - [ ] Job chains use
["i", "<event-id>", "job"]input type - [ ] NIP-89 discovery uses kind:31990 with
ktag for supported job kind - [ ] Cancellation uses kind:5 with
etag referencing the job request
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;
// 1. Send processing feedback
await publishEvent({
kind: 7000,
content: "",
tags: [
["status", "processing", "Starting summarization"],
["e", jobId, "wss://relay.example.com"],
["p", customerPubkey],
],
});
// 2. Extract input data
const inputTag = event.tags.find((t) => t[0] === "i");
const inputType = inputTag[2]; // "text", "url", "event", "job"
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]);
}
// 3. Process the job
const summary = await summarize(inputData);
// 4. Publish result (kind = 5001 + 1000 = 6001)
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
requesttag 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-requiredfeedback 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
jobinput type — Notevent. Thejobtype tells the SP to wait for and use the output of a previous DVM job, not just read a static event.