LUD-23: addressRequest base spec.

May 30, 2026 ยท View on GitHub

author: Christian Moss aka Mandelduck


Lightning Address Sharing

A service can request a user's Lightning address without requiring manual text input. This is particularly useful for games, apps, and services that need to pay users (e.g., game rewards, referral bonuses, content creator payouts) where typing an address would create friction.

The service displays a QR code or triggers a URL scheme, the user's wallet prompts for permission, and upon approval sends the Lightning address to the service's callback endpoint.

User story:

Conference Gaming Example: You're at a Bitcoin conference and want to play a kart racing game that streams sats to your wallet as you collect coins on the track.

Current friction: You must use a keyboard to manually enter your Lightning address into a UI the game developer built. This is slow, error-prone, and breaks immersion.

With LUD-23: You simply scan a QR code displayed on the game screen with your phone, approve the request in your wallet, and start playing immediately. The game streams sats directly to your Lightning address as you collect coins - no typing required.

Use cases:

  • Arcade/kiosk games at conferences that pay players in real-time
  • Point-of-sale systems collecting customer Lightning addresses for refunds/rewards
  • Interactive installations and experiences that reward participants
  • Game developers rewarding players without requiring account creation
  • Apps distributing payments to users
  • Services onboarding users for payouts
  • Any application needing a user's Lightning address without manual input

Server-side generation of addressRequest URL:

When creating an addressRequest handler, LN SERVICE must include a k1 query parameter consisting of randomly generated 32 bytes of data. An example is https://yourapp.com/share-address?tag=addressRequest&k1=hex(32 bytes of random data).

The k1 serves two purposes:

  1. Request matching: Allows the service to match the callback response to the specific user/session that initiated the request
  2. Spam prevention: Prevents random requests to the callback endpoint

LN SERVICE must maintain a cache of unused k1 values and only accept callbacks with valid k1 values from that cache. Used k1 values should be removed after successful address submission to prevent replay attacks.

Server-side choice of subdomain:

While not as critical as with authentication (LUD-04), LN SERVICE should still use clear, meaningful domain names since wallets will display the requesting domain to users for approval. For example, rewards.yourgame.com is clearer than api-v2.yourgame.com.

Wallet to service interaction flow:

  1. LN WALLET scans a QR code or receives a URL scheme intent and decodes the URL which contains:

    • tag query parameter with value addressRequest
    • k1 (hex encoded 32 bytes) for request matching
    • optional callback URL (if different from the initial URL)
    • optional description text (must be present if callback is present in the initial URL)
  2. If callback is not present in the initial URL, LN WALLET makes a GET request to the URL to retrieve details:

    {
        "tag": "addressRequest",
        "callback": "https://yourapp.com/receive-address",
        "k1": "hex(32 bytes of random data)",
        "description": "Share your Lightning address with AwesomeGame to receive rewards"
    }
    

    LN SERVICE may also respond with:

    {"status": "ERROR", "reason": "error details..."}
    

    LN WALLET MUST verify that the returned k1 and tag match the values from the initial request. The description field MUST be a human-readable explanation of why the address is being requested.

  3. LN WALLET displays a confirmation dialog which must include:

    • The domain name extracted from the callback URL
    • The description text explaining the request
    • A clear indication that the Lightning address will be shared
    • Approve/Deny options
  4. Once approved by user, LN WALLET makes a GET request to the callback URL using the existing query parameters and appending k1 and address:

    GET <callback_url>?<existing_query_parameters>&k1=<hex(32 bytes from request)>&address=<user@wallet.com>
    
  5. LN SERVICE responds with JSON:

    {"status": "OK"}
    

    or

    {"status": "ERROR", "reason": "error details..."}
    

URL scheme format (same device):

For same-device interactions, the URL can be formatted as:

lightning:addressRequest?callback=https://yourapp.com/receive&k1=hex(32 bytes)&description=Share%20address%20with%20AwesomeGame

Or services can implement deep linking to their own app:

yourapp://receive-address?address=user@wallet.com

Encoding for QR codes:

For QR code display (cross-device), the URL should be bech32-encoded following LUD-01:

lnurl1dp68gurn8ghj7um9wfmxjcm99e5k7... (encodes https://yourapp.com/share?tag=addressRequest&k1=...)

When used in QR codes, LNURL should be uppercase.

Security considerations:

  1. Public information: Lightning addresses are public identifiers meant to be shared. No sensitive authentication material is transmitted.

  2. User consent: Wallets MUST require explicit user approval before sharing the address. The dialog should clearly show which service is requesting the address.

  3. k1 validation: Services must validate that the k1 in the callback matches an unused k1 from their cache to prevent spam and replay attacks.

  4. HTTPS required: All callback URLs must use HTTPS (or http:// for Tor v2/v3 onion addresses).

  5. Rate limiting: Services should implement rate limiting on the callback endpoint to prevent abuse.

  6. No authentication: This protocol does NOT authenticate the user - it only collects their Lightning address. If authentication is needed, use LUD-04 instead.

Example implementation (JavaScript):

// Server-side: Generate addressRequest
const crypto = require('crypto');

function generateAddressRequest(sessionId) {
  const k1 = crypto.randomBytes(32).toString('hex');

  // Store k1 with session mapping
  sessionCache.set(k1, { sessionId, timestamp: Date.now() });

  return {
    tag: "addressRequest",
    callback: `https://yourgame.com/api/receive-address`,
    k1: k1,
    description: "Share your Lightning address with AwesomeGame to receive in-game rewards"
  };
}

// Server-side: Handle callback
app.get('/api/receive-address', (req, res) => {
  const { k1, address } = req.query;

  // Validate k1
  const session = sessionCache.get(k1);
  if (!session) {
    return res.json({ status: "ERROR", reason: "Invalid or expired request" });
  }

  // Validate address format per LUD-16 (lowercase, [a-z0-9\-_.+] local part)
  const [localPart, domain] = (address || '').split('@');
  if (!localPart || !domain || !/^[a-z0-9\-_.+]+$/.test(localPart)) {
    return res.json({ status: "ERROR", reason: "Invalid Lightning address format" });
  }

  // Store address with user session
  userSessions.updateAddress(session.sessionId, address);

  // Remove used k1
  sessionCache.delete(k1);

  res.json({ status: "OK" });
});

Dependencies:

  • LUD-01: Base LNURL encoding/decoding for QR code support
  • LUD-16: Understanding Lightning address format (user@domain.com)

Differences from LUD-04 (auth):

Unlike authentication, this protocol:

  • Does NOT require signature generation/verification
  • Does NOT derive domain-specific keys
  • Does NOT authenticate the user's identity
  • Only collects a public Lightning address
  • Is much simpler to implement

If you need authentication, use LUD-04. If you just need a payment destination, use LUD-23.