README.md raw

NWC Client

Nostr Wallet Connect (NIP-47) client implementation for the ORLY relay. This package provides a complete client for connecting to NWC-compatible lightning wallets, enabling the relay to accept payments through the Nostr protocol.

Features

Installation

go get next.orly.dev/pkg/protocol/nwc

Usage

Basic Client Setup

import "next.orly.dev/pkg/protocol/nwc"

// Create client from NWC connection URI
client, err := nwc.NewClient("nostr+walletconnect://...")
if err != nil {
    log.Fatal(err)
}
defer client.Close()

Making Requests

ctx := context.Background()

// Get wallet information
var info map[string]any
err = client.Request(ctx, "get_info", nil, &info)
if err != nil {
    log.Printf("Failed to get info: %v", err)
}

// Get wallet balance
var balance map[string]any
err = client.Request(ctx, "get_balance", nil, &balance)

// Create invoice
params := map[string]any{
    "amount": 1000,      // msats
    "description": "ORLY Relay Access",
}
var invoice map[string]any
err = client.Request(ctx, "make_invoice", params, &invoice)

// Check invoice status
lookupParams := map[string]any{
    "payment_hash": "abc123...",
}
var status map[string]any
err = client.Request(ctx, "lookup_invoice", lookupParams, &status)

// Pay invoice
payParams := map[string]any{
    "invoice": "lnbc10n1pj...",
}
var payment map[string]any
err = client.Request(ctx, "pay_invoice", payParams, &payment)

Payment Notifications

Subscribe to real-time payment notifications:

// Subscribe to payment notifications
err = client.SubscribeNotifications(ctx, func(notificationType string, notification map[string]any) error {
    switch notificationType {
    case "payment_received":
        amount := notification["amount"].(float64)
        paymentHash := notification["payment_hash"].(string)
        description := notification["description"].(string)
        log.Printf("Payment received: %.2f sats for %s", amount/1000, description)
        // Process payment...

    case "payment_sent":
        amount := notification["amount"].(float64)
        paymentHash := notification["payment_hash"].(string)
        log.Printf("Payment sent: %.2f sats", amount/1000)
        // Handle outgoing payment...

    default:
        log.Printf("Unknown notification type: %s", notificationType)
    }
    return nil
})

API Reference

Client Methods

NewClient(uri string) (*Client, error)

Creates a new NWC client from a connection URI.

Request(ctx context.Context, method string, params, result any) error

Makes a synchronous request to the wallet.

SubscribeNotifications(ctx context.Context, handler NotificationHandler) error

Subscribes to payment notifications with the provided handler function.

Close() error

Closes the client and cleans up resources.

Supported Methods

MethodParametersDescription
get_infoNoneGet wallet information and supported methods
get_balanceNoneGet current wallet balance
make_invoiceamount, descriptionCreate a new lightning invoice
lookup_invoicepayment_hashCheck status of an existing invoice
pay_invoiceinvoicePay a lightning invoice

Notification Types

Integration with ORLY

The NWC client is integrated into the ORLY relay for payment processing:

# Enable NWC payments
export ORLY_NWC_URI="nostr+walletconnect://..."

# Start relay with payment support
./orly

The relay will automatically:

Error Handling

The client provides comprehensive error handling:

err = client.Request(ctx, "make_invoice", params, &result)
if err != nil {
    var nwcErr *nwc.Error
    if errors.As(err, &nwcErr) {
        switch nwcErr.Code {
        case nwc.ErrInsufficientBalance:
            // Handle insufficient funds
        case nwc.ErrInvoiceExpired:
            // Handle expired invoice
        default:
            // Handle other errors
        }
    }
}

Security Considerations

Testing

The NWC client includes comprehensive tests:

Running Tests

# Run NWC package tests
go test ./pkg/protocol/nwc

# Run with verbose output
go test -v ./pkg/protocol/nwc

# Run integration tests (requires wallet connection)
go test -tags=integration ./pkg/protocol/nwc

Integration Testing

Part of the full test suite:

# Run all tests including NWC
./scripts/test.sh

# Run protocol package tests
go test ./pkg/protocol/...

Test Coverage

Tests verify:

Mock Testing

For testing without a real wallet:

// Create mock client for testing
mockClient := nwc.NewMockClient()
mockClient.SetBalance(1000000) // 1000 sats

// Use in tests
result := make(map[string]any)
err := mockClient.Request(ctx, "get_balance", nil, &result)

Examples

Complete Payment Flow

package main

import (
    "context"
    "log"
    "time"

    "next.orly.dev/pkg/protocol/nwc"
)

func main() {
    client, err := nwc.NewClient("nostr+walletconnect://...")
    if err != nil {
        log.Fatal(err)
    }
    defer client.Close()

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()

    // Get wallet info
    var info map[string]any
    if err := client.Request(ctx, "get_info", nil, &info); err != nil {
        log.Printf("Failed to get info: %v", err)
        return
    }
    log.Printf("Connected to wallet: %v", info)

    // Create invoice
    invoiceParams := map[string]any{
        "amount": 5000,  // 5 sats
        "description": "Test payment",
    }

    var invoice map[string]any
    if err := client.Request(ctx, "make_invoice", invoiceParams, &invoice); err != nil {
        log.Printf("Failed to create invoice: %v", err)
        return
    }

    bolt11 := invoice["invoice"].(string)
    log.Printf("Created invoice: %s", bolt11)

    // In a real application, you would present this invoice to the user
    // For testing, you can pay it using the same client
    payParams := map[string]any{"invoice": bolt11}
    var payment map[string]any
    if err := client.Request(ctx, "pay_invoice", payParams, &payment); err != nil {
        log.Printf("Failed to pay invoice: %v", err)
        return
    }

    log.Printf("Payment successful: %v", payment)
}

Notification Handler

func setupPaymentHandler(client *nwc.Client) {
    ctx := context.Background()

    err := client.SubscribeNotifications(ctx, func(notificationType string, notification map[string]any) error {
        log.Printf("Received notification: %s", notificationType)

        switch notificationType {
        case "payment_received":
            // Grant access to paid feature
            userID := notification["description"].(string)
            amount := notification["amount"].(float64)
            grantPremiumAccess(userID, amount)

        case "payment_sent":
            // Log outgoing payment
            amount := notification["amount"].(float64)
            log.Printf("Outgoing payment: %.2f sats", amount/1000)

        default:
            log.Printf("Unknown notification type: %s", notificationType)
        }

        return nil
    })

    if err != nil {
        log.Printf("Failed to subscribe to notifications: %v", err)
    }
}

Development

Building

go build ./pkg/protocol/nwc

Code Quality

Troubleshooting

Common Issues

  1. Connection Failed: Check NWC URI format and wallet availability
  2. Timeout Errors: Use context with appropriate timeouts
  3. Encryption Errors: Verify NIP-44 implementation compatibility
  4. Notification Issues: Check wallet support for notifications

Debugging

Enable debug logging:

export ORLY_LOG_LEVEL=debug

Monitor relay logs for NWC operations.

License

Part of the next.orly.dev project. See main LICENSE file.