Merge branch 'main' of github.com:lnbits/lnbits-legend

This commit is contained in:
callebtc 2022-07-28 11:55:06 +02:00
commit 51dac0275c
78 changed files with 2095 additions and 294 deletions

View File

@ -5,10 +5,9 @@ on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
if: ${{ 'false' == 'true' }} # skip mypy for now
steps:
- uses: actions/checkout@v1
- uses: jpetrucciani/mypy-check@master
with:
mypy_flags: '--install-types --non-interactive'
path: lnbits
path: 'lnbits'

View File

@ -16,7 +16,6 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Setup Regtest
run: |
docker build -t lnbits-legend .
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker
chmod +x ./tests
@ -39,8 +38,8 @@ jobs:
LNBITS_DATA_FOLDER: ./data
LNBITS_BACKEND_WALLET_CLASS: LndRestWallet
LND_REST_ENDPOINT: https://localhost:8081/
LND_REST_CERT: docker/data/lnd-1/tls.cert
LND_REST_MACAROON: docker/data/lnd-1/data/chain/bitcoin/regtest/admin.macaroon
LND_REST_CERT: ./docker/data/lnd-1/tls.cert
LND_REST_MACAROON: ./docker/data/lnd-1/data/chain/bitcoin/regtest/admin.macaroon
run: |
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
make test-real-wallet
@ -57,7 +56,6 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Setup Regtest
run: |
docker build -t lnbits-legend .
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
cd docker
chmod +x ./tests
@ -79,7 +77,7 @@ jobs:
PORT: 5123
LNBITS_DATA_FOLDER: ./data
LNBITS_BACKEND_WALLET_CLASS: CLightningWallet
CLIGHTNING_RPC: docker/data/clightning-1/regtest/lightning-rpc
CLIGHTNING_RPC: ./docker/data/clightning-1/regtest/lightning-rpc
run: |
sudo chmod -R a+rwx . && rm -rf ./data && mkdir -p ./data
make test-real-wallet

3
.gitignore vendored
View File

@ -35,3 +35,6 @@ coverage.xml
node_modules
lnbits/static/bundle.*
docker
# Nix
*result*

12
Pipfile
View File

@ -36,9 +36,11 @@ pycryptodomex = "*"
[dev-packages]
black = "==20.8b1"
pytest = "*"
pytest-cov = "*"
mypy = "*"
pytest-asyncio = "*"
requests = "*"
mock = "*"
mypy = "*"
pytest = "*"
pytest-asyncio = "*"
pytest-cov = "*"
requests = "*"
types-mock = "*"
types-protobuf = "*"

