Files
next.orly.dev/pkg/protocol/nwc/README.md
mleku e56bf76257
Some checks failed
Go / build (push) Has been cancelled
Go / release (push) Has been cancelled
Add NIP-11 relay synchronization and group management features
- Introduced a new `sync` package for managing NIP-11 relay information and relay group configurations.
- Implemented a cache for NIP-11 documents, allowing retrieval of relay public keys and authoritative configurations.
- Enhanced the sync manager to update peer lists based on authoritative configurations from relay group events.
- Updated event handling to incorporate policy checks during event imports, ensuring compliance with relay rules.
- Refactored various components to utilize the new `sha256-simd` package for improved performance.
- Added comprehensive tests to validate the new synchronization and group management functionalities.
- Bumped version to v0.24.1 to reflect these changes.
2025-11-03 18:17:15 +00:00

9.2 KiB

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

  • NIP-47 Compliance: Full implementation of the Nostr Wallet Connect specification
  • NIP-44 Encryption: End-to-end encrypted communication with wallets
  • Payment Processing: Invoice creation, payment, and balance checking
  • Real-time Notifications: Subscribe to payment events and wallet updates
  • Error Handling: Comprehensive error handling and recovery
  • Context Support: Proper Go context integration for timeouts and cancellation
  • Thread Safety: Concurrent access support with proper synchronization

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

Method Parameters Description
get_info None Get wallet information and supported methods
get_balance None Get current wallet balance
make_invoice amount, description Create a new lightning invoice
lookup_invoice payment_hash Check status of an existing invoice
pay_invoice invoice Pay a lightning invoice

Notification Types

  • payment_received: Incoming payment received
  • payment_sent: Outgoing payment sent
  • invoice_paid: Invoice was paid (alternative to payment_received)

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:

  • Create invoices for premium features
  • Validate payments before granting access
  • Handle payment notifications
  • Update user balances

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

  • URI Protection: Keep NWC URIs secure and don't log them
  • Context Timeouts: Always use context with timeouts for requests
  • Error Sanitization: Don't expose internal wallet errors to users
  • Rate Limiting: Implement rate limiting to prevent abuse
  • Audit Logging: Log payment operations for audit purposes

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:

  • Client creation and connection
  • Request/response handling
  • Notification subscriptions
  • Error conditions and recovery
  • NIP-44 encryption
  • Concurrent access patterns
  • Context cancellation

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

  • Comprehensive error handling
  • Go context support
  • Thread-safe operations
  • Extensive test coverage
  • Proper resource cleanup

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.