Skip to main content

NIP-47: Nostr Wallet Connect

NIP-47 defines Nostr Wallet Connect (NWC), a protocol for connecting Lightning wallets to applications via Nostr.

Summary

AspectDetail
Kind23194 (request), 23195 (response)
PurposeWallet ↔ App communication
StatusMerged
Depends onNIP-01, NIP-04

Overview

NWC enables applications to:

  • Pay Lightning invoices
  • Create invoices
  • Check balances
  • List transactions

All communication is encrypted and passes through Nostr relays.

Connection String

nostr+walletconnect://<wallet_pubkey>?relay=<relay_url>&secret=<secret>

Parameters

ParameterRequiredDescription
wallet_pubkeyYesWallet service public key
relayYesRelay URL for communication
secretYesShared secret for encryption
lud16NoLightning address hint

Event Kinds

Request (kind 23194)

From app to wallet:

{
"kind": 23194,
"pubkey": "app_connection_pubkey",
"content": "<encrypted JSON-RPC request>",
"tags": [
["p", "wallet_service_pubkey"]
]
}

Response (kind 23195)

From wallet to app:

{
"kind": 23195,
"pubkey": "wallet_service_pubkey",
"content": "<encrypted JSON-RPC response>",
"tags": [
["p", "app_connection_pubkey"],
["e", "request_event_id"]
]
}

Methods

pay_invoice

Pay a Lightning invoice:

Request:

{
"method": "pay_invoice",
"params": {
"invoice": "lnbc1000n1..."
}
}

Response:

{
"result_type": "pay_invoice",
"result": {
"preimage": "0123456789abcdef..."
}
}

make_invoice

Create an invoice:

Request:

{
"method": "make_invoice",
"params": {
"amount": 1000,
"description": "Test payment",
"expiry": 3600
}
}

Response:

{
"result_type": "make_invoice",
"result": {
"type": "incoming",
"invoice": "lnbc1000n1...",
"description": "Test payment",
"payment_hash": "abc123...",
"amount": 1000,
"created_at": 1234567890,
"expiry": 3600
}
}

get_balance

Check wallet balance:

Request:

{
"method": "get_balance",
"params": {}
}

Response:

{
"result_type": "get_balance",
"result": {
"balance": 50000
}
}

get_info

Get wallet information:

Request:

{
"method": "get_info",
"params": {}
}

Response:

{
"result_type": "get_info",
"result": {
"alias": "My Wallet",
"color": "#ff9900",
"pubkey": "node_pubkey",
"network": "mainnet",
"block_height": 800000,
"methods": ["pay_invoice", "make_invoice", "get_balance", "get_info"]
}
}

lookup_invoice

Check invoice status:

Request:

{
"method": "lookup_invoice",
"params": {
"payment_hash": "abc123..."
}
}

list_transactions

Get transaction history:

Request:

{
"method": "list_transactions",
"params": {
"from": 1234567890,
"until": 1234657890,
"limit": 10,
"offset": 0,
"type": "incoming"
}
}

Error Codes

CodeDescription
RATE_LIMITEDToo many requests
NOT_IMPLEMENTEDMethod not supported
INSUFFICIENT_BALANCENot enough funds
PAYMENT_FAILEDPayment couldn't complete
NOT_FOUNDResource not found
QUOTA_EXCEEDEDBudget limit reached
RESTRICTEDOperation not allowed
UNAUTHORIZEDAuthentication failed
INTERNALServer error
OTHERUnspecified error

Error Response:

{
"result_type": "pay_invoice",
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Not enough funds in wallet"
}
}

Security

Encryption

All content is NIP-04 encrypted between app and wallet.

Budget Controls

Wallets SHOULD implement:

  • Daily/weekly/monthly limits
  • Per-transaction limits
  • Per-app quotas

Connection Management

  • Each connection uses unique keypair
  • Connections can be revoked
  • Apps should not reuse keys across users

Implementation

Client Side (App)

import { nip04 } from 'nostr-tools';

async function payInvoice(invoice) {
const request = {
method: 'pay_invoice',
params: { invoice }
};

const encrypted = await nip04.encrypt(
connectionSecret,
walletPubkey,
JSON.stringify(request)
);

const event = {
kind: 23194,
pubkey: connectionPubkey,
content: encrypted,
tags: [['p', walletPubkey]]
};

// Sign and publish
await publishEvent(event);

// Subscribe for response
const response = await waitForResponse(event.id);
return JSON.parse(await nip04.decrypt(
connectionSecret,
walletPubkey,
response.content
));
}

Service Side (Wallet)

// Subscribe to requests
const filter = {
kinds: [23194],
'#p': [walletServicePubkey]
};

relay.subscribe(filter, async (event) => {
// Decrypt request
const request = JSON.parse(await nip04.decrypt(
walletPrivkey,
event.pubkey,
event.content
));

// Process request
const result = await processRequest(request);

// Send encrypted response
const response = {
kind: 23195,
content: await nip04.encrypt(
walletPrivkey,
event.pubkey,
JSON.stringify(result)
),
tags: [
['p', event.pubkey],
['e', event.id]
]
};

await publishEvent(response);
});

Resources


Universal Protocol

NWC removes the need for custom integrations. Any NWC-compatible wallet works with any NWC-compatible app.