225
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "503e9942306106e40621c59f37a3ab866b483f8c5f27b879c1c6783dca30949f"
"sha256": "08e53fc6f1fcc021b33f9b2c5ae3bd6dbde815d4b317e9341ab02cf5b625acbc"
},
"pipfile-spec": 6,
"requires": {
@ -29,6 +29,7 @@
"sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b",
"sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"
],
"markers": "python_full_version >= '3.6.2'",
"version": "==3.6.1"
},
"asyncio": {
@ -46,6 +47,7 @@
"sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4",
"sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.4.0"
},
"bech32": {
@ -53,6 +55,7 @@
"sha256:7d6db8214603bd7871fcfa6c0826ef68b85b0abd90fa21c285a9c5e21d2bd899",
"sha256:990dc8e5a5e4feabbdf55207b5315fdd9b73db40be294a19b3752cde9e79d981"
],
"markers": "python_version >= '3.5'",
"version": "==1.2.0"
},
"bitstring": {
@ -76,6 +79,7 @@
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
],
"markers": "python_version >= '3.6'",
"version": "==2022.6.15"
},
"cffi": {
@ -139,6 +143,7 @@
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"ecdsa": {
@ -177,6 +182,7 @@
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
],
"markers": "python_version >= '3.6'",
"version": "==0.12.0"
},
"httpcore": {
@ -184,6 +190,7 @@
"sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6",
"sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"
],
"markers": "python_version >= '3.7'",
"version": "==0.15.0"
},
"httptools": {
@ -238,6 +245,7 @@
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
],
"markers": "python_version >= '3.5'",
"version": "==3.3"
},
"jinja2": {
@ -307,6 +315,7 @@
"sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a",
"sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"
],
"markers": "python_version >= '3.7'",
"version": "==2.1.1"
},
"marshmallow": {
@ -314,6 +323,7 @@
"sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb",
"sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7"
],
"markers": "python_version >= '3.7'",
"version": "==3.17.0"
},
"outcome": {
@ -321,6 +331,7 @@
"sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672",
"sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"
],
"markers": "python_version >= '3.7'",
"version": "==1.2.0"
},
"packaging": {
@ -328,6 +339,7 @@
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
],
"markers": "python_version >= '3.6'",
"version": "==21.3"
},
"psycopg2-binary": {
@ -473,6 +485,7 @@
"sha256:f0f047e11febe5c3198ed346b507e1d010330d56ad615a7e0a89fae604065a0e",
"sha256:fe4670cb32ea98ffbf5a1262f14c3e102cccd92b1869df3bb09538158ba90fe6"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==1.9.1"
},
"pyngrok": {
@ -487,6 +500,7 @@
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
],
"markers": "python_full_version >= '3.6.8'",
"version": "==3.0.9"
},
"pypng": {
@ -517,6 +531,7 @@
"sha256:b7e3b04a59693c42c36f9ab1cc2acc46fa5df8c78e178fc33a8d4cd05c8d498f",
"sha256:d92a187be61fe482e4fd675b6d52200e7be63a12b724abbf931a40ce4fa92938"
],
"markers": "python_version >= '3.5'",
"version": "==0.20.0"
},
"pyyaml": {
@ -562,9 +577,13 @@
"sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0",
"sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.6.0.post0"
},
"rfc3986": {
"extras": [
"idna2008"
],
"hashes": [
"sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835",
"sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"
@ -600,6 +619,14 @@
"index": "pypi",
"version": "==0.14.0"
},
"setuptools": {
"hashes": [
"sha256:0d33c374d41c7863419fc8f6c10bfe25b7b498aa34164d135c622e52580c6b16",
"sha256:c04b44a57a6265fe34a4a444e965884716d34bae963119a76353434d6f18e450"
],
"markers": "python_version >= '3.7'",
"version": "==63.2.0"
},
"shortuuid": {
"hashes": [
"sha256:459f12fa1acc34ff213b1371467c0325169645a31ed989e268872339af7563d5",
@ -613,6 +640,7 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
"version": "==1.16.0"
},
"sniffio": {
@ -620,6 +648,7 @@
"sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663",
"sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"
],
"markers": "python_version >= '3.5'",
"version": "==1.2.0"
},
"sqlalchemy": {
@ -676,17 +705,18 @@
},
"sse-starlette": {
"hashes": [
"sha256:840607fed361360cc76f8408a25f0eca887e7cab3c3ee44f9762f179428e2bd4",
"sha256:ca2de945af80b83f1efda6144df9e13db83880b3b87c660044b64f199395e8b7"
"sha256:14608559d40e3e7c6385e8c5a7b88468f7fc40c2277673a1fe8d26568e8d7c65",
"sha256:72438ed39b1612d1ea6d89a7c0af8afee6de0389dcbe2e77539001e78b5aa89c"
],
"index": "pypi",
"version": "==0.10.3"
"version": "==1.0.0"
},
"starlette": {
"hashes": [
"sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf",
"sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"
],
"markers": "python_version >= '3.6'",
"version": "==0.19.1"
},
"typing-extensions": {
@ -698,6 +728,9 @@
"version": "==4.3.0"
},
"uvicorn": {
"extras": [
"standard"
],
"hashes": [
"sha256:c19a057deb1c5bb060946e2e5c262fc01590c6529c0af2c3d9ce941e89bc30e0",
"sha256:cade07c403c397f9fe275492a48c1b869efd175d5d8a692df649e6e7e2ed8f4e"
@ -815,6 +848,7 @@
"sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4",
"sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.4.0"
},
"black": {
@ -829,6 +863,7 @@
"sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d",
"sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"
],
"markers": "python_version >= '3.6'",
"version": "==2022.6.15"
},
"charset-normalizer": {
@ -836,6 +871,7 @@
"sha256:5189b6f22b01957427f35b6a08d9a0bc45b46d3788ef5a92e978433c7a35f8a5",
"sha256:575e708016ff3a5e3681541cb9d79312c416835686d054a23accb873b254f413"
],
"markers": "python_version >= '3.6'",
"version": "==2.1.0"
},
"click": {
@ -843,9 +879,13 @@
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"coverage": {
"extras": [
"toml"
],
"hashes": [
"sha256:0895ea6e6f7f9939166cc835df8fa4599e2d9b759b02d1521b574e13b859ac32",
"sha256:0f211df2cba951ffcae210ee00e54921ab42e2b64e0bf2c0befc977377fb09b7",
@ -889,6 +929,7 @@
"sha256:f23876b018dfa5d3e98e96f5644b109090f16a4acb22064e0f06933663005d39",
"sha256:f7bd0ffbcd03dc39490a1f40b2669cc414fae0c4e16b77bb26806a4d0b7d1452"
],
"markers": "python_version >= '3.7'",
"version": "==6.4.2"
},
"idna": {
@ -896,6 +937,7 @@
"sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff",
"sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"
],
"markers": "python_version >= '3.5'",
"version": "==3.3"
},
"iniconfig": {
@ -954,6 +996,7 @@
"sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb",
"sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"
],
"markers": "python_version >= '3.6'",
"version": "==21.3"
},
"pathspec": {
@ -968,6 +1011,7 @@
"sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
"sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
],
"markers": "python_version >= '3.6'",
"version": "==1.0.0"
},
"py": {
@ -975,6 +1019,7 @@
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.11.0"
},
"pyparsing": {
@ -982,6 +1027,7 @@
"sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb",
"sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"
],
"markers": "python_full_version >= '3.6.8'",
"version": "==3.0.9"
},
"pytest": {
@ -1010,82 +1056,83 @@
},
"regex": {
"hashes": [
"sha256:00d2e907d3c5e4f85197c8d2263a9cc2d34bf234a9c6236ae42a3fb0bc09b759",
"sha256:0186edcda692c38381db8ac257c2d023fd2e08818d45dc5bee4ed84212045f51",
"sha256:06c509bd7dcb7966bdb03974457d548e54d8327bad5b0c917e87248edc43e2eb",
"sha256:0a3f3f45c5902eb4d90266002ccb035531ae9b9278f6d5e8028247c34d192099",
"sha256:0c1821146b429e6fdbd13ea10f26765e48d5284bc79749468cfbfe3ceb929f0d",
"sha256:0d93167b7d7731fa9ff9fdc1bae84ec9c7133b01a35f8cc04e926d48da6ce1f7",
"sha256:0fd8c3635fa03ef79d07c7b3ed693b3f3930ccb52c0c51761c3296a7525b135c",
"sha256:119091c675e6ad19da8770f89aa1d52f4ad2a2018d631956f3e90c45882df880",
"sha256:121981ba84309dabefd5e1debd49be6d51624e54b4d44bfc184cd8d555ff1df1",
"sha256:1244e9b9b4b81c9c34e8a84273ffaeebdc78abc98a5b02dcdd49845eb3c63bd7",
"sha256:12e1404dfb4e928d3273a10e3468877fe84bdcd3c50b655a2c9613cfc5d9fe63",
"sha256:13d74951c14708f00700bb29475129ecbc40e01b4029c62ee7bfe9d1f59f31ce",
"sha256:162a5939a6fdf48658d3565eeff35acdd207e07367bf5caaff3d9ea7cb77d7a9",
"sha256:1703490c5b850fa9cef1af00c58966756042e6ca22f4fb5bb857345cd535834f",
"sha256:18e6203cfd81df42a987175aaeed7ba46bcb42130cd81763e2d5edcff0006d5d",
"sha256:192c2784833aea6fc7b004730bf1b91b8b8c6b998b30271aaf3bd8adfef20a96",
"sha256:1948d3ceac5b2d55bc93159c1e0679a256a87a54c735be5cef4543a9e692dbb9",
"sha256:206a327e628bc529d64b21ff79a5e2564f5aec7dc7abcd4b2e8a4b271ec10550",
"sha256:2e5db20412f0db8798ff72473d16da5f13ec808e975b49188badb2462f529fa9",
"sha256:2f94b0befc811fe74a972b1739fffbf74c0dc1a91102aca8e324aa4f2c6991bd",
"sha256:303676797c4c7978726e74eb8255d68f7125a3a29da71ff453448f2117290e9a",
"sha256:34ae4f35db30caa4caf85c55069fcb7a05966a3a5ba6e9e1dab5477d84fbb08a",
"sha256:3c6df8be7d1dd35a0d9a200fbc29f888c4452c8882d284f87608046152e049e6",
"sha256:402fa998c5988d11ed34585eb65740dcebd0fd11844d12eb0a6b4be178eb9c64",
"sha256:40a28759d345c0bb1f5b0ac74ac04f5d48136019522c95c0ec4b07786f67ce20",
"sha256:414ae507ba88264444baf771fec43ce0adcd4c5dbb304d3e0716f3f4d4499d2e",
"sha256:42da079e31ae9818ffa7a35cdd16ab7104e3f7eca9c0958040aede827b2e55c6",
"sha256:473a7d21932ce7c314953b33c32e63df690181860edcdf14bba1278cdf71b07f",
"sha256:49fcb45931a693b0e901972c5e077ea2cf30ec39da699645c43cb8b1542c6e14",
"sha256:4c5913cb9769038bd03e42318955c2f15a688384a6a0b807bcfc8271603d9277",
"sha256:4cfeb71095c8d8380a5df5a38ff94d27a3f483717e509130a822b4d6400b7991",
"sha256:4dc74f0171eede67d79a79c06eca0fe5b7b280dbb8c27ad1fae4ced2ad66268f",
"sha256:5b1cffff2d9f832288fe516021cb81c95c57c0067b13a82f1d2daabdbc2f4270",
"sha256:601c99ac775b6c89699a48976f3dbb000b47d3ca59362c8abc9582e6d0780d91",
"sha256:667a06bb8d72b6da3d9cf38dac4ba969688868ed2279a692e993d2c0e1c30aba",
"sha256:673549a0136c7893f567ed71ab5225ed3701c79b17c0a7faee846c645fc24010",
"sha256:67bd3bdd27db7a6460384869dd4b9c54267d805b67d70b20495bb5767f8e051c",
"sha256:727edff0a4eaff3b6d26cbb50216feac9055aba7e6290eec23c061c2fe2fab55",
"sha256:782627a1cb8fbb1c78d8e841f5b71c2c683086c038f975bebdac7cce7678a96f",
"sha256:7d462ba84655abeddae4dfc517fe1afefb5430b3b5acb0a954de12a47aea7183",
"sha256:8ab39aa445d00902c43a1e951871bedc7f18d095a21eccba153d594faac34aea",
"sha256:8e2075ed4ea2e231e2e98b16cfa5dae87e9a6045a71104525e1efc29aa8faa8e",
"sha256:9daeccb2764bf4cc280c40c6411ae176bb0876948e536590a052b3d647254c95",
"sha256:9e4006942334fa954ebd32fa0728718ec870f95f4ba7cda9edc46dd49c294f22",
"sha256:9f1c8fffd4def0b76c0947b8cb261b266e31041785dc2dc2db7569407a2f54fe",
"sha256:a00cd58a30a1041c193777cb1bc090200b05ff4b073d5935738afd1023e63069",
"sha256:a0220a7a16fd4bfc700661f920510defd31ef7830ce992d5cc51777aa8ccd724",
"sha256:a048f91823862270905cb22ef88038b08aac852ce48e0ecc4b4bf1b895ec37d9",
"sha256:a3c47c71fde0c5d584402e67546c81af9951540f1f622d821e9c20761556473a",
"sha256:a6d9ea727fd1233ee746bf44dd37e7d4320b3ed8ff09e73d7638c969b28d280f",
"sha256:ab0709daedc1099bbd4371ae17eeedd4efc1cf70fcdcfe5de1374a0944b61f80",
"sha256:ab1cb36b411f16da6e057ef8e6657dd0af36f59a667f07e0b4b617e44e53d7b2",
"sha256:ae1c5b435d44aa91d48cc710f20c3485e0584a3ad3565d5ae031d61a35f674f4",
"sha256:b279b9bb401af41130fd2a09427105100bc8c624ed45da1c81c1c0d0aa639734",
"sha256:b72a4ec79a15f6066d14ae1c472b743af4b4ecee14420e8d6e4a336b49b8f21c",
"sha256:c2cd93725911c0159d597b90c96151070ef7e0e67604637e2f2abe06c34bf079",
"sha256:c7c5f914b0eb5242c09f91058b80295525897e873b522575ab235b48db125597",
"sha256:d07d849c9e2eca80adb85d3567302a47195a603ad7b1f0a07508e253c041f954",
"sha256:d2672d68cf6c8452b6758fc3cd2d8feac966d511eed79a68182a5297b473af9c",
"sha256:d35bbcbf70d14f724e7489746cf68efe122796578addd98f91428e144d0ad266",
"sha256:d40b4447784dbe0896a6d10a178f6724598161f942c56f5a60dc0ef7fe63f7a1",
"sha256:d561dcb0fb0ab858291837d51330696a45fd3ba6912a332a4ee130e5484b9e47",
"sha256:d7f5ccfff648093152cadf6d886c7bd922047532f72024c953a79c7553aac2fe",
"sha256:dce6b2ad817e3eb107f8704782b091b0631dd3adf47f14bdc086165d05b528b0",
"sha256:e1fdda3ec7e9785065b67941693995cab95b54023a21db9bf39e54cc7b2c3526",
"sha256:e2a262ec85c595fc8e1f3162cafc654d2219125c00ea3a190c173cea70d2cc7a",
"sha256:e2fc1e3928c1189c0382c547c17717c6d9f425fffe619ef94270fe4c6c8be0a6",
"sha256:ea27acd97a752cfefa9907da935e583efecb302e6e9866f37565968c8407ad58",
"sha256:ee769a438827e443ed428e66d0aa7131c653ecd86ddc5d4644a81ed1d93af0e7",
"sha256:f32e0d1c7e7b0b9c3cac76f3d278e7ee6b99c95672d2c1c6ea625033431837c0",
"sha256:f355caec5bbce20421dc26e53787b10e32fd0df68db2b795435217210c08d69c",
"sha256:f87e9108bb532f8a1fc6bf7e69b930a35c7b0267b8fef0a3ede0bcb4c5aaa531",
"sha256:f8a2fd2f62a77536e4e3193303bec380df40d99e253b1c8f9b6eafa07eaeff67",
"sha256:fbdf4fc6adf38fab1091c579ece3fe9f493bd0f1cfc3d2c76d2e52461ca4f8a9"
"sha256:045682b6457f0224deb10c9720b8008dc798b1ad4331de9302fd4b615211e5a5",
"sha256:04e3869fc6fc24b75d38e7547797bb0a82d6cccd49e8ce6ae21a0b87aeb9fac7",
"sha256:073bd76a1f03e05a6ca0df705b6117f75b10c340af068b55becb1334fc6426c2",
"sha256:0aa48740a1385cb668ffd828a7e2c8078ce29c72d64651c8226bd2b7cb5dba0c",
"sha256:0e380ebd841201f980ab022d07033be12c9438a9b2e0f60324c9e3ba31790918",
"sha256:0f9b8ef2e46d627b2a85d3f4fe433ead283c420adcf9461906c3db10766dd3b1",
"sha256:10d3a5dda21a125cfdc31e45ae6ce6bdd1e45cb194199801248d6580be8dc337",
"sha256:11965ae779a0ccfb6d17996d531e2f522124e04d98cc65742b96bf8f50758ab9",
"sha256:1c61175730596266015c4e005c65cbfeeb1ef019ccd024870169c6593a26bdb0",
"sha256:1d1d79a87a33fbd6573e30eb53969a4190035894c4390a0787fb823e9e86b72f",
"sha256:1f0bf228c948f543876f4fb310322a4ff7e398667dd58aeb4815dc9e30bf867e",
"sha256:2026a1c108752e48577f9720076bf6e31a60aaf0c3000fffad4e2527fdffbe95",
"sha256:204705b7ec16267d39870a19e72e832b12739dc48a26d923a9cb94043660d50f",
"sha256:248b16534d1ef8f10a72cda0f97b3dfa25b3d9123a7e726d1594cb07a541bba0",
"sha256:27b5011449213dfd880e592ea6d311d00739e87d9512bad507ee18c9c92a20ac",
"sha256:2880d21e9507869ab1636e50461afb9ffb08797f1cb76f70d3ad52e7dd13a335",
"sha256:2d3f9fc885ecd8b0eb248d0e190aa7264b977cc23b6da7c08444065170c57e2e",
"sha256:2d8b50ae3cbaaa2e5ed89ed81fc025ec64b1a54c4f34e6bdaad9dc63fc2afa6b",
"sha256:3ff5b2b6a136307a5551e7821d83ab12c46f57c32bf23a27877c9c6bdb55aa61",
"sha256:40b4436466d47271fe3b4df63e55307c91a40cda0875a9ff3b7231a08394b283",
"sha256:435c94d5939a7cf4b0af1cce30d196451bae441ebe64f63d08517ab490ceb385",
"sha256:4c0b7c413d4a8a55d72df18acbeb50276eee19cec7e2f54ed3bddc46bc3b3aeb",
"sha256:4cfe87490d0a801749b42491ef7e968342e5787decbf57d5402fc2c17f7302e3",
"sha256:4f480661cd0809a1177b09581c12c9ceff9ca989e4a0c8c0f10379dffa3b4c4c",
"sha256:53370620db6058dbb464324b053fee8608518d76ff6352b2835d71e2ae8ef293",
"sha256:53eca0cece2ddb592b8dd9746f0b258d0c8a45f5a3ac8eb96833058f64778fca",
"sha256:5751bfe0d939d7110396510a39e48ee928b36b55177207c47766b093886a3945",
"sha256:5d750f99c40a7e994df1cf1295bbb3e873417ca69508664fe9f65db92e46ca40",
"sha256:6019737db5c46a24f307eae5069fced0752e3a22380398bbefbef77b068b9537",
"sha256:605ca47681c7405723a4970d66d13fa3a3a66efa6b8499d7ab7bce1ddb44a36f",
"sha256:69120a8fb1eb932b6e3ededb16448be6444eb952f9350c21dddbef947fea5690",
"sha256:72713de336b8d895f91aad34f5591f33d1d8727bd739af3ae2657411ef6e0739",
"sha256:74077b462a9b255c5fca247484f46b0f25c32676fe4645cb6b5304b2d997357a",
"sha256:74f0067495a842f7cc198b14031a2893d377bed38e19d785f35095082ab5a556",
"sha256:817a0618c149d77e493963cd98851ff49d6ab8bcab247fdbf85bb89a14dca5c3",
"sha256:869a0f6405ec569863e09909617138af575b5e2bc5181184e60f339a4c8a6d7a",
"sha256:86b0cf786efd587c27abd1d07020c555a82275bba3506d916d42aed7a3744967",
"sha256:8cbc407c44003a1cb4aaa2d48cc19b45dde07ba0ae2f541c6750ad18f8c118f5",
"sha256:90e082f262cf858cbcde330999ea5612e12918982033b716d2c5d8b1bc7a01db",
"sha256:95d8f1083ff4546ec14fb46dc41b042d372258f8c319df1e2316d8fe1bd3f085",
"sha256:97211bae1bc51f153764485a54d8d1130196cae33d02285c33732b26c5328b8d",
"sha256:98dbaf6a86991e2b09f4d8a7669b4304755bda519565971dc3b87ee00fd6eab2",
"sha256:9e297ff33172853e9a9e46dbb0c2ebf44fb38ebefce659698df4eb9dfce0a748",
"sha256:9e64492c8105312f080e25e457db70f9b0d02e6ba3c1ea14468087b0e3aa876f",
"sha256:a11241808e59deec8314792bbd8a6f0a8a7a95b742709e134c73a3216dbb26ae",
"sha256:a33c36dcc1760d66f1969d7d3dc8956f45a3d502178053074b8489f67718138f",
"sha256:a4e1e7ba8c58c1f0b828418f9a2635c0f6344bf107308b8fe65f234a13c8462a",
"sha256:a55c4d5e5076cc5ece625dd1f7015c9a0818ba1f9ad9db421b495d7ece088e56",
"sha256:a6f5cc82e1fa380eb6b8040d626df6ba9f492b6886527f53d59838b11e9caaef",
"sha256:a96826b5c9dc68417ddb29843998473f9c2c047911e6fca36a9f81a898087b01",
"sha256:acf6cbcc19d86f44e8a9d3cf1f6946a71dc55c2ec8ae374c547b1eefd83b954f",
"sha256:b4be25d3c640a35671431d8ef8cd522319254074b150147fcacad90c91ea42c2",
"sha256:b5a31abc27f9bda7a455bfa1e1bd623da50e3a343a040084c879d07394a93481",
"sha256:bb812e590e3881a93d4d291270440b3795fa4c0bc1b03ba15fe1cc88d2ea4347",
"sha256:bbd542b4afd2ed5491a480e9b15f4bca13e0170ef1895064fd15741382fdebe0",
"sha256:bf27111f56b762238bd3ce4c5e8ad34167d85dd3c077721a0c093517526a94af",
"sha256:c7a4dc9436b7a55c36daa3959e92d70337c547651ebeda685dd6bb083f0b77ab",
"sha256:c88a1068ac8e5dc579d5104903fa2c488448c1137e580a77d1438d98070c4243",
"sha256:c8cffb5040bf432355cfe51378072f20087609694066ace80710bdba04cf9ce2",
"sha256:c944921b0e77f1923dd89cda65534223ac107e24d71b1dfe174237faa5efd32a",
"sha256:ca2a7233275acf0087ecd15e5fb0eeb722a1f4de453b49bb1443edf2c2f5a997",
"sha256:cf0a3b9744f94693f3ebcca1c259354f0043c19a4ce938f80ad6d1816b8fd8f0",
"sha256:dbe2f16a66f64a00dc9ccc0db7f8b5ff014f409840e03675eb431f03b50ddffd",
"sha256:deb7b067b3b9751c60dc7f6de68476138d550c074a5016ba944cc55863fa86d1",
"sha256:df669bacbda209e9b00928f1d00432b27a16c3e051f9f7e5ea306f9b78bf3e7c",
"sha256:e2dd4ca82c2241be9582d2ae060070f2bccb0c98295b608009d5cc6e6041eaed",
"sha256:e432cf909c53506da4c8308753b2671ee37d2d8d1de8b4b54ab76e91ca7ba0b5",
"sha256:e4e7f1aba3aaf08e11d33fd5c2d8dd8cbf573049474e11256c91e3ba3d5e1642",
"sha256:e51ab7fbfe5ac3002b9aee527bcb164b17fd92f5663ebf2a4e5917dd9d577864",
"sha256:ea00c7f86405d88995e7bab5609e343fdedfe1ffc8191d3b5ed0f8c7f5eb17ec",
"sha256:f7006d7c74e25f8bc592604a5a72ba624f10ebd5c0683ab4d3e940a88ac0098c",
"sha256:f93e3e5acf82812ea92a1ccdcce690aab18c4044dd824f6b959d2b6069d84312",
"sha256:fa8a4bc81b15f49c57ede3fd636786c6619179661acf2430fcc387d75bf28d33",
"sha256:fc44c49f33dd75e58b5ff2a5ac50c96c84b6b209d36b4790c85bca08a3b9017d"
],
"version": "==2022.7.9"
"markers": "python_version >= '3.6'",
"version": "==2022.7.24"
},
"requests": {
"hashes": [
@ -1100,6 +1147,7 @@
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'",
"version": "==0.10.2"
},
"tomli": {
@ -1107,6 +1155,7 @@
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.1"
},
"typed-ast": {
@ -1136,8 +1185,25 @@
"sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3",
"sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"
],
"markers": "python_version >= '3.6'",
"version": "==1.5.4"
},
"types-mock": {
"hashes": [
"sha256:4535fbb3912b88a247d43cdb41db0c8b2e187138986f6f01a989717e56105848",
"sha256:a849bc2d966063f4946013bf404822ee2b96f77a8dccda4174b70ab61c5293fe"
],
"index": "pypi",
"version": "==4.0.15"
},
"types-protobuf": {
"hashes": [
"sha256:d291388678af91bb045fafa864f142dc4ac22f5d4cdca097c7d8d8a32fa9b3ab",
"sha256:d2b26861b0cb46a3c8669b0df507b7ef72e487da66d61f9f3576aa76ce028a83"
],
"index": "pypi",
"version": "==3.19.22"
},
"typing-extensions": {
"hashes": [
"sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02",
@ -1151,6 +1217,7 @@
"sha256:8298d6d56d39be0e3bc13c1c97d133f9b45d797169a0e11cdd0e0489d786f7ec",
"sha256:879ba4d1e89654d9769ce13121e0f94310ea32e8d2f8cf587b77c08bbcdb30d6"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5' and python_version < '4'",
"version": "==1.26.10"
}
}

