Compare commits

..
908 Commits
Author SHA1 Message Date
ArcandGitHub 0de2a62159 Merge pull request #1361 from lnbits/tinyurl
Adds universal tinyurl redirect for lnurls and the like
2023-01-24 21:40:07 +00:00
ben dda1d9bb3a mypy 2023-01-24 21:29:52 +00:00
ben c0c4c86808 Added wallet authentication to creat, get and delete tinyurls 2023-01-24 21:26:18 +00:00
ben 7322d27aa3 changed to shortuuid as suggested by Vlad 2023-01-24 20:27:20 +00:00
ArcandGitHub e8f78307f3 Merge pull request #1403 from lnbits/superurlfix
Changes https for http in super user link
2023-01-24 20:23:04 +00:00
ben 6c9a52c6e1 mypy being thoroughly objectionable 2023-01-24 13:48:04 +00:00
ben caadeb77e2 format 2023-01-24 13:32:21 +00:00
ben 5392b2cbfe Fixed models 2023-01-24 13:30:15 +00:00
ben 274615f0b3 Man, broken 2023-01-24 13:27:30 +00:00
ben e53ccb04ac Added timestamp and endless
For future culling of links. If endless is not specified, then links older than x could be removed
2023-01-24 12:45:15 +00:00
ben 0d0817044c Pissy python and its tuples 2023-01-24 11:03:46 +00:00
ben 3c61f61588 removed http var 2023-01-24 10:40:51 +00:00
dni ⚡andGitHub 87ef8638b5 fix annoying package-lock (#1405) 2023-01-24 11:40:17 +01:00
ben 39559cccb9 Removes https in super user link 2023-01-23 21:15:43 +00:00
ben f3b1bca5b8 format 2023-01-23 20:25:47 +00:00
ben 165e5099c1 trying to fix mypy 2023-01-23 19:57:49 +00:00
ben 617b1c941b Merge remote-tracking branch 'origin/main' into tinyurl 2023-01-23 19:53:45 +00:00
Vlad StanandGitHub 331c8e593b fix: ignore extra settings fields (#1398)
* fix: ignore extra settings fields

* fix: format
2023-01-23 14:25:54 +01:00
dni ⚡andGitHub 2976168387 FIX: prettier formatting (#1395)
* prettier

* depreceation warning, and reformatting with new prettier verison
2023-01-23 14:23:30 +01:00
dni ⚡andGitHub e106ec3e9c FIX: improve workflow stability (#1397)
* FIX: improve workflow stability

* just testing

* just testing, nr. 3
2023-01-23 12:57:37 +01:00
Pavol RusnakandGitHub a089188664 add pyright to the project (#1367)
* add pyright to project + bump prettier to latest

* reformat to match the latest prettier rules
2023-01-23 10:29:17 +01:00
TituszandGitHub 9693e5932b Track fiat amount in TPoS invoices. (#1384) 2023-01-23 10:27:19 +01:00
d30a6dc5c1 fix jukebox issues introduced from mypy pr (#1387)
* fix jukebox issues introduced from mypy pr

* fix no invoice

Co-authored-by: ben <ben@arc.wales>
2023-01-23 10:17:16 +01:00
ca51b57355 Faq page (#1388)
* Cleanup formatting

Re-format text in markdown and cleanup extra code.

* small fixes

* Update faq.md

* Update faq.md

* Update faq.md

* small correction

corrected summary open

* correction sections

* add how to connect lndhub wallet

Add info about connecting Zeus and Bluewallet

* finishing 2 sections

add text to funding section and clearnet

* add text to swap and merchant

* group sections

re-organize sections of troubleshooting with did you know. Are practically the same

* small corrections

links corrections

* format sections

* fix link resources

* small corrections

* small corrections

* small correction

* Update faq.md

* Create faq.md

* Update faq.md

* Update faq.md

* Update faq.md

* Update faq.md

* small correction

Co-authored-by: arbadacarba <63317640+arbadacarbaYK@users.noreply.github.com>
2023-01-23 10:16:54 +01:00
b13588dcef DOCS: improve boltz fee information (#1386)
* improve fee information an docs

* review @michael1011

Co-authored-by: ben <ben@arc.wales>
2023-01-23 10:16:27 +01:00
calleandGitHub ffb5a91f67 add robots.txt (#1383) 2023-01-20 09:25:46 +01:00
blackcoffeexbtandGitHub dcf5f0adaa Gerty extension tweaks (#1360)
* Changes to font sizes on Gerty to improve appearance and readability on hardware

* Gerty URL checker removes https, http and trailing slash from display

* Gerty: Added modal with copy pasta for API URL

* Gerty: Removed toggleStates Vue watch to fix weird UI toggle behavour

* Gerty: Remove API link button from list of Gertys

* Gerty: Added block height option

* Gerty: Added api response when no page number is supplied in GET request

* Gerty: blacked

* Gerty: Ran prettier on templates

* Gerty: Added block height text and removed mini gerty option

* Gerty: Remove mini gerty font sizes

* Gerty: Moved URL formatting function. Set max URLs to check to 4

* Gerty: Removed unused async calls

* Gerty: Casing fix in "copy URL" > "Copy URL"

* Gerty: Removed dead code

* Gerty: Removed unused /pages/[id] def from views_api.py

* Gerty: Ran prettier and black
2023-01-19 10:33:48 +01:00
d89a6a337a boltz extension v2, recurring swaps (#981)
* add status to statusdialog

* first commits for boltz update

* formatting

* add latest boltz-clien package

* big refactor, depending on boltz_client package, clean up, mypy issues, not tested yet

* blacking, sorting and stuff

* remove unused req_wrap helper

* remove api docs from frontend

* bug: frontend boltz limits error

* clean up buttons

* update to boltz-client 0.0.8

* fix tests to poetry version 1.3.1

* update requirements

* formatting

* recurring swap works now, need more finetuning

* add exceptions for multiple auto swaps and swapping in with active auto swap

* black

* auto reverse swap actually works :)

* remove swap status dialogs

* update to boltz_client 0.0.9

* update to boltz-client 0.1.1, and fix startup

* update requirement.txt for boltz-client

* fixup columns in table, remove unused payment.extra, change deezy label

* remove balance check for auto swap out

* update boltzc-lient to 0.1.2, fix mypy issue inside boltz package

* nitpicks calle tasks.py

* calle nitpicks crud

* calle nitpicks crud

* refactor

* fix formatting

* circular import

* black :)

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
2023-01-19 10:30:47 +01:00
ArcandGitHub d8b5e3872b Merge pull request #1382 from lnbits/DAupdate
Added models and simple example
2023-01-18 19:05:19 +00:00
ArcandGitHub 0e1f9dbb5a Merge pull request #1371 from HamishMacEwan/patch-2
Additional information in .env
2023-01-18 18:54:31 +00:00
ArcandGitHub 2a2a01d3df Merge pull request #1372 from Darth-Coin/FAQ-page
add FAQ page
2023-01-18 18:53:48 +00:00
ben a8b32194eb Reworded 2023-01-18 18:51:42 +00:00
ben 99d963c726 Added models and simple example 2023-01-18 18:50:07 +00:00
ArcandGitHub 5b6e05af60 Merge pull request #1381 from lnbits/arcbtc-patch-9
Update README.md
2023-01-18 18:10:50 +00:00
ArcandGitHub bfc99937f6 Update README.md 2023-01-18 18:03:29 +00:00
ArcandGitHub 25fce6ba7c Merge pull request #1380 from lnbits/DAimages
Added images
2023-01-18 18:00:48 +00:00
ben 71fc1b7146 Added images 2023-01-18 17:51:15 +00:00
ArcandGitHub 068fc67ade Merge pull request #1379 from lnbits/shippingzone
Added shipping zone
2023-01-18 17:25:30 +00:00
ben 266471a5e3 Added shipping 2023-01-18 17:16:49 +00:00
ben df68400a25 Added shipping zone 2023-01-18 17:10:22 +00:00
ArcandGitHub 01c2b4fe56 Merge pull request #1378 from lnbits/arcbtc-patch-8
typos
2023-01-18 17:02:07 +00:00
arbadacarbaandGitHub ca4c4ac66b Update faq.md 2023-01-18 17:50:04 +01:00
ArcandGitHub a7e8593a9a Update README.md 2023-01-18 16:45:09 +00:00
ArcandGitHub f11b6289b1 Merge pull request #1377 from lnbits/DAprotocol
updated to DA
2023-01-18 16:40:01 +00:00
ben b3aa471125 Made link to DA more prominent 2023-01-18 16:30:39 +00:00
arbadacarbaandGitHub 985bfeaae7 Update faq.md 2023-01-18 17:28:39 +01:00
ben 95f762b687 updated to DA 2023-01-18 16:27:44 +00:00
arbadacarbaandGitHub 813c0aa48c Update faq.md 2023-01-18 17:24:04 +01:00
arbadacarbaandGitHub f3439edfaa Update faq.md 2023-01-18 17:23:03 +01:00
arbadacarbaandGitHub 1e7995927d Update faq.md
🤩
2023-01-18 17:13:47 +01:00
arbadacarbaandGitHub 8f63ca5f56 Update faq.md 2023-01-18 13:35:05 +01:00
DarthCoinandGitHub 025b1b251d Merge branch 'lnbits:main' into FAQ-page 2023-01-18 12:27:54 +01:00
calleandGitHub 9d3290cd9a fix: lntips payment lookup error handling (#1374) 2023-01-18 12:02:32 +01:00
DarthCoinandGitHub 23cca85739 Update faq.md 2023-01-17 13:13:14 +01:00
DarthCoinandGitHub 6e16094572 Update faq.md 2023-01-17 12:57:32 +01:00
DarthCoinandGitHub 003ad7218c Update faq.md 2023-01-17 12:17:56 +01:00
DarthCoinandGitHub 9a0a9aa779 Update faq.md 2023-01-17 12:02:01 +01:00
DarthCoinandGitHub 251c457610 Update faq.md 2023-01-17 11:07:28 +01:00
DarthCoinandGitHub 9988f5167e Update faq.md 2023-01-17 11:01:01 +01:00
DarthCoinandGitHub cb7e311c8d image correction 2023-01-17 10:39:04 +01:00
DarthCoin 079d335648 add FAQ page 2023-01-16 22:34:24 +01:00
ArcandGitHub 2b3b8cd85e Merge pull request #1351 from lnbits/draggablecopilot
Streamer Copilot update: Makes qr code and price draggable
2023-01-16 10:09:11 +00:00
ben 615734e4a9 Merge branch 'main' into draggablecopilot 2023-01-16 10:01:40 +00:00
calleandGitHub 17f5b734b2 Cashu 0.8 (#1370)
* support cashu 0.8

* dont comment out script

* update cashu logo on mint page

* fixing versions

* installing cashu from pypi works

* update requirements.txt
2023-01-15 11:24:11 +01:00
HamishMacEwanandGitHub 2eb76d0e26 Additional information
1. Link to details on what and how to include information in a .env file
2. Clarify how to activate the FORWARDED_ALLOW_IPS environment variable for those situations where the proxy is not at the default 127.0.0.1 (most often inside a Docker container.
2023-01-15 14:46:19 +13:00
Sam KornandGitHub 265ea16d30 add example configuration for connecting LND over grpc (#1364) 2023-01-14 12:31:00 +01:00
Pavol RusnakandGitHub 20e6ca019e replace lnbits-legend with lnbits in github links (#1366) 2023-01-14 12:26:17 +01:00
ben ca4134eb02 added conn 2023-01-12 22:52:46 +00:00
ben f97326bed0 Adde models 2023-01-12 22:42:49 +00:00
ben 6e33784061 Removed lnurlp tests 2023-01-12 22:39:20 +00:00
ben 6d8896c2f6 works 2023-01-12 20:37:03 +00:00
ben e28a6c228e Merge branch 'main' into tinyurl 2023-01-12 20:29:03 +00:00
ArcandGitHub aafd955bb4 Merge pull request #1359 from lnbits/fix/smtp
Bug: SMTP extension migrationfails
2023-01-12 20:27:52 +00:00
ben ce701b9506 Made specific migration 2023-01-12 20:25:51 +00:00
ben 2b3a2afe83 Making universal tinyurl function 2023-01-12 15:16:37 +00:00
dni ⚡ f624b54cfb fix wrong migration 2023-01-12 16:05:25 +01:00
calleandGitHub 492eda5fdc Merge pull request #1332 from lnbits/feat/poetry-metadata
FEAT: add additional metadata to pyproject, description, version, authors
2023-01-12 15:48:08 +01:00
calleandGitHub 97fd5eac8f Update pyproject.toml 2023-01-12 15:47:42 +01:00
calleandGitHub 717ae540b0 Merge pull request #1352 from lnbits/fix/OffShopImageSize
add image by URL and limit size on API
2023-01-12 15:45:49 +01:00
calleandGitHub 3e9bb25b76 Merge pull request #1354 from joelklabo/make-nip5-editable
Enabled Editing the Price for NIP-5 Domains
2023-01-12 15:45:27 +01:00
ben ad3b422e9f Merge branch 'main' into make-nip5-editable 2023-01-12 12:27:19 +00:00
ArcandGitHub c0b1f7f8d4 Merge pull request #1353 from lnbits/unismtp2
FEAT: v2: Overrides having to pay in SMTP if the owners admin key is present
2023-01-12 11:14:10 +00:00
ben 6084ac1633 Merge remote-tracking branch 'origin/main' into make-nip5-editable 2023-01-12 11:11:39 +00:00
calleandGitHub d96aabc14c Merge pull request #1357 from lnbits/addpoetryupdatetodocs
update poetry
2023-01-12 12:08:30 +01:00
ben ca4df159e1 update poetry 2023-01-12 11:07:19 +00:00
dni ⚡ bb57831c53 fix little issues after refactor :) 2023-01-12 08:05:05 +01:00
dni ⚡ 08c485e34b add only 1 general author 2023-01-12 07:54:30 +01:00
calleanddni ⚡ a3f7636db0 Update pyproject.toml 2023-01-12 07:53:22 +01:00
dni ⚡ 1cc3801e38 invalid author string -.- 2023-01-12 07:53:22 +01:00
dni ⚡ 12688efd5f add additional data 2023-01-12 07:53:22 +01:00
Joel Klabo 76e84359ce Remove Accidental Import 2023-01-11 18:51:09 -08:00
Joel Klabo 731ebf9f85 Fix EditDomainData in Test 2023-01-11 15:51:05 -08:00
Joel Klabo 936e9342a2 Formatting 2023-01-11 15:45:43 -08:00
Joel Klabo a5825f1aa9 Enabled Editing the Price for NIP-5 Domains 2023-01-11 15:39:01 -08:00
dni ⚡ 85afb39388 add mission column in sql 2023-01-11 19:24:19 +01:00
dni ⚡ 569990a760 feature request from ben new pr 2023-01-11 19:21:13 +01:00
calleandGitHub a813a37e68 Merge pull request #1325 from lnbits/fix/mypy-bleskomat
fix bleskomat mypy issue
2023-01-11 18:24:04 +01:00
callebtc 5f60b025d5 if this isnt the last commit I will burn it all down 2023-01-11 18:06:42 +01:00
callebtc a0c66871fa relative import 2023-01-11 18:03:50 +01:00
callebtc c3ded0b51c come on bro 2023-01-11 18:01:21 +01:00
callebtc 701b66e71e make format 2023-01-11 17:54:28 +01:00
calleandGitHub affcb9feca Merge branch 'main' into fix/mypy-bleskomat 2023-01-11 17:50:37 +01:00
callebtc 8efa81136b maybe one day Ill learn to use poetry 2023-01-11 17:41:36 +01:00
callebtc 5b51ad6437 fix pyproject 2023-01-11 17:35:59 +01:00
Tiago Vasconcelos fc7a799b89 add image by URL and limit size on API 2023-01-11 16:33:01 +00:00
callebtc 6d25fa4fa0 fix it! 2023-01-11 17:32:39 +01:00
ben 4397516160 Make QR code and price draggable 2023-01-11 14:09:19 +00:00
calleandGitHub e886d17f15 Merge pull request #1350 from lnbits/poetry/update_lock_1.3.1
update poetry.lock to 1.3.1
2023-01-11 13:00:25 +01:00
callebtc 1f23d45d11 make format 2023-01-11 12:52:47 +01:00
callebtc 6afdff0f29 relative import 2023-01-11 12:49:11 +01:00
callebtc c57ddf6aff ignore livestream 2023-01-11 12:40:02 +01:00
callebtc dbaf8a165b update poetry.lock to 1.3.1 2023-01-11 12:19:35 +01:00
calleandGitHub 3e1cc79a23 Merge pull request #1346 from lnbits/fix/settings/lnd_rest_admin_macaroon_nonexistent
fix: add missing entries
2023-01-11 12:15:34 +01:00
calleandGitHub 3b5befdaac Merge pull request #1344 from lnbits/migrations/conv_warn_empty
conv.py: warn if table is empty
2023-01-11 12:15:18 +01:00
calleandGitHub fc6f002cb8 Merge pull request #1242 from lnbits/update/poetry
update lock file and workflows to poetry 1.3.1
2023-01-11 12:14:53 +01:00
calleandGitHub b8dd74299d Merge pull request #1336 from lnbits/mypy_fix_ignore_missing_imports
fix: mypy - constrain `ignore_missing_imports = "True"`
2023-01-11 12:12:53 +01:00
ArcandGitHub 8f91a4a097 Merge pull request #1347 from joelklabo/fix-satoshi-formatting-nip5-index
Fix satoshi formatting nip5 index
2023-01-10 18:55:03 +00:00
ben d38ce5a119 Merge remote-tracking branch 'origin/main' into fix-satoshi-formatting-nip5-index 2023-01-10 18:45:22 +00:00
ArcandGitHub 0901f64118 Merge pull request #1335 from lnbits/fix/DAImageSize
Limit the size of images in Marketplace
2023-01-10 18:44:55 +00:00
ArcandGitHub 63b00d46ee Merge pull request #1341 from lnbits/exampleupdate
Adds some more examples to the example extension
2023-01-10 18:39:20 +00:00
ben 2602f044da format 2023-01-10 18:37:54 +00:00
ben a85cd94234 Elaborated nostr reference 2023-01-10 18:30:09 +00:00
Joel Klabo 2ae97817a7 Run Formatting 2023-01-10 09:41:15 -08:00
Joel Klabo f237bdd4b7 Fix Satoshis Formatting on NIP-5 Index Page 2023-01-10 09:35:43 -08:00
callebtc 822a304127 specify in settings panel 2023-01-10 17:35:43 +01:00
callebtc 2718773b60 fix: add missing entries 2023-01-10 17:25:12 +01:00
callebtc a1a75ddf0b change sign 2023-01-10 16:25:28 +01:00
callebtc 3f61286cad correct placement of warning 2023-01-10 16:19:56 +01:00
calleandGitHub 9128953894 Merge pull request #1343 from lnbits/fix/migration/mock_data_subfolder
remove subfolder from mock_data.zip
2023-01-10 16:18:14 +01:00
callebtc 64c330459b conv.py: warn if table is empty 2023-01-10 16:16:17 +01:00
callebtc 1629609569 remove subfolder from mock_data.zip 2023-01-10 15:59:25 +01:00
calleandGitHub 00af60b1df Merge pull request #1342 from lnbits/fix/update_mock_data_lnurlp_6
Migration: ext_lnurlp update mock data to v 6
2023-01-10 15:51:23 +01:00
callebtc 6e719c8fba update mock data to 6 2023-01-10 15:46:03 +01:00
ben ee3ddb2ef6 DO NOT ADD NEW DEPENDENCIES 2023-01-10 14:39:27 +00:00
ben 1d980afb62 format 2023-01-10 14:29:05 +00:00
ben c2bfc199e1 Adds websocket and qrcode examples to example extension 2023-01-10 14:26:10 +00:00
Tiago Vasconcelos d6a1aa5009 fix error when no categories are defined 2023-01-10 14:20:46 +00:00
ArcandGitHub a8fa4bd117 Merge pull request #1338 from joelklabo/dont-mulitply-sats-nip5
Don't Multiply Sat Price by 100 for NIP-5 Verification
2023-01-10 12:05:43 +00:00
callebtc b32404ca5f correct typing of the callable 2023-01-10 11:39:21 +01:00
ArcandGitHub c50b2562ab Merge pull request #1339 from lnbits/hotfix-smtp
BUG: remove livestream from mypy ignore and remove dead code from smtp
2023-01-10 10:31:38 +00:00
dni ⚡ 31c8973f93 remove livestream from mypy ignore and remove dead code from smtp 2023-01-10 11:09:09 +01:00
Vlad Stan 4d1480eff3 fix: mypy - double import of lnurldevice_ext 2023-01-10 09:50:35 +02:00
Vlad Stan 9ed5a19bd0 Revert "fix: narrow down the list of ignore_missing_imports modules"
This reverts commit 7b6dfe1f498646cefe1bd8a5f25f3abe2d341b36.
2023-01-10 09:45:12 +02:00
Vlad Stan c54a0b4f2a fix: narrow down the list of ignore_missing_imports modules 2023-01-10 09:45:12 +02:00
Vlad Stan 5ce79d9440 chore: use list[] instead of csv for ignore_missing_imports 2023-01-10 09:45:12 +02:00
Vlad Stan 6235215c89 fix: use relative import 2023-01-10 09:45:12 +02:00
Vlad Stan 54139371f6 fix: mypy 2023-01-10 09:45:12 +02:00
Vlad Stan 5547bb69e6 fix: use relative import 2023-01-10 09:45:12 +02:00
Vlad Stan c370bd18c6 refactor: remove redundant # type: ignore 2023-01-10 09:45:12 +02:00
Vlad Stan 8cd6c7c9bc refactor: use relative imports for own module 2023-01-10 09:44:52 +02:00
Vlad Stan e97fb086cc feat: explicitly specify for which modules to ignore_missing_imports 2023-01-10 09:44:52 +02:00
ben aee742a8ac Merge branch 'main' into fix/DAImageSize 2023-01-09 19:24:09 +00:00
ben 68d1271d5f format 2023-01-09 19:23:03 +00:00
Tiago Vasconcelos ab73698a45 add product image with url 2023-01-09 19:11:16 +00:00
Joel Klabo 679b371eb8 Update Formatting of Satoshi Amount (No decimal places) 2023-01-09 08:51:21 -08:00
ArcandGitHub c439732f9b Merge pull request #1304 from lnbits/fix/mypy-lnurldevices
fix mypy lnurldevices
2023-01-09 16:44:23 +00:00
ben b413bbb8ee Added try for failing multiple ATM withdraws to avoid error dump 2023-01-09 16:37:35 +00:00
ben 1873740294 format 2023-01-09 16:14:46 +00:00
dni ⚡ 840de18a91 fixup 2023-01-09 16:39:31 +01:00
dni ⚡ 66c0aac3a1 fix mypy lnurldevices issues 2023-01-09 16:35:42 +01:00
calleandGitHub 8b10198b85 Merge pull request #1295 from lnbits/fix/mypy-livestream
fix mypy issues for livestream ext
2023-01-09 16:22:45 +01:00
Joel Klabo 61c9a22fe1 Don't Multiply Sat Price by 100 for NIP-5 Verification 2023-01-09 07:20:22 -08:00
calleandGitHub bdf5980a1d Merge pull request #1334 from motorina0/fix_wrong_tipjar_id
fix: (rollback refactoring) Use the `tipjar` id to fetch the created `tipjar`
2023-01-09 14:31:03 +01:00
calleandGitHub 901accb60c Merge pull request #1303 from lnbits/fix/mypy-watchonly
fix mypy issues for watchonly
2023-01-09 14:30:05 +01:00
Tiago Vasconcelos 44fa30fdc3 check uploaded image size in API 2023-01-09 12:31:12 +00:00
Tiago Vasconcelos 4d5c7133bc create smaller base64 image in UI 2023-01-09 12:30:21 +00:00
Vlad Stan b764c93e7c fix: (rollback refactoring) Use the tipjar id to fetch the created tipjar 2023-01-09 14:21:14 +02:00
ArcandGitHub e362b04f0e Merge pull request #1333 from lnbits/smtp
Extension: SMTP, send mails for sats
2023-01-09 12:18:51 +00:00
ben ad2a6c7bc4 Added tile 2023-01-09 11:26:20 +00:00
calleandGitHub d9a13a6e30 Merge pull request #1330 from joelklabo/nip5-caddy-example
Example Caddy Configuration for NIP-05 Extension
2023-01-09 12:14:41 +01:00
calleandGitHub cd750957c0 Merge pull request #1326 from bitkarrot/patch-1
add poetry <1.2 version note to troubleshooting
2023-01-09 12:13:50 +01:00
calleandGitHub b228364a41 Apply suggestions from code review 2023-01-09 12:13:43 +01:00
calleandGitHub d66efcaa32 Apply suggestions from code review 2023-01-09 12:13:16 +01:00
calleandGitHub 84f9deed77 Merge pull request #1328 from prusnak/payment-extra
tpos: remove extraneous not payment.extra check
2023-01-09 12:11:20 +01:00
ben 878be3d019 Merge branch 'main' into smtp 2023-01-09 11:03:09 +00:00
ArcandGitHub 5b305cc816 Merge pull request #1331 from joelklabo/add-satoshis-currency-option-nip5
Add Satoshis as Currency Option Nostr NIP-5
2023-01-09 11:02:17 +00:00
ben 96ed9192a3 format 2023-01-09 11:01:15 +00:00
ben cf03ae0a5f Merge remote-tracking branch 'origin/main' into add-satoshis-currency-option-nip5 2023-01-09 11:00:03 +00:00
ArcandGitHub 8861424022 Merge pull request #1181 from Uthpala/main
Deezy swap extension
2023-01-09 10:55:09 +00:00
Vlad Stananddni ⚡ d51ba2b4fb refactor: remove redundant # type: ignore 2023-01-09 10:30:15 +01:00
dni ⚡ 6ea5069835 fix mypy watchonly issues 2023-01-09 10:30:09 +01:00
dni ⚡ e4ab966d2f readd the package-lock.json from main 2023-01-09 10:15:38 +01:00
dni ⚡ 0c1eb13d93 fine tunings ;) 2023-01-09 10:13:54 +01:00
dni ⚡ e9f625f008 mypy fixes, date issue 2023-01-09 09:54:17 +01:00
dni ⚡ 3d8a8664f2 copy initial copy from my fork 2023-01-09 09:41:19 +01:00
Joel Klabo 49d286e7ea Add Satoshis as Currency Option Nostr NIP-5 2023-01-08 21:31:41 -08:00
Joel Klabo 67db763ec9 Example Caddy Configuration for NIP-05 Extension 2023-01-08 14:23:18 -08:00
Pavol Rusnak aa067df380 remove extraneous not payment.extra check
followup to fca7e7bec3
2023-01-08 12:19:58 +00:00
calleandGitHub d5009a7d0a Merge pull request #1327 from lnbits/fix/readme
clean up readme
2023-01-08 12:18:39 +01:00
callebtc 2577b3208b clean up readme 2023-01-08 12:16:39 +01:00
BitkarrotandGitHub d5ead2543c add poetry <1.2 version note to troubleshooting 2023-01-07 16:15:08 -08:00
ben dfd86ed2e0 mypy 2023-01-07 22:16:36 +00:00
ben ccdb6d76bd added Optional 2023-01-07 22:11:17 +00:00
ben 7f1a08e16a mypy 2023-01-07 22:06:35 +00:00
ben ef456c66d7 Format 2023-01-07 21:59:10 +00:00
ben 29f5f44e1b Merge branch 'main' into Uthpalamain 2023-01-07 21:55:58 +00:00
dni ⚡ 85cb8526be fix mypy bleskomat issues 2023-01-07 13:15:29 +01:00
Uthpala Heenatigala af472ba833 fix formatting 2023-01-06 20:34:31 +01:00
ArcandGitHub e9a166f634 Merge pull request #1324 from lnbits/MEGA
Moved example ext sub-menu for mobile
2023-01-06 18:01:40 +00:00
ben 45315084ee changed menu name 2023-01-06 17:54:04 +00:00
ben df5b9de393 format 2023-01-06 17:48:14 +00:00
ben a5cdd69dbe Moved sub-menu for mobile 2023-01-06 17:46:41 +00:00
Danny Diekroeger 4c47a6fd3b Wording, design, and require api token 2023-01-06 08:32:50 -08:00
calleandGitHub 79eec2e751 Merge pull request #1089 from lnbits/fix/paymentChain_splitPayment
Fix workflow for chaining extension
2023-01-06 17:10:57 +01:00
Tiago Vasconcelos 3cdb1ab180 mergeable 2023-01-06 16:00:26 +00:00
ArcandGitHub 4f4a578c82 Merge pull request #1321 from lnbits/MEGA
Make Example Great Again
2023-01-06 15:45:05 +00:00
ben e7c479f702 Tidied tip format 2023-01-06 15:37:04 +00:00
ben 85b5e96ea7 Included currency conversion 2023-01-06 15:28:32 +00:00
Tiago Vasconcelos 319e28ef3f removed as it breaks tags. there's no % in tags 2023-01-06 15:16:41 +00:00
ben d0094427eb Done for now 2023-01-06 14:56:37 +00:00
ben af37be6389 added views.py example 2023-01-06 14:45:20 +00:00
calleandGitHub decf6661fa Merge pull request #1323 from lnbits/fix/sparko/make_format
make format from #967
2023-01-06 15:43:52 +01:00
callebtc d8a8614fb9 make format from #967 2023-01-06 15:43:35 +01:00
calleandGitHub e357dfe37a Merge pull request #967 from mrbitcoiner/fix_sparko_get_payment_status
Fixing spark string subtraction
2023-01-06 15:42:43 +01:00
calleandGitHub 3b1fb1c1a4 Merge pull request #1322 from iWarpBTC/fix_boltcard_refund
Fix: Boltcard refunds
2023-01-06 15:40:43 +01:00
ben c7fd836760 added comment 2023-01-06 14:40:42 +00:00
ben 7b3bab096e Added tasks example 2023-01-06 14:37:49 +00:00
ben a367fcdbda Added tabs 2023-01-06 14:28:43 +00:00
Tiago Vasconcelos 6a262428cd fix indentation 2023-01-06 12:53:26 +00:00
Tiago Vasconcelos 89266f9aa2 Merge remote-tracking branch 'origin/fix/paymentChain_splitPayment' into fix/paymentChain_splitPayment 2023-01-06 12:47:06 +00:00
Tiago Vasconcelos 1032ad3417 add amount to livestream 2023-01-06 12:45:55 +00:00
Tiago Vasconcelos c8cdf2f1d2 tags don't have percent 2023-01-06 12:45:43 +00:00
callebtc cc375c0e40 clean up 2023-01-06 13:41:41 +01:00
Gene Takavic d33724c1c9 getting rid of unnecessary Unions 2023-01-06 12:51:30 +01:00
Tiago Vasconcelos 70899994fe clean visual aid comments 2023-01-06 11:31:19 +00:00
8bb801ac99 Update lnbits/extensions/livestream/tasks.py
Co-authored-by: calle <93376500+callebtc@users.noreply.github.com>
2023-01-06 11:29:38 +00:00
aaecb2b381 Update lnbits/extensions/splitpayments/tasks.py
Co-authored-by: calle <93376500+callebtc@users.noreply.github.com>
2023-01-06 11:29:18 +00:00
ben bbb955df1b Its alive! 2023-01-06 10:57:15 +00:00
ben 2aa9026392 Merge branch 'main' into MEGA 2023-01-06 10:45:17 +00:00
calleandGitHub 7cfbb92bb4 Merge pull request #1320 from lnbits/revert-1318-revert-1302-fix/lnurlp_shorter_url
Revert "Revert "shorten lnurlp endpoint URL for smaller QR codes""
2023-01-06 11:44:33 +01:00
calleandGitHub e1c01c6028 Revert "Revert "shorten lnurlp endpoint URL for smaller QR codes"" 2023-01-06 11:44:22 +01:00
Gene Takavic 1ff1605cce fix boltcard refunds 2023-01-06 11:42:47 +01:00
Tiago Vasconcelos bc0d86c771 correct amount on invoice 2023-01-06 10:27:03 +00:00
calleandGitHub 9b26528226 Merge pull request #1318 from lnbits/revert-1302-fix/lnurlp_shorter_url
Revert "shorten lnurlp endpoint URL for smaller QR codes"
2023-01-06 11:26:15 +01:00
calleandGitHub 4dad37c595 Revert "shorten lnurlp endpoint URL for smaller QR codes" 2023-01-06 11:26:07 +01:00
calleandGitHub f5614333f5 Merge pull request #1302 from lnbits/fix/lnurlp_shorter_url
shorten lnurlp endpoint URL for smaller QR codes
2023-01-06 11:23:12 +01:00
calleandGitHub 14b9d2260f Merge pull request #1317 from lnbits/revert-1309-revert-1298-revert-1277-revert-1255-fix/lnurlp_id_uuid
Revert "Revert "Revert "Revert "add UUID as id instead of sequential integer""""
2023-01-06 11:16:29 +01:00
callebtc 407128fd61 fix migration for postgres 2023-01-06 11:13:25 +01:00
Tiago Vasconcelos 41faf42afe calle's suggestion 2023-01-06 09:46:43 +00:00
callebtc 6cd5e44ad6 use bigint 2023-01-06 10:28:50 +01:00
calleandGitHub 3203950310 Revert "Revert "Revert "Revert "add UUID as id instead of sequential integer"""" 2023-01-06 10:22:58 +01:00
Uthpala Heenatigala 5dc7a77e43 running format and adding sqlite db to mock_data.zip 2023-01-06 00:41:20 +01:00
UthpalaandGitHub 453d65a91d Merge branch 'lnbits:main' into main 2023-01-06 00:34:57 +01:00
Uthpala Heenatigala 810b3a1a84 add txid and tx hex 2023-01-06 00:34:24 +01:00
ArcandGitHub 03429746af Merge pull request #1316 from lnbits/gertyfix
Final v-if edit
2023-01-05 23:12:10 +00:00
ben c2dbe993da Final v-if fix 2023-01-05 23:03:59 +00:00
ArcandGitHub 3beeb1bef9 Merge pull request #1315 from lnbits/gertyfix
color fix
2023-01-05 22:57:19 +00:00
ben 53db5ba473 changed color 2023-01-05 22:50:57 +00:00
ArcandGitHub b714f255fc Merge pull request #1314 from lnbits/gertyfix
added proper if for showing urls
2023-01-05 22:48:07 +00:00
ben 7118dc95a5 added proper if for showing urls 2023-01-05 22:39:06 +00:00
ArcandGitHub 4ea967f899 Merge pull request #1313 from lnbits/gertytweak
Fix/gerty mempool caching timestamp bug (again)
2023-01-05 22:33:41 +00:00
ben 723c9006e1 duplicate 2023-01-05 22:27:25 +00:00
ben 84c4643e1b format 2023-01-05 22:21:40 +00:00
ben 96b07af331 swapped timestamp type for float, fixes issue 2023-01-05 22:20:10 +00:00
ben d8af0da440 fixes timestamp 2023-01-05 21:39:33 +00:00
ArcandGitHub d838f63454 Merge pull request #1311 from lnbits/lnurldeviceatmfix
Fixes lnurldevice atm
2023-01-05 18:49:56 +00:00
ArcandGitHub 32d4bf4c17 Merge pull request #1309 from lnbits/revert-1298-revert-1277-revert-1255-fix/lnurlp_id_uuid
Revert "Revert "Revert "add UUID as id instead of sequential integer"""
2023-01-05 18:44:47 +00:00
ArcandGitHub 48966f3924 Merge pull request #1312 from lnbits/revert-1310-lnurlpfix
Revert "LNURLp fix"
2023-01-05 18:44:24 +00:00
ArcandGitHub 183f7fadf5 Revert "LNURLp fix" 2023-01-05 18:44:13 +00:00
ArcandGitHub 941ac30277 Merge pull request #1310 from lnbits/lnurlpfix
LNURLp fix
2023-01-05 18:42:03 +00:00
Uthpala Heenatigala 57d345c149 show the ln to btc swap table and btc to ln swap table 2023-01-05 19:33:13 +01:00
ben 4ed966fefe LNURLp fix
This commit breaks when migrating data https://github.com/lnbits/lnbits/pull/1298
2023-01-05 18:31:29 +00:00
ben 3420cee9dc Fixes lnurldevice atm 2023-01-05 18:27:54 +00:00
ArcandGitHub 08caea4b9a Revert "Revert "Revert "add UUID as id instead of sequential integer""" 2023-01-05 18:25:08 +00:00
calleandGitHub 1fcbd8923b Merge pull request #1305 from blackcoffeexbt/fix/gerty_mempool_caching_timestamp_bug
Fix/gerty mempool caching timestamp bug
2023-01-05 16:33:28 +01:00
blackcoffeexbt 637a468ec3 fix: Changed mempool caching to use db.timestamp_now instead of time.time() 2023-01-05 15:25:38 +00:00
blackcoffeexbtandGitHub 1546847c50 Merge branch 'lnbits:main' into main 2023-01-05 15:16:46 +00:00
Tiago Vasconcelos 9e96025dc7 mypy 2023-01-05 14:37:31 +00:00
callebtc 4dc94bba7d change link url as well 2023-01-05 13:44:57 +01:00
callebtc 9e953bc3e1 unique name for deprecated endpoitn 2023-01-05 13:30:47 +01:00
calleandGitHub 26d661deca Merge pull request #1301 from prusnak/payment-extra
followup to #1300
2023-01-05 13:29:08 +01:00
callebtc cfab334d0f shorten lnurl endpoint URL for smaller QR codes 2023-01-05 13:28:33 +01:00
Pavol Rusnak e4c01285b2 followup to fca7e7bec3
one extraneous check was forgotten
2023-01-05 13:27:11 +01:00
Tiago Vasconcelos 954bbc6de4 Merge branch 'main' into fix/paymentChain_splitPayment 2023-01-05 12:25:28 +00:00
Tiago Vasconcelos 7a7bd93639 refactor splitpayments with tags 2023-01-05 12:20:31 +00:00
callebtc 3129a0441e merge 2023-01-05 13:12:27 +01:00
callebtc e0cbb41bb9 remove extra check 2023-01-05 13:09:48 +01:00
calleandGitHub 3ecdad42a5 Merge pull request #1296 from lnbits/fix/mypy-lnaddress
fix lnaddress mypy issues
2023-01-05 13:09:01 +01:00
callebtc f45db909fd remove extra check 2023-01-05 13:08:39 +01:00
calleandGitHub 162c1d1eb4 Merge pull request #1300 from lnbits/refactor/payment_extra_addition
refactor: remove remaining checks, as in #1280
2023-01-05 13:07:30 +01:00
callebtc fca7e7bec3 refactor: remove remaining checks, as in #1280 2023-01-05 13:06:36 +01:00
calleandGitHub 4d7f1d44c4 Merge pull request #1280 from prusnak/unify-payment-extra
make payment.extra non-optional
2023-01-05 13:04:21 +01:00
calleandGitHub 997d3726f0 Merge pull request #1297 from lnbits/fix/mypy-boltscard
fix boltscard mypy issue
2023-01-05 13:03:34 +01:00
calleandGitHub ef69919644 Merge pull request #1299 from lnbits/fix/mypy-satspay
fix mypy satspay issues
2023-01-05 13:00:04 +01:00
calleandGitHub 2429a88e95 Merge pull request #1298 from lnbits/revert-1277-revert-1255-fix/lnurlp_id_uuid
Revert "Revert "add UUID as id instead of sequential integer""
2023-01-05 12:58:56 +01:00
callebtc 8f63b55ff3 fix mypy 2023-01-05 12:56:32 +01:00
callebtc 260bfe5423 shorten link id 2023-01-05 12:50:16 +01:00
dni ⚡ 6e1d0cf109 fix mypy satspay issues 2023-01-05 12:47:53 +01:00
calleandGitHub 7788298b8c Revert "Revert "add UUID as id instead of sequential integer"" 2023-01-05 12:46:26 +01:00
dni ⚡ 2949253cb2 fix boltscard mypy issue 2023-01-05 12:27:43 +01:00
Tiago Vasconcelos 4ae88ad3b9 before merging main 2023-01-05 11:05:03 +00:00
dni ⚡ 8b60c64ade fix lnaddress mypy issue 2023-01-05 12:02:23 +01:00
Uthpala Heenatigala 93d6d1279e adding deezy token 2023-01-05 11:46:19 +01:00
dni ⚡ 2b46fa6c56 fix livestream mypy issues 2023-01-05 09:28:38 +01:00
Pavol Rusnak 191ad1e224 make payment.extra non-optional
this makes handling in tasks.py so much easier
2023-01-05 09:22:34 +01:00
ArcandGitHub 301a784e95 Merge pull request #1282 from lnbits/fix/mypy-streamalerts
fix streamalert mypy issues
2023-01-05 07:54:51 +00:00
ben cd05eba183 Added tile and tweaked description for size limitation 2023-01-05 00:52:08 +00:00
ben 82eafe2900 Merge branch 'main' into Deezymain 2023-01-05 00:42:08 +00:00
ArcandGitHub f428fcb871 Merge pull request #1293 from lnbits/fix/mypy-invoices
fix invoices mypy issues
2023-01-05 00:02:36 +00:00
ben 9b74d0a4f4 Fixed uppercase of null issue 2023-01-04 23:56:19 +00:00
ArcandGitHub a9eddd184c Merge pull request #1294 from lnbits/wallethotfix
Fixes error in formatting in wallet
2023-01-04 23:31:28 +00:00
ben 6bb483bfd6 Fixes error in formatting in wallet 2023-01-04 23:16:26 +00:00
dni ⚡ 30eccab53c fix invoices mypy issues 2023-01-04 22:49:10 +01:00
ArcandGitHub 1c6fc8a178 Merge pull request #908 from lnbits/diagon-alley
WIP: Diagon alley Extension
2023-01-04 21:25:09 +00:00
ben 4ff0d0a799 Name Tweak 2023-01-04 21:17:04 +00:00
ben a035612da3 format 2023-01-04 21:11:06 +00:00
ben a5727a0c92 Changed naming to Market and Stall 2023-01-04 21:08:28 +00:00
dni ⚡ 6800e93d7a fix mypy streamalerts issues 2023-01-04 21:51:38 +01:00
ben 4a86acf879 Added tile 2023-01-04 20:45:30 +00:00
ben a9ed8ceb7a Merge branch 'main' into diagon-alley 2023-01-04 19:50:37 +00:00
ArcandGitHub 7e4a3a6831 Merge pull request #1283 from lnbits/fix/mypy-offlineshop
fix mypy offlineshop issues
2023-01-04 19:50:13 +00:00
ben 29bc735094 Merge branch 'main' into diagon-alley 2023-01-04 19:49:02 +00:00
ben 71f2923ee1 Merge branch 'main' into fix/mypy-offlineshop 2023-01-04 19:39:01 +00:00
ArcandGitHub c18ba12ca9 Merge pull request #1288 from lnbits/fix/mypy-lndhub
fix lndhub mypy issues
2023-01-04 19:33:43 +00:00
ArcandGitHub 76eb5a9309 Merge pull request #1290 from lnbits/fix/mypy-withdraw
fix mypy issue on withdraw + some refactoring
2023-01-04 19:31:27 +00:00
ArcandGitHub 8931cceb97 Merge pull request #1281 from lnbits/fix/mypy-tpos
FIX: tpos mypy issues
2023-01-04 19:14:59 +00:00
ArcandGitHub 9408b0e2d4 Merge pull request #1286 from lnbits/fix/mypy-lnurlp
fix lnurlp and lnurlpayout mypy issue
2023-01-04 19:10:15 +00:00
ArcandGitHub 608e78f307 Merge pull request #1263 from lnbits/fix/mypy/gerty
fix mypy issues for gerty
2023-01-04 19:09:04 +00:00
ben 61f415ccee software gerty url_checker fixed 2023-01-04 19:01:52 +00:00
ArcandGitHub fce1a96736 Merge pull request #1291 from lnbits/lnurldevicefix
swaps incremental id for small uuid for pos/atm
2023-01-04 18:27:18 +00:00
ben bb4ab7ab92 can in fact be smaller, as for uniqueness, not security 2023-01-04 18:16:04 +00:00
ben 9db9594a48 swaps incremental id for small uuid for pos/atm 2023-01-04 18:11:53 +00:00
dni ⚡ 9fb63f268f formatting 2023-01-04 18:51:36 +01:00
dni ⚡ 31fe5a5141 fix removing the right index from unique withdrawlinks 2023-01-04 18:48:07 +01:00
dni ⚡ 99a8c7afb0 sorting 2023-01-04 18:32:18 +01:00
dni ⚡ 38d3f5f6da remove unneeded assert 2023-01-04 18:29:47 +01:00
dni ⚡ 9bace16a45 refactoring some stuff 2023-01-04 18:27:39 +01:00
Tiago Vasconcelos 58b6b9505d add splitpayments tag to extra if no tag 2023-01-04 16:53:12 +00:00
Tiago Vasconcelos ea91c37489 fix typo in tasks and add/fix extra props 2023-01-04 16:53:12 +00:00
Tiago Vasconcelos c22329e86b fix invoices extra props 2023-01-04 16:51:41 +00:00
Tiago Vasconcelos c0f742bc3e fix splitpayments flow and add amount in extra dict 2023-01-04 16:51:02 +00:00
dni ⚡ 40bcee6d22 fix issue @ben ;) 2023-01-04 17:09:50 +01:00
dni ⚡ 32b785d790 fixes 2023-01-04 17:01:02 +01:00
dni ⚡ 2b65682960 inbetween withdraw commit 2023-01-04 16:29:47 +01:00
ben 92cfcca1d9 replaced with working software gerty 2023-01-04 15:03:05 +00:00
ben c5b59d98ef Merge remote-tracking branch 'origin/fix/mypy/gerty' into fix/mypy/gerty 2023-01-04 14:29:45 +00:00
dni ⚡ 45acfb353d fix lndhub mypy issues 2023-01-04 14:01:18 +01:00
dni ⚡ 4a2b8ab1af add prusnak suggestion 2023-01-04 13:51:18 +01:00
dni ⚡ 64370462b9 remove unused lnurlpayout ext that became scrub 2023-01-04 13:46:33 +01:00
dni ⚡ 799451ce49 merge main 2023-01-04 13:45:18 +01:00
ArcandGitHub cc7c3807dd Merge pull request #1287 from lnbits/revert-1284-revert-1259-merge-error
Revert "Revert "remove duplicit functions ""
2023-01-04 12:38:35 +00:00
dni ⚡ 341e8aa3e4 formatting 2023-01-04 13:36:08 +01:00
ArcandGitHub c835fc28f9 Revert "Revert "remove duplicit functions "" 2023-01-04 12:27:18 +00:00
dni ⚡ bf4070f7cb fix duplication typo 2023-01-04 13:26:34 +01:00
ArcandGitHub 8771c1b147 Merge pull request #1285 from lnbits/gertyhotfix
Gerty timestamp was being saved as an int
2023-01-04 12:26:10 +00:00
dni ⚡ 9d2ef274b9 fix lnurlp and lnurlpayout mypy issue 2023-01-04 13:24:45 +01:00
ben 487646e2e3 timestamp being saved as int 2023-01-04 12:19:16 +00:00
ArcandGitHub fe89817c58 Merge pull request #1284 from lnbits/revert-1259-merge-error
Revert "remove duplicit functions "
2023-01-04 12:08:24 +00:00
ArcandGitHub a521b95070 Revert "remove duplicit functions " 2023-01-04 12:01:27 +00:00
ben 239d35a60a trying to fix errors in software gerty 2023-01-04 11:49:50 +00:00
Tiago Vasconcelos 7328d222a0 prusnak's suggestion 2023-01-04 11:28:43 +00:00
dni ⚡ cef6382e7a fix mypy offlineshop issues 2023-01-04 12:21:12 +01:00
Tiago Vasconcelos 491495aa8a Merge remote-tracking branch 'origin/diagon-alley' into diagon-alley 2023-01-04 11:16:55 +00:00
Tiago Vasconcelos 9291b54ef7 mypyfication and format 2023-01-04 11:16:20 +00:00
dni ⚡ 908369efff fix tpos mypy issue 2023-01-04 11:32:33 +01:00
dni ⚡ 19ef282581 adding back removed functions 2023-01-04 11:17:30 +01:00
calleandGitHub 268d101189 Merge pull request #1279 from lnbits/docs/change_subdomain
legend.lnbits.org -> docs.lnbits.org
2023-01-04 11:12:40 +01:00
callebtc e9e85d870d legend.lnbits.org -> docs.lnbits.org 2023-01-04 11:11:03 +01:00
calleandGitHub b28c6f5c79 Update CNAME 2023-01-04 11:07:54 +01:00
calleandGitHub f9c1a24b72 Merge pull request #1278 from lnbits/fix/adminui/denomination
Fix super_user check, fixes #1250
2023-01-04 10:47:41 +01:00
callebtc c96fd6783b fix super_user check, fixes #1250 2023-01-04 10:41:56 +01:00
calleandGitHub b622028f7d Merge pull request #1271 from lnbits/fix/mypy/ignores
removing type: ignore from Query, Depends, Body and import them correctly
2023-01-04 09:19:06 +01:00
calleandGitHub fb215ed2e3 Merge pull request #1269 from lnbits/fix/mypy/events
fix mypy issues for events
2023-01-04 09:14:23 +01:00
calleandGitHub dd65935b80 Merge pull request #1275 from prusnak/mypy-extension-paywall
fix mypy for lnbits/extensions/paywall
2023-01-04 09:10:49 +01:00
calleandGitHub 82903071b8 Merge pull request #1276 from prusnak/mypy-extension-splitpayments
fix mypy for lnbits/extensions/splitpayments
2023-01-04 09:10:38 +01:00
calleandGitHub ce97dda738 Merge pull request #1274 from prusnak/mypy-extension-lnticket
fix mypy for lnbits/extensions/lnticket
2023-01-04 09:10:26 +01:00
dni ⚡ f7c38a413e sorting 2023-01-04 09:09:30 +01:00
dni ⚡ eac55c2236 fix up copilot 2023-01-04 09:05:38 +01:00
calleandGitHub eaf31adaa8 Merge pull request #1273 from prusnak/encode-decode-default
remove unnecessary argument encoding from bytes.decode() and str.encode()
2023-01-04 09:04:44 +01:00
dni ⚡ d371dac84c fixup events bugs, tickets 2023-01-04 08:55:47 +01:00
ArcandGitHub 0ffb134b8c Merge pull request #1277 from lnbits/revert-1255-fix/lnurlp_id_uuid
Revert "add UUID as id instead of sequential integer"
2023-01-03 23:24:22 +00:00
ArcandGitHub ffdb7244c4 Revert "add UUID as id instead of sequential integer" 2023-01-03 23:16:21 +00:00
Pavol Rusnak 2c1d02940d fix mypy for lnbits/extensions/lnticket 2023-01-03 23:32:18 +01:00
Pavol Rusnak 0efc27993e fix mypy for lnbits/extensions/splitpayments 2023-01-03 23:25:42 +01:00
Pavol Rusnak 236fe9f8eb fix mypy for lnbits/extensions/paywall 2023-01-03 23:21:17 +01:00
Pavol Rusnak 3d0c66f0f6 remove unnecessary argument encoding from bytes.decode() and str.encode()
the encoding parameter defaults to utf-8, so it's not needed
2023-01-03 22:38:28 +01:00
ArcandGitHub c9b02a6470 Merge pull request #1266 from lnbits/icontiles
Swap out material icons for extensions for actual logo tiles
2023-01-03 21:02:47 +00:00
ben fc3465c750 Added some titling 2023-01-03 21:01:30 +00:00
ben dc2689f2c1 Name fix 2023-01-03 20:54:38 +00:00
ben f998681dc0 int to float 2023-01-03 20:51:38 +00:00
ben d2ada889dc typo 2023-01-03 20:21:10 +00:00
UthpalaandGitHub 5bf8bc3a75 Merge branch 'lnbits:main' into main 2023-01-03 21:15:33 +01:00
ben e0f55d3e8c Tile size standard should be 128px 2023-01-03 20:05:44 +00:00
ben 1b201086ba Merge remote-tracking branch 'origin/main' into icontiles 2023-01-03 19:36:05 +00:00
ben 7bfc455960 mobile tweak 2023-01-03 19:35:24 +00:00
Tiago Vasconcelos 87933d1f8b Merge branch 'main' into diagon-alley 2023-01-03 12:25:11 +00:00
calleandGitHub 56e74f28be Merge pull request #1270 from lnbits/fix/mypy/scrub
fix scrub for mypy
2023-01-03 12:38:37 +01:00
callebtc a6cb1267e8 merge main 2023-01-03 12:23:11 +01:00
calleandGitHub f6ec1bc92c Merge pull request #1262 from lnbits/fix/mypy/tipjar
fix tipjar mypy
2023-01-03 12:21:16 +01:00
calleandGitHub 8e386f6a67 Merge pull request #1265 from prusnak/fix-find-opts
add -o to find opts meaning or (default is and)
2023-01-03 12:09:58 +01:00
callebtc 24e72d1e48 fix gerty nitpicks 2023-01-03 11:37:43 +01:00
callebtc 6d29cc85b0 use proper typing 2023-01-03 11:35:10 +01:00
calleandGitHub ef1871f40d Merge pull request #1260 from prusnak/duplicit-function-names
let's use unique function names
2023-01-03 11:28:11 +01:00
calleandGitHub 5038605549 Merge pull request #1272 from lnbits/mypy/remove/hiveming_lnurlpayout
remove hivemind and lnurlpayout from mypy ignore list
2023-01-03 11:10:54 +01:00
callebtc 15bb0f3924 remove hivemind and lnurlpayout from mypy ignore list 2023-01-03 11:08:46 +01:00
calleandGitHub 2d6f89b77f Merge pull request #1248 from prusnak/flake-darwin
enable nix flake on {aarch64,x86_64}-darwin
2023-01-03 11:06:26 +01:00
calleandGitHub fb728d322b Merge pull request #1259 from prusnak/merge-error
remove duplicit functions
2023-01-03 10:59:12 +01:00
calleandGitHub bb2d443246 Merge pull request #1256 from iWarpBTC/revert-1241-main
Revert "Update BoltCard Extension README"
2023-01-03 10:52:58 +01:00
calleandGitHub 7a406786f6 Merge pull request #1246 from prusnak/drop-binascii
use bytes.fromhex/hex builtins instead of binascii.hexlify/unhexlify
2023-01-03 10:52:28 +01:00
Pavol Rusnak 2e57210623 extensions/satspay: remove duplicit functions
probably merge error
2023-01-02 18:37:54 +01:00
dni ⚡ b1d1cc6de3 removing type: ignore from Query, Depends, Body and import them correctly 2023-01-02 11:56:28 +01:00
dni ⚡ ff274e4eac formatting 2023-01-02 11:25:57 +01:00
dni ⚡ 3824a108aa fix scrub for mypy 2023-01-02 11:25:01 +01:00
dni ⚡ dd645f8822 fix events for mypy 2023-01-02 11:18:25 +01:00
ben a8c4bea53d Updated extension card layout 2023-01-01 18:14:20 +00:00
ben 7d7cbeeb71 Adjusted layout for ratings and to stop overflow on mobile 2023-01-01 14:55:01 +00:00
ArcandGitHub 765fd892bf Merge pull request #1264 from lnbits/fix/mypy/nostrnip5
fix mypy for nostrnip5 extension
2023-01-01 13:52:07 +00:00
ben 59a8ff9dce All extension icons swapped for logo tiles 2022-12-31 14:15:23 +00:00
ben f5250e0584 initial
Added bunch tiles, more to add
2022-12-31 01:46:55 +00:00
Tiago Vasconcelos b6f87e5a1e some sat sugar in market products 2022-12-30 17:07:06 +00:00
Tiago Vasconcelos 7c1a639796 make markets great again 2022-12-30 16:51:09 +00:00
Pavol Rusnak aa97e3cb54 add -o to find opts meaning or (default is and)
which returns no results
2022-12-30 16:07:32 +01:00
Pavol Rusnak f7a977ecc8 let's use unique function names
so mypy is not confused
2022-12-30 13:16:11 +01:00
Tiago Vasconcelos 715709ce25 fix typo 2022-12-30 12:03:34 +00:00
Tiago Vasconcelos 77051d2609 toggle shipped 2022-12-30 12:00:16 +00:00
Tiago Vasconcelos 301cfb25d9 market is not fully ready! added stall name 2022-12-30 10:59:24 +00:00
Tiago Vasconcelos 0b9672076d add remove product and reset cart buttons 2022-12-30 10:30:29 +00:00
Tiago Vasconcelos 0fdc13666f fix NaN in cart total 2022-12-30 10:00:11 +00:00
dni ⚡ bb8e86addf formatting 2022-12-30 09:48:52 +01:00
dni ⚡ f3e0bd4907 formatting 2022-12-30 09:47:49 +01:00
dni ⚡ bf69e91cae fix mypy for nostrnip5 ext 2022-12-30 09:46:45 +01:00
dni ⚡ 8e344ecf4b gerty mypy fixes 2022-12-30 09:24:31 +01:00
dni ⚡ 4b388475ce fix tipjar mypy issues 2022-12-30 08:48:31 +01:00
ArcandGitHub efc156689f Merge pull request #1253 from iWarpBTC/qr_inv_pref
QR invoices and lnurls got prefix and uppercased
2022-12-30 01:04:47 +00:00
ArcandGitHub 749fbe1ed8 Merge pull request #1255 from lnbits/fix/lnurlp_id_uuid
add UUID as id instead of sequential integer
2022-12-30 00:53:09 +00:00
ArcandGitHub 6ec81c5586 Merge pull request #1261 from lnbits/hyperlinfix
Fixes all those ugly hyperlinks
2022-12-30 00:02:42 +00:00
ben 91afd69a95 format 2022-12-29 23:58:36 +00:00
ben 6e8abc1c32 Fixes all those ugly hyperlinks 2022-12-29 23:51:18 +00:00
ArcandGitHub 9b9ecfb913 Merge pull request #1258 from lnbits/nip05linkinstructions
Added docs to nostrnip05
2022-12-29 23:39:14 +00:00
ben ba8b2f33d0 format 2022-12-29 23:27:50 +00:00
ben 065d09df42 Little legal disclaimer 2022-12-29 23:13:56 +00:00
Pavol Rusnak 2f377343f1 extensions/gerty: remove duplicit functions
probably merge error
2022-12-30 00:11:23 +01:00
ben 945b0f3001 Added docs to extension 2022-12-29 23:08:48 +00:00
ArcandGitHub b1e08744ff Merge pull request #1225 from leesalminen/main
New Extension: NIP-05 Verification
2022-12-29 22:32:42 +00:00
ArcandGitHub a447eb5f85 Merge pull request #1207 from lnbits/gerty
Adds Gerty Extension
2022-12-29 22:27:16 +00:00
ben 860e6ddc4a Added to ignore 2022-12-29 22:19:38 +00:00
ben 861f1e585e added to ignore 2022-12-29 22:18:42 +00:00
Gene TakavicandGitHub 0cc6c0db79 Update README.md
note on non-standard states
2022-12-29 22:03:46 +01:00
Gene TakavicandGitHub e01683b387 Revert "Update BoltCard Extension README" 2022-12-29 21:50:53 +01:00
ben fe29ad8c5e format 2022-12-29 19:35:58 +00:00
ben ec8202d008 Merge branch 'main' into gerty 2022-12-29 19:05:31 +00:00
ben 89889eced1 Merge remote-tracking branch 'blackcoffeexbt/gerty' into gerty 2022-12-29 19:05:21 +00:00
Pavol Rusnak a377381942 use bytes.fromhex/hex builtins instead of binascii.hexlify/unhexlify 2022-12-29 16:46:35 +01:00
Lee Salminen 4ab5285156 format 2022-12-29 08:16:51 -06:00
calleandGitHub 55c9c18bd1 Merge pull request #1241 from arbadacarbaYK/main
Update BoltCard Extension README
2022-12-29 13:58:27 +01:00
calleandGitHub 8d72354d3f Update lnbits/extensions/boltcards/README.md 2022-12-29 13:56:20 +01:00
calleandGitHub 3cc27c9d80 Update lnbits/extensions/boltcards/README.md 2022-12-29 13:56:15 +01:00
calleandGitHub 7d3888a28b Merge pull request #1244 from lnbits/refactor/remove_unused_imports
refactor: remove unused imports
2022-12-29 13:51:17 +01:00
calleandGitHub 579df7df7f Merge pull request #1247 from prusnak/drop-result
remove result link
2022-12-29 13:50:53 +01:00
calleandGitHub a0e46f1994 Merge pull request #1254 from joelklabo/joel/fix-typo-migration-logging
Fix Typo in Migration Logging
2022-12-29 13:39:10 +01:00
Tiago Vasconcelos 6634c568b2 fix returning link on create 2022-12-29 12:05:02 +00:00
Tiago Vasconcelos 690ef74313 fix products price in fiat 2022-12-29 12:02:03 +00:00
Tiago Vasconcelos 02810fb358 add UUID as id 2022-12-29 11:21:29 +00:00
Joel Klabo f70b417b41 Fix Typo in Migration Logging 2022-12-28 16:06:01 -08:00
Gene Takavic e7cf4e6cb8 make format 2022-12-28 23:46:06 +01:00
Gene Takavic 71eebc1441 QR invoices and lnurls got prefix and uppercased 2022-12-28 23:42:36 +01:00
Pavol Rusnak aa267f52a2 enable nix flake on {aarch64,x86_64}-darwin 2022-12-28 15:09:30 +01:00
Pavol Rusnak 86b0cec215 remove result link
this shouldn't have been commited since it it listed in gitignore)
2022-12-28 12:48:31 +01:00
Lee Salminen 8265bbfeb5 regex validation 2022-12-27 16:19:39 -06:00
Lee Salminen 2c803a745d case insensitive 2022-12-27 16:13:56 -06:00
callebtc cd11f92ed9 make format 2022-12-27 15:00:41 +01:00
callebtc ead5ae0d82 refactor: remove unused imports 2022-12-27 14:50:42 +01:00
calleandGitHub 89aa4a1de9 Merge pull request #1243 from lnbits/fix/require_admin_user/super_user
fix: remove require_admin_user
2022-12-27 14:42:57 +01:00
callebtc 6e7bfd03ee clean up 2022-12-27 14:19:51 +01:00
callebtc a0429660f4 fix: remove require_admin_user 2022-12-27 14:18:00 +01:00
dni ⚡ 7218057ca2 update lock file and workflows to poetry 1.3.1 2022-12-27 13:20:07 +01:00
Tiago Vasconcelos 11b015e516 add currency to stalls show products in fiat 2022-12-27 11:12:29 +00:00
arbadacarbaandGitHub 55069b0216 Update README.md 2022-12-27 11:50:26 +01:00
calleandGitHub 9968b1885c Merge pull request #1178 from lnbits/refactor/depend_admin_user
Refactor: `require_admin_user` decorator
2022-12-26 12:20:25 +01:00
callebtc a243e4e320 fix: update to latest changes 2022-12-26 12:12:27 +01:00
callebtc feb61a3fa7 merge 2022-12-26 12:04:07 +01:00
calleandGitHub e3c14876a6 Merge pull request #1229 from motorina0/fix_extra_column_select
fix: handle `extra` updates for internal payments
2022-12-26 11:56:30 +01:00
calleandGitHub b655b9959d Merge pull request #1235 from motorina0/fix_wallet_deleted_for_charge
fix: onchain wallet deleted for charge
2022-12-26 11:54:16 +01:00
calleandGitHub 6825a1f2e6 Update lnbits/extensions/satspay/views_api.py 2022-12-26 11:52:40 +01:00
calleandGitHub 925ed3c821 Merge pull request #1239 from lnbits/api/health_checl
feat: api health check endpoint
2022-12-25 18:51:40 +01:00
callebtc b6f0d7b769 feat: api health check endpoint 2022-12-25 18:49:51 +01:00
calleandGitHub 0df81e5aaf Merge pull request #1238 from lnbits/fix/cashu/wallet/duplicate_history
Cashu wallet: correctly update ui on token sending
2022-12-24 16:33:24 +01:00
callebtc 8e2cb187da fix: correctly update ui on token sending 2022-12-24 16:32:38 +01:00
calleandGitHub 13f0159c15 Merge pull request #1237 from lnbits/fix/cashu/timeout
cashu: refactor wallet
2022-12-24 14:02:13 +01:00
callebtc 5ecbbb1abe add warning 2022-12-24 13:58:39 +01:00
Lee Salminen fe2be38ebb add manual activate button 2022-12-24 05:42:00 -06:00
calleandGitHub 7b7d06cee3 Merge pull request #1232 from lnbits/fix/allowed_user
BUG: fix auth issue for superusers  when allowed users are set
2022-12-24 11:22:49 +01:00
callebtc b5147181af feat: add /keys/{keyset} endpoint 2022-12-24 04:03:04 +01:00
callebtc 1fd351b959 refactor wallet 2022-12-24 02:32:40 +01:00
ArcandGitHub e541f0224c Merge pull request #1236 from lnbits/lnurldeviceshorten
Swaps UUID for number, to reduce URL size
2022-12-23 22:56:40 +00:00
ben dd90c3a2eb format 2022-12-23 22:47:14 +00:00
ben cd6f4c909f swaps pos id for number 2022-12-23 22:38:19 +00:00
Vlad Stan 50addd73dd chore: log error in debug mode 2022-12-23 22:21:14 +02:00
Vlad Stan 8a447ecc4f fix: onchain wallet deleted for charge 2022-12-23 22:15:32 +02:00
ben dce50105c9 no such column 2022-12-23 15:41:59 +00:00
ben 0ce987a20e await 2022-12-23 15:40:52 +00:00
ben 03846adbfb unsupported type 2022-12-23 15:40:05 +00:00
ben 886070755c hacky lnurlpos bump 2022-12-23 15:35:06 +00:00
dni ⚡ 1be460aec3 fix user not exist bug in frontend 2022-12-23 11:53:45 +01:00
calleandGitHub d0ed93aec8 Merge pull request #1234 from lnbits/fix/cashu/logging1
fix logger
2022-12-22 20:10:28 +01:00
callebtc 0d8ca58a67 fix logger 2022-12-22 20:10:01 +01:00
calleandGitHub e3ed2c4e6d Merge pull request #1233 from lnbits/fix/cashu/logging
cashu: add more logging
2022-12-22 19:58:47 +01:00
callebtc 090cf414e7 cashu: add more logging 2022-12-22 19:58:01 +01:00
Tiago Vasconcelos b1473fc1a4 add currency option 2022-12-22 18:26:34 +00:00
dni ⚡ 1f75d5fd41 fix it up 2022-12-22 17:58:49 +01:00
dni ⚡ 7c96a12a3c fix issue with allowed_users and admin/superuser 2022-12-22 17:48:46 +01:00
Tiago Vasconcelos c412371ee6 cleanup 2022-12-22 12:33:15 +00:00
Tiago Vasconcelos 738f392270 Merge branch 'main' into diagon-alley 2022-12-22 12:25:30 +00:00
Tiago Vasconcelos ef73f3ca75 fix some bugs and format 2022-12-22 12:23:22 +00:00
Tiago Vasconcelos cdb5e8176d more cleanup 2022-12-22 12:13:25 +00:00
Tiago Vasconcelos 293edd0d6b bring key backup and add bookmark dialog 2022-12-22 12:07:38 +00:00
Tiago Vasconcelos 4ad7d915e5 checkwebsocket function 2022-12-22 11:03:23 +00:00
Tiago Vasconcelos 270712e07d DROP CaSCADE for deleting shops 2022-12-22 10:45:56 +00:00
Tiago Vasconcelos 1191957495 clean up prints 2022-12-22 10:45:34 +00:00
Tiago Vasconcelos 2a078651b9 add product button to table 2022-12-22 10:45:18 +00:00
Tiago Vasconcelos 952677ae13 refactor keys 2022-12-22 10:44:56 +00:00
Tiago Vasconcelos b179b48edc message notification 2022-12-22 10:44:10 +00:00
Vlad Stan 98e0ea5ae2 refactor: only use one (direction) flag for identifying payment 2022-12-22 12:23:46 +02:00
calleandGitHub 5f920fd290 Merge pull request #1228 from AxelHamburch/Admin_UI_doc
Add "How to activat the Admin UI"
2022-12-22 11:08:03 +01:00
calleandGitHub 49a68df5e4 Apply suggestions from code review 2022-12-22 11:07:49 +01:00
Vlad Stan a434731729 fix: update the rest of update_payment_extra calls 2022-12-22 11:28:12 +02:00
Vlad Stan b56d08a70e fix: handle extra updates for internal payments 2022-12-22 11:12:19 +02:00
Lee Salminen 4f39dce206 update readme 2022-12-21 15:17:28 -06:00
AxelandGitHub 8d5f5e4afb Add "How to activat the Admin UI" 2022-12-21 21:33:10 +01:00
Lee Salminen e65082b585 add self-service pubkey rotation for nip-05 addresses 2022-12-21 14:05:45 -06:00
Tiago Vasconcelos 10a1a28d04 make chat work again + some weird formatting... again 2022-12-21 18:35:53 +00:00
Tiago Vasconcelos b59fa77dbd some formatting 2022-12-21 17:47:15 +00:00
Tiago Vasconcelos c9c7ad90ab remove view details from product card 2022-12-21 17:47:00 +00:00
Tiago Vasconcelos 272e5b6fd0 use nostr-tools but nip04 encrypt doesn't work 2022-12-21 17:46:27 +00:00
Tiago Vasconcelos 8e337c890e endpoint to add buyer public key to db 2022-12-21 17:45:44 +00:00
Tiago Vasconcelos 8037557db6 remove private key everywhere 2022-12-21 17:44:37 +00:00
calleandGitHub 4e8014a177 Merge pull request #1227 from motorina0/withdraw_callback_extra
Withdraw webhook improvements
2022-12-21 14:52:07 +01:00
Vlad Stan 7b0f9f61fc fix: remove double dict 2022-12-21 15:47:35 +02:00
Vlad Stan f3c884111d refactor: use dict.update() 2022-12-21 15:47:22 +02:00
blackcoffeexbt fc60dc125f Merge branch 'lnbits-main' 2022-12-21 13:46:18 +00:00
blackcoffeexbt d423a0307f Merge branch 'main' of https://github.com/lnbits/lnbits into lnbits-main 2022-12-21 13:46:04 +00:00
Vlad Stan ec01c74da1 fix: mypy 2022-12-21 15:10:12 +02:00
Vlad Stan 60cd40d5e5 feat: update_payment_extra with webhook response 2022-12-21 15:06:45 +02:00
Vlad Stan 0ab99bef41 refactor: use update_payment_extra 2022-12-21 15:06:45 +02:00
Vlad Stan 8fae90bb9d feat: add webhook_headers and webhook_body to withdraws 2022-12-21 15:06:45 +02:00
Vlad Stan dd4a9f10cf feat: add function to update the extra JSON values 2022-12-21 15:06:45 +02:00
Vlad Stan b68b8a0292 fix: do not loose error, log it 2022-12-21 15:06:45 +02:00
blackcoffeexbtandGitHub cbb909bb3a Merge pull request #6 from lnbits/gerty
Merge in lnbits/Gerty
2022-12-21 12:16:15 +00:00
Tiago Vasconcelos f1f68658d2 make partials for readability 2022-12-20 16:44:33 +00:00
Tiago Vasconcelos dd09eac2a5 generate keys on frontend 2022-12-20 16:44:00 +00:00
Lee Salminen 6996155cae format 2022-12-20 10:40:01 -06:00
Lee Salminen 6787e4916e allow admins to manually create addresses without paying 2022-12-20 10:39:45 -06:00
Lee Salminen 0f417110cb improved validation 2022-12-20 10:32:45 -06:00
Lee Salminen 45a69a1139 fix readme 2022-12-20 10:32:28 -06:00
Tiago Vasconcelos 70a051a413 make format && generate keys with schnorr 2022-12-20 16:11:56 +00:00
calleandGitHub 81df5a56e2 Merge pull request #1219 from islandbitcoin/patch-2
Remove duplicate LNBITS_ALLOWED_USERS
2022-12-20 16:25:56 +01:00
Lee Salminen ad572f267a Merge branch 'main' of github.com:leesalminen/lnbits-legend 2022-12-20 08:45:11 -06:00
Lee Salminen 4e996c821e format and mypy 2022-12-20 08:44:28 -06:00
Lee SalminenandGitHub f5bdcc8ea5 Merge branch 'lnbits:main' into main 2022-12-20 08:08:57 -06:00
Lee Salminen 189347ee39 readme 2022-12-20 08:06:25 -06:00
Lee Salminen 51076634bd allow users to enter their npub bech32 encoded too 2022-12-20 07:49:59 -06:00
Lee Salminen f7b4aa91ab API docs 2022-12-20 07:49:35 -06:00
calleandGitHub 49e722d9da Merge pull request #1223 from lnbits/fix/mypy/usermanager
FIX: mypy issues with usermanager extension
2022-12-20 14:48:38 +01:00
dni ⚡ c2df13bf24 formatting 2022-12-20 14:23:02 +01:00
Lee Salminen 632636ff0a rename function 2022-12-20 07:16:55 -06:00
Lee Salminen 048ccd6d65 formatting 2022-12-20 07:15:54 -06:00
Lee Salminen 9e857e6329 better validation on pubkeys 2022-12-20 07:14:03 -06:00
dni ⚡ 36ae851a8d nitpicking vlad :) 2022-12-20 13:44:14 +01:00
Lee Salminen 3ca9edeee6 WIP nostr nip5 extension 2022-12-20 06:28:43 -06:00
dni ⚡ 29869cfcda formatting 2022-12-20 13:18:37 +01:00
dni ⚡ 22cafe9ed0 remove uneeded if 2022-12-20 13:17:07 +01:00
dni ⚡ c90f7878d7 fix usermanager mypy 2022-12-20 13:14:49 +01:00
Vlad Stan c5fdd35078 feat: store mor info about the webhook call status 2022-12-20 13:32:03 +02:00
Vlad Stan 681883a8f9 fix: catch all exceptions for webhook 2022-12-20 13:32:03 +02:00
calleandGitHub 9fed2f2ae0 Merge pull request #1221 from lnbits/deployment/docker/update-python3.10
docker: update to python 3.10
2022-12-20 11:10:00 +01:00
callebtc 375f5d9f25 docker: update to python 3.10 2022-12-20 11:07:26 +01:00
Tiago Vasconcelos 9b69851155 Merge remote-tracking branch 'origin/diagon-alley' into diagon-alley 2022-12-20 09:53:54 +00:00
Tiago Vasconcelos 7a1ec09104 add toast on add to cart 2022-12-20 09:53:37 +00:00
calleandGitHub ab6d9daf6d Merge pull request #1220 from lnbits/deployment/build_docker_on_minor
deploy: build on minor changes
2022-12-20 10:36:41 +01:00
callebtc 4bd0372819 deploy: build on minor changes 2022-12-20 10:30:32 +01:00
ben 4d2f9facee fixed message box 2022-12-20 08:51:07 +00:00
ben 64bf0c406b messages max height 2022-12-20 08:48:42 +00:00
ben 9e03c05bcc Whoops 2022-12-20 08:44:59 +00:00
ben 3038b4be86 removed key need 2022-12-20 08:44:22 +00:00
ben 1bf05df4a9 removed 2022-12-20 08:43:22 +00:00
ben 1a820b4dac v-if all diagonalley things 2022-12-20 08:42:11 +00:00
ben 5765c3c8c9 renamed folder 2022-12-20 08:35:17 +00:00
ben fa23eeb055 improved description 2022-12-20 08:32:54 +00:00
ben e1cad034a2 Fixed toggle, made description more generic 2022-12-20 08:25:42 +00:00
ben 2a89743a3d Renamed DA to shop 2022-12-20 08:21:48 +00:00
ben 955eace387 Merge remote-tracking branch 'origin/main' into gerty 2022-12-20 08:18:13 +00:00
DreadandGitHub 97d1effe1c Remove duplicate LNBITS_ALLOWED_USERS 2022-12-19 18:55:05 -05:00
ben 8ba380fcbd format 2022-12-19 23:34:54 +00:00
Tiago Vasconcelos 8fdebc2a98 Merge branch 'main' into diagon-alley 2022-12-19 15:56:17 +00:00
Tiago Vasconcelos 8381e32148 add diagonally mode toggle 2022-12-19 15:29:02 +00:00
Tiago Vasconcelos 105d6b7964 rename extension to Shop 2022-12-19 15:21:35 +00:00
calleandGitHub 9881e68402 Merge pull request #1217 from lnbits/fix/wallets/cln/settings
Fix/wallets/cln/settings
2022-12-19 15:56:09 +01:00
callebtc 7650d5775f backwards compatible 2022-12-19 15:54:31 +01:00
callebtc 385cda47cf fix cln setting 2022-12-19 15:48:55 +01:00
ben e69f2be1f0 format 2022-12-19 13:24:08 +00:00
ben 53be745737 Bad env calls 2022-12-19 13:22:53 +00:00
ben fcaf6967fa Merge branch 'main' into gerty 2022-12-19 13:03:19 +00:00
ben 65419b1b4b Merge remote-tracking branch 'origin/main' into diagon-alley 2022-12-17 15:04:51 +00:00
ben 04bc6c8975 Merge branch 'main' into gerty 2022-12-17 10:12:53 +00:00
ben 85dbab14f9 isort 2022-12-16 23:13:20 +00:00
ben 8f00e35d1e Merge branch 'main' into gerty 2022-12-16 23:02:45 +00:00
ben 68bcfadd42 Added servers to check 2022-12-16 12:38:14 +00:00
ben 2403d0adfb adding urls 2022-12-16 10:36:49 +00:00
ben 731544492e market link broken 2022-12-15 23:47:31 +00:00
ben d4d5b7a14d Merge branch 'main' into diagon-alley 2022-12-15 23:33:07 +00:00
ben 1357b17b64 keys now working 2022-12-15 23:32:49 +00:00
ben 495719c366 MVP working 2022-12-15 23:12:13 +00:00
ben ee79bdcfc8 Merge remote-tracking branch 'origin/main' into gerty 2022-12-15 14:50:48 +00:00
ben 2e786e63bf Pretty much everything loading in software Gerty 2022-12-15 14:50:32 +00:00
ben eb9ec21c89 Spagetthi, needs cleaning and refactor 2022-12-15 11:16:19 +00:00
ben cde7e4a5e9 added wallet colors 2022-12-15 10:51:29 +00:00
ben 7ce2587bdb gerty lnbits walllet working 2022-12-14 14:07:05 +00:00
UthpalaandGitHub e7d1309c76 Merge pull request #2 from Uthpala/add-migrations
add migrations
2022-12-11 17:53:48 +01:00
Uthpala Heenatigala 663cee8fbe fix ui bug 2022-12-11 17:42:47 +01:00
UthpalaandGitHub 5cbe89632f Merge branch 'lnbits:main' into main 2022-12-11 17:22:04 +01:00
Danny Diekroeger c2423605cc add migrations 2022-12-10 11:00:36 -08:00
Uthpala Heenatigala d338e3889a Add on chain fee suggestions 2022-12-08 16:42:33 +01:00
ben b6db683f30 Merge remote-tracking branch 'origin/main' into gerty 2022-12-08 13:45:50 +00:00
ben c239c4f552 Endpoints working 2022-12-08 13:45:38 +00:00
ben 8e6eb63c9e Added dashboards 2022-12-08 11:42:51 +00:00
ben 54c1757423 adding checkboxes for dashboards 2022-12-08 11:10:09 +00:00
Uthpala Heenatigala 902a9bdc42 running make format 2022-12-08 11:57:14 +01:00
ben 08ed86fb18 Shuffling UI, trying to ditch variables 2022-12-08 10:24:37 +00:00
Danny Diekroeger daa0070e08 description in panel 2022-12-07 17:10:39 -08:00
Danny Diekroeger 3c07e5ad8b deezy readme 2022-12-07 15:44:28 -08:00
Uthpala Heenatigala 472e332d05 clean up, update readme 2022-12-07 23:56:22 +01:00
UthpalaandGitHub 244f440cb8 Merge branch 'lnbits:main' into main 2022-12-07 23:40:02 +01:00
dannydeezyandGitHub bd5d489ac2 Merge pull request #1 from Uthpala/minor-touchups
Nits + mainnet
2022-12-07 11:40:31 -08:00
ben 53e910d549 Merge branch 'main' into gerty 2022-12-07 15:38:06 +00:00
callebtc ab4a9370e7 style: make format 2022-12-07 14:57:10 +01:00
callebtc 5f4fa61310 refactor:depend_admin_user 2022-12-07 14:56:45 +01:00
Danny Diekroeger c0789858a1 Nits + mainnet 2022-12-06 18:46:11 -08:00
Uthpala Heenatigala aa0ff732df adding polling and success message 2022-12-06 23:48:31 +01:00
Uthpala Heenatigala 1b2e8d218d add api docs and update according to requirements 2022-12-06 22:39:45 +01:00
ben d70085c606 Merge branch 'main' into gerty 2022-12-06 20:36:06 +00:00
ben 9971a560e1 Trying to catchup with main 2022-12-06 20:35:58 +00:00
Uthpala Heenatigala 209750386e forgot to update this one 2022-12-05 21:12:17 +01:00
Uthpala Heenatigala 6ec5b9abf6 convert string to ints 2022-12-05 21:07:38 +01:00
UthpalaandGitHub f52e8ebfa1 Merge branch 'lnbits:main' into main 2022-12-04 23:31:17 +01:00
Uthpala Heenatigala 4645b8338b added the reverse swap as well 2022-12-04 23:30:48 +01:00
Black Coffee 42ce869b08 Merge branch 'main' of githubblackcoffeexbt:blackcoffeexbt/lnbits-legend 2022-12-01 10:14:49 +00:00
blackcoffeexbtandGitHub ca2aaa1221 Merge branch 'lnbits:main' into main 2022-12-01 10:14:29 +00:00
ben 8edcb25eee Merge branch 'diagon-alley' of https://github.com/lnbits/lnbits-legend into diagon-alley 2022-11-30 12:47:29 +00:00
Tiago Vasconcelos 3051971db7 add a check if generateKeys gets called from merchant 2022-11-30 12:37:15 +00:00
ben dae9db7042 Merge remote-tracking branch 'origin/diagon-alley' into diagon-alley 2022-11-30 12:22:36 +00:00
Tiago Vasconcelos 8e649501cf remove payment_hash from generate keys 2022-11-30 12:20:58 +00:00
ben 5dbcbb7489 Merge remote-tracking branch 'origin/diagon-alley' into diagon-alley 2022-11-30 11:59:51 +00:00
UthpalaandGitHub 697b105a10 Merge branch 'lnbits:main' into main 2022-11-30 00:29:27 +01:00
Uthpala Heenatigala 43a974af83 initializing the deezy extension 2022-11-29 23:18:24 +01:00
Tiago Vasconcelos 610814f0d0 add onboarding tutorial 2022-11-27 16:50:31 +00:00
Tiago Vasconcelos e9109b0b49 make format 2022-11-25 14:24:17 +00:00
ben 166df0104e Revert "Merge remote-tracking branch 'origin/fix/strip_html_db' into gerty"
This reverts commit 490ab65165.
2022-11-25 14:22:14 +00:00
ben 490ab65165 Merge remote-tracking branch 'origin/fix/strip_html_db' into gerty 2022-11-25 14:21:00 +00:00
Tiago Vasconcelos 78e94df9c5 export order page URL 2022-11-25 14:20:01 +00:00
ben 5be1cf2973 Fixed fe bugs, working fine 2022-11-25 01:00:29 +00:00
ben 83e4eb16b1 Caching working, fetches from mempool every 20 secs 2022-11-25 00:49:31 +00:00
ben e84eedd66f cleaned up 2022-11-24 23:31:22 +00:00
ben e6a3c63dd5 Shared mempool.space cache 2022-11-24 19:27:12 +00:00
ben b4c970a005 Merge branch 'main' into gerty 2022-11-24 16:55:04 +00:00
Tiago Vasconcelos e725ae097d display and backup keys for merchant 2022-11-24 14:42:04 +00:00
ben 5827eb33c5 Merge branch 'main' into gerty 2022-11-23 21:07:16 +00:00
ben 061e9a1812 Merge remote-tracking branch 'blackcoffeexbt/gerty' into gerty 2022-11-20 12:13:22 +00:00
Black Coffee 00e846b49b Reduce precision in get_percent_difference func 2022-11-18 09:48:55 +00:00
Black Coffee 9ca928c6e1 Gerty label tweaks 2022-11-18 09:46:26 +00:00
Black Coffee 189593af75 Added blockheight as standalone screen to Gerty 2022-11-17 21:55:23 +00:00
Black Coffee 7ef61b6d3c gerty tweaks 2022-11-17 17:43:52 +00:00
Black Coffee 60962713f4 gerty tweaks 2022-11-17 17:43:09 +00:00
Black Coffee b8be50a592 gerty tweaks 2022-11-17 17:42:15 +00:00
Black Coffee 4a6f7050cd gerty tweaks 2022-11-17 17:40:15 +00:00
Black Coffee 1d7109ddf9 gerty tweaks 2022-11-17 17:39:04 +00:00
Black Coffee 41c22b2587 Disable mempool fees for mini gerty 2022-11-17 15:14:45 +00:00
Black Coffee b2e2c11af8 Added mini gerty api support to front end 2022-11-17 15:11:31 +00:00
Black Coffee 592a52e9a2 Added type to Gerty extension Vue, migration and Crud 2022-11-17 14:13:20 +00:00
ArcandGitHub d4645496f1 Merge pull request #1130 from blackcoffeexbt/gerty
Gerty
2022-11-14 20:28:44 -08:00
Tiago Vasconcelos 7e24e129f6 fix create index error on sqlite 2022-11-15 00:59:41 +00:00
Black Coffee df56b84ad2 Tweaks 2022-11-14 14:07:26 +00:00
Black Coffee a0a7ae9be1 Added single page stats back to Gerty 2022-11-14 13:28:25 +00:00
Tiago Vasconcelos d5e1551839 format 2022-11-11 12:12:31 +00:00
Tiago Vasconcelos 82e887b7a8 clean up and add pubkey to order 2022-11-11 12:11:14 +00:00
Black Coffee b1c9e9a87e Update gerty api line lengths 2022-11-07 16:18:40 +00:00
Black Coffee 6cb837d2b7 Removed "priority" text from Gerty mempool display 2022-11-07 15:01:26 +00:00
Black Coffee d856d4c920 Ran make format 2022-11-07 15:00:45 +00:00
Black Coffee 1a17ec08ee Bug fix 2022-11-06 10:04:18 +00:00
Black Coffee e5c694bae9 Merge branch 'main' into gerty 2022-11-03 21:44:50 +00:00
Tiago Vasconcelos f3fc1e4a2b format 2022-10-25 12:32:19 +01:00
Tiago Vasconcelos 45ef9222fe Merge branch 'main' into diagon-alley 2022-10-25 12:30:42 +01:00
Tiago Vasconcelos c282f38726 cleanup and keys management 2022-10-25 12:30:15 +01:00
Tiago Vasconcelos 227eaeae75 some cleanup 2022-10-25 11:57:01 +01:00
Tiago Vasconcelos 4dcb8d2ca0 redirect to order page 2022-10-25 11:55:55 +01:00
ben 07a95532be Merge branch 'bcgerty' into gerty 2022-10-24 15:37:52 +01:00
ben 2f2e06c99e Merge remote-tracking branch 'origin/main' into bcgerty 2022-10-24 15:37:25 +01:00
Black Coffee 5010806c4d Merge lnbits/gerty into blackcoffeexbt/gerty 2022-10-24 10:33:49 +01:00
Black Coffee a9d72fbef0 Merge in main 2022-10-24 10:26:16 +01:00
ben 82fea2a4ea Merge remote-tracking branch 'origin/main' into gerty 2022-10-23 20:35:12 +01:00
Black Coffee aa33f74f5d Bug fix wwhen trying to get a gerty screen that doesnt exist 2022-10-20 17:20:47 +01:00
Black Coffee e193c4ef47 Replaced pytz with utc_offset 2022-10-20 17:15:12 +01:00
Black Coffee 65a3e4feb2 Font size changes on onchain data dashboard 2022-10-20 17:04:34 +01:00
Black Coffee 82d4933d74 Fix link to API 2022-10-20 17:01:21 +01:00
Black Coffee ce092fcded Fix link to API 2022-10-20 17:01:03 +01:00
Black Coffee ecfd4b3d66 Added UTC field to gerty modal 2022-10-20 16:58:28 +01:00
Black Coffee f636951d3c Added gerty migration for UTC offset col 2022-10-20 16:40:48 +01:00
Black Coffee 07042a7d4f Formatted gerty extension 2022-10-20 16:31:09 +01:00
Black Coffee 1c56622d89 formatted 2022-10-20 16:14:45 +01:00
Black Coffee 60c88beb29 Removed unused toggleAll logic from gerty modal 2022-10-20 16:05:15 +01:00
Black Coffee dc02601db1 Fixed casing on Gerty extension modal UI 2022-10-20 16:03:28 +01:00
Black Coffee ff88e20fa3 Replaced current block itme with progress through current epoch 2022-10-20 16:02:21 +01:00
Black Coffee 9f3b4c48ea Created mining dashboard 2022-10-20 15:59:55 +01:00
Black Coffee f1df9fe243 Removed mempool_tx_count from api 2022-10-20 15:14:22 +01:00
Black Coffee de583caa80 Moved lightninig network dashboard out of expansion group 2022-10-20 15:13:16 +01:00
Black Coffee 004cd05973 Mempool fees now standalone item 2022-10-20 15:10:08 +01:00
Black Coffee b86ffd0c3d Merged all onchain items into single dashboard 2022-10-20 15:09:09 +01:00
Black Coffee 5ae4353a70 Removed LNbits wallets balance screen from options 2022-10-20 15:00:49 +01:00
Black Coffee 79e98c2f6c Removed pieter wuille quotes 2022-10-20 14:59:41 +01:00
Tiago Vasconcelos 02d8d4ca4a Merge branch 'main' into diagon-alley 2022-10-19 22:09:51 +01:00
Tiago Vasconcelos 532e12ec83 merchant chat functional almost out of WIP 2022-10-19 22:08:48 +01:00
Tiago Vasconcelos ebe8d8b0b9 just for bragging... 2022-10-19 22:07:38 +01:00
Black Coffee aa040f3a6c Bug fixes 2022-10-10 13:38:21 +01:00
Black Coffee 0b63db46db Tweak wallet balance text 2022-10-08 18:08:58 +01:00
Black Coffee 95e303d9ba bug fix for sleep hours 2022-10-07 22:12:48 +01:00
Black Coffee b5c992afd9 Added overnight sleep for Gerty 2022-10-07 10:25:54 +01:00
Black Coffee 40c921c7f5 Bug fix wallet balance 2022-10-06 22:01:40 +01:00
Black Coffee 24b87bed9f Added pytz to requirements.txt 2022-10-06 21:49:47 +01:00
Black Coffee 4a40f4edc7 Added local next update time using pytz 2022-10-06 21:36:53 +01:00
Black Coffee 9308d1b689 Add dashboard title 2022-10-06 17:48:49 +01:00
Black Coffee d3e6bb3444 loads more work on API formatting 2022-10-06 17:06:22 +01:00
Black Coffee 662e637758 Added lightning network dashboard 2022-10-06 16:12:48 +01:00
Black Coffee 6ecf27e168 Api tweaks 2022-10-06 15:02:45 +01:00
Black Coffee c5b1f2870f added mining difficulty 2022-10-06 13:14:31 +01:00
Black Coffee d67efc320d Added hashrate endpoint 2022-10-06 12:37:11 +01:00
Black Coffee 1280d3ba74 Started move of gerty api calls into helpers.py 2022-10-06 12:13:19 +01:00
Black Coffee f828e4a641 Laid out mempool.space fees page 2022-10-06 12:08:46 +01:00
Black Coffee 9efe15a699 Adding mempool fees endpoint 2022-10-06 11:05:49 +01:00
Black Coffee 0f0d24a7ec move non dashboard data into areas list 2022-10-04 19:08:24 +01:00
Black Coffee 0a63576a6a Added refresh time 2022-10-04 10:54:34 +01:00
Black Coffee 46daf57cac Moved text into an areas list 2022-10-04 10:52:53 +01:00
Tiago vasconcelos c59e9efabc add public key and refactor endpoint 2022-10-04 10:00:42 +01:00
Tiago vasconcelos 12d94b3e60 placeholder for product page WIP 2022-10-04 09:59:34 +01:00
Tiago vasconcelos d5388ba7de add publickey if stored 2022-10-04 09:59:18 +01:00
Tiago vasconcelos bd4fd73417 rename endpoint and add more data 2022-10-04 09:58:53 +01:00
Black Coffee 72b953b354 Got basic dashboard layout working 2022-10-03 17:18:53 +01:00
Tiago vasconcelos 93d61a7ac5 Merge branch 'main' into diagon-alley 2022-10-03 10:35:50 +01:00
Black Coffee 5a2aa8b42b sat quote font size to 15 2022-10-02 16:23:59 +01:00
Black Coffee ce398d26c3 decrease sat char count to 180 2022-10-02 16:23:20 +01:00
Black Coffee 2e5a7b6a86 Reinstated all sat quotes and added temp max char limit in views_api for testing 2022-10-02 16:17:16 +01:00
Black Coffee 6f09136f73 Trimmed satoshi quotes down to one with 5 lines for testing memory issues 2022-10-02 16:05:26 +01:00
Black Coffee 1f660d6694 Fix satoshi quotes bug 2022-10-02 11:25:07 +01:00
Black Coffee 01681c3af2 x 2022-10-02 10:20:05 +01:00
Black Coffee 168941a0ad Removed really long satoshi quotes 2022-10-01 16:25:46 +01:00
Black Coffee ebc0e60cc9 Added Wuille facts and tweaked line lengths 2022-10-01 16:21:46 +01:00
Tiago vasconcelos 98ef0dfcb1 format 2022-09-30 17:29:35 +01:00
Tiago vasconcelos db2ffadb43 retrieve chat messages from db 2022-09-30 17:28:35 +01:00
Tiago vasconcelos 57bb9665c6 chat messages stored in db 2022-09-30 10:16:34 +01:00
Black Coffee a1fbd10561 Drop satoshit quote font size 2022-09-30 09:48:04 +01:00
Black Coffee 454ae1bf95 Split text for gerty 2022-09-30 09:37:28 +01:00
Black Coffee d2b4d6c837 bug squash 2022-09-30 09:30:08 +01:00
Black Coffee 61201a6573 Disable debug 2022-09-30 09:17:20 +01:00
ArcandGitHub e084495814 Merge pull request #1018 from blackcoffeexbt/gerty
Gerty
2022-09-30 09:03:36 +01:00
Black Coffee 659f95ccd3 enable debug 2022-09-30 09:00:48 +01:00
Black Coffee ff198d4728 Disable debug 2022-09-29 22:01:28 +01:00
Black Coffee 2135fd8656 bug fix 2022-09-29 21:38:16 +01:00
Black Coffee e38c0d2ff8 Font size to 80 2022-09-29 21:37:12 +01:00
Black Coffee c73bfcb1ea Font size tweaks 2022-09-29 19:28:50 +01:00
Black Coffee 6f21c0c34b Font size tweaks 2022-09-29 19:27:54 +01:00
Black Coffee 5cddae5ba9 Move title over stat 2022-09-29 19:25:02 +01:00
Black Coffee f99eae6b5a font size to 60 2022-09-29 18:02:52 +01:00
Black Coffee a2b61558d3 Change p from query var to route arg 2022-09-29 17:53:53 +01:00
Black Coffee f8ccb97d60 Added tx in mempool stat 2022-09-29 17:38:51 +01:00
Black Coffee 9973e9dbbb Added time until next diff adjustment 2022-09-29 17:33:44 +01:00
Black Coffee 90ed8c0e0a Added Blocks remaining until next difficulty adjustment 2022-09-29 17:27:14 +01:00
Black Coffee c77a9d1747 Add next difficulty adjustment data api output 2022-09-29 17:23:27 +01:00
Black Coffee df700d80e4 Added start of onchain stats function and getting epocch progress stat 2022-09-29 17:10:26 +01:00
Black Coffee 33b8b94f22 Added exchange price to api 2022-09-29 16:54:37 +01:00
Black Coffee b9f8a8b4b8 Added Pieter Wuille facts 2022-09-29 16:44:56 +01:00
Black Coffee 84b48eac60 Added astoshi quotes api endpoint 2022-09-29 16:34:47 +01:00
Black Coffee 67803cfdd3 Got placeholder responses in place for api requests 2022-09-29 16:21:02 +01:00
Black Coffee b5157ad8dc Work on api 2022-09-29 16:06:45 +01:00
Black Coffee 55de179730 Added refresh time to gerty settings 2022-09-29 15:08:01 +01:00
Black Coffee c0edd15edb new icon for mining 2022-09-29 15:00:21 +01:00
Black Coffee 43f4228e19 Bug fix on toggle all 2022-09-29 12:59:52 +01:00
Black Coffee 99762dce94 Added gerty toggle alls 2022-09-29 12:38:18 +01:00
Black Coffee 45dcd7f21f Now saving gerty screen display preferences as JSON in a single db field i 🙂 2022-09-29 12:13:29 +01:00
Tiago vasconcelos 8ab9590594 format 2022-09-28 17:03:09 +01:00
Tiago vasconcelos b9371805e2 websockets working localstorage for review 2022-09-28 17:02:15 +01:00
Black Coffee 2d18153b1d Added front end and work on db migrations and saving 2022-09-28 15:05:37 +01:00
Tiago vasconcelos 62e35ec006 Merge branch 'main' into diagon-alley 2022-09-28 11:14:24 +01:00
Tiago vasconcelos e0b93fbd49 chat and WS initial commit 2022-09-28 11:12:34 +01:00
Tiago vasconcelos 4aa17930fd chat page 2022-09-28 11:12:10 +01:00
Tiago vasconcelos aadb567712 WIP added keys to merchant 2022-09-28 11:11:52 +01:00
ben f3230fc59e Added onchain stat exampes 2022-09-26 23:48:38 +01:00
ben dc5ae91bb2 Frontend page kinda wokrking
Websockets need to be added
2022-09-26 23:06:41 +01:00
ben f747091257 Fixed json 2022-09-26 21:13:40 +01:00
ben d0a76cf665 Typo 2022-09-26 19:32:45 +01:00
ben c6cc6d34e1 Added some error checking 2022-09-26 19:30:40 +01:00
ben 938024fea2 json loading 2022-09-26 18:06:09 +01:00
ben 08ce55e29e Added update 2022-09-26 16:16:41 +01:00
ben 69c7cdb49f Adding frontend page 2022-09-26 10:30:48 +01:00
ben f4439974e4 Merge branch 'main' into gerty 2022-09-23 14:44:10 +01:00
ben 8d1f59cf46 Basics working 2022-09-23 14:38:55 +01:00
Tiago vasconcelos e80daf2795 rename stores to stalls 2022-09-23 10:25:57 +01:00
Tiago vasconcelos 131d9f689c Merge branch 'main' into diagon-alley 2022-09-23 09:49:05 +01:00
ben d7c12362ac Merge branch 'main' into diagon-alley 2022-09-22 10:51:36 +01:00
Tiago vasconcelos 384cc8305d Merge remote-tracking branch 'lnbits/diagon-alley' into diagon-alley 2022-09-21 18:18:18 +01:00
ben 71d643260c Merge branch 'main' into diagon-alley 2022-09-21 16:25:52 +01:00
Tiago vasconcelos c23756f546 Merge branch 'main' into diagon-alley 2022-09-20 10:20:05 +01:00
Tiago vasconcelos ee47575431 format 2022-09-15 15:52:08 +01:00
Tiago vasconcelos ad98cb0d45 marketplace html skeleton 2022-09-15 15:51:12 +01:00
mrbitcoiner 03beeac049 Fixing string subtraction because sparko returns the msat amounts with suffix 'msat' 2022-09-14 12:59:17 +00:00
Tiago vasconcelos 6a5c0bd8ee list marketplaces with stalls in index.html 2022-09-13 12:52:26 +01:00
Tiago vasconcelos 807eb59192 Merge branch 'main' into diagon-alley 2022-09-13 09:57:50 +01:00
Tiago vasconcelos 418cc7bf86 make format 2022-09-12 17:01:41 +01:00
Tiago vasconcelos f429037d63 create marketplace with stalls 2022-09-12 16:58:56 +01:00
Tiago vasconcelos 6ef7411999 clean up 2022-09-12 10:21:17 +01:00
Tiago vasconcelos 108e2f579d ui cosmetic on cards 2022-09-12 10:20:58 +01:00
Tiago vasconcelos 51e30c1517 Merge remote-tracking branch 'lnbits/main' into diagon-alley 2022-09-12 08:56:02 +01:00
Tiago vasconcelos c9884e512b stock values update 2022-08-20 08:18:28 +01:00
Tiago vasconcelos 7c8e3de261 update product stock tests 2022-08-19 13:24:18 +01:00
Tiago vasconcelos a5c6b1135c order paying works 2022-08-19 11:19:45 +01:00
Tiago vasconcelos e76c23b096 remove nostradmin from this branch 2022-08-18 16:56:10 +01:00
Tiago vasconcelos 627914abda Merge branch 'main' into diagon-alley 2022-08-18 16:50:58 +01:00
Tiago vasconcelos 2e9b8c1360 make format 2022-08-18 16:20:15 +01:00
Tiago vasconcelos d6fe562067 have orders show on user page and invoice init 2022-08-18 16:18:07 +01:00
Tiago vasconcelos 4cd86d0daf create order 2022-08-18 10:49:04 +01:00
Tiago vasconcelos 58b046254f make format diagonalley 2022-08-16 12:19:31 +01:00
Tiago vasconcelos 39bae68b1c Merge remote-tracking branch 'lnbits/diagon-alley' into diagon-alley 2022-08-16 12:11:43 +01:00
Tiago vasconcelos 777b9bef1b place order flow WIP 2022-08-16 12:11:30 +01:00
Tiago vasconcelos 35cf441d91 remove privatekey from stall 2022-08-16 12:11:30 +01:00
Tiago vasconcelos 25d0309ff3 ui and checkout 2022-08-16 12:11:30 +01:00
Tiago vasconcelos 8032566f44 place order flow WIP 2022-08-16 12:08:27 +01:00
Tiago vasconcelos d9242ddd5b remove privatekey from stall 2022-08-16 12:07:56 +01:00
Tiago vasconcelos 0a54fe6db8 ui and checkout 2022-08-16 12:07:23 +01:00
Tiago vasconcelos c4509d726a UI and checkout 2022-08-16 12:07:04 +01:00
Tiago vasconcelos af906740ca Merge branch 'main' into diagon-alley 2022-08-16 09:02:06 +01:00
Tiago vasconcelos 3f58676849 rename shop to market and create market 2022-07-27 16:46:27 +01:00
Tiago vasconcelos 6d5e9aba39 initial launch market 2022-07-27 15:28:58 +01:00
Tiago vasconcelos 0f238286a3 add order_details table 2022-07-25 10:11:03 +01:00
Tiago vasconcelos 245e2a353e add cart menu and filter products 2022-07-20 17:00:24 +01:00
Tiago vasconcelos e56b26146a toolbar 2022-07-20 11:47:03 +01:00
Tiago vasconcelos b151341148 add a placeholder img for no photo product 2022-07-20 11:46:48 +01:00
Tiago vasconcelos 6965459f5d add StaticFiles 2022-07-20 11:46:20 +01:00
Tiago vasconcelos f7f442c9d7 add stall page 2022-07-19 10:13:06 +01:00
Tiago vasconcelos b79fb373a8 clean prints 2022-07-19 10:12:54 +01:00
Tiago vasconcelos b0db9ecf78 Merge remote-tracking branch 'lnbits/diagon-alley' into diagon-alley 2022-07-18 09:12:10 +01:00
Tiago vasconcelos df780124d7 rename stall to store 2022-07-18 09:11:19 +01:00
Tiago vasconcelos e8e161098f create and edit products 2022-07-18 09:11:19 +01:00
Tiago vasconcelos 83fe977a85 create product 2022-07-18 09:11:19 +01:00
Tiago vasconcelos 5618de2cd3 fix create edit delete permissions 2022-07-18 09:11:19 +01:00
Tiago vasconcelos 00e6aced53 black'ed 2022-07-18 09:11:19 +01:00
Tiago vasconcelos 7eb0d13590 stalls/stores create update with human readable zones 2022-07-18 09:11:19 +01:00
Tiago vasconcelos 820852e08b create and display zones 2022-07-18 09:11:19 +01:00
Tiago vasconcelos d3038fe49b clean up optional nostr,timestamp 2022-07-18 09:11:19 +01:00
Tiago vasconcelos e1b6288860 add timespamp on orders, nullable nostr,user keyword 2022-07-18 09:11:19 +01:00
Tiago vasconcelos 99d285335e remove nostrAdmin check 2022-07-18 09:11:19 +01:00
Ben ArcandTiago vasconcelos 08f124b6f9 Fusion44s relay_manager added to nostradmin 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 734a72d7a0 blah 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos ec8c504f28 wdvds 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos dbc47332ee Working, looking good 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 4e65c39b36 typo 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 3f356f9de8 Started planning websocket daemon 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 2c435ee9d9 models decided 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 30f9c3d290 Chnaged nostr to nostradmin 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos d60d5c928c extension loading 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 61cf79de69 Added nostr ext 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 671ac08a04 small fix 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 40fd894cce Zones are saving/updating 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 1988a9d5d7 Added error messages 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos 80a5d26dce Message ui working 2022-07-18 09:11:19 +01:00
Tiago vasconcelos a55dd18528 UI fires up 2022-07-18 09:11:19 +01:00
Tiago vasconcelos c41f6033be orders 2022-07-18 09:11:19 +01:00
Tiago vasconcelos 8b37302164 stalls 2022-07-18 09:11:19 +01:00
Tiago vasconcelos ebaf0c990f zones 2022-07-18 09:11:19 +01:00
Tiago vasconcelos 5cc39930e5 products 2022-07-18 09:11:19 +01:00
benarcandTiago vasconcelos b1bfef8784 initial 2022-07-18 09:11:19 +01:00
Tiago vasconcelos f9bb1024a7 rename stall to store 2022-07-15 10:43:06 +01:00
Tiago vasconcelos 88c45a9c45 create and edit products 2022-07-15 10:22:13 +01:00
Tiago vasconcelos c19618eddc create product 2022-07-12 17:22:03 +01:00
Tiago vasconcelos b11597b0de fix create edit delete permissions 2022-07-12 15:29:46 +01:00
Tiago vasconcelos 2fed92e69a black'ed 2022-07-12 15:25:25 +01:00
Tiago vasconcelos 4fde09e0cc stalls/stores create update with human readable zones 2022-07-12 15:07:56 +01:00
Tiago vasconcelos fd280aee1a create and display zones 2022-07-12 11:02:27 +01:00
Tiago vasconcelos 4deb116076 clean up optional nostr,timestamp 2022-07-12 11:02:12 +01:00
Tiago vasconcelos 89181eb74e add timespamp on orders, nullable nostr,user keyword 2022-07-12 11:01:15 +01:00
Tiago vasconcelos f72ec97b37 remove nostrAdmin check 2022-07-11 21:38:05 +01:00
ben b7fa4b8b05 Merge remote-tracking branch 'origin/main' into diagon-alley 2022-06-23 09:33:47 +01:00
ben 7037793369 Merge branch 'main' into diagon-alley 2022-06-21 10:14:17 +01:00
Ben Arc 14b95d3ba0 Fusion44s relay_manager added to nostradmin 2022-02-17 23:17:44 +00:00
Ben Arc 769b966806 Merge branch 'main' into diagon-alley 2022-02-17 21:58:12 +00:00
Ben Arc 39bec9f631 Merge branch 'main' into diagon-alley 2022-02-17 20:32:48 +00:00
benarc 17fa3399ff blah 2022-02-12 12:40:12 +00:00
benarc bc68b924e5 Merge branch 'main' into diagon-alley 2022-02-11 11:18:24 +00:00
benarc 7e009516e4 Merge branch 'main' into diagon-alley 2022-02-11 11:14:47 +00:00
benarc b565ec5d6c wdvds 2022-02-10 13:54:36 +00:00
benarc 62f5376c11 Merge branch 'nostr' into diagon-alley 2022-02-10 11:42:06 +00:00
benarc fd4aae3b30 Merge branch 'main' into nostr 2022-02-10 10:10:02 +00:00
benarc 6917813e2a Working, looking good 2022-02-10 09:58:50 +00:00
benarc 0a5495f185 typo 2022-02-08 14:02:50 +00:00
benarc 21c316293b Started planning websocket daemon 2022-02-08 13:52:19 +00:00
benarc 2083ecbc27 models decided 2022-02-08 13:13:36 +00:00
benarc 29bd8d9ce9 Chnaged nostr to nostradmin 2022-02-08 10:35:20 +00:00
benarc 89c3c1b272 Merge branch 'main' into nostr 2022-02-07 22:03:34 +00:00
benarc da1025a625 extension loading 2022-02-07 20:01:01 +00:00
benarc f38492a920 Added nostr ext 2022-02-07 13:22:43 +00:00
benarc 80b73e7aba small fix 2022-02-06 22:40:51 +00:00
benarc 44f7fae2d4 Zones are saving/updating 2022-02-04 13:05:48 +00:00
benarc ecaea51a1c Added error messages 2022-02-03 22:30:53 +00:00
benarc 301135d006 Message ui working 2022-02-03 21:19:24 +00:00
benarc 3bf1d6c6ae Merge branch 'main' into diagon-alley 2022-02-03 13:57:04 +00:00
ArcandGitHub e26e342dd7 Merge pull request #501 from talvasconcelos/diagon-alley
UI is working
2022-02-03 13:55:46 +00:00
Tiago vasconcelos 5048080ff6 UI fires up 2022-01-28 16:22:54 +00:00
Tiago vasconcelos cad896f909 orders 2022-01-28 15:11:31 +00:00
Tiago vasconcelos d6d33c6a22 stalls 2022-01-27 16:18:12 +00:00
Tiago vasconcelos 8db457bd42 zones 2022-01-27 15:26:55 +00:00
Tiago vasconcelos 903e0d5b0b products 2022-01-27 14:58:58 +00:00
benarc 6b2f3d05d5 initial 2022-01-27 12:24:38 +00:00
459 changed files with 21764 additions and 5765 deletions
+19 -7
View File
@@ -1,7 +1,9 @@
#For more information on .env files, their content and format: https://pypi.org/project/python-dotenv/
HOST=127.0.0.1
PORT=5000
# uvicorn variable, allow https behind a proxy
# uvicorn variable, uncomment to allow https behind a proxy
# FORWARDED_ALLOW_IPS="*"
DEBUG=false
@@ -11,11 +13,12 @@ LNBITS_ALLOWED_USERS=""
LNBITS_ADMIN_USERS=""
# Extensions only admin can access
LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
LNBITS_ADMIN_UI=false
# Restricts access, User IDs seperated by comma
LNBITS_ALLOWED_USERS=""
# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
# Warning: Enabling this will make LNbits ignore this configuration file. Your settings will
# be stored in your database and you will be able to change them only through the Admin UI.
# Disable this to make LNbits use this config file again.
LNBITS_ADMIN_UI=false
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
@@ -53,8 +56,9 @@ LNBITS_SITE_DESCRIPTION="Some description about your service, will display if ti
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, freedom, mint, autumn, monochrome, salvador"
# LNBITS_CUSTOM_LOGO="https://lnbits.com/assets/images/logo/logo.svg"
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet, LnTipsWallet
# LndRestWallet, CoreLightningWallet, LNbitsWallet, SparkWallet, FakeWallet, EclairWallet
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, ClicheWallet,
# LndWallet, LndRestWallet, CoreLightningWallet, EclairWallet,
# LnTipsWallet, LNbitsWallet, SparkWallet, FakeWallet,
LNBITS_BACKEND_WALLET_CLASS=VoidWallet
# VoidWallet is just a fallback that works without any actual Lightning capabilities,
# just so you can see the UI before dealing with this file.
@@ -75,6 +79,14 @@ CORELIGHTNING_RPC="/home/bob/.lightning/bitcoin/lightning-rpc"
LNBITS_ENDPOINT=https://legend.lnbits.com
LNBITS_KEY=LNBITS_ADMIN_KEY
# LndWallet
LND_GRPC_ENDPOINT=127.0.0.1
LND_GRPC_PORT=10009
LND_GRPC_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_GRPC_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
# To use an AES-encrypted macaroon, set
# LND_GRPC_MACAROON="eNcRyPtEdMaCaRoOn"
# LndRestWallet
LND_REST_ENDPOINT=https://127.0.0.1:8080/
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
+4 -2
View File
@@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.2.1"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -24,7 +24,9 @@ jobs:
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Install packages
run: poetry install
run: |
poetry config virtualenvs.create false
poetry install
- name: Check black
run: make checkblack
- name: Check isort
+2 -1
View File
@@ -23,7 +23,7 @@ jobs:
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.2.1"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -36,6 +36,7 @@ jobs:
poetry-version: ${{ matrix.poetry-version }}
- name: Install dependencies
run: |
poetry config virtualenvs.create false
poetry install
sudo apt install unzip
- name: Run migrations
+2 -1
View File
@@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.2.1"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -21,6 +21,7 @@ jobs:
poetry-version: ${{ matrix.poetry-version }}
- name: Install dependencies
run: |
poetry config virtualenvs.create false
poetry install
- name: Run tests
run: poetry run mypy
+1
View File
@@ -7,6 +7,7 @@ on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+.[0-9]+"
- "[0-9]+.[0-9]+.[0-9]+-*"
jobs:
+6 -3
View File
@@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.2.1"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -29,6 +29,7 @@ jobs:
sudo chmod -R a+rwx .
- name: Install dependencies
run: |
poetry config virtualenvs.create false
poetry install
- name: Run tests
env:
@@ -51,7 +52,7 @@ jobs:
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.2.1"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -72,6 +73,7 @@ jobs:
sudo chmod -R a+rwx .
- name: Install dependencies
run: |
poetry config virtualenvs.create false
poetry install
- name: Run tests
env:
@@ -95,7 +97,7 @@ jobs:
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.2.1"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -116,6 +118,7 @@ jobs:
sudo chmod -R a+rwx .
- name: Install dependencies
run: |
poetry config virtualenvs.create false
poetry install
- name: Run tests
env:
+5 -3
View File
@@ -8,7 +8,7 @@ jobs:
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.2.1"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -31,7 +31,7 @@ jobs:
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.2.1"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -44,6 +44,7 @@ jobs:
poetry-version: ${{ matrix.poetry-version }}
- name: Install dependencies
run: |
poetry config virtualenvs.create false
poetry install
- name: Run tests
run: make test
@@ -67,7 +68,7 @@ jobs:
strategy:
matrix:
python-version: ["3.9"]
poetry-version: ["1.2.1"]
poetry-version: ["1.3.1"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
@@ -80,6 +81,7 @@ jobs:
poetry-version: ${{ matrix.poetry-version }}
- name: Install dependencies
run: |
poetry config virtualenvs.create false
poetry install
- name: Run tests
env:
+1 -1
View File
@@ -7,6 +7,6 @@
"singleQuote": true,
"trailingComma": "none",
"useTabs": false,
"jsxBracketSameLine": false,
"bracketSameLine": false,
"bracketSpacing": false
}
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.9-slim
FROM python:3.10-slim
RUN apt-get clean
RUN apt-get update
+5 -2
View File
@@ -6,9 +6,12 @@ format: prettier isort black
check: mypy checkprettier checkisort checkblack
prettier: $(shell find lnbits -name "*.js" -name ".html")
prettier: $(shell find lnbits -name "*.js" -o -name ".html")
./node_modules/.bin/prettier --write lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js lnbits/extensions/*/static/components/*/*.js lnbits/extensions/*/static/components/*/*.html
pyright:
./node_modules/.bin/pyright
black:
poetry run black .
@@ -18,7 +21,7 @@ mypy:
isort:
poetry run isort .
checkprettier: $(shell find lnbits -name "*.js" -name ".html")
checkprettier: $(shell find lnbits -name "*.js" -o -name ".html")
./node_modules/.bin/prettier --check lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js lnbits/extensions/*/static/components/*/*.js lnbits/extensions/*/static/components/*/*.html
checkblack:
+12 -13
View File
@@ -7,29 +7,29 @@ LNbits
![Lightning network wallet](https://i.imgur.com/EHvK6Lq.png)
# LNbits v0.9 BETA, free and open-source lightning-network wallet/accounts system
# LNbits v0.9 BETA, free and open-source Lightning wallet accounts system
(Join us on [https://t.me/lnbits](https://t.me/lnbits))
(LNbits is beta, for responsible disclosure of any concerns please contact lnbits@pm.me)
LNbits is beta, for responsible disclosure of any concerns please contact lnbits@pm.me
Use [legend.lnbits.com](https://legend.lnbits.com), or run your own LNbits server!
LNbits is a very simple Python server that sits on top of any funding source, and can be used as:
LNbits is a Python server that sits on top of any funding source. It can be used as:
* Accounts system to mitigate the risk of exposing applications to your full balance, via unique API keys for each wallet
* Extendable platform for exploring lightning-network functionality via LNbits extension framework
* Accounts system to mitigate the risk of exposing applications to your full balance via unique API keys for each wallet
* Extendable platform for exploring Lightning network functionality via the LNbits extension framework
* Part of a development stack via LNbits API
* Fallback wallet for the LNURL scheme
* Instant wallet for LN demonstrations
LNbits can run on top of any lightning-network funding source, currently there is support for LND, c-lightning, Spark, LNpay, OpenNode, lntxbot, with more being added regularly.
LNbits can run on top of any Lightning funding source. It supports LND, CLN, Eclair, Spark, LNpay, OpenNode, lntxbot, LightningTipBot, and with more being added regularly.
See [legend.lnbits.org](https://legend.lnbits.org) for more detailed documentation.
See [docs.lnbits.org](https://docs.lnbits.org) for more detailed documentation.
Checkout the LNbits [YouTube](https://www.youtube.com/playlist?list=PLPj3KCksGbSYG0ciIQUWJru1dWstPHshe) video series.
LNbits is inspired by all the great work of [opennode.com](https://www.opennode.com/), and in particular [lnpay.co](https://lnpay.co/). Both work as excellent funding sources for LNbits.
LNbits is inspired by all the great work of [opennode.com](https://www.opennode.com/), and in particular [lnpay.co](https://lnpay.co/). Both work as funding sources for LNbits.
## Running LNbits
@@ -58,19 +58,18 @@ Example use would be an ATM, which utilises LNURL, if the user scans the QR with
![lnurl ATM](https://i.imgur.com/Gi6bn3L.jpg)
## LNbits as an insta-wallet
## LNbits as an instant wallet
Wallets can be easily generated and given out to people at events (one click multi-wallet generation to be added soon).
"Go to this website", has a lot less friction than "Download this app".
Wallets can be easily generated and given out to people at events. "Go to this website", has a lot less friction than "Download this app".
![lnurl ATM](https://i.imgur.com/xFWDnwy.png)
## Tip us
If you like this project and might even use or extend it, why not [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)!
If you like this project [send some tip love](https://legend.lnbits.com/paywall/GAqKguK5S8f6w5VNjS9DfK)!
[docs]: https://legend.lnbits.org/
[docs]: https://docs.lnbits.org/
[docs-badge]: https://img.shields.io/badge/docs-lnbits.org-673ab7.svg
[github-mypy]: https://github.com/lnbits/lnbits/actions?query=workflow%3Amypy
[github-mypy-badge]: https://github.com/lnbits/lnbits/workflows/mypy/badge.svg
+1 -1
View File
@@ -1 +1 @@
legend.lnbits.org
docs.lnbits.org
+1 -1
View File
@@ -3,7 +3,7 @@ remote_theme: pmarsceill/just-the-docs
color_scheme: dark
logo: "/logos/lnbits-full--inverse.png"
search_enabled: true
url: https://legend.lnbits.org
url: https://docs.lnbits.org
aux_links:
"LNbits on GitHub":
- "//github.com/lnbits/lnbits"
+1 -1
View File
@@ -9,4 +9,4 @@ nav_order: 3
API reference
=============
[Swagger Docs](https://legend.lnbits.org/devs/swagger.html)
[Swagger Docs](https://docs.lnbits.org/devs/swagger.html)
+4 -3
View File
@@ -28,7 +28,9 @@ Going over the example extension's structure:
Adding new dependencies
-----------------------
If for some reason your extensions needs a new python package to work, you can add a new package using `venv`, or `poerty`:
DO NOT ADD NEW DEPENDENCIES. Try to use the dependencies that are availabe in `pyproject.toml`. Getting the LNbits project to accept a new dependency is time consuming and uncertain, and may result in your extension NOT being made available to others.
If for some reason your extensions must have a new python package to work, and its nees are not met in `pyproject.toml`, you can add a new package using `venv`, or `poerty`:
```sh
$ poetry add <package>
@@ -37,8 +39,7 @@ $ ./venv/bin/pip install <package>
```
**But we need an extra step to make sure LNbits doesn't break in production.**
Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry`.
`nix` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`.
Dependencies need to be added to `pyproject.toml` and `requirements.txt`, then tested by running on `venv` and `poetry` compatability can be tested with `nix build .#checks.x86_64-linux.vmTest`.
SQLite to PostgreSQL migration
+30
View File
@@ -40,3 +40,33 @@ Allowed Users
=============
enviroment variable: LNBITS_ALLOWED_USERS, comma-seperated list of user ids
By defining this users, LNbits will no longer be useable by the public, only defined users and admins can then access the LNbits frontend.
How to activate
=============
```
$ sudo systemctl stop lnbits.service
$ cd ~/lnbits-legend
$ sudo nano .env
```
-> set: `LNBITS_ADMIN_UI=true`
Now start LNbits once in the terminal window
```
$ poetry run lnbits
```
It will now show you the Super User Account:
`SUCCESS | ✔️ Access super user account at: https://127.0.0.1:5000/wallet?usr=5711d7..`
The `/wallet?usr=..` is your super user account. You just have to append it to your normal LNbits web domain.
After that you will find the __`Admin` / `Manage Server`__ between `Wallets` and `Extensions`
Here you can design the interface, it has TOPUP to fill wallets and you can restrict access rights to extensions only for admins or generally deactivated for everyone. You can make users admins or set up Allowed Users if you want to restrict access. And of course the classic settings of the .env file, e.g. to change the funding source wallet or set a charge fee.
Do not forget
```
sudo systemctl start lnbits.service
```
A little hint, if you set `RESET TO DEFAULTS`, then a new Super User Account will also be created. The old one is then no longer valid.
+268
View File
@@ -0,0 +1,268 @@
---
layout: default
title: FAQ
nav_order: 5
---
# FAQ - Frequently Asked Questions
## Install options
<ul><p>LNbits is not a node management software but a ⚡️LN only accounting system on top of a funding source.</p>
<details><summary>Funding my LNbits wallet from my node it doesn't work.</summary>
<p>If you want to send sats from the same node that is the funding source of your LNbits, you will need to edit the lnd.conf file. The parameters to be included are:</p>
```
allow-circular-route=1
allow-self-payment=1
```
</details>
<details><summary>Funding source only available via tor (e.g. Umbrel)</summary>
<p>If you want your setup to stay behind tor then only apps, pos and wallets that have tor activated can communicate with your wallets. Most likely you will have trouble when people try to redeem your voucher through onion or when importing your lnbits wallets into a wallet-app that doesnt support tor. If you plan to let LNbits wallets interact with plain internet shops and services you should consider <a href="https://github.com/TrezorHannes/Dual-LND-Hybrid-VPS">setting up hybrid mode for your node</a>.</p>
</details>
<details><summary>Funding source is in a cloud</summary>
<p>This means that you might not have access to some files which would allow certain administrative functions. E.g. on <a href="https://voltage.cloud/">Voltage</a> lnd.conf can not be edited. Payments from your node to LNbits wallets can therefore not be configurated in this case atm so you will need to take an extra wallet to send from funding source->wallet x->LNbits wallet (only) for the initial funding of the wallet.</p>
</details>
<details><summary>LNbits via clearnet domain</summary>
<p><a href="https://github.com/TrezorHannes/Dual-LND-Hybrid-VPS">Step by step guide how to convert your Tor only node</a> into a clearnet node to make apps like LNbits accessible via https.</p>
</details>
<details><summary>Which funding sources can I use for LNbits?</summary>
<p>There are several ways to run a LNbits instance funded from different sources. It is importan to choose a source that has a good liquidity and good peers connected. If you use LNbits for public services your users´ payments can then flow happily in both directions. If you would like to fund your LNbits wallet via btc please see section Troubleshooting.</p>
<p>The <a href="http://docs.lnbits.org/guide/wallets.html">LNbits manual</a> shows you which sources can be used and how to configure each: CLN, LND, LNPay, Cliche, OpenNode as well as bots.</p>
</details>
<!--Later to be added
<details><summary>Advanced setup options</summary>
<p>more text coming soon...</p>
</details>
-->
<details><summary>Can I prevent others from generating wallets on my node?</summary>
<p>When you run your LNbits in clearnet basically everyone can generate a wallet on it. Since the funds of your node are bound to these wallets you might want to prevent that. There are two ways to do so:</p>
<ul>
<li>Configure allowed users & extensions <a href="https://github.com/lnbits/lnbits/blob/main/.env.example">in the .env file</a></li>
<li>Configure allowed users & extensions <a href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/usermanager">via the Usermanager-Extension</a>. You can find <a href="http://docs.lnbits.org/guide/admin_ui.html">more info about the superuser and the admin extension here</a></li>
</ul>
<p>Please note that all entries in the .env file will not be the taken into account once you activated the admin extension.</p>
</details>
</ul>
## Troubleshooting
<ul><details><summary>Message "https error" or network error" when scanning a LNbits QR</summary>
<p>Bad news, this is a routing error that might have quite a lot of reasons. Let´s try a few of the most possible problems and their solutions. First choose your setup</p>
<ul>
<li>
<details><summary>LNbits is running via Tor only, you can't open it on a public domain like lnbits.yourdomain.com</summary>
<ul>
<li>Given that you want your setup to stay like this open your LNbits wallet using the .onion URI and create it again. In this way the QR is generated to be accessible via this .onion URI so via tor only. Do not generate that QR from a .local URI, because it will not be reachable via internet - only from within your home-LAN.</li>
<li>Open your LN wallet app that you used to scan that QR and this time by using tor (see wallet app settings).
If the app doesn't offer tor, you can use Orbot (Android) instead. See section Installation->Clearnet for detailed instructions.</li>
</ul>
</details>
</li>
<li>
<details><summary>LNbits is running via Tor only, you want to offer public LN services via https</summary>
<ul>
<li>For this we need to partially open LNbits to a clearnet (domain/IP) through a https SSL certificate. Follow the instructions from <a href="https://docs.lnbits.org/guide/installation.html#reverse-proxy-with-automatic-https-using-caddy">this LNbits caddy installation instruction</a>.
You need to have a domain and to be able to configure a CNAME for your DNS record as well as generate a subdomain dedicated to your LNbits instance like eg. lnbits.yourdomain.com.
You also need access to your internet router to open the https port (usually 443) and forward it your LNbits IP within your LAN (usually 80). The ports might depend on your node implementation if those ports do not work please ask for them in a help group of your node supplier.</li>
<li>You can also follow the Apache installation option, explained in the <a href="https://docs.lnbits.org/guide/installation.html#running-behind-an-apache2-reverse-proxy-over-https">LNbits installation manual</a>.</li>
<li>If you run LNbits from a bundle node (Umbrel, myNode, Embassy, Raspiblitz etc), you can follow <a href="https://github.com/TrezorHannes/vps-lnbits">this extensive guide</a> with many options to switch your Tor only LNbits into a clearnet LNbits. For Citadel there is a HTTPS Option in your manual to activate https for LNbits in the newest version.</li>
</ul>
</details>
</li>
</ul>
</details>
<details><summary>Wallet-URL deleted, are my funds safu ?</summary>
<ul>
<li>
<details><summary>Wallet on demo server legend.lnbits</summary>
<p>Always save a copy of your wallet-URL, Export2phone-QR or LNDhub for your own wallets in a safe place. LNbits CANNOT help you to recover them when lost.</p>
</details>
</li>
<li>
<details><summary>Wallet on your own funding source/node</summary>
<p>Always save a copy of your wallet-URL, Export2phone-QR or LNDhub for your own wallets in a safe place.
You can find all LNbits users and wallet-IDs in your LNbits user manager extension or in your sqlite database.
To edit or read the LNbits database, go to the LNbits /data folder and look for the file called sqlite.db.
You can open and edit it with excel or with a dedicated SQL-Editor like <a href="https://sqlitebrowser.org/">SQLite browser</a>.</p>
</details>
</li>
</ul>
</details>
<details><summary>Configure a comment that people see when paying to my LNURLp QR</summary>
<p>When you create a LNURL-p, by default the comment box is not filled. That means comments are not allowed to be attached to payments.<p>
<p>In order to allow comments, add the characters lenght of the box, from 1 to 250. Once you put a number there,
the comment box will be displayed in the payment process. You can also edit a LNURL-p already created and add that number.</p>
![lnbits-lnurl-comment.png](https://i.postimg.cc/HkJQ9xKr/lnbits-lnurl-comment.png)
</details>
<details><summary>Can I deposit onchain BTC to LNbits ?</summary>
<p>There are multiple ways to exchange sats from onchain btc to LN btc (resp. to LNbits).</p>
<ul>
<li>
<details><summary>A - Via an external swap service</summary>
<p>If the user do not have full acceess of your LNbits, is just an external user, can use swap services like <a href="https://boltz.exchange/">Boltz</a>, <a href="https://fixedfloat.com/">FixedFloat</a>, <a href="https://swap.diamondhands.technology/">DiamondHands</a> or <a href="https://zigzag.io/">ZigZag</a>.</p>
<p>This is useful if you provide only LNURL/LN invoices from your LNbits instance, but a payer only has onchain sats so
they will have to the swap those sats first on their side.</p>
<p>The procedure is simple: user sends onchain btc to the swap service and provides the LNURL / LN invoice from LNbits as destination of the swap.</p>
</details>
</li>
<li>
<details><summary>B - Using the Onchain LNbits extension</summary>
<p>Keep in mind that this is a separate wallet, not the LN btc one that is represented by LNbits as "your wallet" upon your LN funding source.
This onchain wallet can be used also to swap LN btc to (e.g. your hardwarewallet) by using the LNbits Boltz or Deezy extension.
If you run a webshop that is linked to your LNbits for LN payments, it is very handy to regularily drain all the sats from LN into onchain.
This leads to more space in your LN channels to be able to receive new fresh sats.</p>
<p>Procedure:</p>
<ul>
<li>Use Electrum or Sparrow wallet to create a new onchain wallet and save the backup seed in a safe place</li>
<li>Go to wallet information and copy the xpub</li>
<li>Go to LNbits - Onchain extension and create a new watch-only wallet with that xpub</li>
<li>Go to LNbits - Tipjar extension and create a new Tipjar. Select also the onchain option besides the LN wallet.</li>
<li>Optional - Go to LNbits - SatsPay extension and create a new charge for onchain btc.
You can choose between onchain and LN or both. It will then create an invoice that can be shared.</li>
<li>Optional - If you use your LNbits linked to a Wordpress + Woocommerce page, once you create/link a watch-only wallet to your LN btc shop wallet,
the customer will have both options to pay on the same screen.</li>
</ul>
</details>
</li>
</ul>
</details>
<details><summary>Where can I see payment details?</summary>
<p>When you receive a payment in LNbits, the transaction log will display only a resumed type of the transaction.
![lnbits-tx-log.png](https://i.postimg.cc/gk2FMFG9/lnbits-tx-log.png)
<p>In your transaction overview you will find a little green arrow for received and a red arrow for sended funds.<p>
<p>If you click on those arrows, a details popup shows attached messages as well as the sender´s name if given.</p>
</details>
<details><summary>Can I configure a name to the payments i make?</summary>
<p>In LNbits this is currently not possible to do - but to receive. This is only possible if the sender's LN wallet supports <a href="https://github.com/lnurl/luds">LUD-18</a> (nameDesc) like e.g. <a href="https://darthcoin.substack.com/p/obw-open-bitcoin-wallet">Open Bitcion Wallet - OBW</a> does. You will then see an alias/pseudonym in the details section of your LNbits transactions (click the arrows). Note that you can give any name there and it might not be related to the real sender´s name(!) if your receive such.</p>
![lnbits-tx-details.png](https://i.postimg.cc/yYnvyK4w/lnbits-tx-details.png)
</p>
</details>
<details><summary>How can I use a LNbits lndhub account in other wallet apps?</summary>
<p>Open your LNbits with the account / wallet you want to use, go to "manage extensions" and activate the LNDHUB extension.</p>
<p>Then open the LNDHUB extension, choose the wallet you want to use and scan the QR code you want to use: "admin" or "invoice only", depending on the security level you want for that wallet.</p>
<p>You can use <a href="https://zeusln.app">Zeus</a> or <a href="https://bluewallet.io">Bluewallet</a> as wallet apps for a lndhub account.</p>
<p>Keep in mind: if your LNbits instance is Tor only, you must use also theose apps behind Tor and open the LNbits page through your Tor .onion address.</p>
</details>
</ul>
</ul>
## Building hardware tools
<ul> <p>LNbits has all sorts of open APIs and tools to program and connect to a lot of different devices for a gazillion of use-cases. Let us know what you did with it ! Come to the <a href="https://t.me/makerbits">Makerbits Telegram Group</a> if you are interested in building or if you need help with a project - we got you!</p>
<details><summary>ATM - deposit and withdraw in your shop or at your meetup</summary>
<p>This is a do-it-yourself project consisting of a mini-computer (Raspberry Pi Zero), a coin acceptor, a display, a 3D printed case, and a Bitcoin Lightning wallet as a funding source. It exchanges fiat coins for valuable Bitcoin Lightning ⚡ Satoshis. The user can pick up the Satoshis via QR code (LNURL-withdraw) on a mobile phone wallet. It works based on BTCPayer server, LNTXBOT is not longer an option. You can get the components as individual parts and build the case yourself e.g. from <a href="https://www.Fulmo.org">Fulmo</a> who also made a <a href="https://blog.fulmo.org/the-lightningatm-pocket-edition/">guide</a> on it. The shop offers payments in Bitcoin and Lightning ⚡. The code can be found on <a href="https://github.com/21isenough/LightningATM">the ATM github project page></a>.</p>
</details>
<details><summary>POS Terminal - an offline terminal for merchants</summary>
<p>The LNpos is a self-sufficient point of sale terminal which allows offline onchain payments and an offline Lightning ATM for withdrawals. Free and open source software, free from intermediaries, with integrated battery, WLAN, DIY. You can get the 3D print as well as the whole kit from the LNbits shop from 👇 Ressources. It allows
<li>LNPoS Online interactive Lightning payments</li>
<li>LNURLPoS Offline Lightning Payments. Passive interaction, sharing a secret as evidence</li>
<li>OnChain For onchain payments. Generates an address and displays a link for verification</li>
<li>LNURLATM Offline Lightning Payouts. Generates LNURLw link to do withdrawals</li>
<p>
<img width="285" alt="Bildschirm­foto 2023-01-20 um 18 09 34" src="https://user-images.githubusercontent.com/63317640/213761202-4c4d8531-7184-4e53-8645-fe0f08ac7d17.png">
</p>
</p>
</details>
<details><summary>Hardware Wallet- build your own, stack harder</summary>
<p>The hardwarewallet is a very cheap solution for builders. The projects´ <a hrel="https://github.com/lnbits/hardware-wallet">code and installation instructions for the LNbits hardware wallet can be found on github</a></p>
<p>
<img width="546" alt="Bildschirm­foto 2023-01-20 um 18 08 37" src="https://user-images.githubusercontent.com/63317640/213760948-38fd77b0-9247-4505-9433-f5af1b223527.png">
</p>
</details>
<details><summary>Bitcoin Switch - turn things on with bitcoin</summary>
<p>Candy dispenser, vending machines (online), grabbing machines, jukeboxes, bandits and <a href="https://github.com/cryptoteun/awesome-lnbits">all sorts of other things have already been build with LNbits´ tools</a>. Further info see 👇 Ressources.</p>
<p>
<img width="549" alt="Bildschirm­foto 2023-01-20 um 18 11 55" src="https://user-images.githubusercontent.com/63317640/213761646-d25d4745-e50d-4389-98e5-f83237a8cf6b.png">
</p>
</details>
<details><summary>Vending machine (offline)</summary>
<p>This code works similar to the LNpos. Note that the <a href=" https://www.youtube.com/watch?v=Fg0UuuzsYXc&t=762s">setup-video for the vending machine</a> misses the new way of installing it via the new LNURLdevices extension. The <a href="https://github.com/arcbtc/LNURLVend">vending machine project code resides on github</a>.</p>
<p>
<img width="753" alt="Bildschirm­foto 2023-01-20 um 18 13 22" src="https://user-images.githubusercontent.com/63317640/213761946-5025a7b8-c6d4-40cf-a6d3-d298593e79f6.png">
</p>
</details>
<details><summary><b>Resources - Building hardware tools</b></summary>
<ul>
<li><a href="https://t.me/makerbits'">MakerBits</a> - Telegram support group</li>
<li><a href="https://ereignishorizont.xyz/">Instructions for LNpos, Switch, ATM, BTCticker</a> - guides in DE & EN</li>
<li><a href="https://shop.lnbits.com/">LNbits shop</a> - eadymade hardware gimmicks from the community</li>
<li><a href="https://github.com/cryptoteun/awesome-lnbits#hardware-projects-utilizing-lnbits">Collection of hardware projects using LNbits</a></li>
</ul>
</details>
</ul>
## Use cases of LNbits
<ul><details><summary>Merchant</summary>
<p>LNbits is a powerful solution for merchants, due to the easy setup with various extensions, that can be used for many scenarios.</p>
<p><a href="https://darthcoin.substack.com/p/lnbits-for-small-merchants">Here is an overview of the LNbits tools available for a small restaurant as well as a hotel</a></p>
</details>
<details><summary>Swapping ⚡️LN BTC to a BTC address</summary>
<p>LNbits has two swap extensions integrated: <a href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/boltz">Boltz</a> and <a href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/deezy">Deezy</a>.</p>
<p>For a merchant that uses LNbits to receive BTC payments through LN, this is very handy to move the received sats from LN channels into onchain wallets. It not only helps you HODLing but is also freeing up "space in your channels" so you are ready to receive more sats.</p>
<p>Boltz has an option to setup an automated swap triggered by a certain amount received.</p>
</details>
<details><summary>Voucher</summary>
<p>Printed voucher links or tippingcards</p>
<p>To generate voucher you will need LNbits to be available in clearnet. Please consider running your own LNbits instance for this.</p>
<p>LNURLw are strings that represent a faucet-link to a wallet. By scanning it, everyone will be able to withdraw sats from it. A LNURLw can be either a QR that leads to a static link or to one that responds with new invoices every time it is scanned (click "no assmilking"). You can create these QR by adding the LNURLw extension and generate the vouchertype you need.</p>
<ul>
<li>Voucher can as well be printed directly from LNbits. After you created it, click the "eye" next to the link. By pressing the printer-button you print the plain QR but you could as well integrate it into a nice tippincard or voucher template by choosing "Advanced voucher" -> "Use custom voucher design". We collected some designs as well as templates to make your own ones under <a href="https://youtu.be/c5EV9UNgVqk">this LNbits voucher video-guide.</a>. You will be able to create and print as much voucher as you like with it. Happy orangepilling!</li>
<li> Note that your LNbits needs to be reachable in clearnet to offer vouchers to others.</li>
</ul>
</details>
<details><summary>NFC Cards, Badges, Rings etc.</summary>
<p>Creating a NFC card for a wallet</p>
<p>To generate links for your cards you will need LNbits to be available in clearnet. Please consider running your own LNbits instance for this.</p>
<ul>
<li>On top to just printing voucher for your wallet you can also <a href="https://youtu.be/CQz1ILcK0PY">write these LNURLw to a simple NFC card fromon NTAG216</a> by not clicking the printer but the NFC symbol on android/chrome and tapping your card against the device. This will enable the cardholder to directly spend those sats at a tpos, pos or wallet-app another one uses that can handle lightning payments via NFC. </li>
<li>If you run an event and want to hand out bigger amounts of cards with simple voucher links on consider this <a hrel="nfc-brrr.com/">NFC-brrr batch tool</a> as well as using NTAG424 cards, so that your customers can rewrite them later with an own wallet and the boltcard service (see ff)</li>
<li>For bigger amounts the Boltcard-Extension should be used. It will generate a link that sends a new invoice every time it is used for payments and keeps track too if the allowed card-ID is redeeming funds. Hence the setup of Boltcards is a bit safer but it needs some additional tools. You can find <a href="https://plebtag.com/write-tags/">further infos on creating or updating boltcards here</a>.</li>
</ul><p>
<ul><details><summary>Resources - NFC & LNbits</summary>
<ul>
<li><a href="https://www.boltcard.org">Coincorner Boltcard</a></li>
<li><a href="https://www.plebtag.com">PlebTag (infos, Lasercards, Badges)</a></li>
<li><a href="https://www.lasereyes.cards">Lasercards</a></li>
<li><a href="https://www.bitcoin-ring.com">Bitcoin Ring</a></li>
<li><a href="https://github.com/taxmeifyoucan/HCPP2021-Badge">Badge</a></li>
<li><a href="https://github.com/cryptoteun/awesome-lnbits#powered-by-lnbits">Powered by LNbits examples</a></li>
</ul>
</ul>
</p>
</details>
</details>
</ul>
## Developing for LNbits
<ul>
<li><a href="http://docs.lnbits.org/devs/development.html">Making Estension / how to use Websockets / API reference</a></li>
<li><a href="https://t.me/lnbits">Telegram LNbits Support Group</a></li></ul>
</ul>
+19 -14
View File
@@ -15,8 +15,8 @@ By default, LNbits will use SQLite as its database. You can also use PostgreSQL
If you have problems installing LNbits using these instructions, please have a look at the [Troubleshooting](#troubleshooting) section.
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
git clone https://github.com/lnbits/lnbits.git
cd lnbits
# for making sure python 3.9 is installed, skip if installed. To check your installed version: python3 --version
sudo apt update
@@ -50,9 +50,10 @@ poetry run lnbits
#### Updating the server
```
cd lnbits-legend/
cd lnbits
# Stop LNbits with `ctrl + x`
git pull
# Keep your poetry install up to date, this can be done with `poetry self update`
poetry install --only main
# Start LNbits with `poetry run lnbits`
```
@@ -62,8 +63,8 @@ poetry install --only main
> note: currently not supported while we make some architectural changes on the path to leave beta
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
git clone https://github.com/lnbits/lnbits.git
cd lnbits
# Modern debian distros usually include Nix, however you can install with:
# 'sh <(curl -L https://nixos.org/nix/install) --daemon', or use setup here https://nixos.org/download.html#nix-verify-installation
@@ -82,8 +83,8 @@ LNBITS_DATA_FOLDER=data LNBITS_BACKEND_WALLET_CLASS=LNbitsWallet LNBITS_ENDPOINT
## Option 3: venv
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
git clone https://github.com/lnbits/lnbits.git
cd lnbits
# ensure you have virtualenv installed, on debian/ubuntu 'apt install python3.9-venv'
python3.9 -m venv venv
# If you have problems here, try `sudo apt install -y pkg-config libpq-dev`
@@ -105,9 +106,9 @@ If you want to host LNbits on the internet, run with the option `--host 0.0.0.0`
## Option 4: Docker
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend
docker build -t lnbits-legend .
git clone https://github.com/lnbits/lnbits.git
cd lnbits
docker build -t lnbits .
cp .env.example .env
mkdir data
docker run --detach --publish 5000:5000 --name lnbits-legend --volume ${PWD}/.env:/app/.env --volume ${PWD}/data/:/app/data lnbits-legend
@@ -135,8 +136,8 @@ You can either run those commands, then `source ~/.bash_profile` or, if you don'
Once installed, run the following commands.
```
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend
git clone https://github.com/lnbits/lnbits.git
cd lnbits
fly auth login
[complete login process]
fly launch
@@ -206,6 +207,10 @@ poetry add setuptools wheel
./venv/bin/pip install setuptools wheel
```
#### Poetry
If your Poetry version is older than 1.2, for `poetry install`, ignore the `--only main` flag.
### Optional: PostgreSQL database
If you want to use LNbits at scale, we recommend using PostgreSQL as the backend database. Install Postgres and setup a database for LNbits:
@@ -433,8 +438,8 @@ If you want to run LNbits on your Umbrel but want it to be reached through clear
To install using docker you first need to build the docker image as:
```
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend
git clone https://github.com/lnbits/lnbits.git
cd lnbits
docker build -t lnbits-legend .
```
+1 -1
View File
@@ -5,7 +5,7 @@
};
outputs = { self, nixpkgs, poetry2nix }@inputs:
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" ];
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forSystems = systems: f:
nixpkgs.lib.genAttrs systems
(system: f system (import nixpkgs { inherit system; overlays = [ poetry2nix.overlay self.overlays.default ]; }));
+10 -11
View File
@@ -1,16 +1,15 @@
import hashlib
import re
import time
from binascii import unhexlify
from decimal import Decimal
from typing import List, NamedTuple, Optional
import bitstring # type: ignore
import bitstring
import embit
import secp256k1
from bech32 import CHARSET, bech32_decode, bech32_encode
from ecdsa import SECP256k1, VerifyingKey # type: ignore
from ecdsa.util import sigdecode_string # type: ignore
from ecdsa import SECP256k1, VerifyingKey
from ecdsa.util import sigdecode_string
class Route(NamedTuple):
@@ -75,7 +74,7 @@ def decode(pr: str) -> Invoice:
data_length = len(tagdata) / 5
if tag == "d":
invoice.description = _trim_to_bytes(tagdata).decode("utf-8")
invoice.description = _trim_to_bytes(tagdata).decode()
elif tag == "h" and data_length == 52:
invoice.description_hash = _trim_to_bytes(tagdata).hex()
elif tag == "p" and data_length == 52:
@@ -108,7 +107,7 @@ def decode(pr: str) -> Invoice:
message = bytearray([ord(c) for c in hrp]) + data.tobytes()
sig = signature[0:64]
if invoice.payee:
key = VerifyingKey.from_string(unhexlify(invoice.payee), curve=SECP256k1)
key = VerifyingKey.from_string(bytes.fromhex(invoice.payee), curve=SECP256k1)
key.verify(sig, message, hashlib.sha256, sigdecode=sigdecode_string)
else:
keys = VerifyingKey.from_public_key_recovery(
@@ -131,7 +130,7 @@ def encode(options):
if options["timestamp"]:
addr.date = int(options["timestamp"])
addr.paymenthash = unhexlify(options["paymenthash"])
addr.paymenthash = bytes.fromhex(options["paymenthash"])
if options["description"]:
addr.tags.append(("d", options["description"]))
@@ -149,8 +148,8 @@ def encode(options):
while len(splits) >= 5:
route.append(
(
unhexlify(splits[0]),
unhexlify(splits[1]),
bytes.fromhex(splits[0]),
bytes.fromhex(splits[1]),
int(splits[2]),
int(splits[3]),
int(splits[4]),
@@ -235,7 +234,7 @@ def lnencode(addr, privkey):
raise ValueError("Must include either 'd' or 'h'")
# We actually sign the hrp, then data (padded to 8 bits with zeroes).
privkey = secp256k1.PrivateKey(bytes(unhexlify(privkey)))
privkey = secp256k1.PrivateKey(bytes.fromhex(privkey))
sig = privkey.ecdsa_sign_recoverable(
bytearray([ord(c) for c in hrp]) + data.tobytes()
)
@@ -261,7 +260,7 @@ class LnAddr(object):
def __str__(self):
return "LnAddr[{}, amount={}{} tags=[{}]]".format(
hexlify(self.pubkey.serialize()).decode("utf-8"),
bytes.hex(self.pubkey.serialize()).decode(),
self.amount,
self.currency,
", ".join([k + "=" + str(v) for k, v in self.tags]),
+72 -1
View File
@@ -4,12 +4,14 @@ from typing import Any, Dict, List, Optional
from urllib.parse import urlparse
from uuid import uuid4
import shortuuid
from lnbits import bolt11
from lnbits.db import COCKROACH, POSTGRES, Connection
from lnbits.settings import AdminSettings, EditableSettings, SuperSettings, settings
from . import db
from .models import BalanceCheck, Payment, User, Wallet
from .models import BalanceCheck, Payment, TinyURL, User, Wallet
# accounts
# --------
@@ -451,6 +453,34 @@ async def update_payment_details(
return
async def update_payment_extra(
payment_hash: str,
extra: dict,
outgoing: bool = False,
conn: Optional[Connection] = None,
) -> None:
"""
Only update the `extra` field for the payment.
Old values in the `extra` JSON object will be kept unless the new `extra` overwrites them.
"""
amount_clause = "AND amount < 0" if outgoing else "AND amount > 0"
row = await (conn or db).fetchone(
f"SELECT hash, extra from apipayments WHERE hash = ? {amount_clause}",
(payment_hash,),
)
if not row:
return
db_extra = json.loads(row["extra"] if row["extra"] else "{}")
db_extra.update(extra)
await (conn or db).execute(
f"UPDATE apipayments SET extra = ? WHERE hash = ? {amount_clause} ",
(json.dumps(db_extra), payment_hash),
)
async def delete_payment(checking_id: str, conn: Optional[Connection] = None) -> None:
await (conn or db).execute(
"DELETE FROM apipayments WHERE checking_id = ?", (checking_id,)
@@ -592,3 +622,44 @@ async def create_admin_settings(super_user: str, new_settings: dict):
sql = f"INSERT INTO settings (super_user, editable_settings) VALUES (?, ?)"
await db.execute(sql, (super_user, json.dumps(new_settings)))
return await get_super_settings()
# tinyurl
# -------
async def create_tinyurl(domain: str, endless: bool, wallet: str):
tinyurl_id = shortuuid.uuid()[:8]
await db.execute(
f"INSERT INTO tiny_url (id, url, endless, wallet) VALUES (?, ?, ?, ?)",
(
tinyurl_id,
domain,
endless,
wallet,
),
)
return await get_tinyurl(tinyurl_id)
async def get_tinyurl(tinyurl_id: str) -> Optional[TinyURL]:
row = await db.fetchone(
f"SELECT * FROM tiny_url WHERE id = ?",
(tinyurl_id,),
)
return TinyURL.from_row(row) if row else None
async def get_tinyurl_by_url(url: str) -> List[TinyURL]:
rows = await db.fetchall(
f"SELECT * FROM tiny_url WHERE url = ?",
(url,),
)
return [TinyURL.from_row(row) for row in rows]
async def delete_tinyurl(tinyurl_id: str):
row = await db.execute(
f"DELETE FROM tiny_url WHERE id = ?",
(tinyurl_id,),
)
+17 -3
View File
@@ -1,7 +1,7 @@
import datetime
from loguru import logger
from sqlalchemy.exc import OperationalError # type: ignore
from sqlalchemy.exc import OperationalError
from lnbits import bolt11
@@ -224,7 +224,7 @@ async def m007_set_invoice_expiries(db):
)
).fetchall()
if len(rows):
logger.info(f"Mirgraion: Checking expiry of {len(rows)} invoices")
logger.info(f"Migration: Checking expiry of {len(rows)} invoices")
for i, (
payment_request,
checking_id,
@@ -238,7 +238,7 @@ async def m007_set_invoice_expiries(db):
invoice.date + invoice.expiry
)
logger.info(
f"Mirgraion: {i+1}/{len(rows)} setting expiry of invoice {invoice.payment_hash} to {expiration_date}"
f"Migration: {i+1}/{len(rows)} setting expiry of invoice {invoice.payment_hash} to {expiration_date}"
)
await db.execute(
"""
@@ -269,3 +269,17 @@ async def m008_create_admin_settings_table(db):
);
"""
)
async def m009_create_tinyurl_table(db):
await db.execute(
f"""
CREATE TABLE IF NOT EXISTS tiny_url (
id TEXT PRIMARY KEY,
url TEXT,
endless BOOL NOT NULL DEFAULT false,
wallet TEXT,
time TIMESTAMP NOT NULL DEFAULT {db.timestamp_now}
);
"""
)
+19 -7
View File
@@ -4,13 +4,13 @@ import hmac
import json
import time
from sqlite3 import Row
from typing import Dict, List, NamedTuple, Optional
from typing import Dict, List, Optional
from ecdsa import SECP256k1, SigningKey # type: ignore
from ecdsa import SECP256k1, SigningKey
from fastapi import Query
from lnurl import encode as lnurl_encode # type: ignore
from lnurl import encode as lnurl_encode
from loguru import logger
from pydantic import BaseModel, Extra, validator
from pydantic import BaseModel
from lnbits.db import Connection
from lnbits.helpers import url_for
@@ -46,8 +46,8 @@ class Wallet(BaseModel):
return ""
def lnurlauth_key(self, domain: str) -> SigningKey:
hashing_key = hashlib.sha256(self.id.encode("utf-8")).digest()
linking_key = hmac.digest(hashing_key, domain.encode("utf-8"), "sha256")
hashing_key = hashlib.sha256(self.id.encode()).digest()
linking_key = hmac.digest(hashing_key, domain.encode(), "sha256")
return SigningKey.from_string(
linking_key, curve=SECP256k1, hashfunc=hashlib.sha256
@@ -88,7 +88,7 @@ class Payment(BaseModel):
preimage: str
payment_hash: str
expiry: Optional[float]
extra: Optional[Dict] = {}
extra: Dict = {}
wallet_id: str
webhook: Optional[str]
webhook_status: Optional[int]
@@ -213,3 +213,15 @@ class BalanceCheck(BaseModel):
@classmethod
def from_row(cls, row: Row):
return cls(wallet=row["wallet"], service=row["service"], url=row["url"])
class TinyURL(BaseModel):
id: str
url: str
endless: bool
wallet: str
time: float
@classmethod
def from_row(cls, row: Row):
return cls(**dict(row))
+5 -12
View File
@@ -1,6 +1,5 @@
import asyncio
import json
from binascii import unhexlify
from io import BytesIO
from typing import Dict, List, Optional, Tuple
from urllib.parse import parse_qs, urlparse
@@ -8,17 +7,12 @@ from urllib.parse import parse_qs, urlparse
import httpx
from fastapi import Depends, WebSocket
from lnurl import LnurlErrorResponse
from lnurl import decode as decode_lnurl # type: ignore
from lnurl import decode as decode_lnurl
from loguru import logger
from lnbits import bolt11
from lnbits.db import Connection
from lnbits.decorators import (
WalletTypeInfo,
get_key_type,
require_admin_key,
require_invoice_key,
)
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.requestvars import g
from lnbits.settings import (
@@ -50,7 +44,7 @@ from .crud import (
from .models import Payment
try:
from typing import TypedDict # type: ignore
from typing import TypedDict
except ImportError: # pragma: nocover
from typing_extensions import TypedDict
@@ -308,7 +302,7 @@ async def perform_lnurlauth(
) -> Optional[LnurlErrorResponse]:
cb = urlparse(callback)
k1 = unhexlify(parse_qs(cb.query)["k1"][0])
k1 = bytes.fromhex(parse_qs(cb.query)["k1"][0])
key = wallet.wallet.lnurlauth_key(cb.netloc)
@@ -438,9 +432,8 @@ async def check_admin_settings():
for key, value in settings.dict(exclude_none=True).items():
logger.debug(f"{key}: {value}")
http = "https" if settings.lnbits_force_https else "http"
admin_url = (
f"{http}://{settings.host}:{settings.port}/wallet?usr={settings.super_user}"
f"http://{settings.host}:{settings.port}/wallet?usr={settings.super_user}"
)
logger.success(f"✔️ Access super user account at: {admin_url}")
-1
View File
@@ -4,7 +4,6 @@ from typing import Dict
import httpx
from loguru import logger
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import SseListenersDict, register_invoice_listener
from . import db
+11 -11
View File
@@ -141,15 +141,15 @@
return {
settings: {},
lnbits_theme_options: [
'classic',
'bitcoin',
'flamingo',
'freedom',
'mint',
'autumn',
'monochrome',
'salvador'
],
'classic',
'bitcoin',
'flamingo',
'freedom',
'mint',
'autumn',
'monochrome',
'salvador'
],
formData: {},
formAddAdmin: '',
formAddUser: '',
@@ -204,11 +204,11 @@
value: null,
label: 'Certificate'
},
lnd_admin_macaroon: {
lnd_rest_admin_macaroon: {
value: null,
label: 'Admin Macaroon'
},
lnd_invoice_macaroon: {
lnd_rest_invoice_macaroon: {
value: null,
label: 'Invoice Macaroon'
}
+49 -8
View File
@@ -23,14 +23,55 @@
>
<q-card>
<q-card-section>
<q-icon
:name="extension.icon"
color="grey-5"
style="font-size: 4rem"
></q-icon>
{% raw %}
<h5 class="q-mt-lg q-mb-xs">{{ extension.name }}</h5>
<small>{{ extension.shortDescription }} </small>{% endraw %}
<div class="row">
<div class="col-3">
<q-img
:src="extension.tile"
spinner-color="white"
style="max-width: 100%"
></q-img>
</div>
<div class="col-9 q-pl-sm">
{% raw %}
<div class="text-h5 gt-sm q-mt-sm q-mb-xs">
{{ extension.name }}
</div>
<div
class="text-subtitle2 gt-sm"
style="font-size: 11px; height: 34px"
>
{{ extension.shortDescription }}
</div>
<div class="text-subtitle1 lt-md q-mt-sm q-mb-xs">
{{ extension.name }}
</div>
<div
class="text-subtitle2 lt-md"
style="font-size: 9px; height: 34px"
>
{{ extension.shortDescription }}
</div>
{% endraw %}
</div>
</div>
</q-card-section>
<q-card-section>
<div>
<q-rating
class="gt-sm"
disable
size="2em"
:max="5"
color="primary"
></q-rating
><q-rating
class="lt-md"
size="1.5em"
:max="5"
color="primary"
></q-rating
><q-tooltip>Ratings coming soon</q-tooltip>
</div>
</q-card-section>
<q-separator></q-separator>
<q-card-actions>
+1 -1
View File
@@ -66,7 +66,7 @@
outline
color="grey"
type="a"
href="https://github.com/lnbits/lnbits-legend"
href="https://github.com/lnbits/lnbits"
target="_blank"
rel="noopener"
>View project in GitHub</q-btn
+9 -20
View File
@@ -23,7 +23,7 @@
<strong>{% raw %}{{ formattedBalance }} {% endraw %}</strong>
{{LNBITS_DENOMINATION}}
<q-btn
v-if="'{{user.admin}}' == 'True'"
v-if="'{{user.super_user}}' == 'True'"
flat
round
color="primary"
@@ -36,27 +36,16 @@
v-model="credit"
>
<q-input
v-if="'{{LNBITS_DENOMINATION}}' != 'sats'"
label="Amount to credit account"
filled
label="{{LNBITS_DENOMINATION}} to credit"
hint="Press Enter to credit account"
v-model="scope.value"
dense
autofocus
mask="#.##"
:mask="'{{LNBITS_DENOMINATION}}' != 'sats' ? '#.##' : '#'"
fill-mask="0"
reverse-fill-mask
@keyup.enter="updateBalance(scope.value)"
>
<template v-slot:append>
<q-icon name="edit" />
</template>
</q-input>
<q-input
v-else
type="number"
label="Amount to credit account"
v-model="scope.value"
dense
autofocus
:step="'{{LNBITS_DENOMINATION}}' != 'sats' ? '0.01' : '1'"
@keyup.enter="updateBalance(scope.value)"
>
<template v-slot:append>
@@ -231,7 +220,7 @@
<a :href="'lightning:' + props.row.bolt11">
<q-responsive :ratio="1" class="q-mx-xl">
<qrcode
:value="props.row.bolt11"
:value="'lightning:' + props.row.bolt11.toUpperCase()"
:options="{width: 340}"
class="rounded-borders"
></qrcode>
@@ -325,7 +314,7 @@
</p>
<a href="lightning:{{wallet.lnurlwithdraw_full}}">
<qrcode
value="{{wallet.lnurlwithdraw_full}}"
value="lightning:{{wallet.lnurlwithdraw_full}}"
:options="{width:240}"
></qrcode>
</a>
@@ -524,7 +513,7 @@
<a :href="'lightning:' + receive.paymentReq">
<q-responsive :ratio="1" class="q-mx-xl">
<qrcode
:value="receive.paymentReq"
:value="'lightning:' + receive.paymentReq.toUpperCase()"
:options="{width: 340}"
class="rounded-borders"
></qrcode>
+1 -1
View File
@@ -17,7 +17,7 @@ from ..crud import delete_admin_settings, get_admin_settings, update_admin_setti
@core_app.get("/admin/api/v1/settings/")
async def api_get_settings(
user: User = Depends(check_admin), # type: ignore
user: User = Depends(check_admin),
) -> Optional[AdminSettings]:
admin_settings = await get_admin_settings(user.super_user)
return admin_settings
+93 -17
View File
@@ -1,5 +1,4 @@
import asyncio
import binascii
import hashlib
import json
import time
@@ -13,6 +12,7 @@ import async_timeout
import httpx
import pyqrcode
from fastapi import (
Body,
Depends,
Header,
Query,
@@ -22,12 +22,11 @@ from fastapi import (
WebSocketDisconnect,
)
from fastapi.exceptions import HTTPException
from fastapi.params import Body
from loguru import logger
from pydantic import BaseModel
from pydantic.fields import Field
from sse_starlette.sse import EventSourceResponse
from starlette.responses import StreamingResponse
from starlette.responses import RedirectResponse, StreamingResponse
from lnbits import bolt11, lnurl
from lnbits.core.models import Payment, Wallet
@@ -38,7 +37,7 @@ from lnbits.decorators import (
require_admin_key,
require_invoice_key,
)
from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.helpers import url_for
from lnbits.settings import get_wallet_class, settings
from lnbits.utils.exchange_rates import (
currencies,
@@ -48,14 +47,15 @@ from lnbits.utils.exchange_rates import (
from .. import core_app, db
from ..crud import (
create_payment,
create_tinyurl,
delete_tinyurl,
get_payments,
get_standalone_payment,
get_tinyurl,
get_tinyurl_by_url,
get_total_balance,
get_wallet,
get_wallet_for_key,
save_balance_check,
update_payment_status,
update_wallet,
)
from ..services import (
@@ -71,6 +71,11 @@ from ..services import (
from ..tasks import api_invoice_listeners
@core_app.get("/api/v1/health", status_code=HTTPStatus.OK)
async def health():
return
@core_app.get("/api/v1/wallet")
async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
if wallet.wallet_type == 0:
@@ -140,16 +145,14 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
if data.description_hash or data.unhashed_description:
try:
description_hash = (
binascii.unhexlify(data.description_hash)
if data.description_hash
else b""
bytes.fromhex(data.description_hash) if data.description_hash else b""
)
unhashed_description = (
binascii.unhexlify(data.unhashed_description)
bytes.fromhex(data.unhashed_description)
if data.unhashed_description
else b""
)
except binascii.Error:
except ValueError:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail="'description_hash' and 'unhashed_description' must be a valid hex strings",
@@ -214,7 +217,8 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
lnurl_response = resp["reason"]
else:
lnurl_response = True
except (httpx.ConnectError, httpx.RequestError):
except (httpx.ConnectError, httpx.RequestError) as ex:
logger.error(ex)
lnurl_response = False
return {
@@ -251,7 +255,7 @@ async def api_payments_pay_invoice(bolt11: str, wallet: Wallet):
)
async def api_payments_create(
wallet: WalletTypeInfo = Depends(require_invoice_key),
invoiceData: CreateInvoiceData = Body(...), # type: ignore
invoiceData: CreateInvoiceData = Body(...),
):
if invoiceData.out is True and wallet.wallet_type == 0:
if not invoiceData.bolt11:
@@ -387,7 +391,7 @@ async def subscribe_wallet_invoices(request: Request, wallet: Wallet):
jdata = json.dumps(dict(data.dict(), pending=False))
yield dict(data=jdata, event=typ)
except asyncio.CancelledError as e:
except asyncio.CancelledError:
logger.debug(f"removing listener for wallet {uid}")
api_invoice_listeners.pop(uid)
task.cancel()
@@ -536,7 +540,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
params.update(
description_hash=hashlib.sha256(
data["metadata"].encode("utf-8")
data["metadata"].encode()
).hexdigest()
)
metadata = json.loads(data["metadata"])
@@ -658,7 +662,7 @@ async def img(request: Request, data):
)
@core_app.get("/api/v1/audit/", dependencies=[Depends(check_admin)])
@core_app.get("/api/v1/audit", dependencies=[Depends(check_admin)])
async def api_auditor():
WALLET = get_wallet_class()
total_balance = await get_total_balance()
@@ -706,3 +710,75 @@ async def websocket_update_get(item_id: str, data: str):
return {"sent": True, "data": data}
except:
return {"sent": False, "data": data}
############################TINYURL##################################
@core_app.post("/api/v1/tinyurl")
async def api_create_tinyurl(
url: str, endless: bool = False, wallet: WalletTypeInfo = Depends(get_key_type)
):
tinyurls = await get_tinyurl_by_url(url)
try:
for tinyurl in tinyurls:
if tinyurl:
if tinyurl.wallet == wallet.wallet.inkey:
return tinyurl
return await create_tinyurl(url, endless, wallet.wallet.inkey)
except:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Unable to create tinyurl"
)
@core_app.get("/api/v1/tinyurl/{tinyurl_id}")
async def api_get_tinyurl(
tinyurl_id: str, wallet: WalletTypeInfo = Depends(get_key_type)
):
try:
tinyurl = await get_tinyurl(tinyurl_id)
if tinyurl:
if tinyurl.wallet == wallet.wallet.inkey:
return tinyurl
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided."
)
except:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Unable to fetch tinyurl"
)
@core_app.delete("/api/v1/tinyurl/{tinyurl_id}")
async def api_delete_tinyurl(
tinyurl_id: str, wallet: WalletTypeInfo = Depends(get_key_type)
):
try:
tinyurl = await get_tinyurl(tinyurl_id)
if tinyurl:
if tinyurl.wallet == wallet.wallet.inkey:
await delete_tinyurl(tinyurl_id)
return {"deleted": True}
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Wrong key provided."
)
except:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Unable to delete"
)
@core_app.get("/t/{tinyurl_id}")
async def api_tinyurl(tinyurl_id: str):
try:
tinyurl = await get_tinyurl(tinyurl_id)
if tinyurl:
response = RedirectResponse(url=tinyurl.url)
return response
else:
return
except:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="unable to find tinyurl"
)
+25 -12
View File
@@ -2,9 +2,8 @@ import asyncio
from http import HTTPStatus
from typing import Optional
from fastapi import Request, status
from fastapi import Depends, Query, Request, status
from fastapi.exceptions import HTTPException
from fastapi.params import Depends, Query
from fastapi.responses import FileResponse, RedirectResponse
from fastapi.routing import APIRouter
from loguru import logger
@@ -38,20 +37,29 @@ async def favicon():
@core_html_routes.get("/", response_class=HTMLResponse)
async def home(request: Request, lightning: str = None):
async def home(request: Request, lightning: str = ""):
return template_renderer().TemplateResponse(
"core/index.html", {"request": request, "lnurl": lightning}
)
@core_html_routes.get("/robots.txt", response_class=HTMLResponse)
async def robots():
data = """
User-agent: *
Disallow: /
"""
return HTMLResponse(content=data, media_type="text/plain")
@core_html_routes.get(
"/extensions", name="core.extensions", response_class=HTMLResponse
)
async def extensions(
request: Request,
user: User = Depends(check_user_exists), # type: ignore
enable: str = Query(None), # type: ignore
disable: str = Query(None), # type: ignore
user: User = Depends(check_user_exists),
enable: str = Query(None),
disable: str = Query(None),
):
extension_to_enable = enable
extension_to_disable = disable
@@ -103,10 +111,10 @@ nothing: create everything<br>
""",
)
async def wallet(
request: Request = Query(None), # type: ignore
nme: Optional[str] = Query(None), # type: ignore
usr: Optional[UUID4] = Query(None), # type: ignore
wal: Optional[UUID4] = Query(None), # type: ignore
request: Request = Query(None),
nme: Optional[str] = Query(None),
usr: Optional[UUID4] = Query(None),
wal: Optional[UUID4] = Query(None),
):
user_id = usr.hex if usr else None
wallet_id = wal.hex if wal else None
@@ -124,12 +132,17 @@ async def wallet(
if (
len(settings.lnbits_allowed_users) > 0
and user_id not in settings.lnbits_allowed_users
and user_id not in settings.lnbits_admin_users
and user_id != settings.super_user
):
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User not authorized."}
)
if user_id == settings.super_user or user_id in settings.lnbits_admin_users:
user.admin = True
if user_id == settings.super_user:
user.super_user = True
if not wallet_id:
if user.wallets and not wallet_name: # type: ignore
wallet = user.wallets[0] # type: ignore
@@ -214,7 +227,7 @@ async def lnurl_full_withdraw_callback(request: Request):
@core_html_routes.get("/deletewallet", response_class=RedirectResponse)
async def deletewallet(request: Request, wal: str = Query(...), usr: str = Query(...)): # type: ignore
async def deletewallet(wal: str = Query(...), usr: str = Query(...)):
user = await get_user(usr)
user_wallet_ids = [u.id for u in user.wallets] # type: ignore
@@ -310,7 +323,7 @@ async def manifest(usr: str):
@core_html_routes.get("/admin", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_admin)): # type: ignore
async def index(request: Request, user: User = Depends(check_admin)):
WALLET = get_wallet_class()
_, balance = await WALLET.status()
+1 -2
View File
@@ -6,7 +6,6 @@ from urllib.parse import urlparse
from fastapi import HTTPException
from loguru import logger
from starlette.requests import Request
from starlette.responses import HTMLResponse
from lnbits import bolt11
@@ -17,7 +16,7 @@ from ..tasks import api_invoice_listeners
@core_app.get("/.well-known/lnurlp/{username}")
async def lnaddress(username: str, request: Request):
from lnbits.extensions.lnaddress.lnurl import lnurl_response
from lnbits.extensions.lnaddress.lnurl import lnurl_response # type: ignore
domain = urlparse(str(request.url)).netloc
return await lnurl_response(username, domain, request)
+2 -2
View File
@@ -9,7 +9,7 @@ from typing import Optional
from loguru import logger
from sqlalchemy import create_engine
from sqlalchemy_aio.base import AsyncConnection
from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore
from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY
from lnbits.settings import settings
@@ -129,7 +129,7 @@ class Database(Compat):
else:
self.type = POSTGRES
import psycopg2 # type: ignore
import psycopg2
def _parse_timestamp(value, _):
if value is None:
+8 -11
View File
@@ -1,11 +1,8 @@
from http import HTTPStatus
from typing import Union
from cerberus import Validator # type: ignore
from fastapi import status
from fastapi import Security, status
from fastapi.exceptions import HTTPException
from fastapi.openapi.models import APIKey, APIKeyIn
from fastapi.params import Security
from fastapi.security.api_key import APIKeyHeader, APIKeyQuery
from fastapi.security.base import SecurityBase
from pydantic.types import UUID4
@@ -118,8 +115,8 @@ api_key_query = APIKeyQuery(
async def get_key_type(
r: Request,
api_key_header: str = Security(api_key_header), # type: ignore
api_key_query: str = Security(api_key_query), # type: ignore
api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query),
) -> WalletTypeInfo:
# 0: admin
# 1: invoice
@@ -174,8 +171,8 @@ async def get_key_type(
async def require_admin_key(
r: Request,
api_key_header: str = Security(api_key_header), # type: ignore
api_key_query: str = Security(api_key_query), # type: ignore
api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query),
):
token = api_key_header or api_key_query
@@ -200,8 +197,8 @@ async def require_admin_key(
async def require_invoice_key(
r: Request,
api_key_header: str = Security(api_key_header), # type: ignore
api_key_query: str = Security(api_key_query), # type: ignore
api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query),
):
token = api_key_header or api_key_query
@@ -236,8 +233,8 @@ async def check_user_exists(usr: UUID4) -> User:
if (
len(settings.lnbits_allowed_users) > 0
and g().user.id not in settings.lnbits_allowed_users
and g().user.id != settings.super_user
and g().user.id not in settings.lnbits_admin_users
and g().user.id != settings.super_user
):
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Bleskomat",
"short_description": "Connect a Bleskomat ATM to an lnbits",
"icon": "money",
"tile": "/bleskomat/static/image/bleskomat.png",
"contributors": ["chill117"]
}
+12 -7
View File
@@ -1,5 +1,6 @@
import json
import os
from typing import Callable, Dict, Union
import httpx
@@ -12,7 +13,9 @@ fiat_currencies = json.load(
)
)
exchange_rate_providers = {
exchange_rate_providers: dict[
str, dict[str, Union[str, Callable[[dict, dict], str]]]
] = {
"bitfinex": {
"name": "Bitfinex",
"domain": "bitfinex.com",
@@ -65,17 +68,19 @@ async def fetch_fiat_exchange_rate(currency: str, provider: str):
"to": currency.lower(),
}
url = exchange_rate_providers[provider]["api_url"]
if url:
api_url_or_none = exchange_rate_providers[provider]["api_url"]
if api_url_or_none is not None:
api_url = str(api_url_or_none)
for key in replacements.keys():
url = url.replace("{" + key + "}", replacements[key])
api_url = api_url.replace("{" + key + "}", replacements[key])
async with httpx.AsyncClient() as client:
r = await client.get(url)
r = await client.get(api_url)
r.raise_for_status()
data = r.json()
else:
data = {}
getter = exchange_rate_providers[provider]["getter"]
rate = float(getter(data, replacements))
print(getter)
if callable(getter):
rate = float(getter(data, replacements))
return rate
+13 -14
View File
@@ -1,12 +1,11 @@
import base64
import hashlib
import hmac
import urllib
from binascii import unhexlify
from http import HTTPStatus
from typing import Dict
from urllib import parse
from starlette.requests import Request
from fastapi import Request
def generate_bleskomat_lnurl_hash(secret: str):
@@ -19,11 +18,11 @@ def generate_bleskomat_lnurl_signature(
payload: str, api_key_secret: str, api_key_encoding: str = "hex"
):
if api_key_encoding == "hex":
key = unhexlify(api_key_secret)
key = bytes.fromhex(api_key_secret)
elif api_key_encoding == "base64":
key = base64.b64decode(api_key_secret)
else:
key = bytes(f"{api_key_secret}")
key = bytes.fromhex(api_key_secret)
return hmac.new(key=key, msg=payload.encode(), digestmod=hashlib.sha256).hexdigest()
@@ -58,8 +57,8 @@ class LnurlValidationError(Exception):
pass
def prepare_lnurl_params(tag: str, query: Dict[str, str]):
params = {}
def prepare_lnurl_params(tag: str, query: dict) -> dict:
params: dict = {}
if not is_supported_lnurl_subprotocol(tag):
raise LnurlValidationError(f'Unsupported subprotocol: "{tag}"')
if tag == "withdrawRequest":
@@ -86,15 +85,15 @@ def query_to_signing_payload(query: Dict[str, str]) -> str:
payload = []
for key in sorted_keys:
if not key == "signature":
encoded_key = urllib.parse.quote(key, safe=encode_uri_component_safe_chars)
encoded_value = urllib.parse.quote(
encoded_key = parse.quote(key, safe=encode_uri_component_safe_chars)
encoded_value = parse.quote(
query[key], safe=encode_uri_component_safe_chars
)
payload.append(f"{encoded_key}={encoded_value}")
return "&".join(payload)
unshorten_rules = {
unshorten_rules: dict[str, dict] = {
"query": {"n": "nonce", "s": "signature", "t": "tag"},
"tags": {
"c": "channelRequest",
@@ -115,7 +114,7 @@ unshorten_rules = {
}
def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]:
def unshorten_lnurl_query(query: dict) -> Dict[str, str]:
new_query = {}
rules = unshorten_rules
if "tag" in query:
@@ -132,9 +131,9 @@ def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]:
if not tag in rules["params"]:
raise LnurlValidationError(f'Unknown tag: "{tag}"')
for key in query:
if key in rules["params"][tag]:
if key in rules["params"][str(tag)]:
short_param_key = key
long_param_key = rules["params"][tag][short_param_key]
long_param_key = rules["params"][str(tag)][short_param_key]
if short_param_key in query:
new_query[long_param_key] = query[short_param_key]
else:
@@ -147,7 +146,7 @@ def unshorten_lnurl_query(query: Dict[str, str]) -> Dict[str, str]:
if short_key in query:
new_query[long_key] = query[short_key]
else:
new_query[long_key] = query[long_key]
new_query[long_key] = query[str(long_key)]
else:
# Keep unknown key/value pairs unchanged:
new_query[key] = query[key]
+8 -5
View File
@@ -1,6 +1,5 @@
import json
import math
import traceback
from http import HTTPStatus
from loguru import logger
@@ -28,7 +27,7 @@ from .helpers import (
@bleskomat_ext.get("/u", name="bleskomat.api_bleskomat_lnurl")
async def api_bleskomat_lnurl(req: Request):
try:
query = req.query_params
query = dict(req.query_params)
# Unshorten query if "s" is used instead of "signature".
if "s" in query:
@@ -89,11 +88,15 @@ async def api_bleskomat_lnurl(req: Request):
# Convert to msats:
params[key] = int(amount_sats_less_fee * 1e3)
except LnurlValidationError as e:
raise LnurlHttpError(e.message, HTTPStatus.BAD_REQUEST)
raise LnurlHttpError(str(e), HTTPStatus.BAD_REQUEST)
# Create a new LNURL using the query parameters provided in the signed URL.
params = json.JSONEncoder().encode(params)
json_params = json.JSONEncoder().encode(params)
lnurl = await create_bleskomat_lnurl(
bleskomat=bleskomat, secret=secret, tag=tag, params=params, uses=1
bleskomat=bleskomat,
secret=secret,
tag=tag,
params=json_params,
uses=1,
)
# Reply with LNURL response object.
+3 -4
View File
@@ -2,10 +2,9 @@ import json
import time
from typing import Dict
from fastapi.params import Query
from fastapi import Query, Request
from loguru import logger
from pydantic import BaseModel, validator
from starlette.requests import Request
from lnbits import bolt11
from lnbits.core.services import PaymentFailure, pay_invoice
@@ -80,7 +79,7 @@ class BleskomatLnurl(BaseModel):
response["k1"] = secret
return response
def validate_action(self, query: Dict[str, str]) -> None:
def validate_action(self, query) -> None:
tag = self.tag
params = json.loads(self.params)
# Perform tag-specific checks.
@@ -109,7 +108,7 @@ class BleskomatLnurl(BaseModel):
else:
raise LnurlValidationError(f'Unknown subprotocol: "{tag}"')
async def execute_action(self, query: Dict[str, str]):
async def execute_action(self, query):
self.validate_action(query)
used = False
async with db.connect() as conn:
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

@@ -9,11 +9,13 @@
<p>
This extension allows you to connect a Bleskomat ATM to an lnbits
wallet. It will work with both the
<a href="https://github.com/samotari/bleskomat"
<a class="text-secondary" href="https://github.com/samotari/bleskomat"
>open-source DIY Bleskomat ATM project</a
>
as well as the
<a href="https://www.bleskomat.com/">commercial Bleskomat ATM</a>.
<a class="text-secondary" href="https://www.bleskomat.com/"
>commercial Bleskomat ATM</a
>.
</p>
<h5 class="text-subtitle1 q-my-none">Connect Your Bleskomat ATM</h5>
<div>
+1 -2
View File
@@ -1,5 +1,4 @@
from fastapi import Request
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse
+6 -4
View File
@@ -6,7 +6,6 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.bleskomat.models import CreateBleskomat
from . import bleskomat_ext
from .crud import (
@@ -17,6 +16,7 @@ from .crud import (
update_bleskomat,
)
from .exchange_rates import fetch_fiat_exchange_rate
from .models import CreateBleskomat
@bleskomat_ext.get("/api/v1/bleskomats")
@@ -27,7 +27,8 @@ async def api_bleskomats(
wallet_ids = [wallet.wallet.id]
if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [bleskomat.dict() for bleskomat in await get_bleskomats(wallet_ids)]
@@ -54,9 +55,9 @@ async def api_bleskomat_create_or_update(
wallet: WalletTypeInfo = Depends(require_admin_key),
bleskomat_id=None,
):
fiat_currency = data.fiat_currency
exchange_rate_provider = data.exchange_rate_provider
try:
fiat_currency = data.fiat_currency
exchange_rate_provider = data.exchange_rate_provider
await fetch_fiat_exchange_rate(
currency=fiat_currency, provider=exchange_rate_provider
)
@@ -79,6 +80,7 @@ async def api_bleskomat_create_or_update(
else:
bleskomat = await create_bleskomat(wallet_id=wallet.wallet.id, data=data)
assert bleskomat
return bleskomat.dict()
+3 -1
View File
@@ -2,7 +2,7 @@
This extension allows you to link your Bolt Card (or other compatible NXP NTAG device) with a LNbits instance and use it in a more secure way than a static LNURLw. A technology called [Secure Unique NFC](https://mishka-scan.com/blog/secure-unique-nfc) is utilized in this workflow.
<a href="https://www.youtube.com/watch?v=wJ7QLFTRjK0">Tutorial</a>
<a class="text-secondary" href="https://www.youtube.com/watch?v=wJ7QLFTRjK0">Tutorial</a>
**Disclaimer:** ***Use this only if you either know what you are doing or are a reckless lightning pioneer. Only you are responsible for all your sats, cards and other devices. Always backup all your card keys!***
@@ -55,6 +55,8 @@ Since v0.1.2 of Boltcard NFC Card Creator it is possible not only reset the keys
- Click RESET CARD NOW and approach the NFC card to erase it. DO NOT REMOVE THE CARD PREMATURELY!
- Now if there is all success the card can be safely delete from LNbits (but keep the keys backuped anyway; batter safe than brick).
If you somehow find yourself in some non-standard state (for instance only k3 and k4 remains filled after previous unsuccessful reset), then you need edit the key fields manually (for instance leave k0-k2 to zeroes and provide the right k3 and k4).
## Setting the card - computer (hard way)
Follow the guide.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Bolt Cards",
"short_description": "Self custody Bolt Cards with one time LNURLw",
"icon": "payment",
"tile": "/boltcards/static/image/boltcard.png",
"contributors": ["iwarpbtc", "arcbtc", "leesalminen"]
}
+12 -13
View File
@@ -1,6 +1,6 @@
import secrets
from datetime import date, datetime
from typing import List, Optional, Union
from datetime import datetime
from typing import List, Optional
from lnbits.helpers import urlsafe_short_hash
@@ -66,9 +66,9 @@ async def update_card(card_id: str, **kwargs) -> Optional[Card]:
return Card(**row) if row else None
async def get_cards(wallet_ids: Union[str, List[str]]) -> List[Card]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
async def get_cards(wallet_ids: List[str]) -> List[Card]:
if len(wallet_ids) == 0:
return []
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(
@@ -124,14 +124,13 @@ async def get_card_by_otp(otp: str) -> Optional[Card]:
async def delete_card(card_id: str) -> None:
# Delete cards
card = await get_card(card_id)
await db.execute("DELETE FROM boltcards.cards WHERE id = ?", (card_id,))
# Delete hits
hits = await get_hits([card_id])
for hit in hits:
await db.execute("DELETE FROM boltcards.hits WHERE id = ?", (hit.id,))
# Delete refunds
refunds = await get_refunds([hit])
refunds = await get_refunds([hit.id])
for refund in refunds:
await db.execute(
"DELETE FROM boltcards.refunds WHERE id = ?", (refund.hit_id,)
@@ -146,7 +145,7 @@ async def update_card_counter(counter: int, id: str):
async def enable_disable_card(enable: bool, id: str) -> Optional[Card]:
row = await db.execute(
await db.execute(
"UPDATE boltcards.cards SET enable = ? WHERE id = ?",
(enable, id),
)
@@ -161,7 +160,7 @@ async def update_card_otp(otp: str, id: str):
async def get_hit(hit_id: str) -> Optional[Hit]:
row = await db.fetchone(f"SELECT * FROM boltcards.hits WHERE id = ?", (hit_id))
row = await db.fetchone(f"SELECT * FROM boltcards.hits WHERE id = ?", (hit_id,))
if not row:
return None
@@ -170,7 +169,7 @@ async def get_hit(hit_id: str) -> Optional[Hit]:
return Hit.parse_obj(hit)
async def get_hits(cards_ids: Union[str, List[str]]) -> List[Hit]:
async def get_hits(cards_ids: List[str]) -> List[Hit]:
if len(cards_ids) == 0:
return []
@@ -182,7 +181,7 @@ async def get_hits(cards_ids: Union[str, List[str]]) -> List[Hit]:
return [Hit(**row) for row in rows]
async def get_hits_today(card_id: str) -> Optional[Hit]:
async def get_hits_today(card_id: str) -> List[Hit]:
rows = await db.fetchall(
f"SELECT * FROM boltcards.hits WHERE card_id = ?",
(card_id,),
@@ -259,7 +258,7 @@ async def create_refund(hit_id, refund_amount) -> Refund:
async def get_refund(refund_id: str) -> Optional[Refund]:
row = await db.fetchone(
f"SELECT * FROM boltcards.refunds WHERE id = ?", (refund_id)
f"SELECT * FROM boltcards.refunds WHERE id = ?", (refund_id,)
)
if not row:
return None
@@ -267,7 +266,7 @@ async def get_refund(refund_id: str) -> Optional[Refund]:
return Refund.parse_obj(refund)
async def get_refunds(hits_ids: Union[str, List[str]]) -> List[Refund]:
async def get_refunds(hits_ids: List[str]) -> List[Refund]:
if len(hits_ids) == 0:
return []
+17 -18
View File
@@ -3,13 +3,9 @@ import secrets
from http import HTTPStatus
from urllib.parse import urlparse
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends, Query
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
from starlette.exceptions import HTTPException
from starlette.requests import Request
from fastapi import HTTPException, Query, Request
from lnurl import encode as lnurl_encode
from lnurl.types import LnurlPayMetadata
from starlette.responses import HTMLResponse
from lnbits import bolt11
@@ -28,14 +24,13 @@ from .crud import (
update_card_counter,
update_card_otp,
)
from .models import CreateCardData
from .nxp424 import decryptSUN, getSunMAC
###############LNURLWITHDRAW#################
# /boltcards/api/v1/scan?p=00000000000000000000000000000000&c=0000000000000000
@boltcards_ext.get("/api/v1/scan/{external_id}")
async def api_scan(p, c, request: Request, external_id: str = None):
async def api_scan(p, c, request: Request, external_id: str = Query(None)):
# some wallets send everything as lower case, no bueno
p = p.upper()
c = c.upper()
@@ -63,6 +58,7 @@ async def api_scan(p, c, request: Request, external_id: str = None):
await update_card_counter(ctr_int, card.id)
# gathering some info for hit record
assert request.client
ip = request.client.host
if "x-real-ip" in request.headers:
ip = request.headers["x-real-ip"]
@@ -95,7 +91,6 @@ async def api_scan(p, c, request: Request, external_id: str = None):
name="boltcards.lnurl_callback",
)
async def lnurl_callback(
request: Request,
pr: str = Query(None),
k1: str = Query(None),
):
@@ -120,7 +115,9 @@ async def lnurl_callback(
return {"status": "ERROR", "reason": "Failed to decode payment request"}
card = await get_card(hit.card_id)
assert card
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
assert hit
try:
await pay_invoice(
wallet_id=card.wallet,
@@ -155,7 +152,7 @@ async def api_auth(a, request: Request):
response = {
"card_name": card.card_name,
"id": 1,
"id": str(1),
"k0": card.k0,
"k1": card.k1,
"k2": card.k2,
@@ -163,7 +160,7 @@ async def api_auth(a, request: Request):
"k4": card.k2,
"lnurlw_base": "lnurlw://" + lnurlw_base,
"protocol_name": "new_bolt_card_response",
"protocol_version": 1,
"protocol_version": str(1),
}
return response
@@ -179,7 +176,9 @@ async def api_auth(a, request: Request):
)
async def lnurlp_response(req: Request, hit_id: str = Query(None)):
hit = await get_hit(hit_id)
assert hit
card = await get_card(hit.card_id)
assert card
if not hit:
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
if not card.enable:
@@ -199,21 +198,21 @@ async def lnurlp_response(req: Request, hit_id: str = Query(None)):
response_class=HTMLResponse,
name="boltcards.lnurlp_callback",
)
async def lnurlp_callback(
req: Request, hit_id: str = Query(None), amount: str = Query(None)
):
async def lnurlp_callback(hit_id: str = Query(None), amount: str = Query(None)):
hit = await get_hit(hit_id)
assert hit
card = await get_card(hit.card_id)
assert card
if not hit:
return {"status": "ERROR", "reason": f"LNURL-pay record not found."}
payment_hash, payment_request = await create_invoice(
_, payment_request = await create_invoice(
wallet_id=card.wallet,
amount=int(amount) / 1000,
amount=int(int(amount) / 1000),
memo=f"Refund {hit_id}",
unhashed_description=LnurlPayMetadata(
json.dumps([["text/plain", "Refund"]])
).encode("utf-8"),
).encode(),
extra={"refund": hit_id},
)
+8 -8
View File
@@ -1,14 +1,11 @@
import json
from sqlite3 import Row
from typing import Optional
from fastapi import Request
from fastapi.params import Query
from fastapi import Query, Request
from lnurl import Lnurl
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
from lnurl import encode as lnurl_encode
from lnurl.types import LnurlPayMetadata
from pydantic import BaseModel
from pydantic.main import BaseModel
ZERO_KEY = "00000000000000000000000000000000"
@@ -32,6 +29,7 @@ class Card(BaseModel):
otp: str
time: int
@classmethod
def from_row(cls, row: Row) -> "Card":
return cls(**dict(row))
@@ -40,7 +38,7 @@ class Card(BaseModel):
return lnurl_encode(url)
async def lnurlpay_metadata(self) -> LnurlPayMetadata:
return LnurlPayMetadata(json.dumps([["text/plain", self.title]]))
return LnurlPayMetadata(json.dumps([["text/plain", self.card_name]]))
class CreateCardData(BaseModel):
@@ -69,6 +67,7 @@ class Hit(BaseModel):
amount: int
time: int
@classmethod
def from_row(cls, row: Row) -> "Hit":
return cls(**dict(row))
@@ -79,5 +78,6 @@ class Refund(BaseModel):
refund_amount: int
time: int
@classmethod
def from_row(cls, row: Row) -> "Refund":
return cls(**dict(row))
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

+5 -6
View File
@@ -1,8 +1,6 @@
import asyncio
import json
import httpx
from lnbits.core import db as core_db
from lnbits.core.models import Payment
from lnbits.helpers import get_current_extension_name
@@ -21,22 +19,23 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra.get("refund"):
return
if payment.extra.get("wh_status"):
# this webhook has already been sent
return
hit = await get_hit(payment.extra.get("refund"))
hit = await get_hit(str(payment.extra.get("refund")))
if hit:
refund = await create_refund(
hit_id=hit.id, refund_amount=(payment.amount / 1000)
)
await create_refund(hit_id=hit.id, refund_amount=(payment.amount / 1000))
await mark_webhook_sent(payment, 1)
async def mark_webhook_sent(payment: Payment, status: int) -> None:
payment.extra["wh_status"] = status
await core_db.execute(
@@ -11,6 +11,7 @@
Manage your Bolt Cards self custodian way<br />
<a
class="text-secondary"
href="https://github.com/lnbits/lnbits/tree/main/lnbits/extensions/boltcards"
>More details</a
>
@@ -375,6 +375,7 @@
<p class="text-center" v-show="!qrCodeDialog.wipe">
(QR for <strong>create</strong> the card in
<a
class="text-secondary"
href="https://play.google.com/store/apps/details?id=com.lightningnfcapp"
target="_blank"
style="color: inherit"
@@ -395,6 +396,7 @@
<p class="text-center" v-show="qrCodeDialog.wipe">
(QR for <strong>wipe</strong> the card in
<a
class="text-secondary"
href="https://play.google.com/store/apps/details?id=com.lightningnfcapp"
target="_blank"
style="color: inherit"
+1 -2
View File
@@ -1,5 +1,4 @@
from fastapi import FastAPI, Request
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse
+11 -13
View File
@@ -1,10 +1,6 @@
import secrets
from http import HTTPStatus
from fastapi.params import Depends, Query
from loguru import logger
from starlette.exceptions import HTTPException
from starlette.requests import Request
from fastapi import Depends, HTTPException, Query
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
@@ -15,13 +11,11 @@ from .crud import (
delete_card,
enable_disable_card,
get_card,
get_card_by_otp,
get_card_by_uid,
get_cards,
get_hits,
get_refunds,
update_card,
update_card_otp,
)
from .models import CreateCardData
@@ -33,7 +27,8 @@ async def api_cards(
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [card.dict() for card in await get_cards(wallet_ids)]
@@ -41,9 +36,8 @@ async def api_cards(
@boltcards_ext.post("/api/v1/cards", status_code=HTTPStatus.CREATED)
@boltcards_ext.put("/api/v1/cards/{card_id}", status_code=HTTPStatus.OK)
async def api_card_create_or_update(
# req: Request,
data: CreateCardData,
card_id: str = None,
card_id: str = Query(None),
wallet: WalletTypeInfo = Depends(require_admin_key),
):
try:
@@ -95,6 +89,7 @@ async def api_card_create_or_update(
status_code=HTTPStatus.BAD_REQUEST,
)
card = await create_card(wallet_id=wallet.wallet.id, data=data)
assert card
return card.dict()
@@ -110,6 +105,7 @@ async def enable_card(
if card.wallet != wallet.wallet.id:
raise HTTPException(detail="Not your card.", status_code=HTTPStatus.FORBIDDEN)
card = await enable_disable_card(enable=enable, id=card_id)
assert card
return card.dict()
@@ -136,7 +132,8 @@ async def api_hits(
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
cards = await get_cards(wallet_ids)
cards_ids = []
@@ -147,13 +144,14 @@ async def api_hits(
@boltcards_ext.get("/api/v1/refunds")
async def api_hits(
async def api_refunds(
g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)
):
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
cards = await get_cards(wallet_ids)
cards_ids = []
+6 -4
View File
@@ -5,11 +5,12 @@ move **IN** and **OUT** of the **lightning network** and remain in control of yo
* [Documentation](https://docs.boltz.exchange/en/latest/)
* [Discord](https://discord.gg/d6EK85KK)
* [Twitter](https://twitter.com/Boltzhq)
* [FAQ](https://www.notion.so/Frequently-Asked-Questions-585328ae43944e2eba351050790d5eec) very cool!
# usage
This extension lets you create swaps, reverse swaps and in the case of failure refund your onchain funds.
## create normal swap
## create normal swap (Onchain -> Lightning)
1. click on "Swap (IN)" button to open following dialog, select a wallet, choose a proper amount in the min-max range and choose a onchain address to do your refund to if the swap fails after you already commited onchain funds.
---
![create swap](https://imgur.com/OyOh3Nm.png)
@@ -22,14 +23,14 @@ This extension lets you create swaps, reverse swaps and in the case of failure r
if anything goes wrong when boltz is trying to pay your invoice, the swap will fail and you will need to refund your onchain funds after the timeout block height hit. (if boltz can pay the invoice, it wont be able to redeem your onchain funds either).
## create reverse swap
## create reverse swap (Lightning -> Onchain)
1. click on "Swap (OUT)" button to open following dialog, select a wallet, choose a proper amount in the min-max range and choose a onchain address to receive your funds to. Instant settlement: means that LNbits will create the onchain claim transaction if it sees the boltz lockup transaction in the mempool, but it is not confirmed yet. it is advised to leave this checked because it is faster and the longer is takes to settle, the higher the chances are that the lightning invoice expires and the swap fails.
---
![reverse swap](https://imgur.com/UEAPpbs.png)
---
if this swap fails, boltz is doing the onchain refunding, because they have to commit onchain funds.
# refund locked onchain funds from a normal swap
# refund locked onchain funds from a normal swap (Onchain -> Lightning)
if for some reason the normal swap fails and you already paid onchain, you can easily refund your btc.
this can happen if boltz is not able to pay your lightning invoice after you locked up your funds.
in case that happens, there is a info icon in the Swap (In) List which opens following dialog.
@@ -37,4 +38,5 @@ in case that happens, there is a info icon in the Swap (In) List which opens fol
![refund](https://imgur.com/pN81ltf.png)
----
if the timeout block height is exceeded you can either press refund and lnbits will do the refunding to the address you specified when creating the swap. Or download the refundfile so you can manually refund your onchain directly on the boltz.exchange website.
if you think there is something wrong and/or you are unsure, you can ask for help either in LNbits telegram or in Boltz [Discord](https://discord.gg/d6EK85KK)
if you think there is something wrong and/or you are unsure, you can ask for help either in LNbits telegram or in Boltz [Discord](https://discord.gg/d6EK85KK).
In a recent update we made *automated check*, every 15 minutes, to check if LNbits can refund your failed swap.
+9
View File
@@ -1,6 +1,7 @@
import asyncio
from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
@@ -15,6 +16,14 @@ def boltz_renderer():
return template_renderer(["lnbits/extensions/boltz/templates"])
boltz_static_files = [
{
"path": "/boltz/static",
"app": StaticFiles(directory="lnbits/extensions/boltz/static"),
"name": "boltz_static",
}
]
from .tasks import check_for_pending_swaps, wait_for_paid_invoices
from .views import * # noqa
from .views_api import * # noqa
-422
View File
@@ -1,422 +0,0 @@
import asyncio
import os
from binascii import hexlify, unhexlify
from hashlib import sha256
from typing import Awaitable, Union
import httpx
from embit import ec, script
from embit.networks import NETWORKS
from embit.transaction import SIGHASH, Transaction, TransactionInput, TransactionOutput
from loguru import logger
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.helpers import urlsafe_short_hash
from lnbits.settings import settings
from .crud import update_swap_status
from .mempool import (
get_fee_estimation,
get_mempool_blockheight,
get_mempool_fees,
get_mempool_tx,
get_mempool_tx_from_txs,
send_onchain_tx,
wait_for_websocket_message,
)
from .models import (
CreateReverseSubmarineSwap,
CreateSubmarineSwap,
ReverseSubmarineSwap,
SubmarineSwap,
SwapStatus,
)
from .utils import check_balance, get_timestamp, req_wrap
net = NETWORKS[settings.boltz_network]
async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
if not check_boltz_limits(data.amount):
msg = f"Boltz - swap not in boltz limits"
logger.warning(msg)
raise Exception(msg)
swap_id = urlsafe_short_hash()
try:
payment_hash, payment_request = await create_invoice(
wallet_id=data.wallet,
amount=data.amount,
memo=f"swap of {data.amount} sats on boltz.exchange",
extra={"tag": "boltz", "swap_id": swap_id},
)
except Exception as exc:
msg = f"Boltz - create_invoice failed {str(exc)}"
logger.error(msg)
raise
refund_privkey = ec.PrivateKey(os.urandom(32), True, net)
refund_pubkey_hex = hexlify(refund_privkey.sec()).decode("UTF-8")
res = req_wrap(
"post",
f"{settings.boltz_url}/createswap",
json={
"type": "submarine",
"pairId": "BTC/BTC",
"orderSide": "sell",
"refundPublicKey": refund_pubkey_hex,
"invoice": payment_request,
"referralId": "lnbits",
},
headers={"Content-Type": "application/json"},
)
res = res.json()
logger.info(
f"Boltz - created normal swap, boltz_id: {res['id']}. wallet: {data.wallet}"
)
return SubmarineSwap(
id=swap_id,
time=get_timestamp(),
wallet=data.wallet,
amount=data.amount,
payment_hash=payment_hash,
refund_privkey=refund_privkey.wif(net),
refund_address=data.refund_address,
boltz_id=res["id"],
status="pending",
address=res["address"],
expected_amount=res["expectedAmount"],
timeout_block_height=res["timeoutBlockHeight"],
bip21=res["bip21"],
redeem_script=res["redeemScript"],
)
"""
explanation taken from electrum
send on Lightning, receive on-chain
- User generates preimage, RHASH. Sends RHASH to server.
- Server creates an LN invoice for RHASH.
- User pays LN invoice - except server needs to hold the HTLC as preimage is unknown.
- Server creates on-chain output locked to RHASH.
- User spends on-chain output, revealing preimage.
- Server fulfills HTLC using preimage.
Note: expected_onchain_amount_sat is BEFORE deducting the on-chain claim tx fee.
"""
async def create_reverse_swap(
data: CreateReverseSubmarineSwap,
) -> [ReverseSubmarineSwap, asyncio.Task]:
if not check_boltz_limits(data.amount):
msg = f"Boltz - reverse swap not in boltz limits"
logger.warning(msg)
raise Exception(msg)
swap_id = urlsafe_short_hash()
if not await check_balance(data):
logger.error(f"Boltz - reverse swap, insufficient balance.")
return False
claim_privkey = ec.PrivateKey(os.urandom(32), True, net)
claim_pubkey_hex = hexlify(claim_privkey.sec()).decode("UTF-8")
preimage = os.urandom(32)
preimage_hash = sha256(preimage).hexdigest()
res = req_wrap(
"post",
f"{settings.boltz_url}/createswap",
json={
"type": "reversesubmarine",
"pairId": "BTC/BTC",
"orderSide": "buy",
"invoiceAmount": data.amount,
"preimageHash": preimage_hash,
"claimPublicKey": claim_pubkey_hex,
"referralId": "lnbits",
},
headers={"Content-Type": "application/json"},
)
res = res.json()
logger.info(
f"Boltz - created reverse swap, boltz_id: {res['id']}. wallet: {data.wallet}"
)
swap = ReverseSubmarineSwap(
id=swap_id,
amount=data.amount,
wallet=data.wallet,
onchain_address=data.onchain_address,
instant_settlement=data.instant_settlement,
claim_privkey=claim_privkey.wif(net),
preimage=preimage.hex(),
status="pending",
boltz_id=res["id"],
timeout_block_height=res["timeoutBlockHeight"],
lockup_address=res["lockupAddress"],
onchain_amount=res["onchainAmount"],
redeem_script=res["redeemScript"],
invoice=res["invoice"],
time=get_timestamp(),
)
logger.debug(f"Boltz - waiting for onchain tx, reverse swap_id: {swap.id}")
task = create_task_log_exception(
swap.id, wait_for_onchain_tx(swap, swap_websocket_callback_initial)
)
return swap, task
def start_onchain_listener(swap: ReverseSubmarineSwap) -> asyncio.Task:
return create_task_log_exception(
swap.id, wait_for_onchain_tx(swap, swap_websocket_callback_restart)
)
async def start_confirmation_listener(
swap: ReverseSubmarineSwap, mempool_lockup_tx
) -> asyncio.Task:
logger.debug(f"Boltz - reverse swap, waiting for confirmation...")
tx, txid, *_ = mempool_lockup_tx
confirmed = await wait_for_websocket_message({"track-tx": txid}, "txConfirmed")
if confirmed:
logger.debug(f"Boltz - reverse swap lockup transaction confirmed! claiming...")
await create_claim_tx(swap, mempool_lockup_tx)
else:
logger.debug(f"Boltz - reverse swap lockup transaction still not confirmed.")
def create_task_log_exception(swap_id: str, awaitable: Awaitable) -> asyncio.Task:
async def _log_exception(awaitable):
try:
return await awaitable
except Exception as e:
logger.error(f"Boltz - reverse swap failed!: {swap_id} - {e}")
await update_swap_status(swap_id, "failed")
return asyncio.create_task(_log_exception(awaitable))
async def swap_websocket_callback_initial(swap):
wstask = asyncio.create_task(
wait_for_websocket_message(
{"track-address": swap.lockup_address}, "address-transactions"
)
)
logger.debug(
f"Boltz - created task, waiting on mempool websocket for address: {swap.lockup_address}"
)
# create_task is used because pay_invoice is stuck as long as boltz does not
# see the onchain claim tx and it ends up in deadlock
task: asyncio.Task = create_task_log_exception(
swap.id,
pay_invoice(
wallet_id=swap.wallet,
payment_request=swap.invoice,
description=f"reverse swap for {swap.amount} sats on boltz.exchange",
extra={"tag": "boltz", "swap_id": swap.id, "reverse": True},
),
)
logger.debug(f"Boltz - task pay_invoice created, reverse swap_id: {swap.id}")
done, pending = await asyncio.wait(
[task, wstask], return_when=asyncio.FIRST_COMPLETED
)
message = done.pop().result()
# pay_invoice already failed, do not wait for onchain tx anymore
if message is None:
logger.debug(f"Boltz - pay_invoice already failed cancel websocket task.")
wstask.cancel()
raise
return task, message
async def swap_websocket_callback_restart(swap):
logger.debug(f"Boltz - swap_websocket_callback_restart called...")
message = await wait_for_websocket_message(
{"track-address": swap.lockup_address}, "address-transactions"
)
return None, message
async def wait_for_onchain_tx(swap: ReverseSubmarineSwap, callback):
task, txs = await callback(swap)
mempool_lockup_tx = get_mempool_tx_from_txs(txs, swap.lockup_address)
if mempool_lockup_tx:
tx, txid, *_ = mempool_lockup_tx
if swap.instant_settlement or tx["status"]["confirmed"]:
logger.debug(
f"Boltz - reverse swap instant settlement, claiming immediatly..."
)
await create_claim_tx(swap, mempool_lockup_tx)
else:
await start_confirmation_listener(swap, mempool_lockup_tx)
try:
if task:
await task
except:
logger.error(
f"Boltz - could not await pay_invoice task, but sent onchain. should never happen!"
)
else:
logger.error(f"Boltz - mempool lockup tx not found.")
async def create_claim_tx(swap: ReverseSubmarineSwap, mempool_lockup_tx):
tx = await create_onchain_tx(swap, mempool_lockup_tx)
await send_onchain_tx(tx)
logger.debug(f"Boltz - onchain tx sent, reverse swap completed")
await update_swap_status(swap.id, "complete")
async def create_refund_tx(swap: SubmarineSwap):
mempool_lockup_tx = get_mempool_tx(swap.address)
tx = await create_onchain_tx(swap, mempool_lockup_tx)
await send_onchain_tx(tx)
def check_block_height(block_height: int):
current_block_height = get_mempool_blockheight()
if current_block_height <= block_height:
msg = f"refund not possible, timeout_block_height ({block_height}) is not yet exceeded ({current_block_height})"
logger.debug(msg)
raise Exception(msg)
"""
a submarine swap consists of 2 onchain tx's a lockup and a redeem tx.
we create a tx to redeem the funds locked by the onchain lockup tx.
claim tx for reverse swaps, refund tx for normal swaps they are the same
onchain redeem tx, the difference between them is the private key, onchain_address,
input sequence and input script_sig
"""
async def create_onchain_tx(
swap: Union[ReverseSubmarineSwap, SubmarineSwap], mempool_lockup_tx
) -> Transaction:
is_refund_tx = type(swap) == SubmarineSwap
if is_refund_tx:
check_block_height(swap.timeout_block_height)
privkey = ec.PrivateKey.from_wif(swap.refund_privkey)
onchain_address = swap.refund_address
preimage = b""
sequence = 0xFFFFFFFE
else:
privkey = ec.PrivateKey.from_wif(swap.claim_privkey)
preimage = unhexlify(swap.preimage)
onchain_address = swap.onchain_address
sequence = 0xFFFFFFFF
locktime = swap.timeout_block_height
redeem_script = unhexlify(swap.redeem_script)
fees = get_fee_estimation()
tx, txid, vout_cnt, vout_amount = mempool_lockup_tx
script_pubkey = script.address_to_scriptpubkey(onchain_address)
vin = [TransactionInput(unhexlify(txid), vout_cnt, sequence=sequence)]
vout = [TransactionOutput(vout_amount - fees, script_pubkey)]
tx = Transaction(vin=vin, vout=vout)
if is_refund_tx:
tx.locktime = locktime
# TODO: 2 rounds for fee calculation, look at vbytes after signing and do another TX
s = script.Script(data=redeem_script)
for i, inp in enumerate(vin):
if is_refund_tx:
rs = bytes([34]) + bytes([0]) + bytes([32]) + sha256(redeem_script).digest()
tx.vin[i].script_sig = script.Script(data=rs)
h = tx.sighash_segwit(i, s, vout_amount)
sig = privkey.sign(h).serialize() + bytes([SIGHASH.ALL])
witness_items = [sig, preimage, redeem_script]
tx.vin[i].witness = script.Witness(items=witness_items)
return tx
def get_swap_status(swap: Union[SubmarineSwap, ReverseSubmarineSwap]) -> SwapStatus:
swap_status = SwapStatus(
wallet=swap.wallet,
swap_id=swap.id,
)
try:
boltz_request = get_boltz_status(swap.boltz_id)
swap_status.boltz = boltz_request["status"]
except httpx.HTTPStatusError as exc:
json = exc.response.json()
swap_status.boltz = json["error"]
if "could not find" in swap_status.boltz:
swap_status.exists = False
if type(swap) == SubmarineSwap:
swap_status.reverse = False
swap_status.address = swap.address
else:
swap_status.reverse = True
swap_status.address = swap.lockup_address
swap_status.block_height = get_mempool_blockheight()
swap_status.timeout_block_height = (
f"{str(swap.timeout_block_height)} -> current: {str(swap_status.block_height)}"
)
if swap_status.block_height >= swap.timeout_block_height:
swap_status.hit_timeout = True
mempool_tx = get_mempool_tx(swap_status.address)
swap_status.lockup = mempool_tx
if mempool_tx == None:
swap_status.has_lockup = False
swap_status.confirmed = False
swap_status.mempool = "transaction.unknown"
swap_status.message = "lockup tx not in mempool"
else:
swap_status.has_lockup = True
tx, *_ = mempool_tx
if tx["status"]["confirmed"] == True:
swap_status.mempool = "transaction.confirmed"
swap_status.confirmed = True
else:
swap_status.confirmed = False
swap_status.mempool = "transaction.unconfirmed"
return swap_status
def check_boltz_limits(amount):
try:
pairs = get_boltz_pairs()
limits = pairs["pairs"]["BTC/BTC"]["limits"]
return amount >= limits["minimal"] and amount <= limits["maximal"]
except:
return False
def get_boltz_pairs():
res = req_wrap(
"get",
f"{settings.boltz_url}/getpairs",
headers={"Content-Type": "application/json"},
)
return res.json()
def get_boltz_status(boltzid):
res = req_wrap(
"post",
f"{settings.boltz_url}/swapstatus",
json={"id": boltzid},
)
return res.json()
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "Boltz",
"short_description": "Perform onchain/offchain swaps",
"icon": "swap_horiz",
"tile": "/boltz/static/image/boltz.png",
"contributors": ["dni"]
}
+141 -82
View File
@@ -1,21 +1,21 @@
from http import HTTPStatus
import time
from typing import List, Optional, Union
from boltz_client.boltz import BoltzReverseSwapResponse, BoltzSwapResponse
from loguru import logger
from starlette.exceptions import HTTPException
from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import (
AutoReverseSubmarineSwap,
CreateAutoReverseSubmarineSwap,
CreateReverseSubmarineSwap,
CreateSubmarineSwap,
ReverseSubmarineSwap,
SubmarineSwap,
)
"""
Submarine Swaps
"""
async def get_submarine_swaps(wallet_ids: Union[str, List[str]]) -> List[SubmarineSwap]:
if isinstance(wallet_ids, str):
@@ -30,20 +30,6 @@ async def get_submarine_swaps(wallet_ids: Union[str, List[str]]) -> List[Submari
return [SubmarineSwap(**row) for row in rows]
async def get_pending_submarine_swaps(
wallet_ids: Union[str, List[str]]
) -> List[SubmarineSwap]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(
f"SELECT * FROM boltz.submarineswap WHERE wallet IN ({q}) and status='pending' order by time DESC",
(*wallet_ids,),
)
return [SubmarineSwap(**row) for row in rows]
async def get_all_pending_submarine_swaps() -> List[SubmarineSwap]:
rows = await db.fetchall(
f"SELECT * FROM boltz.submarineswap WHERE status='pending' order by time DESC",
@@ -51,14 +37,20 @@ async def get_all_pending_submarine_swaps() -> List[SubmarineSwap]:
return [SubmarineSwap(**row) for row in rows]
async def get_submarine_swap(swap_id) -> SubmarineSwap:
async def get_submarine_swap(swap_id) -> Optional[SubmarineSwap]:
row = await db.fetchone(
"SELECT * FROM boltz.submarineswap WHERE id = ?", (swap_id,)
)
return SubmarineSwap(**row) if row else None
async def create_submarine_swap(swap: SubmarineSwap) -> Optional[SubmarineSwap]:
async def create_submarine_swap(
data: CreateSubmarineSwap,
swap: BoltzSwapResponse,
swap_id: str,
refund_privkey_wif: str,
payment_hash: str,
) -> Optional[SubmarineSwap]:
await db.execute(
"""
@@ -80,26 +72,22 @@ async def create_submarine_swap(swap: SubmarineSwap) -> Optional[SubmarineSwap]:
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
swap_id,
data.wallet,
payment_hash,
"pending",
swap.id,
swap.wallet,
swap.payment_hash,
swap.status,
swap.boltz_id,
swap.refund_privkey,
swap.refund_address,
swap.expected_amount,
swap.timeout_block_height,
refund_privkey_wif,
data.refund_address,
swap.expectedAmount,
swap.timeoutBlockHeight,
swap.address,
swap.bip21,
swap.redeem_script,
swap.amount,
swap.redeemScript,
data.amount,
),
)
return await get_submarine_swap(swap.id)
async def delete_submarine_swap(swap_id):
await db.execute("DELETE FROM boltz.submarineswap WHERE id = ?", (swap_id,))
return await get_submarine_swap(swap_id)
async def get_reverse_submarine_swaps(
@@ -117,21 +105,6 @@ async def get_reverse_submarine_swaps(
return [ReverseSubmarineSwap(**row) for row in rows]
async def get_pending_reverse_submarine_swaps(
wallet_ids: Union[str, List[str]]
) -> List[ReverseSubmarineSwap]:
if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids]
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(
f"SELECT * FROM boltz.reverse_submarineswap WHERE wallet IN ({q}) and status='pending' order by time DESC",
(*wallet_ids,),
)
return [ReverseSubmarineSwap(**row) for row in rows]
async def get_all_pending_reverse_submarine_swaps() -> List[ReverseSubmarineSwap]:
rows = await db.fetchall(
f"SELECT * FROM boltz.reverse_submarineswap WHERE status='pending' order by time DESC"
@@ -140,7 +113,7 @@ async def get_all_pending_reverse_submarine_swaps() -> List[ReverseSubmarineSwap
return [ReverseSubmarineSwap(**row) for row in rows]
async def get_reverse_submarine_swap(swap_id) -> SubmarineSwap:
async def get_reverse_submarine_swap(swap_id) -> Optional[ReverseSubmarineSwap]:
row = await db.fetchone(
"SELECT * FROM boltz.reverse_submarineswap WHERE id = ?", (swap_id,)
)
@@ -148,8 +121,31 @@ async def get_reverse_submarine_swap(swap_id) -> SubmarineSwap:
async def create_reverse_submarine_swap(
swap: ReverseSubmarineSwap,
) -> Optional[ReverseSubmarineSwap]:
data: CreateReverseSubmarineSwap,
claim_privkey_wif: str,
preimage_hex: str,
swap: BoltzReverseSwapResponse,
) -> ReverseSubmarineSwap:
swap_id = urlsafe_short_hash()
reverse_swap = ReverseSubmarineSwap(
id=swap_id,
wallet=data.wallet,
status="pending",
boltz_id=swap.id,
instant_settlement=data.instant_settlement,
preimage=preimage_hex,
claim_privkey=claim_privkey_wif,
lockup_address=swap.lockupAddress,
invoice=swap.invoice,
onchain_amount=swap.onchainAmount,
onchain_address=data.onchain_address,
timeout_block_height=swap.timeoutBlockHeight,
redeem_script=swap.redeemScript,
amount=data.amount,
time=int(time.time()),
)
await db.execute(
"""
@@ -172,36 +168,93 @@ async def create_reverse_submarine_swap(
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
swap.id,
reverse_swap.id,
reverse_swap.wallet,
reverse_swap.status,
reverse_swap.boltz_id,
reverse_swap.instant_settlement,
reverse_swap.preimage,
reverse_swap.claim_privkey,
reverse_swap.lockup_address,
reverse_swap.invoice,
reverse_swap.onchain_amount,
reverse_swap.onchain_address,
reverse_swap.timeout_block_height,
reverse_swap.redeem_script,
reverse_swap.amount,
),
)
return reverse_swap
async def get_auto_reverse_submarine_swaps(
wallet_ids: List[str],
) -> List[AutoReverseSubmarineSwap]:
q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall(
f"SELECT * FROM boltz.auto_reverse_submarineswap WHERE wallet IN ({q}) order by time DESC",
(*wallet_ids,),
)
return [AutoReverseSubmarineSwap(**row) for row in rows]
async def get_auto_reverse_submarine_swap(
swap_id,
) -> Optional[AutoReverseSubmarineSwap]:
row = await db.fetchone(
"SELECT * FROM boltz.auto_reverse_submarineswap WHERE id = ?", (swap_id,)
)
return AutoReverseSubmarineSwap(**row) if row else None
async def get_auto_reverse_submarine_swap_by_wallet(
wallet_id,
) -> Optional[AutoReverseSubmarineSwap]:
row = await db.fetchone(
"SELECT * FROM boltz.auto_reverse_submarineswap WHERE wallet = ?", (wallet_id,)
)
return AutoReverseSubmarineSwap(**row) if row else None
async def create_auto_reverse_submarine_swap(
swap: CreateAutoReverseSubmarineSwap,
) -> Optional[AutoReverseSubmarineSwap]:
swap_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO boltz.auto_reverse_submarineswap (
id,
wallet,
onchain_address,
instant_settlement,
balance,
amount
)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
swap_id,
swap.wallet,
swap.status,
swap.boltz_id,
swap.instant_settlement,
swap.preimage,
swap.claim_privkey,
swap.lockup_address,
swap.invoice,
swap.onchain_amount,
swap.onchain_address,
swap.timeout_block_height,
swap.redeem_script,
swap.instant_settlement,
swap.balance,
swap.amount,
),
)
return await get_reverse_submarine_swap(swap.id)
return await get_auto_reverse_submarine_swap(swap_id)
async def delete_auto_reverse_submarine_swap(swap_id):
await db.execute(
"DELETE FROM boltz.auto_reverse_submarineswap WHERE id = ?", (swap_id,)
)
async def update_swap_status(swap_id: str, status: str):
reverse = ""
swap = await get_submarine_swap(swap_id)
if swap is None:
swap = await get_reverse_submarine_swap(swap_id)
if swap is None:
return None
if type(swap) == SubmarineSwap:
if swap:
await db.execute(
"UPDATE boltz.submarineswap SET status='"
+ status
@@ -209,17 +262,23 @@ async def update_swap_status(swap_id: str, status: str):
+ swap.id
+ "'"
)
if type(swap) == ReverseSubmarineSwap:
reverse = "reverse"
logger.info(
f"Boltz - swap status change: {status}. boltz_id: {swap.boltz_id}, wallet: {swap.wallet}"
)
return swap
reverse_swap = await get_reverse_submarine_swap(swap_id)
if reverse_swap:
await db.execute(
"UPDATE boltz.reverse_submarineswap SET status='"
+ status
+ "' WHERE id='"
+ swap.id
+ reverse_swap.id
+ "'"
)
logger.info(
f"Boltz - reverse swap status change: {status}. boltz_id: {reverse_swap.boltz_id}, wallet: {reverse_swap.wallet}"
)
return reverse_swap
message = f"Boltz - {reverse} swap status change: {status}. boltz_id: {swap.boltz_id}, wallet: {swap.wallet}"
logger.info(message)
return swap
return None
-94
View File
@@ -1,94 +0,0 @@
import asyncio
import json
from binascii import hexlify
import httpx
import websockets
from embit.transaction import Transaction
from loguru import logger
from lnbits.settings import settings
from .utils import req_wrap
websocket_url = f"{settings.boltz_mempool_space_url_ws}/api/v1/ws"
async def wait_for_websocket_message(send, message_string):
async for websocket in websockets.connect(websocket_url):
try:
await websocket.send(json.dumps({"action": "want", "data": ["blocks"]}))
await websocket.send(json.dumps(send))
async for raw in websocket:
message = json.loads(raw)
if message_string in message:
return message.get(message_string)
except websockets.ConnectionClosed:
continue
def get_mempool_tx(address):
res = req_wrap(
"get",
f"{settings.boltz_mempool_space_url}/api/address/{address}/txs",
headers={"Content-Type": "text/plain"},
)
txs = res.json()
return get_mempool_tx_from_txs(txs, address)
def get_mempool_tx_from_txs(txs, address):
if len(txs) == 0:
return None
tx = txid = vout_cnt = vout_amount = None
for a_tx in txs:
for i, vout in enumerate(a_tx["vout"]):
if vout["scriptpubkey_address"] == address:
tx = a_tx
txid = a_tx["txid"]
vout_cnt = i
vout_amount = vout["value"]
# should never happen
if tx == None:
raise Exception("mempool tx not found")
if txid == None:
raise Exception("mempool txid not found")
return tx, txid, vout_cnt, vout_amount
def get_fee_estimation() -> int:
# TODO: hardcoded maximum tx size, in the future we try to get the size of the tx via embit
# we need a function like Transaction.vsize()
tx_size_vbyte = 200
mempool_fees = get_mempool_fees()
return mempool_fees * tx_size_vbyte
def get_mempool_fees() -> int:
res = req_wrap(
"get",
f"{settings.boltz_mempool_space_url}/api/v1/fees/recommended",
headers={"Content-Type": "text/plain"},
)
fees = res.json()
return int(fees["economyFee"])
def get_mempool_blockheight() -> int:
res = req_wrap(
"get",
f"{settings.boltz_mempool_space_url}/api/blocks/tip/height",
headers={"Content-Type": "text/plain"},
)
return int(res.text)
async def send_onchain_tx(tx: Transaction):
raw = hexlify(tx.serialize())
logger.debug(f"Boltz - mempool sending onchain tx...")
req_wrap(
"post",
f"{settings.boltz_mempool_space_url}/api/tx",
headers={"Content-Type": "text/plain"},
content=raw,
)
+18
View File
@@ -44,3 +44,21 @@ async def m001_initial(db):
);
"""
)
async def m002_auto_swaps(db):
await db.execute(
"""
CREATE TABLE boltz.auto_reverse_submarineswap (
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
onchain_address TEXT NOT NULL,
amount INT NOT NULL,
balance INT NOT NULL,
instant_settlement BOOLEAN NOT NULL,
time TIMESTAMP NOT NULL DEFAULT """
+ db.timestamp_now
+ """
);
"""
)
+24 -31
View File
@@ -1,9 +1,5 @@
import json
from typing import Dict, List, Optional
from fastapi.params import Query
from pydantic.main import BaseModel
from sqlalchemy.engine import base # type: ignore
from fastapi import Query
from pydantic import BaseModel
class SubmarineSwap(BaseModel):
@@ -24,9 +20,9 @@ class SubmarineSwap(BaseModel):
class CreateSubmarineSwap(BaseModel):
wallet: str = Query(...) # type: ignore
refund_address: str = Query(...) # type: ignore
amount: int = Query(...) # type: ignore
wallet: str = Query(...)
refund_address: str = Query(...)
amount: int = Query(...)
class ReverseSubmarineSwap(BaseModel):
@@ -48,28 +44,25 @@ class ReverseSubmarineSwap(BaseModel):
class CreateReverseSubmarineSwap(BaseModel):
wallet: str = Query(...) # type: ignore
amount: int = Query(...) # type: ignore
instant_settlement: bool = Query(...) # type: ignore
# validate on-address, bcrt1 for regtest addresses
onchain_address: str = Query(
..., regex="^(bcrt1|bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$"
) # type: ignore
wallet: str = Query(...)
amount: int = Query(...)
instant_settlement: bool = Query(...)
onchain_address: str = Query(...)
class SwapStatus(BaseModel):
swap_id: str
class AutoReverseSubmarineSwap(BaseModel):
id: str
wallet: str
status: str = ""
message: str = ""
boltz: str = ""
mempool: str = ""
address: str = ""
block_height: int = 0
timeout_block_height: str = ""
lockup: Optional[dict] = {}
has_lockup: bool = False
hit_timeout: bool = False
confirmed: bool = True
exists: bool = True
reverse: bool = False
amount: int
balance: int
onchain_address: str
instant_settlement: bool
time: int
class CreateAutoReverseSubmarineSwap(BaseModel):
wallet: str = Query(...)
amount: int = Query(...)
balance: int = Query(0)
instant_settlement: bool = Query(...)
onchain_address: str = Query(...)
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

+147 -121
View File
@@ -1,129 +1,25 @@
import asyncio
import httpx
from boltz_client.boltz import BoltzNotFoundException, BoltzSwapStatusException
from boltz_client.mempool import MempoolBlockHeightException
from loguru import logger
from lnbits.core.crud import get_wallet
from lnbits.core.models import Payment
from lnbits.core.services import check_transaction_status
from lnbits.core.services import check_transaction_status, fee_reserve
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
from .boltz import (
create_claim_tx,
create_refund_tx,
get_swap_status,
start_confirmation_listener,
start_onchain_listener,
)
from .crud import (
create_reverse_submarine_swap,
get_all_pending_reverse_submarine_swaps,
get_all_pending_submarine_swaps,
get_reverse_submarine_swap,
get_auto_reverse_submarine_swap_by_wallet,
get_submarine_swap,
update_swap_status,
)
"""
testcases for boltz startup
A. normal swaps
1. test: create -> kill -> start -> startup invoice listeners -> pay onchain funds -> should complete
2. test: create -> kill -> pay onchain funds -> start -> startup check -> should complete
3. test: create -> kill -> mine blocks and hit timeout -> start -> should go timeout/failed
4. test: create -> kill -> pay to less onchain funds -> mine blocks hit timeout -> start lnbits -> should be refunded
B. reverse swaps
1. test: create instant -> kill -> boltz does lockup -> not confirmed -> start lnbits -> should claim/complete
2. test: create instant -> kill -> no lockup -> start lnbits -> should start onchain listener -> boltz does lockup -> should claim/complete (difficult to test)
3. test: create -> kill -> boltz does lockup -> not confirmed -> start lnbits -> should start tx listener -> after confirmation -> should claim/complete
4. test: create -> kill -> boltz does lockup -> confirmed -> start lnbits -> should claim/complete
5. test: create -> kill -> boltz does lockup -> hit timeout -> boltz refunds -> start -> should timeout
"""
async def check_for_pending_swaps():
try:
swaps = await get_all_pending_submarine_swaps()
reverse_swaps = await get_all_pending_reverse_submarine_swaps()
if len(swaps) > 0 or len(reverse_swaps) > 0:
logger.debug(f"Boltz - startup swap check")
except:
# database is not created yet, do nothing
return
if len(swaps) > 0:
logger.debug(f"Boltz - {len(swaps)} pending swaps")
for swap in swaps:
try:
swap_status = get_swap_status(swap)
# should only happen while development when regtest is reset
if swap_status.exists is False:
logger.debug(f"Boltz - swap: {swap.boltz_id} does not exist.")
await update_swap_status(swap.id, "failed")
continue
payment_status = await check_transaction_status(
swap.wallet, swap.payment_hash
)
if payment_status.paid:
logger.debug(
f"Boltz - swap: {swap.boltz_id} got paid while offline."
)
await update_swap_status(swap.id, "complete")
else:
if swap_status.hit_timeout:
if not swap_status.has_lockup:
logger.debug(
f"Boltz - swap: {swap.id} hit timeout, but no lockup tx..."
)
await update_swap_status(swap.id, "timeout")
else:
logger.debug(f"Boltz - refunding swap: {swap.id}...")
await create_refund_tx(swap)
await update_swap_status(swap.id, "refunded")
except Exception as exc:
logger.error(f"Boltz - swap: {swap.id} - {str(exc)}")
if len(reverse_swaps) > 0:
logger.debug(f"Boltz - {len(reverse_swaps)} pending reverse swaps")
for reverse_swap in reverse_swaps:
try:
swap_status = get_swap_status(reverse_swap)
if swap_status.exists is False:
logger.debug(
f"Boltz - reverse_swap: {reverse_swap.boltz_id} does not exist."
)
await update_swap_status(reverse_swap.id, "failed")
continue
# if timeout hit, boltz would have already refunded
if swap_status.hit_timeout:
logger.debug(
f"Boltz - reverse_swap: {reverse_swap.boltz_id} timeout."
)
await update_swap_status(reverse_swap.id, "timeout")
continue
if not swap_status.has_lockup:
# start listener for onchain address
logger.debug(
f"Boltz - reverse_swap: {reverse_swap.boltz_id} restarted onchain address listener."
)
await start_onchain_listener(reverse_swap)
continue
if reverse_swap.instant_settlement or swap_status.confirmed:
await create_claim_tx(reverse_swap, swap_status.lockup)
else:
logger.debug(
f"Boltz - reverse_swap: {reverse_swap.boltz_id} restarted confirmation listener."
)
await start_confirmation_listener(reverse_swap, swap_status.lockup)
except Exception as exc:
logger.error(f"Boltz - reverse swap: {reverse_swap.id} - {str(exc)}")
from .models import CreateReverseSubmarineSwap, ReverseSubmarineSwap, SubmarineSwap
from .utils import create_boltz_client, execute_reverse_swap
async def wait_for_paid_invoices():
@@ -136,19 +32,149 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if "boltz" != payment.extra.get("tag"):
await check_for_auto_swap(payment)
if payment.extra.get("tag") != "boltz":
# not a boltz invoice
return
await payment.set_pending(False)
swap_id = payment.extra.get("swap_id")
swap = await get_submarine_swap(swap_id)
if not swap:
logger.error(f"swap_id: {swap_id} not found.")
if payment.extra:
swap_id = payment.extra.get("swap_id")
if swap_id:
swap = await get_submarine_swap(swap_id)
if swap:
await update_swap_status(swap_id, "complete")
async def check_for_auto_swap(payment: Payment) -> None:
auto_swap = await get_auto_reverse_submarine_swap_by_wallet(payment.wallet_id)
if auto_swap:
wallet = await get_wallet(payment.wallet_id)
if wallet:
reserve = fee_reserve(wallet.balance_msat) / 1000
balance = wallet.balance_msat / 1000
amount = balance - auto_swap.balance - reserve
if amount >= auto_swap.amount:
client = create_boltz_client()
claim_privkey_wif, preimage_hex, swap = client.create_reverse_swap(
amount=int(amount)
)
new_swap = await create_reverse_submarine_swap(
CreateReverseSubmarineSwap(
wallet=auto_swap.wallet,
amount=int(amount),
instant_settlement=auto_swap.instant_settlement,
onchain_address=auto_swap.onchain_address,
),
claim_privkey_wif,
preimage_hex,
swap,
)
await execute_reverse_swap(client, new_swap)
logger.info(
f"Boltz: auto reverse swap created with amount: {amount}, boltz_id: {new_swap.boltz_id}"
)
"""
testcases for boltz startup
A. normal swaps
1. test: create -> kill -> start -> startup invoice listeners -> pay onchain funds -> should complete
2. test: create -> kill -> pay onchain funds -> mine block -> start -> startup check -> should complete
3. test: create -> kill -> mine blocks and hit timeout -> start -> should go timeout/failed
4. test: create -> kill -> pay to less onchain funds -> mine blocks hit timeout -> start lnbits -> should be refunded
B. reverse swaps
1. test: create instant -> kill -> boltz does lockup -> not confirmed -> start lnbits -> should claim/complete
2. test: create -> kill -> boltz does lockup -> not confirmed -> start lnbits -> mine blocks -> should claim/complete
3. test: create -> kill -> boltz does lockup -> confirmed -> start lnbits -> should claim/complete
"""
async def check_for_pending_swaps():
try:
swaps = await get_all_pending_submarine_swaps()
reverse_swaps = await get_all_pending_reverse_submarine_swaps()
if len(swaps) > 0 or len(reverse_swaps) > 0:
logger.debug(f"Boltz - startup swap check")
except:
logger.error(
f"Boltz - startup swap check, database is not created yet, do nothing"
)
return
logger.info(
f"Boltz - lightning invoice is paid, normal swap completed. swap_id: {swap_id}"
)
await update_swap_status(swap_id, "complete")
client = create_boltz_client()
if len(swaps) > 0:
logger.debug(f"Boltz - {len(swaps)} pending swaps")
for swap in swaps:
await check_swap(swap, client)
if len(reverse_swaps) > 0:
logger.debug(f"Boltz - {len(reverse_swaps)} pending reverse swaps")
for reverse_swap in reverse_swaps:
await check_reverse_swap(reverse_swap, client)
async def check_swap(swap: SubmarineSwap, client):
try:
payment_status = await check_transaction_status(swap.wallet, swap.payment_hash)
if payment_status.paid:
logger.debug(f"Boltz - swap: {swap.boltz_id} got paid while offline.")
await update_swap_status(swap.id, "complete")
else:
try:
_ = client.swap_status(swap.id)
except:
txs = client.mempool.get_txs_from_address(swap.address)
if len(txs) == 0:
await update_swap_status(swap.id, "timeout")
else:
await client.refund_swap(
privkey_wif=swap.refund_privkey,
lockup_address=swap.address,
receive_address=swap.refund_address,
redeem_script_hex=swap.redeem_script,
timeout_block_height=swap.timeout_block_height,
)
await update_swap_status(swap.id, "refunded")
except BoltzNotFoundException as exc:
logger.debug(f"Boltz - swap: {swap.boltz_id} does not exist.")
await update_swap_status(swap.id, "failed")
except MempoolBlockHeightException as exc:
logger.debug(
f"Boltz - tried to refund swap: {swap.id}, but has not reached the timeout."
)
except Exception as exc:
logger.error(f"Boltz - unhandled exception, swap: {swap.id} - {str(exc)}")
async def check_reverse_swap(reverse_swap: ReverseSubmarineSwap, client):
try:
_ = client.swap_status(reverse_swap.boltz_id)
await client.claim_reverse_swap(
lockup_address=reverse_swap.lockup_address,
receive_address=reverse_swap.onchain_address,
privkey_wif=reverse_swap.claim_privkey,
preimage_hex=reverse_swap.preimage,
redeem_script_hex=reverse_swap.redeem_script,
zeroconf=reverse_swap.instant_settlement,
)
await update_swap_status(reverse_swap.id, "complete")
except BoltzSwapStatusException as exc:
logger.debug(f"Boltz - swap_status: {str(exc)}")
await update_swap_status(reverse_swap.id, "failed")
# should only happen while development when regtest is reset
except BoltzNotFoundException as exc:
logger.debug(f"Boltz - reverse swap: {reverse_swap.boltz_id} does not exist.")
await update_swap_status(reverse_swap.id, "failed")
except Exception as exc:
logger.error(
f"Boltz - unhandled exception, reverse swap: {reverse_swap.id} - {str(exc)}"
)
@@ -1,236 +1,93 @@
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="About Boltz"
:content-inset-level="0.5"
>
<q-card>
<q-card>
<q-card-section>
<img src="https://boltz.exchange/static/media/Shape.6c1a92b3.svg" alt="" />
<img
src="https://boltz.exchange/static/media/Boltz.02fb7acb.svg"
style="padding: 5px 9px"
alt=""
/>
<p><b>NON CUSTODIAL atomic swap service</b></p>
<h5 class="text-subtitle1 q-my-none">
Providing trustless and account-free swap services since 2018. Move IN and
OUT of the lightning network and remain in control of your bitcoin, at all
time.
</h5>
<p>
Link:
<a target="_blank" href="https://boltz.exchange"
>https://boltz.exchange
</a>
<br />
README:
<a
target="_blank"
href="https://github.com/lnbits/lnbits-legend/tree/main/lnbits/extensions/boltz"
>read more</a
>
</p>
<p>
<small
>Extension created by,
<a target="_blank" href="https://github.com/dni">dni</a></small
>
</p>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<h3 class="text-subtitle1 q-my-none">
<b>Fee Information</b>
</h3>
<span>
Every swap consists of 2 onchain transactions, lockup and claim / refund,
routing fees and a Boltz fee of <b>0.5%</b>.
</span>
</q-card-section>
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="Fee example: Lightning -> Onchain"
:content-inset-level="0.5"
>
<q-card-section>
<img
src="https://boltz.exchange/static/media/Shape.6c1a92b3.svg"
alt=""
/>
<img
src="https://boltz.exchange/static/media/Boltz.02fb7acb.svg"
style="padding: 5px 9px"
alt=""
/>
<h5 class="text-subtitle1 q-my-none">
Boltz.exchange: Do onchain to offchain and vice-versa swaps
</h5>
You want to swap out 100.000 sats, Lightning to Onchain:
<ul style="padding-left: 12px">
<li>Onchain lockup tx fee: ~500 sats</li>
<li>Onchain claim tx fee: 1000 sats (hardcoded)</li>
<li>Routing fees (paid by you): unknown</li>
<li>Boltz fees: 500 sats</li>
<li>Fees total: 2000 sats + routing fees</li>
<li>You receive: 98.000 sats</li>
</ul>
<p>
Submarine and Reverse Submarine Swaps on LNbits via boltz.exchange
API<br />
</p>
<p>
Link :
<a target="_blank" href="https://boltz.exchange"
>https://boltz.exchange
</a>
</p>
<p>
<a
target="_blank"
href="https://github.com/lnbits/lnbits-legend/tree/main/lnbits/extensions/boltz"
>More details</a
>
</p>
<p>
<small
>Created by,
<a target="_blank" href="https://github.com/dni">dni</a></small
>
onchain_amount_received = amount - (amount * boltz_fee / 100) -
lockup_fee - claim_fee
</p>
<p>98.000 = 100.000 - 500 - 500 - 1000</p>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="API info"
:content-inset-level="0.5"
>
<q-expansion-item group="api" dense expand-separator label="GET swap/reverse">
<q-card>
<q-card-section>
<code
><span class="text-light-blue">GET</span>
/boltz/api/v1/swap/reverse</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>JSON list of reverse submarine swaps</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ root_url }}/boltz/api/v1/swap/reverse -H "X-Api-Key:
{{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="POST swap/reverse"
group="extras"
icon="swap_vertical_circle"
label="Fee example: Onchain -> Lightning"
:content-inset-level="0.5"
>
<q-card>
<q-card-section>
<code
><span class="text-light-blue">POST</span>
/boltz/api/v1/swap/reverse</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code
>{"wallet": &lt;string&gt;, "onchain_address": &lt;string&gt;,
"amount": &lt;integer&gt;, "instant_settlement":
&lt;boolean&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>JSON create a reverse-submarine swaps</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ root_url }}/boltz/api/v1/swap/reverse -H "X-Api-Key:
{{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
<q-card-section>
You want to swap in 100.000 sats, Onchain to Lightning:
<ul style="padding-left: 12px">
<li>Onchain lockup tx fee: whatever you choose when paying</li>
<li>Onchain claim tx fee: ~500 sats</li>
<li>Routing fees (paid by boltz): unknown</li>
<li>Boltz fees: 500 sats (0.5%)</li>
<li>Fees total: 1000 sats + lockup_fee</li>
<li>You pay onchain: 101.000 sats + lockup_fee</li>
<li>You receive lightning: 100.000 sats</li>
</ul>
<p>
onchain_payment + lockup_fee = amount + (amount * boltz_fee / 100) +
claim_fee + lockup_fee
</p>
<p>101.000 + lockup_fee = 100.000 + 500 + 500 + lockup_fee</p>
</q-card-section>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="GET swap">
<q-card>
<q-card-section>
<code><span class="text-light-blue">GET</span> /boltz/api/v1/swap</code>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>JSON list of submarine swaps</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ root_url }}/boltz/api/v1/swap -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="POST swap">
<q-card>
<q-card-section>
<code
><span class="text-light-blue">POST</span> /boltz/api/v1/swap</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
<code
>{"wallet": &lt;string&gt;, "refund_address": &lt;string&gt;,
"amount": &lt;integer&gt;}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>JSON create a submarine swaps</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ root_url }}/boltz/api/v1/swap -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="GET swap/refund">
<q-card>
<q-card-section>
<code
><span class="text-light-blue">POST</span>
/boltz/api/v1/swap/refund/{swap_id}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>JSON submarine swap</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ root_url }}/boltz/api/v1/swap/refund/{swap_id} -H
"X-Api-Key: {{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="GET swap/status">
<q-card>
<q-card-section>
<code
><span class="text-light-blue">POST</span>
/boltz/api/v1/swap/status/{swap_id}</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (text/plain)
</h5>
<code>swap status</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ root_url }}/boltz/api/v1/swap/status/{swap_id} -H
"X-Api-Key: {{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="GET swap/check">
<q-card>
<q-card-section>
<code
><span class="text-light-blue">GET</span>
/boltz/api/v1/swap/check</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>JSON pending swaps</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ root_url }}/boltz/api/v1/swap/check -H "X-Api-Key: {{
user.wallets[0].adminkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="GET boltz-config">
<q-card>
<q-card-section>
<code
><span class="text-light-blue">GET</span>
/boltz/api/v1/swap/boltz</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (text/plain)
</h5>
<code>JSON boltz config</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ root_url }}/boltz/api/v1/swap/boltz -H "X-Api-Key: {{
user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="GET mempool-url">
<q-card>
<q-card-section>
<code
><span class="text-light-blue">GET</span>
/boltz/api/v1/swap/mempool</code
>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (text/plain)
</h5>
<code>mempool url</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ root_url }}/boltz/api/v1/swap/mempool -H "X-Api-Key:
{{ user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item>
</q-card>
@@ -0,0 +1,83 @@
<q-dialog v-model="autoReverseSubmarineSwapDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendAutoReverseSubmarineSwapFormData" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="autoReverseSubmarineSwapDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
:disable="autoReverseSubmarineSwapDialog.data.id ? true : false"
>
</q-select>
<q-input
filled
dense
emit-value
label="Balance to kept + fee_reserve"
v-model="autoReverseSubmarineSwapDialog.data.balance"
type="number"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
mininum balance kept in wallet after a swap + the fee_reserve
</q-tooltip>
</q-input>
<q-input
filled
dense
emit-value
:label="amountLabel()"
v-model.trim="autoReverseSubmarineSwapDialog.data.amount"
type="number"
></q-input>
<div class="row">
<div class="col">
<q-checkbox
v-model="autoReverseSubmarineSwapDialog.data.instant_settlement"
value="false"
label="Instant settlement"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
Create Onchain TX when transaction is in mempool, but not
confirmed yet.
</q-tooltip>
</q-checkbox>
</div>
</div>
<q-input
filled
dense
emit-value
v-model.trim="autoReverseSubmarineSwapDialog.data.onchain_address"
type="string"
label="Onchain address to receive funds"
></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="autoReverseSubmarineSwapDialog.data.id"
unelevated
color="primary"
type="submit"
label="Update Swap"
></q-btn>
<q-btn
v-else
unelevated
color="primary"
:disable="disableAutoReverseSubmarineSwapDialog()"
type="submit"
label="Create Auto Reverse Swap (Out)"
></q-btn>
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-auto"
@click="resetAutoReverseSubmarineSwapDialog"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
@@ -0,0 +1,54 @@
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Auto Lightning -> Onchain</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportAutoReverseSubmarineSwapCSV"
>Export to CSV</q-btn
>
</div>
</div>
<q-table
dense
flat
:data="autoReverseSubmarineSwaps"
row-key="id"
:columns="autoReverseSubmarineSwapTable.columns"
:pagination.sync="autoReverseSubmarineSwapTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td>
<q-btn
unelevated
dense
size="xs"
icon="delete"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="deleteAutoReverseSwap(props.row.id)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>delete the automatic reverse swap</q-tooltip
>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
@@ -0,0 +1,35 @@
<q-card>
<q-card-section>
<q-btn
label="Onchain -> Lightning"
unelevated
color="primary"
@click="submarineSwapDialog.show = true"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
Send onchain funds offchain (BTC -> LN)
</q-tooltip>
</q-btn>
<q-btn
label="Lightning -> Onchain"
unelevated
color="primary"
@click="reverseSubmarineSwapDialog.show = true"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
Send offchain funds to onchain address (LN -> BTC)
</q-tooltip>
</q-btn>
<q-btn
label="Auto (Lightning -> Onchain)"
unelevated
color="primary"
@click="autoReverseSubmarineSwapDialog.show = true"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
Automatically send offchain funds to onchain address (LN -> BTC) with a
predefined threshold
</q-tooltip>
</q-btn>
</q-card-section>
</q-card>
@@ -0,0 +1,113 @@
<q-dialog v-model="checkSwapDialog.show" maximized position="top">
<q-card v-if="checkSwapDialog.data" class="q-pa-lg lnbits__dialog-card">
<h5>pending swaps</h5>
<q-table
dense
flat
:data="checkSwapDialog.data.swaps"
row-key="id"
:columns="allStatusTable.columns"
:rows-per-page-options="[0]"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td style="width: 10%">
<q-btn
unelevated
dense
size="xs"
icon="cached"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="refundSwap(props.row.swap_id)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>refund swap</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="download"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="downloadRefundFile(props.row.swap_id)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>dowload refund file</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="flip_to_front"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openMempool(props.row.swap_id)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>open tx on mempool.space</q-tooltip
>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
<h5>pending reverse swaps</h5>
<q-table
dense
flat
:data="checkSwapDialog.data.reverse_swaps"
row-key="id"
:columns="allStatusTable.columns"
:rows-per-page-options="[0]"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td style="width: 10%">
<q-btn
unelevated
dense
size="xs"
icon="flip_to_front"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openMempool(props.row.swap_id)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>open tx on mempool.space</q-tooltip
>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
<div class="row q-mt-lg q-gutter-sm">
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
@@ -0,0 +1,31 @@
<q-dialog v-model="qrCodeDialog.show" position="top">
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode
:value="qrCodeDialog.data.bip21"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
<div>
{% raw %}
<b>Bitcoin On-Chain TX</b><br />
<b>Expected amount (sats): </b> {{ qrCodeDialog.data.expected_amount }}
<br />
<b>Expected amount (btc): </b> {{ qrCodeDialog.data.expected_amount_btc }}
<br />
<b>Onchain Address: </b> {{ qrCodeDialog.data.address }} <br />
{% endraw %}
</div>
<div class="row q-mt-lg q-gutter-sm">
<q-btn
outline
color="grey"
@click="copyText(qrCodeDialog.data.address, 'Onchain address copied to clipboard!')"
class="q-ml-sm"
>Copy On-Chain Address</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
@@ -0,0 +1,72 @@
<q-dialog v-model="reverseSubmarineSwapDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendReverseSubmarineSwapFormData" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="reverseSubmarineSwapDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
:disable="reverseSubmarineSwapDialog.data.id ? true : false"
>
</q-select>
<q-input
filled
dense
emit-value
:label="amountLabel()"
v-model.trim="reverseSubmarineSwapDialog.data.amount"
type="number"
></q-input>
<div class="row">
<div class="col">
<q-checkbox
v-model="reverseSubmarineSwapDialog.data.instant_settlement"
value="false"
label="Instant settlement"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
Create Onchain TX when transaction is in mempool, but not
confirmed yet.
</q-tooltip>
</q-checkbox>
</div>
</div>
<q-input
filled
dense
emit-value
v-model.trim="reverseSubmarineSwapDialog.data.onchain_address"
type="string"
label="Onchain address to receive funds"
></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="reverseSubmarineSwapDialog.data.id"
unelevated
color="primary"
type="submit"
label="Update Swap"
></q-btn>
<q-btn
v-else
unelevated
color="primary"
:disable="disableReverseSubmarineSwapDialog()"
type="submit"
label="Create Reverse Swap (OUT)"
></q-btn>
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-auto"
@click="resetReverseSubmarineSwapDialog"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
@@ -0,0 +1,66 @@
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Lightning -> Onchain</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportReverseSubmarineSwapCSV"
>Export to CSV</q-btn
>
</div>
</div>
<q-table
dense
flat
:data="reverseSubmarineSwaps"
row-key="id"
:columns="reverseSubmarineSwapTable.columns"
:pagination.sync="reverseSubmarineSwapTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td style="width: 10%">
<q-btn
unelevated
dense
size="xs"
icon="info"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openStatusDialog(props.row.id, true)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>open swap status info</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="flip_to_front"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openMempool(props.row.id)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>open tx on mempool.space</q-tooltip
>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
@@ -0,0 +1,29 @@
<q-dialog v-model="statusDialog.show" position="top">
<q-card v-if="statusDialog.data" class="q-pa-lg lnbits__dialog-card">
<div>
{% raw %}
<b>Status: </b> {{ statusDialog.data.status }} <br />
<br />
{% endraw %}
</div>
<div class="row q-mt-lg q-gutter-sm">
<q-btn
outline
color="grey"
@click="refundSwap(statusDialog.data.swap_id)"
v-if="!statusDialog.data.reverse"
class="q-ml-sm"
>Refund
</q-btn>
<q-btn
outline
color="grey"
@click="downloadRefundFile(statusDialog.data.swap_id)"
v-if="!statusDialog.data.reverse"
class="q-ml-sm"
>Download refundfile</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
@@ -0,0 +1,58 @@
<q-dialog v-model="submarineSwapDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendSubmarineSwapFormData" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="submarineSwapDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
:disable="submarineSwapDialog.data.id ? true : false"
>
</q-select>
<q-input
filled
dense
emit-value
v-model.trim="submarineSwapDialog.data.amount"
:label="amountLabel()"
type="number"
></q-input>
<q-input
filled
dense
emit-value
v-model.trim="submarineSwapDialog.data.refund_address"
type="string"
label="Onchain address to receive funds if swap fails"
></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="submarineSwapDialog.data.id"
unelevated
color="primary"
type="submit"
label="Update Swap"
></q-btn>
<q-btn
v-else
unelevated
color="primary"
:disable="disableSubmarineSwapDialog()"
type="submit"
label="Create Swap (IN)"
></q-btn>
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-auto"
@click="resetSubmarineSwapDialog"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
@@ -0,0 +1,78 @@
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Onchain -> Lightning</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportSubmarineSwapCSV"
>Export to CSV</q-btn
>
</div>
</div>
<q-table
dense
flat
:data="submarineSwaps"
row-key="id"
:columns="submarineSwapTable.columns"
:pagination.sync="submarineSwapTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td style="width: 10%">
<q-btn
unelevated
dense
size="xs"
icon="visibility"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.id)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>open swap onchain details</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="info"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openStatusDialog(props.row.id)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>open swap status info</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="flip_to_front"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openMempool(props.row.id)"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
>open tx on mempool.space</q-tooltip
>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
+170 -574
View File
@@ -1,531 +1,19 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
<div class="row q-col-gutter-md">
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn
label="Swap (In)"
unelevated
color="primary"
@click="submarineSwapDialog.show = true"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
Send onchain funds offchain (BTC -> LN)
</q-tooltip>
</q-btn>
<q-btn
label="Reverse Swap (Out)"
unelevated
color="primary"
@click="reverseSubmarineSwapDialog.show = true"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
Send offchain funds to onchain address (LN -> BTC)
</q-tooltip>
</q-btn>
<q-btn
label="Check Swaps"
icon="cached"
unelevated
color="primary"
@click="checkSwaps"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
Check all pending swaps if they can be refunded.
</q-tooltip>
</q-btn>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Swaps (In)</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportSubmarineSwapCSV"
>Export to CSV</q-btn
>
</div>
</div>
<q-table
dense
flat
:data="submarineSwaps"
row-key="id"
:columns="submarineSwapTable.columns"
:pagination.sync="submarineSwapTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td style="width: 10%">
<q-btn
unelevated
dense
size="xs"
icon="visibility"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.id)"
>
<q-tooltip
class="bg-grey-8"
anchor="bottom left"
self="top left"
>open swap onchain details</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="info"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openStatusDialog(props.row.id)"
>
<q-tooltip
class="bg-grey-8"
anchor="bottom left"
self="top left"
>open swap status info</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="flip_to_front"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openMempool(props.row.id)"
>
<q-tooltip
class="bg-grey-8"
anchor="bottom left"
self="top left"
>open tx on mempool.space</q-tooltip
>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-md">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Reverse Swaps (Out)</h5>
</div>
<div class="col-auto">
<q-btn flat color="grey" @click="exportReverseSubmarineSwapCSV"
>Export to CSV</q-btn
>
</div>
</div>
<q-table
dense
flat
:data="reverseSubmarineSwaps"
row-key="id"
:columns="reverseSubmarineSwapTable.columns"
:pagination.sync="reverseSubmarineSwapTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td style="width: 10%">
<q-btn
unelevated
dense
size="xs"
icon="info"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openStatusDialog(props.row.id, true)"
>
<q-tooltip
class="bg-grey-8"
anchor="bottom left"
self="top left"
>open swap status info</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="flip_to_front"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openMempool(props.row.id)"
>
<q-tooltip
class="bg-grey-8"
anchor="bottom left"
self="top left"
>open tx on mempool.space</q-tooltip
>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
</q-card-section>
</q-card>
<div class="col-12 col-md-8 q-gutter-y-md">
{% include "boltz/_buttons.html" %} {% include
"boltz/_submarineSwapList.html" %} {% include
"boltz/_reverseSubmarineSwapList.html" %} {% include
"boltz/_autoReverseSwapList.html" %}
</div>
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">{{SITE_TITLE}} Boltz extension</h6>
</q-card-section>
<q-card-section class="q-pa-none">
<q-separator></q-separator>
<q-list> {% include "boltz/_api_docs.html" %} </q-list>
</q-card-section>
</q-card>
<div class="col-12 col-md-4 q-gutter-y-md">
{% include "boltz/_api_docs.html" %}
</div>
<q-dialog v-model="submarineSwapDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendSubmarineSwapFormData" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="submarineSwapDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
:disable="submarineSwapDialog.data.id ? true : false"
>
</q-select>
<q-input
filled
dense
emit-value
v-model.trim="submarineSwapDialog.data.amount"
:label="amountLabel()"
type="number"
></q-input>
<q-input
filled
dense
emit-value
v-model.trim="submarineSwapDialog.data.refund_address"
type="string"
label="Onchain address to receive funds if swap fails"
></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="submarineSwapDialog.data.id"
unelevated
color="primary"
type="submit"
label="Update Swap"
></q-btn>
<q-btn
v-else
unelevated
color="primary"
:disable="disableSubmarineSwapDialog()"
type="submit"
label="Create Swap (IN)"
></q-btn>
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-auto"
@click="resetSubmarineSwapDialog"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog v-model="reverseSubmarineSwapDialog.show" position="top">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendReverseSubmarineSwapFormData" class="q-gutter-md">
<q-select
filled
dense
emit-value
v-model="reverseSubmarineSwapDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
:disable="reverseSubmarineSwapDialog.data.id ? true : false"
>
</q-select>
<q-input
filled
dense
emit-value
:label="amountLabel()"
v-model.trim="reverseSubmarineSwapDialog.data.amount"
type="number"
></q-input>
<div class="row">
<div class="col">
<q-checkbox
v-model="reverseSubmarineSwapDialog.data.instant_settlement"
value="false"
label="Instant settlement"
>
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
Create Onchain TX when transaction is in mempool, but not
confirmed yet.
</q-tooltip>
</q-checkbox>
</div>
</div>
<q-input
filled
dense
emit-value
v-model.trim="reverseSubmarineSwapDialog.data.onchain_address"
type="string"
label="Onchain address to receive funds"
></q-input>
<div class="row q-mt-lg">
<q-btn
v-if="reverseSubmarineSwapDialog.data.id"
unelevated
color="primary"
type="submit"
label="Update Swap"
></q-btn>
<q-btn
v-else
unelevated
color="primary"
:disable="disableReverseSubmarineSwapDialog()"
type="submit"
label="Create Reverse Swap (OUT)"
></q-btn>
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-auto"
@click="resetReverseSubmarineSwapDialog"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
</q-dialog>
<q-dialog v-model="qrCodeDialog.show" position="top">
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode
:value="qrCodeDialog.data.bip21"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive>
<div>
{% raw %}
<b>Bitcoin On-Chain TX</b><br />
<b>Expected amount (sats): </b> {{ qrCodeDialog.data.expected_amount }}
<br />
<b>Expected amount (btc): </b> {{ qrCodeDialog.data.expected_amount_btc
}} <br />
<b>Onchain Address: </b> {{ qrCodeDialog.data.address }} <br />
{% endraw %}
</div>
<div class="row q-mt-lg q-gutter-sm">
<q-btn
outline
color="grey"
@click="copyText(qrCodeDialog.data.address, 'Onchain address copied to clipboard!')"
class="q-ml-sm"
>Copy On-Chain Address</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
<q-dialog v-model="statusDialog.show" position="top">
<q-card v-if="statusDialog.data" class="q-pa-lg lnbits__dialog-card">
<div>
{% raw %}
<b>Wallet: </b> {{ statusDialog.data.wallet }} <br />
<b>Boltz Status: </b> {{ statusDialog.data.boltz }} <br />
<b>Mempool Status: </b> {{ statusDialog.data.mempool }} <br />
<b>Blockheight timeout: </b> {{ statusDialog.data.timeout_block_height
}} <br />
{% endraw %}
</div>
<div class="row q-mt-lg q-gutter-sm">
<q-btn
outline
color="grey"
@click="refundSwap(statusDialog.data.swap_id)"
v-if="!statusDialog.data.reverse"
class="q-ml-sm"
>Refund
</q-btn>
<q-btn
outline
color="grey"
@click="downloadRefundFile(statusDialog.data.swap_id)"
v-if="!statusDialog.data.reverse"
class="q-ml-sm"
>Download refundfile</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
<q-dialog v-model="allStatusDialog.show" maximized position="top">
<q-card v-if="allStatusDialog.data" class="q-pa-lg lnbits__dialog-card">
<h5>pending swaps</h5>
<q-table
dense
flat
:data="allStatusDialog.data.swaps"
row-key="id"
:columns="allStatusTable.columns"
:rows-per-page-options="[0]"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td style="width: 10%">
<q-btn
unelevated
dense
size="xs"
icon="cached"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="refundSwap(props.row.swap_id)"
>
<q-tooltip
class="bg-grey-8"
anchor="bottom left"
self="top left"
>refund swap</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="download"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="downloadRefundFile(props.row.swap_id)"
>
<q-tooltip
class="bg-grey-8"
anchor="bottom left"
self="top left"
>dowload refund file</q-tooltip
>
</q-btn>
<q-btn
unelevated
dense
size="xs"
icon="flip_to_front"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openMempool(props.row.swap_id)"
>
<q-tooltip
class="bg-grey-8"
anchor="bottom left"
self="top left"
>open tx on mempool.space</q-tooltip
>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
<h5>pending reverse swaps</h5>
<q-table
dense
flat
:data="allStatusDialog.data.reverse_swaps"
row-key="id"
:columns="allStatusTable.columns"
:rows-per-page-options="[0]"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">
{{ col.label }}
</q-th>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td style="width: 10%">
<q-btn
unelevated
dense
size="xs"
icon="flip_to_front"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openMempool(props.row.swap_id)"
>
<q-tooltip
class="bg-grey-8"
anchor="bottom left"
self="top left"
>open tx on mempool.space</q-tooltip
>
</q-btn>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ col.value }}
</q-td>
</q-tr>
</template>
{% endraw %}
</q-table>
<div class="row q-mt-lg q-gutter-sm">
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div>
</q-card>
</q-dialog>
{% include "boltz/_submarineSwapDialog.html" %} {% include
"boltz/_reverseSubmarineSwapDialog.html" %} {% include
"boltz/_autoReverseSwapDialog.html" %} {% include "boltz/_qrDialog.html" %} {%
include "boltz/_statusDialog.html" %}
</div>
{% endblock %} {% block scripts %} {{ window_vars(user) }}
<script>
@@ -539,6 +27,7 @@
boltzConfig: {},
submarineSwaps: [],
reverseSubmarineSwaps: [],
autoReverseSubmarineSwaps: [],
statuses: [],
submarineSwapDialog: {
show: false,
@@ -550,6 +39,13 @@
instant_settlement: true
}
},
autoReverseSubmarineSwapDialog: {
show: false,
data: {
balance: 100,
instant_settlement: true
}
},
qrCodeDialog: {
show: false,
data: {}
@@ -558,40 +54,36 @@
show: false,
data: {}
},
allStatusDialog: {
show: false,
data: {}
},
allStatusTable: {
columns: [
{
name: 'swap_id',
align: 'left',
label: 'swap_id',
label: 'Swap ID',
field: 'swap_id'
},
{
name: 'status',
align: 'left',
label: 'status',
label: 'Status',
field: 'message'
},
{
name: 'boltz',
align: 'left',
label: 'boltz',
label: 'Boltz',
field: 'boltz'
},
{
name: 'mempool',
align: 'left',
label: 'mempool',
label: 'Mempool',
field: 'mempool'
},
{
name: 'timeout_block_height',
align: 'left',
label: 'block height',
label: 'Timeout block height',
field: 'timeout_block_height'
}
],
@@ -599,12 +91,60 @@
rowsPerPage: 10
}
},
autoReverseSubmarineSwapTable: {
columns: [
{
name: 'time',
align: 'left',
label: 'Time',
field: 'time',
sortable: true,
format: function (val, row) {
return new Date(val * 1000).toUTCString()
}
},
{
name: 'wallet',
align: 'left',
label: 'Wallet',
field: data => {
let wallet = _.findWhere(this.g.user.wallets, {
id: data.wallet
})
if (wallet) {
return wallet.name
}
}
},
{
name: 'balance',
align: 'left',
label: 'Balance',
field: 'balance'
},
{
name: 'amount',
align: 'left',
label: 'Amount',
field: 'amount'
},
{
name: 'onchain_address',
align: 'left',
label: 'Onchain address',
field: 'onchain_address'
}
],
pagination: {
rowsPerPage: 10
}
},
reverseSubmarineSwapTable: {
columns: [
{
name: 'time',
align: 'left',
label: 'time',
label: 'Time',
field: 'time',
sortable: true,
format: function (val, row) {
@@ -614,7 +154,7 @@
{
name: 'wallet',
align: 'left',
label: 'wallet',
label: 'Wallet',
field: data => {
let wallet = _.findWhere(this.g.user.wallets, {
id: data.wallet
@@ -627,25 +167,25 @@
{
name: 'status',
align: 'left',
label: 'status',
label: 'Status',
field: 'status'
},
{
name: 'boltz_id',
align: 'left',
label: 'boltz id',
label: 'Boltz ID',
field: 'boltz_id'
},
{
name: 'onchain_amount',
align: 'left',
label: 'onchain amount',
label: 'Onchain amount',
field: 'onchain_amount'
},
{
name: 'timeout_block_height',
align: 'left',
label: 'timeout block height',
label: 'Timeout block height',
field: 'timeout_block_height'
}
],
@@ -658,7 +198,7 @@
{
name: 'time',
align: 'left',
label: 'time',
label: 'Time',
field: 'time',
sortable: true,
format: function (val, row) {
@@ -668,7 +208,7 @@
{
name: 'wallet',
align: 'left',
label: 'wallet',
label: 'Wallet',
field: data => {
let wallet = _.findWhere(this.g.user.wallets, {
id: data.wallet
@@ -681,25 +221,25 @@
{
name: 'status',
align: 'left',
label: 'status',
label: 'Status',
field: 'status'
},
{
name: 'boltz_id',
align: 'left',
label: 'boltz id',
label: 'Boltz ID',
field: 'boltz_id'
},
{
name: 'expected_amount',
align: 'left',
label: 'expected amount',
label: 'Expected amount',
field: 'expected_amount'
},
{
name: 'timeout_block_height',
align: 'left',
label: 'timeout block height',
label: 'Timeout block height',
field: 'timeout_block_height'
}
],
@@ -711,11 +251,10 @@
},
methods: {
getLimits() {
const cfg = this.boltzConfig.data
if (cfg) {
if (this.boltzConfig) {
return {
min: cfg.limits.minimal,
max: cfg.limits.maximal
min: this.boltzConfig.minimal,
max: this.boltzConfig.maximal
}
}
return {
@@ -753,6 +292,19 @@
data.amount > limits.max
)
},
disableAutoReverseSubmarineSwapDialog() {
const data = this.autoReverseSubmarineSwapDialog.data
let limits = this.getLimits()
return (
data.onchain_address == null ||
data.onchain_address.search(
/^(bcrt1|bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/
) !== 0 ||
data.wallet == null ||
data.amount < limits.min ||
data.amount > limits.max
)
},
downloadRefundFile(swapId) {
let swap = _.findWhere(this.submarineSwaps, {id: swapId})
let json = {
@@ -816,6 +368,7 @@
swap_id: swap_id,
wallet: res.data.wallet,
boltz: res.data.boltz,
status: res.data.status,
mempool: res.data.mempool,
timeout_block_height: res.data.timeout_block_height,
date: new Date().toUTCString()
@@ -847,12 +400,6 @@
data: {}
}
},
resetAllStatusDialog() {
this.allStatusDialog = {
show: false,
data: {}
}
},
resetSubmarineSwapDialog() {
this.submarineSwapDialog = {
show: false,
@@ -865,6 +412,12 @@
data: {}
}
},
resetAutoReverseSubmarineSwapDialog() {
this.autoReverseSubmarineSwapDialog = {
show: false,
data: {}
}
},
sendReverseSubmarineSwapFormData() {
let wallet = _.findWhere(this.g.user.wallets, {
id: this.reverseSubmarineSwapDialog.data.wallet
@@ -872,6 +425,13 @@
let data = this.reverseSubmarineSwapDialog.data
this.createReverseSubmarineSwap(wallet, data)
},
sendAutoReverseSubmarineSwapFormData() {
let wallet = _.findWhere(this.g.user.wallets, {
id: this.autoReverseSubmarineSwapDialog.data.wallet
})
let data = this.autoReverseSubmarineSwapDialog.data
this.createAutoReverseSubmarineSwap(wallet, data)
},
sendSubmarineSwapFormData() {
let wallet = _.findWhere(this.g.user.wallets, {
id: this.submarineSwapDialog.data.wallet
@@ -891,6 +451,12 @@
this.reverseSubmarineSwaps
)
},
exportAutoReverseSubmarineSwapCSV() {
LNbits.utils.exportCSV(
this.autoReverseSubmarineSwapTable.columns,
this.autoReverseSubmarineSwaps
)
},
createSubmarineSwap(wallet, data) {
LNbits.api
.request(
@@ -924,6 +490,40 @@
LNbits.utils.notifyApiError(error)
})
},
createAutoReverseSubmarineSwap(wallet, data) {
LNbits.api
.request(
'POST',
'/boltz/api/v1/swap/reverse/auto',
this.g.user.wallets[0].adminkey,
data
)
.then(res => {
this.autoReverseSubmarineSwaps.unshift(res.data)
this.resetAutoReverseSubmarineSwapDialog()
})
.catch(error => {
LNbits.utils.notifyApiError(error)
})
},
deleteAutoReverseSwap(swap_id) {
LNbits.api
.request(
'DELETE',
'/boltz/api/v1/swap/reverse/auto/' + swap_id,
this.g.user.wallets[0].adminkey
)
.then(res => {
let i = this.autoReverseSubmarineSwaps.findIndex(
swap => swap.id === swap_id
)
this.autoReverseSubmarineSwaps.splice(i, 1)
})
.catch(error => {
console.log(error)
LNbits.utils.notifyApiError(error)
})
},
getSubmarineSwap() {
LNbits.api
.request(
@@ -952,6 +552,20 @@
LNbits.utils.notifyApiError(error)
})
},
getAutoReverseSubmarineSwap() {
LNbits.api
.request(
'GET',
'/boltz/api/v1/swap/reverse/auto?all_wallets=true',
this.g.user.wallets[0].inkey
)
.then(response => {
this.autoReverseSubmarineSwaps = response.data
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
getMempool() {
LNbits.api
.request('GET', '/boltz/api/v1/swap/mempool')
@@ -967,26 +581,7 @@
LNbits.api
.request('GET', '/boltz/api/v1/swap/boltz')
.then(res => {
this.boltzConfig = res
})
.catch(error => {
console.log('error', error)
LNbits.utils.notifyApiError(error)
})
},
checkSwaps() {
LNbits.api
.request(
'POST',
'/boltz/api/v1/swap/check',
this.g.user.wallets[0].adminkey
)
.then(res => {
this.allStatusDialog.data = {
swaps: _.where(res.data, {reverse: false}),
reverse_swaps: _.where(res.data, {reverse: true})
}
this.allStatusDialog.show = true
this.boltzConfig = res.data
})
.catch(error => {
console.log('error', error)
@@ -999,6 +594,7 @@
this.getBoltzConfig()
this.getSubmarineSwap()
this.getReverseSubmarineSwap()
this.getAutoReverseSubmarineSwap()
}
})
</script>
+64 -21
View File
@@ -1,10 +1,25 @@
import asyncio
import calendar
import datetime
from typing import Awaitable
import httpx
from loguru import logger
from boltz_client.boltz import BoltzClient, BoltzConfig
from lnbits.core.services import fee_reserve, get_wallet
from lnbits.core.services import fee_reserve, get_wallet, pay_invoice
from lnbits.settings import settings
from .models import ReverseSubmarineSwap
def create_boltz_client() -> BoltzClient:
config = BoltzConfig(
network=settings.boltz_network,
api_url=settings.boltz_url,
mempool_url=f"{settings.boltz_mempool_space_url}/api",
mempool_ws_url=f"{settings.boltz_mempool_space_url_ws}/api/v1/ws",
referral_id="lnbits",
)
return BoltzClient(config)
async def check_balance(data) -> bool:
@@ -23,22 +38,50 @@ def get_timestamp():
return calendar.timegm(date.utctimetuple())
def req_wrap(funcname, *args, **kwargs):
try:
async def execute_reverse_swap(client, swap: ReverseSubmarineSwap):
# claim_task is watching onchain address for the lockup transaction to arrive / confirm
# and if the lockup is there, claim the onchain revealing preimage for hold invoice
claim_task = asyncio.create_task(
client.claim_reverse_swap(
privkey_wif=swap.claim_privkey,
preimage_hex=swap.preimage,
lockup_address=swap.lockup_address,
receive_address=swap.onchain_address,
redeem_script_hex=swap.redeem_script,
)
)
# pay_task is paying the hold invoice which gets held until you reveal your preimage when claiming your onchain funds
pay_task = pay_invoice_and_update_status(
swap.id,
claim_task,
pay_invoice(
wallet_id=swap.wallet,
payment_request=swap.invoice,
description=f"reverse swap for {swap.onchain_amount} sats on boltz.exchange",
extra={"tag": "boltz", "swap_id": swap.id, "reverse": True},
),
)
# they need to run be concurrently, because else pay_task will lock the eventloop and claim_task will not be executed.
# the lockup transaction can only happen after you pay the invoice, which cannot be redeemed immediatly -> hold invoice
# after getting the lockup transaction, you can claim the onchain funds revealing the preimage for boltz to redeem the hold invoice
asyncio.gather(claim_task, pay_task)
def pay_invoice_and_update_status(
swap_id: str, wstask: asyncio.Task, awaitable: Awaitable
) -> asyncio.Task:
async def _pay_invoice(awaitable):
from .crud import update_swap_status
try:
func = getattr(httpx, funcname)
except AttributeError:
logger.error('httpx function not found "%s"' % funcname)
else:
res = func(*args, timeout=30, **kwargs)
res.raise_for_status()
return res
except httpx.RequestError as exc:
msg = f"Unreachable: {exc.request.url!r}."
logger.error(msg)
raise
except httpx.HTTPStatusError as exc:
msg = f"HTTP Status Error: {exc.response.status_code} while requesting {exc.request.url!r}."
logger.error(msg)
logger.error(exc.response.json()["error"])
raise
awaited = await awaitable
await update_swap_status(swap_id, "complete")
return awaited
except asyncio.exceptions.CancelledError:
"""lnbits process was exited, do nothing and handle it in startup script"""
except:
wstask.cancel()
await update_swap_status(swap_id, "failed")
return asyncio.create_task(_pay_invoice(awaitable))
+2 -4
View File
@@ -1,11 +1,10 @@
from urllib.parse import urlparse
from fastapi import Request
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse
from lnbits.core.models import Payment, User
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from . import boltz_ext, boltz_renderer
@@ -16,7 +15,6 @@ templates = Jinja2Templates(directory="templates")
@boltz_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
root_url = urlparse(str(request.url)).netloc
wallet_ids = [wallet.id for wallet in user.wallets]
return boltz_renderer().TemplateResponse(
"boltz/index.html",
{"request": request, "user": user.dict(), "root_url": root_url},
+135 -145
View File
@@ -1,34 +1,23 @@
from datetime import datetime
from http import HTTPStatus
from typing import List
import httpx
from fastapi import status
from fastapi.encoders import jsonable_encoder
from fastapi.param_functions import Body
from fastapi.params import Depends, Query
from loguru import logger
from pydantic import BaseModel
from fastapi import Depends, Query, status
from starlette.exceptions import HTTPException
from starlette.requests import Request
from lnbits.core.crud import get_user
from lnbits.core.services import create_invoice
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.helpers import urlsafe_short_hash
from lnbits.settings import settings
from . import boltz_ext
from .boltz import (
create_refund_tx,
create_reverse_swap,
create_swap,
get_boltz_pairs,
get_swap_status,
)
from .crud import (
create_auto_reverse_submarine_swap,
create_reverse_submarine_swap,
create_submarine_swap,
get_pending_reverse_submarine_swaps,
get_pending_submarine_swaps,
delete_auto_reverse_submarine_swap,
get_auto_reverse_submarine_swap_by_wallet,
get_auto_reverse_submarine_swaps,
get_reverse_submarine_swap,
get_reverse_submarine_swaps,
get_submarine_swap,
@@ -36,12 +25,14 @@ from .crud import (
update_swap_status,
)
from .models import (
AutoReverseSubmarineSwap,
CreateAutoReverseSubmarineSwap,
CreateReverseSubmarineSwap,
CreateSubmarineSwap,
ReverseSubmarineSwap,
SubmarineSwap,
)
from .utils import check_balance
from .utils import check_balance, create_boltz_client, execute_reverse_swap
@boltz_ext.get(
@@ -76,17 +67,8 @@ async def api_submarineswap(
):
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
for swap in await get_pending_submarine_swaps(wallet_ids):
swap_status = get_swap_status(swap)
if swap_status.hit_timeout:
if not swap_status.has_lockup:
logger.warning(
f"Boltz - swap: {swap.id} hit timeout, but no lockup tx..."
)
await update_swap_status(swap.id, "timeout")
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [swap.dict() for swap in await get_submarine_swaps(wallet_ids)]
@@ -109,35 +91,29 @@ async def api_submarineswap(
},
},
)
async def api_submarineswap_refund(
swap_id: str,
g: WalletTypeInfo = Depends(require_admin_key), # type: ignore
):
if swap_id == None:
async def api_submarineswap_refund(swap_id: str):
if not swap_id:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="swap_id missing"
)
swap = await get_submarine_swap(swap_id)
if swap == None:
if not swap:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="swap does not exist."
)
if swap.status != "pending":
raise HTTPException(
status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail="swap is not pending."
)
try:
await create_refund_tx(swap)
except httpx.RequestError as exc:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Unreachable: {exc.request.url!r}.",
)
except Exception as exc:
raise HTTPException(status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail=str(exc))
client = create_boltz_client()
await client.refund_swap(
privkey_wif=swap.refund_privkey,
lockup_address=swap.address,
receive_address=swap.refund_address,
redeem_script_hex=swap.redeem_script,
timeout_block_height=swap.timeout_block_height,
)
await update_swap_status(swap.id, "refunded")
return swap
@@ -153,37 +129,43 @@ async def api_submarineswap_refund(
""",
response_description="create swap",
response_model=SubmarineSwap,
dependencies=[Depends(require_admin_key)],
responses={
405: {"description": "not allowed method, insufficient balance"},
405: {
"description": "auto reverse swap is active, a swap would immediatly be swapped out again."
},
500: {"description": "boltz error"},
},
)
async def api_submarineswap_create(
data: CreateSubmarineSwap,
wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore
):
try:
swap_data = await create_swap(data)
except httpx.RequestError as exc:
async def api_submarineswap_create(data: CreateSubmarineSwap):
auto_swap = await get_auto_reverse_submarine_swap_by_wallet(data.wallet)
if auto_swap:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Unreachable: {exc.request.url!r}.",
status_code=HTTPStatus.METHOD_NOT_ALLOWED,
detail="auto reverse swap is active, a swap would immediatly be swapped out again.",
)
except Exception as exc:
raise HTTPException(status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail=str(exc))
except httpx.HTTPStatusError as exc:
raise HTTPException(
status_code=exc.response.status_code, detail=exc.response.json()["error"]
)
swap = await create_submarine_swap(swap_data)
return swap.dict()
client = create_boltz_client()
swap_id = urlsafe_short_hash()
payment_hash, payment_request = await create_invoice(
wallet_id=data.wallet,
amount=data.amount,
memo=f"swap of {data.amount} sats on boltz.exchange",
extra={"tag": "boltz", "swap_id": swap_id},
)
refund_privkey_wif, swap = client.create_swap(payment_request)
new_swap = await create_submarine_swap(
data, swap, swap_id, refund_privkey_wif, payment_hash
)
return new_swap.dict() if new_swap else None
# REVERSE SWAP
@boltz_ext.get(
"/api/v1/swap/reverse",
name=f"boltz.get /swap/reverse",
summary="get a list of reverse swaps a swap",
summary="get a list of reverse swaps",
description="""
This endpoint gets a list of reverse swaps.
""",
@@ -192,13 +174,14 @@ async def api_submarineswap_create(
response_model=List[ReverseSubmarineSwap],
)
async def api_reverse_submarineswap(
g: WalletTypeInfo = Depends(get_key_type), # type:ignore
g: WalletTypeInfo = Depends(get_key_type),
all_wallets: bool = Query(False),
):
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
return [swap.dict() for swap in await get_reverse_submarine_swaps(wallet_ids)]
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [swap for swap in await get_reverse_submarine_swaps(wallet_ids)]
@boltz_ext.post(
@@ -211,6 +194,7 @@ async def api_reverse_submarineswap(
""",
response_description="create reverse swap",
response_model=ReverseSubmarineSwap,
dependencies=[Depends(require_admin_key)],
responses={
405: {"description": "not allowed method, insufficient balance"},
500: {"description": "boltz error"},
@@ -218,30 +202,88 @@ async def api_reverse_submarineswap(
)
async def api_reverse_submarineswap_create(
data: CreateReverseSubmarineSwap,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
) -> ReverseSubmarineSwap:
if not await check_balance(data):
raise HTTPException(
status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail="Insufficient balance."
)
client = create_boltz_client()
claim_privkey_wif, preimage_hex, swap = client.create_reverse_swap(
amount=data.amount
)
new_swap = await create_reverse_submarine_swap(
data, claim_privkey_wif, preimage_hex, swap
)
await execute_reverse_swap(client, new_swap)
return new_swap
try:
swap_data, task = await create_reverse_swap(data)
except httpx.RequestError as exc:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Unreachable: {exc.request.url!r}.",
)
except httpx.HTTPStatusError as exc:
raise HTTPException(
status_code=exc.response.status_code, detail=exc.response.json()["error"]
)
except Exception as exc:
raise HTTPException(status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail=str(exc))
swap = await create_reverse_submarine_swap(swap_data)
return swap.dict()
@boltz_ext.get(
"/api/v1/swap/reverse/auto",
name=f"boltz.get /swap/reverse/auto",
summary="get a list of auto reverse swaps",
description="""
This endpoint gets a list of auto reverse swaps.
""",
response_description="list of auto reverse swaps",
dependencies=[Depends(get_key_type)],
response_model=List[AutoReverseSubmarineSwap],
)
async def api_auto_reverse_submarineswap(
g: WalletTypeInfo = Depends(get_key_type),
all_wallets: bool = Query(False),
):
wallet_ids = [g.wallet.id]
if all_wallets:
user = await get_user(g.wallet.user)
wallet_ids = user.wallet_ids if user else []
return [swap.dict() for swap in await get_auto_reverse_submarine_swaps(wallet_ids)]
@boltz_ext.post(
"/api/v1/swap/reverse/auto",
status_code=status.HTTP_201_CREATED,
name=f"boltz.post /swap/reverse/auto",
summary="create a auto reverse submarine swap",
description="""
This endpoint creates a auto reverse submarine swap
""",
response_description="create auto reverse swap",
response_model=AutoReverseSubmarineSwap,
dependencies=[Depends(require_admin_key)],
responses={
405: {
"description": "auto reverse swap is active, only 1 swap per wallet possible."
},
},
)
async def api_auto_reverse_submarineswap_create(data: CreateAutoReverseSubmarineSwap):
auto_swap = await get_auto_reverse_submarine_swap_by_wallet(data.wallet)
if auto_swap:
raise HTTPException(
status_code=HTTPStatus.METHOD_NOT_ALLOWED,
detail="auto reverse swap is active, only 1 swap per wallet possible.",
)
swap = await create_auto_reverse_submarine_swap(data)
return swap.dict() if swap else None
@boltz_ext.delete(
"/api/v1/swap/reverse/auto/{swap_id}",
name=f"boltz.delete /swap/reverse/auto",
summary="delete a auto reverse submarine swap",
description="""
This endpoint deletes a auto reverse submarine swap
""",
response_description="delete auto reverse swap",
dependencies=[Depends(require_admin_key)],
)
async def api_auto_reverse_submarineswap_delete(swap_id: str):
await delete_auto_reverse_submarine_swap(swap_id)
return "OK"
@boltz_ext.post(
@@ -252,65 +294,22 @@ async def api_reverse_submarineswap_create(
This endpoint attempts to get the status of the swap.
""",
response_description="status of swap json",
dependencies=[Depends(require_admin_key)],
responses={
404: {"description": "when swap_id is not found"},
},
)
async def api_swap_status(
swap_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) # type: ignore
):
async def api_swap_status(swap_id: str):
swap = await get_submarine_swap(swap_id) or await get_reverse_submarine_swap(
swap_id
)
if swap == None:
if not swap:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="swap does not exist."
)
try:
status = get_swap_status(swap)
except httpx.RequestError as exc:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Unreachable: {exc.request.url!r}.",
)
except Exception as exc:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
)
return status
@boltz_ext.post(
"/api/v1/swap/check",
name=f"boltz.swap_check",
summary="list all pending swaps",
description="""
This endpoint gives you 2 lists of pending swaps and reverse swaps.
""",
response_description="list of pending swaps",
)
async def api_check_swaps(
g: WalletTypeInfo = Depends(require_admin_key), # type: ignore
all_wallets: bool = Query(False),
):
wallet_ids = [g.wallet.id]
if all_wallets:
wallet_ids = (await get_user(g.wallet.user)).wallet_ids
status = []
try:
for swap in await get_pending_submarine_swaps(wallet_ids):
status.append(get_swap_status(swap))
for reverseswap in await get_pending_reverse_submarine_swaps(wallet_ids):
status.append(get_swap_status(reverseswap))
except httpx.RequestError as exc:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Unreachable: {exc.request.url!r}.",
)
except Exception as exc:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(exc)
)
client = create_boltz_client()
status = client.swap_status(swap.boltz_id)
return status
@@ -325,14 +324,5 @@ async def api_check_swaps(
response_model=dict,
)
async def api_boltz_config():
try:
res = get_boltz_pairs()
except httpx.RequestError as exc:
raise HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
detail=f"Unreachable: {exc.request.url!r}.",
)
except Exception as e:
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
return res["pairs"]["BTC/BTC"]
client = create_boltz_client()
return {"minimal": client.limit_minimal, "maximal": client.limit_maximal}
+1 -1
View File
@@ -1,6 +1,6 @@
import asyncio
from environs import Env # type: ignore
from environs import Env
from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "Cashu",
"short_description": "Ecash mint and wallet",
"icon": "account_balance",
"tile": "/cashu/static/image/cashu.png",
"contributors": ["calle", "vlad", "arcbtc"],
"hidden": false
}
-1
View File
@@ -1,7 +1,6 @@
import os
import random
import time
from binascii import hexlify, unhexlify
from typing import Any, List, Optional, Union
from cashu.core.base import MintKeyset
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+2 -1
View File
@@ -28,6 +28,7 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
if payment.extra and not payment.extra.get("tag") == "cashu":
if payment.extra.get("tag") != "cashu":
return
return
@@ -4,9 +4,24 @@
<p>Create Cashu ecash mints and wallets.</p>
<small
>Created by
<a href="https://github.com/arcbtc" target="_blank">arcbtc</a>,
<a href="https://github.com/motorina0" target="_blank">vlad</a>,
<a href="https://github.com/calle" target="_blank">calle</a>.</small
<a
class="text-secondary"
href="https://github.com/arcbtc"
target="_blank"
>arcbtc</a
>,
<a
class="text-secondary"
href="https://github.com/motorina0"
target="_blank"
>vlad</a
>,
<a
class="text-secondary"
href="https://github.com/calle"
target="_blank"
>calle</a
>.</small
>
</q-card-section>
</q-card>
@@ -4,17 +4,19 @@
<q-card class="q-pa-lg q-mb-xl">
<q-card-section class="q-pa-none">
<center>
<q-icon
name="account_balance"
class="text-grey"
style="font-size: 10rem"
></q-icon>
<h4 class="q-mt-none q-mb-md">{{ mint_name }}</h4>
<q-img
src="/cashu/static/image/cashu.png"
spinner-color="white"
style="max-width: 20%"
></q-img>
<h4 class="q-mt-sm q-mb-md">{{ mint_name }}</h4>
<!-- <a class="text-secondary">Mint URL: {{testfield}} </a> <br /> -->
<a
class="text-secondary"
class="q-my-xl text-white"
style="font-size: 1.5rem"
href="../wallet?mint_id={{ mint_id }}"
>Open wallet</a
>click to open wallet</a
>
</center>
</q-card-section>
@@ -24,7 +26,11 @@
<h5 class="q-my-md">Read the following carefully!</h5>
<p>
This is a
<a href="https://cashu.space/" style="color: white" target="”_blank”"
<a
class="text-secondary"
href="https://cashu.space/"
style="color: white"
target="”_blank”"
>Cashu</a
>
mint. Cashu is an ecash system for Bitcoin.
@@ -53,24 +59,34 @@
</p>
<p>
<strong>This service is in BETA</strong> <br />
We hold no responsibility for people losing access to funds. Use at
your own risk!
Cashu is still experimental and in active development. There are
likely bugs in this implementation so please use this with caution. We
hold no responsibility for people losing access to funds. Use at your
own risk!
</p>
</q-card-section>
</q-card>
</div>
{% endblock %} {% block scripts %}
<script>
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {}
}
})
</script>
{% endblock %}
</div>
{% endblock %} {% block scripts %}
<script>
Vue.component(VueQrcode.name, VueQrcode)
new Vue({
el: '#vue',
mixins: [windowMixin],
data: function () {
return {
testfield: 'asd',
mintURL: {
location: window.location,
base_url: location.protocol + '//' + location.host + location.pathname
}
}
}
})
</script>
{% endblock %}
@@ -159,7 +159,7 @@ page_container %}
size="lg"
color="secondary"
class="q-mr-md cursor-pointer"
@click="recheckInvoice(props.row.hash)"
@click="checkInvoice(props.row.hash)"
>
Check
</q-badge>
@@ -616,10 +616,10 @@ page_container %}
></q-input>
</div>
<div v-else class="text-center q-mb-lg">
<a :href="'lightning:' + invoiceData.bolt11">
<a class="text-secondary" :href="'lightning:' + invoiceData.bolt11">
<q-responsive :ratio="1" class="q-mx-xl">
<qrcode
:value="invoiceData.bolt11"
:value="'lightning:' + invoiceData.bolt11.toUpperCase()"
:options="{width: 340}"
class="rounded-borders"
>
@@ -681,7 +681,7 @@ page_container %}
</div>
<div v-else class="text-center q-mb-lg">
<div class="text-center q-mb-lg">
<!-- <a :href="'cashu:' + sendData.tokensBase64"> -->
<!-- <a class="text-secondary" :href="'cashu:' + sendData.tokensBase64"> -->
<q-responsive :ratio="1" class="q-mx-xl">
<qrcode
:value="disclaimerDialog.base_url + '?mint_id=' + mintId + '&recv_token=' + sendData.tokensBase64"
@@ -1479,15 +1479,15 @@ page_container %}
},
constructOutputs: async function (amounts, secrets) {
const blindedMessages = []
const outputs = []
const rs = []
for (let i = 0; i < amounts.length; i++) {
const {B_, r} = await step1Alice(secrets[i])
blindedMessages.push({amount: amounts[i], B_: B_})
outputs.push({amount: amounts[i], B_: B_})
rs.push(r)
}
return {
blindedMessages,
outputs,
rs
}
},
@@ -1528,57 +1528,17 @@ page_container %}
return proofs.reduce((s, t) => (s += t.amount), 0)
},
deleteProofs: function (proofs) {
// delete proofs from this.proofs
const usedSecrets = proofs.map(p => p.secret)
this.proofs = this.proofs.filter(p => !usedSecrets.includes(p.secret))
this.storeProofs()
return this.proofs
},
//////////// API ///////////
clearAllWorkers: function () {
if (this.invoiceCheckListener) {
clearInterval(this.invoiceCheckListener)
}
if (this.tokensCheckSpendableListener) {
clearInterval(this.tokensCheckSpendableListener)
}
},
invoiceCheckWorker: async function () {
let nInterval = 0
this.clearAllWorkers()
this.invoiceCheckListener = setInterval(async () => {
try {
nInterval += 1
// exit loop after 2m
if (nInterval > 40) {
console.log('### stopping invoice check worker')
this.clearAllWorkers()
}
console.log('### invoiceCheckWorker setInterval', nInterval)
console.log(this.invoiceData)
// this will throw an error if the invoice is pending
await this.recheckInvoice(this.invoiceData.hash, false)
// only without error (invoice paid) will we reach here
console.log('### stopping invoice check worker')
this.clearAllWorkers()
this.invoiceData.bolt11 = ''
this.showInvoiceDetails = false
if (window.navigator.vibrate) navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
message: 'Payment received',
position: 'top',
actions: [
{
icon: 'close',
color: 'white',
handler: () => {}
}
]
})
} catch (error) {
console.log('not paid yet')
}
}, 3000)
},
// MINT
requestMintButton: async function () {
await this.requestMint()
@@ -1586,8 +1546,12 @@ page_container %}
await this.invoiceCheckWorker()
},
// /mint
requestMint: async function () {
// gets an invoice from the mint to get new tokens
/*
gets an invoice from the mint to get new tokens
*/
try {
const {data} = await LNbits.api.request(
'GET',
@@ -1611,24 +1575,32 @@ page_container %}
throw error
}
},
// /mint
mintApi: async function (amounts, payment_hash, verbose = true) {
/*
asks the mint to check whether the invoice with payment_hash has been paid
and requests signing of the attached outputs.
*/
console.log('### promises', payment_hash)
try {
let secrets = await this.generateSecrets(amounts)
let {blindedMessages, rs} = await this.constructOutputs(
amounts,
secrets
)
let {outputs, rs} = await this.constructOutputs(amounts, secrets)
const promises = await LNbits.api.request(
'POST',
`/cashu/api/v1/${this.mintId}/mint?payment_hash=${payment_hash}`,
'',
{
blinded_messages: blindedMessages
outputs
}
)
console.log('### promises data', promises.data)
let proofs = await this.constructProofs(promises.data, secrets, rs)
console.log('### promises data', promises.data.promises)
let proofs = await this.constructProofs(
promises.data.promises,
secrets,
rs
)
return proofs
} catch (error) {
console.error(error)
@@ -1647,7 +1619,19 @@ page_container %}
}
this.proofs = this.proofs.concat(proofs)
this.storeProofs()
// update UI
await this.setInvoicePaid(payment_hash)
tokensBase64 = btoa(JSON.stringify(proofs))
this.historyTokens.push({
status: 'paid',
amount: amount,
date: currentDateStr(),
token: tokensBase64
})
this.storehistoryTokens()
return proofs
} catch (error) {
console.error(error)
@@ -1657,62 +1641,20 @@ page_container %}
throw error
}
},
splitToSend: async function (proofs, amount, invlalidate = false) {
// splits proofs so the user can keep firstProofs, send scndProofs
try {
const spendableProofs = proofs.filter(p => !p.reserved)
if (this.sumProofs(spendableProofs) < amount) {
this.$q.notify({
timeout: 5000,
type: 'warning',
message: 'Balance too low',
position: 'top',
actions: [
{
icon: 'close',
color: 'white',
handler: () => {}
}
]
})
throw Error('balance too low.')
}
let {fristProofs, scndProofs} = await this.split(
spendableProofs,
amount
)
// set scndProofs in this.proofs as reserved
const usedSecrets = proofs.map(p => p.secret)
for (let i = 0; i < this.proofs.length; i++) {
if (usedSecrets.includes(this.proofs[i].secret)) {
this.proofs[i].reserved = true
}
}
if (invlalidate) {
// delete tokens from db
this.proofs = fristProofs
// add new fristProofs, scndProofs to this.proofs
this.storeProofs()
}
return {fristProofs, scndProofs}
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
throw error
}
},
// SPLIT
split: async function (proofs, amount) {
/*
supplies proofs and requests a split from the mint of these
proofs at a specific amount
*/
try {
if (proofs.length == 0) {
throw new Error('no proofs provided.')
}
let {fristProofs, scndProofs} = await this.splitApi(proofs, amount)
// delete proofs from this.proofs
const usedSecrets = proofs.map(p => p.secret)
this.proofs = this.proofs.filter(p => !usedSecrets.includes(p.secret))
this.deleteProofs(proofs)
// add new fristProofs, scndProofs to this.proofs
this.proofs = this.proofs.concat(fristProofs).concat(scndProofs)
this.storeProofs()
@@ -1723,6 +1665,9 @@ page_container %}
throw error
}
},
// /split
splitApi: async function (proofs, amount) {
try {
const total = this.sumProofs(proofs)
@@ -1738,16 +1683,11 @@ page_container %}
'number of secrets does not match number of outputs.'
)
}
let {blindedMessages, rs} = await this.constructOutputs(
amounts,
secrets
)
let {outputs, rs} = await this.constructOutputs(amounts, secrets)
const payload = {
amount,
proofs,
outputs: {
blinded_messages: blindedMessages
}
outputs
}
console.log('payload', JSON.stringify(payload))
@@ -1782,7 +1722,62 @@ page_container %}
}
},
splitToSend: async function (proofs, amount, invlalidate = false) {
/*
splits proofs so the user can keep firstProofs, send scndProofs.
then sets scndProofs as reserved.
if invalidate, scndProofs (the one to send) are invalidated
*/
try {
const spendableProofs = proofs.filter(p => !p.reserved)
if (this.sumProofs(spendableProofs) < amount) {
this.$q.notify({
timeout: 5000,
type: 'warning',
message: 'Balance too low',
position: 'top',
actions: [
{
icon: 'close',
color: 'white',
handler: () => {}
}
]
})
throw Error('balance too low.')
}
// call /split
let {fristProofs, scndProofs} = await this.split(
spendableProofs,
amount
)
// set scndProofs in this.proofs as reserved
const usedSecrets = proofs.map(p => p.secret)
for (let i = 0; i < this.proofs.length; i++) {
if (usedSecrets.includes(this.proofs[i].secret)) {
this.proofs[i].reserved = true
}
}
if (invlalidate) {
// delete scndProofs from db
this.deleteProofs(scndProofs)
}
return {fristProofs, scndProofs}
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
throw error
}
},
redeem: async function () {
/*
uses split to receive new tokens.
*/
this.showReceiveTokens = false
console.log('### receive tokens', this.receiveData.tokensBase64)
try {
@@ -1793,6 +1788,9 @@ page_container %}
const proofs = JSON.parse(tokenJson)
const amount = proofs.reduce((s, t) => (s += t.amount), 0)
let {fristProofs, scndProofs} = await this.split(proofs, amount)
// update UI
// HACK: we need to do this so the balance updates
this.proofs = this.proofs.concat([])
@@ -1827,13 +1825,18 @@ page_container %}
},
sendTokens: async function () {
/*
calls splitToSend, displays token and kicks off the spendableWorker
*/
try {
// keep firstProofs, send scndProofs
// keep firstProofs, send scndProofs and delete them (invalidate=true)
let {fristProofs, scndProofs} = await this.splitToSend(
this.proofs,
this.sendData.amount,
true
)
// update UI
this.sendData.tokens = scndProofs
console.log('### this.sendData.tokens', this.sendData.tokens)
this.sendData.tokensBase64 = btoa(
@@ -1846,33 +1849,19 @@ page_container %}
date: currentDateStr(),
token: this.sendData.tokensBase64
})
// store "pending" outgoing tokens in history table
this.storehistoryTokens()
this.checkTokenSpendableWorker()
} catch (error) {
console.error(error)
throw error
}
},
checkFees: async function (payment_request) {
const payload = {
pr: payment_request
}
console.log('#### payload', JSON.stringify(payload))
try {
const {data} = await LNbits.api.request(
'POST',
`/cashu/api/v1/${this.mintId}/checkfees`,
'',
payload
)
console.log('#### checkFees', payment_request, data.fee)
return data.fee
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
throw error
}
},
// /melt
melt: async function () {
// todo: get fees from server and add to inputs
this.payInvoiceData.blocking = true
@@ -1888,10 +1877,7 @@ page_container %}
'amount with fees',
amount
)
// if (amount > balance()) {
// LNbits.utils.notifyApiError('Balance too low')
// return
// }
let {fristProofs, scndProofs} = await this.splitToSend(
this.proofs,
amount
@@ -1924,8 +1910,20 @@ page_container %}
]
})
// delete spent tokens from db
this.proofs = fristProofs
this.storeProofs()
this.deleteProofs(scndProofs)
// update UI
tokensBase64 = btoa(JSON.stringify(scndProofs))
this.historyTokens.push({
status: 'paid',
amount: -amount,
date: currentDateStr(),
token: tokensBase64
})
this.storehistoryTokens()
console.log({
amount: -amount,
bolt11: this.payInvoiceData.data.request,
@@ -1953,13 +1951,95 @@ page_container %}
throw error
}
},
// /check
checkProofsSpendable: async function (proofs, update_history = false) {
/*
checks with the mint whether an array of proofs is still
spendable or already invalidated
*/
const payload = {
proofs: proofs.flat()
}
console.log('#### payload', JSON.stringify(payload))
try {
const {data} = await LNbits.api.request(
'POST',
`/cashu/api/v1/${this.mintId}/check`,
'',
payload
)
// delete proofs from database if it is spent
let spentProofs = proofs.filter((p, pidx) => !data[pidx])
if (spentProofs.length) {
this.deleteProofs(spentProofs)
// update UI
if (update_history) {
tokensBase64 = btoa(JSON.stringify(spentProofs))
this.historyTokens.push({
status: 'paid',
amount: -this.sumProofs(spentProofs),
date: currentDateStr(),
token: tokensBase64
})
this.storehistoryTokens()
}
}
return data
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
throw error
}
},
// /checkfees
checkFees: async function (payment_request) {
const payload = {
pr: payment_request
}
console.log('#### payload', JSON.stringify(payload))
try {
const {data} = await LNbits.api.request(
'POST',
`/cashu/api/v1/${this.mintId}/checkfees`,
'',
payload
)
console.log('#### checkFees', payment_request, data.fee)
return data.fee
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
throw error
}
},
// /keys
fetchMintKeys: async function () {
const {data} = await LNbits.api.request(
'GET',
`/cashu/api/v1/${this.mintId}/keys`
)
this.keys = data
localStorage.setItem(
this.mintKey(this.mintId, 'keys'),
JSON.stringify(data)
)
},
setInvoicePaid: async function (payment_hash) {
const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
invoice.status = 'paid'
this.storeinvoicesCashu()
},
recheckInvoice: async function (payment_hash, verbose = true) {
console.log('### recheckInvoice.hash', payment_hash)
checkInvoice: async function (payment_hash, verbose = true) {
console.log('### checkInvoice.hash', payment_hash)
const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
try {
proofs = await this.mint(invoice.amount, invoice.hash, verbose)
@@ -1969,15 +2049,15 @@ page_container %}
throw error
}
},
recheckPendingInvoices: async function () {
checkPendingInvoices: async function () {
for (const invoice of this.invoicesCashu) {
if (invoice.status === 'pending' && invoice.sat > 0) {
this.recheckInvoice(invoice.hash, false)
if (invoice.status === 'pending' && invoice.amount > 0) {
this.checkInvoice(invoice.hash, false)
}
}
},
recheckPendingTokens: async function () {
checkPendingTokens: async function () {
for (const token of this.historyTokens) {
if (token.status === 'pending' && token.amount < 0) {
this.checkTokenSpendable(token.token, false)
@@ -1990,6 +2070,126 @@ page_container %}
this.storehistoryTokens()
},
checkTokenSpendable: async function (token, verbose = true) {
/*
checks whether a base64-encoded token (from the history table) has been spent already.
if it is spent, the appropraite entry in the history table is set to paid.
*/
const tokenJson = atob(token)
const proofs = JSON.parse(tokenJson)
let data = await this.checkProofsSpendable(proofs)
// iterate through response of form {0: true, 1: false, ...}
let paid = false
for (const [key, spendable] of Object.entries(data)) {
if (!spendable) {
this.setTokenPaid(token)
paid = true
}
}
if (paid) {
console.log('### token paid')
if (window.navigator.vibrate) navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
message: 'Token paid',
position: 'top',
actions: [
{
icon: 'close',
color: 'white',
handler: () => {}
}
]
})
} else {
console.log('### token not paid yet')
if (verbose) {
this.$q.notify({
timeout: 5000,
color: 'grey',
message: 'Token still pending',
position: 'top',
actions: [
{
icon: 'close',
color: 'white',
handler: () => {}
}
]
})
}
this.sendData.tokens = token
}
return paid
},
findTokenForAmount: function (amount) {
for (const token of this.proofs) {
const index = token.promises?.findIndex(p => p.amount === amount)
if (index >= 0) {
return {
promise: token.promises[index],
secret: token.secrets[index],
r: token.rs[index]
}
}
}
},
////////////// WORKERS //////////////
clearAllWorkers: function () {
if (this.invoiceCheckListener) {
clearInterval(this.invoiceCheckListener)
}
if (this.tokensCheckSpendableListener) {
clearInterval(this.tokensCheckSpendableListener)
}
},
invoiceCheckWorker: async function () {
let nInterval = 0
this.clearAllWorkers()
this.invoiceCheckListener = setInterval(async () => {
try {
nInterval += 1
// exit loop after 2m
if (nInterval > 40) {
console.log('### stopping invoice check worker')
this.clearAllWorkers()
}
console.log('### invoiceCheckWorker setInterval', nInterval)
console.log(this.invoiceData)
// this will throw an error if the invoice is pending
await this.checkInvoice(this.invoiceData.hash, false)
// only without error (invoice paid) will we reach here
console.log('### stopping invoice check worker')
this.clearAllWorkers()
this.invoiceData.bolt11 = ''
this.showInvoiceDetails = false
if (window.navigator.vibrate) navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
message: 'Payment received',
position: 'top',
actions: [
{
icon: 'close',
color: 'white',
handler: () => {}
}
]
})
} catch (error) {
console.log('not paid yet')
}
}, 3000)
},
checkTokenSpendableWorker: async function () {
let nInterval = 0
this.clearAllWorkers()
@@ -2021,158 +2221,11 @@ page_container %}
}, 3000)
},
checkTokenSpendable: async function (token, verbose = true) {
const tokenJson = atob(token)
const proofs = JSON.parse(tokenJson)
const payload = {
proofs: proofs.flat()
}
console.log('#### payload', JSON.stringify(payload))
try {
const {data} = await LNbits.api.request(
'POST',
`/cashu/api/v1/${this.mintId}/check`,
'',
payload
)
// iterate through response of form {0: true, 1: false, ...}
let paid = false
for (const [key, spendable] of Object.entries(data)) {
if (!spendable) {
this.setTokenPaid(token)
paid = true
}
}
if (paid) {
console.log('### token paid')
if (window.navigator.vibrate) navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
message: 'Token paid',
position: 'top',
actions: [
{
icon: 'close',
color: 'white',
handler: () => {}
}
]
})
} else {
console.log('### token not paid yet')
if (verbose) {
this.$q.notify({
timeout: 5000,
color: 'grey',
message: 'Token still pending',
position: 'top',
actions: [
{
icon: 'close',
color: 'white',
handler: () => {}
}
]
})
}
this.sendData.tokens = token
}
return paid
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
throw error
}
},
fetchMintKeys: async function () {
const {data} = await LNbits.api.request(
'GET',
`/cashu/api/v1/${this.mintId}/keys`
)
this.keys = data
localStorage.setItem(
this.mintKey(this.mintId, 'keys'),
JSON.stringify(data)
)
},
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
findTokenForAmount: function (amount) {
for (const token of this.proofs) {
const index = token.promises?.findIndex(p => p.amount === amount)
if (index >= 0) {
return {
promise: token.promises[index],
secret: token.secrets[index],
r: token.rs[index]
}
}
}
},
checkInvoice: function () {
console.log('#### checkInvoice')
try {
const invoice = decode(this.payInvoiceData.data.request)
const cleanInvoice = {
msat: invoice.human_readable_part.amount,
sat: invoice.human_readable_part.amount / 1000,
fsat: LNbits.utils.formatSat(
invoice.human_readable_part.amount / 1000
)
}
_.each(invoice.data.tags, tag => {
if (_.isObject(tag) && _.has(tag, 'description')) {
if (tag.description === 'payment_hash') {
cleanInvoice.hash = tag.value
} else if (tag.description === 'description') {
cleanInvoice.description = tag.value
} else if (tag.description === 'expiry') {
var expireDate = new Date(
(invoice.data.time_stamp + tag.value) * 1000
)
cleanInvoice.expireDate = Quasar.utils.date.formatDate(
expireDate,
'YYYY-MM-DDTHH:mm:ss.SSSZ'
)
cleanInvoice.expired = false // TODO
}
}
this.payInvoiceData.invoice = cleanInvoice
})
console.log(
'#### this.payInvoiceData.invoice',
this.payInvoiceData.invoice
)
} catch (error) {
this.$q.notify({
timeout: 5000,
type: 'warning',
message: 'Could not decode invoice',
caption: error + '',
position: 'top',
actions: [
{
icon: 'close',
color: 'white',
handler: () => {}
}
]
})
throw error
}
},
////////////// STORAGE /////////////
getLocalstorageToFile: async function () {
@@ -2335,8 +2388,9 @@ page_container %}
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
this.recheckPendingInvoices()
this.recheckPendingTokens()
this.checkProofsSpendable(this.proofs, true)
this.checkPendingInvoices()
this.checkPendingTokens()
}
})
</script>
+2 -3
View File
@@ -1,7 +1,6 @@
from http import HTTPStatus
from fastapi import Request
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
@@ -18,7 +17,7 @@ templates = Jinja2Templates(directory="templates")
@cashu_ext.get("/", response_class=HTMLResponse)
async def index(
request: Request,
user: User = Depends(check_user_exists), # type: ignore
user: User = Depends(check_user_exists),
):
return cashu_renderer().TemplateResponse(
"cashu/index.html", {"request": request, "user": user.dict()}
+103 -60
View File
@@ -1,10 +1,7 @@
import json
import math
from http import HTTPStatus
from typing import Dict, List, Union
import httpx
# -------- cashu imports
from cashu.core.base import (
BlindedSignature,
@@ -15,16 +12,13 @@ from cashu.core.base import (
GetMintResponse,
Invoice,
MeltRequest,
MintRequest,
PostMintRequest,
PostMintResponse,
PostSplitResponse,
Proof,
SplitRequest,
)
from fastapi import Query
from fastapi.params import Depends
from lnurl import decode as decode_lnurl
from fastapi import Depends, Query
from loguru import logger
from secp256k1 import PublicKey
from starlette.exceptions import HTTPException
from lnbits import bolt11
@@ -35,7 +29,6 @@ from lnbits.core.services import (
fee_reserve,
pay_invoice,
)
from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.helpers import urlsafe_short_hash
from lnbits.wallets.base import PaymentStatus
@@ -46,9 +39,16 @@ from .models import Cashu
# --------- extension imports
# WARNING: Do not set this to False in production! This will create
# tokens for free otherwise. This is for testing purposes only!
LIGHTNING = True
if not LIGHTNING:
logger.warning(
"Cashu: LIGHTNING is set False! That means that I will create ecash for free!"
)
########################################
############### LNBITS MINTS ###########
########################################
@@ -56,7 +56,7 @@ LIGHTNING = True
@cashu_ext.get("/api/v1/mints", status_code=HTTPStatus.OK)
async def api_cashus(
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
"""
Get all mints of this wallet.
@@ -73,7 +73,7 @@ async def api_cashus(
@cashu_ext.post("/api/v1/mints", status_code=HTTPStatus.CREATED)
async def api_cashu_create(
data: Cashu,
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
wallet: WalletTypeInfo = Depends(get_key_type),
):
"""
Create a new mint for this wallet.
@@ -91,7 +91,7 @@ async def api_cashu_create(
@cashu_ext.delete("/api/v1/mints/{cashu_id}")
async def api_cashu_delete(
cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) # type: ignore
cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
):
"""
Delete an existing cashu mint.
@@ -130,6 +130,28 @@ async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
return ledger.get_keyset(keyset_id=cashu.keyset_id)
@cashu_ext.get("/api/v1/{cashu_id}/keys/{idBase64Urlsafe}")
async def keyset_keys(
cashu_id: str = Query(None), idBase64Urlsafe: str = Query(None)
) -> dict[int, str]:
"""
Get the public keys of the mint of a specificy keyset id.
The id is encoded in base64_urlsafe and needs to be converted back to
normal base64 before it can be processed.
"""
cashu: Union[Cashu, None] = await get_cashu(cashu_id)
if not cashu:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
)
id = idBase64Urlsafe.replace("-", "+").replace("_", "/")
keyset = ledger.get_keyset(keyset_id=id)
return keyset
@cashu_ext.get("/api/v1/{cashu_id}/keysets", status_code=HTTPStatus.OK)
async def keysets(cashu_id: str = Query(None)) -> dict[str, list[str]]:
"""Get the public keys of the mint"""
@@ -182,11 +204,11 @@ async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintR
@cashu_ext.post("/api/v1/{cashu_id}/mint")
async def mint_coins(
data: MintRequest,
async def mint(
data: PostMintRequest,
cashu_id: str = Query(None),
payment_hash: str = Query(None),
) -> List[BlindedSignature]:
) -> PostMintResponse:
"""
Requests the minting of tokens belonging to a paid payment request.
Call this endpoint after `GET /mint`.
@@ -197,6 +219,8 @@ async def mint_coins(
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
)
keyset = ledger.keysets.keysets[cashu.keyset_id]
if LIGHTNING:
invoice: Invoice = await ledger.crud.get_lightning_invoice(
db=ledger.db, hash=payment_hash
@@ -206,42 +230,51 @@ async def mint_coins(
status_code=HTTPStatus.NOT_FOUND,
detail="Mint does not know this invoice.",
)
if invoice.issued == True:
if invoice.issued:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED,
detail="Tokens already issued for this invoice.",
)
total_requested = sum([bm.amount for bm in data.blinded_messages])
if total_requested > invoice.amount:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED,
detail=f"Requested amount too high: {total_requested}. Invoice amount: {invoice.amount}",
)
status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
if LIGHTNING and status.paid != True:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
)
try:
keyset = ledger.keysets.keysets[cashu.keyset_id]
promises = await ledger._generate_promises(
B_s=data.blinded_messages, keyset=keyset
)
assert len(promises), HTTPException(
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="No promises returned."
)
# set this invoice as issued
await ledger.crud.update_lightning_invoice(
db=ledger.db, hash=payment_hash, issued=True
)
return promises
except Exception as e:
logger.error(e)
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
status: PaymentStatus = await check_transaction_status(
cashu.wallet, payment_hash
)
try:
total_requested = sum([bm.amount for bm in data.outputs])
if total_requested > invoice.amount:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED,
detail=f"Requested amount too high: {total_requested}. Invoice amount: {invoice.amount}",
)
if not status.paid:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
)
promises = await ledger._generate_promises(B_s=data.outputs, keyset=keyset)
return PostMintResponse(promises=promises)
except (Exception, HTTPException) as e:
logger.debug(f"Cashu: /melt {str(e) or getattr(e, 'detail')}")
# unset issued flag because something went wrong
await ledger.crud.update_lightning_invoice(
db=ledger.db, hash=payment_hash, issued=False
)
raise HTTPException(
status_code=getattr(e, "status_code")
or HTTPStatus.INTERNAL_SERVER_ERROR,
detail=str(e) or getattr(e, "detail"),
)
else:
# only used for testing when LIGHTNING=false
promises = await ledger._generate_promises(B_s=data.outputs, keyset=keyset)
return PostMintResponse(promises=promises)
@cashu_ext.post("/api/v1/{cashu_id}/melt")
@@ -285,28 +318,38 @@ async def melt_coins(
f"Provided proofs ({total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)."
)
logger.debug(f"Cashu: Initiating payment of {total_provided} sats")
await pay_invoice(
wallet_id=cashu.wallet,
payment_request=invoice,
description=f"Pay cashu invoice",
extra={"tag": "cashu", "cashu_name": cashu.name},
)
logger.debug(
f"Cashu: Wallet {cashu.wallet} checking PaymentStatus of {invoice_obj.payment_hash}"
)
status: PaymentStatus = await check_transaction_status(
cashu.wallet, invoice_obj.payment_hash
)
if status.paid == True:
logger.debug("Cashu: Payment successful, invalidating proofs")
await ledger._invalidate_proofs(proofs)
try:
await pay_invoice(
wallet_id=cashu.wallet,
payment_request=invoice,
description=f"Pay cashu invoice",
extra={"tag": "cashu", "cashu_name": cashu.name},
)
except Exception as e:
logger.debug(f"Cashu error paying invoice {invoice_obj.payment_hash}: {e}")
raise e
finally:
logger.debug(
f"Cashu: Wallet {cashu.wallet} checking PaymentStatus of {invoice_obj.payment_hash}"
)
status: PaymentStatus = await check_transaction_status(
cashu.wallet, invoice_obj.payment_hash
)
if status.paid == True:
logger.debug(
f"Cashu: Payment successful, invalidating proofs for {invoice_obj.payment_hash}"
)
await ledger._invalidate_proofs(proofs)
else:
logger.debug(f"Cashu: Payment failed for {invoice_obj.payment_hash}")
except Exception as e:
logger.debug(f"Cashu: Exception for {invoice_obj.payment_hash}: {str(e)}")
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Cashu: {str(e)}",
)
finally:
logger.debug(f"Cashu: Unset pending for {invoice_obj.payment_hash}")
# delete proofs from pending list
await ledger._unset_proofs_pending(proofs)
@@ -375,7 +418,7 @@ async def split(
)
amount = payload.amount
outputs = payload.outputs.blinded_messages
outputs = payload.outputs
assert outputs, Exception("no outputs provided.")
split_return = None
try:
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "Streamer Copilot",
"short_description": "Video tips/animations/webhooks",
"icon": "face",
"tile": "/copilot/static/bitcoin-streaming.png",
"contributors": [
"arcbtc"
]
+2 -2
View File
@@ -6,7 +6,7 @@ from fastapi import Request
from fastapi.param_functions import Query
from lnurl.types import LnurlPayMetadata
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse # type: ignore
from starlette.responses import HTMLResponse
from lnbits.core.services import create_invoice
@@ -75,7 +75,7 @@ async def lnurl_callback(
memo=cp.lnurl_title,
unhashed_description=(
LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)]]))
).encode("utf-8"),
).encode(),
extra={"tag": "copilot", "copilotid": cp.id, "comment": comment},
)
payResponse = {"pr": payment_request, "routes": []}
+2 -2
View File
@@ -4,11 +4,11 @@ from typing import Dict, Optional
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
from fastapi.param_functions import Query
from lnurl.types import LnurlPayMetadata # type: ignore
from lnurl.types import LnurlPayMetadata
from pydantic import BaseModel
from starlette.requests import Request
from lnbits.lnurl import encode as lnurl_encode # type: ignore
from lnbits.lnurl import encode as lnurl_encode
class CreateCopilotData(BaseModel):
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

+3 -3
View File
@@ -24,12 +24,12 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
webhook = None
data = None
if not payment.extra or payment.extra.get("tag") != "copilot":
if payment.extra.get("tag") != "copilot":
# not an copilot invoice
return
webhook = None
data = None
copilot = await get_copilot(payment.extra.get("copilotid", -1))
if not copilot:
@@ -4,7 +4,10 @@
StreamerCopilot: get tips via static QR (lnurl-pay) and show an
animation<br />
<small>
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
Created by,
<a class="text-secondary" href="https://github.com/benarc"
>Ben Arc</a
></small
>
</p>
</q-card-section>
@@ -15,7 +15,8 @@
<div
v-if="copilot.lnurl_toggle == 1"
class="rounded-borders column fixed-right"
id="draggableqr"
class="rounded-borders"
style="
width: 250px;
background-color: white;
@@ -25,7 +26,7 @@
>
<div class="col">
<qrcode
:value="copilot.lnurl"
:value="'lightning:' + copilot.lnurl"
:options="{width:250}"
class="rounded-borders"
></qrcode>
@@ -36,8 +37,9 @@
</div>
<h2
id="draggableprice"
v-if="copilot.show_price != 0"
class="text-bold fixed-bottom-left"
class="text-bold"
style="
margin: 60px 60px;
font-size: 110px;
@@ -47,6 +49,7 @@
>
{% raw %}{{ price }}{% endraw %}
</h2>
<p
v-if="copilot.show_ack != 0"
class="fixed-top"
@@ -73,7 +76,19 @@
.q-page {
padding: 0px;
}
#draggableqr {
width: 250px;
height: 300px;
cursor: grab;
}
#draggableprice {
width: 550px;
height: 60px;
cursor: grab;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.js"></script>
<script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script>
<script>
Vue.component(VueQrcode.name, VueQrcode)
@@ -93,9 +108,8 @@
},
methods: {
showNotif: function (userMessage) {
var colour = this.colours[
Math.floor(Math.random() * this.colours.length)
]
var colour =
this.colours[Math.floor(Math.random() * this.colours.length)]
this.$q.notify({
color: colour,
icon: 'chat_bubble_outline',
@@ -167,7 +181,11 @@
this.initCamera()
},
created: function () {
self = this
$(function () {
$('#draggableqr').draggable()
$('#draggableprice').draggable()
}),
(self = this)
self.copilot = JSON.parse(localStorage.getItem('copilot'))
LNbits.api
.request(
+3 -7
View File
@@ -1,23 +1,19 @@
from typing import List
from fastapi import Request, WebSocket, WebSocketDisconnect
from fastapi.params import Depends
from fastapi import Depends, Request
from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse # type: ignore
from starlette.responses import HTMLResponse
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from . import copilot_ext, copilot_renderer
from .crud import get_copilot
templates = Jinja2Templates(directory="templates")
@copilot_ext.get("/", response_class=HTMLResponse)
async def index(
request: Request, user: User = Depends(check_user_exists) # type: ignore
):
async def index(request: Request, user: User = Depends(check_user_exists)):
return copilot_renderer().TemplateResponse(
"copilot/index.html", {"request": request, "user": user.dict()}
)
+5 -9
View File
@@ -1,8 +1,6 @@
from http import HTTPStatus
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends
from fastapi import Depends, Query, Request
from starlette.exceptions import HTTPException
from lnbits.core.services import websocketUpdater
@@ -22,9 +20,7 @@ from .models import CreateCopilotData
@copilot_ext.get("/api/v1/copilot")
async def api_copilots_retrieve(
req: Request, wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
async def api_copilots_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
wallet_user = wallet.wallet.user
copilots = [copilot.dict() for copilot in await get_copilots(wallet_user)]
try:
@@ -37,7 +33,7 @@ async def api_copilots_retrieve(
async def api_copilot_retrieve(
req: Request,
copilot_id: str = Query(None),
wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
wallet: WalletTypeInfo = Depends(get_key_type),
):
copilot = await get_copilot(copilot_id)
if not copilot:
@@ -54,7 +50,7 @@ async def api_copilot_retrieve(
async def api_copilot_create_or_update(
data: CreateCopilotData,
copilot_id: str = Query(None),
wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore
wallet: WalletTypeInfo = Depends(require_admin_key),
):
data.user = wallet.wallet.user
data.wallet = wallet.wallet.id
@@ -68,7 +64,7 @@ async def api_copilot_create_or_update(
@copilot_ext.delete("/api/v1/copilot/{copilot_id}")
async def api_copilot_delete(
copilot_id: str = Query(None),
wallet: WalletTypeInfo = Depends(require_admin_key), # type: ignore
wallet: WalletTypeInfo = Depends(require_admin_key),
):
copilot = await get_copilot(copilot_id)
+11
View File
@@ -0,0 +1,11 @@
# Deezy: Home for Lightning Liquidity
Swap lightning bitcoin for on-chain bitcoin to get inbound liquidity. Or get an on-chain deposit address for your lightning address.
* [Website](https://deezy.io)
* [Lightning Node](https://amboss.space/node/024bfaf0cabe7f874fd33ebf7c6f4e5385971fc504ef3f492432e9e3ec77e1b5cf)
* [Documentation](https://docs.deezy.io)
* [Discord](https://discord.gg/nEBbrUAvPy)
# Usage
This extension lets you swap lightning btc for on-chain btc and vice versa.
* Swap Lightning -> BTC to get inbound liquidity
* Swap BTC -> Lightning to generate an on-chain deposit address for your lightning address

Some files were not shown because too many files have changed in this diff Show More