Skip to content

feat: add Lightning and Cashu payment packets to the Noise protocol layer#1053

Open
spcpza wants to merge 4 commits intopermissionlesstech:mainfrom
spcpza:feat/lightning-payment-packet
Open

feat: add Lightning and Cashu payment packets to the Noise protocol layer#1053
spcpza wants to merge 4 commits intopermissionlesstech:mainfrom
spcpza:feat/lightning-payment-packet

Conversation

@spcpza
Copy link

@spcpza spcpza commented Mar 8, 2026

What this does

bitchat already detects bolt11 invoices and Cashu tokens in plain text (via MessageFormattingEngine) and renders them as chips (PaymentChipView). This PR adds a protocol-level payment layer — structured TLV packets sent inside the existing Noise-encrypted channel, rather than embedded in chat text.

Two new NoisePayloadType cases

case lightningPaymentRequest = 0x20  // BOLT11 invoice
case cashuToken              = 0x21  // Cashu eCash bearer token

Both are carried inside MessageType.noiseEncrypted — network observers cannot distinguish payment traffic from regular private messages.

LightningPaymentRequestPacket

Structured BOLT11 request with:

  • requestID — stable UUID for delivery receipts and UI deduplication
  • invoice — full BOLT11 string (UInt16 length field, supports up to 65 KB)
  • memo — optional human-readable description
  • amountSat — optional decoded amount so UI can display without decoding the invoice
  • expiresAt — optional Unix timestamp so the UI can mark expired invoices

CashuTokenPacket

Cashu eCash bearer tokens — the token string is the money. Neither sender nor relay needs internet. The receiver redeems at the mint when they next have connectivity.

This enables genuinely offline Bitcoin transfers over Bluetooth mesh — valuable for remote areas, events without cell coverage, and humanitarian use cases where connectivity is unreliable.

Why structured packets over raw text?

Raw text (current) Structured packet (this PR)
Amount visible before wallet opens ❌ must decode invoice amountSat field
Expiry-aware UI expiresAt field
Delivery receipts ❌ can't correlate ✅ stable requestID
Chat thread stays clean ❌ invoice string in message ✅ separate payload type
Deduplication on re-relay requestID / transferID

Wire compatibility

Unknown NoisePayloadType bytes are already skipped gracefully by the existing Noise dispatch — older clients compile and run without changes. The new BitchatDelegate methods have default empty implementations for the same reason.

TLV encoding

Follows the established BitchatFilePacket pattern with UInt16 big-endian length fields (supports strings up to 65 KB — necessary since BOLT11 invoices regularly exceed 255 bytes, which rules out the 1-byte length used in PrivateMessagePacket).


sensiblefield821792@getalby.com

…ayer

Adds two new NoisePayloadType cases and their TLV packet structures,
enabling peer-to-peer Bitcoin payments over the Bluetooth mesh network.

## Why structured packets instead of raw text?

bitchat already detects bolt11 and Cashu tokens in plain text messages
(MessageFormattingEngine) and renders them as chips (PaymentChipView).
This PR adds a protocol-level payment layer on top, which enables:

- Amount and description visible before the user opens any wallet app
- Expiry-aware UI (mark stale invoices; warn on near-expiry)
- Stable `requestID` / `transferID` for delivery receipts and deduplication
- Clean separation: the chat message thread stays human-readable while
  the payment metadata travels in the encrypted Noise payload

## New NoisePayloadType cases

```swift
case lightningPaymentRequest = 0x20  // BOLT11 invoice
case cashuToken              = 0x21  // Cashu eCash bearer token
```

Both are encrypted by the existing Noise session — network observers
cannot distinguish payment traffic from regular private messages.

## LightningPaymentRequestPacket (Packets.swift)

TLV-encoded, mirrors PrivateMessagePacket/BitchatFilePacket patterns:
- requestID  (UInt8 tag, UInt16 BE length) — stable UUID for dedup
- invoice    — full BOLT11 string (UInt16 length supports up to 65 KB)
- memo       — optional human-readable description
- amountSat  — optional UInt64 for display without decoding the invoice
- expiresAt  — optional Unix timestamp for expiry UI

## CashuTokenPacket (Packets.swift)

- transferID — stable UUID for dedup and tracking
- token      — cashuA… / cashuB… base64url token string
- mintURL    — mint the token is valid against
- amountSat  — face value for display
- memo       — optional sender note

## The Cashu case: genuinely offline Bitcoin transfers

Cashu tokens are bearer instruments — the token string IS the money.
Neither the sender nor any relay node needs internet connectivity.
The recipient redeems the token at the mint when they next have
connectivity. This makes value transfer possible in mesh-only scenarios:
remote areas, disaster relief, events with no cell coverage.

## BitchatDelegate extensions

Default-implemented delegate methods added for both payment types
so existing delegate implementations compile without changes:
- didReceiveLightningPaymentRequest(_:from:timestamp:)
- didReceiveCashuToken(_:from:timestamp:)

