Compare commits

..
54 Commits
Author SHA1 Message Date
ArcandGitHub 41a9689014 Update installation.md 2022-02-18 17:17:18 +00:00
ArcandGitHub 76edfa2134 Update installation.md 2022-02-18 17:16:49 +00:00
ArcandGitHub d7b3a28950 Update installation.md 2022-02-18 17:16:18 +00:00
ArcandGitHub 2c161a079d Update installation.md 2022-02-18 17:16:03 +00:00
ArcandGitHub 88df4266b7 Merge pull request #524 from lnbits/docs_api_add_unit_to_invoice
Fix fiat amount entry in invoice creation
2022-02-18 11:44:05 +00:00
callebtc 6496fac35f fill mask for fiat 2022-02-18 12:35:25 +01:00
Ben Arc 10492ec3d0 Changed bitcoin theme back 2022-02-17 23:40:29 +00:00
Ben Arc 302d79a30f Improved bitcoin theme 2022-02-17 23:33:55 +00:00
ArcandGitHub b88abd1a46 Merge pull request #527 from talvasconcelos/fix/instantWallet
Fix instant wallet form LNURLw
2022-02-17 23:21:58 +00:00
Ben Arc 573c4cf5ea Changed theme order to default to classic 2022-02-17 21:46:51 +00:00
Ben Arc e9065833c0 Fixed themes, added bitcoin theme 2022-02-17 21:34:36 +00:00
Tiago vasconcelos 251ecb8f1d url_for params 2022-02-17 21:01:14 +00:00
calleandGitHub c626e12fa3 LNDHhub for fastapi with LIMIT (#499)
* add limit to get_payments

* add limit to lndhub

* fix defaults

* default memo

* add offset cluase

* offset for the api
2022-02-17 13:10:34 +01:00
callebtc 13a72d9a41 replace string with LNBITS_SITE_TITLE 2022-02-17 11:42:08 +01:00
benarc 3c5fac3874 Fixed paywall, unnecessary key check added 2022-02-17 09:23:33 +00:00
callebtc 15c8557459 allow empty memo 2022-02-17 10:16:11 +01:00
callebtc d559c604fa invoice without memo 2022-02-17 09:29:10 +01:00
callebtc cd84623305 amount label adaptive to uni - look at me Im a frontent dev now 2022-02-17 09:14:07 +01:00
callebtc 9fb30080ab black 2022-02-16 22:42:27 +01:00
callebtc 30ab519c3a amount is float and sats are int 2022-02-16 22:41:12 +01:00
callebtc 914b9f3ffe allow amounts >0 2022-02-16 22:23:26 +01:00
0764f4fdf1 Support for encrypted macaroons (#521)
* encrypted macaroons

* fix GRPC env entry

* example config entry

* add pycryptodomex to requirements

* documentation

* Added pycryptodomex to pip file

Co-authored-by: Ben Arc <ben@arc.wales>
2022-02-14 17:54:05 +01:00
Ben Arc 0367ee85a7 removed dense from lower buttons 2022-02-14 15:51:45 +00:00
Ben Arc 0aef9d4d9a Made dense to save space 2022-02-14 15:48:56 +00:00
Ben Arc 8967011cbe Merge branch 'mobileui' into main 2022-02-14 15:38:17 +00:00
Ben Arc 682226c4b6 chnaged button names 2022-02-14 15:36:10 +00:00
Ben Arc c7e501ac08 Adds lower buttons to wallet UI for smaller screens 2022-02-14 15:29:10 +00:00
calleandGitHub 8d7302e4cd fix bleskomat test (#523) 2022-02-14 13:49:15 +01:00
callebtc 09b86605dd fix bleskomat tests 2022-02-14 13:42:43 +01:00
orangesurfandGitHub 9e8376b3fb Remove suggestion to switch to FastAPI branch (#520)
Branch does not exist
2022-02-14 13:38:22 +01:00
ArcandGitHub 1a492cbfb8 Update installation.md 2022-02-14 11:42:19 +00:00
ArcandGitHub 155c32504a Update api.py 2022-02-14 11:31:42 +00:00
ArcandGitHub 8c923ad653 Update README.md 2022-02-14 09:23:04 +00:00
ArcandGitHub 25b9014c3d Update README.md 2022-02-14 09:22:23 +00:00
ArcandGitHub 9807e7d646 Merge pull request #518 from talvasconcelos/fix/disapearingAdminExt
Disapearing Extensions in Admin Only
2022-02-11 11:17:55 +00:00
ArcandGitHub 728ab517f5 Merge branch 'main' into fix/disapearingAdminExt 2022-02-11 11:17:37 +00:00
benarc f1fb569e2b lnurlpayout fix 2022-02-11 10:03:26 +00:00
Tiago vasconcelos 6d58027147 remove print 2022-02-10 17:06:15 +00:00
Tiago vasconcelos 4a35f84e95 fix admin default false 2022-02-10 16:44:47 +00:00
ArcandGitHub f7b37fb843 Merge pull request #498 from pseudozach/main
fix livestream startup issue
2022-02-10 15:24:05 +00:00
ArcandGitHub 699bdd9a06 Merge pull request #497 from bitkarrot/main
fix curl example paths in API docs on LNURLw extension
2022-02-10 15:21:28 +00:00
benarc b5aad94ecd rolled back admin fix 2022-02-10 13:55:45 +00:00
benarc 0f79213b0f Tals admin extension fix 2022-02-10 10:09:44 +00:00
ArcandGitHub c1654a21d4 Merge pull request #515 from lnbits/revert-513-fix/disapearingAdminExt
Revert "fix disapearing admin extension"
2022-02-10 09:56:15 +00:00
ArcandGitHub da6a2e772b Revert "fix disapearing admin extension" 2022-02-10 09:56:02 +00:00
ArcandGitHub 1626cd2b4b Merge pull request #513 from talvasconcelos/fix/disapearingAdminExt
fix disapearing admin extension
2022-02-10 09:55:14 +00:00
Tiago vasconcelos d1d62583c4 fix for whitespace in admin users list 2022-02-09 11:16:13 +00:00
Tiago vasconcelos 4c18eb0b34 fix disapearing admin extension 2022-02-09 10:57:24 +00:00
ArcandGitHub 35bb26b6fd Merge pull request #512 from lnbits/adminextensions
Adds admin extensions
2022-02-07 21:32:17 +00:00
benarc 2c853e7a52 slight bug 2022-02-07 21:28:48 +00:00
benarc 6bc1474c19 Organised .env 2022-02-07 20:48:48 +00:00
benarc 4b11342f0c works 2022-02-07 20:43:47 +00:00
pseudozach b00a39a7ce fix livestream startup issue 2022-01-24 15:58:51 -08:00
BitkarrotandGitHub b4dc07170f fix curl example paths 2022-01-24 14:51:39 -08:00
33 changed files with 524 additions and 231 deletions
+10 -4
View File
@@ -5,11 +5,15 @@ QUART_DEBUG=true
HOST=127.0.0.1
PORT=5000
LNBITS_ALLOWED_USERS=""
LNBITS_ADMIN_USERS=""
# Extensions only admin can access
LNBITS_ADMIN_EXTENSIONS="ngrok"
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
# Disable extensions for all users, use "all" to disable all extensions
LNBITS_DISABLED_EXTENSIONS="amilk"
# Database: to use SQLite, specify LNBITS_DATA_FOLDER
# to use PostgreSQL, specify LNBITS_DATABASE_URL=postgres://...
# to use CockroachDB, specify LNBITS_DATABASE_URL=cockroachdb://...
@@ -18,8 +22,6 @@ LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
LNBITS_DATA_FOLDER="./data"
# LNBITS_DATABASE_URL="postgres://user:password@host:port/databasename"
# disable selected extensions, or use "all" to disable all extensions
LNBITS_DISABLED_EXTENSIONS="amilk,ngrok"
LNBITS_FORCE_HTTPS=true
LNBITS_SERVICE_FEE="0.0"
@@ -28,7 +30,7 @@ LNBITS_SITE_TITLE="LNbits"
LNBITS_SITE_TAGLINE="free and open-source lightning wallet"
LNBITS_SITE_DESCRIPTION="Some description about your service, will display if title is not 'LNbits'"
# Choose from mint, flamingo, salvador, autumn, monochrome, classic
LNBITS_THEME_OPTIONS="mint, flamingo, classic, autumn, monochrome, salvador"
LNBITS_THEME_OPTIONS="classic, bitcoin, flamingo, mint, autumn, monochrome, salvador"
# Choose from LNPayWallet, OpenNodeWallet, LntxbotWallet, LndWallet (gRPC),
# LndRestWallet, CLightningWallet, LNbitsWallet, SparkWallet, FakeWallet
@@ -53,11 +55,15 @@ LND_GRPC_ENDPOINT=127.0.0.1
LND_GRPC_PORT=11009
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_ENCRYPTED="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"
LND_REST_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_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn"
# LNPayWallet
LNPAY_API_ENDPOINT=https://api.lnpay.co/v1/
+1
View File
@@ -30,6 +30,7 @@ sse-starlette = "*"
jinja2 = "3.0.1"
pyngrok = "*"
secp256k1 = "*"
pycryptodomex = "*"
[dev-packages]
black = "==20.8b1"
Generated
+97 -67
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "bd78144379115a1566549f5f2d7dba7a96539fb5893b3999e61bc13c6847827e"
"sha256": "3e19364434fd2db3748162ccc1f3b6bddcf7a382473069d15cee6eda5e07eef1"
},
"pipfile-spec": 6,
"requires": {
@@ -146,11 +146,11 @@
},
"charset-normalizer": {
"hashes": [
"sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45",
"sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"
"sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597",
"sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"
],
"markers": "python_version >= '3.5'",
"version": "==2.0.11"
"version": "==2.0.12"
},
"click": {
"hashes": [
@@ -201,11 +201,11 @@
},
"httpcore": {
"hashes": [
"sha256:2621ee769d0236574df51b305c5f4c69ca8f0c7b215221ad247b1ee42a9a9de1",
"sha256:435ab519628a6e2393f67812dea3ca5c6ad23b457412cd119295d9f906d96a2b"
"sha256:47d772f754359e56dd9d892d9593b6f9870a37aeb8ba51e9a88b09b3d68cfade",
"sha256:7503ec1c0f559066e7e39bc4003fd2ce023d01cf51793e3c173b864eb456ead1"
],
"markers": "python_version >= '3.6'",
"version": "==0.14.5"
"version": "==0.14.7"
},
"httptools": {
"hashes": [
@@ -427,6 +427,39 @@
],
"version": "==2.21"
},
"pycryptodomex": {
"hashes": [
"sha256:1ca8e1b4c62038bb2da55451385246f51f412c5f5eabd64812c01766a5989b4a",
"sha256:298c00ea41a81a491d5b244d295d18369e5aac4b61b77b2de5b249ca61cd6659",
"sha256:2aa887683eee493e015545bd69d3d21ac8d5ad582674ec98f4af84511e353e45",
"sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2",
"sha256:3da13c2535b7aea94cc2a6d1b1b37746814c74b6e80790daddd55ca5c120a489",
"sha256:406ec8cfe0c098fadb18d597dc2ee6de4428d640c0ccafa453f3d9b2e58d29e2",
"sha256:4d0db8df9ffae36f416897ad184608d9d7a8c2b46c4612c6bc759b26c073f750",
"sha256:530756d2faa40af4c1f74123e1d889bd07feae45bac2fd32f259a35f7aa74151",
"sha256:77931df40bb5ce5e13f4de2bfc982b2ddc0198971fbd947776c8bb5050896eb2",
"sha256:797a36bd1f69df9e2798e33edb4bd04e5a30478efc08f9428c087f17f65a7045",
"sha256:8085bd0ad2034352eee4d4f3e2da985c2749cb7344b939f4d95ead38c2520859",
"sha256:8536bc08d130cae6dcba1ea689f2913dfd332d06113904d171f2f56da6228e89",
"sha256:a4d412eba5679ede84b41dbe48b1bed8f33131ab9db06c238a235334733acc5e",
"sha256:aebecde2adc4a6847094d3bd6a8a9538ef3438a5ea84ac1983fcb167db614461",
"sha256:b276cc4deb4a80f9dfd47a41ebb464b1fe91efd8b1b8620cf5ccf8b824b850d6",
"sha256:b5a185ae79f899b01ca49f365bdf15a45d78d9856f09b0de1a41b92afce1a07f",
"sha256:c4d8977ccda886d88dc3ca789de2f1adc714df912ff3934b3d0a3f3d777deafb",
"sha256:c5dd3ffa663c982d7f1be9eb494a8924f6d40e2e2f7d1d27384cfab1b2ac0662",
"sha256:ca88f2f7020002638276439a01ffbb0355634907d1aa5ca91f3dc0c2e44e8f3b",
"sha256:d2cce1c82a7845d7e2e8a0956c6b7ed3f1661c9acf18eb120fc71e098ab5c6fe",
"sha256:d709572d64825d8d59ea112e11cc7faf6007f294e9951324b7574af4251e4de8",
"sha256:da8db8374295fb532b4b0c467e66800ef17d100e4d5faa2bbbd6df35502da125",
"sha256:e36c7e3b5382cd5669cf199c4a04a0279a43b2a3bdd77627e9b89778ac9ec08c",
"sha256:e95a4a6c54d27a84a4624d2af8bb9ee178111604653194ca6880c98dcad92f48",
"sha256:ee835def05622e0c8b1435a906491760a43d0c462f065ec9143ec4b8d79f8bff",
"sha256:f75009715dcf4a3d680c2338ab19dac5498f8121173a929872950f4fb3a48fbf",
"sha256:f8524b8bc89470cec7ac51734907818d3620fb1637f8f8b542d650ebec42a126"
],
"index": "pypi",
"version": "==3.14.1"
},
"pydantic": {
"hashes": [
"sha256:085ca1de245782e9b46cefcf99deecc67d418737a1fd3f6a4f511344b613a5b3",
@@ -684,22 +717,22 @@
},
"typing-extensions": {
"hashes": [
"sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
"sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
"sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42",
"sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"
],
"index": "pypi",
"version": "==4.0.1"
"version": "==4.1.1"
},
"uvicorn": {
"extras": [
"standard"
],
"hashes": [
"sha256:8b16d9ecb76500f7804184f182835fe8a2b54716d3b0b6bb2da0b2b192f62c73",
"sha256:dffbacb8cc25d924d68d231d2c478c4fe6727c36537d8de21e5de591b37afc41"
"sha256:25850bbc86195a71a6477b3e4b3b7b4c861fb687fb96912972ce5324472b1011",
"sha256:e85872d84fb651cccc4c5d2a71cf7ead055b8fb4d8f1e78e36092282c0cf2aec"
],
"index": "pypi",
"version": "==0.17.1"
"version": "==0.17.4"
},
"uvloop": {
"hashes": [
@@ -819,53 +852,50 @@
"toml"
],
"hashes": [
"sha256:012157499ec4f135fc36cd2177e3d1a1840af9b236cbe80e9a5ccfc83d912a69",
"sha256:0a34d313105cdd0d3644c56df2d743fe467270d6ab93b5d4a347eb9fec8924d6",
"sha256:11e61c5548ecf74ea1f8b059730b049871f0e32b74f88bd0d670c20c819ad749",
"sha256:152cc2624381df4e4e604e21bd8e95eb8059535f7b768c1fb8b8ae0b26f47ab0",
"sha256:1b4285fde5286b946835a1a53bba3ad41ef74285ba9e8013e14b5ea93deaeafc",
"sha256:27a94db5dc098c25048b0aca155f5fac674f2cf1b1736c5272ba28ead2fc267e",
"sha256:27ac7cb84538e278e07569ceaaa6f807a029dc194b1c819a9820b9bb5dbf63ab",
"sha256:2a491e159294d756e7fc8462f98175e2d2225e4dbe062cca7d3e0d5a75ba6260",
"sha256:2bc85664b06ba42d14bb74d6ddf19d8bfc520cb660561d2d9ce5786ae72f71b5",
"sha256:32168001f33025fd756884d56d01adebb34e6c8c0b3395ca8584cdcee9c7c9d2",
"sha256:3c4ce3b647bd1792d4394f5690d9df6dc035b00bcdbc5595099c01282a59ae01",
"sha256:433b99f7b0613bdcdc0b00cc3d39ed6d756797e3b078d2c43f8a38288520aec6",
"sha256:4578728c36de2801c1deb1c6b760d31883e62e33f33c7ba8f982e609dc95167d",
"sha256:509c68c3e2015022aeda03b003dd68fa19987cdcf64e9d4edc98db41cfc45d30",
"sha256:51372e24b1f7143ee2df6b45cff6a721f3abe93b1e506196f3ffa4155c2497f7",
"sha256:5d008e0f67ac800b0ca04d7914b8501312c8c6c00ad8c7ba17754609fae1231a",
"sha256:649df3641eb351cdfd0d5533c92fc9df507b6b2bf48a7ef8c71ab63cbc7b5c3c",
"sha256:6e78b1e25e5c5695dea012be473e442f7094d066925604be20b30713dbd47f89",
"sha256:72d9d186508325a456475dd05b1756f9a204c7086b07fffb227ef8cee03b1dc2",
"sha256:7d82c610a2e10372e128023c5baf9ce3d270f3029fe7274ff5bc2897c68f1318",
"sha256:7ee317486593193e066fc5e98ac0ce712178c21529a85c07b7cb978171f25d53",
"sha256:7eed8459a2b81848cafb3280b39d7d49950d5f98e403677941c752e7e7ee47cb",
"sha256:823f9325283dc9565ba0aa2d240471a93ca8999861779b2b6c7aded45b58ee0f",
"sha256:85c5fc9029043cf8b07f73fbb0a7ab6d3b717510c3b5642b77058ea55d7cacde",
"sha256:86c91c511853dfda81c2cf2360502cb72783f4b7cebabef27869f00cbe1db07d",
"sha256:8e0c3525b1a182c8ffc9bca7e56b521e0c2b8b3e82f033c8e16d6d721f1b54d6",
"sha256:987a84ff98a309994ca77ed3cc4b92424f824278e48e4bf7d1bb79a63cfe2099",
"sha256:9ed3244b415725f08ca3bdf02ed681089fd95e9465099a21c8e2d9c5d6ca2606",
"sha256:a189036c50dcd56100746139a459f0d27540fef95b09aba03e786540b8feaa5f",
"sha256:a4748349734110fd32d46ff8897b561e6300d8989a494ad5a0a2e4f0ca974fc7",
"sha256:a5d79c9af3f410a2b5acad91258b4ae179ee9c83897eb9de69151b179b0227f5",
"sha256:a7596aa2f2b8fa5604129cfc9a27ad9beec0a96f18078cb424d029fdd707468d",
"sha256:ab4fc4b866b279740e0d917402f0e9a08683e002f43fa408e9655818ed392196",
"sha256:bde4aeabc0d1b2e52c4036c54440b1ad05beeca8113f47aceb4998bb7471e2c2",
"sha256:c72bb4679283c6737f452eeb9b2a0e570acaef2197ad255fb20162adc80bea76",
"sha256:c8582e9280f8d0f38114fe95a92ae8d0790b56b099d728cc4f8a2e14b1c4a18c",
"sha256:ca29c352389ea27a24c79acd117abdd8a865c6eb01576b6f0990cd9a4e9c9f48",
"sha256:ce443a3e6df90d692c38762f108fc4c88314bf477689f04de76b3f252e7a351c",
"sha256:d1675db48490e5fa0b300f6329ecb8a9a37c29b9ab64fa9c964d34111788ca2d",
"sha256:da1a428bdbe71f9a8c270c7baab29e9552ac9d0e0cba5e7e9a4c9ee6465d258d",
"sha256:e4ff163602c5c77e7bb4ea81ba5d3b793b4419f8acd296aae149370902cf4e92",
"sha256:e67ccd53da5958ea1ec833a160b96357f90859c220a00150de011b787c27b98d",
"sha256:e8071e7d9ba9f457fc674afc3de054450be2c9b195c470147fbbc082468d8ff7",
"sha256:fff16a30fdf57b214778eff86391301c4509e327a65b877862f7c929f10a4253"
"sha256:1245ab82e8554fa88c4b2ab1e098ae051faac5af829efdcf2ce6b34dccd5567c",
"sha256:1bc6d709939ff262fd1432f03f080c5042dc6508b6e0d3d20e61dd045456a1a0",
"sha256:25e73d4c81efa8ea3785274a2f7f3bfbbeccb6fcba2a0bdd3be9223371c37554",
"sha256:276b13cc085474e482566c477c25ed66a097b44c6e77132f3304ac0b039f83eb",
"sha256:2aed4761809640f02e44e16b8b32c1a5dee5e80ea30a0ff0912158bde9c501f2",
"sha256:2dd70a167843b4b4b2630c0c56f1b586fe965b4f8ac5da05b6690344fd065c6b",
"sha256:352c68e233409c31048a3725c446a9e48bbff36e39db92774d4f2380d630d8f8",
"sha256:3f2b05757c92ad96b33dbf8e8ec8d4ccb9af6ae3c9e9bd141c7cc44d20c6bcba",
"sha256:448d7bde7ceb6c69e08474c2ddbc5b4cd13c9e4aa4a717467f716b5fc938a734",
"sha256:463e52616ea687fd323888e86bf25e864a3cc6335a043fad6bbb037dbf49bbe2",
"sha256:482fb42eea6164894ff82abbcf33d526362de5d1a7ed25af7ecbdddd28fc124f",
"sha256:56c4a409381ddd7bbff134e9756077860d4e8a583d310a6f38a2315b9ce301d0",
"sha256:56d296cbc8254a7dffdd7bcc2eb70be5a233aae7c01856d2d936f5ac4e8ac1f1",
"sha256:5e15d424b8153756b7c903bde6d4610be0c3daca3986173c18dd5c1a1625e4cd",
"sha256:618eeba986cea7f621d8607ee378ecc8c2504b98b3fdc4952b30fe3578304687",
"sha256:61d47a897c1e91f33f177c21de897267b38fbb45f2cd8e22a710bcef1df09ac1",
"sha256:621f6ea7260ea2ffdaec64fe5cb521669984f567b66f62f81445221d4754df4c",
"sha256:6a5cdc3adb4f8bb8d8f5e64c2e9e282bc12980ef055ec6da59db562ee9bdfefa",
"sha256:6c3f6158b02ac403868eea390930ae64e9a9a2a5bbfafefbb920d29258d9f2f8",
"sha256:704f89b87c4f4737da2860695a18c852b78ec7279b24eedacab10b29067d3a38",
"sha256:72128176fea72012063200b7b395ed8a57849282b207321124d7ff14e26988e8",
"sha256:78fbb2be068a13a5d99dce9e1e7d168db880870f7bc73f876152130575bd6167",
"sha256:7bff3a98f63b47464480de1b5bdd80c8fade0ba2832c9381253c9b74c4153c27",
"sha256:84f2436d6742c01136dd940ee158bfc7cf5ced3da7e4c949662b8703b5cd8145",
"sha256:9976fb0a5709988778ac9bc44f3d50fccd989987876dfd7716dee28beed0a9fa",
"sha256:9ad0a117b8dc2061ce9461ea4c1b4799e55edceb236522c5b8f958ce9ed8fa9a",
"sha256:9e3dd806f34de38d4c01416344e98eab2437ac450b3ae39c62a0ede2f8b5e4ed",
"sha256:9eb494070aa060ceba6e4bbf44c1bc5fa97bfb883a0d9b0c9049415f9e944793",
"sha256:9fde6b90889522c220dd56a670102ceef24955d994ff7af2cb786b4ba8fe11e4",
"sha256:9fff3ff052922cb99f9e52f63f985d4f7a54f6b94287463bc66b7cdf3eb41217",
"sha256:a06c358f4aed05fa1099c39decc8022261bb07dfadc127c08cfbd1391b09689e",
"sha256:a4f923b9ab265136e57cc14794a15b9dcea07a9c578609cd5dbbfff28a0d15e6",
"sha256:c5b81fb37db76ebea79aa963b76d96ff854e7662921ce742293463635a87a78d",
"sha256:d5ed164af5c9078596cfc40b078c3b337911190d3faeac830c3f1274f26b8320",
"sha256:d651fde74a4d3122e5562705824507e2f5b2d3d57557f1916c4b27635f8fbe3f",
"sha256:de73fca6fb403dd72d4da517cfc49fcf791f74eee697d3219f6be29adf5af6ce",
"sha256:e647a0be741edbb529a72644e999acb09f2ad60465f80757da183528941ff975",
"sha256:e92c7a5f7d62edff50f60a045dc9542bf939758c95b2fcd686175dd10ce0ed10",
"sha256:eeffd96882d8c06d31b65dddcf51db7c612547babc1c4c5db6a011abe9798525",
"sha256:f5a4551dfd09c3bd12fca8144d47fe7745275adf3229b7223c2f9e29a975ebda",
"sha256:fac0bcc5b7e8169bffa87f0dcc24435446d329cbc2b5486d155c2e0f3b493ae1"
],
"markers": "python_version >= '3.7'",
"version": "==6.3"
"version": "==6.3.1"
},
"iniconfig": {
"hashes": [
@@ -948,11 +978,11 @@
},
"pytest": {
"hashes": [
"sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
"sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
"sha256:9ce3ff477af913ecf6321fe337b93a2c0dcf2a0a1439c43f5452112c1e4280db",
"sha256:e30905a0c131d3d94b89624a1cc5afec3e0ba2fbdb151867d8e0ebd49850f171"
],
"index": "pypi",
"version": "==6.2.5"
"version": "==7.0.1"
},
"pytest-cov": {
"hashes": [
@@ -1051,10 +1081,10 @@
},
"tomli": {
"hashes": [
"sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224",
"sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"version": "==2.0.0"
"version": "==2.0.1"
},
"typed-ast": {
"hashes": [
@@ -1088,11 +1118,11 @@
},
"typing-extensions": {
"hashes": [
"sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e",
"sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"
"sha256:1a9462dcc3347a79b1f1c0271fbe79e844580bb598bafa1ed208b94da3cdcd42",
"sha256:21c85e0fe4b9a155d0799430b0ad741cdce7e359660ccbd8b530613e8df88ce2"
],
"index": "pypi",
"version": "==4.0.1"
"version": "==4.1.1"
}
}
}
-3
View File
@@ -1,9 +1,6 @@
LNbits
======
[![github-tests-badge]][github-tests]
[![github-mypy-badge]][github-mypy]
[![codecov-badge]][codecov]
[![license-badge]](LICENSE)
[![docs-badge]][docs]
+6 -3
View File
@@ -13,9 +13,9 @@ LNbits uses [Pipenv][pipenv] to manage Python packages.
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
pipenv shell
# pipenv --python 3.8 shell (if you wish to use a version of Python higher than 3.7)
# pipenv --python 3.9 shell (if you wish to use a version of Python higher than 3.7)
pipenv install --dev
# pipenv --python 3.8 install --dev (if you wish to use a version of Python higher than 3.7)
# pipenv --python 3.9 install --dev (if you wish to use a version of Python higher than 3.7)
# If any of the modules fails to install, try checking and upgrading your setupTool module
# pip install -U setuptools
@@ -41,4 +41,7 @@ E.g. when you want to use LND you have to `pipenv run pip install lndgrpc` and `
Take a look at [Polar][polar] for an excellent way of spinning up a Lightning Network dev environment.
**Note**: We reccomend using <a href="https://caddyserver.com/docs/install#debian-ubuntu-raspbian">Caddy</a> for a reverse-proxy, if you want to serve your install through a domain, alternatively you can use [ngrok](https://ngrok.com/).
**Notes**:
* We reccomend using <a href="https://caddyserver.com/docs/install#debian-ubuntu-raspbian">Caddy</a> for a reverse-proxy if you want to serve your install through a domain, alternatively you can use [ngrok](https://ngrok.com/).
* <a href="https://linuxize.com/post/how-to-use-linux-screen/#starting-linux-screen">Screen</a> works well if you want LNbits to continue running when you close your terminal session.
-1
View File
@@ -28,7 +28,6 @@ Download this repo and install the dependencies:
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
# ensure you have virtualenv installed, on debian/ubuntu 'apt install python3-venv' should work
# for now you'll need to `git checkout FastAPI`
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
cp .env.example .env
+10 -2
View File
@@ -36,16 +36,24 @@ Using this wallet requires the installation of the `grpcio` and `protobuf` Pytho
- `LND_GRPC_ENDPOINT`: ip_address
- `LND_GRPC_PORT`: port
- `LND_GRPC_CERT`: /file/path/tls.cert
- `LND_GRPC_MACAROON`: /file/path/admin.macaroon
- `LND_GRPC_MACAROON`: /file/path/admin.macaroon or Bech64/Hex
You can also use an AES-encrypted macaroon (more info) instead by using
- `LND_GRPC_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
To encrypt your macaroon, run `./venv/bin/python lnbits/wallets/macaroon/macaroon.py`.
### LND (REST)
- `LNBITS_BACKEND_WALLET_CLASS`: **LndRestWallet**
- `LND_REST_ENDPOINT`: ip_address
- `LND_REST_CERT`: /file/path/tls.cert
- `LND_GRPC_MACAROON`: /file/path/admin.macaroon
- `LND_REST_MACAROON`: /file/path/admin.macaroon or Bech64/Hex
or
- `LND_REST_MACAROON_ENCRYPTED`: eNcRyPtEdMaCaRoOn
### LNbits
+16 -2
View File
@@ -6,7 +6,7 @@ from urllib.parse import urlparse
from lnbits import bolt11
from lnbits.db import Connection, POSTGRES, COCKROACH
from lnbits.settings import DEFAULT_WALLET_NAME
from lnbits.settings import DEFAULT_WALLET_NAME, LNBITS_ADMIN_USERS
from . import db
from .models import User, Wallet, Payment, BalanceCheck
@@ -61,6 +61,9 @@ async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[
email=user["email"],
extensions=[e[0] for e in extensions],
wallets=[Wallet(**w) for w in wallets],
admin=user["id"] in [x.strip() for x in LNBITS_ADMIN_USERS]
if LNBITS_ADMIN_USERS
else False,
)
@@ -216,6 +219,8 @@ async def get_payments(
incoming: bool = False,
since: Optional[int] = None,
exclude_uncheckable: bool = False,
limit: Optional[int] = None,
offset: Optional[int] = None,
conn: Optional[Connection] = None,
) -> List[Payment]:
"""
@@ -260,6 +265,15 @@ async def get_payments(
clause.append("checking_id NOT LIKE 'temp_%'")
clause.append("checking_id NOT LIKE 'internal_%'")
limit_clause = f"LIMIT {limit}" if type(limit) == int and limit > 0 else ""
offset_clause = f"OFFSET {offset}" if type(offset) == int and offset > 0 else ""
# combine limit and offset clauses
limit_offset_clause = (
f"{limit_clause} {offset_clause}"
if limit_clause and offset_clause
else limit_clause or offset_clause
)
where = ""
if clause:
where = f"WHERE {' AND '.join(clause)}"
@@ -270,10 +284,10 @@ async def get_payments(
FROM apipayments
{where}
ORDER BY time DESC
{limit_offset_clause}
""",
tuple(args),
)
return [Payment.from_row(row) for row in rows]
+1 -1
View File
@@ -61,7 +61,7 @@
<code
>curl -X POST {{ request.base_url }}api/v1/payments -d '{"out": false,
"amount": &lt;int&gt;, "memo": &lt;string&gt;, "webhook":
&lt;url:string&gt;}' -H "X-Api-Key: <i>{{ wallet.inkey }}</i>" -H
&lt;url:string&gt;, "unit": &lt;string&gt;}' -H "X-Api-Key: <i>{{ wallet.inkey }}</i>" -H
"Content-type: application/json"</code
>
</q-card-section>
+1 -1
View File
@@ -11,7 +11,7 @@
color="primary"
@click="processing"
type="a"
href="{{ url_for('core.lnurlwallet', lightning=lnurl) }}"
href="{{ url_for('core.lnurlwallet') }}?lightning={{ lnurl }}"
>
Press to claim bitcoin
</q-btn>
+23 -5
View File
@@ -62,7 +62,7 @@
</q-btn>
</h3>
</q-card-section>
<div class="row q-pb-md q-px-md q-col-gutter-md">
<div class="row q-pb-md q-px-md q-col-gutter-md gt-sm">
<div class="col">
<q-btn
unelevated
@@ -417,9 +417,11 @@
filled
dense
v-model.number="receive.data.amount"
type="number"
label="Amount ({{LNBITS_DENOMINATION}}) *"
:step="receive.unit != 'sat' ? '0.001' : '1'"
:label="'Amount (' + receive.unit + ') *'"
:mask="receive.unit != 'sat' ? '#.##' : '#'"
fill-mask="0"
reverse-fill-mask
:step="receive.unit != 'sat' ? '0.01' : '1'"
:min="receive.minMax[0]"
:max="receive.minMax[1]"
:readonly="receive.lnurl && receive.lnurl.fixed"
@@ -437,7 +439,7 @@
<q-btn
unelevated
color="primary"
:disable="receive.data.memo == null || receive.data.amount == null || receive.data.amount <= 0"
:disable="receive.data.amount == null || receive.data.amount <= 0"
type="submit"
>
<span v-if="receive.lnurl">
@@ -662,7 +664,23 @@
</q-card-section>
</q-card>
</q-dialog>
<q-tabs
class="lt-md fixed-bottom left-0 right-0 bg-primary text-white shadow-2 z-max"
active-class="px-0"
indicator-color="transparent"
>
<q-tab
icon="account_balance_wallet"
label="Wallets"
@click="g.visibleDrawer = !g.visibleDrawer"
>
</q-tab>
<q-tab icon="content_paste" label="Paste" @click="showParseDialog"> </q-tab>
<q-tab icon="file_download" label="Receive" @click="showReceiveDialog">
</q-tab>
<q-tab icon="photo_camera" label="Scan" @click="showCamera"> </q-tab>
</q-tabs>
{% if service_fee > 0 %}
<div ref="disclaimer"></div>
<q-dialog v-model="disclaimerDialog.show">
+10 -9
View File
@@ -31,6 +31,7 @@ from lnbits.utils.exchange_rates import (
fiat_amount_as_satoshis,
satoshis_amount_as_fiat,
)
from lnbits.settings import LNBITS_SITE_TITLE
from .. import core_app, db
from ..crud import (
@@ -71,7 +72,7 @@ async def api_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
async def api_update_balance(
amount: int, wallet: WalletTypeInfo = Depends(get_key_type)
):
if LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS:
if wallet.wallet.user not in LNBITS_ADMIN_USERS:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not an admin user"
)
@@ -123,7 +124,7 @@ async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)):
class CreateInvoiceData(BaseModel):
out: Optional[bool] = True
amount: int = Query(None, ge=1)
amount: float = Query(None, ge=0)
memo: str = None
unit: Optional[str] = "sat"
description_hash: Optional[str] = None
@@ -140,9 +141,9 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
memo = ""
else:
description_hash = b""
memo = data.memo
memo = data.memo or LNBITS_SITE_TITLE
if data.unit == "sat":
amount = data.amount
amount = int(data.amount)
else:
price_in_sats = await fiat_amount_as_satoshis(data.amount, data.unit)
amount = price_in_sats
@@ -292,11 +293,11 @@ async def api_payments_pay_lnurl(
detail=f"{domain} returned an invalid invoice. Expected {data.amount} msat, got {invoice.amount_msat}.",
)
# if invoice.description_hash != data.description_hash:
# raise HTTPException(
# status_code=HTTPStatus.BAD_REQUEST,
# detail=f"{domain} returned an invalid invoice. Expected description_hash == {data.description_hash}, got {invoice.description_hash}.",
# )
# if invoice.description_hash != data.description_hash:
# raise HTTPException(
# status_code=HTTPStatus.BAD_REQUEST,
# detail=f"{domain} returned an invalid invoice. Expected description_hash == {data.description_hash}, got {invoice.description_hash}.",
# )
extra = {}
+2 -2
View File
@@ -15,8 +15,8 @@ from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from lnbits.helpers import template_renderer, url_for
from lnbits.settings import (
LNBITS_ALLOWED_USERS,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
LNBITS_SITE_TITLE,
SERVICE_FEE,
)
@@ -226,7 +226,7 @@ async def lnurl_balance_notify(request: Request, service: str):
redeem_lnurl_withdraw(bc.wallet, bc.url)
@core_html_routes.get("/lnurlwallet", response_class=RedirectResponse)
@core_html_routes.get("/lnurlwallet", response_class=RedirectResponse, name="core.lnurlwallet")
async def lnurlwallet(request: Request):
async with db.connect() as conn:
account = await create_account(conn=conn)
+10 -3
View File
@@ -13,7 +13,7 @@ from starlette.requests import Request
from lnbits.core.crud import get_user, get_wallet_for_key
from lnbits.core.models import User, Wallet
from lnbits.requestvars import g
from lnbits.settings import LNBITS_ALLOWED_USERS, LNBITS_ADMIN_USERS
from lnbits.settings import LNBITS_ALLOWED_USERS, LNBITS_ADMIN_USERS, LNBITS_ADMIN_EXTENSIONS
class KeyChecker(SecurityBase):
@@ -122,6 +122,7 @@ async def get_key_type(
# 0: admin
# 1: invoice
# 2: invalid
pathname = r['path'].split('/')[1]
if not api_key_header and not api_key_query:
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
@@ -131,7 +132,10 @@ async def get_key_type(
try:
checker = WalletAdminKeyChecker(api_key=token)
await checker.__call__(r)
return WalletTypeInfo(0, checker.wallet)
wallet = WalletTypeInfo(0, checker.wallet)
if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS):
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized.")
return wallet
except HTTPException as e:
if e.status_code == HTTPStatus.BAD_REQUEST:
raise
@@ -143,7 +147,10 @@ async def get_key_type(
try:
checker = WalletInvoiceKeyChecker(api_key=token)
await checker.__call__(r)
return WalletTypeInfo(1, checker.wallet)
wallet = WalletTypeInfo(0, checker.wallet)
if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and (LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS):
raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized.")
return wallet
except HTTPException as e:
if e.status_code == HTTPStatus.BAD_REQUEST:
raise
+1 -1
View File
@@ -30,6 +30,6 @@ from .views import * # noqa
from .views_api import * # noqa
def lnticket_start():
def livestream_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
+1 -1
View File
@@ -1,5 +1,5 @@
from http import HTTPStatus
from mmap import MAP_DENYWRITE
# from mmap import MAP_DENYWRITE
from fastapi.param_functions import Depends
from fastapi.params import Query
+23 -8
View File
@@ -1,4 +1,6 @@
import time
import asyncio
from base64 import urlsafe_b64encode
from http import HTTPStatus
@@ -11,7 +13,7 @@ from lnbits import bolt11
from lnbits.core.crud import delete_expired_invoices, get_payments
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.decorators import WalletTypeInfo
from lnbits.settings import WALLET
from lnbits.settings import WALLET, LNBITS_SITE_TITLE
from . import lndhub_ext
from .decorators import check_wallet, require_admin_key
@@ -55,14 +57,13 @@ async def lndhub_addinvoice(
_, pr = await create_invoice(
wallet_id=wallet.wallet.id,
amount=int(data.amt),
memo=data.memo or "received sats",
memo=data.memo or LNBITS_SITE_TITLE,
extra={"tag": "lndhub"},
)
except:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Failed to create invoice"
)
invoice = bolt11.decode(pr)
return {
"pay_req": pr,
@@ -116,7 +117,9 @@ async def lndhub_balance(
@lndhub_ext.get("/ext/gettxs")
async def lndhub_gettxs(
wallet: WalletTypeInfo = Depends(check_wallet), limit: int = Query(0, ge=0, lt=200)
wallet: WalletTypeInfo = Depends(check_wallet),
limit: int = Query(20, ge=1, le=20),
offset: int = Query(0, ge=0),
):
for payment in await get_payments(
wallet_id=wallet.wallet.id,
@@ -124,11 +127,15 @@ async def lndhub_gettxs(
pending=True,
outgoing=True,
incoming=False,
limit=limit,
offset=offset,
exclude_uncheckable=True,
):
await payment.set_pending(
(await WALLET.get_payment_status(payment.checking_id)).pending
)
await asyncio.sleep(0.1)
return [
{
"payment_preimage": payment.preimage,
@@ -148,28 +155,34 @@ async def lndhub_gettxs(
complete=True,
outgoing=True,
incoming=False,
limit=limit,
offset=offset,
)
)[:limit]
)
)
]
@lndhub_ext.get("/ext/getuserinvoices")
async def lndhub_getuserinvoices(
wallet: WalletTypeInfo = Depends(check_wallet), limit: int = Query(0, ge=0, lt=200)
wallet: WalletTypeInfo = Depends(check_wallet),
limit: int = Query(20, ge=1, le=20),
offset: int = Query(0, ge=0),
):
await delete_expired_invoices()
for invoice in await get_payments(
wallet_id=wallet.wallet.id,
complete=False,
pending=True,
outgoing=False,
incoming=True,
limit=limit,
offset=offset,
exclude_uncheckable=True,
):
await invoice.set_pending(
(await WALLET.get_invoice_status(invoice.checking_id)).pending
)
await asyncio.sleep(0.1)
return [
{
@@ -192,8 +205,10 @@ async def lndhub_getuserinvoices(
complete=True,
incoming=True,
outgoing=False,
limit=limit,
offset=offset,
)
)[:limit]
)
)
]
+22 -11
View File
@@ -1,15 +1,17 @@
import asyncio
import json
from http import HTTPStatus
import httpx
from starlette.exceptions import HTTPException
from lnbits.core import db as core_db
from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener
from lnbits.core.views.api import api_wallet
from lnbits.core.crud import get_wallet
from lnbits.core.views.api import api_payment, api_payments_decode, pay_invoice
from lnbits.core.models import Payment
from lnbits.core.services import pay_invoice
from lnbits.core.views.api import api_payments_decode
from lnbits.tasks import register_invoice_listener
from .crud import get_lnurlpayout, get_lnurlpayout_from_wallet
from .crud import get_lnurlpayout_from_wallet
async def wait_for_paid_invoices():
@@ -25,16 +27,16 @@ async def on_invoice_paid(payment: Payment) -> None:
try:
# Check its got a payout associated with it
lnurlpayout_link = await get_lnurlpayout_from_wallet(payment.wallet_id)
print("LNURLpayout", lnurlpayout_link)
if lnurlpayout_link:
# Check the wallet balance is more than the threshold
wallet = await get_wallet(lnurlpayout_link.wallet)
if wallet.balance < lnurlpayout_link.threshold + (
lnurlpayout_link.threshold * 0.02
):
return
threshold = lnurlpayout_link.threshold + (lnurlpayout_link.threshold * 0.02)
if wallet.balance < threshold:
return
# Get the invoice from the LNURL to pay
async with httpx.AsyncClient() as client:
try:
@@ -43,6 +45,7 @@ async def on_invoice_paid(payment: Payment) -> None:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="LNURL broken"
)
try:
r = await client.get(str(url["domain"]), timeout=40)
res = r.json()
@@ -56,6 +59,12 @@ async def on_invoice_paid(payment: Payment) -> None:
timeout=40,
)
res = r.json()
if hasattr(res, "status") and res["status"] == "ERROR":
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail=res["reason"],
)
try:
await pay_invoice(
wallet_id=payment.wallet_id,
@@ -65,7 +74,9 @@ async def on_invoice_paid(payment: Payment) -> None:
return
except:
pass
except:
except Exception as e:
print("ERROR", str(e))
return
except (httpx.ConnectError, httpx.RequestError):
return
+20 -9
View File
@@ -4,7 +4,8 @@ from fastapi import Query
from fastapi.params import Depends
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user, get_payments
from lnbits.core.crud import get_payments, get_user
from lnbits.core.models import Payment
from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment, api_payments_decode
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
@@ -14,10 +15,10 @@ from .crud import (
create_lnurlpayout,
delete_lnurlpayout,
get_lnurlpayout,
get_lnurlpayouts,
get_lnurlpayout_from_wallet,
get_lnurlpayouts,
)
from .models import lnurlpayout, CreateLnurlPayoutData
from .models import CreateLnurlPayoutData, lnurlpayout
from .tasks import on_invoice_paid
@@ -87,14 +88,24 @@ async def api_lnurlpayout_check(
lnurlpayout_id: str, wallet: WalletTypeInfo = Depends(get_key_type)
):
lnurlpayout = await get_lnurlpayout(lnurlpayout_id)
payments = await get_payments(
wallet_id=lnurlpayout.wallet,
complete=True,
## THIS
mock_payment = Payment(
checking_id="mock",
pending=False,
outgoing=True,
incoming=True,
amount=1,
fee=1,
time=0000,
bolt11="mock",
preimage="mock",
payment_hash="mock",
wallet_id=lnurlpayout.wallet,
)
result = await on_invoice_paid(payments[0])
## INSTEAD OF THIS
# payments = await get_payments(
# wallet_id=lnurlpayout.wallet, complete=True, pending=False, outgoing=True, incoming=True
# )
result = await on_invoice_paid(mock_payment)
return
@@ -77,8 +77,8 @@
mixins: [windowMixin],
data: function () {
return {
userAmount: {{ paywall.amount }},
paywallAmount: {{ paywall.amount }},
userAmount: '{{ paywall.amount }}',
paywallAmount: '{{ paywall.amount }}',
paymentReq: null,
redirectUrl: null,
paymentDialog: {
@@ -89,7 +89,9 @@
},
computed: {
amount: function () {
return (this.paywallAmount > this.userAmount) ? this.paywallAmount : this.userAmount
return this.paywallAmount > this.userAmount
? this.paywallAmount
: this.userAmount
}
},
methods: {
@@ -102,48 +104,55 @@
},
createInvoice: function () {
var self = this
console.log(this.amount)
axios
.post(
'/paywall/api/v1/paywalls/{{ paywall.id }}/invoice',
{amount: self.amount}
LNbits.api
.request(
'POST',
'/paywall/api/v1/paywalls/invoice/{{ paywall.id }}',
'filler',
{
amount: self.amount
}
)
.then(function (response) {
self.paymentReq = response.data.payment_request.toUpperCase()
if (response.data) {
self.paymentReq = response.data.payment_request.toUpperCase()
self.paymentDialog.dismissMsg = self.$q.notify({
timeout: 0,
message: 'Waiting for payment...'
})
self.paymentDialog.dismissMsg = self.$q.notify({
timeout: 0,
message: 'Waiting for payment...'
})
self.paymentDialog.checker = setInterval(function () {
axios
.post(
'/paywall/api/v1/paywalls/{{ paywall.id }}/check_invoice',
{payment_hash: response.data.payment_hash}
)
.then(function (res) {
if (res.data.paid) {
self.cancelPayment()
self.redirectUrl = res.data.url
if (res.data.remembers) {
self.$q.localStorage.set(
'lnbits.paywall.{{ paywall.id }}',
res.data.url
)
self.paymentDialog.checker = setInterval(function () {
LNbits.api
.request(
'POST',
'/paywall/api/v1/paywalls/check_invoice/{{ paywall.id }}',
'filler',
{payment_hash: response.data.payment_hash}
)
.then(function (response) {
if (response.data) {
if (response.data.paid) {
self.cancelPayment()
self.redirectUrl = response.data.url
if (response.data.remembers) {
self.$q.localStorage.set(
'lnbits.paywall.{{ paywall.id }}',
response.data.url
)
}
self.$q.notify({
type: 'positive',
message: 'Payment received!',
icon: null
})
}
}
self.$q.notify({
type: 'positive',
message: 'Payment received!',
icon: null
})
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
}, 2000)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
}, 2000)
}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
+4 -9
View File
@@ -52,20 +52,17 @@ async def api_paywall_delete(
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
@paywall_ext.post("/api/v1/paywalls/{paywall_id}/invoice")
@paywall_ext.post("/api/v1/paywalls/invoice/{paywall_id}")
async def api_paywall_create_invoice(
paywall_id,
data: CreatePaywallInvoice,
wallet: WalletTypeInfo = Depends(get_key_type),
paywall_id: str = Query(None)
):
paywall = await get_paywall(paywall_id)
if data.amount < paywall.amount:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=f"Minimum amount is {paywall.amount} sat.",
)
try:
amount = data.amount if data.amount > paywall.amount else paywall.amount
payment_hash, payment_request = await create_invoice(
@@ -80,15 +77,14 @@ async def api_paywall_create_invoice(
return {"payment_hash": payment_hash, "payment_request": payment_request}
@paywall_ext.post("/api/v1/paywalls/{paywall_id}/check_invoice")
async def api_paywal_check_invoice(data: CheckPaywallInvoice, paywall_id):
@paywall_ext.post("/api/v1/paywalls/check_invoice/{paywall_id}")
async def api_paywal_check_invoice(data: CheckPaywallInvoice, paywall_id: str = Query(None)):
paywall = await get_paywall(paywall_id)
payment_hash = data.payment_hash
if not paywall:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Paywall does not exist."
)
try:
status = await check_invoice_status(paywall.wallet, payment_hash)
is_paid = not status.pending
@@ -101,5 +97,4 @@ async def api_paywal_check_invoice(data: CheckPaywallInvoice, paywall_id):
await payment.set_pending(False)
return {"paid": True, "url": paywall.url, "remembers": paywall.remembers}
return {"paid": False}
@@ -22,7 +22,7 @@
<code>[&lt;withdraw_link_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url }}api/v1/links -H "X-Api-Key: {{
>curl -X GET {{ request.base_url }}withdraw/api/v1/links -H "X-Api-Key: {{
user.wallets[0].inkey }}"
</code>
</q-card-section>
@@ -49,7 +49,7 @@
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url }}api/v1/links/&lt;withdraw_id&gt; -H
>curl -X GET {{ request.base_url }}withdraw/api/v1/links/&lt;withdraw_id&gt; -H
"X-Api-Key: {{ user.wallets[0].inkey }}"
</code>
</q-card-section>
@@ -78,7 +78,7 @@
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ request.base_url }}api/v1/links -d '{"title":
>curl -X POST {{ request.base_url }}withdraw/api/v1/links -d '{"title":
&lt;string&gt;, "min_withdrawable": &lt;integer&gt;,
"max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}' -H
@@ -114,7 +114,7 @@
<code>{"lnurl": &lt;string&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X PUT {{ request.base_url }}api/v1/links/&lt;withdraw_id&gt; -d
>curl -X PUT {{ request.base_url }}withdraw/api/v1/links/&lt;withdraw_id&gt; -d
'{"title": &lt;string&gt;, "min_withdrawable": &lt;integer&gt;,
"max_withdrawable": &lt;integer&gt;, "uses": &lt;integer&gt;,
"wait_time": &lt;integer&gt;, "is_unique": &lt;boolean&gt;}' -H
@@ -142,7 +142,7 @@
<code></code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X DELETE {{ request.base_url }}api/v1/links/&lt;withdraw_id&gt;
>curl -X DELETE {{ request.base_url }}withdraw/api/v1/links/&lt;withdraw_id&gt;
-H "X-Api-Key: {{ user.wallets[0].adminkey }}"
</code>
</q-card-section>
@@ -170,7 +170,7 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url
}}api/v1/links/&lt;the_hash&gt;/&lt;lnurl_id&gt; -H "X-Api-Key: {{
}}withdraw/api/v1/links/&lt;the_hash&gt;/&lt;lnurl_id&gt; -H "X-Api-Key: {{
user.wallets[0].inkey }}"
</code>
</q-card-section>
@@ -191,7 +191,7 @@
>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.base_url }}/withdraw/img/&lt;lnurl_id&gt;"
>curl -X GET {{ request.base_url }}withdraw/img/&lt;lnurl_id&gt;"
</code>
</q-card-section>
</q-card>
+5
View File
@@ -15,6 +15,7 @@ import lnbits.settings as settings
class Extension(NamedTuple):
code: str
is_valid: bool
is_admin_only: bool
name: Optional[str] = None
short_description: Optional[str] = None
icon: Optional[str] = None
@@ -25,6 +26,7 @@ class Extension(NamedTuple):
class ExtensionManager:
def __init__(self):
self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS
self._admin_only: List[str] = [x.strip(' ') for x in settings.LNBITS_ADMIN_EXTENSIONS]
self._extension_folders: List[str] = [
x[1] for x in os.walk(os.path.join(settings.LNBITS_PATH, "extensions"))
][0]
@@ -47,14 +49,17 @@ class ExtensionManager:
) as json_file:
config = json.load(json_file)
is_valid = True
is_admin_only = True if extension in self._admin_only else False
except Exception:
config = {}
is_valid = False
is_admin_only = False
output.append(
Extension(
extension,
is_valid,
is_admin_only,
config.get("name"),
config.get("short_description"),
config.get("icon"),
+1
View File
@@ -29,6 +29,7 @@ LNBITS_ALLOWED_USERS: List[str] = env.list(
"LNBITS_ALLOWED_USERS", default=[], subcast=str
)
LNBITS_ADMIN_USERS: List[str] = env.list("LNBITS_ADMIN_USERS", default=[], subcast=str)
LNBITS_ADMIN_EXTENSIONS: List[str] = env.list("LNBITS_ADMIN_EXTENSIONS", default=[], subcast=str)
LNBITS_DISABLED_EXTENSIONS: List[str] = env.list(
"LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str
)
+37 -11
View File
@@ -111,7 +111,7 @@ window.LNbits = {
'/wallet?' + (userId ? 'usr=' + userId + '&' : '') + 'nme=' + walletName
},
updateWallet: function (walletName, userId, walletId) {
window.location.href = `/wallet?usr=${userId}&wal=${walletId}&nme=${walletName}`
window.location.href = `/wallet?usr=${userId}&wal=${walletId}&nme=${walletName}`
},
deleteWallet: function (walletId, userId) {
window.location.href = '/deletewallet?usr=' + userId + '&wal=' + walletId
@@ -123,6 +123,7 @@ window.LNbits = {
[
'code',
'isValid',
'isAdminOnly',
'name',
'shortDescription',
'icon',
@@ -135,7 +136,12 @@ window.LNbits = {
return obj
},
user: function (data) {
var obj = {id: data.id, email: data.email, extensions: data.extensions, wallets: data.wallets}
var obj = {
id: data.id,
email: data.email,
extensions: data.extensions,
wallets: data.wallets
}
var mapWallet = this.wallet
obj.wallets = obj.wallets
.map(function (obj) {
@@ -153,16 +159,23 @@ window.LNbits = {
return obj
},
wallet: function (data) {
newWallet = {id: data.id, name: data.name, adminkey: data.adminkey, inkey: data.inkey}
newWallet = {
id: data.id,
name: data.name,
adminkey: data.adminkey,
inkey: data.inkey
}
newWallet.msat = data.balance_msat
newWallet.sat = Math.round(data.balance_msat / 1000)
newWallet.fsat = new Intl.NumberFormat(window.LOCALE).format(newWallet.sat)
newWallet.fsat = new Intl.NumberFormat(window.LOCALE).format(
newWallet.sat
)
newWallet.url = ['/wallet?usr=', data.user, '&wal=', data.id].join('')
return newWallet
},
payment: function (data) {
obj = {
checking_id:data.id,
checking_id: data.id,
pending: data.pending,
amount: data.amount,
fee: data.fee,
@@ -174,8 +187,8 @@ window.LNbits = {
extra: data.extra,
wallet_id: data.wallet_id,
webhook: data.webhook,
webhook_status: data.webhook_status,
}
webhook_status: data.webhook_status
}
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
@@ -225,7 +238,8 @@ window.LNbits = {
Quasar.plugins.Notify.create({
timeout: 5000,
type: types[error.response.status] || 'warning',
message: error.response.data.message || error.response.data.detail || null,
message:
error.response.data.message || error.response.data.detail || null,
caption:
[error.response.status, ' ', error.response.statusText]
.join('')
@@ -331,17 +345,25 @@ window.windowMixin = {
}
},
created: function () {
this.$q.dark.set(this.$q.localStorage.getItem('lnbits.darkMode'))
this.g.allowedThemes = window.allowedThemes ?? ['classic']
if(this.$q.localStorage.getItem('lnbits.darkMode') == true || this.$q.localStorage.getItem('lnbits.darkMode') == false){
this.$q.dark.set(this.$q.localStorage.getItem('lnbits.darkMode'))
}
else{
this.$q.dark.set(true)
}
this.g.allowedThemes = window.allowedThemes ?? ['bitcoin']
// failsafe if admin changes themes halfway
if (!this.$q.localStorage.getItem('lnbits.theme')){
this.changeColor(this.g.allowedThemes[0])
}
if (
this.$q.localStorage.getItem('lnbits.theme') &&
!this.g.allowedThemes.includes(
this.$q.localStorage.getItem('lnbits.theme')
)
) {
console.log('allowedThemes changed by Admin', this.g.allowedThemes[0])
this.changeColor(this.g.allowedThemes[0])
}
@@ -368,6 +390,10 @@ window.windowMixin = {
.filter(function (obj) {
return !obj.hidden
})
.filter(function (obj) {
if (window.user.admin) return obj
return !obj.isAdminOnly
})
.map(function (obj) {
if (user) {
obj.isEnabled = user.extensions.indexOf(obj.code) !== -1
+8
View File
@@ -7,6 +7,14 @@ $themes: (
marginal-bg: #1f2234,
marginal-text: #fff
),
'bitcoin': (
primary: #ff9853,
secondary: #ff7353,
dark: #2d293b,
info: #333646,
marginal-bg: #2d293b,
marginal-text: #fff
),
'mint': (
primary: #3ab77d,
secondary: #27b065,
+17 -4
View File
@@ -20,7 +20,7 @@
{% block head_scripts %}{% endblock %}
</head>
<body data-theme="classic">
<body data-theme="bitcoin">
<q-layout id="vue" view="hHh lpR lfr" v-cloak>
<q-header bordered class="bg-marginal-bg">
<q-toolbar>
@@ -67,8 +67,19 @@
icon="format_color_fill"
color="deep-purple"
size="md"
><q-tooltip>classic</q-tooltip> </q-btn
><q-btn
><q-tooltip>classic</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('bitcoin')"
dense
flat
@click="changeColor('bitcoin')"
icon="format_color_fill"
color="orange"
size="md"
><q-tooltip>bitcoin</q-tooltip>
</q-btn>
<q-btn
v-if="g.allowedThemes.includes('mint')"
dense
flat
@@ -151,11 +162,12 @@
</q-page>
</q-page-container>
{% endblock %} {% block footer %}
<q-footer
class="bg-transparent q-px-lg q-py-md"
:class="{'text-dark': !$q.dark.isActive}"
>
<q-toolbar>
<q-toolbar class="gt-sm">
<q-toolbar-title class="text-caption">
{{ SITE_TITLE }}, {{SITE_TAGLINE}}
<br />
@@ -179,6 +191,7 @@
</q-btn>
</q-toolbar>
</q-footer>
{% endblock %}
</q-layout>
+8 -12
View File
@@ -11,6 +11,7 @@ import base64
import hashlib
from os import environ, error, getenv
from typing import Optional, Dict, AsyncGenerator
from .macaroon import load_macaroon, AESCipher
if imports_ok:
import lnbits.wallets.lnd_grpc_files.lightning_pb2 as ln
@@ -58,12 +59,6 @@ def get_ssl_context(cert_path: str):
return context
def load_macaroon(macaroon_path: str):
with open(macaroon_path, "rb") as f:
macaroon_bytes = f.read()
return macaroon_bytes.hex()
def parse_checking_id(checking_id: str) -> bytes:
return base64.b64decode(checking_id.replace("_", "/"))
@@ -90,18 +85,19 @@ class LndWallet(Wallet):
self.port = int(getenv("LND_GRPC_PORT"))
self.cert_path = getenv("LND_GRPC_CERT") or getenv("LND_CERT")
macaroon_path = (
macaroon = (
getenv("LND_GRPC_MACAROON")
or getenv("LND_GRPC_ADMIN_MACAROON")
or getenv("LND_ADMIN_MACAROON")
or getenv("LND_GRPC_INVOICE_MACAROON")
or getenv("LND_INVOICE_MACAROON")
)
if macaroon_path.split(".")[-1] == "macaroon":
self.macaroon = load_macaroon(macaroon_path)
else:
self.macaroon = macaroon_path
encrypted_macaroon = getenv("LND_GRPC_MACAROON_ENCRYPTED")
if encrypted_macaroon:
macaroon = AESCipher(description="macaroon decryption").decrypt(encrypted_macaroon)
self.macaroon = load_macaroon(macaroon)
cert = open(self.cert_path, "rb").read()
creds = grpc.ssl_channel_credentials(cert)
+9 -1
View File
@@ -1,4 +1,5 @@
import asyncio
from pydoc import describe
import httpx
import json
import base64
@@ -6,6 +7,7 @@ from os import getenv
from typing import Optional, Dict, AsyncGenerator
from lnbits import bolt11 as lnbits_bolt11
from .macaroon import load_macaroon, AESCipher
from .base import (
StatusResponse,
@@ -34,7 +36,13 @@ class LndRestWallet(Wallet):
or getenv("LND_INVOICE_MACAROON")
or getenv("LND_REST_INVOICE_MACAROON")
)
self.auth = {"Grpc-Metadata-macaroon": macaroon}
encrypted_macaroon = getenv("LND_REST_MACAROON_ENCRYPTED")
if encrypted_macaroon:
macaroon = AESCipher(description="macaroon decryption").decrypt(encrypted_macaroon)
self.macaroon = load_macaroon(macaroon)
self.auth = {"Grpc-Metadata-macaroon": self.macaroon}
self.cert = getenv("LND_REST_CERT")
async def status(self) -> StatusResponse:
+1
View File
@@ -0,0 +1 @@
from .macaroon import load_macaroon, AESCipher
+103
View File
@@ -0,0 +1,103 @@
from Cryptodome import Random
from Cryptodome.Cipher import AES
import base64
from hashlib import md5
import getpass
BLOCK_SIZE = 16
import getpass
def load_macaroon(macaroon: str) -> str:
"""Returns hex version of a macaroon encoded in base64 or the file path.
:param macaroon: Macaroon encoded in base64 or file path.
:type macaroon: str
:return: Hex version of macaroon.
:rtype: str
"""
# if the macaroon is a file path, load it
if macaroon.split(".")[-1] == "macaroon":
with open(macaroon, "rb") as f:
macaroon_bytes = f.read()
return macaroon_bytes.hex()
else:
# convert the bas64 macaroon to hex
try:
macaroon = base64.b64decode(macaroon).hex()
except:
pass
return macaroon
class AESCipher(object):
"""This class is compatible with crypto-js/aes.js
Encrypt and decrypt in Javascript using:
import AES from "crypto-js/aes.js";
import Utf8 from "crypto-js/enc-utf8.js";
AES.encrypt(decrypted, password).toString()
AES.decrypt(encrypted, password).toString(Utf8);
"""
def __init__(self, key=None, description=""):
self.key = key
self.description = description + " "
def pad(self, data):
length = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
return data + (chr(length) * length).encode()
def unpad(self, data):
return data[: -(data[-1] if type(data[-1]) == int else ord(data[-1]))]
@property
def passphrase(self):
passphrase = self.key if self.key is not None else None
if passphrase is None:
passphrase = getpass.getpass(f"Enter {self.description}password:")
return passphrase
def bytes_to_key(self, data, salt, output=48):
# extended from https://gist.github.com/gsakkis/4546068
assert len(salt) == 8, len(salt)
data += salt
key = md5(data).digest()
final_key = key
while len(final_key) < output:
key = md5(key + data).digest()
final_key += key
return final_key[:output]
def decrypt(self, encrypted: str) -> str:
"""Decrypts a string using AES-256-CBC.
"""
passphrase = self.passphrase
encrypted = base64.b64decode(encrypted)
assert encrypted[0:8] == b"Salted__"
salt = encrypted[8:16]
key_iv = self.bytes_to_key(passphrase.encode(), salt, 32 + 16)
key = key_iv[:32]
iv = key_iv[32:]
aes = AES.new(key, AES.MODE_CBC, iv)
try:
return self.unpad(aes.decrypt(encrypted[16:])).decode()
except UnicodeDecodeError:
raise ValueError("Wrong passphrase")
def encrypt(self, message: bytes) -> str:
passphrase = self.passphrase
salt = Random.new().read(8)
key_iv = self.bytes_to_key(passphrase.encode(), salt, 32 + 16)
key = key_iv[:32]
iv = key_iv[32:]
aes = AES.new(key, AES.MODE_CBC, iv)
return base64.b64encode(b"Salted__" + salt + aes.encrypt(self.pad(message))).decode()
# if this file is executed directly, ask for a macaroon and encrypt it
if __name__ == "__main__":
macaroon = input("Enter macaroon: ")
macaroon = load_macaroon(macaroon)
macaroon = AESCipher(description="encryption").encrypt(macaroon.encode())
print("Encrypted macaroon:")
print(macaroon)
+2 -1
View File
@@ -25,6 +25,7 @@ markupsafe==2.0.1
marshmallow==3.13.0
outcome==1.1.0
psycopg2-binary==2.9.1
pycryptodomex==3.14.1
pydantic==1.8.2
pypng==0.0.21
pyqrcode==1.2.1
@@ -46,4 +47,4 @@ uvicorn==0.15.0
uvloop==0.16.0
watchgod==0.7
websockets==10.0
zipp==3.5.0
zipp==3.5.0
+18 -12
View File
@@ -9,18 +9,21 @@ from tests.helpers import credit_wallet
from tests.extensions.bleskomat.conftest import bleskomat, lnurl
from tests.mocks import WALLET
@pytest.mark.asyncio
async def test_bleskomat_lnurl_api_missing_secret(client):
response = await client.get("/bleskomat/u")
assert response.status_code == 200
assert response.json() == {"status": "ERROR", "reason": "Missing secret"}
@pytest.mark.asyncio
async def test_bleskomat_lnurl_api_invalid_secret(client):
response = await client.get("/bleskomat/u?k1=invalid-secret")
assert response.status_code == 200
assert response.json() == {"status": "ERROR", "reason": "Invalid secret"}
@pytest.mark.asyncio
async def test_bleskomat_lnurl_api_unknown_api_key(client):
query = {
@@ -33,11 +36,12 @@ async def test_bleskomat_lnurl_api_unknown_api_key(client):
"f": "EUR",
}
payload = query_to_signing_payload(query)
signature = "xxx"# not checked, so doesn't matter
response = await client.get(f'/bleskomat/u?{payload}&signature={signature}')
signature = "xxx" # not checked, so doesn't matter
response = await client.get(f"/bleskomat/u?{payload}&signature={signature}")
assert response.status_code == 200
assert response.json() == {"status": "ERROR", "reason": "Unknown API key"}
@pytest.mark.asyncio
async def test_bleskomat_lnurl_api_invalid_signature(client, bleskomat):
query = {
@@ -51,10 +55,11 @@ async def test_bleskomat_lnurl_api_invalid_signature(client, bleskomat):
}
payload = query_to_signing_payload(query)
signature = "invalid"
response = await client.get(f'/bleskomat/u?{payload}&signature={signature}')
response = await client.get(f"/bleskomat/u?{payload}&signature={signature}")
assert response.status_code == 200
assert response.json() == {"status": "ERROR", "reason": "Invalid API key signature"}
@pytest.mark.asyncio
async def test_bleskomat_lnurl_api_valid_signature(client, bleskomat):
query = {
@@ -64,41 +69,42 @@ async def test_bleskomat_lnurl_api_valid_signature(client, bleskomat):
"minWithdrawable": "1",
"maxWithdrawable": "1",
"defaultDescription": "test valid sig",
"f": "EUR",# tests use the dummy exchange rate provider
"f": "EUR", # tests use the dummy exchange rate provider
}
payload = query_to_signing_payload(query)
signature = generate_bleskomat_lnurl_signature(
payload=payload,
api_key_secret=bleskomat.api_key_secret,
api_key_encoding=bleskomat.api_key_encoding
payload=payload, api_key_secret=bleskomat.api_key_secret, api_key_encoding=bleskomat.api_key_encoding
)
response = await client.get(f'/bleskomat/u?{payload}&signature={signature}')
response = await client.get(f"/bleskomat/u?{payload}&signature={signature}")
assert response.status_code == 200
data = response.json()
assert data["tag"] == "withdrawRequest"
assert data["minWithdrawable"] == 1000
assert data["maxWithdrawable"] == 1000
assert data["defaultDescription"] == "test valid sig"
assert data["callback"] == f'http://{HOST}:{PORT}/bleskomat/u'
assert data["callback"] == f"http://{HOST}:{PORT}/bleskomat/u"
k1 = data["k1"]
lnurl = await get_bleskomat_lnurl(secret=k1)
assert lnurl
@pytest.mark.asyncio
async def test_bleskomat_lnurl_api_action_insufficient_balance(client, lnurl):
bleskomat = lnurl["bleskomat"]
secret = lnurl["secret"]
pr = "lntb500n1pseq44upp5xqd38rgad72lnlh4gl339njlrsl3ykep82j6gj4g02dkule7k54qdqqcqzpgxqyz5vqsp5h0zgewuxdxcl2rnlumh6g520t4fr05rgudakpxm789xgjekha75s9qyyssq5vhwsy9knhfeqg0wn6hcnppwmum8fs3g3jxkgw45havgfl6evchjsz3s8e8kr6eyacz02szdhs7v5lg0m7wehd5rpf6yg8480cddjlqpae52xu"
WALLET.pay_invoice.reset_mock()
response = await client.get(f'/bleskomat/u?k1={secret}&pr={pr}')
response = await client.get(f"/bleskomat/u?k1={secret}&pr={pr}")
assert response.status_code == 200
assert response.json() == {"status": "ERROR", "reason": "Failed to pay invoice: Insufficient balance."}
assert response.json()["status"] == "ERROR"
assert ("Insufficient balance" in response.json()["reason"]) or ("fee" in response.json()["reason"])
wallet = await get_wallet(bleskomat.wallet)
assert wallet.balance_msat == 0
bleskomat_lnurl = await get_bleskomat_lnurl(secret)
assert bleskomat_lnurl.has_uses_remaining() == True
WALLET.pay_invoice.assert_not_called()
@pytest.mark.asyncio
async def test_bleskomat_lnurl_api_action_success(client, lnurl):
bleskomat = lnurl["bleskomat"]
@@ -111,7 +117,7 @@ async def test_bleskomat_lnurl_api_action_success(client, lnurl):
wallet = await get_wallet(bleskomat.wallet)
assert wallet.balance_msat == 100000
WALLET.pay_invoice.reset_mock()
response = await client.get(f'/bleskomat/u?k1={secret}&pr={pr}')
response = await client.get(f"/bleskomat/u?k1={secret}&pr={pr}")
assert response.json() == {"status": "OK"}
wallet = await get_wallet(bleskomat.wallet)
assert wallet.balance_msat == 50000