This project contains a complete client-side implementation of NIP-47 Nostr Wallet Connect for PHP, building upon nostr-php.
This project applied for the NWC Hackathon Grant on Geyser (Geyser project)
Async websocket communication is implemented using valtzu/guzzle-websocket-middleware, which is an opinionated decision and therefore the NWC functionality is provided as a separate library instead of added to the core functionality.
Nostr Wallet Connect (NWC) allows applications to connect to Lightning wallets over the Nostr protocol in a secure, decentralized way. This implementation provides:
- Client-side functionality for connecting to NWC wallet services
- Complete command support for all major Lightning operations
- Secure encryption using NIP-04 (deprecated) or NIP-44 (recommended) for wallet communications
- Comprehensive error handling with specific exception types
- Event-driven architecture following Nostr patterns
- Parse and validate NWC connection URIs
- Support for multiple relays and optional Lightning addresses
- Secure parameter validation and format checking
- Get Balance - Check wallet balance
- Pay Invoice - Pay Lightning invoices
- Make Invoice - Create Lightning invoices
- Lookup Invoice - Query invoice status
- Get Info - Retrieve wallet capabilities
- NIP-04/NIP-44 encryption for wallet communications
- Proper key management and validation
- Secure event signing and verification
- Request Events (kind 23194) - Encrypted commands to wallets
- Response Events (kind 23195) - Encrypted responses from wallets
- Info Events (kind 13194) - Public wallet capability announcements
use dsbaars\nostr\Nip47\NwcClient;
use dsbaars\nostr\Nip47\NwcUri;
// Parse NWC URI
$nwcUri = 'nostr+walletconnect://wallet_pubkey?relay=wss://relay.com&secret=client_secret';
$client = new NwcClient($nwcUri);
// Check wallet capabilities
$info = $client->getWalletInfo();
echo "Supported methods: " . implode(', ', $info->getMethods());$balance = $client->getBalance();
if ($balance->isSuccess()) {
echo "Balance: " . $balance->getBalance() . " msats";
echo "(" . $balance->getBalanceInSats() . " sats)";
}$invoice = $client->makeInvoice(
amount: 1000, // 1000 msats = 1 sat
description: "Test payment",
expiry: 3600 // 1 hour
);
if ($invoice->isSuccess()) {
echo "Invoice: " . $invoice->getInvoice();
echo "Payment hash: " . $invoice->getPaymentHash();
}$payment = $client->payInvoice("lnbc1000n1...");
if ($payment->isPaymentSuccessful()) {
echo "Payment successful!";
echo "Preimage: " . $payment->getPreimage();
}This implementation provides complete coverage of the NIP-47 specification. Below are detailed tables showing all functionality and implementation status.
| Kind | Description | Purpose | Implementation | Class |
|---|---|---|---|---|
| 13194 | Info Event | Wallet capability announcement | β | InfoEvent.php |
| 23194 | Request Event | Encrypted commands to wallet | β | RequestEvent.php |
| 23195 | Response Event | Encrypted responses from wallet | β | ResponseEvent.php |
| 23196 | Notification Event | Real-time wallet notifications | β | NotificationEvent.php |
| Command | Description | Parameters | Implementation | Class |
|---|---|---|---|---|
get_info |
Get wallet capabilities | None | β | GetInfoCommand.php |
get_balance |
Get wallet balance | None | β | GetBalanceCommand.php |
pay_invoice |
Pay Lightning invoice | invoice, amount? |
β | PayInvoiceCommand.php |
make_invoice |
Create Lightning invoice | amount, description?, description_hash?, expiry? |
β | MakeInvoiceCommand.php |
lookup_invoice |
Lookup invoice details | payment_hash?, invoice? |
β | LookupInvoiceCommand.php |
list_transactions |
List wallet transactions | from?, until?, limit?, offset?, unpaid?, type? |
β | ListTransactionsCommand.php |
pay_keysend |
Send keysend payment | amount, pubkey, preimage?, tlv_records? |
β | PayKeysendCommand.php |
multi_pay_invoice |
Pay multiple invoices | invoices[] |
β | MultiPayInvoiceCommand.php |
multi_pay_keysend |
Send multiple keysends | keysends[] |
β | MultiPayKeysendCommand.php |
| Response | Description | Fields | Implementation | Class |
|---|---|---|---|---|
get_info |
Wallet info response | alias, color, pubkey, network, block_height, block_hash, methods[], notifications[] |
β | GetInfoResponse.php |
get_balance |
Balance response | balance |
β | GetBalanceResponse.php |
pay_invoice |
Payment response | preimage, fees_paid? |
β | PayInvoiceResponse.php |
make_invoice |
Invoice creation response | type, invoice?, description?, description_hash?, preimage?, payment_hash, amount, fees_paid, created_at, expires_at?, metadata? |
β | MakeInvoiceResponse.php |
lookup_invoice |
Invoice lookup response | type, invoice?, description?, description_hash?, preimage?, payment_hash, amount, fees_paid, created_at, expires_at?, settled_at?, metadata? |
β | LookupInvoiceResponse.php |
list_transactions |
Transaction list response | transactions[] |
β | ListTransactionsResponse.php |
pay_keysend |
Keysend payment response | preimage, fees_paid? |
β | PayKeysendResponse.php |
multi_pay_invoice |
Multi-payment response | Multiple individual responses | β | MultiPayInvoiceResponse.php |
multi_pay_keysend |
Multi-keysend response | Multiple individual responses | β | MultiPayKeysendResponse.php |
| Type | Description | Fields | Implementation | Class |
|---|---|---|---|---|
payment_received |
Payment successfully received | type, invoice, description?, description_hash?, preimage, payment_hash, amount, fees_paid, created_at, expires_at?, settled_at, metadata? |
β | PaymentReceivedNotification.php |
payment_sent |
Payment successfully sent | type, invoice, description?, description_hash?, preimage, payment_hash, amount, fees_paid, created_at, expires_at?, settled_at, metadata? |
β | PaymentSentNotification.php |
| Code | Description | When Used | Implementation |
|---|---|---|---|
RATE_LIMITED |
Client sending commands too fast | Rate limiting exceeded | β |
NOT_IMPLEMENTED |
Command not known/implemented | Unsupported methods | β |
INSUFFICIENT_BALANCE |
Not enough funds available | Payment amount > balance | β |
QUOTA_EXCEEDED |
Spending quota exceeded | Budget limits reached | β |
RESTRICTED |
Operation not allowed | Permission denied | β |
UNAUTHORIZED |
No wallet connected | Invalid authorization | β |
INTERNAL |
Internal wallet error | Server-side issues | β |
OTHER |
Other unspecified error | Catch-all error | β |
PAYMENT_FAILED |
Payment processing failed | Routing/capacity issues | β |
NOT_FOUND |
Invoice not found | Invalid payment hash/invoice | β |
| Component | Description | Required | Format | Implementation |
|---|---|---|---|---|
| Protocol | NWC protocol identifier | β | nostr+walletconnect:// |
β |
| Pubkey | Wallet service public key | β | 32-byte hex | β |
relay |
Relay URL(s) for communication | β | WebSocket URL | β |
secret |
Client private key | β | 32-byte hex | β |
lud16 |
Lightning address | β | Lightning address format | β |
| Feature | Description | Implementation | Notes |
|---|---|---|---|
| NIP-04 Encryption | End-to-end encryption of commands/responses | β | Deprecated, using NIP-44 recommended |
| NIP-44 Encryption | End-to-end encryption of commands/responses | β | All wallet communications encrypted |
| Event Signing | Cryptographic signatures on all events | β | Prevents tampering |
| Key Isolation | Unique keys per wallet connection | β | Improves privacy |
| Relay Authentication | Optional relay-level auth | β | Metadata protection |
| Request Expiration | Time-bounded request validity | β | Prevents replay attacks |
| Feature | Description | Implementation | Class |
|---|---|---|---|
| WebSocket Communication | Real-time relay communication | β | NwcClient.php |
| Notification Listener | Real-time payment notifications | β | NwcNotificationListener.php |
| Multi-Command Support | Batch payment operations | β | MultiPay*Command.php |
| Filter Management | Subscription filtering | β | NwcClient.php |
| Connection Validation | URI and capability validation | β | NwcUri.php |
| Error Handling | Comprehensive exception system | β | Exception/ namespace |
| Logging Support | Configurable logging | β | PSR-3 compatible |
src/Nip47/
βββ NwcClient.php # Main client implementation
βββ NwcNotificationListener.php # Real-time notification listener
βββ NwcUri.php # URI parsing and validation
βββ NwcUriInterface.php # URI interface
βββ ErrorCode.php # NWC error codes enum
β
βββ Command/ # Command implementations
β βββ CommandInterface.php
β βββ AbstractCommand.php
β βββ GetBalanceCommand.php # β
get_balance
β βββ GetInfoCommand.php # β
get_info
β βββ PayInvoiceCommand.php # β
pay_invoice
β βββ MakeInvoiceCommand.php # β
make_invoice
β βββ LookupInvoiceCommand.php # β
lookup_invoice
β βββ ListTransactionsCommand.php # β
list_transactions
β βββ PayKeysendCommand.php # β
pay_keysend
β βββ MultiPayInvoiceCommand.php # β
multi_pay_invoice
β βββ MultiPayKeysendCommand.php # β
multi_pay_keysend
β
βββ Response/ # Response implementations
β βββ ResponseInterface.php
β βββ AbstractResponse.php
β βββ GetBalanceResponse.php # β
Balance data + conversions
β βββ GetInfoResponse.php # β
Wallet info + capability checks
β βββ PayInvoiceResponse.php # β
Payment confirmation
β βββ MakeInvoiceResponse.php # β
Invoice creation
β βββ LookupInvoiceResponse.php # β
Invoice details + status
β βββ ListTransactionsResponse.php # β
Transaction history
β βββ PayKeysendResponse.php # β
Keysend confirmation
β βββ MultiPayInvoiceResponse.php # β
Batch payment results
β βββ MultiPayKeysendResponse.php # β
Batch keysend results
β
βββ Event/ # Nostr event implementations
β βββ RequestEvent.php # β
kind 23194
β βββ ResponseEvent.php # β
kind 23195
β βββ InfoEvent.php # β
kind 13194
β βββ NotificationEvent.php # β
kind 23196
β
βββ Notification/ # Notification system
β βββ NotificationInterface.php
β βββ NotificationFactory.php # β
Factory for creating notifications
β βββ PaymentReceivedNotification.php # β
payment_received
β βββ PaymentSentNotification.php # β
payment_sent
β
βββ Exception/ # Exception hierarchy
βββ NwcException.php
βββ InvalidUriException.php
βββ CommandException.php
βββ PaymentFailedException.php
βββ InsufficientBalanceException.php
βββ UnauthorizedException.php
βββ RateLimitedException.php
This project includes working examples demonstrating all NIP-47 functionality with a centralized configuration system.
First, configure your wallet connection:
cd examples/
cp config.php.example config.php
# Edit config.php with your actual NWC URIcd examples/
# Complete wallet functionality demo
php client-example.php
# URI parsing and validation
php uri-parsing-example.php
# End-to-end payment workflow
php payment-flow-example.php
# Real-time notification listening
php notification-listener.php
# Simple wallet info and balance check
php get-info-command.phpThe examples use environment variables for easy configuration:
# Set your NWC URI
export NWC_URI="nostr+walletconnect://your-wallet-details"
# Enable verbose output
export NWC_VERBOSE=true
# Run any example
php client-example.phpNostr Wallet Connect Client Example
===================================
1. Parsing NWC URI...
β Wallet Pubkey: b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4
β Relays: wss://relay.getalby.com/v1
β Secret: 71a8c14c...
β Lightning Address: [email protected]
2. Creating NWC client...
β Client created with pubkey: a1b2c3d4e5f6789...
3. Getting wallet info...
β Wallet connected successfully!
- Alias: My Lightning Wallet
- Network: bitcoin
- Supported methods: get_balance, pay_invoice, make_invoice, lookup_invoice
- Supported notifications: payment_received, payment_sent
4. Getting wallet balance...
β Balance: 100000 msats
- In sats: 100 sats
- In BTC: 0.00000100 BTC
5. Creating invoice...
β Invoice created!
- Amount: 21000 msats
- Description: Test invoice from NWC client
- Invoice: lnbc210n1pjw8...
6. List transactions...
β Found 5 transactions
- Incoming: 3
- Outgoing: 2
- Total amount: 150000 msats
- Total fees: 2100 msats
Example completed successfully!
The implementation includes comprehensive error handling:
use dsbaars\nostr\Nip47\Exception\PaymentFailedException;
use dsbaars\nostr\Nip47\Exception\InsufficientBalanceException;
try {
$payment = $client->payInvoice($invoice);
} catch (PaymentFailedException $e) {
echo "Payment failed: " . $e->getMessage();
} catch (InsufficientBalanceException $e) {
echo "Insufficient balance: " . $e->getMessage();
} catch (NwcException $e) {
echo "General NWC error: " . $e->getMessage();
}This implementation supports:
- β All required event kinds (23194, 23195, 13194, 23196)
- β All standard commands (get_info, get_balance, pay_invoice, make_invoice, lookup_invoice, list_transactions, pay_keysend, multi_pay_invoice, multi_pay_keysend)
- β All notification types (payment_received, payment_sent)
- β All error codes per specification
- β Proper NIP-04/NIP-44 encryption
- β URI format validation
- β Relay communication
- β Event signing and verification
- β Real-time WebSocket notifications
- Encryption: All communications are encrypted using NIP-04/NIP-44
- Authentication: Events are signed with client private keys
- Validation: All inputs are validated before processing
- Error Handling: Sensitive information is not leaked in errors
- Timeouts: Commands have reasonable timeout limits
- Rate Limiting: Built-in support for rate limit handling
# Run all tests
php vendor/bin/phpunit tests/
# Run specific test file
php vendor/bin/phpunit tests/Nip47Test.php- Use testnet wallets for development
- Start with small amounts - examples use 1-100 sats by default
- Configure safely - set
NWC_VERBOSE=truefor detailed output - Verify parameters before executing payments
- Check capabilities with
get-info-command.phpfirst
# Use testnet amounts
export NWC_TEST_AMOUNT=1000 # 1 sat
export NWC_SMALL_AMOUNT=21000 # 21 sats
export NWC_MEDIUM_AMOUNT=100000 # 100 sats
# Enable verbose mode
export NWC_VERBOSE=true
# Test basic functionality
cd examples/
php get-info-command.php
php client-example.phpTo integrate NWC into your application:
- Parse the NWC URI from user input or configuration
- Create an NwcClient instance
- Check wallet capabilities with
getWalletInfo() - Implement your payment flow using the available commands
- Handle errors gracefully with proper exception handling