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 URI
cd 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.php
The 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.php
Nostr Wallet Connect Client Example
===================================
1. Parsing NWC URI...
β Wallet Pubkey: b889ff5b1513b641e2a139f661a661364979c5beee91842f8f0ef42ab558e9d4
β Relays: wss://relay.getalby.com/v1
β Secret: 71a8c14c...
β Lightning Address: wallet@example.com
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=true
for detailed output - Verify parameters before executing payments
- Check capabilities with
get-info-command.php
first
# 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.php
To 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