View File

@ -7,7 +7,7 @@ LNbits
![Lightning network wallet](https://i.imgur.com/EHvK6Lq.png)
# LNbits v0.3 BETA, free and open-source lightning-network wallet/accounts system
# LNbits v0.9 BETA, free and open-source lightning-network wallet/accounts system
(Join us on [https://t.me/lnbits](https://t.me/lnbits))

110
build.py Normal file
View File

@ -0,0 +1,110 @@
import warnings
import subprocess
import glob
import os
from os import path
from typing import Any, List, NamedTuple, Optional
from pathlib import Path
LNBITS_PATH = path.dirname(path.realpath(__file__)) + "/lnbits"
def get_js_vendored(prefer_minified: bool = False) -> List[str]:
paths = get_vendored(".js", prefer_minified)
def sorter(key: str):
if "moment@" in key:
return 1
if "vue@" in key:
return 2
if "vue-router@" in key:
return 3
if "polyfills" in key:
return 4
return 9
return sorted(paths, key=sorter)
def get_css_vendored(prefer_minified: bool = False) -> List[str]:
paths = get_vendored(".css", prefer_minified)
def sorter(key: str):
if "quasar@" in key:
return 1
if "vue@" in key:
return 2
if "chart.js@" in key:
return 100
return 9
return sorted(paths, key=sorter)
def get_vendored(ext: str, prefer_minified: bool = False) -> List[str]:
paths: List[str] = []
for path in glob.glob(
os.path.join(LNBITS_PATH, "static/vendor/**"), recursive=True
):
if path.endswith(".min" + ext):
# path is minified
unminified = path.replace(".min" + ext, ext)
if prefer_minified:
paths.append(path)
if unminified in paths:
paths.remove(unminified)
elif unminified not in paths:
paths.append(path)
elif path.endswith(ext):
# path is not minified
minified = path.replace(ext, ".min" + ext)
if not prefer_minified:
paths.append(path)
if minified in paths:
paths.remove(minified)
elif minified not in paths:
paths.append(path)
return sorted(paths)
def url_for_vendored(abspath: str) -> str:
return "/" + os.path.relpath(abspath, LNBITS_PATH)
def transpile_scss():
with warnings.catch_warnings():
warnings.simplefilter("ignore")
from scss.compiler import compile_string # type: ignore
with open(os.path.join(LNBITS_PATH, "static/scss/base.scss")) as scss:
with open(os.path.join(LNBITS_PATH, "static/css/base.css"), "w") as css:
css.write(compile_string(scss.read()))
def bundle_vendored():
for getfiles, outputpath in [
(get_js_vendored, os.path.join(LNBITS_PATH, "static/bundle.js")),
(get_css_vendored, os.path.join(LNBITS_PATH, "static/bundle.css")),
]:
output = ""
for path in getfiles():
with open(path) as f:
output += "/* " + url_for_vendored(path) + " */\n" + f.read() + ";\n"
with open(outputpath, "w") as f:
f.write(output)
def build():
transpile_scss()
bundle_vendored()
# root = Path("lnbits/static/foo")
# root.mkdir(parents=True)
# root.joinpath("example.css").write_text("")
if __name__ == "__main__":
build()
#def build(setup_kwargs):
# """Build """
# transpile_scss()
# bundle_vendored()
# subprocess.run(["ls", "-la", "./lnbits/static"])

View File

@ -8,13 +8,32 @@ nav_order: 2
# Basic installation
You can choose between two python package managers, `venv` and `pipenv`. Both are fine but if you don't know what you're doing, just go for the first option.
You can choose between four package managers, `poetry`, `pipenv`, `venv` and `nix`.
By default, LNbits will use SQLite as its database. You can also use PostgreSQL which is recommended for applications with a high load (see guide below).
## Option 1: pipenv
## Option 1: poetry
You can also use Pipenv to manage your python packages.
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
curl -sSL https://install.python-poetry.org | python3 -
poetry install
# You may need to install python 3.9, update your python following this guide https://linuxize.com/post/how-to-install-python-3-9-on-ubuntu-20-04/
mkdir data && cp .env.example .env
```
#### Running the server
```sh
poetry run lnbits
# To change port/host pass 'poetry run lnbits --port 9000 --host 0.0.0.0'
```
## Option 2: pipenv
```sh
git clone https://github.com/lnbits/lnbits-legend.git
@ -44,9 +63,7 @@ pipenv run python -m uvicorn lnbits.__main__:app --port 5000 --host 0.0.0.0
Add the flag `--reload` for development (includes hot-reload).
## Option 2: venv
Download this repo and install the dependencies:
## Option 3: venv
```sh
git clone https://github.com/lnbits/lnbits-legend.git
@ -67,6 +84,26 @@ mkdir data && cp .env.example .env
If you want to host LNbits on the internet, run with the option `--host 0.0.0.0`.
## Option 4: Nix
```sh
git clone https://github.com/lnbits/lnbits-legend.git
cd lnbits-legend/
# Install nix, modern debian distros usually already include
sh <(curl -L https://nixos.org/nix/install) --daemon
nix build .#lnbits
mkdir data
```
#### Running the server
```sh
# .env variables are currently passed when running
LNBITS_DATA_FOLDER=data LNBITS_BACKEND_WALLET_CLASS=LNbitsWallet LNBITS_ENDPOINT=https://legend.lnbits.com LNBITS_KEY=7b1a78d6c78f48b09a202f2dcb2d22eb ./result/bin/lnbits --port 9000
```
### Troubleshooting
Problems installing? These commands have helped us install LNbits.

View File

@ -17,7 +17,6 @@ A backend wallet can be configured using the following LNbits environment variab
### CLightning
Using this wallet requires the installation of the `pylightning` Python package.
If you want to use LNURLp you should use SparkWallet because of an issue with description_hash and CLightning.
- `LNBITS_BACKEND_WALLET_CLASS`: **CLightningWallet**
- `CLIGHTNING_RPC`: /file/path/lightning-rpc

77
flake.lock generated Normal file
View File

@ -0,0 +1,77 @@
{
"nodes": {
"flake-utils": {
"locked": {
"lastModified": 1656928814,
"narHash": "sha256-RIFfgBuKz6Hp89yRr7+NR5tzIAbn52h8vT6vXkYjZoM=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "7e2a3b3dfd9af950a856d66b0a7d01e3c18aa249",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1657114324,
"narHash": "sha256-fWuaUNXrHcz/ciHRHlcSO92dvV3EVS0GJQUSBO5JIB4=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "a5c867d9fe9e4380452628e8f171c26b69fa9d3d",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1657261001,
"narHash": "sha256-sUZeuRYfhG59uD6xafM07bc7bAIkpcGq84Vj4B+cyms=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "0be91cefefde5701f8fa957904618a13e3bb51d8",
"type": "github"
},
"original": {
"owner": "NixOS",
"repo": "nixpkgs",
"type": "github"
}
},
"poetry2nix": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1657149754,
"narHash": "sha256-iSnZoqwNDDVoO175whSuvl4sS9lAb/2zZ3Sa4ywo970=",
"owner": "nix-community",
"repo": "poetry2nix",
"rev": "fc1930e011dea149db81863aac22fe701f36f1b5",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "poetry2nix",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
"poetry2nix": "poetry2nix"
}
}
},
"root": "root",
"version": 7
}

55
flake.nix Normal file
View File

@ -0,0 +1,55 @@
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
poetry2nix.url = "github:nix-community/poetry2nix";
};
outputs = { self, nixpkgs, poetry2nix }@inputs:
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" ];
forSystems = systems: f:
nixpkgs.lib.genAttrs systems
(system: f system (import nixpkgs { inherit system; overlays = [ poetry2nix.overlay self.overlays.default ]; }));
forAllSystems = forSystems supportedSystems;
projectName = "lnbits";
in
{
devShells = forAllSystems (system: pkgs: {
default = pkgs.mkShell {
buildInputs = with pkgs; [
nodePackages.prettier
];
};
});
overlays = {
default = final: prev: {
${projectName} = self.packages.${final.hostPlatform.system}.${projectName};
};
};
packages = forAllSystems (system: pkgs: {
default = self.packages.${system}.${projectName};
${projectName} = pkgs.poetry2nix.mkPoetryApplication {
projectDir = ./.;
python = pkgs.python39;
};
});
nixosModules = {
default = { pkgs, lib, config, ... }: {
imports = [ "${./nix/modules/${projectName}-service.nix}" ];
nixpkgs.overlays = [ self.overlays.default ];
};
};
checks = forAllSystems (system: pkgs:
let
vmTests = import ./nix/tests {
makeTest = (import (nixpkgs + "/nixos/lib/testing-python.nix") { inherit system; }).makeTest;
inherit inputs pkgs;
};
in
pkgs.lib.optionalAttrs pkgs.stdenv.isLinux vmTests # vmTests can only be ran on Linux, so append them only if on Linux.
//
{
# Other checks here...
}
);
};
}

View File

@ -4,7 +4,7 @@ import uvloop
from loguru import logger
from starlette.requests import Request
from .commands import bundle_vendored, migrate_databases, transpile_scss
from .commands import migrate_databases
from .settings import (
DEBUG,
HOST,
@ -19,8 +19,6 @@ from .settings import (
uvloop.install()
asyncio.create_task(migrate_databases())
transpile_scss()
bundle_vendored()
from .app import create_app

View File

@ -17,7 +17,6 @@ from loguru import logger
import lnbits.settings
from lnbits.core.tasks import register_task_listeners
from .commands import db_migrate, handle_assets
from .core import core_app
from .core.views.generic import core_html_routes
from .helpers import (
@ -45,10 +44,19 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"""
configure_logger()
app = FastAPI()
app.mount("/static", StaticFiles(directory="lnbits/static"), name="static")
app = FastAPI(
title="LNbits API",
description="API for LNbits, the free and open source bitcoin wallet and accounts system with plugins.",
license_info={
"name": "MIT License",
"url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE",
},
)
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
app.mount(
"/core/static", StaticFiles(directory="lnbits/core/static"), name="core_static"
"/core/static",
StaticFiles(packages=[("lnbits.core", "static")]),
name="core_static",
)
origins = ["*"]
@ -84,7 +92,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
check_funding_source(app)
register_assets(app)
register_routes(app)
# register_commands(app)
register_async_tasks(app)
register_exception_handlers(app)
@ -137,12 +144,6 @@ def register_routes(app: FastAPI) -> None:
)
def register_commands(app: FastAPI):
"""Register Click commands."""
app.cli.add_command(db_migrate)
app.cli.add_command(handle_assets)
def register_assets(app: FastAPI):
"""Serve each vendored asset separately or a bundle."""

View File

@ -113,7 +113,7 @@ async def create_wallet(
async def update_wallet(
wallet_id: str, new_name: str, conn: Optional[Connection] = None
) -> Optional[Wallet]:
await (conn or db).execute(
return await (conn or db).execute(
"""
UPDATE wallets SET
name = ?

View File

@ -106,6 +106,8 @@ class Payment(BaseModel):
@property
def tag(self) -> Optional[str]:
if self.extra is None:
return ""
return self.extra.get("tag")
@property

View File

@ -109,18 +109,15 @@ async def pay_invoice(
raise ValueError("Amount in invoice is too high.")
# put all parameters that don't change here
PaymentKwargs = TypedDict(
"PaymentKwargs",
{
"wallet_id": str,
"payment_request": str,
"payment_hash": str,
"amount": int,
"memo": str,
"extra": Optional[Dict],
},
)
payment_kwargs: PaymentKwargs = dict(
class PaymentKwargs(TypedDict):
wallet_id: str
payment_request: str
payment_hash: str
amount: int
memo: str
extra: Optional[Dict]
payment_kwargs: PaymentKwargs = PaymentKwargs(
wallet_id=wallet_id,
payment_request=payment_request,
payment_hash=invoice.payment_hash,
@ -272,6 +269,7 @@ async def perform_lnurlauth(
cb = urlparse(callback)
k1 = unhexlify(parse_qs(cb.query)["k1"][0])
key = wallet.wallet.lnurlauth_key(cb.netloc)
def int_to_bytes_suitable_der(x: int) -> bytes:

View File

@ -55,7 +55,7 @@ async def dispatch_webhook(payment: Payment):
data = payment.dict()
try:
logger.debug("sending webhook", payment.webhook)
r = await client.post(payment.webhook, json=data, timeout=40)
r = await client.post(payment.webhook, json=data, timeout=40) # type: ignore
await mark_webhook_sent(payment, r.status_code)
except (httpx.ConnectError, httpx.RequestError):
await mark_webhook_sent(payment, -1)

View File

@ -3,7 +3,7 @@ import hashlib
import json
from binascii import unhexlify
from http import HTTPStatus
from typing import Dict, List, Optional, Union
from typing import Dict, List, Optional, Tuple, Union
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import httpx
@ -185,7 +185,7 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
assert (
data.lnurl_balance_check is not None
), "lnurl_balance_check is required"
save_balance_check(wallet.id, data.lnurl_balance_check)
await save_balance_check(wallet.id, data.lnurl_balance_check)
async with httpx.AsyncClient() as client:
try:
@ -248,7 +248,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(...),
invoiceData: CreateInvoiceData = Body(...), # type: ignore
):
if invoiceData.out is True and wallet.wallet_type == 0:
if not invoiceData.bolt11:
@ -291,7 +291,7 @@ async def api_payments_pay_lnurl(
timeout=40,
)
if r.is_error:
raise httpx.ConnectError
raise httpx.ConnectError("LNURL callback connection error")
except (httpx.ConnectError, httpx.RequestError):
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
@ -354,7 +354,7 @@ async def subscribe(request: Request, wallet: Wallet):
logger.debug("adding sse listener", payment_queue)
api_invoice_listeners.append(payment_queue)
send_queue: asyncio.Queue[tuple[str, Payment]] = asyncio.Queue(0)
send_queue: asyncio.Queue[Tuple[str, Payment]] = asyncio.Queue(0)
async def payment_received() -> None:
while True:
@ -393,16 +393,13 @@ async def api_payments_sse(
async def api_payment(payment_hash, X_Api_Key: Optional[str] = Header(None)):
# We use X_Api_Key here because we want this call to work with and without keys
# If a valid key is given, we also return the field "details", otherwise not
wallet = None
try:
if X_Api_Key.extra:
logger.warning("No key")
except:
wallet = await get_wallet_for_key(X_Api_Key)
wallet = await get_wallet_for_key(X_Api_Key) if type(X_Api_Key) == str else None
# we have to specify the wallet id here, because postgres and sqlite return internal payments in different order
# and get_standalone_payment otherwise just fetches the first one, causing unpredictable results
payment = await get_standalone_payment(
payment_hash, wallet_id=wallet.id if wallet else None
) # we have to specify the wallet id here, because postgres and sqlite return internal payments in different order
# and get_standalone_payment otherwise just fetches the first one, causing unpredictable results
)
if payment is None:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Payment does not exist."
@ -488,7 +485,8 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
)
try:
tag = data["tag"]
tag: str = data.get("tag")
params.update(**data)
if tag == "channelRequest":
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
@ -498,10 +496,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
"message": "unsupported",
},
)
params.update(**data)
if tag == "withdrawRequest":
elif tag == "withdrawRequest":
params.update(kind="withdraw")
params.update(fixed=data["minWithdrawable"] == data["maxWithdrawable"])
@ -519,8 +514,7 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
query=urlencode(qs, doseq=True)
)
params.update(callback=urlunparse(parsed_callback))
if tag == "payRequest":
elif tag == "payRequest":
params.update(kind="pay")
params.update(fixed=data["minSendable"] == data["maxSendable"])
@ -538,8 +532,8 @@ async def api_lnurlscan(code: str, wallet: WalletTypeInfo = Depends(get_key_type
params.update(image=data_uri)
if k == "text/email" or k == "text/identifier":
params.update(targetUser=v)
params.update(commentAllowed=data.get("commentAllowed", 0))
except KeyError as exc:
raise HTTPException(
status_code=HTTPStatus.SERVICE_UNAVAILABLE,
@ -612,8 +606,8 @@ class ConversionData(BaseModel):
async def api_fiat_as_sats(data: ConversionData):
output = {}
if data.from_ == "sat":
output["sats"] = int(data.amount)
output["BTC"] = data.amount / 100000000
output["sats"] = int(data.amount)
for currency in data.to.split(","):
output[currency.strip().upper()] = await satoshis_amount_as_fiat(
data.amount, currency.strip()

View File

@ -55,9 +55,9 @@ async def home(request: Request, lightning: str = None):
)
async def extensions(
request: Request,
user: User = Depends(check_user_exists),
enable: str = Query(None),
disable: str = Query(None),
user: User = Depends(check_user_exists), # type: ignore
enable: str = Query(None), # type: ignore
disable: str = Query(None), # type: ignore
):
extension_to_enable = enable
extension_to_disable = disable
@ -88,7 +88,7 @@ async def extensions(
# Update user as his extensions have been updated
if extension_to_enable or extension_to_disable:
user = await get_user(user.id)
user = await get_user(user.id) # type: ignore
return template_renderer().TemplateResponse(
"core/extensions.html", {"request": request, "user": user.dict()}
@ -109,10 +109,10 @@ nothing: create everything<br>
""",
)
async def wallet(
request: Request = Query(None),
nme: Optional[str] = Query(None),
usr: Optional[UUID4] = Query(None),
wal: Optional[UUID4] = Query(None),
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
):
user_id = usr.hex if usr else None
wallet_id = wal.hex if wal else None
@ -121,7 +121,7 @@ async def wallet(
if not user_id:
user = await get_user((await create_account()).id)
logger.info(f"Create user {user.id}")
logger.info(f"Create user {user.id}") # type: ignore
else:
user = await get_user(user_id)
if not user:
@ -135,22 +135,22 @@ async def wallet(
if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
user.admin = True
if not wallet_id:
if user.wallets and not wallet_name:
wallet = user.wallets[0]
if user.wallets and not wallet_name: # type: ignore
wallet = user.wallets[0] # type: ignore
else:
wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name)
wallet = await create_wallet(user_id=user.id, wallet_name=wallet_name) # type: ignore
logger.info(
f"Created new wallet {wallet_name if wallet_name else '(no name)'} for user {user.id}"
f"Created new wallet {wallet_name if wallet_name else '(no name)'} for user {user.id}" # type: ignore
)
return RedirectResponse(
f"/wallet?usr={user.id}&wal={wallet.id}",
f"/wallet?usr={user.id}&wal={wallet.id}", # type: ignore
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
logger.debug(f"Access wallet {wallet_name}{'of user '+ user.id if user else ''}")
wallet = user.get_wallet(wallet_id)
if not wallet:
userwallet = user.get_wallet(wallet_id) # type: ignore
if not userwallet:
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "Wallet not found"}
)
@ -159,10 +159,10 @@ async def wallet(
"core/wallet.html",
{
"request": request,
"user": user.dict(),
"wallet": wallet.dict(),
"user": user.dict(), # type: ignore
"wallet": userwallet.dict(),
"service_fee": service_fee,
"web_manifest": f"/manifest/{user.id}.webmanifest",
"web_manifest": f"/manifest/{user.id}.webmanifest", # type: ignore
},
)
@ -216,20 +216,20 @@ 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(...)):
async def deletewallet(request: Request, wal: str = Query(...), usr: str = Query(...)): # type: ignore
user = await get_user(usr)
user_wallet_ids = [u.id for u in user.wallets]
user_wallet_ids = [u.id for u in user.wallets] # type: ignore
if wal not in user_wallet_ids:
raise HTTPException(HTTPStatus.FORBIDDEN, "Not your wallet.")
else:
await delete_wallet(user_id=user.id, wallet_id=wal)
await delete_wallet(user_id=user.id, wallet_id=wal) # type: ignore
user_wallet_ids.remove(wal)
logger.debug("Deleted wallet {wal} of user {user.id}")
if user_wallet_ids:
return RedirectResponse(
url_for("/wallet", usr=user.id, wal=user_wallet_ids[0]),
url_for("/wallet", usr=user.id, wal=user_wallet_ids[0]), # type: ignore
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)
@ -242,7 +242,7 @@ async def deletewallet(request: Request, wal: str = Query(...), usr: str = Query
async def lnurl_balance_notify(request: Request, service: str):
bc = await get_balance_check(request.query_params.get("wal"), service)
if bc:
redeem_lnurl_withdraw(bc.wallet, bc.url)
await redeem_lnurl_withdraw(bc.wallet, bc.url)
@core_html_routes.get(
@ -252,7 +252,7 @@ async def lnurlwallet(request: Request):
async with db.connect() as conn:
account = await create_account(conn=conn)
user = await get_user(account.id, conn=conn)
wallet = await create_wallet(user_id=user.id, conn=conn)
wallet = await create_wallet(user_id=user.id, conn=conn) # type: ignore
asyncio.create_task(
redeem_lnurl_withdraw(
@ -265,7 +265,7 @@ async def lnurlwallet(request: Request):
)
return RedirectResponse(
f"/wallet?usr={user.id}&wal={wallet.id}",
f"/wallet?usr={user.id}&wal={wallet.id}", # type: ignore
status_code=status.HTTP_307_TEMPORARY_REDIRECT,
)

View File

@ -1,4 +1,5 @@
from http import HTTPStatus
from typing import Union
from cerberus import Validator # type: ignore
from fastapi import status
@ -29,20 +30,21 @@ class KeyChecker(SecurityBase):
self._key_type = "invoice"
self._api_key = api_key
if api_key:
self.model: APIKey = APIKey(
key = APIKey(
**{"in": APIKeyIn.query},
name="X-API-KEY",
description="Wallet API Key - QUERY",
)
else:
self.model: APIKey = APIKey(
key = APIKey(
**{"in": APIKeyIn.header},
name="X-API-KEY",
description="Wallet API Key - HEADER",
)
self.wallet = None
self.wallet = None # type: ignore
self.model: APIKey = key
async def __call__(self, request: Request) -> Wallet:
async def __call__(self, request: Request):
try:
key_value = (
self._api_key
@ -52,7 +54,7 @@ class KeyChecker(SecurityBase):
# FIXME: Find another way to validate the key. A fetch from DB should be avoided here.
# Also, we should not return the wallet here - thats silly.
# Possibly store it in a Redis DB
self.wallet = await get_wallet_for_key(key_value, self._key_type)
self.wallet = await get_wallet_for_key(key_value, self._key_type) # type: ignore
if not self.wallet:
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED,
@ -120,8 +122,8 @@ api_key_query = APIKeyQuery(
async def get_key_type(
r: Request,
api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query),
api_key_header: str = Security(api_key_header), # type: ignore
api_key_query: str = Security(api_key_query), # type: ignore
) -> WalletTypeInfo:
# 0: admin
# 1: invoice
@ -134,9 +136,9 @@ async def get_key_type(
token = api_key_header if api_key_header else api_key_query
try:
checker = WalletAdminKeyChecker(api_key=token)
await checker.__call__(r)
wallet = WalletTypeInfo(0, checker.wallet)
admin_checker = WalletAdminKeyChecker(api_key=token)
await admin_checker.__call__(r)
wallet = WalletTypeInfo(0, admin_checker.wallet) # type: ignore
if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and (
LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS
):
@ -153,9 +155,9 @@ async def get_key_type(
raise
try:
checker = WalletInvoiceKeyChecker(api_key=token)
await checker.__call__(r)
wallet = WalletTypeInfo(1, checker.wallet)
invoice_checker = WalletInvoiceKeyChecker(api_key=token)
await invoice_checker.__call__(r)
wallet = WalletTypeInfo(1, invoice_checker.wallet) # type: ignore
if (LNBITS_ADMIN_USERS and wallet.wallet.user not in LNBITS_ADMIN_USERS) and (
LNBITS_ADMIN_EXTENSIONS and pathname in LNBITS_ADMIN_EXTENSIONS
):
@ -167,15 +169,16 @@ async def get_key_type(
if e.status_code == HTTPStatus.BAD_REQUEST:
raise
if e.status_code == HTTPStatus.UNAUTHORIZED:
return WalletTypeInfo(2, None)
return WalletTypeInfo(2, None) # type: ignore
except:
raise
return wallet
async def require_admin_key(
r: Request,
api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query),
api_key_header: str = Security(api_key_header), # type: ignore
api_key_query: str = Security(api_key_query), # type: ignore
):
token = api_key_header if api_key_header else api_key_query
@ -193,8 +196,8 @@ async def require_admin_key(
async def require_invoice_key(
r: Request,
api_key_header: str = Security(api_key_header),
api_key_query: str = Security(api_key_query),
api_key_header: str = Security(api_key_header), # type: ignore
api_key_query: str = Security(api_key_query), # type: ignore
):
token = api_key_header if api_key_header else api_key_query

View File

@ -9,7 +9,7 @@ db = Database("ext_bleskomat")
bleskomat_static_files = [
{
"path": "/bleskomat/static",
"app": StaticFiles(directory="lnbits/extensions/bleskomat/static"),
"app": StaticFiles(packages=[("lnbits", "extensions/bleskomat/static")]),
"name": "bleskomat_static",
}
]

View File

@ -62,4 +62,5 @@
</p>
</q-card-section>
</q-card>
<q-btn flat label="Swagger API" type="a" href="../docs#/Bleskomat"></q-btn>
</q-expansion-item>

View File

@ -12,7 +12,7 @@ db = Database("ext_copilot")
copilot_static_files = [
{
"path": "/copilot/static",
"app": StaticFiles(directory="lnbits/extensions/copilot/static"),
"app": StaticFiles(packages=[("lnbits", "extensions/copilot/static")]),
"name": "copilot_static",
}
]

View File

@ -14,6 +14,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/copilot"></q-btn>
<q-expansion-item group="api" dense expand-separator label="Create copilot">
<q-card>
<q-card-section>

View File

@ -9,7 +9,7 @@ db = Database("ext_discordbot")
discordbot_static_files = [
{
"path": "/discordbot/static",
"app": StaticFiles(directory="lnbits/extensions/discordbot/static"),
"app": StaticFiles(packages=[("lnbits", "extensions/discordbot/static")]),
"name": "discordbot_static",
}
]

View File

@ -34,6 +34,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/discordbot"></q-btn>
<q-expansion-item group="api" dense expand-separator label="GET users">
<q-card>
<q-card-section>

View File

@ -16,7 +16,7 @@ async def create_ticket(
INSERT INTO events.ticket (id, wallet, event, name, email, registered, paid)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(payment_hash, wallet, event, name, email, False, False),
(payment_hash, wallet, event, name, email, False, True),
)
ticket = await get_ticket(payment_hash)

View File

@ -20,4 +20,5 @@
</p>
</q-card-section>
</q-card>
<q-btn flat label="Swagger API" type="a" href="../docs#/events"></q-btn>
</q-expansion-item>

View File

@ -135,15 +135,7 @@
var self = this
axios
.post(
'/events/api/v1/tickets/' + '{{ event_id }}/{{ event_price }}',
{
event: '{{ event_id }}',
event_name: '{{ event_name }}',
name: self.formDialog.data.name,
email: self.formDialog.data.email
}
)
.get('/events/api/v1/tickets/' + '{{ event_id }}')
.then(function (response) {
self.paymentReq = response.data.payment_request
self.paymentCheck = response.data.payment_hash
@ -161,7 +153,17 @@
paymentChecker = setInterval(function () {
axios
.get('/events/api/v1/tickets/' + self.paymentCheck)
.post(
'/events/api/v1/tickets/' +
'{{ event_id }}/' +
self.paymentCheck,
{
event: '{{ event_id }}',
event_name: '{{ event_name }}',
name: self.formDialog.data.name,
email: self.formDialog.data.email
}
)
.then(function (res) {
if (res.data.paid) {
clearInterval(paymentChecker)

View File

@ -133,7 +133,10 @@
var self = this
LNbits.api
.request('GET', '/events/api/v1/register/ticket/' + res)
.request(
'GET',
'/events/api/v1/register/ticket/' + res.split('//')[1]
)
.then(function (response) {
self.$q.notify({
type: 'positive',

View File

@ -13,9 +13,8 @@
<br />
<qrcode
:value="'{{ ticket_id }}'"
:options="{width: 340}"
class="rounded-borders"
:value="'ticket://{{ ticket_id }}'"
:options="{width: 500}"
></qrcode>
<br />
<q-btn @click="printWindow" color="grey" class="q-ml-auto">

View File

@ -97,8 +97,8 @@ async def api_tickets(
return [ticket.dict() for ticket in await get_tickets(wallet_ids)]
@events_ext.post("/api/v1/tickets/{event_id}/{sats}")
async def api_ticket_make_ticket(event_id, sats, data: CreateTicket):
@events_ext.get("/api/v1/tickets/{event_id}")
async def api_ticket_make_ticket(event_id):
event = await get_event(event_id)
if not event:
raise HTTPException(
@ -107,13 +107,22 @@ async def api_ticket_make_ticket(event_id, sats, data: CreateTicket):
try:
payment_hash, payment_request = await create_invoice(
wallet_id=event.wallet,
amount=int(sats),
amount=event.price_per_ticket,
memo=f"{event_id}",
extra={"tag": "events"},
)
except Exception as e:
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
return {"payment_hash": payment_hash, "payment_request": payment_request}
@events_ext.post("/api/v1/tickets/{event_id}/{payment_hash}")
async def api_ticket_send_ticket(event_id, payment_hash, data: CreateTicket):
event = await get_event(event_id)
try:
status = await api_payment(payment_hash)
if status["paid"]:
ticket = await create_ticket(
payment_hash=payment_hash,
wallet=event.wallet,
@ -124,20 +133,10 @@ async def api_ticket_make_ticket(event_id, sats, data: CreateTicket):
if not ticket:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail=f"Event could not be fetched."
status_code=HTTPStatus.NOT_FOUND,
detail=f"Event could not be fetched.",
)
return {"payment_hash": payment_hash, "payment_request": payment_request}
@events_ext.get("/api/v1/tickets/{payment_hash}")
async def api_ticket_send_ticket(payment_hash):
ticket = await get_ticket(payment_hash)
try:
status = await api_payment(payment_hash)
if status["paid"]:
await set_ticket_paid(payment_hash=payment_hash)
return {"paid": True, "ticket_id": ticket.id}
except Exception:
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Not paid")

View File

@ -12,7 +12,7 @@ db = Database("ext_jukebox")
jukebox_static_files = [
{
"path": "/jukebox/static",
"app": StaticFiles(directory="lnbits/extensions/jukebox/static"),
"app": StaticFiles(packages=[("lnbits", "extensions/jukebox/static")]),
"name": "jukebox_static",
}
]

View File

@ -24,6 +24,8 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/jukebox"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List jukeboxes">
<q-card>
<q-card-section>

View File

@ -12,7 +12,7 @@ db = Database("ext_livestream")
livestream_static_files = [
{
"path": "/livestream/static",
"app": StaticFiles(directory="lnbits/extensions/livestream/static"),
"app": StaticFiles(packages=[("lnbits", "extensions/livestream/static")]),
"name": "livestream_static",
}
]

View File

@ -17,6 +17,8 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/livestream"></q-btn>
<q-expansion-item
group="api"
dense

View File

@ -31,6 +31,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/lnaddress"></q-btn>
<q-expansion-item group="api" dense expand-separator label="GET domains">
<q-card>
<q-card-section>

View File

@ -31,5 +31,6 @@
</li>
</ul>
</q-card-section>
<q-btn flat label="Swagger API" type="a" href="../docs#/lndhub"></q-btn>
</q-card>
</q-expansion-item>

View File

@ -19,4 +19,5 @@
</p>
</q-card-section>
</q-card>
<q-btn flat label="Swagger API" type="a" href="../docs#/lnticket"></q-btn>
</q-expansion-item>

View File

@ -17,6 +17,12 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn
flat
label="Swagger API"
type="a"
href="../docs#/lnurldevice"
></q-btn>
<q-expansion-item
group="api"
dense

View File

@ -12,7 +12,7 @@ db = Database("ext_lnurlp")
lnurlp_static_files = [
{
"path": "/lnurlp/static",
"app": StaticFiles(directory="lnbits/extensions/lnurlp/static"),
"app": StaticFiles(packages=[("lnbits", "extensions/lnurlp/static")]),
"name": "lnurlp_static",
}
]

View File

@ -4,6 +4,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/lnurlp"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List pay links">
<q-card>
<q-card-section>
@ -51,6 +52,7 @@
expand-separator
label="Create a pay link"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/lnurlp"></q-btn>
<q-card>
<q-card-section>
<code><span class="text-green">POST</span> /lnurlp/api/v1/links</code>

View File

@ -2,5 +2,5 @@
"name": "LNURLPayout",
"short_description": "Autodump wallet funds to LNURLpay",
"icon": "exit_to_app",
"contributors": ["arcbtc"]
"contributors": ["arcbtc","talvasconcelos"]
}

View File

@ -4,6 +4,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/lnurlpayout"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List lnurlpayout">
<q-card>
<q-card-section>

View File

@ -9,7 +9,7 @@ db = Database("ext_offlineshop")
offlineshop_static_files = [
{
"path": "/offlineshop/static",
"app": StaticFiles(directory="lnbits/extensions/offlineshop/static"),
"app": StaticFiles(packages=[("lnbits", "extensions/offlineshop/static")]),
"name": "offlineshop_static",
}
]

View File

@ -47,6 +47,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/offlineshop"></q-btn>
<q-expansion-item
group="api"
dense

View File

@ -4,6 +4,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/paywall"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List paywalls">
<q-card>
<q-card-section>

View File

@ -4,6 +4,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/satsdice"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List satsdices">
<q-card>
<q-card-section>

View File

@ -15,6 +15,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/satspay"></q-btn>
<q-expansion-item group="api" dense expand-separator label="Create charge">
<q-card>
<q-card-section>

View File

@ -12,7 +12,7 @@ db = Database("ext_splitpayments")
splitpayments_static_files = [
{
"path": "/splitpayments/static",
"app": StaticFiles(directory="lnbits/extensions/splitpayments/static"),
"app": StaticFiles(packages=[("lnbits", "extensions/splitpayments/static")]),
"name": "splitpayments_static",
}
]

View File

@ -28,6 +28,12 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn
flat
label="Swagger API"
type="a"
href="../docs#/splitpayments"
></q-btn>
<q-expansion-item
group="api"
dense

View File

@ -15,4 +15,5 @@
>
</p>
</q-card-section>
<q-btn flat label="Swagger API" type="a" href="../docs#/streamalerts"></q-btn>
</q-card>

View File

@ -22,5 +22,6 @@
>
</p>
</q-card-section>
<q-btn flat label="Swagger API" type="a" href="../docs#/subdomains"></q-btn>
</q-card>
</q-expansion-item>

View File

@ -12,4 +12,5 @@
>
</p>
</q-card-section>
<q-btn flat label="Swagger API" type="a" href="../docs#/tipjar"></q-btn>
</q-card>

View File

@ -4,6 +4,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/tpos"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List TPoS">
<q-card>
<q-card-section>

View File

@ -308,7 +308,9 @@
},
sat: function () {
if (!this.exchangeRate) return 0
return Math.ceil((this.amount / this.exchangeRate) * 100000000)
return Math.ceil(
((this.amount - this.tipAmount) / this.exchangeRate) * 100000000
)
},
tipAmountSat: function () {
if (!this.exchangeRate) return 0
@ -423,10 +425,9 @@
'{{ tpos.tip_options | tojson }}' == 'null'
? null
: JSON.parse('{{ tpos.tip_options }}')
console.log(typeof this.tip_options, this.tip_options)
setInterval(function () {
getRates()
}, 20000)
}, 120000)
}
})
</script>

View File

@ -28,6 +28,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/usermanager"></q-btn>
<q-expansion-item group="api" dense expand-separator label="GET users">
<q-card>
<q-card-section>

View File

@ -20,6 +20,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/watchonly"></q-btn>
<q-expansion-item group="api" dense expand-separator label="List wallets">
<q-card>
<q-card-section>

View File

@ -9,7 +9,7 @@ db = Database("ext_withdraw")
withdraw_static_files = [
{
"path": "/withdraw/static",
"app": StaticFiles(directory="lnbits/extensions/withdraw/static"),
"app": StaticFiles(packages=[("lnbits", "extensions/withdraw/static")]),
"name": "withdraw_static",
}
]

View File

@ -4,6 +4,7 @@
label="API info"
:content-inset-level="0.5"
>
<q-btn flat label="Swagger API" type="a" href="../docs#/withdraw"></q-btn>
<q-expansion-item
group="api"
dense

View File

@ -34,7 +34,7 @@ class ExtensionManager:
@property
def extensions(self) -> List[Extension]:
output = []
output: List[Extension] = []
if "all" in self._disabled:
return output

18
lnbits/server.py Normal file
View File

@ -0,0 +1,18 @@
import click
import uvicorn
@click.command()
@click.option("--port", default="5000", help="Port to run LNBits on")
@click.option("--host", default="127.0.0.1", help="Host to run LNBits on")
def main(port, host):
"""Launched with `poetry run lnbits` at root level"""
uvicorn.run("lnbits.__main__:app", port=port, host=host)
if __name__ == "__main__":
main()
# def main():
# """Launched with `poetry run start` at root level"""
# uvicorn.run("lnbits.__main__:app")

View File

@ -66,7 +66,7 @@ async def webhook_handler():
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
internal_invoice_queue = asyncio.Queue(0)
internal_invoice_queue: asyncio.Queue = asyncio.Queue(0)
async def internal_invoice_listener():

View File

@ -1,5 +1,5 @@
import asyncio
from typing import Callable, NamedTuple
from typing import Callable, List, NamedTuple
import httpx
from loguru import logger
@ -227,10 +227,10 @@ async def btc_price(currency: str) -> float:
"TO": currency.upper(),
"to": currency.lower(),
}
rates = []
tasks = []
rates: List[float] = []
tasks: List[asyncio.Task] = []
send_channel = asyncio.Queue()
send_channel: asyncio.Queue = asyncio.Queue()
async def controller():
failures = 0

View File

@ -7,7 +7,10 @@ from typing import AsyncGenerator, Dict, Optional
import httpx
from loguru import logger
from websockets import connect
# TODO: https://github.com/lnbits/lnbits-legend/issues/764
# mypy https://github.com/aaugustin/websockets/issues/940
from websockets import connect # type: ignore
from websockets.exceptions import (
ConnectionClosed,
ConnectionClosedError,

View File

@ -28,7 +28,7 @@ class FakeWallet(Wallet):
logger.info(
"FakeWallet funding source is for using LNbits as a centralised, stand-alone payment system with brrrrrr."
)
return StatusResponse(None, float("inf"))
return StatusResponse(None, 1000000000)
async def create_invoice(
self,
@ -82,7 +82,7 @@ class FakeWallet(Wallet):
invoice = decode(bolt11)
if (
hasattr(invoice, "checking_id")
and invoice.checking_id[6:] == data["privkey"][:6]
and invoice.checking_id[6:] == data["privkey"][:6] # type: ignore
):
return PaymentResponse(True, invoice.payment_hash, 0)
else:
@ -97,7 +97,7 @@ class FakeWallet(Wallet):
return PaymentStatus(None)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue = asyncio.Queue(0)
self.queue: asyncio.Queue = asyncio.Queue(0)
while True:
value = await self.queue.get()
yield value

View File

@ -119,7 +119,7 @@ class LNPayWallet(Wallet):
return PaymentStatus(statuses[r.json()["settled"]])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue = asyncio.Queue(0)
self.queue: asyncio.Queue = asyncio.Queue(0)
while True:
value = await self.queue.get()
yield value

View File

@ -73,10 +73,10 @@ class AESCipher(object):
final_key += key
return final_key[:output]
def decrypt(self, encrypted: str) -> str:
def decrypt(self, encrypted: str) -> str: # type: ignore
"""Decrypts a string using AES-256-CBC."""
passphrase = self.passphrase
encrypted = base64.b64decode(encrypted)
encrypted = base64.b64decode(encrypted) # type: ignore
assert encrypted[0:8] == b"Salted__"
salt = encrypted[8:16]
key_iv = self.bytes_to_key(passphrase.encode(), salt, 32 + 16)
@ -84,7 +84,7 @@ class AESCipher(object):
iv = key_iv[32:]
aes = AES.new(key, AES.MODE_CBC, iv)
try:
return self.unpad(aes.decrypt(encrypted[16:])).decode()
return self.unpad(aes.decrypt(encrypted[16:])).decode() # type: ignore
except UnicodeDecodeError:
raise ValueError("Wrong passphrase")

View File

@ -127,7 +127,7 @@ class OpenNodeWallet(Wallet):
return PaymentStatus(statuses[r.json()["data"]["status"]])
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
self.queue = asyncio.Queue(0)
self.queue: asyncio.Queue = asyncio.Queue(0)
while True:
value = await self.queue.get()
yield value

View File

@ -1,7 +1,8 @@
[mypy]
ignore_missing_imports = True
exclude = lnbits/wallets/lnd_grpc_files/
exclude = lnbits/extensions/
exclude = (?x)(
^lnbits/extensions.
| ^lnbits/wallets/lnd_grpc_files.
)
[mypy-lnbits.wallets.lnd_grpc_files.*]
follow_imports = skip

View File

@ -0,0 +1,106 @@
{ config, pkgs, lib, ... }:
let
defaultUser = "lnbits";
cfg = config.services.lnbits;
inherit (lib) mkOption mkIf types optionalAttrs;
in
{
options = {
services.lnbits = {
enable = mkOption {
default = false;
type = types.bool;
description = ''
Whether to enable the lnbits service
'';
};
openFirewall = mkOption {
type = types.bool;
default = false;
description = ''
Whether to open the ports used by lnbits in the firewall for the server
'';
};
package = mkOption {
type = types.package;
default = pkgs.lnbits;
description = ''
The lnbits package to use.
'';
};
stateDir = mkOption {
type = types.path;
default = "/var/lib/lnbits";
description = ''
The lnbits state directory which LNBITS_DATA_FOLDER will be set to
'';
};
host = mkOption {
type = types.str;
default = "127.0.0.1";
description = ''
The host to bind to
'';
};
port = mkOption {
type = types.port;
default = 8231;
description = ''
The port to run on
'';
};
user = mkOption {
type = types.str;
default = "lnbits";
description = "user to run lnbits as";
};
group = mkOption {
type = types.str;
default = "lnbits";
description = "group to run lnbits as";
};
};
};
config = mkIf cfg.enable {
users.users = optionalAttrs (cfg.user == defaultUser) {
${defaultUser} = {
isSystemUser = true;
group = defaultUser;
};
};
users.groups = optionalAttrs (cfg.group == defaultUser) {
${defaultUser} = { };
};
systemd.tmpfiles.rules = [
"d ${cfg.stateDir} 0700 ${cfg.user} ${cfg.group} - -"
];
systemd.services.lnbits = {
enable = true;
description = "lnbits";
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
environment = {
LNBITS_DATA_FOLDER = "${cfg.stateDir}";
};
serviceConfig = {
User = cfg.user;
Group = cfg.group;
WorkingDirectory = "${cfg.package.src}";
StateDirectory = "${cfg.stateDir}";
ExecStart = "${lib.getExe cfg.package} --port ${toString cfg.port} --host ${cfg.host}";
Restart = "always";
PrivateTmp = true;
};
};
networking.firewall = mkIf cfg.openFirewall {
allowedTCPPorts = [ cfg.port ];
};
};
}

4
nix/tests/default.nix Normal file
View File

@ -0,0 +1,4 @@
{ pkgs, makeTest, inputs }:
{
vmTest = import ./nixos-module { inherit pkgs makeTest inputs; };
}

View File

@ -0,0 +1,25 @@
{ pkgs, makeTest, inputs }:
makeTest {
nodes = {
client = { config, pkgs, ... }: {
environment.systemPackages = [ pkgs.curl ];
};
lnbits = { ... }: {
imports = [ inputs.self.nixosModules.default ];
services.lnbits = {
enable = true;
openFirewall = true;
host = "0.0.0.0";
};
};
};
testScript = { nodes, ... }: ''
start_all()
lnbits.wait_for_open_port(${toString nodes.lnbits.config.services.lnbits.port})
client.wait_for_unit("multi-user.target")
with subtest("Check that the lnbits webserver can be reached."):
assert "<title>LNbits</title>" in client.succeed(
"curl -sSf http:/lnbits:8231/ | grep title"
)
'';
}

1165
poetry.lock generated Normal file

File diff suppressed because it is too large Load Diff

70
pyproject.toml Normal file
View File

@ -0,0 +1,70 @@
[tool.poetry]
name = "lnbits"
version = "0.1.0"
description = ""
authors = ["matthewcroughan <matt@croughan.sh>"]
[tool.poetry.build]
generate-setup-file = false
script = "build.py"
[tool.poetry.dependencies]
python = "^3.9"
aiofiles = "0.7.0"
asgiref = "3.4.1"
attrs = "21.2.0"
bech32 = "1.2.0"
bitstring = "3.1.9"
cerberus = "1.3.4"
certifi = "2021.5.30"
charset-normalizer = "2.0.6"
click = "8.0.1"
ecdsa = "0.17.0"
embit = "0.4.9"
environs = "9.3.3"
fastapi = "0.78.0"
h11 = "0.12.0"
httpcore = "0.13.7"
httptools = "0.2.0"
httpx = "0.19.0"
idna = "3.2"
importlib-metadata = "4.8.1"
jinja2 = "3.0.1"
lnurl = "0.3.6"
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"
pyscss = "1.3.7"
python-dotenv = "0.19.0"
pyyaml = "5.4.1"
represent = "1.6.0.post0"
rfc3986 = "1.5.0"
secp256k1 = "0.14.0"
shortuuid = "1.0.1"
six = "1.16.0"
sniffio = "1.2.0"
sqlalchemy = "1.3.23"
sqlalchemy-aio = "0.16.0"
sse-starlette = "0.6.2"
typing-extensions = "3.10.0.2"
uvicorn = "0.18.1"
uvloop = "0.16.0"
watchgod = "0.7"
websockets = "10.0"
zipp = "3.5.0"
loguru = "0.5.3"
cffi = "1.15.0"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
lnbits = "lnbits.server:main"

View File

@ -1,52 +1,52 @@
aiofiles==0.7.0
anyio==3.3.1
asgiref==3.4.1
aiofiles==0.8.0
anyio==3.6.1
asyncio==3.4.3
attrs==21.2.0
attrs==21.4.0
bech32==1.2.0
bitstring==3.1.9
cerberus==1.3.4
certifi==2021.5.30
charset-normalizer==2.0.6
click==8.0.1
ecdsa==0.17.0
embit==0.4.9
environs==9.3.3
fastapi==0.68.1
certifi==2022.6.15
cffi==1.15.0
click==8.1.3
ecdsa==0.18.0
embit==0.5.0
environs==9.5.0
fastapi==0.79.0
h11==0.12.0
httpcore==0.15.0
httptools==0.2.0
httptools==0.4.0
httpx==0.23.0
idna==3.2
importlib-metadata==4.8.1
idna==3.3
jinja2==3.0.1
lnurl==0.3.6
loguru==0.6.0
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
markupsafe==2.1.1
marshmallow==3.17.0
outcome==1.2.0
packaging==21.3
psycopg2-binary==2.9.3
pycparser==2.21
pycryptodomex==3.15.0
pydantic==1.9.1
pyngrok==5.1.0
pyparsing==3.0.9
pypng==0.20220715.0
pyqrcode==1.2.1
pyscss==1.3.7
python-dotenv==0.19.0
pyyaml==5.4.1
pyscss==1.4.0
python-dotenv==0.20.0
pyyaml==6.0
represent==1.6.0.post0
rfc3986==1.5.0
secp256k1==0.14.0
cffi==1.15.0
shortuuid==1.0.1
shortuuid==1.0.9
six==1.16.0
sniffio==1.2.0
sqlalchemy-aio==0.17.0
sqlalchemy==1.3.23
sqlalchemy-aio==0.16.0
sse-starlette==0.6.2
starlette==0.14.2
typing-extensions==3.10.0.2
uvicorn==0.15.0
sse-starlette==0.10.3
starlette==0.19.1
typing-extensions==4.3.0
uvicorn==0.18.2
uvloop==0.16.0
watchgod==0.7
websockets==10.0
zipp==3.5.0
watchfiles==0.16.0
websockets==10.3

1
result Symbolic link
View File

@ -0,0 +1 @@
/nix/store/ds9c48q7hnkdmpzy3aq14kc1x9wrrszd-python3.9-lnbits-0.1.0

View File

@ -1,6 +1,7 @@
import pytest
import pytest_asyncio
from lnbits.core.crud import get_wallet
from lnbits.core.views.api import api_payment
from ...helpers import get_random_invoice_data
@ -155,3 +156,26 @@ async def test_decode_invoice(client, invoice):
)
assert response.status_code < 300
assert response.json()["payment_hash"] == invoice["payment_hash"]
# check api_payment() internal function call (NOT API): payment status
@pytest.mark.asyncio
async def test_api_payment_without_key(invoice):
# check the payment status
response = await api_payment(invoice["payment_hash"])
assert type(response) == dict
assert response["paid"] == True
# no key, that's why no "details"
assert "details" not in response
# check api_payment() internal function call (NOT API): payment status
@pytest.mark.asyncio
async def test_api_payment_with_key(invoice, inkey_headers_from):
# check the payment status
response = await api_payment(
invoice["payment_hash"], inkey_headers_from["X-Api-Key"]
)
assert type(response) == dict
assert response["paid"] == True
assert "details" in response