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 service
relays - Arrayref of relay URLs
secret - 32-byte hex secret for the client
lud16 - Lightning address (optional)

Info

Returned by "parse_info". Croaks on unknown arguments.

capabilities - Arrayref of supported command names
encryption - Arrayref of supported encryption schemes
notification_types - Arrayref of supported notification types
supports_capability($name) - Returns true if the capability is supported
supports_encryption($scheme) - Returns true if the encryption scheme is supported
preferred_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 to
result - Hashref with result data, or undef on error
error - Hashref with code and message, or undef on success
is_error - Returns true if this is an error response
error_code - Returns the error code string, or undef
error_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

SEE ALSO

NIP-47, Net::Nostr, Net::Nostr::Event, Net::Nostr::Zap