Commit graph

82 commits

Author SHA1 Message Date
9c0e58a87c 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
v1.2.2-aio.2
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.
2026-06-21 17:26:14 +02:00
2877cf6b20 Revert "fix: allow HTTP LNURL for RFC1918/loopback baseurls (#2)"
Some checks failed
lint.yml / Revert "fix: allow HTTP LNURL for RFC1918/loopback baseurls (#2)" (push) Failing after 0s
This reverts commit 66026ab.

Closes #2 as resolved by switching the dev LNbits to TLS
(self-signed cert) instead of carving out plain HTTP for
RFC1918 hosts. With HTTPS the producer-side python-lnurl
validation accepts any host, AND the lnbits-core consumer-side
`lnurlscan` accepts it too — the symmetric problem the carve-out
couldn't solve on its own.

`create_lnurl_from_baseurl` (#1, `e9d911e`) is kept — it's
orthogonal to the transport scheme and still wanted for the
nostr-transport `lnurl=null` fix.
2026-06-01 21:44:57 +02:00
0e06ab2087 Revert "fix: extend RFC1918 LNURL carve-out to the HTTP-views path"
This reverts commit 40dce41.

Going with TLS termination on the dev LNbits instead, so the
RFC1918 carve-out becomes unnecessary. The lnbits-core
`/api/v1/lnurlscan` consumer-side validator applies the same
HTTPS-required rule python-lnurl enforces; carving the producer
side out only got greg's LNURL generated, not redeemed.
2026-06-01 21:43:37 +02:00
40dce4d88c fix: extend RFC1918 LNURL carve-out to the HTTP-views path
Some checks failed
lint.yml / fix: extend RFC1918 LNURL carve-out to the HTTP-views path (push) Failing after 0s
#2 added the loopback/RFC1918 carve-out to the nostr-transport helper
(`create_lnurl_from_baseurl`) but `views.py` / `views_api.py` still call
`create_lnurl`, which went straight through `lnurl_encode` and got the
same `InvalidUrl` rejection. Visible as a 500 "Error creating LNURL …
check your webserver proxy configuration." on the admin UI when LNbits
itself is on `http://192.168.x.x:port`.

Extract the encode + carve-out logic into `_encode_lnurl(url, hint)` and
route both `create_lnurl` and `create_lnurl_from_baseurl` through it.
Both now return the same `_EncodedLnurl` dataclass (a minimal duck for
`.bech32`/`.url`) — `Lnurl` itself can't be returned in the LAN-local
case because its `__new__` re-runs python-lnurl's host validation on
bech32-decode.

Call sites in views.py / views_api.py unchanged: they already access
`.bech32` and `.url`, which the dataclass exposes. `_populate_lnurl`
back to attribute access too.
2026-06-01 21:35:04 +02:00
66026abe96 fix: allow HTTP LNURL for RFC1918/loopback baseurls (#2)
Some checks failed
lint.yml / fix: allow HTTP LNURL for RFC1918/loopback baseurls (#2) (push) Failing after 0s
`python-lnurl`'s `lnurl_encode` rejects HTTP URLs whose host isn't
`localhost`/`127.0.0.1`/`.onion`, so a regtest LNbits on a LAN IP
(e.g. `http://192.168.0.32:5001`) made `_populate_lnurl` swallow
`InvalidUrl` and leave `link.lnurl=None` — breaking the LAN-local
cross-device smoke flow.

Extend the existing localhost carve-out to the full RFC1918 set:
loopback, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`. These are
intrinsically unreachable from the public internet, so producing an
HTTP LNURL pointing at one is unambiguously a dev/internal scenario.
For matching URLs, skip `lnurl_encode`'s host validation by calling
the public `lnurl.helpers.url_encode` directly (which bech32-encodes
without URL validation). Everything else still goes through the
validated path — production with HTTP + public IP/hostname stays
rejected.

`create_lnurl_from_baseurl` now returns `(bech32, url)` directly
rather than a `Lnurl` instance, since the private-network branch
can't construct a real `Lnurl` (its `__new__` re-runs the same host
validation on bech32-decode). The caller `_populate_lnurl` was the
only consumer.

Test coverage on `_is_private_network_http` covers the carve-out
boundary (loopback, RFC1918, the just-outside-RFC1918 ranges, public
hosts, and the `https://` case). The full encode path is exercised
via regtest smoke.

Closes #2.
2026-06-01 21:14:48 +02:00
e9d911e593 fix: populate lnurl/lnurl_url in nostr-transport handlers (#1)
Some checks failed
lint.yml / fix: populate lnurl/lnurl_url in nostr-transport handlers (#1) (push) Failing after 0s
The HTTP views populate `link.lnurl` and `link.lnurl_url` from
`request.url_for(...)`; the nostr-transport RPC handlers had no
`Request` and so left both fields as `None`. Consumers (ATMs over
nostr) were forced to provision a separate `LNBITS_HTTP_URL` env var
and compose the LNURL callback themselves.

Add `helpers.create_lnurl_from_baseurl(link)` that mirrors
`create_lnurl` but composes the callback URL from
`settings.lnbits_baseurl` instead, and thread it through the
create/get/update/list RPC handlers via a `_populate_lnurl` shim
so the response shape matches the HTTP path. Encoding errors are
swallowed (fields stay `None`) so a misconfigured baseurl falls
back to current behavior rather than failing the RPC.

Closes #1.
2026-06-01 20:01:09 +02:00
82a6d4a894 feat: lnurlw_list_links + lnurlw_unique_hashes transport RPCs
Some checks failed
lint.yml / feat: lnurlw_list_links + lnurlw_unique_hashes transport RPCs (push) Failing after 0s
Two additions surface withdraw-extension capabilities the ATM use
case in aiolabs/lamassu-next (issues #24, #25) needs but couldn't
reach over the nostr transport before:

## lnurlw_list_links (AUTH_ACCOUNT)

Enumerate withdraw links across all wallets owned by the calling
account, with `limit`/`offset` pagination matching the existing
HTTP `/api/v1/links`. Lets an ATM (or any client) re-discover its
links after a reconnect without having to keep its own index.

If `request.wallet_id` is supplied and matches one of the account's
wallets, narrows the listing to just that wallet — mirrors lnurlp's
list semantics.

Returns `{data: [...links], total: <int>}`.

## lnurlw_unique_hashes (AUTH_WALLET)

For an `is_unique=True` link, return the per-use `id_unique_hash`
values derived from each unredeemed slot in `link.usescsv`. Mirrors
the formula in `helpers.py:create_lnurl:13`:

  id_unique_hash = shortuuid.uuid(name=link.id + link.unique_hash + index)

Without this RPC an ATM that wants to generate distinct QR codes
per use (lamassu-next #25) had to reimplement the derivation
client-side — fragile if the extension's hash format ever changes
upstream. With this RPC the ATM asks the server for the canonical
list of unredeemed hashes; each one becomes the trailing path
component of `/withdraw/api/v1/lnurl/<unique_hash>/<id_unique_hash>`.

`is_unique=False` links return an empty `unredeemed_hashes` list;
the base `unique_hash` alone identifies the callback path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 09:45:40 +02:00
95ed17754d feat: register transport RPCs over LNbits nostr transport
Some checks failed
lint.yml / feat: register transport RPCs over LNbits nostr transport (push) Failing after 0s
Hooks the existing withdraw CRUD into the LNbits nostr transport layer
so an HTTP-allergic client (e.g. lamassu-next ATM) can manage LNURL-
withdraw links over kind-21000 encrypted events instead of HTTP.

New `withdraw_start()` lifecycle hook (auto-invoked by the LNbits
extension manager) imports the transport's `register_rpc` and registers
four RPCs mirroring the Lightning.Pub `withdraw.*` contract exactly so
lamassu-next's adapter can be a pure name-translation layer:

  lnurlw_create_link   AUTH_WALLET
  lnurlw_get_link      AUTH_WALLET
  lnurlw_update_link   AUTH_WALLET
  lnurlw_delete_link   AUTH_WALLET

All handlers are thin shims around the existing crud.py functions —
no business logic duplication. *_get / *_update / *_delete verify
that the link's stored wallet matches the caller's wallet id.

Also registers a link-owner resolver with the core subscriptions
module (under tag "withdraw", extras-key "withdrawal_link_id" — the
exact field name views_lnurl.py:144 stamps on payment.extra when a
withdraw settles). That lets clients call
`subscribe_payments({tag:"withdraw", link_id:...})` and stream real-
time claim events without polling, with ownership enforced server-side.

The transport import is guarded by try/except ImportError so this
extension still loads cleanly against an LNbits build that doesn't
have nostr_transport.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 07:34:25 +02:00
dni ⚡
2e52400f52
fix: enforce check minimum (#72)
Some checks failed
lint / lint (push) Has been cancelled
v1.2.2-aio.1
2026-03-31 09:54:41 +01:00
Tiago Vasconcelos
74852e3494
feat: add disable option for LNURLw (#70) 2026-03-17 21:41:17 +00:00
dni ⚡
ab96594f70
chore: update to 1.2.2
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v1.2.2
2025-12-27 09:48:17 +01:00
PatMulligan
8a20df70fe
FIX: generate LNURL server-side for unique voucher links (#68)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-27 09:45:57 +01:00
dni ⚡
68ff753cfd
fix: format function for table column (#67) 2025-12-15 07:41:36 +01:00
dni ⚡
eb7f7fda47
chore: update to version 1.2.1 (#66)
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v1.2.1
2025-10-06 18:47:56 +02:00
dni ⚡
720aa694c1
fix: revert withdraw to using bech32 lnurl field (#65) 2025-10-06 18:44:49 +02:00
Arc
d0689b7859
fix: timing logic for time between withdraws (#63) 2025-09-15 10:00:40 +02:00
Tiago Vasconcelos
8efacf2d4c
fix: print qr code (#62)
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v1.2.0
2025-09-12 14:26:18 +01:00
dni ⚡
10a4caff7e
feat: add lud17 support (#60) 2025-08-25 12:25:20 +02:00
dni ⚡
1bce3bde2d
feat: update to uv (#59) 2025-08-24 23:10:31 +02:00
dni ⚡
717d9c88f8
feat: new lnurl lib and types on endpoints (#57)
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v1.1.0
2025-07-21 16:11:10 +02:00
dni ⚡
b42fee99e5
fix: lnurl_encoding error was not handled (#56) 2025-07-15 15:11:17 +02:00
Vlad Stan
6b11dec0cc
[fix] hash check (#54)
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v1.0.1
2024-12-11 14:48:15 +02:00
Judy
f05169f994
fix: update select query in get_withdraw_links (#52) 2024-11-29 18:46:47 +02:00
Vlad Stan
432ed5299a
Merge pull request #53 from lnbits/update-v1
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v1.0.0
feat: update to lnbits 1.0.0
2024-11-12 11:25:14 +02:00
Vlad Stan
ce56a00d30 chore: ruff 2024-11-12 11:23:21 +02:00
Vlad Stan
f6aee04c40 fix: lint 2024-11-12 11:00:11 +02:00
Tiago Vasconcelos
d0e5e42398 fix: datetime model 2024-10-29 12:39:47 +00:00
Tiago Vasconcelos
3ca9d35a5d . 2024-10-25 15:18:43 +01:00
Tiago Vasconcelos
c0e85cb0a7 fix: update link 2024-10-25 15:18:13 +01:00
Tiago Vasconcelos
adf5faa6bf fix api_docs 2024-10-25 15:17:09 +01:00
Tiago Vasconcelos
8d731dccfc fix: consistent js 2024-10-25 15:16:50 +01:00
Tiago Vasconcelos
2d0a9f1599 fix QR codes on vouchers 2024-10-25 14:32:53 +01:00
Tiago Vasconcelos
51ea172bc2 fix: migrations 2024-10-25 14:32:40 +01:00
dni ⚡
120e744993
fixup! 2024-10-25 12:07:50 +02:00
dni ⚡
cab62b5c00
rc5 3 2024-10-18 09:52:23 +02:00
dni ⚡
8394e56f5d
rc5 2 2024-10-16 15:52:13 +02:00
dni ⚡
59b3941843
rc5 2024-10-16 11:09:13 +02:00
dni ⚡
9a1cc1b2cd
rc4-1 2024-10-16 11:06:43 +02:00
dni ⚡
d8eafa3e13
fix qrcodes and type ignores 2024-10-07 08:10:58 +02:00
Vlad Stan
1fe26c297f fix: qr code 2024-10-04 13:15:32 +03:00
Vlad Stan
7ea4146d7f fix: v1 changes 2024-10-04 13:12:50 +03:00
dni ⚡
134016312f
fixup! 2024-09-26 10:41:57 +02:00
dni ⚡
2095f86618
feat: update to lnbits 1.0.0 2024-09-13 15:41:27 +02:00
dni ⚡
73e3bab6b3
fix: use timestamp placeholder for postgres compat (#51)
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v0.1.13
* fix: use timestamp placeholder for postgres compat
---------

Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
2024-09-03 22:13:51 +02:00
dni ⚡
5b1fc0ffbe
chore: bump min version to 0.12.11 (#50)
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v0.1.12
2024-09-03 09:57:37 +02:00
Pavol Rusnak
bbe4ef9cf6
fix broken import of MilliSatoshi (#48) 2024-09-03 09:38:45 +02:00
dni ⚡
e2f97f05dd
feat: add created_at time to links (#46)
closes #16
2024-08-30 06:17:26 +02:00
dni ⚡
4e6d61fa01
fix: return 200 status for lnurl errors and fix uniques (#43)
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v0.1.11
2024-07-23 15:15:04 +02:00
Julian
00064f65d0
Added paginated fetching to withdraw link table (#41)
Some checks failed
/ release (push) Has been cancelled
/ pullrequest (push) Has been cancelled
v0.1.10
2024-07-19 07:22:46 +02:00
dni ⚡
a44820f61f
feat: add linters and ci (#28)
* feat: introduce linting and ci
* add locks
* prettier
* black and sorting
* f405 missing imports
* E902
* mypy
* renderer
* circular imports
* check comment
* add exports
* add lnurlerrorhandler only on lnurl routes
* add test case
2024-07-11 10:30:28 +02:00