nostr-zap-integration
Nostrアプリケーションに、Lightning ZapsやNutzapsといった決済機能を実装し、Zapリクエストの作成や検証、LNURL-pay、Zap分割、Cashu/Nutzap送受信など、NostrとLightning/Cashu間のあらゆる決済連携を可能にするSkill。
📜 元の英語説明(参考)
Implement Lightning Zaps (NIP-57) and Nutzaps (NIP-61) for Nostr applications. Use when building zap request construction (kind:9734), zap receipt validation (kind:9735), LNURL-pay flows, zap splits, Cashu/Nutzap sending (kind:9321), nutzap receiving configuration (kind:10019), or any payment integration between Nostr and Lightning/Cashu.
🇯🇵 日本人クリエイター向け解説
Nostrアプリケーションに、Lightning ZapsやNutzapsといった決済機能を実装し、Zapリクエストの作成や検証、LNURL-pay、Zap分割、Cashu/Nutzap送受信など、NostrとLightning/Cashu間のあらゆる決済連携を可能にするSkill。
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o nostr-zap-integration.zip https://jpskill.com/download/9142.zip && unzip -o nostr-zap-integration.zip && rm nostr-zap-integration.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/9142.zip -OutFile "$d\nostr-zap-integration.zip"; Expand-Archive "$d\nostr-zap-integration.zip" -DestinationPath $d -Force; ri "$d\nostr-zap-integration.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
nostr-zap-integration.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
nostr-zap-integrationフォルダができる - 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 Zap 連携
概要
Nostr アプリケーション向けの正しい Lightning Zap および Nutzap のフローを構築します。このスキルは、NIP-57 のライフサイクル全体 (LNURL の検出、zap リクエストの構築、インボイスの処理、zap レシートの検証) と、NIP-61 の Cashu による代替手段 (nutzap の構成、P2PK トークンのミント、nutzap の公開と償還) をカバーします。
どのような時に使うべきか
- Nostr クライアントに "zap" 機能を実装する
- kind:9734 zap リクエストイベントを構築する
- kind:9735 zap レシートイベントを検証する
- LNURL-pay エンドポイントを Nostr と統合する
- 複数の
zapタグを持つイベントに対する zap 分割のサポートを構築する - NIP-61 Nutzaps (Cashu ベースの zaps) を実装する
- kind:10019 nutzap 受信構成を設定する
- P2PK ロックされたトークンを持つ kind:9321 nutzap イベントを構築する
- zap フローをデバッグする (タグの欠落、検証の失敗、金額の不一致)
使用すべきでない場合:
- Nostr とは関係のない一般的な Lightning/LNURL ウォレットを構築する場合
- リレー WebSocket プロトコルのロジックを実装する場合
- NIP-19 bech32 エンコーディングを扱う場合 (別の関心事)
- Nostr 統合なしで Cashu ウォレットを構築する場合
ワークフロー
1. 支払いパスを決定する
質問: "これは Lightning Zap (NIP-57) ですか、それとも Nutzap (NIP-61) ですか?"
| パス | いつ使うべきか | 主要な種類 |
|---|---|---|
| Lightning Zap | 受信者が lud16/lud06 を持っている、LNURL サーバー | 9734, 9735 |
| Nutzap (Cashu) | 受信者が kind:10019 を持っている、信頼できるミント | 10019, 9321 |
不明な場合は、受信者のプロファイル (kind:0) で lud16/lud06 フィールド (Lightning パス) を確認するか、kind:10019 イベント (Nutzap パス) をクエリします。
2. Lightning Zap フロー (NIP-57)
完全な実装については、references/zap-flow.md の手順に従ってください。概要:
ステップ 2a: LNURL エンドポイントを検出する
// lud16 (例: "bob@example.com") から
const [name, domain] = lud16.split("@");
const url = `https://${domain}/.well-known/lnurlp/${name}`;
const res = await fetch(url);
const lnurlPayData = await res.json();
// Nostr のサポートを確認
if (!lnurlPayData.allowsNostr || !lnurlPayData.nostrPubkey) {
throw new Error("Recipient does not support Nostr zaps");
}
LNURL レスポンスに対する重要なチェック:
allowsNostrはtrueでなければなりませんnostrPubkeyは有効な 32 バイトの 16 進数の公開鍵でなければなりません- 後で使用するために
callback、minSendable、maxSendableを保存します
ステップ 2b: Zap リクエストを構築する (kind:9734)
{
"kind": 9734,
"content": "オプションの zap コメント",
"tags": [
["relays", "wss://relay1.example.com", "wss://relay2.example.com"],
["amount", "21000"],
["lnurl", "lnurl1dp68gurn8ghj7..."],
["p", "<recipient-pubkey-hex>"],
["e", "<event-id-hex>"],
["k", "<event-kind-string>"]
]
}
必須タグ: relays (リレー URL のリスト)、p (受信者の公開鍵)。
推奨タグ: amount (ミリサトシを文字列として)、lnurl (bech32 エンコード)。
オプションのタグ: e (zap されるイベント)、a (アドレス指定可能なイベント座標)、k (zap されるイベントの種類を文字列として)。
重要: zap リクエストはリレーに公開されません。LNURL コールバック URL に送信されます。
ステップ 2c: コールバックに送信してインボイスを取得する
const zapRequestEncoded = encodeURIComponent(JSON.stringify(signedZapRequest));
const url =
`${callback}?amount=${amountMsats}&nostr=${zapRequestEncoded}&lnurl=${lnurlBech32}`;
const { pr: invoice } = await fetch(url).then((r) => r.json());
ステップ 2d: インボイスを支払う
bolt11 インボイスを Lightning ウォレットに渡して支払います。支払い後、受信者の LNURL サーバーは zap レシート (kind:9735) を作成して公開します。
ステップ 2e: Zap レシートを検証する
完全な検証ロジックについては、references/zap-flow.md を参照してください。3 つの重要なチェック:
- レシートの
pubkeyは、受信者の LNURLnostrPubkeyと一致しなければなりません bolt11タグのインボイス金額は、zap リクエストのamountと一致しなければなりませんSHA256(description)は、bolt11 の説明ハッシュと一致するはずです
3. Nutzap フロー (NIP-61)
完全な実装については、references/nutzap-flow.md の手順に従ってください。概要:
ステップ 3a: 受信者の Nutzap 構成を取得する (kind:10019)
{
"kind": 10019,
"tags": [
["relay", "wss://relay1.example.com"],
["relay", "wss://relay2.example.com"],
["mint", "https://mint.example.com", "sat"],
["mint", "https://othermint.example.com", "usd", "sat"],
["pubkey", "<p2pk-pubkey-hex>"]
]
}
重要: pubkey タグの値は、ユーザーのメイン Nostr 公開鍵であってはなりません。これは、P2PK ロック専用に使用される別のキーです。
ステップ 3b: P2PK ロックされたトークンをミントする
- 受信者の
mintタグからミントを選択します - 受信者の
pubkey値に P2PK ロックされたトークンをミントまたはスワップします - nostr-cashu との互換性のために、公開鍵に
"02"をプレフィックスとして付けます - DLEQ 証明 (NUT-12) を含めます
ステップ 3c: Nutzap を公開する (kind:9321)
{
"kind": 9321,
"content": "オプションのコメント",
"tags": [
["proof", "<cashu-proof-json>"],
["unit", "sat"],
["u", "https://mint.example.com"],
["e", "<zapped-event-id>", "<relay-hint>"],
["k", "<zapped-event-kind>"],
["p", "<recipient-nostr-pubkey>"]
]
}
受信者の kind:10019 relay タグにリストされているリレーに公開します。
ステップ 3d: Nutzaps を受信する
受信者は、信頼できるミント URL (#u) でフィルタリングされた、p-tagging された kind:9321 イベントをクエリします。受信時に、トークンをウォレットにスワップし、kind:7376 償還イベントを公開します。
4. Zap 分割
イベントに zap タグがある場合は、受信者間で zap を分配します。
["zap", "<pubkey>", "<relay>", "<weight>"]
重みは相対的です。パーセンテージを計算します。
const totalWeight = zapTags.reduce((sum, t) => sum + Number(t[3] || 0), 0);
for (const tag of zapTags) {
const weight = Number(tag[3] || 0);
const pct = weight / totalWeight;
const recipientAmount = Math.floor(totalAmount * pct);
// 個別の zap を作成
(原文がここで切り詰められています) 📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
Nostr Zap Integration
Overview
Build correct Lightning Zap and Nutzap flows for Nostr applications. This skill covers the full NIP-57 lifecycle (LNURL discovery, zap request construction, invoice handling, zap receipt validation) and the NIP-61 Cashu alternative (nutzap configuration, P2PK token minting, nutzap publishing and redemption).
When to Use
- Implementing "zap" functionality in a Nostr client
- Constructing kind:9734 zap request events
- Validating kind:9735 zap receipt events
- Integrating LNURL-pay endpoints with Nostr
- Building zap split support for events with multiple
zaptags - Implementing NIP-61 Nutzaps (Cashu-based zaps)
- Setting up kind:10019 nutzap receiving configuration
- Constructing kind:9321 nutzap events with P2PK-locked tokens
- Debugging zap flows (missing tags, validation failures, amount mismatches)
Do NOT use when:
- Building general Lightning/LNURL wallets unrelated to Nostr
- Implementing relay WebSocket protocol logic
- Working with NIP-19 bech32 encoding (separate concern)
- Building Cashu wallets without Nostr integration
Workflow
1. Determine the Payment Path
Ask: "Is this a Lightning Zap (NIP-57) or a Nutzap (NIP-61)?"
| Path | When to Use | Key Kinds |
|---|---|---|
| Lightning Zap | Recipient has lud16/lud06, LNURL server | 9734, 9735 |
| Nutzap (Cashu) | Recipient has kind:10019, trusted mints | 10019, 9321 |
If unsure, check the recipient's profile (kind:0) for lud16/lud06 fields
(Lightning path) or query for their kind:10019 event (Nutzap path).
2. Lightning Zap Flow (NIP-57)
Follow the steps in references/zap-flow.md for the complete implementation. Summary:
Step 2a: Discover the LNURL Endpoint
// From lud16 (e.g., "bob@example.com")
const [name, domain] = lud16.split("@");
const url = `https://${domain}/.well-known/lnurlp/${name}`;
const res = await fetch(url);
const lnurlPayData = await res.json();
// Verify Nostr support
if (!lnurlPayData.allowsNostr || !lnurlPayData.nostrPubkey) {
throw new Error("Recipient does not support Nostr zaps");
}
Critical checks on the LNURL response:
allowsNostrMUST betruenostrPubkeyMUST be a valid 32-byte hex public key- Save
callback,minSendable,maxSendablefor later use
Step 2b: Construct the Zap Request (kind:9734)
{
"kind": 9734,
"content": "Optional zap comment",
"tags": [
["relays", "wss://relay1.example.com", "wss://relay2.example.com"],
["amount", "21000"],
["lnurl", "lnurl1dp68gurn8ghj7..."],
["p", "<recipient-pubkey-hex>"],
["e", "<event-id-hex>"],
["k", "<event-kind-string>"]
]
}
Required tags: relays (list of relay URLs), p (recipient pubkey).
Recommended tags: amount (millisats as string), lnurl (bech32-encoded).
Optional tags: e (event being zapped), a (addressable event coordinate),
k (kind of zapped event as string).
Critical: The zap request is NOT published to relays. It is sent to the LNURL callback URL.
Step 2c: Send to Callback and Get Invoice
const zapRequestEncoded = encodeURIComponent(JSON.stringify(signedZapRequest));
const url =
`${callback}?amount=${amountMsats}&nostr=${zapRequestEncoded}&lnurl=${lnurlBech32}`;
const { pr: invoice } = await fetch(url).then((r) => r.json());
Step 2d: Pay the Invoice
Pass the bolt11 invoice to a Lightning wallet for payment. After payment, the recipient's LNURL server creates and publishes the zap receipt (kind:9735).
Step 2e: Validate Zap Receipts
See references/zap-flow.md for full validation logic. The three critical checks:
- Receipt
pubkeyMUST match the recipient's LNURLnostrPubkey - Invoice amount in
bolt11tag MUST matchamountin the zap request SHA256(description)SHOULD match the bolt11 description hash
3. Nutzap Flow (NIP-61)
Follow the steps in references/nutzap-flow.md for the complete implementation. Summary:
Step 3a: Fetch Recipient's Nutzap Configuration (kind:10019)
{
"kind": 10019,
"tags": [
["relay", "wss://relay1.example.com"],
["relay", "wss://relay2.example.com"],
["mint", "https://mint.example.com", "sat"],
["mint", "https://othermint.example.com", "usd", "sat"],
["pubkey", "<p2pk-pubkey-hex>"]
]
}
Critical: The pubkey tag value MUST NOT be the user's main Nostr pubkey.
It is a separate key used exclusively for P2PK locking.
Step 3b: Mint P2PK-Locked Tokens
- Choose a mint from the recipient's
minttags - Mint or swap tokens P2PK-locked to the recipient's
pubkeyvalue - Prefix the pubkey with
"02"for nostr-cashu compatibility - Include DLEQ proofs (NUT-12)
Step 3c: Publish the Nutzap (kind:9321)
{
"kind": 9321,
"content": "Optional comment",
"tags": [
["proof", "<cashu-proof-json>"],
["unit", "sat"],
["u", "https://mint.example.com"],
["e", "<zapped-event-id>", "<relay-hint>"],
["k", "<zapped-event-kind>"],
["p", "<recipient-nostr-pubkey>"]
]
}
Publish to the relays listed in the recipient's kind:10019 relay tags.
Step 3d: Receiving Nutzaps
Recipients query for kind:9321 events p-tagging them, filtered by trusted mint
URLs (#u). Upon receiving, swap the tokens into their wallet and publish a
kind:7376 redemption event.
4. Zap Splits
When an event has zap tags, distribute the zap across recipients:
["zap", "<pubkey>", "<relay>", "<weight>"]
Weights are relative. Calculate percentages:
const totalWeight = zapTags.reduce((sum, t) => sum + Number(t[3] || 0), 0);
for (const tag of zapTags) {
const weight = Number(tag[3] || 0);
const pct = weight / totalWeight;
const recipientAmount = Math.floor(totalAmount * pct);
// Create separate zap request for each recipient
}
Recipients without a weight value get weight 0 (no zap). If no weights are present on any tag, divide equally.
Checklist
- [ ] Identified payment path (Lightning vs Nutzap)
- [ ] For Lightning: LNURL endpoint discovered and verified (
allowsNostr,nostrPubkey) - [ ] For Lightning: Zap request (kind:9734) has required tags (
relays,p) - [ ] For Lightning: Zap request sent to callback URL, NOT published to relays
- [ ] For Lightning: Amount in millisats, within
minSendable/maxSendable - [ ] For Lightning: Zap receipt validation checks all three criteria
- [ ] For Nutzap: Recipient's kind:10019 fetched and parsed
- [ ] For Nutzap: Tokens minted at one of recipient's listed mints
- [ ] For Nutzap: P2PK pubkey prefixed with "02" and is NOT the main Nostr key
- [ ] For Nutzap: kind:9321 published to recipient's specified relays
- [ ] For splits: Weights calculated correctly, separate zap per recipient
Common Mistakes
| Mistake | Why It Breaks | Fix |
|---|---|---|
| Publishing kind:9734 to relays | Zap requests are sent to LNURL callback, never published | Send via HTTP GET to callback URL |
| Amount in satoshis instead of millisats | NIP-57 uses millisats (1 sat = 1000 msats) | Multiply sats by 1000 for the amount tag |
| Using recipient's Nostr pubkey for P2PK | NIP-61 requires a SEPARATE key for P2PK locking | Use the pubkey from kind:10019, never the main key |
Missing relays tag on zap request |
LNURL server won't know where to publish the receipt | Always include at least one relay in relays tag |
| Not validating receipt pubkey | Fake zap receipts from wrong keys accepted | Receipt pubkey MUST match LNURL nostrPubkey |
| Sending nutzap to unlisted mint | Recipient may never see it; tokens could be lost | Only use mints from recipient's kind:10019 mint tags |
| Missing "02" prefix on P2PK pubkey | Cashu P2PK expects compressed pubkey format | Always prefix with "02" for nostr-cashu compat |
Not checking allowsNostr on LNURL |
Server may not support Nostr zaps at all | Verify allowsNostr: true before constructing zap |
| Treating zap receipt as proof of payment | Receipts can be forged by rogue LNURL servers | Trust the receipt author, not the receipt itself |
Quick Reference
| Operation | Kind | Key Tags | Published? |
|---|---|---|---|
| Zap request | 9734 | relays, p, amount, lnurl, e |
NO (HTTP only) |
| Zap receipt | 9735 | p, P, bolt11, description, e |
YES (by LNURL server) |
| Nutzap config | 10019 | relay, mint, pubkey |
YES (replaceable) |
| Nutzap send | 9321 | proof, u, unit, p, e |
YES |
| Nutzap redeem | 7376 | e (9321 ref), p (sender) |
YES (encrypted) |
Key Principles
-
Zap requests are HTTP-only — Kind:9734 events are NEVER published to relays. They are signed, JSON-encoded, URI-encoded, and sent as a query parameter to the LNURL callback URL. This is the most common mistake.
-
Validate the full chain — A valid zap receipt requires matching the receipt pubkey to the LNURL
nostrPubkey, matching the invoice amount to the request amount, and verifying the description hash. Skipping any check allows forged zaps. -
Nutzap keys are separate — The P2PK pubkey in kind:10019 MUST be a different key from the user's main Nostr identity key. Using the same key would allow anyone to spend received tokens. Always prefix with "02".
-
Amounts are in millisatoshis — NIP-57 uses millisats everywhere (1 sat = 1000 msats). The
amounttag,minSendable,maxSendable, and invoice amounts are all in millisats. -
Trust boundaries matter — Zap receipts are NOT cryptographic proofs of payment. They prove that a LNURL server claims payment was received. The trust is in the LNURL server operator, not in the protocol itself.