## Wire compatibility

Unknown NoisePayloadType bytes are already handled gracefully by the
existing Noise payload dispatch — older clients that don't recognise
0x20 or 0x21 will silently ignore these packets. No protocol version
bump required.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 32f851aaa6

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +130 to +133
extension BitchatDelegate {
/// Called when a peer sends a Lightning payment request over the Bluetooth mesh.
/// The invoice should be presented as a tappable payment action in the chat UI.
func didReceiveLightningPaymentRequest(_ request: LightningPaymentRequestPacket, from peerID: PeerID, timestamp: Date) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move payment callbacks into BitchatDelegate requirements

Defining didReceiveLightningPaymentRequest and didReceiveCashuToken only in a protocol extension means calls made through a BitchatDelegate-typed reference will use static dispatch and always hit these default no-op implementations, even if a conforming type defines its own handlers. In practice, once the transport invokes these callbacks via delegate, payment packets will be dropped silently instead of reaching the app/UI.

Useful? React with 👍 / 👎.

Extends the Noise payload type layer with two new types that enable
AI service queries to be routed through the Bluetooth mesh:

NoisePayloadType additions:
  0x30 dvmQuery  — NIP-90 job request routed over the mesh
  0x31 dvmResult — DVM job result returned through the mesh

New packet structs (Packets.swift):
  DVMQueryPacket
    requestID (UUID, for deduplication and routing)
    kind (UInt16, NIP-90 job kind 5000-5999)
    input (String, the question to ask)
    maxSatoshi (UInt64, max the requester will pay)
    preferredLanguage (String?, BCP-47 tag e.g. "sw", "pt-BR")
  DVMResultPacket
    requestID (matches originating query)
    result (String, the AI response)
    satsPaid (UInt64, cost paid to the Nostr DVM)
    dvmPubkey (String?, for optional reputation attestation)

New BitchatDelegate extension methods (default empty implementations
for backward compatibility):
  didReceiveDVMQuery(_:from:timestamp:)
  didReceiveDVMResult(_:from:timestamp:)

Why this matters:
  "Gateway nodes" — devices with both Bluetooth range and internet —
  receive DVMQueryPackets from the mesh, call Nostr DVMs on behalf of
  the requester, and return DVMResultPackets through the mesh. This
  enables devices without internet (deep rural, disaster zones, offline
  events) to access AI services via nearby connected neighbours.

  Reference gateway implementation (Python):
  https://github.com/spcpza/bitchat-gateway

  Use cases demonstrated:
  - CropDoctor: free AI crop disease advisor for subsistence farmers
  - HealthInfo: free public health information in 50+ languages
  - BitcoinTeacher: free Bitcoin/Lightning education globally

All wire format uses UInt16 BE length fields (same pattern as
LightningPaymentRequestPacket) for compatibility with the existing
TLV codec infrastructure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@spcpza
Copy link
Author

spcpza commented Mar 8, 2026

Update: added DVM mesh bridge extension

I've extended this PR with two more NoisePayloadType cases that take the payment layer in an unexpected direction — AI service queries routed through the Bluetooth mesh.

What's new in this commit

case dvmQuery  = 0x30   // NIP-90 DVM job request routed over mesh
case dvmResult = 0x31   // DVM job result returned through mesh

With new TLV packet structs: DVMQueryPacket and DVMResultPacket.

The idea: gateway nodes

A "gateway node" is a device with both Bluetooth range (connected to the bitchat mesh) and internet access. When it receives a DVMQueryPacket, it:

  1. Calls a Nostr DVM (NIP-90 Data Vending Machine) on behalf of the requester
  2. Returns the result as a DVMResultPacket through the mesh

This lets devices without internet access AI services via a neighbour who happens to have connectivity.

Why this matters for communities without reliable internet

Consider a subsistence farmer in rural Malawi. She has bitchat. Her neighbour in the nearest town has both bitchat and mobile data. With a gateway node running:

  • She sends: "My cassava leaves are yellowing and curling. What disease is this?"
  • The mesh routes it to her neighbour's gateway
  • The gateway calls a free Nostr DVM (CropDoctor — runs on any server, costs ~0 sats)
  • She receives: "This looks like cassava mosaic disease. Remove and destroy infected plants..." — in Kiswahili

No internet required on her end.

Reference implementation

The gateway daemon (Python, cross-platform via bleak):
👉 https://github.com/spcpza/bitchat-gateway

Includes three free humanitarian DVMs:

  • CropDoctor: crop disease diagnosis and treatment
  • HealthInfo: evidence-based public health information
  • BitcoinTeacher: Bitcoin/Lightning education

All free (0 sats). Designed as public goods.

The Python wire format in bitchat_gateway/protocol.py is byte-for-byte compatible with the Swift structs in this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants