高級
NIP-47 錢包連接
深入了解 Nostr Wallet Connect (NWC),讓應用程式透過 Nostr 協議控制閃電網路錢包。
12 分鐘
什麼是 NIP-47?
NIP-47 定義了 Nostr Wallet Connect (NWC) 協議,讓應用程式可以透過 Nostr 中繼器與閃電網路錢包通訊。應用程式可以請求付款、產生發票、查詢餘額等, 而用戶保持對錢包的完全控制權。
核心優勢: NWC 讓用戶可以將任何支援的錢包連接到任何支援的應用程式, 無需在每個應用中設定錢包。一次連接,處處使用。
運作原理
錢包 (Wallet Service) 應用程式 (Client)
│ │
│ 1. 產生連接 URI │
│ nostr+walletconnect://... │
│ ─────────────────────────────────>│
│ │
│ 2. 掃描 QR Code 或輸入 URI │
│ │
│ 3. 發送加密請求 (kind 23194) │
│ <─────────────────────────────────│
│ │
│ 4. 處理請求(付款、產生發票等) │
│ │
│ 5. 發送加密回應 (kind 23195) │
│ ─────────────────────────────────>│
│ │ 連接 URI 格式
nostr+walletconnect://<wallet-pubkey>?
relay=wss://relay.example.com&
secret=<connection-secret>&
[email protected]
// 參數說明:
// wallet-pubkey: 錢包服務的公鑰
// relay: 用於通訊的中繼器(可多個)
// secret: 連接密鑰(用於加密通訊)
// lud16: 可選的 Lightning Address
// 實際範例
nostr+walletconnect://b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4?relay=wss%3A%2F%2Frelay.getalby.com%2Fv1&secret=12345abcde 事件類型
| Kind | 名稱 | 發送方 | 說明 |
|---|---|---|---|
| 23194 | Request | 應用程式 | 加密的 RPC 請求 |
| 23195 | Response | 錢包 | 加密的 RPC 回應 |
| 13194 | Info | 錢包 | 錢包資訊(支援的方法) |
支援的方法
付款相關
- pay_invoice
- pay_keysend
- multi_pay_invoice
- multi_pay_keysend
發票相關
- make_invoice
- lookup_invoice
- list_transactions
資訊查詢
- get_balance
- get_info
進階功能
- sign_message
- notifications
請求格式
// 請求事件 (kind 23194)
{
"kind": 23194,
"content": "<NIP-04 加密的 JSON>",
"tags": [
["p", "<錢包公鑰>"]
],
"pubkey": "<應用程式公鑰>",
...
}
// 解密後的 content 格式
{
"method": "pay_invoice",
"params": {
"invoice": "lnbc100n1p..."
}
}
// pay_invoice 請求
{
"method": "pay_invoice",
"params": {
"invoice": "lnbc100n1p...",
"amount": 1000 // 可選,覆蓋發票金額(毫聰)
}
}
// make_invoice 請求
{
"method": "make_invoice",
"params": {
"amount": 21000, // 毫聰
"description": "Coffee",
"description_hash": "...", // 可選
"expiry": 3600 // 可選,秒
}
}
// get_balance 請求
{
"method": "get_balance",
"params": {}
} 回應格式
// 回應事件 (kind 23195)
{
"kind": 23195,
"content": "<NIP-04 加密的 JSON>",
"tags": [
["p", "<應用程式公鑰>"],
["e", "<請求事件 ID>"]
],
"pubkey": "<錢包公鑰>",
...
}
// 成功回應
{
"result_type": "pay_invoice",
"result": {
"preimage": "abcdef123456..."
}
}
// make_invoice 成功回應
{
"result_type": "make_invoice",
"result": {
"type": "incoming",
"invoice": "lnbc100n1p...",
"description": "Coffee",
"description_hash": null,
"preimage": null,
"payment_hash": "abc123...",
"amount": 21000,
"fees_paid": 0,
"created_at": 1704067200,
"expires_at": 1704070800,
"settled_at": null
}
}
// get_balance 成功回應
{
"result_type": "get_balance",
"result": {
"balance": 100000000 // 毫聰
}
}
// 錯誤回應
{
"result_type": "pay_invoice",
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "餘額不足"
}
} 程式碼範例
解析連接 URI
function parseNwcUri(uri) {
// 移除前綴
const withoutPrefix = uri.replace('nostr+walletconnect://', '')
// 分離公鑰和參數
const [pubkey, queryString] = withoutPrefix.split('?')
// 解析參數
const params = new URLSearchParams(queryString)
return {
walletPubkey: pubkey,
relays: params.getAll('relay').map(decodeURIComponent),
secret: params.get('secret'),
lud16: params.get('lud16')
}
}
// 使用
const connection = parseNwcUri(
'nostr+walletconnect://abc123...?relay=wss%3A%2F%2Frelay.example.com&secret=xyz'
)
console.log(connection.walletPubkey)
console.log(connection.relays)
console.log(connection.secret) NWC 客戶端類別
import { nip04, generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools'
class NwcClient {
constructor(connectionUri) {
const parsed = parseNwcUri(connectionUri)
this.walletPubkey = parsed.walletPubkey
this.relays = parsed.relays
this.secret = parsed.secret
// 從 secret 派生密鑰對
this.privateKey = this.derivePrivateKey(parsed.secret)
this.publicKey = getPublicKey(this.privateKey)
}
derivePrivateKey(secret) {
// 使用 secret 作為種子派生私鑰
const encoder = new TextEncoder()
const data = encoder.encode(secret)
// 簡化示例,實際應使用 HKDF
return crypto.subtle.digest('SHA-256', data)
}
async sendRequest(method, params) {
// 1. 建立請求內容
const content = JSON.stringify({ method, params })
// 2. 加密內容(NIP-04)
const encrypted = await nip04.encrypt(
this.privateKey,
this.walletPubkey,
content
)
// 3. 建立事件
const event = finalizeEvent({
kind: 23194,
content: encrypted,
tags: [['p', this.walletPubkey]],
created_at: Math.floor(Date.now() / 1000)
}, this.privateKey)
// 4. 發送到中繼器
const relay = await this.connectRelay()
await relay.publish(event)
// 5. 等待回應
return this.waitForResponse(event.id)
}
async waitForResponse(requestId) {
return new Promise((resolve, reject) => {
const relay = this.relay
const sub = relay.subscribe([{
kinds: [23195],
'#e': [requestId],
'#p': [this.publicKey]
}])
const timeout = setTimeout(() => {
sub.close()
reject(new Error('請求逾時'))
}, 30000)
sub.on('event', async (event) => {
clearTimeout(timeout)
sub.close()
// 解密回應
const decrypted = await nip04.decrypt(
this.privateKey,
this.walletPubkey,
event.content
)
const response = JSON.parse(decrypted)
if (response.error) {
reject(new Error(response.error.message))
} else {
resolve(response.result)
}
})
})
}
// 便捷方法
async payInvoice(invoice, amount = null) {
const params = { invoice }
if (amount) params.amount = amount
return this.sendRequest('pay_invoice', params)
}
async makeInvoice(amount, description = '') {
return this.sendRequest('make_invoice', {
amount,
description
})
}
async getBalance() {
return this.sendRequest('get_balance', {})
}
async getInfo() {
return this.sendRequest('get_info', {})
}
}
// 使用
const nwc = new NwcClient('nostr+walletconnect://...')
const balance = await nwc.getBalance()
console.log(`餘額: ${balance.balance / 1000} sats`) 使用 @getalby/sdk
import { nwc } from '@getalby/sdk'
// 使用現成的 SDK 更簡單
const client = new nwc.NWCClient({
nostrWalletConnectUrl: 'nostr+walletconnect://...'
})
// 付款
const response = await client.payInvoice({
invoice: 'lnbc100n1p...'
})
console.log('付款成功,preimage:', response.preimage)
// 產生發票
const invoice = await client.makeInvoice({
amount: 21000, // 毫聰
description: '咖啡'
})
console.log('發票:', invoice.invoice)
// 查詢餘額
const balance = await client.getBalance()
console.log('餘額:', balance.balance, '毫聰')
// 取得錢包資訊
const info = await client.getInfo()
console.log('支援的方法:', info.methods) 支援的錢包
Alby
瀏覽器擴充錢包
完整 NWC 支援Mutiny Wallet
自託管網頁錢包
NWC 支援Umbrel
家用節點
NWC 應用程式Start9
個人伺服器
NWC 支援LNbits
帳戶系統
NWC 擴充Coinos
託管錢包
NWC 支援權限控制
錢包可以為每個連接設定權限限制:
// 錢包資訊事件 (kind 13194)
{
"kind": 13194,
"content": "pay_invoice get_balance get_info make_invoice",
"tags": [],
"pubkey": "<錢包公鑰>"
}
// content 列出支援的方法
// 應用程式應該檢查這個列表
// 常見權限配置範例:
// 只讀:get_balance get_info list_transactions lookup_invoice
// 收款:get_balance make_invoice lookup_invoice
// 完整:pay_invoice make_invoice get_balance get_info ...
// 預算限制(由錢包實作)
// - 單筆付款上限
// - 每日/每月付款上限
// - 總付款上限 錯誤代碼
| 代碼 | 說明 |
|---|---|
| RATE_LIMITED | 請求過於頻繁 |
| NOT_IMPLEMENTED | 方法不支援 |
| INSUFFICIENT_BALANCE | 餘額不足 |
| QUOTA_EXCEEDED | 超出預算限制 |
| RESTRICTED | 權限不足 |
| UNAUTHORIZED | 未授權 |
| INTERNAL | 內部錯誤 |
| PAYMENT_FAILED | 付款失敗 |
安全考量
安全特性
- • NIP-04 端對端加密
- • 連接特定的密鑰對
- • 可設定預算限制
- • 可隨時撤銷連接
注意事項
- • 謹慎分享連接 URI
- • 設定合理的預算限制
- • 定期檢查已連接的應用
- • 不要在不信任的設備上使用
延伸閱讀: NWC 是閃電網路與 Nostr 整合的重要橋樑。搭配 NIP-57 Zaps, 可以實現完全去中心化的社交打賞系統。
已複製連結