LaWallet NWC
Architecture

Courtesy NWC Proxy

Provider-agnostic proxy for provisioning temporary NWC connection strings.

Overview

Lightweight standalone Node.js service that acts as a provider-agnostic proxy for provisioning temporary NWC connection strings. Does not hold funds.

Container: lawallet-nwc-proxy Ports: 3003 (API), 3004 (health check) Storage: Own storage (connection tracking, provider credentials)


Purpose

Enables users to upgrade from alias/redirect to NWC-connected addresses without needing their own wallet infrastructure. The proxy provisions temporary NWC connection strings from external providers and returns them to the user.


Independence

  • Own container, own storage
  • No access to lawallet-web database
  • No access to lawallet-listener
  • Communicates with lawallet-web via HTTP API only
  • Stateless proxy: does not hold or manage funds

Supported Providers

ProviderProtocolNotes
Alby HubOAuth + NWCProvisions via Alby account API
LNBitsAPI + NWCProvisions via LNBits wallet API
BTCPayServerAPI + NWCProvisions via BTCPay Greenfield API
YakiHonneNWCDirect NWC provisioning
Generic NWCNWC stringAny provider exposing NWC connection strings

How It Works

  1. User requests a courtesy NWC connection from the proxy
  2. Proxy authenticates with the configured external NWC provider
  3. Proxy provisions a new NWC connection string from the provider
  4. Connection string is returned to the user and linked to their lightning address
  5. Payments to the address now route via NWC instead of redirect
  6. User can replace the courtesy NWC with their own wallet at any time

API Endpoints

EndpointMethodDescription
/connectionsPOSTProvision new courtesy NWC connection
/connections/:idDELETERevoke courtesy NWC connection
/connections/:idGETGet connection status
/providersGETList available NWC providers
/healthGETHealth check

POST /connections

Request:

{
  "provider": "alby",
  "userId": "user_123",
  "addressId": "addr_456"
}

Response:

{
  "id": "conn_789",
  "provider": "alby",
  "nwcConnectionString": "nostr+walletconnect://...",
  "status": "active",
  "createdAt": "2026-03-15T10:00:00Z"
}

Configuration

VariableDescriptionDefault
PROVIDERSEnabled providers (comma-separated)alby
ALBY_CLIENT_IDAlby Hub OAuth client ID(required if alby enabled)
ALBY_CLIENT_SECRETAlby Hub OAuth client secret(required if alby enabled)
LNBITS_URLLNBits instance URL(required if lnbits enabled)
LNBITS_ADMIN_KEYLNBits admin API key(required if lnbits enabled)
BTCPAY_URLBTCPayServer instance URL(required if btcpay enabled)
BTCPAY_API_KEYBTCPayServer Greenfield API key(required if btcpay enabled)
PORTService port3003
HEALTH_CHECK_PORTHealth endpoint port3004
LOG_LEVELMinimum log levelinfo

Design Principles

  • Stateless proxy: Does not hold funds, only provisions connection strings
  • Provider-agnostic: Unified API abstracting differences between providers
  • Extensible: Adding a new provider = implementing one adapter interface
  • Independent: No shared infrastructure with other services

Provider Adapter Interface

Each provider implements a common interface:

interface NWCProvider {
  name: string;
  provision(userId: string): Promise<NWCConnection>;
  revoke(connectionId: string): Promise<void>;
  status(connectionId: string): Promise<ConnectionStatus>;
}

Tech Stack

  • Runtime: Node.js (TypeScript)
  • Logging: pino (JSON structured)
  • Container: Dedicated Docker image

React Hook Integration

The frontend consumes this service via the useCourtesyNWC hook:

const { provision, revoke, status, isLoading, error } = useCourtesyNWC();

// Provision a new connection
const connection = await provision({ provider: 'alby' });

// Check status
const currentStatus = await status(connection.id);

// Revoke when upgrading to own wallet
await revoke(connection.id);

On this page