NAME
Net::Nostr::WalletConnect - NIP-47 Nostr Wallet Connect
SYNOPSIS
use Net::Nostr::WalletConnect;
# Parse a connection URI
my $conn = Net::Nostr::WalletConnect->parse_uri(
'nostr+walletconnect://b889ff5b...?relay=wss%3A%2F%2Frelay.damus.io&secret=71a8c14c...'
);
say $conn->wallet_pubkey;
say $conn->secret;
say $conn->relays->[0];
# Create a connection URI
my $uri = Net::Nostr::WalletConnect->create_uri(
wallet_pubkey => $pubkey,
relay => 'wss://relay.damus.io',
secret => $secret,
lud16 => 'alice@example.com',
);
# Parse wallet service info event
my $info = Net::Nostr::WalletConnect->parse_info($info_event);
say join ', ', @{$info->capabilities};
say $info->preferred_encryption; # 'nip44_v2' or 'nip04'
# Build a request payload (JSON string, ready for encryption)
my $payload = Net::Nostr::WalletConnect->request(
method => 'pay_invoice',
params => { invoice => 'lnbc50n1...' },
);
# Build a request event (kind 23194)
my $event = Net::Nostr::WalletConnect->request_event(
method => 'pay_invoice',
params => { invoice => 'lnbc50n1...' },
pubkey => $client_pubkey,
wallet_pubkey => $wallet_pubkey,
encryption => 'nip44_v2',
);
# Parse a decrypted response
my $resp = Net::Nostr::WalletConnect->parse_response($decrypted_json);
if ($resp->is_error) {
warn $resp->error_code . ': ' . $resp->error_message;
} else {
say $resp->result->{preimage};
}
# Parse a decrypted notification
my $notif = Net::Nostr::WalletConnect->parse_notification($decrypted_json);
say $notif->notification_type; # 'payment_received'
say $notif->notification->{amount};
DESCRIPTION
Implements NIP-47 Nostr Wallet Connect (NWC), a protocol for clients to interact with a remote lightning wallet through encrypted nostr messages. Communication happens via E2E-encrypted direct messages over nostr relays using dedicated ephemeral keys.
The protocol uses four event kinds:
Info event (kind 13194) - Published by the wallet service to indicate supported capabilities.
Request (kind 23194) - Sent by the client to the wallet service.
Response (kind 23195) - Sent by the wallet service back to the client.
Notification (kind 23197, or 23196 for NIP-04 backwards compatibility) - Push notifications from the wallet service to the client.
Encryption of the content field is handled separately using NIP-44 (preferred) or NIP-04 (deprecated, for backwards compatibility). This module handles the payload structure and event creation; the caller is responsible for encrypting/decrypting the content.
Supported commands: pay_invoice, pay_keysend, make_invoice, lookup_invoice, list_transactions, get_balance, get_info, make_hold_invoice, cancel_hold_invoice, settle_hold_invoice.
CLASS METHODS
parse_uri
my $conn = Net::Nostr::WalletConnect->parse_uri($uri_string);
Parses a nostr+walletconnect:// connection URI. Returns a "Connection" object. The pubkey is lowercased during parsing to ensure consistency. Croaks if the URI is malformed or missing required parameters (relay, secret).
create_uri
my $uri = Net::Nostr::WalletConnect->create_uri(
wallet_pubkey => $hex_pubkey, # required
relay => $url_or_arrayref, # required
secret => $hex_secret, # required
lud16 => 'user@domain.com', # optional
);
Creates a nostr+walletconnect:// URI string. The relay parameter accepts a single URL string or an arrayref of URLs.
info_event
my $event = Net::Nostr::WalletConnect->info_event(
pubkey => $wallet_pubkey,
capabilities => [qw(pay_invoice get_balance)],
encryption => [qw(nip44_v2 nip04)], # optional
notifications => [qw(payment_received)], # optional
);
Creates a kind 13194 info Net::Nostr::Event. The capabilities are joined as a space-separated string in the content field.
parse_info
my $info = Net::Nostr::WalletConnect->parse_info($event);
Parses a kind 13194 info event. Returns an "Info" object. Croaks if the event is not kind 13194. If the event has no encryption tag, defaults to ['nip04'] per spec.
request
my $json = Net::Nostr::WalletConnect->request(
method => 'pay_invoice',
params => { invoice => 'lnbc50n1...' },
);
Builds a JSON-encoded request payload string. This is the content that should be encrypted before placing in the event. Croaks if method or params is missing.
request_event
my $event = Net::Nostr::WalletConnect->request_event(
method => 'pay_invoice',
params => { invoice => 'lnbc50n1...' },
pubkey => $client_pubkey,
wallet_pubkey => $wallet_pubkey,
encryption => 'nip44_v2', # optional
expiration => $unix_timestamp, # optional
);
Creates a kind 23194 request Net::Nostr::Event with the JSON payload as unencrypted content (the caller should encrypt before publishing). Includes a p tag with the wallet service pubkey, and optionally encryption and expiration tags. Croaks if pubkey is missing or not 64-char lowercase hex.
parse_response
my $resp = Net::Nostr::WalletConnect->parse_response($json);
Parses a decrypted JSON response payload. Returns a "Response" object. Croaks if result_type is missing.
response_event
my $event = Net::Nostr::WalletConnect->response_event(
result_type => 'pay_invoice',
result => { preimage => '...' }, # or undef on error
error => undef, # or { code => '...', message => '...' }
pubkey => $wallet_pubkey,
client_pubkey => $client_pubkey,
request_id => $request_event_id,
);
Creates a kind 23195 response Net::Nostr::Event with p and e tags. Croaks if pubkey is missing or not 64-char lowercase hex.
parse_notification
my $notif = Net::Nostr::WalletConnect->parse_notification($json);
Parses a decrypted JSON notification payload. Returns a "Notification" object. Croaks if notification_type or notification is missing.
notification_event
my $event = Net::Nostr::WalletConnect->notification_event(
notification_type => 'payment_received',
notification => { type => 'incoming', amount => 50000 },
pubkey => $wallet_pubkey,
client_pubkey => $client_pubkey,
encryption => 'nip44_v2', # or 'nip04' for kind 23196
);
Creates a notification Net::Nostr::Event with a p tag. Uses kind 23197 by default (NIP-44 encryption). Pass encryption => 'nip04' to create a kind 23196 event for NIP-04 backwards compatibility. Wallet services supporting both should publish both kinds for each notification. Croaks if pubkey is missing or not 64-char lowercase hex.
validate_metadata
Net::Nostr::WalletConnect->validate_metadata($hashref);
Validates that metadata does not exceed 4096 characters when JSON-encoded. Returns true on success, croaks on failure.
validate_request
Net::Nostr::WalletConnect->validate_request($event);
Validates that a request event is kind 23194 and has a p tag. Croaks on failure.
validate_response
Net::Nostr::WalletConnect->validate_response($event);
Validates that a response event is kind 23195. Croaks on failure.
is_expired
my $bool = Net::Nostr::WalletConnect->is_expired($event);
Returns true if the event has an expiration tag with a timestamp in the past.
OBJECTS
Connection
Returned by "parse_uri". Croaks on unknown arguments.
wallet_pubkey- 32-byte hex public key of the wallet servicerelays- Arrayref of relay URLssecret- 32-byte hex secret for the clientlud16- Lightning address (optional)
Info
Returned by "parse_info". Croaks on unknown arguments.
capabilities- Arrayref of supported command namesencryption- Arrayref of supported encryption schemesnotification_types- Arrayref of supported notification typessupports_capability($name)- Returns true if the capability is supportedsupports_encryption($scheme)- Returns true if the encryption scheme is supportedpreferred_encryption- Returns'nip44_v2'if supported, otherwise the first available scheme
Response
Returned by "parse_response". Croaks on unknown arguments or missing result_type.
result_type- The method name this response corresponds toresult- Hashref with result data, or undef on errorerror- Hashref withcodeandmessage, or undef on successis_error- Returns true if this is an error responseerror_code- Returns the error code string, or undeferror_message- Returns the human-readable error message, or undef
Error codes: RATE_LIMITED, NOT_IMPLEMENTED, INSUFFICIENT_BALANCE, QUOTA_EXCEEDED, RESTRICTED, UNAUTHORIZED, INTERNAL, UNSUPPORTED_ENCRYPTION, OTHER, PAYMENT_FAILED, NOT_FOUND.
Notification
Returned by "parse_notification". Croaks on unknown arguments or missing required fields (notification_type, notification).
notification_type-'payment_received','payment_sent', or'hold_invoice_accepted'notification- Hashref with notification data