Implement unique hash (id_unique_hash) for LNURL-withdraw session tracking #25
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The LNURL-withdraw implementation currently creates simple single-use links without unique hash tracking. For production use, we should implement proper
id_unique_hashsupport for reliable session matching.Current Implementation
We poll
/api/v1/lnurl/:unique_hashto detect when the link is spent, but this doesn't give us per-use tracking.Proposed Implementation
The Lightning.Pub withdraw extension supports
is_unique: truewhich generates a unique hash per use:Benefits of unique hashes
Changes Required
is_unique: trueuses_csvreturned by the extension (contains per-use hashes)id_unique_hashfor this specific sessionRelated
src/extensions/withdraw/managers/withdrawManager.tsUnblocked on the LNbits side —
lnurlw_unique_hashes({id})now exposes per-use hashesThis issue asks for unique-mode (
is_unique: true) LNURL-withdraw with per-useid_unique_hashtracking, calling LP's/api/v1/withdraw/createand polling/api/v1/lnurl/:unique_hash/:id_unique_hash. Under the LNbits migration (see #22) the equivalent surface is:1. Create the unique link over nostr — already works:
Returns the link record with
id,unique_hash,uses,usescsv(CSV of remaining slot indexes),is_unique.2. Enumerate per-use sub-link hashes —
aiolabs/withdrawcommit82a6d4aadded:The hashes are derived server-side via the canonical formula in
helpers.py:create_lnurl:13:The ATM mints one QR per
unredeemed_hashesentry; the callback path for that QR is/withdraw/api/v1/lnurl/<unique_hash>/<id_unique_hash>.3. Observe claims —
subscribe_payments({tag: "withdraw", link_id})(see #24). The settlement push includesPayment.extra.withdrawal_link_idmatching the parent link but does not include the specificid_unique_hashthat was claimed. If the ATM needs to know which sub-link was hit, two options:lnurlw_unique_hashes({id})on each settlement push and compare the newunredeemed_hashesset to the previous one — the difference is the just-claimed hash.uses=1, is_unique=Falselink per session — simpler, no diff logic needed, but uses more DB rows.For the ATM use case (each customer cash-in is a discrete session) option (b) is usually simpler. Option (a) is the right fit for shareable / batch vouchers.
Withdraw extension's concurrency guard also worth flagging: the
hash_checktable (withdraw/crud.py:131-175,views_lnurl.py:135-137) blocks concurrent redemption of the sameid_unique_hashby single-column primary key collision. So even under hostile load, a unique sub-link can only be claimed once.Net: LNbits surface is in place. Recommend closing this issue once the ATM's
client.tsswaps its LP/api/v1/withdraw/*calls for the equivalent LNbits transport RPCs.2026-05-26 cross-codebase review — adjacent finding worth folding in:
The 15-minute safety timeout (
apps/machine/src/services/lightning.ts:139,SESSION_SAFETY_TIMEOUT_MS = 15 * 60 * 1000) is on the ATM-business-side but isn't bound to the LNURL link's own server-side TTL. Scenarios this creates:Adding
id_unique_hash(this issue's main thrust) solves the correlation problem. But the liveness problem stays open unless we also:lnurlw_delete_linkon session cleanup (the current code does this viasession.cleanupatlightning.ts:735-738, but only onexpireLnurlSession()— if Electron crashes hard between session creation and cleanup, the delete never fires).(linkId, expiryTs)to the state-store DB on link creation so a crash-recovered ATM can clean up orphaned links on boot.Two cheap improvements that fold cleanly into this issue:
lnbits.deleteWithdrawLinkin a retry-with-backoff so a transient LNbits / relay failure during cleanup doesn't leak the link.Not new work for this issue per se, but the TTL-binding part is in the same code path as unique-hash tracking — worth implementing them together so the cleanup story is coherent.
Identified during the cross-codebase review pass (2026-05-26). Related:
aiolabs/lamassu-next#24(Nostr-native completion notification).