An LNbits extension that implements lnurlcash (LUD-25) — Lightning bearer assets on top of LUD-03 withdrawRequest and LUD-06 payRequest. A port of the standalone lnurl-mint FastAPI app into the LNbits extension model.
Each LNbits wallet can run its own mint, issuing bearer notes that circulate offline as lnurlw:// withdraw links and can be rotated, split, merged, or melted back to a BOLT-11 payment.
A bearer note is a k1 (a secret preimage) that the mint has credited with value. It is:
- Minted by paying a LUD-06 invoice — the payment preimage is the note
- Circulated offline as
lnurlw://<host>/w?k1=<k1>— no account needed - Redeemed by anyone with a spec-compliant wallet — rotate, split, merge, or melt back to sats
Redeem notes with lnurl-wallet (hosted at wallet.lnurlcash.com) or any LUD-03/LUD-25 compatible wallet.
- Per-wallet mints — each LNbits wallet can create and configure its own mint with custom fees, limits, and identity
- Mint (LUD-06) — pay an invoice, receive a bearer note keyed by the payment preimage
- Melt (LUD-03) — redeem a note for a BOLT-11 payment, with confirm-before-burn tristate settlement
- Rotate — burn a note, mint a fresh one keyed by a wallet-supplied hash (same value)
- Split — burn one or more notes, mint two (amount + change)
- Merge — burn several notes, mint one worth the sum
- Verify (LUD-21) — settlement status endpoint for mint and melt invoices
- Offline verification (LUD-25) — per-mint secp256k1 keypair;
mintPubkeyadvertised,sig/sig2on rotate/split/merge - Comment protection — notes can be keyed by a wallet-supplied comment hash instead of the payment preimage
- Sunset mode — stop issuing new notes while allowing existing notes to be redeemed
- Tor support — onion URL substitution for callback URLs
- Mint fees — configurable base fee + percentage, withheld at mint time
- Management SPA — create/configure mints, view outstanding notes and activity
- Public one-pager — QR code, LNURL, mint limits, pubkey, and node info
cd lnbits/lnbits/extensions
git clone https://github.com/bitkarrot/lnurlmint.gitRestart LNbits. The extension appears in the extensions list.
- LNbits >= 1.5.4
- No additional Python dependencies — uses only libraries already in LNbits (
bolt11,bech32,httpx,pyqrcode,loguru)
In the LNbits UI, open the lnurlmint extension and click Create Mint. Configure:
| Field | Description | Default |
|---|---|---|
| Username | Mint identity (shown on the public page) | — |
| Min sendable | Minimum mintable amount (msat) | 10,000 (10 sats) |
| Max sendable | Maximum mintable amount (msat) | 1,000,000,000 (1,000 sats) |
| Min mint | Net-of-fee floor — rejects mints where amount - fee < min_mint |
10,000 |
| Base fee | Flat fee per mint (msat) | 0 |
| Fee percent | Percentage fee in ppm (parts per million) | 0 |
| Verify enabled | Toggle LUD-21 verify endpoint | true |
| Sunset mint | Stop issuing new notes | false |
| Onion URL | Tor hidden service URL for callback substitution | — |
Each mint has a public one-pager at /lnurlmint/m/{mint_id} showing a QR code of the mint's LNURL. Share this URL or the LNURL string with anyone who should be able to mint notes.
A payer scans the QR code (or pastes the LNURL into a wallet), pays the invoice, and receives a bearer note. The payment preimage becomes the note's secret (k1).
The note holder opens the lnurlw:// link in a compatible wallet and chooses:
- Melt — provide a BOLT-11 invoice; the note is reserved, the invoice is paid asynchronously, and the note is burned on settlement
- Rotate — get a fresh note (same value) keyed by a new hash
- Split — break into two notes (specified amount + change)
- Merge — combine multiple notes into one
| Method | Endpoint | Description |
|---|---|---|
POST |
/lnurlmint/api/v1/mints |
Create a mint |
GET |
/lnurlmint/api/v1/mints |
List mints for wallet |
GET |
/lnurlmint/api/v1/mints/{mint_id} |
Get mint details |
PUT |
/lnurlmint/api/v1/mints/{mint_id} |
Update mint config |
DELETE |
/lnurlmint/api/v1/mints/{mint_id} |
Delete mint (fails if notes outstanding) |
GET |
/lnurlmint/api/v1/mints/{mint_id}/notes |
List outstanding notes |
GET |
/lnurlmint/api/v1/mints/{mint_id}/activity |
Recent mint/melt activity |
| Method | Endpoint | Description |
|---|---|---|
GET |
/lnurlmint/api/v1/public/{mint_id} |
Mint info for the public one-pager |
| Method | Endpoint | LUD | Description |
|---|---|---|---|
GET |
/lnurlmint/lnurlp/{mint_id} |
LUD-06 | payRequest with withdrawLink advertisement |
GET |
/lnurlmint/p/cb/{mint_id} |
LUD-06 | Pay callback — returns invoice, preimage becomes note |
GET |
/lnurlmint/w/{mint_id} |
LUD-03 | withdrawRequest (informational, never burns) |
GET |
/lnurlmint/w/cb/{mint_id} |
LUD-03 | Mutating callback — melt, rotate, split, merge |
GET |
/lnurlmint/verify/{mint_id}/{payment_hash} |
LUD-21 | Settlement status for mint/melt invoices |
k1 |
pr |
amount |
Result |
|---|---|---|---|
| one | yes | — | Melt: note reserved, invoice paid async, burned on settlement |
| one | no | no | Rotate: burned, new note keyed by h (same value) |
| one+ | no | yes | Split: all burned, two notes minted (amount → h, remainder → h2) |
| many | no | — | Merge: all burned, one note worth the sum keyed by h |
h (sha256 of the new note's preimage) is required whenever pr is absent. h2 is additionally required for splits. The wallet — never the mint — generates the replacement note's secret.
- Confirm-before-burn — melting a note reserves it (pending), pays the invoice asynchronously, and only burns on positive settlement. A failed payment restores the note.
- No double-spend — pending notes reject all callbacks with
{"status":"ERROR","reason":"pending"} - No secret leakage — the mint stores
sha256(k1)(the hash), never the preimage itself. For rotate/split/merge, the mint never sees the new note's secret — only the wallet-supplied hash. - Per-wallet isolation — every query is scoped by
wallet_id; no cross-wallet note access is possible - Background reconciliation — a permanent task checks in-flight melts and resolves them (settle or restore) even if payment hooks are missed
cd lnbits
.venv/bin/python -m pytest lnbits/extensions/lnurlmint/tests/ -vThe test suite includes 167 test functions across 21 files, ported from the original lnurl-mint project — covering double-spend, race conditions, preimage leakage, fee conservation, reconcile-inflight, bearer threat scenarios, and all LNURL endpoint behavior.
lnurlmint/
├── __init__.py # Extension registration, lifecycle, static files
├── config.py # Extension-level settings (env vars)
├── models.py # Pydantic v1 models (Mint, Note, wire models)
├── migrations.py # DB migrations (mints, notes, mints_records, melts)
├── crud.py # Wallet-scoped DB operations
├── services.py # Business logic (mint, melt, rotate, split, merge, reconcile)
├── signing.py # Per-mint secp256k1 keypair, sign_note
├── tasks.py # Background reconcile task
├── views.py # Generic routes (management page)
├── views_api.py # Management + public REST API
├── views_lnurl.py # LNURL endpoints (payRequest, withdraw, verify)
├── manifest.json # LNbits extension manifest
├── config.json # LNbits extension config
├── static/
│ ├── js/
│ │ ├── index.js # Management SPA (Vue 3, inline template)
│ │ ├── index.vue # Dummy template (required by LNbits loader)
│ │ ├── public.js # Public one-pager (Vue 3, inline template)
│ │ └── public.vue # Dummy template
│ └── routes.json # SPA route definitions
└── tests/ # 167 test functions, 21 files
| Aspect | lnurl-mint (standalone) |
lnurlmint (LNbits extension) |
|---|---|---|
| Funding | Direct lnd/cln REST client | LNbits wallet abstraction (create_invoice/pay_invoice) |
| Multi-tenancy | Single global mint | Per-wallet mints |
| Database | Standalone sqlite + module-level lock | LNbits Database (SQLite + Postgres) |
| Lightning Address | .well-known/lnurlp/{username} (LUD-16) |
Raw LNURL/QR (LUD-16 deferred to v2) |
| Frontend | Server-rendered HTML page | Vue 3 SPA (management + public) |
| Background tasks | FastAPI lifespan + monitor | LNbits create_permanent_unique_task |
| Offline verification | Node-direct signmessage (Option A) |
Per-mint keypair (Option B, portable) |
- dni — original
lnurl-mintimplementation - bitkarrot — LNbits extension port
MIT
