feat: merge a link's extra into the payout payment (v1.2.2-aio.2) #3

Merged
padreug merged 1 commit from feat/extra-passthrough into main 2026-06-21 15:27:43 +00:00
Owner

What

Adds an optional extra (JSON) field to a withdraw link. When the link is claimed, that extra is merged onto the payout payment's extra, so a caller can tag the resulting payment with metadata an external listener keys on.

Before this, an LNURL-withdraw payout carried only {tag: "withdraw", withdrawal_link_id} — there was no way to attach caller metadata, because the customer (not the operator) triggers the payout via the public callback.

Why

bitSpire cash-in settlements. The operator's spirekeeper listener creates a cash_in settlement (and pays the platform its fee) only on an outbound payment stamped source=bitspire. With no way to stamp an LNURL-withdraw payout, cash-ins never settled, never charged a fee, and never paid the platform. bitSpire now creates the cash-in link for the NET amount with extra={source, type:cash_in, principal_sats, fee_sats, nostr attribution}, and the settlement fires on claim.

Changes

  • models: extra: dict | None on CreateWithdrawData + WithdrawLink. LNbits' db layer (de)serializes dict columns to/from JSON natively (same as Payment.extra) — no per-field validator needed.
  • migrations_fork.py: withdraw_link.extra TEXT tracked under withdraw_fork, keeping the upstream-tracked migrations.py byte-identical for clean rebases (aiolabs/lnbits#8 pattern — withdraw's first fork migration).
  • views_lnurl: extra={**(link.extra or {}), "tag": "withdraw", "withdrawal_link_id": link.id} — the extension's own keys are written last so a caller cannot clobber them.
  • config.json: 1.2.21.2.2-aio.2.

Verification (dev stack)

A stamped link's payout carries the merged extra (source=bitspire, type=cash_in, principal=82386, fee=6591) and drives a full spirekeeper cash_in settlement — net to customer 75,795, super fee 2,472 paid, settlement processed.

black + ruff clean on all changed files.

Post-merge

Tag v1.2.2-aio.2 at the merged HEAD and add a new catalog entry to aiolabs/lnbits-extensions (alongside -aio.1, not overwriting it).

🤖 Generated with Claude Code

## What Adds an optional `extra` (JSON) field to a withdraw link. When the link is claimed, that `extra` is **merged onto the payout payment's `extra`**, so a caller can tag the resulting payment with metadata an external listener keys on. Before this, an LNURL-withdraw payout carried only `{tag: "withdraw", withdrawal_link_id}` — there was no way to attach caller metadata, because the customer (not the operator) triggers the payout via the public callback. ## Why bitSpire **cash-in settlements**. The operator's `spirekeeper` listener creates a `cash_in` settlement (and pays the platform its fee) only on an outbound payment stamped `source=bitspire`. With no way to stamp an LNURL-withdraw payout, cash-ins never settled, never charged a fee, and never paid the platform. bitSpire now creates the cash-in link for the **NET** amount with `extra={source, type:cash_in, principal_sats, fee_sats, nostr attribution}`, and the settlement fires on claim. ## Changes - **models**: `extra: dict | None` on `CreateWithdrawData` + `WithdrawLink`. LNbits' db layer (de)serializes dict columns to/from JSON natively (same as `Payment.extra`) — no per-field validator needed. - **migrations_fork.py**: `withdraw_link.extra TEXT` tracked under `withdraw_fork`, keeping the upstream-tracked `migrations.py` byte-identical for clean rebases (`aiolabs/lnbits#8` pattern — withdraw's first fork migration). - **views_lnurl**: `extra={**(link.extra or {}), "tag": "withdraw", "withdrawal_link_id": link.id}` — the extension's own keys are written **last** so a caller cannot clobber them. - **config.json**: `1.2.2` → `1.2.2-aio.2`. ## Verification (dev stack) A stamped link's payout carries the merged `extra` (`source=bitspire, type=cash_in, principal=82386, fee=6591`) and drives a full spirekeeper `cash_in` settlement — net to customer 75,795, **super fee 2,472 paid**, settlement `processed`. `black` + `ruff` clean on all changed files. ## Post-merge Tag `v1.2.2-aio.2` at the merged HEAD and add a new catalog entry to `aiolabs/lnbits-extensions` (alongside `-aio.1`, not overwriting it). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat: merge a link's extra into the payout payment (v1.2.2-aio.2)
Some checks failed
lint.yml / feat: merge a link's `extra` into the payout payment (v1.2.2-aio.2) (pull_request) Failing after 0s
lint.yml / feat: merge a link's `extra` into the payout payment (v1.2.2-aio.2) (push) Failing after 0s
9c0e58a87c
Adds an optional `extra` (JSON) field to a withdraw link. When the link
is claimed, that `extra` is merged onto the payout payment's `extra`, so
a caller can tag the resulting payment with metadata an external listener
keys on — the link is the only place to attach it (the customer-facing
LNURL-withdraw payout otherwise carries just `{tag, withdrawal_link_id}`).

Motivating use: bitSpire cash-in settlements. The operator's spirekeeper
listener fires a `cash_in` settlement (fee split to the platform) only on
an outbound payment stamped `source=bitspire`; before this there was no
way to stamp an LNURL-withdraw payout, so cash-ins never settled. bitSpire
now creates the cash-in link for the NET amount with
`extra={source, type:cash_in, principal_sats, fee_sats, ...}` and the
settlement fires on claim.

- models: `extra: dict | None` on CreateWithdrawData + WithdrawLink.
  LNbits' db layer (de)serializes dict columns to/from JSON natively
  (same as Payment.extra) — no per-field validator needed.
- migrations_fork.py: `withdraw_link.extra TEXT` under `withdraw_fork`,
  keeping the upstream-tracked migrations.py byte-identical for clean
  rebases (aiolabs/lnbits#8 pattern).
- views_lnurl: `extra={**(link.extra or {}), "tag": ..., "withdrawal_link_id": ...}`
  — the withdraw extension's own keys are written last so a caller cannot
  clobber them.

Verified end-to-end on the dev stack: a stamped link's payout carries the
merged extra and drives a spirekeeper cash_in settlement + super-fee payout.
padreug deleted branch feat/extra-passthrough 2026-06-21 15:27:43 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/withdraw!3
No description provided.