Compare commits
53
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5388e23a9 | ||
|
|
d01893f577 | ||
|
|
1fc15cde81 | ||
|
|
c711be4d60 | ||
|
|
ccb9dda164 | ||
|
|
100bc45185 | ||
|
|
222d1c8990 | ||
|
|
69c414fabf | ||
|
|
b33aa41b9a | ||
|
|
68a36dfd18 | ||
|
|
84a958af6e | ||
|
|
7db645d388 | ||
|
|
30e61eacf3 | ||
|
|
de496ed9e8 | ||
|
|
8cb35d9aac | ||
|
|
d662b03370 | ||
|
|
ef78ca7db3 | ||
|
|
c97c5842ad | ||
|
|
abce6196de | ||
|
|
e7d3be9854 | ||
|
|
e9dc8c775d | ||
|
|
31a2d68285 | ||
|
|
bf1ad185b9 | ||
|
|
62de35541c | ||
|
|
1a61435868 | ||
|
|
03ba28d0cb | ||
|
|
2b260bfdaf | ||
|
|
30ab42c0b2 | ||
|
|
f603026d5e | ||
|
|
84e6b3a9a8 | ||
|
|
3aafc0724f | ||
|
|
b978ce11fc | ||
|
|
656c6cac5b | ||
|
|
45e773b66f | ||
|
|
f692ac6f12 | ||
|
|
78202a027b | ||
|
|
28fa0906a6 | ||
|
|
91354f8be4 | ||
|
|
5f5d45e89f | ||
|
|
52bb125a25 | ||
|
|
2ca1ed897c | ||
|
|
631f4e8455 | ||
|
|
b22f88b264 | ||
|
|
c08bc02930 | ||
|
|
e020a7c5a0 | ||
|
|
d2e8914835 | ||
|
|
5e79ca35ab | ||
|
|
4690d178a2 | ||
|
|
c5825aa4c3 | ||
|
|
ebe0c4e3c3 | ||
|
|
383bdb6312 | ||
|
|
a947686d79 | ||
|
|
28e9d32b44 |
+1
-10
@@ -59,7 +59,7 @@ LNBITS_EXTENSIONS_DEFAULT_INSTALL="tpos"
|
||||
# LNBITS_EXT_GITHUB_TOKEN=github_pat_xxxxxxxxxxxxxxxxxx
|
||||
|
||||
# which fundingsources are allowed in the admin ui
|
||||
# LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, BlinkWallet, AlbyWallet, ZBDWallet, PhoenixdWallet, OpenNodeWallet, NWCWallet, BreezSdkWallet, BoltzWallet, StrikeWallet, CLNRestWallet, SparkWallet, SparkL2Wallet"
|
||||
# LNBITS_ALLOWED_FUNDING_SOURCES="VoidWallet, FakeWallet, CoreLightningWallet, CoreLightningRestWallet, LndRestWallet, EclairWallet, LndWallet, LnTipsWallet, LNPayWallet, LNbitsWallet, BlinkWallet, AlbyWallet, ZBDWallet, PhoenixdWallet, OpenNodeWallet, NWCWallet, BreezSdkWallet, BoltzWallet, StrikeWallet, CLNRestWallet"
|
||||
|
||||
# uvicorn variable, allow https behind a proxy
|
||||
# IMPORTANT: this also needs the webserver to be configured to forward the headers
|
||||
@@ -191,15 +191,6 @@ BOLTZ_CLIENT_MACAROON="/home/bob/.boltz/macaroons/admin.macaroon"
|
||||
# HEXSTRING instead of path also possible
|
||||
BOLTZ_CLIENT_CERT="/home/bob/.boltz/tls.cert"
|
||||
|
||||
# SparkL2Wallet (external sidecar: https://github.com/lnbits/spark_sidecar)
|
||||
SPARK_L2_NETWORK=MAINNET
|
||||
SPARK_L2_EXTERNAL_ENDPOINT=http://127.0.0.1:8765
|
||||
SPARK_L2_EXTERNAL_API_KEY=
|
||||
# optional tuning
|
||||
# SPARK_L2_PAY_WAIT_MS=4000
|
||||
# SPARK_L2_PAY_POLL_MS=500
|
||||
# SPARK_L2_STREAM_KEEPALIVE_MS=15000
|
||||
|
||||
# StrikeWallet
|
||||
STRIKE_API_ENDPOINT=https://api.strike.me/v1
|
||||
STRIKE_API_KEY=YOUR_STRIKE_API_KEY
|
||||
|
||||
@@ -55,21 +55,11 @@ jobs:
|
||||
- name: Build and push boltz
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: docker/boltzclient
|
||||
context: .
|
||||
file: Dockerfile.boltz
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits-boltz:${{ inputs.tag }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
build-args: LNBITS_TAG=${{ inputs.tag }}
|
||||
|
||||
- name: Build and push sparkl2
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: docker/sparkl2
|
||||
push: true
|
||||
tags: ${{ secrets.DOCKER_USERNAME }}/lnbits-sparkl2:${{ inputs.tag }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
||||
build-args: LNBITS_TAG=${{ inputs.tag }}
|
||||
|
||||
@@ -38,7 +38,8 @@ jobs:
|
||||
|
||||
- name: Setup Regtest
|
||||
run: |
|
||||
cd docker/regtest
|
||||
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
|
||||
cd docker
|
||||
chmod +x ./start-regtest
|
||||
./start-regtest
|
||||
sudo chmod -R a+rwx .
|
||||
@@ -49,16 +50,16 @@ jobs:
|
||||
LNBITS_DATABASE_URL: ${{ inputs.db-url }}
|
||||
LNBITS_BACKEND_WALLET_CLASS: ${{ inputs.backend-wallet-class }}
|
||||
LND_REST_ENDPOINT: https://localhost:8081/
|
||||
LND_REST_CERT: ./docker/regtest/data/lnd-3/tls.cert
|
||||
LND_REST_MACAROON: ./docker/regtest/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
||||
LND_REST_CERT: ./docker/data/lnd-3/tls.cert
|
||||
LND_REST_MACAROON: ./docker/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
||||
LND_GRPC_ENDPOINT: localhost
|
||||
LND_GRPC_PORT: 10009
|
||||
LND_GRPC_CERT: ./docker/regtest/data/lnd-3/tls.cert
|
||||
LND_GRPC_MACAROON: ./docker/regtest/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
||||
CORELIGHTNING_RPC: ./docker/regtest/data/clightning-1/regtest/lightning-rpc
|
||||
LND_GRPC_CERT: docker/data/lnd-3/tls.cert
|
||||
LND_GRPC_MACAROON: docker/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
||||
CORELIGHTNING_RPC: ./docker/data/clightning-1/regtest/lightning-rpc
|
||||
CORELIGHTNING_REST_URL: https://localhost:3001
|
||||
CORELIGHTNING_REST_MACAROON: ./docker/regtest/data/clightning-2-rest/access.macaroon
|
||||
CORELIGHTNING_REST_CERT: ./docker/regtest/data/clightning-2-rest/certificate.pem
|
||||
CORELIGHTNING_REST_MACAROON: ./docker/data/clightning-2-rest/access.macaroon
|
||||
CORELIGHTNING_REST_CERT: ./docker/data/clightning-2-rest/certificate.pem
|
||||
LNBITS_ENDPOINT: http://localhost:5001
|
||||
LNBITS_KEY: "d08a3313322a4514af75d488bcc27eee"
|
||||
ECLAIR_URL: http://127.0.0.1:8082
|
||||
|
||||
@@ -42,6 +42,7 @@ lnbits/static/bundle.min.js.old
|
||||
lnbits/static/bundle.min.css.old
|
||||
lnbits/static/bundle-components.min.js.old
|
||||
lnbits/upgrades
|
||||
docker
|
||||
|
||||
# Nix
|
||||
*result*
|
||||
|
||||
@@ -14,13 +14,11 @@ ENV PATH="/root/.local/bin:$PATH"
|
||||
# Reinstall dependencies just in case (needed for CMD usage)
|
||||
RUN uv sync --all-extras
|
||||
|
||||
# LNbits
|
||||
# LNbits + boltzd configuration
|
||||
ENV LNBITS_PORT="5000"
|
||||
ENV LNBITS_HOST="0.0.0.0"
|
||||
ENV LNBITS_BACKEND_WALLET_CLASS="BoltzWallet"
|
||||
ENV FUNDING_SOURCE_MAX_RETRIES=10
|
||||
|
||||
# Boltzd
|
||||
ENV BOLTZ_CLIENT_ENDPOINT="127.0.0.1:9002"
|
||||
ENV BOLTZ_CLIENT_MACAROON="/root/.boltz/macaroons/admin.macaroon"
|
||||
ENV BOLTZ_CLIENT_CERT="/root/.boltz/tls.cert"
|
||||
@@ -28,7 +26,8 @@ ENV BOLTZ_CLIENT_WALLET="lnbits"
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
# Entrypoint to start boltzd and LNbits
|
||||
COPY dockerboltz.sh /dockerboltz.sh
|
||||
RUN chmod +x /dockerboltz.sh
|
||||
|
||||
CMD ["/entrypoint.sh"]
|
||||
CMD ["/dockerboltz.sh"]
|
||||
@@ -1,18 +0,0 @@
|
||||
!data
|
||||
data/*
|
||||
|
||||
!data/boltz
|
||||
data/boltz/*
|
||||
!data/boltz/boltz.conf
|
||||
|
||||
!data/boltz-client
|
||||
data/boltz-client/*
|
||||
!data/boltz-client/boltz.toml
|
||||
|
||||
!data/boltz-nginx
|
||||
data/boltz-nginx/*
|
||||
!data/boltz-nginx/default.conf
|
||||
|
||||
!data/eclair
|
||||
data/eclair/*
|
||||
!data/eclair/eclair.conf
|
||||
@@ -1,99 +0,0 @@
|
||||

|
||||
|
||||
# nodes
|
||||
|
||||
- lnd-1: for locally testing your current lnbits
|
||||
- lnd-2: used for boltz backend
|
||||
- lnd-3: used for lnbits inside docker
|
||||
- cln-1: for locally testing your current lnbits
|
||||
- cln-2: used for clightning-REST
|
||||
- eclair-1: for locally testing your current lnbits
|
||||
|
||||
# Installing regtest
|
||||
|
||||
get the regtest enviroment ready
|
||||
|
||||
```sh
|
||||
# Install docker https://docs.docker.com/engine/install/
|
||||
# Make sure your user has permission to use docker 'sudo usermod -aG docker ${USER}' then reboot
|
||||
# Stop/start docker 'sudo systemctl stop docker' 'sudo systemctl start docker'
|
||||
|
||||
sudo apt install jq
|
||||
git clone https://github.com/lnbits/lnbits.git
|
||||
cd lnbits
|
||||
docker build -t lnbits/lnbits .
|
||||
mkdir docker
|
||||
git clone https://github.com/lnbits/legend-regtest-enviroment.git docker
|
||||
cd docker
|
||||
chmod +x ./start-regtest
|
||||
./start-regtest # start the regtest and also run tests
|
||||
sudo chown -R $USER ./data # Give the data file permissions for user
|
||||
```
|
||||
|
||||
# Running LNbits on regtest
|
||||
|
||||
add this ENV variables to your `.env` file
|
||||
|
||||
```sh
|
||||
DEBUG=true
|
||||
|
||||
# LND
|
||||
LNBITS_BACKEND_WALLET_CLASS="LndRestWallet"
|
||||
LND_REST_ENDPOINT=https://127.0.0.1:8081/
|
||||
LND_REST_CERT=/home/user/repos/lnbits/docker/data/lnd-1/tls.cert
|
||||
LND_REST_MACAROON=/home/user/repos/lnbits/docker/data/lnd-1/data/chain/bitcoin/regtest/admin.macaroon
|
||||
|
||||
# CLN
|
||||
LNBITS_BACKEND_WALLET_CLASS="CoreLightningWallet"
|
||||
CORELIGHTNING_RPC=./docker/data/clightning-1/regtest/lightning-rpc
|
||||
|
||||
|
||||
# Run LNbits
|
||||
uv run lnbits
|
||||
|
||||
# Run LNbits with hot reload
|
||||
make dev
|
||||
```
|
||||
|
||||
# testing
|
||||
|
||||
```sh
|
||||
chmod +x ./start-regtest
|
||||
./start-regtest
|
||||
# short answer :)
|
||||
./start-regtest && echo "PASSED" || echo "FAILED" > /dev/null
|
||||
```
|
||||
|
||||
usage of the `bitcoin-cli-sim`, `lightning-cli-sim` and `lncli-sim` aliases
|
||||
|
||||
```sh
|
||||
cd ~/lnbits/docker
|
||||
source docker-scripts.sh
|
||||
# use bitcoin core, mine a block
|
||||
bitcoin-cli-sim -generate 1
|
||||
|
||||
# use c-lightning nodes
|
||||
lightning-cli-sim 1 newaddr | jq -r '.bech32' # use node 1
|
||||
lightning-cli-sim 2 getinfo # use node 2
|
||||
lightning-cli-sim 3 getinfo # use node 3
|
||||
|
||||
# use lnd nodes
|
||||
lncli-sim 1 newaddr p2wsh
|
||||
lncli-sim 2 listpeers
|
||||
```
|
||||
|
||||
# urls
|
||||
|
||||
- mempool: http://localhost:8080/
|
||||
- boltz api: http://localhost:9001/
|
||||
- lnd-1 rest: http://localhost:8081/
|
||||
- lnbits: http://localhost:5001/
|
||||
|
||||
# debugging docker logs
|
||||
|
||||
```sh
|
||||
docker logs lnbits-lnbits-1 -f
|
||||
docker logs lnbits-boltz-1 -f
|
||||
docker logs lnbits-clightning-1-1 -f
|
||||
docker logs lnbits-lnd-2-1 -f
|
||||
```
|
||||
@@ -1,49 +0,0 @@
|
||||
standalone = true
|
||||
network = "regtest"
|
||||
|
||||
# Path the the log file
|
||||
logfile = ""
|
||||
|
||||
electrumUrl = "electrs:19001"
|
||||
electrumLiquidUrl = "electrs-liquid:19002"
|
||||
|
||||
[BOLTZ]
|
||||
# By default the daemon automatically connects to the official Boltz instance for the network LND is on
|
||||
# This value is used to override that
|
||||
url = "http://boltz-nginx:9001"
|
||||
|
||||
[DATABASE]
|
||||
# Path to the SQLite database file
|
||||
# path = "/home/michael/test.db"
|
||||
|
||||
[RPC]
|
||||
# Host of
|
||||
host = "0.0.0.0"
|
||||
|
||||
# Port of the gRPC interface
|
||||
port = 9002
|
||||
|
||||
# Whether the REST proxy for the gRPC interface should be disabled
|
||||
restDisabled = false
|
||||
|
||||
# Host of the REST proxy
|
||||
restHost = "0.0.0.0"
|
||||
|
||||
# Port of the REST proxy
|
||||
restPort = 9003
|
||||
|
||||
# Path to the TLS cert for the gRPC and REST interface
|
||||
tlsCert = ""
|
||||
|
||||
# Path to the TLS private key for the gRPC and REST interface
|
||||
tlsKey = ""
|
||||
noTls = true
|
||||
|
||||
# Whether the macaroon authentication for the gRPC and REST interface should be disabled
|
||||
noMacaroons = true
|
||||
|
||||
# Path to the admin macaroon for the gRPC and REST interface
|
||||
adminMacaroonPath = ""
|
||||
|
||||
# Path to the read only macaroon for the gRPC and REST interface
|
||||
readOnlyMacaroonPath = ""
|
||||
@@ -1,49 +0,0 @@
|
||||
upstream boltz {
|
||||
server boltz:9001;
|
||||
}
|
||||
|
||||
upstream boltzr {
|
||||
server boltz:9005;
|
||||
}
|
||||
|
||||
upstream ws {
|
||||
server boltz:9004;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 9001;
|
||||
listen [::]:9001;
|
||||
server_name localhost;
|
||||
|
||||
add_header Access-Control-Allow-Origin "*" always;
|
||||
add_header Access-Control-Allow-Methods 'GET, PATCH, DELETE, POST, OPTIONS' always;
|
||||
add_header Access-Control-Allow-Headers "*" always;
|
||||
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
if ($request_method = OPTIONS) {
|
||||
return 204;
|
||||
}
|
||||
|
||||
location /v2/ws {
|
||||
proxy_pass http://ws/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
}
|
||||
|
||||
location ~ ^/v2/(lightning|swap/rescue|swap/restore) {
|
||||
proxy_pass http://boltzr;
|
||||
}
|
||||
|
||||
location /streamswapstatus {
|
||||
proxy_pass http://boltzr;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://boltz;
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
[api]
|
||||
host = "0.0.0.0"
|
||||
port = 9_001
|
||||
|
||||
[grpc]
|
||||
host = "0.0.0.0"
|
||||
port = 9_000
|
||||
|
||||
[postgres]
|
||||
host = "boltz-postgres"
|
||||
port = 5432
|
||||
database = "boltz"
|
||||
username = "boltz"
|
||||
password = "boltz"
|
||||
|
||||
[sidecar]
|
||||
[sidecar.grpc]
|
||||
host = "127.0.0.1"
|
||||
port = 9003
|
||||
|
||||
[sidecar.ws]
|
||||
host = "0.0.0.0"
|
||||
port = 9004
|
||||
|
||||
[sidecar.api]
|
||||
host = "0.0.0.0"
|
||||
port = 9005
|
||||
|
||||
[swap]
|
||||
deferredClaimSymbols = ["BTC", "L-BTC"]
|
||||
|
||||
[[pairs]]
|
||||
base = "BTC"
|
||||
quote = "BTC"
|
||||
rate = 1
|
||||
fee = 0.5
|
||||
swapInFee = 0.1
|
||||
maxSwapAmount = 40_294_967
|
||||
minSwapAmount = 50_000
|
||||
|
||||
[pairs.timeoutDelta]
|
||||
chain = 1440
|
||||
reverse = 1440
|
||||
swapMinimal = 1440
|
||||
swapMaximal = 2880
|
||||
swapTaproot = 10080
|
||||
|
||||
[[pairs]]
|
||||
base = "L-BTC"
|
||||
quote = "BTC"
|
||||
fee = 0.25
|
||||
swapInFee = 0.1
|
||||
rate = 1
|
||||
maxSwapAmount = 40_294_967
|
||||
minSwapAmount = 100
|
||||
|
||||
[pairs.submarineSwap]
|
||||
minSwapAmount = 1_000
|
||||
minBatchedAmount = 21
|
||||
|
||||
[pairs.chainSwap]
|
||||
minSwapAmount = 25_000
|
||||
|
||||
[pairs.timeoutDelta]
|
||||
chain = 1440
|
||||
reverse = 1440
|
||||
swapMinimal = 1440
|
||||
swapMaximal = 2880
|
||||
swapTaproot = 10080
|
||||
|
||||
[[currencies]]
|
||||
symbol = "BTC"
|
||||
network = "bitcoinRegtest"
|
||||
minWalletBalance = 10_000_000
|
||||
minChannelBalance = 10_000_000
|
||||
maxSwapAmount = 40_294_967
|
||||
minSwapAmount = 10_000
|
||||
maxZeroConfAmount = 0
|
||||
|
||||
[currencies.chain]
|
||||
# mempoolSpace = "http://mempool-web:8090/api"
|
||||
host = "bitcoind"
|
||||
zmqpubrawtx = "tcp://bitcoind:29000"
|
||||
zmqpubrawblock = "tcp://bitcoind:29001"
|
||||
port = 18_443
|
||||
cookie = "/root/.bitcoin/regtest/.cookie"
|
||||
feeFloor = 0.2
|
||||
|
||||
wallet = "lnbits"
|
||||
|
||||
[currencies.lnd]
|
||||
host = "lnd-2"
|
||||
port = 10_009
|
||||
certpath = "/data/lnd/tls.cert"
|
||||
macaroonpath = "/data/lnd/data/chain/bitcoin/regtest/admin.macaroon"
|
||||
|
||||
|
||||
[liquid]
|
||||
symbol = "L-BTC"
|
||||
network = "liquidRegtest"
|
||||
|
||||
maxSwapAmount = 40_294_967
|
||||
minSwapAmount = 10_000
|
||||
maxZeroConfAmount = 40_294_967
|
||||
|
||||
|
||||
[liquid.chain]
|
||||
host = "elementsd"
|
||||
port = 18884
|
||||
cookie = "/root/.elements/liquidregtest/.cookie"
|
||||
zmqpubrawtx = "tcp://elementsd:31000"
|
||||
zmqpubhashblock = "tcp://elementsd:31002"
|
||||
|
||||
wallet = "lnbits"
|
||||
@@ -1,28 +0,0 @@
|
||||
eclair {
|
||||
chain = "regtest"
|
||||
|
||||
api {
|
||||
binding-ip = "0.0.0.0"
|
||||
enabled = true
|
||||
port = 8080
|
||||
password = "lnbits"
|
||||
}
|
||||
|
||||
bitcoind {
|
||||
host = "bitcoind"
|
||||
rpcport = 18443
|
||||
auth = "safecookie"
|
||||
cookie = "/root/.bitcoin/regtest/.cookie"
|
||||
|
||||
zmqblock = "tcp://bitcoind:29002"
|
||||
zmqtx = "tcp://bitcoind:29000"
|
||||
}
|
||||
|
||||
channel {
|
||||
max-funding-satoshis = 10000000000
|
||||
}
|
||||
|
||||
features {
|
||||
option_support_large_channel = mandatory
|
||||
}
|
||||
}
|
||||
@@ -1,434 +0,0 @@
|
||||
services:
|
||||
|
||||
lnbits:
|
||||
hostname: lnbits
|
||||
depends_on:
|
||||
- lnd-3
|
||||
image: lnbits/lnbits
|
||||
restart: on-failure
|
||||
user: "0:0"
|
||||
environment:
|
||||
LNBITS_PORT: 5001
|
||||
DEBUG: true
|
||||
LNBITS_ADMIN_UI: false
|
||||
LNBITS_BACKEND_WALLET_CLASS: "LndRestWallet"
|
||||
LNBITS_DATA_FOLDER: "./data"
|
||||
LND_REST_ENDPOINT: "https://lnd-3:8081/"
|
||||
LND_REST_CERT: "./lnd/tls.cert"
|
||||
LND_REST_MACAROON: "./lnd/data/chain/bitcoin/regtest/admin.macaroon"
|
||||
ports:
|
||||
- 5001:5001
|
||||
volumes:
|
||||
- lnbits-data:/app/data
|
||||
- ./data/lnd-3:/app/lnd:uid=1000,gid=1000
|
||||
|
||||
boltz:
|
||||
hostname: boltz
|
||||
depends_on:
|
||||
- lnd-2
|
||||
- boltz-postgres
|
||||
restart: always
|
||||
image: boltz/boltz:v3.12.1
|
||||
ports:
|
||||
- 9000:9000
|
||||
entrypoint: "sh -c 'sleep 30; /boltz-backend/bin/boltzd'"
|
||||
volumes:
|
||||
- ./data/lnd-2:/data/lnd/
|
||||
- ./data/boltz/:/root/.boltz/
|
||||
- elements-data:/root/.elements
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
boltz-client:
|
||||
hostname: boltz-client
|
||||
depends_on:
|
||||
- boltz
|
||||
restart: always
|
||||
image: boltz/boltz-client:latest
|
||||
ports:
|
||||
- 9002:9002
|
||||
- 9003:9003
|
||||
expose:
|
||||
- 9002
|
||||
healthcheck:
|
||||
test: ['CMD', 'boltzcli', '--host', 'boltz-client', 'getinfo']
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
start_period: 0s
|
||||
volumes:
|
||||
- elements-data:/root/.elements
|
||||
- ./data/boltz-client:/root/.boltz
|
||||
|
||||
boltz-backend-nginx:
|
||||
hostname: boltz-nginx
|
||||
restart: always
|
||||
image: nginx:latest
|
||||
ports:
|
||||
- 9001:9001
|
||||
volumes:
|
||||
- nginx-data:/etc/nginx/conf.d
|
||||
healthcheck:
|
||||
test: ['CMD-SHELL', 'curl http://localhost:9001/version']
|
||||
timeout: 1s
|
||||
retries: 10
|
||||
interval: 1s
|
||||
start_period: 0s
|
||||
|
||||
boltz-postgres:
|
||||
hostname: boltz-postgres
|
||||
restart: always
|
||||
image: postgres:14-alpine
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready --dbname boltz --username boltz"]
|
||||
interval: 5s
|
||||
timeout: 30s
|
||||
retries: 10
|
||||
start_period: 5s
|
||||
environment:
|
||||
- POSTGRES_DB=boltz
|
||||
- POSTGRES_USER=boltz
|
||||
- POSTGRES_PASSWORD=boltz
|
||||
expose:
|
||||
- 5432
|
||||
|
||||
bitcoind:
|
||||
hostname: bitcoind
|
||||
image: boltz/bitcoin-core:25.0
|
||||
command:
|
||||
- -regtest
|
||||
- -fallbackfee=0.00000253
|
||||
- -zmqpubrawtx=tcp://0.0.0.0:29000
|
||||
- -zmqpubrawblock=tcp://0.0.0.0:29001
|
||||
- -zmqpubhashblock=tcp://0.0.0.0:29002
|
||||
- -txindex
|
||||
- -rpcallowip=0.0.0.0/0
|
||||
- -rpcbind=0.0.0.0
|
||||
- -addresstype=bech32
|
||||
- -changetype=bech32
|
||||
- -dbcache=2048
|
||||
- -rpcworkqueue=256
|
||||
volumes:
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
expose:
|
||||
- 29000
|
||||
- 29001
|
||||
- 29002
|
||||
- 18443
|
||||
- 18444
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD",
|
||||
"bitcoin-cli",
|
||||
"--rpccookiefile=/root/.bitcoin/regtest/.cookie",
|
||||
"-regtest",
|
||||
"getblockchaininfo",
|
||||
]
|
||||
timeout: 1s
|
||||
retries: 1
|
||||
interval: 1s
|
||||
start_period: 0s
|
||||
|
||||
clightning-1:
|
||||
hostname: clightning-1
|
||||
depends_on:
|
||||
- bitcoind
|
||||
image: boltz/c-lightning:24.11
|
||||
command:
|
||||
- --large-channels
|
||||
- --network=regtest
|
||||
- --grpc-port=9736
|
||||
- --bind-addr=0.0.0.0:9735
|
||||
- --bitcoin-rpcconnect=bitcoind
|
||||
- --bitcoin-rpcport=18443
|
||||
- --clnrest-host=0.0.0.0
|
||||
- --clnrest-port=3010
|
||||
expose:
|
||||
- 9735
|
||||
ports:
|
||||
- 9736:9736
|
||||
- 3010:3010
|
||||
volumes:
|
||||
- ./data/clightning-1:/root/.lightning/
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
clightning-2:
|
||||
hostname: clightning-2
|
||||
depends_on:
|
||||
- bitcoind
|
||||
image: boltz/c-lightning:22.11.1
|
||||
command:
|
||||
- --large-channels
|
||||
- --network=regtest
|
||||
- --grpc-port=9737
|
||||
- --bind-addr=0.0.0.0:9735
|
||||
- --bitcoin-rpcconnect=bitcoind
|
||||
- --bitcoin-rpcport=18443
|
||||
expose:
|
||||
- 9735
|
||||
ports:
|
||||
- 9737:9737
|
||||
volumes:
|
||||
- ./data/clightning-2:/root/.lightning/
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
clightning-2-rest:
|
||||
hostname: clightning-2-rest
|
||||
depends_on:
|
||||
- clightning-2
|
||||
image: saubyk/c-lightning-rest:0.10.7
|
||||
entrypoint: "sh -c 'sleep 35 && /sbin/tini -g -- ./docker-entrypoint.sh'"
|
||||
ports:
|
||||
- 3001:3001
|
||||
expose:
|
||||
- 3001
|
||||
volumes:
|
||||
- ./data/clightning-2:/root/.lightning/:uid=1000,gid=1000
|
||||
- ./data/clightning-2-rest:/usr/src/app/certs/
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
clightning-3:
|
||||
hostname: clightning-3
|
||||
depends_on:
|
||||
- bitcoind
|
||||
image: boltz/c-lightning:24.11
|
||||
command:
|
||||
- --large-channels
|
||||
- --network=regtest
|
||||
- --grpc-port=9738
|
||||
- --bind-addr=0.0.0.0:9735
|
||||
- --bitcoin-rpcconnect=bitcoind
|
||||
- --bitcoin-rpcport=18443
|
||||
expose:
|
||||
- 9735
|
||||
ports:
|
||||
- 9738:9738
|
||||
volumes:
|
||||
- ./data/clightning-3:/root/.lightning/
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
lnd-1:
|
||||
hostname: lnd-1
|
||||
depends_on:
|
||||
- bitcoind
|
||||
image: boltz/lnd:0.18.4-beta
|
||||
restart: on-failure
|
||||
command:
|
||||
- --listen=lnd-1:9735
|
||||
- --rpclisten=lnd-1:10009
|
||||
- --restlisten=lnd-1:8081
|
||||
- --bitcoin.active
|
||||
- --bitcoin.regtest
|
||||
- --bitcoin.node=bitcoind
|
||||
- --bitcoind.rpchost=bitcoind
|
||||
- --bitcoind.rpccookie=/root/.bitcoin/regtest/.cookie
|
||||
- --bitcoind.zmqpubrawtx=bitcoind:29000
|
||||
- --bitcoind.zmqpubrawblock=bitcoind:29001
|
||||
- --noseedbackup
|
||||
- --protocol.wumbo-channels
|
||||
expose:
|
||||
- 8081
|
||||
- 9735
|
||||
- 10009
|
||||
volumes:
|
||||
- ./data/lnd-1:/root/.lnd/
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
lnd-2:
|
||||
hostname: lnd-2
|
||||
depends_on:
|
||||
- bitcoind
|
||||
image: boltz/lnd:0.19.3-beta
|
||||
restart: on-failure
|
||||
command:
|
||||
- --listen=lnd-2:9735
|
||||
- --rpclisten=lnd-2:10009
|
||||
- --restlisten=lnd-2:8081
|
||||
- --bitcoin.active
|
||||
- --bitcoin.regtest
|
||||
- --bitcoin.node=bitcoind
|
||||
- --bitcoind.rpchost=bitcoind
|
||||
- --bitcoind.rpccookie=/root/.bitcoin/regtest/.cookie
|
||||
- --bitcoind.zmqpubrawtx=bitcoind:29000
|
||||
- --bitcoind.zmqpubrawblock=bitcoind:29001
|
||||
- --noseedbackup
|
||||
- --protocol.wumbo-channels
|
||||
expose:
|
||||
- 8081
|
||||
- 9735
|
||||
- 10009
|
||||
volumes:
|
||||
- ./data/lnd-2:/root/.lnd/
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
lnd-3:
|
||||
hostname: lnd-3
|
||||
depends_on:
|
||||
- bitcoind
|
||||
image: boltz/lnd:0.18.4-beta
|
||||
restart: on-failure
|
||||
command:
|
||||
- --listen=lnd-3:9735
|
||||
- --rpclisten=lnd-3:10009
|
||||
- --restlisten=lnd-3:8081
|
||||
- --bitcoin.active
|
||||
- --bitcoin.regtest
|
||||
- --bitcoin.node=bitcoind
|
||||
- --bitcoind.rpchost=bitcoind
|
||||
- --bitcoind.rpccookie=/root/.bitcoin/regtest/.cookie
|
||||
- --bitcoind.zmqpubrawtx=bitcoind:29000
|
||||
- --bitcoind.zmqpubrawblock=bitcoind:29001
|
||||
- --noseedbackup
|
||||
- --protocol.wumbo-channels
|
||||
ports:
|
||||
- 8081:8081
|
||||
- 10009:10009
|
||||
expose:
|
||||
- 8081
|
||||
- 9735
|
||||
- 10009
|
||||
volumes:
|
||||
- ./data/lnd-3:/root/.lnd/
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
lnd-4:
|
||||
hostname: lnd-4
|
||||
depends_on:
|
||||
- bitcoind
|
||||
image: boltz/lnd:0.18.4-beta
|
||||
restart: on-failure
|
||||
command:
|
||||
- --listen=lnd-4:9735
|
||||
- --rpclisten=lnd-4:10009
|
||||
- --restlisten=lnd-4:8081
|
||||
- --bitcoin.active
|
||||
- --bitcoin.regtest
|
||||
- --bitcoin.node=bitcoind
|
||||
- --bitcoind.rpchost=bitcoind
|
||||
- --bitcoind.rpccookie=/root/.bitcoin/regtest/.cookie
|
||||
- --bitcoind.zmqpubrawtx=bitcoind:29000
|
||||
- --bitcoind.zmqpubrawblock=bitcoind:29001
|
||||
- --noseedbackup
|
||||
- --protocol.wumbo-channels
|
||||
expose:
|
||||
- 8081
|
||||
- 9735
|
||||
- 10009
|
||||
volumes:
|
||||
- ./data/lnd-4:/root/.lnd/
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
eclair:
|
||||
hostname: eclair
|
||||
depends_on:
|
||||
- bitcoind
|
||||
image: boltz/eclair:0.8.0
|
||||
restart: on-failure
|
||||
entrypoint: "sh -c 'JAVA_OPTS=-Xmx512m /eclair-node/bin/eclair-node.sh -Declair.datadir=/root/eclair -Declair.printToConsole'"
|
||||
ports:
|
||||
- 8082:8080
|
||||
expose:
|
||||
- 9735
|
||||
- 8080
|
||||
volumes:
|
||||
- ./data/eclair:/root/eclair
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
electrs:
|
||||
hostname: electrs
|
||||
restart: always
|
||||
image: boltz/electrs:latest
|
||||
entrypoint: ["electrs-bitcoin"]
|
||||
command:
|
||||
- --electrum-rpc-addr
|
||||
- electrs:19001
|
||||
- --http-addr
|
||||
- electrs:3002
|
||||
- --daemon-rpc-addr
|
||||
- bitcoind:18443
|
||||
- --network
|
||||
- regtest
|
||||
- --jsonrpc-import
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -s $(hostname):3002/blocks/tip/height"]
|
||||
timeout: 1s
|
||||
retries: 20
|
||||
interval: 2s
|
||||
start_period: 5s
|
||||
ports:
|
||||
- 19001:19001
|
||||
- 3002:3002
|
||||
volumes:
|
||||
- bitcoin-data:/root/.bitcoin
|
||||
|
||||
elementsd:
|
||||
hostname: elementsd
|
||||
restart: always
|
||||
image: boltz/elements:latest
|
||||
expose:
|
||||
- 31001
|
||||
ports:
|
||||
- 31000:31000
|
||||
- 31002:31002
|
||||
- 18884:18884
|
||||
command:
|
||||
- -chain=liquidregtest
|
||||
- -txindex=1
|
||||
- -rest=1
|
||||
- -server=1
|
||||
- -rpcallowip=0.0.0.0/0
|
||||
- -validatepegin=0
|
||||
- -initialfreecoins=2100000000000000
|
||||
- -fallbackfee=0.000001
|
||||
- -rpcbind=0.0.0.0
|
||||
- -rpcport=18884
|
||||
- -zmqpubrawtx=tcp://0.0.0.0:31000
|
||||
- -zmqpubrawblock=tcp://0.0.0.0:31001
|
||||
- -zmqpubhashblock=tcp://0.0.0.0:31002
|
||||
- -acceptdiscountct=1
|
||||
- -creatediscountct=1
|
||||
volumes:
|
||||
- elements-data:/root/.elements
|
||||
|
||||
electrs-liquid:
|
||||
hostname: electrs-liquid
|
||||
restart: always
|
||||
image: boltz/electrs:latest
|
||||
entrypoint: ["electrs-liquid"]
|
||||
command:
|
||||
- --electrum-rpc-addr
|
||||
- electrs-liquid:19002
|
||||
- --http-addr
|
||||
- electrs-liquid:3003
|
||||
- --daemon-rpc-addr
|
||||
- elementsd:18884
|
||||
- --daemon-dir
|
||||
- /root/.elements
|
||||
- --network
|
||||
- liquidregtest
|
||||
- --parent-network
|
||||
- regtest
|
||||
- --jsonrpc-import
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -s $(hostname):3003/blocks/tip/height"]
|
||||
timeout: 15s
|
||||
retries: 20
|
||||
interval: 2s
|
||||
start_period: 5s
|
||||
ports:
|
||||
- 19002:19002
|
||||
- 3003:3003
|
||||
volumes:
|
||||
- elements-data:/root/.elements
|
||||
|
||||
volumes:
|
||||
lnbits-data:
|
||||
bitcoin-data:
|
||||
elements-data:
|
||||
nginx-data:
|
||||
name: nginx-data
|
||||
driver: local
|
||||
driver_opts:
|
||||
type: none
|
||||
o: bind
|
||||
device: ./data/boltz-nginx/
|
||||
@@ -1,322 +0,0 @@
|
||||
#!/bin/sh
|
||||
export COMPOSE_PROJECT_NAME=lnbits
|
||||
|
||||
boltzcli-sim() {
|
||||
docker exec -it lnbits-boltz-client-1 boltzcli "$@"
|
||||
}
|
||||
|
||||
bitcoin-cli-sim() {
|
||||
docker exec lnbits-bitcoind-1 bitcoin-cli -regtest "$@"
|
||||
}
|
||||
|
||||
elements-cli-sim() {
|
||||
docker exec lnbits-elementsd-1 elements-cli -rpcport=18884 -chain=liquidregtest "$@"
|
||||
}
|
||||
|
||||
# args(i, cmd)
|
||||
lightning-cli-sim() {
|
||||
i=$1
|
||||
shift # shift first argument so we can use $@
|
||||
docker exec lnbits-clightning-$i-1 lightning-cli --network regtest "$@"
|
||||
}
|
||||
|
||||
# args(i, cmd)
|
||||
lncli-sim() {
|
||||
i=$1
|
||||
shift # shift first argument so we can use $@
|
||||
docker exec lnbits-lnd-$i-1 lncli --network regtest --rpcserver=lnd-$i:10009 "$@"
|
||||
}
|
||||
|
||||
get-eclair-pubkey() {
|
||||
while true; do
|
||||
pubkey=$(docker exec lnbits-eclair-1 curl http://localhost:8080/getinfo -X POST -s -u :lnbits | jq -r .nodeId 2> /dev/null)
|
||||
pubkeyPrefix=$(echo $pubkey | cut -c1,2)
|
||||
if [[ "$pubkeyPrefix" == "02" || "$pubkeyPrefix" == "03" ]]; then
|
||||
echo $pubkey
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
wait-for-eclair-channel() {
|
||||
while true; do
|
||||
state=$(docker exec lnbits-eclair-1 curl http://localhost:8080/channels -X POST -s -u :lnbits | jq -r ".[0].state")
|
||||
pending=$(docker exec lnbits-eclair-1 curl -s http://localhost:8080/channels -X POST -u :lnbits| jq '. | length')
|
||||
echo "eclair-1 pendingchannels: $pending, current state: $state"
|
||||
if [[ "$state" == "NORMAL" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
# args(i)
|
||||
fund_boltz_client() {
|
||||
# first address of seed: abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about
|
||||
address="el1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f3mmz5l7uw5pqmx6xf5xy50hsn6vhkm5euwt72x878eq6zxx2z0z676mna6kdq"
|
||||
echo "funding: $address on boltz-client"
|
||||
elements-cli-sim -named sendtoaddress address=$address amount=30 fee_rate=100 > /dev/null
|
||||
}
|
||||
|
||||
|
||||
# args(i)
|
||||
fund_clightning_node() {
|
||||
address=$(lightning-cli-sim $1 newaddr | jq -r .bech32)
|
||||
echo "funding: $address on clightning-node: $1"
|
||||
bitcoin-cli-sim -named sendtoaddress address=$address amount=30 fee_rate=100 > /dev/null
|
||||
}
|
||||
|
||||
# args(i)
|
||||
fund_lnd_node() {
|
||||
address=$(lncli-sim $1 newaddress p2wkh | jq -r .address)
|
||||
echo "funding: $address on lnd-node: $1"
|
||||
bitcoin-cli-sim -named sendtoaddress address=$address amount=30 fee_rate=100 > /dev/null
|
||||
}
|
||||
|
||||
# args(i, j)
|
||||
connect_clightning_node() {
|
||||
pubkey=$(lightning-cli-sim $2 getinfo | jq -r '.id')
|
||||
lightning-cli-sim $1 connect $pubkey@lnbits-clightning-$2-1:9735 | jq -r '.id'
|
||||
}
|
||||
|
||||
lnbits-regtest-start(){
|
||||
if ! command -v jq &> /dev/null
|
||||
then
|
||||
echo "jq is not installed"
|
||||
exit
|
||||
fi
|
||||
if ! command -v docker &> /dev/null
|
||||
then
|
||||
echo "docker is not installed"
|
||||
exit
|
||||
fi
|
||||
if ! command -v docker version &> /dev/null
|
||||
then
|
||||
echo "dockerd is not running"
|
||||
exit
|
||||
fi
|
||||
lnbits-regtest-stop
|
||||
docker compose up -d --remove-orphans
|
||||
lnbits-regtest-init
|
||||
}
|
||||
|
||||
lnbits-regtest-start-log(){
|
||||
lnbits-regtest-stop
|
||||
docker compose up --remove-orphans
|
||||
lnbits-regtest-init
|
||||
}
|
||||
|
||||
lnbits-regtest-stop(){
|
||||
docker compose down --volumes
|
||||
# clean up lightning node data
|
||||
sudo rm -rf ./data/clightning-1 ./data/clightning-2 ./data/clightning-3 ./data/lnd-1 ./data/lnd-2 ./data/lnd-3 ./data/lnd-4 ./data/boltz/boltz.db ./data/eclair/regtest ./data/boltz-client/liquid-wallet ./data/boltz-client/bitcoin-wallet ./data/boltz-client/wallet ./data/boltz-client/boltz.db
|
||||
# recreate lightning node data folders preventing permission errors
|
||||
mkdir ./data/clightning-1 ./data/clightning-2 ./data/clightning-3 ./data/lnd-1 ./data/lnd-2 ./data/lnd-3 ./data/lnd-4
|
||||
}
|
||||
|
||||
lnbits-regtest-restart(){
|
||||
lnbits-regtest-stop
|
||||
lnbits-regtest-start
|
||||
}
|
||||
|
||||
boltz-client-init(){
|
||||
for i in 0 1 2; do
|
||||
fund_boltz_client
|
||||
done
|
||||
elements-cli-sim -generate 3 > /dev/null
|
||||
}
|
||||
|
||||
lnbits-bitcoin-init(){
|
||||
echo "init_bitcoin_wallet..."
|
||||
bitcoin-cli-sim createwallet lnbits || bitcoin-cli-sim loadwallet lnbits
|
||||
echo "mining 150 blocks..."
|
||||
bitcoin-cli-sim -generate 150 > /dev/null
|
||||
}
|
||||
|
||||
lnbits-elements-init(){
|
||||
echo "init_elements_wallet..."
|
||||
elements-cli-sim createwallet lnbits || elements-cli-sim loadwallet lnbits
|
||||
echo "mining 150 blocks..."
|
||||
elements-cli-sim -generate 150 > /dev/null
|
||||
elements-cli-sim rescanblockchain
|
||||
}
|
||||
|
||||
lnbits-init(){
|
||||
echo "init_lnbits..."
|
||||
docker exec lnbits-lnbits-1 uv run python tools/create_fake_admin.py
|
||||
}
|
||||
|
||||
lnbits-regtest-init(){
|
||||
lnbits-bitcoin-init
|
||||
lnbits-elements-init
|
||||
lnbits-lightning-sync
|
||||
lnbits-lightning-init
|
||||
boltz-client-init
|
||||
lnbits-init
|
||||
}
|
||||
|
||||
lnbits-lightning-sync(){
|
||||
wait-for-clightning-sync 1
|
||||
wait-for-clightning-sync 2
|
||||
wait-for-clightning-sync 3
|
||||
wait-for-lnd-sync 1
|
||||
wait-for-lnd-sync 2
|
||||
wait-for-lnd-sync 3
|
||||
wait-for-lnd-sync 4
|
||||
}
|
||||
|
||||
lnbits-lightning-init(){
|
||||
|
||||
# create 10 UTXOs for each node
|
||||
for i in 0 1 2; do
|
||||
fund_clightning_node 1
|
||||
fund_clightning_node 2
|
||||
fund_clightning_node 3
|
||||
fund_lnd_node 1
|
||||
fund_lnd_node 2
|
||||
fund_lnd_node 3
|
||||
fund_lnd_node 4
|
||||
done
|
||||
|
||||
echo "mining 3 blocks..."
|
||||
bitcoin-cli-sim -generate 3 > /dev/null
|
||||
|
||||
lnbits-lightning-sync
|
||||
|
||||
channel_confirms=6
|
||||
channel_size=24000000 # 0.024 btc
|
||||
balance_size=12000000 # 0.12 btc
|
||||
balance_size_msat=12000000000 # 0.12 btc
|
||||
|
||||
# lnd-1 -> lnd-2
|
||||
lncli-sim 1 connect $(lncli-sim 2 getinfo | jq -r '.identity_pubkey')@lnbits-lnd-2-1 > /dev/null
|
||||
echo "open channel from lnd-1 to lnd-2"
|
||||
lncli-sim 1 openchannel $(lncli-sim 2 getinfo | jq -r '.identity_pubkey') $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 1
|
||||
|
||||
# lnd-1 -> lnd-3
|
||||
lncli-sim 1 connect $(lncli-sim 3 getinfo | jq -r '.identity_pubkey')@lnbits-lnd-3-1 > /dev/null
|
||||
echo "open channel from lnd-1 to lnd-3"
|
||||
lncli-sim 1 openchannel $(lncli-sim 3 getinfo | jq -r '.identity_pubkey') $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 1
|
||||
|
||||
# lnd-1 -> cln-1
|
||||
lncli-sim 1 connect $(lightning-cli-sim 1 getinfo | jq -r '.id')@lnbits-clightning-1-1 > /dev/null
|
||||
echo "open channel from lnd-1 to cln-1"
|
||||
lncli-sim 1 openchannel $(lightning-cli-sim 1 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 1
|
||||
|
||||
# lnd-1 -> cln-2
|
||||
lncli-sim 1 connect $(lightning-cli-sim 2 getinfo | jq -r '.id')@lnbits-clightning-2-1 > /dev/null
|
||||
echo "open channel from lnd-1 to cln-2"
|
||||
lncli-sim 1 openchannel $(lightning-cli-sim 2 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 1
|
||||
|
||||
# lnd-1 -> cln-3
|
||||
lncli-sim 1 connect $(lightning-cli-sim 3 getinfo | jq -r '.id')@lnbits-clightning-3-1 > /dev/null
|
||||
echo "open channel from lnd-1 to cln-3"
|
||||
lncli-sim 1 openchannel $(lightning-cli-sim 3 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 1
|
||||
|
||||
# lnd-2 -> cln-2
|
||||
lncli-sim 2 connect $(lightning-cli-sim 2 getinfo | jq -r '.id')@lnbits-clightning-2-1 > /dev/null
|
||||
echo "open channel from lnd-2 to cln-2"
|
||||
lncli-sim 2 openchannel $(lightning-cli-sim 2 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 2
|
||||
|
||||
# lnd-3 -> cln-3
|
||||
lncli-sim 3 connect $(lightning-cli-sim 3 getinfo | jq -r '.id')@lnbits-clightning-3-1 > /dev/null
|
||||
echo "open channel from lnd-3 to cln-1"
|
||||
lncli-sim 3 openchannel $(lightning-cli-sim 3 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 3
|
||||
|
||||
# lnd-3 -> cln-1
|
||||
lncli-sim 3 connect $(lightning-cli-sim 1 getinfo | jq -r '.id')@lnbits-clightning-1-1 > /dev/null
|
||||
echo "open channel from lnd-3 to cln-1"
|
||||
lncli-sim 3 openchannel $(lightning-cli-sim 1 getinfo | jq -r '.id') $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 3
|
||||
|
||||
# lnd-1 -> eclair-1
|
||||
lncli-sim 1 connect $(get-eclair-pubkey)@lnbits-eclair-1 > /dev/null
|
||||
echo "open channel from lnd-2 to eclair-1"
|
||||
lncli-sim 1 openchannel $(get-eclair-pubkey) $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 1
|
||||
|
||||
# lnd-2 -> eclair-1
|
||||
lncli-sim 2 connect $(get-eclair-pubkey)@lnbits-eclair-1 > /dev/null
|
||||
echo "open channel from lnd-2 to eclair-1"
|
||||
lncli-sim 2 openchannel $(get-eclair-pubkey) $channel_size $balance_size > /dev/null
|
||||
bitcoin-cli-sim -generate $channel_confirms > /dev/null
|
||||
wait-for-lnd-channel 2
|
||||
|
||||
wait-for-clightning-channel 1
|
||||
wait-for-clightning-channel 2
|
||||
wait-for-clightning-channel 3
|
||||
|
||||
wait-for-eclair-channel
|
||||
|
||||
lnbits-lightning-sync
|
||||
|
||||
}
|
||||
|
||||
wait-for-lnd-channel(){
|
||||
while true; do
|
||||
pending=$(lncli-sim $1 pendingchannels | jq -r '.pending_open_channels | length')
|
||||
echo "lnd-$1 pendingchannels: $pending"
|
||||
if [[ "$pending" == "0" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
wait-for-lnd-sync(){
|
||||
while true; do
|
||||
if [[ "$(lncli-sim $1 getinfo 2>&1 | jq -r '.synced_to_chain' 2> /dev/null)" == "true" ]]; then
|
||||
echo "lnd-$1 is synced!"
|
||||
break
|
||||
fi
|
||||
echo "waiting for lnd-$1 to sync..."
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
wait-for-clightning-channel(){
|
||||
while true; do
|
||||
pending=$(lightning-cli-sim $1 getinfo | jq -r '.num_pending_channels | length')
|
||||
echo "cln-$1 pendingchannels: $pending"
|
||||
if [[ "$pending" == "0" ]]; then
|
||||
if [[ "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.warning_bitcoind_sync' 2> /dev/null)" == "null" ]]; then
|
||||
if [[ "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.warning_lightningd_sync' 2> /dev/null)" == "null" ]]; then
|
||||
break
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
wait-for-clightning-sync(){
|
||||
while true; do
|
||||
if [[ ! "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.id' 2> /dev/null)" == "null" ]]; then
|
||||
if [[ "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.warning_bitcoind_sync' 2> /dev/null)" == "null" ]]; then
|
||||
if [[ "$(lightning-cli-sim $1 getinfo 2>&1 | jq -r '.warning_lightningd_sync' 2> /dev/null)" == "null" ]]; then
|
||||
echo "cln-$1 is synced!"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo "waiting for cln-$1 to sync..."
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
#!/bin/bash
|
||||
print_success() {
|
||||
printf "\033[;1;32mPASSED\033[;0m $1\n"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
printf "\033[;1;31mFAILED\033[;0m $1\n"
|
||||
}
|
||||
|
||||
run(){
|
||||
label=$1
|
||||
value=$2
|
||||
cmd=$3
|
||||
if [[ "$cmd" == "$value" ]]; then
|
||||
print_success "$label is $cmd"
|
||||
else
|
||||
print_error "$label is $cmd, should be $value"
|
||||
failed="true"
|
||||
fi
|
||||
}
|
||||
|
||||
failed="false"
|
||||
blockheight=213
|
||||
utxos=3
|
||||
channel_size=24000000 # 0.024 btc
|
||||
balance_size=12000000 # 0.012 btc
|
||||
|
||||
source $(pwd)/docker-scripts.sh
|
||||
lnbits-regtest-start
|
||||
echo "=================================="
|
||||
printf "\033[;1;36mregtest started! starting tests...\033[;0m\n"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
|
||||
for i in 1 2 3; do
|
||||
run "lnd-$i .synced_to_chain" "true" $(lncli-sim $i getinfo | jq -r ".synced_to_chain")
|
||||
run "lnd-$i utxo count" $utxos $(lncli-sim $i listunspent | jq -r ".utxos | length")
|
||||
run "lnd-$i .block_height" $blockheight $(lncli-sim $i getinfo | jq -r ".block_height")
|
||||
if [[ "$i" == "1" ]]; then
|
||||
channel_count=6
|
||||
else
|
||||
channel_count=3
|
||||
fi
|
||||
run "lnd-$i openchannels" $channel_count $(lncli-sim $i listchannels | jq -r ".channels | length")
|
||||
run "lnd-$i .channels[0].capacity" $channel_size $(lncli-sim $i listchannels | jq -r ".channels[0].capacity")
|
||||
run "lnd-$i .channels[0].push_amount_sat" $balance_size $(lncli-sim $i listchannels | jq -r ".channels[0].push_amount_sat")
|
||||
done
|
||||
for i in 1 2 3; do
|
||||
# run "cln-$i blockheight" $blockheight $(lightning-cli-sim $i getinfo | jq -r ".blockheight")
|
||||
run "cln-$i utxo count" $utxos $(lightning-cli-sim $i listfunds | jq -r ".outputs | length")
|
||||
run "cln-$i openchannels" 2 $(lightning-cli-sim $i getinfo | jq -r ".num_active_channels")
|
||||
run "cln-$i channel[0].state" "CHANNELD_NORMAL" $(lightning-cli-sim $i listfunds | jq -r ".channels[0].state")
|
||||
run "cln-$i channel[0].amount_msat" $(($channel_size * 1000)) $(lightning-cli-sim $i listfunds | jq -r ".channels[0].amount_msat" | sed 's/msat//g')
|
||||
run "cln-$i channel[0].our_amount_msat" $(($balance_size * 1000)) $(lightning-cli-sim $i listfunds | jq -r ".channels[0].our_amount_msat" | sed 's/msat//g')
|
||||
done
|
||||
|
||||
run "eclair-1 openchannels" 2 $(docker exec lnbits-eclair-1 curl -s http://localhost:8080/channels -X POST -u :lnbits| jq '. | length')
|
||||
run "eclair-1 blockHeight" $blockheight $(docker exec lnbits-eclair-1 curl -s http://localhost:8080/getinfo -X POST -u :lnbits| jq '.blockHeight')
|
||||
run "lnbits service status" "200" $(curl -s -o /dev/null -w "%{http_code}" "http://localhost:5001/")
|
||||
run "boltz service status" "200" $(curl -s -o /dev/null --head -w "%{http_code}" "http://localhost:9001/version")
|
||||
|
||||
# return non-zero exit code if a test fails
|
||||
if [[ "$failed" == "true" ]]; then
|
||||
echo ""
|
||||
echo "=================================="
|
||||
print_error "one more more tests failed"
|
||||
echo "=================================="
|
||||
exit 1
|
||||
else
|
||||
echo ""
|
||||
echo "=================================="
|
||||
print_success "all tests passed! yay!"
|
||||
echo "=================================="
|
||||
fi
|
||||
@@ -1,36 +0,0 @@
|
||||
ARG LNBITS_TAG=latest
|
||||
|
||||
FROM lnbits/lnbits:${LNBITS_TAG}
|
||||
|
||||
RUN apt-get update && apt-get -y upgrade
|
||||
RUN apt-get install -y netcat-openbsd npm nodejs git
|
||||
|
||||
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
ENV PATH="/root/.local/bin:$PATH"
|
||||
|
||||
# install sparksidecar
|
||||
RUN git clone https://github.com/lnbits/spark_sidecar
|
||||
RUN cd spark_sidecar && npm install
|
||||
|
||||
# Reinstall dependencies for lnbits just in case (needed for CMD usage)
|
||||
RUN uv sync --all-extras
|
||||
|
||||
# LNBITS
|
||||
ENV LNBITS_PORT="5000"
|
||||
ENV LNBITS_HOST="0.0.0.0"
|
||||
ENV LNBITS_BACKEND_WALLET_CLASS="SparkL2Wallet"
|
||||
ENV FUNDING_SOURCE_MAX_RETRIES=10
|
||||
|
||||
# spark sidecar
|
||||
ENV SPARK_MNEMONIC="bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom"
|
||||
ENV SPARK_NETWORK="MAINNET"
|
||||
ENV SPARK_SIDECAR_PORT="8765"
|
||||
ENV SPARK_PAY_WAIT_MS="20000"
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
# Entrypoint to start sparksidebar and LNbits
|
||||
CMD ["/entrypoint.sh"]
|
||||
@@ -1,25 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
node spark_sidecar/server.mjs &
|
||||
|
||||
SIDECAR_PID=$!
|
||||
|
||||
# Wait for startup
|
||||
for i in {1..10}; do
|
||||
if nc -z localhost $SPARK_SIDECAR_PORT; then
|
||||
echo "sparksidebar is up!"
|
||||
break
|
||||
fi
|
||||
echo "Waiting for sparksidebar to start..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Optional: check if still not up
|
||||
if ! nc -z localhost $SPARK_SIDECAR_PORT; then
|
||||
echo "sparksidebar did not start successfully."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Starting LNbits on $LNBITS_HOST:$LNBITS_PORT..."
|
||||
exec uv run lnbits --port "$LNBITS_PORT" --host "$LNBITS_HOST" --forwarded-allow-ips='*'
|
||||
@@ -50,7 +50,6 @@ Below is a side-by-side comparison of Lightning funding sources you can use with
|
||||
| **Blink** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; focuses on mobile integrations. |
|
||||
| **ZBD** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Gaming-focused payment platform. |
|
||||
| **Spark (CLN)** | Self-custodial | ❌ | Higher | ✅ | High | Manual | Moderate | High | Infrastructure cost and channel opening fees | High | Web interface for CLN; requires Spark server setup. |
|
||||
| **Spark (L2)** | Self-custodial | ❌ | Medium | ❌ | High | Automatic | Easy | Low | Minimal fees | Medium | Runs via Spark sidecar; seed-based self-custody. |
|
||||
| **Cliche Wallet** | Self-custodial | ❌ | Medium | ❌ | Medium | Manual | Moderate | Moderate | Minimal fees | Medium | Lightweight wallet; suitable for embedded systems. |
|
||||
| **Strike** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
||||
| **LNPay** | Custodial | ✅ | Low | ❌ | Low | Provider-managed | Easy | Low | Transaction fees apply | Medium | Third-party service; suitable for quick setups. |
|
||||
@@ -58,9 +57,7 @@ Below is a side-by-side comparison of Lightning funding sources you can use with
|
||||
| **LN.tips** | Custodial/Self-Custodial | Depends on provider | Medium | ❌ | Low | Provider-managed | Moderate | Low | Transaction fees may apply | Medium | Simple hosted service; use LN.tips API as your backend. |
|
||||
| **Fake Wallet** | Testing (simulated) | ❌ | Low | ❌ | N/A | N/A | Easy | Low | None (test only) | N/A | For testing only; mints accounting units in LNbits (no real sats, unit name configurable). |
|
||||
|
||||
## Spark (L2)
|
||||
|
||||
Spark L2 uses a local Node.js sidecar to expose an HTTP API that LNbits can use as a funding source. It is self-custodial and secured by a standard mnemonic seed. Sidecar repo `https://github.com/lnbits/spark_sidecar`.
|
||||
---
|
||||
|
||||
### Notes for readers
|
||||
|
||||
|
||||
+2
-30
@@ -41,8 +41,7 @@ A backend wallet is selected and configured entirely through LNbits environment
|
||||
| [CoreLightning](#corelightning) | [LND (gRPC)](#lnd-grpc) | [Blink](#blink) |
|
||||
| [CoreLightning REST](#corelightning-rest) | [LNbits](#lnbits) | [Alby](#alby) |
|
||||
| [Spark (Core Lightning)](#spark-core-lightning) | [LNPay](#lnpay) | [Boltz](#boltz) |
|
||||
| [Spark L2](#spark-l2) | [ZBD](#zbd) | [Phoenixd](#phoenixd) |
|
||||
| [Cliche Wallet](#cliche-wallet) | | |
|
||||
| [Cliche Wallet](#cliche-wallet) | [ZBD](#zbd) | [Phoenixd](#phoenixd) |
|
||||
| [Breez SDK](#breez-sdk) | [Breez Liquid SDK](#breez-liquid-sdk) | [Nostr Wallet Connect](#nostr-wallet-connect-nwc) |
|
||||
| [Strike](#strike) | [Eclair (ACINQ)](#eclair-acinq) | [LN.tips](#lntips) |
|
||||
| [Fake Wallet](#fake-wallet) | | |
|
||||
@@ -53,7 +52,7 @@ A backend wallet is selected and configured entirely through LNbits environment
|
||||
|
||||
### CLNRest (using [runes](https://docs.corelightning.org/reference/lightning-createrune))
|
||||
|
||||
[Core Lightning REST API docs](https://docs.corelightning.org/docs/rest)
|
||||
[Core Lightning REST API docs](https://docs.corelightning.org/docs/rest)
|
||||
Should also work with the [Rust version of CLNRest](https://github.com/daywalker90/clnrest-rs)
|
||||
|
||||
**Environment variables**
|
||||
@@ -128,33 +127,6 @@ Old REST interface using [RTL c-lightning-REST](https://github.com/Ride-The-Ligh
|
||||
- `SPARK_URL`: `http://10.147.17.230:9737/rpc`
|
||||
- `SPARK_TOKEN`: `secret_access_key`
|
||||
|
||||
## Spark L2
|
||||
|
||||
Self-custodial funding source using the [Spark L2](https://docs.spark.money/start/overview) network. Requires a Node.js [sidecar](https://github.com/lnbits/spark_sidecar) that bridges lnbits talking to Spark. Works in addition with any Spark-compatible seed (Wallet of Satoshi, BuhoGO, BlitzWallet).
|
||||
If the sidecar is started with a `mnemonic` then that mnemonic will be used. Otherwhise if a mnemonic is set for the `Spark L2` LNbits funding source then that mnemonic will be used.
|
||||
|
||||
### Optional tuning
|
||||
|
||||
- `SPARK_L2_PAY_WAIT_MS`: `4000` _(payment timeout in ms)_
|
||||
- `SPARK_L2_PAY_POLL_MS`: `500` _(polling interval in ms)_
|
||||
- `SPARK_L2_STREAM_KEEPALIVE_MS`: `15000` _(SSE keepalive in ms)_
|
||||
|
||||
### Example: run the sidecar
|
||||
|
||||
```bash
|
||||
git clone https://github.com/lnbits/spark_sidecar.git
|
||||
cd spark_sidecar
|
||||
npm install
|
||||
|
||||
SPARK_MNEMONIC="bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom bottom" \
|
||||
SPARK_NETWORK=MAINNET \
|
||||
SPARK_SIDECAR_PORT=8765 \
|
||||
SPARK_PAY_WAIT_MS=20000 \
|
||||
node server.mjs
|
||||
```
|
||||
|
||||
For testing, you can generate a 12-word mnemonic at https://iancoleman.io/bip39/. Store it securely — it controls your funds. Then select Spark (L2) as the funding source in LNbits.
|
||||
|
||||
## LND (REST)
|
||||
|
||||
**Required env vars**
|
||||
|
||||
@@ -318,7 +318,7 @@ class RegisterUser(BaseModel):
|
||||
username: str = Query(default=..., min_length=2, max_length=20)
|
||||
password: str = Query(default=..., min_length=8, max_length=50)
|
||||
password_repeat: str = Query(default=..., min_length=8, max_length=50)
|
||||
invitation_code: str | None = Query(default=None, max_length=256)
|
||||
invitation_code: str | None = Query(default=None, min_length=1, max_length=256)
|
||||
|
||||
|
||||
class CreateUser(BaseModel):
|
||||
|
||||
@@ -118,8 +118,8 @@ async def verify_paypal_webhook(headers, payload: bytes):
|
||||
"""
|
||||
webhook_id = settings.paypal_webhook_id
|
||||
if not webhook_id:
|
||||
logger.warning("PayPal webhook ID not set.")
|
||||
raise ValueError("PayPal webhook cannot be verified. Missing webhook ID.")
|
||||
logger.warning("PayPal webhook ID not set; skipping verification.")
|
||||
return
|
||||
|
||||
required_headers = {
|
||||
"PAYPAL-TRANSMISSION-ID": headers.get("PAYPAL-TRANSMISSION-ID"),
|
||||
@@ -129,8 +129,8 @@ async def verify_paypal_webhook(headers, payload: bytes):
|
||||
"PAYPAL-AUTH-ALGO": headers.get("PAYPAL-AUTH-ALGO"),
|
||||
}
|
||||
if not all(required_headers.values()):
|
||||
logger.warning("Missing PayPal webhook headers.")
|
||||
raise ValueError("PayPal webhook cannot be verified. Missing headers.")
|
||||
logger.warning("Missing PayPal webhook headers; skipping verification.")
|
||||
return
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(base_url=settings.paypal_api_endpoint) as client:
|
||||
|
||||
@@ -511,21 +511,15 @@ async def update_ui_customization(
|
||||
async def first_install(data: UpdateSuperuserPassword) -> JSONResponse:
|
||||
if not settings.first_install:
|
||||
raise HTTPException(HTTPStatus.FORBIDDEN, "This is not your first install")
|
||||
|
||||
if settings.first_install_token:
|
||||
if not data.first_install_token:
|
||||
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Missing first_install_token.")
|
||||
if settings.first_install_token != data.first_install_token:
|
||||
raise HTTPException(HTTPStatus.UNAUTHORIZED, "Invalid first_install_token.")
|
||||
|
||||
account = await get_account_by_username(data.username, False)
|
||||
if account:
|
||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Username already exists.")
|
||||
|
||||
account = await get_account(settings.super_user)
|
||||
if not account:
|
||||
raise HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR, "Superuser not found.")
|
||||
|
||||
account.username = data.username
|
||||
account.extra = account.extra or UserExtra()
|
||||
account.extra.provider = "lnbits"
|
||||
|
||||
@@ -24,7 +24,7 @@ callback_router = APIRouter(prefix="/api/v1/callback", tags=["callback"])
|
||||
async def api_generic_webhook_handler(
|
||||
provider_name: str, request: Request
|
||||
) -> SimpleStatus:
|
||||
logger.info(f"Received callback from provider: '{provider_name}'.")
|
||||
|
||||
if provider_name.lower() == "stripe":
|
||||
payload = await request.body()
|
||||
sig_header = request.headers.get("Stripe-Signature")
|
||||
@@ -181,10 +181,8 @@ async def _get_stripe_subscription_payment_options(
|
||||
|
||||
|
||||
async def handle_paypal_event(event: dict):
|
||||
event_id = event.get("id", "")
|
||||
event_type = event.get("event_type", "")
|
||||
resource = event.get("resource", {})
|
||||
logger.info(f"Handling PayPal event: '{event_id}'. Type: '{event_type}'.")
|
||||
|
||||
if event_type in ("CHECKOUT.ORDER.APPROVED", "PAYMENT.CAPTURE.COMPLETED"):
|
||||
payment_hash = _paypal_extract_payment_hash(resource)
|
||||
@@ -198,17 +196,20 @@ async def handle_paypal_event(event: dict):
|
||||
await check_fiat_status(payment)
|
||||
return
|
||||
|
||||
if event_type in ("PAYMENT.SALE.COMPLETED"):
|
||||
if event_type in (
|
||||
"PAYMENT.SALE.COMPLETED",
|
||||
"BILLING.SUBSCRIPTION.PAYMENT.SUCCEEDED",
|
||||
):
|
||||
await _handle_paypal_subscription_payment(resource)
|
||||
return
|
||||
|
||||
logger.warning(f"Unhandled PayPal event type: '{event_type}'.")
|
||||
logger.info(f"Unhandled PayPal event type: '{event_type}'.")
|
||||
|
||||
|
||||
async def _handle_paypal_subscription_payment(resource: dict):
|
||||
amount_info = resource.get("amount") or {}
|
||||
currency = (amount_info.get("currency") or "").upper()
|
||||
total = amount_info.get("total")
|
||||
currency = (amount_info.get("currency_code") or "").upper()
|
||||
total = amount_info.get("value")
|
||||
if not currency or total is None:
|
||||
raise ValueError("PayPal subscription event missing amount.")
|
||||
|
||||
@@ -216,14 +217,18 @@ async def _handle_paypal_subscription_payment(resource: dict):
|
||||
if not custom_id:
|
||||
raise ValueError("PayPal subscription event missing custom metadata.")
|
||||
|
||||
payment_options = _deserialize_paypal_metadata(custom_id)
|
||||
try:
|
||||
metadata = json.loads(custom_id)
|
||||
except json.JSONDecodeError:
|
||||
metadata = {}
|
||||
|
||||
payment_options = FiatSubscriptionPaymentOptions(**metadata)
|
||||
if not payment_options.wallet_id:
|
||||
raise ValueError("PayPal subscription event missing wallet_id.")
|
||||
|
||||
memo = payment_options.memo or ""
|
||||
extra = {
|
||||
**(payment_options.extra or {}),
|
||||
"subscription_request_id": resource.get("billing_agreement_id"),
|
||||
"fiat_method": "subscription",
|
||||
"tag": payment_options.tag,
|
||||
"subscription": {
|
||||
@@ -254,29 +259,3 @@ def _paypal_extract_payment_hash(resource: dict) -> str | None:
|
||||
if pu.get("custom_id"):
|
||||
return pu.get("custom_id")
|
||||
return None
|
||||
|
||||
|
||||
def _deserialize_paypal_metadata(custom_id: str) -> FiatSubscriptionPaymentOptions:
|
||||
try:
|
||||
meta = json.loads(custom_id)
|
||||
wallet_id = meta[0] if len(meta) > 0 else None
|
||||
tag = meta[1] if len(meta) > 1 else None
|
||||
subscription_request_id = meta[2] if len(meta) > 2 else None
|
||||
extra_link = meta[3] if len(meta) > 3 else None
|
||||
memo = meta[4] if len(meta) > 4 else None
|
||||
|
||||
extra = {
|
||||
"link": extra_link,
|
||||
"subscription_request_id": subscription_request_id,
|
||||
}
|
||||
|
||||
return FiatSubscriptionPaymentOptions(
|
||||
wallet_id=wallet_id,
|
||||
tag=tag,
|
||||
subscription_request_id=subscription_request_id,
|
||||
extra=extra,
|
||||
memo=memo,
|
||||
)
|
||||
except (json.JSONDecodeError, IndexError) as e:
|
||||
logger.warning(f"Failed to deserialize PayPal metadata: {e}")
|
||||
return FiatSubscriptionPaymentOptions()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from http import HTTPStatus
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core.models.misc import SimpleStatus
|
||||
from lnbits.core.models.wallets import WalletTypeInfo
|
||||
@@ -31,11 +30,6 @@ async def create_subscription(
|
||||
data: CreateFiatSubscription,
|
||||
key_type: WalletTypeInfo = Depends(require_admin_key),
|
||||
) -> FiatSubscriptionResponse:
|
||||
logger.debug(
|
||||
f"Creating subscription with provider '{provider}. '"
|
||||
f"Subscription ID '{data.subscription_id}'."
|
||||
)
|
||||
|
||||
fiat_provider = await get_fiat_provider(provider)
|
||||
if not fiat_provider:
|
||||
raise HTTPException(404, "Fiat provider not found")
|
||||
@@ -53,12 +47,6 @@ async def create_subscription(
|
||||
subscription_response = await fiat_provider.create_subscription(
|
||||
data.subscription_id, data.quantity, data.payment_options
|
||||
)
|
||||
if subscription_response.error_message:
|
||||
logger.warning(
|
||||
f"Failed to create subscription with provider '{provider}': "
|
||||
f"{subscription_response.error_message}"
|
||||
)
|
||||
|
||||
return subscription_response
|
||||
|
||||
|
||||
|
||||
+42
-9
@@ -12,6 +12,7 @@ from typing import Any, Generic, Literal, TypeVar, get_origin
|
||||
|
||||
from loguru import logger
|
||||
from pydantic import BaseModel, ValidationError, root_validator
|
||||
from sqlalchemy import event
|
||||
from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, create_async_engine
|
||||
from sqlalchemy.sql import text
|
||||
|
||||
@@ -55,6 +56,14 @@ def compat_timestamp_placeholder(key: str):
|
||||
return f":{key}"
|
||||
|
||||
|
||||
def get_placeholder(model: Any, field: str) -> str:
|
||||
type_ = model.__fields__[field].type_
|
||||
if type_ == datetime:
|
||||
return compat_timestamp_placeholder(field)
|
||||
else:
|
||||
return f":{field}"
|
||||
|
||||
|
||||
class Compat:
|
||||
type: str | None = "<inherited>"
|
||||
schema: str | None = "<inherited>"
|
||||
@@ -317,7 +326,31 @@ class Database(Compat):
|
||||
self.engine: AsyncEngine = create_async_engine(
|
||||
database_uri, echo=settings.debug_database
|
||||
)
|
||||
|
||||
if self.type in {POSTGRES, COCKROACH}:
|
||||
|
||||
@event.listens_for(self.engine.sync_engine, "connect")
|
||||
def register_custom_types(dbapi_connection, *_):
|
||||
def _parse_date(value) -> datetime:
|
||||
if value is None:
|
||||
value = "1970-01-01 00:00:00"
|
||||
f = "%Y-%m-%d %H:%M:%S.%f"
|
||||
if "." not in value:
|
||||
f = "%Y-%m-%d %H:%M:%S"
|
||||
# Parse and add UTC timezone info
|
||||
return datetime.strptime(value, f).replace(tzinfo=timezone.utc)
|
||||
|
||||
dbapi_connection.run_async(
|
||||
lambda connection: connection.set_type_codec(
|
||||
"TIMESTAMP",
|
||||
encoder=datetime,
|
||||
decoder=_parse_date,
|
||||
schema="pg_catalog",
|
||||
)
|
||||
)
|
||||
|
||||
self.lock = asyncio.Lock()
|
||||
|
||||
logger.trace(f"database {self.type} added for {self.name}")
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -630,7 +663,7 @@ def insert_query(table_name: str, model: BaseModel) -> str:
|
||||
placeholders = []
|
||||
keys = model_to_dict(model).keys()
|
||||
for field in keys:
|
||||
placeholders.append(f":{field}")
|
||||
placeholders.append(get_placeholder(model, field))
|
||||
# add quotes to keys to avoid SQL conflicts (e.g. `user` is a reserved keyword)
|
||||
fields = ", ".join([f'"{key}"' for key in keys])
|
||||
values = ", ".join(placeholders)
|
||||
@@ -648,8 +681,9 @@ def update_query(
|
||||
"""
|
||||
fields = []
|
||||
for field in model_to_dict(model).keys():
|
||||
placeholder = get_placeholder(model, field)
|
||||
# add quotes to keys to avoid SQL conflicts (e.g. `user` is a reserved keyword)
|
||||
fields.append(f'"{field}" = :{field}')
|
||||
fields.append(f'"{field}" = {placeholder}')
|
||||
query = ", ".join(fields)
|
||||
return f"UPDATE {table_name} SET {query} {where}" # noqa: S608
|
||||
|
||||
@@ -667,12 +701,7 @@ def model_to_dict(model: BaseModel) -> dict:
|
||||
if model.__fields__[key].field_info.extra.get("no_database", False):
|
||||
continue
|
||||
if isinstance(value, datetime):
|
||||
if DB_TYPE == SQLITE:
|
||||
_dict[key] = value.timestamp()
|
||||
else:
|
||||
# remove tz. postgres and cockroach TIMESTAMP is not tz aware
|
||||
# so it will throw if we dont remove the UTC.
|
||||
_dict[key] = value.replace(tzinfo=None)
|
||||
_dict[key] = value.timestamp()
|
||||
continue
|
||||
if (
|
||||
type(type_) is type(BaseModel)
|
||||
@@ -728,7 +757,11 @@ def dict_to_model(_row: dict, model: type[TModel]) -> TModel: # noqa: C901
|
||||
if DB_TYPE == SQLITE:
|
||||
_dict[key] = datetime.fromtimestamp(value, timezone.utc)
|
||||
else:
|
||||
_dict[key] = value.replace(tzinfo=timezone.utc)
|
||||
# Ensure PostgreSQL datetime values have timezone info
|
||||
if isinstance(value, datetime) and value.tzinfo is None:
|
||||
_dict[key] = value.replace(tzinfo=timezone.utc)
|
||||
else:
|
||||
_dict[key] = value
|
||||
continue
|
||||
if issubclass(type_, BaseModel):
|
||||
_dict[key] = dict_to_submodel(type_, value)
|
||||
|
||||
+18
-63
@@ -2,7 +2,7 @@ import asyncio
|
||||
import json
|
||||
import time
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any, Literal
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
@@ -24,8 +24,6 @@ from .base import (
|
||||
FiatSubscriptionResponse,
|
||||
)
|
||||
|
||||
FiatMethod = Literal["checkout", "subscription"]
|
||||
|
||||
|
||||
class PayPalCheckoutOptions(BaseModel):
|
||||
class Config:
|
||||
@@ -36,21 +34,11 @@ class PayPalCheckoutOptions(BaseModel):
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class PayPalSubscriptionOptions(BaseModel):
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
|
||||
checking_id: str | None = None
|
||||
payment_request: str | None = None
|
||||
|
||||
|
||||
class PayPalCreateInvoiceOptions(BaseModel):
|
||||
class Config:
|
||||
extra = "ignore"
|
||||
|
||||
fiat_method: FiatMethod = "checkout"
|
||||
checkout: PayPalCheckoutOptions | None = None
|
||||
subscription: PayPalSubscriptionOptions | None = None
|
||||
|
||||
|
||||
class PayPalWallet(FiatProvider):
|
||||
@@ -108,14 +96,6 @@ class PayPalWallet(FiatProvider):
|
||||
opts = self._parse_create_opts(extra or {})
|
||||
if opts is None:
|
||||
return FiatInvoiceResponse(ok=False, error_message="Invalid PayPal options")
|
||||
if opts.fiat_method == "subscription":
|
||||
term = opts.subscription or PayPalSubscriptionOptions()
|
||||
checking_id = term.checking_id or urlsafe_short_hash()
|
||||
return FiatInvoiceResponse(
|
||||
ok=True,
|
||||
checking_id=f"subscription_{checking_id}",
|
||||
payment_request=term.payment_request or "",
|
||||
)
|
||||
|
||||
try:
|
||||
await self._ensure_access_token()
|
||||
@@ -191,7 +171,6 @@ class PayPalWallet(FiatProvider):
|
||||
or "https://lnbits.com"
|
||||
)
|
||||
|
||||
logger.debug(f"Creating PayPal subscription with ID '{subscription_id}'")
|
||||
if not payment_options.subscription_request_id:
|
||||
payment_options.subscription_request_id = urlsafe_short_hash()
|
||||
payment_options.extra = payment_options.extra or {}
|
||||
@@ -203,6 +182,7 @@ class PayPalWallet(FiatProvider):
|
||||
await self._ensure_access_token()
|
||||
payload = {
|
||||
"plan_id": subscription_id,
|
||||
"quantity": str(quantity),
|
||||
"custom_id": self._serialize_metadata(payment_options),
|
||||
"application_context": {
|
||||
"return_url": success_url,
|
||||
@@ -225,7 +205,7 @@ class PayPalWallet(FiatProvider):
|
||||
return FiatSubscriptionResponse(
|
||||
ok=True,
|
||||
checkout_session_url=approval_url,
|
||||
subscription_request_id=data.get("id"),
|
||||
subscription_request_id=payment_options.subscription_request_id,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
@@ -239,10 +219,6 @@ class PayPalWallet(FiatProvider):
|
||||
correlation_id: str,
|
||||
**kwargs,
|
||||
) -> FiatSubscriptionResponse:
|
||||
logger.debug(
|
||||
f"Cancelling PayPal subscription '{subscription_id}'. "
|
||||
f"Correlation ID '{correlation_id}'."
|
||||
)
|
||||
try:
|
||||
await self._ensure_access_token()
|
||||
r = await self.client.post(
|
||||
@@ -264,20 +240,12 @@ class PayPalWallet(FiatProvider):
|
||||
async def get_invoice_status(self, checking_id: str) -> FiatPaymentStatus:
|
||||
try:
|
||||
await self._ensure_access_token()
|
||||
paypal_id = self._normalize_paypal_id(checking_id)
|
||||
if paypal_id.startswith("subscription_"):
|
||||
capture_id = paypal_id.replace("subscription_", "", 1)
|
||||
r = await self.client.get(
|
||||
f"v2/payments/captures/{capture_id}", headers=self._auth_headers()
|
||||
)
|
||||
r.raise_for_status()
|
||||
return self._status_from_capture(r.json())
|
||||
else:
|
||||
r = await self.client.get(
|
||||
f"/v2/checkout/orders/{paypal_id}", headers=self._auth_headers()
|
||||
)
|
||||
r.raise_for_status()
|
||||
return self._status_from_order(r.json())
|
||||
order_id = self._normalize_paypal_id(checking_id)
|
||||
r = await self.client.get(
|
||||
f"/v2/checkout/orders/{order_id}", headers=self._auth_headers()
|
||||
)
|
||||
r.raise_for_status()
|
||||
return self._status_from_order(r.json())
|
||||
except Exception as exc:
|
||||
logger.debug(f"Error getting PayPal order status: {exc}")
|
||||
return FiatPaymentPendingStatus()
|
||||
@@ -302,14 +270,6 @@ class PayPalWallet(FiatProvider):
|
||||
return FiatPaymentFailedStatus()
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
def _status_from_capture(self, order: dict[str, Any]) -> FiatPaymentStatus:
|
||||
status = (order.get("status") or "").upper()
|
||||
if status in ["COMPLETED"]:
|
||||
return FiatPaymentSuccessStatus()
|
||||
if status in ["DECLINED", "FAILED", "CANCELLED", "CANCELED"]:
|
||||
return FiatPaymentFailedStatus()
|
||||
return FiatPaymentPendingStatus()
|
||||
|
||||
def _normalize_paypal_id(self, checking_id: str) -> str:
|
||||
return (
|
||||
checking_id.replace("fiat_paypal_", "", 1)
|
||||
@@ -320,20 +280,15 @@ class PayPalWallet(FiatProvider):
|
||||
def _serialize_metadata(
|
||||
self, payment_options: FiatSubscriptionPaymentOptions
|
||||
) -> str:
|
||||
meta = [
|
||||
payment_options.wallet_id,
|
||||
payment_options.tag,
|
||||
payment_options.subscription_request_id,
|
||||
]
|
||||
if payment_options.extra:
|
||||
meta.append(payment_options.extra.get("link", None))
|
||||
|
||||
# Keep custom_id within PayPal's 127-char limit
|
||||
memo_limit = 120 - len(json.dumps(meta))
|
||||
if memo_limit > 0 and payment_options.memo:
|
||||
meta.append(payment_options.memo[:memo_limit])
|
||||
|
||||
return json.dumps(meta)
|
||||
md = {
|
||||
"wallet_id": payment_options.wallet_id,
|
||||
"memo": payment_options.memo,
|
||||
"extra": payment_options.extra,
|
||||
"tag": payment_options.tag,
|
||||
"subscription_request_id": payment_options.subscription_request_id,
|
||||
}
|
||||
raw = json.dumps(md)
|
||||
return raw[:127] # PayPal custom_id limit
|
||||
|
||||
def _parse_create_opts(
|
||||
self, raw_opts: dict[str, Any]
|
||||
|
||||
+1
-27
@@ -292,14 +292,10 @@ class ThemesSettings(LNbitsSettings):
|
||||
lnbits_qr_logo: str = Field(default="/static/images/favicon_qr_logo.png")
|
||||
lnbits_apple_touch_icon: str | None = Field(default=None)
|
||||
lnbits_default_reaction: str = Field(default="confettiBothSides")
|
||||
lnbits_default_theme: str = Field(default="bitcoin")
|
||||
lnbits_default_theme: str = Field(default="salvador")
|
||||
lnbits_default_border: str = Field(default="hard-border")
|
||||
lnbits_default_gradient: bool = Field(default=True)
|
||||
lnbits_default_bgimage: str | None = Field(default=None)
|
||||
lnbits_default_dark: bool = Field(default=True)
|
||||
lnbits_default_card_rounded: bool = Field(default=True)
|
||||
lnbits_default_card_gradient: bool = Field(default=True)
|
||||
lnbits_default_card_shadow: bool = Field(default=False)
|
||||
|
||||
|
||||
class OpsSettings(LNbitsSettings):
|
||||
@@ -606,16 +602,6 @@ class SparkFundingSource(LNbitsSettings):
|
||||
spark_token: str | None = Field(default=None)
|
||||
|
||||
|
||||
class SparkL2FundingSource(LNbitsSettings):
|
||||
spark_l2_network: str = Field(default="MAINNET")
|
||||
spark_l2_external_endpoint: str | None = Field(default="http://localhost:8765")
|
||||
spark_l2_external_api_key: str | None = Field(default=None)
|
||||
spark_l2_mnemonic: str | None = Field(default=None)
|
||||
spark_l2_pay_wait_ms: int = Field(default=4000, ge=0)
|
||||
spark_l2_pay_poll_ms: int = Field(default=500, ge=0)
|
||||
spark_l2_stream_keepalive_ms: int = Field(default=15000, ge=0)
|
||||
|
||||
|
||||
class LnTipsFundingSource(LNbitsSettings):
|
||||
lntips_api_endpoint: str | None = Field(default=None)
|
||||
lntips_api_key: str | None = Field(default=None)
|
||||
@@ -721,7 +707,6 @@ class FundingSourcesSettings(
|
||||
PhoenixdFundingSource,
|
||||
OpenNodeFundingSource,
|
||||
SparkFundingSource,
|
||||
SparkL2FundingSource,
|
||||
LnTipsFundingSource,
|
||||
NWCFundingSource,
|
||||
BreezSdkFundingSource,
|
||||
@@ -1050,7 +1035,6 @@ class SuperUserSettings(LNbitsSettings):
|
||||
"FakeWallet",
|
||||
"LNPayWallet",
|
||||
"LNbitsWallet",
|
||||
"SparkL2Wallet",
|
||||
"LnTipsWallet",
|
||||
"LndRestWallet",
|
||||
"LndWallet",
|
||||
@@ -1202,10 +1186,6 @@ class PublicSettings(BaseModel):
|
||||
default_border: str = Field(alias="defaultBorder")
|
||||
default_bgimage: str | None = Field(alias="defaultBgimage")
|
||||
default_gradient: bool = Field(alias="defaultGradient")
|
||||
default_dark: bool = Field(alias="defaultDark")
|
||||
default_card_rounded: bool = Field(alias="defaultCardRounded")
|
||||
default_card_gradient: bool = Field(alias="defaultCardGradient")
|
||||
default_card_shadow: bool = Field(alias="defaultCardShadow")
|
||||
denomination: str | None = Field()
|
||||
extensions: list[str] = Field()
|
||||
allowed_currencies: list[str] = Field(alias="allowedCurrencies")
|
||||
@@ -1221,7 +1201,6 @@ class PublicSettings(BaseModel):
|
||||
lnbits_user_activation_by_invitation_code: bool = Field(
|
||||
alias="userActivationByInvitationCode"
|
||||
)
|
||||
has_first_install_token: bool = Field(alias="hasFirstInstallToken")
|
||||
|
||||
@classmethod
|
||||
def from_settings(cls, settings: Settings):
|
||||
@@ -1263,10 +1242,6 @@ class PublicSettings(BaseModel):
|
||||
defaultBorder=settings.lnbits_default_border,
|
||||
defaultGradient=settings.lnbits_default_gradient,
|
||||
defaultBgimage=settings.lnbits_default_bgimage,
|
||||
defaultDark=settings.lnbits_default_dark,
|
||||
defaultCardRounded=settings.lnbits_default_card_rounded,
|
||||
defaultCardGradient=settings.lnbits_default_card_gradient,
|
||||
defaultCardShadow=settings.lnbits_default_card_shadow,
|
||||
denomination=settings.lnbits_denomination,
|
||||
extensions=list(settings.lnbits_installed_extensions_ids),
|
||||
allowedCurrencies=settings.lnbits_allowed_currencies,
|
||||
@@ -1280,7 +1255,6 @@ class PublicSettings(BaseModel):
|
||||
userActivationByEmail=settings.lnbits_user_activation_by_email,
|
||||
userActivationByPayment=settings.lnbits_user_activation_by_payment,
|
||||
userActivationByInvitationCode=settings.lnbits_user_activation_by_invitation_code,
|
||||
hasFirstInstallToken=settings.first_install_token is not None,
|
||||
)
|
||||
|
||||
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+11
-13
File diff suppressed because one or more lines are too long
@@ -220,181 +220,6 @@ body.body--dark .q-drawer {
|
||||
backdrop-filter: blur(6px) brightness(0.8);
|
||||
}
|
||||
|
||||
body.rounded-ui .q-card,
|
||||
body.rounded-ui .q-btn {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
body[data-theme=classic].card-gradient .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgba(103, 58, 183, 0.08), rgba(156, 39, 176, 0.06));
|
||||
}
|
||||
body[data-theme=classic].card-gradient .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgba(103, 58, 183, 0.06), rgba(156, 39, 176, 0.04));
|
||||
}
|
||||
|
||||
body[data-theme=classic].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgb(38.6192771084, 42.356626506, 64.7807228916), rgba(103, 58, 183, 0.07));
|
||||
}
|
||||
body[data-theme=classic].card-gradient.body--dark .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgb(36.7144578313, 40.2674698795, 61.5855421687), rgba(103, 58, 183, 0.05));
|
||||
}
|
||||
|
||||
body[data-theme=bitcoin].card-gradient .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgba(234, 97, 29, 0.08), rgba(229, 111, 53, 0.06));
|
||||
}
|
||||
body[data-theme=bitcoin].card-gradient .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgba(234, 97, 29, 0.06), rgba(229, 111, 53, 0.04));
|
||||
}
|
||||
|
||||
body[data-theme=bitcoin].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgb(54.18, 49.364, 71.036), rgba(234, 97, 29, 0.07));
|
||||
}
|
||||
body[data-theme=bitcoin].card-gradient.body--dark .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgb(51.885, 47.273, 68.027), rgba(234, 97, 29, 0.05));
|
||||
}
|
||||
|
||||
body[data-theme=freedom].card-gradient .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgba(226, 33, 86, 0.08), rgba(185, 26, 69, 0.06));
|
||||
}
|
||||
body[data-theme=freedom].card-gradient .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgba(226, 33, 86, 0.06), rgba(185, 26, 69, 0.04));
|
||||
}
|
||||
|
||||
body[data-theme=freedom].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgb(82.2051282051, 55.1948717949, 63.4153846154), rgba(226, 33, 86, 0.07));
|
||||
}
|
||||
body[data-theme=freedom].card-gradient.body--dark .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgb(79.1538461538, 53.1461538462, 61.0615384615), rgba(226, 33, 86, 0.05));
|
||||
}
|
||||
|
||||
body[data-theme=cyber].card-gradient .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgba(124, 179, 66, 0.08), rgba(85, 139, 47, 0.06));
|
||||
}
|
||||
body[data-theme=cyber].card-gradient .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgba(124, 179, 66, 0.06), rgba(85, 139, 47, 0.04));
|
||||
}
|
||||
|
||||
body[data-theme=cyber].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgb(10.2, 10.2, 10.2), rgba(124, 179, 66, 0.07));
|
||||
}
|
||||
body[data-theme=cyber].card-gradient.body--dark .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgb(7.65, 7.65, 7.65), rgba(124, 179, 66, 0.05));
|
||||
}
|
||||
|
||||
body[data-theme=mint].card-gradient .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgba(58, 183, 125, 0.08), rgba(39, 176, 101, 0.06));
|
||||
}
|
||||
body[data-theme=mint].card-gradient .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgba(58, 183, 125, 0.06), rgba(39, 176, 101, 0.04));
|
||||
}
|
||||
|
||||
body[data-theme=mint].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgb(38.6192771084, 64.7807228916, 53.5686746988), rgba(58, 183, 125, 0.07));
|
||||
}
|
||||
body[data-theme=mint].card-gradient.body--dark .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgb(36.7144578313, 61.5855421687, 50.9265060241), rgba(58, 183, 125, 0.05));
|
||||
}
|
||||
|
||||
body[data-theme=autumn].card-gradient .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgba(183, 118, 58, 0.08), rgba(176, 121, 39, 0.06));
|
||||
}
|
||||
body[data-theme=autumn].card-gradient .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgba(183, 118, 58, 0.06), rgba(176, 121, 39, 0.04));
|
||||
}
|
||||
|
||||
body[data-theme=autumn].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgb(64.7807228916, 51.0771084337, 38.6192771084), rgba(183, 118, 58, 0.07));
|
||||
}
|
||||
body[data-theme=autumn].card-gradient.body--dark .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgb(61.5855421687, 48.5578313253, 36.7144578313), rgba(183, 118, 58, 0.05));
|
||||
}
|
||||
|
||||
body[data-theme=flamingo].card-gradient .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgba(255, 0, 255, 0.08), rgba(253, 163, 253, 0.06));
|
||||
}
|
||||
body[data-theme=flamingo].card-gradient .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgba(255, 0, 255, 0.06), rgba(253, 163, 253, 0.04));
|
||||
}
|
||||
|
||||
body[data-theme=flamingo].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgb(66.176, 4.224, 66.176), rgba(255, 0, 255, 0.07));
|
||||
}
|
||||
body[data-theme=flamingo].card-gradient.body--dark .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgb(61.382, 3.918, 61.382), rgba(255, 0, 255, 0.05));
|
||||
}
|
||||
|
||||
body[data-theme=monochrome].card-gradient .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgba(73, 73, 73, 0.08), rgba(107, 107, 107, 0.06));
|
||||
}
|
||||
body[data-theme=monochrome].card-gradient .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgba(73, 73, 73, 0.06), rgba(107, 107, 107, 0.04));
|
||||
}
|
||||
|
||||
body[data-theme=monochrome].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgb(10.2, 10.2, 10.2), rgba(73, 73, 73, 0.07));
|
||||
}
|
||||
body[data-theme=monochrome].card-gradient.body--dark .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgb(7.65, 7.65, 7.65), rgba(73, 73, 73, 0.05));
|
||||
}
|
||||
|
||||
body[data-theme=salvador].card-gradient .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgba(25, 118, 210, 0.08), rgba(38, 166, 154, 0.06));
|
||||
}
|
||||
body[data-theme=salvador].card-gradient .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgba(25, 118, 210, 0.06), rgba(38, 166, 154, 0.04));
|
||||
}
|
||||
|
||||
body[data-theme=salvador].card-gradient.body--dark .q-card:not(.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark) {
|
||||
background-image: linear-gradient(135deg, rgb(43.9888888889, 64.2, 84.4111111111), rgba(25, 118, 210, 0.07));
|
||||
}
|
||||
body[data-theme=salvador].card-gradient.body--dark .q-drawer {
|
||||
background-image: linear-gradient(165deg, rgb(42.2416666667, 61.65, 81.0583333333), rgba(25, 118, 210, 0.05));
|
||||
}
|
||||
|
||||
body.card-shadow .q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark) {
|
||||
filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
|
||||
body.card-shadow.body--dark .q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark) {
|
||||
filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45));
|
||||
}
|
||||
|
||||
:root {
|
||||
--size: 100px;
|
||||
--gap: 25px;
|
||||
|
||||
+85
-97
@@ -6,7 +6,7 @@ window.localisation.en = {
|
||||
funding: 'Funding',
|
||||
users: 'Users',
|
||||
audit: 'Audit',
|
||||
api_watch: 'API Watch',
|
||||
api_watch: 'Api Watch',
|
||||
apps: 'Apps',
|
||||
channels: 'Channels',
|
||||
transactions: 'Transactions',
|
||||
@@ -271,16 +271,12 @@ window.localisation.en = {
|
||||
update: 'Update',
|
||||
update_available: 'Update {version} available!',
|
||||
funding_sources: 'Funding Sources',
|
||||
funding_source: 'Funding Source',
|
||||
requires_server_restart:
|
||||
'Changing these settings requires a server restart to take effect.',
|
||||
funding_source_info: 'Select the active funding wallet',
|
||||
latest_update: 'You are on the latest version {version}.',
|
||||
notifications: 'Notifications',
|
||||
notifications_configure: 'Configure Notifications',
|
||||
notifications_nostr_config: 'Nostr Configuration',
|
||||
notifications_enable_nostr: 'Enable Nostr',
|
||||
notifications_enable_nostr_desc: 'Send notifications over Nostr',
|
||||
notifications_enable_nostr_desc: 'Send notfications over Nostr',
|
||||
notifications_nostr_private_key: 'Nostr Private Key',
|
||||
notifications_nostr_private_key_desc:
|
||||
'Private key (hex or nsec) to sign the messages sent to Nostr',
|
||||
@@ -293,7 +289,7 @@ window.localisation.en = {
|
||||
|
||||
notifications_telegram_config: 'Telegram Configuration',
|
||||
notifications_enable_telegram: 'Enable Telegram',
|
||||
notifications_enable_telegram_desc: 'Send notifications over Telegram',
|
||||
notifications_enable_telegram_desc: 'Send notfications over Telegram',
|
||||
notifications_telegram_access_token: 'Access Token',
|
||||
notifications_telegram_access_token_desc: 'Access token for the bot',
|
||||
notifications_chat_id: 'Telegram Chat ID',
|
||||
@@ -322,15 +318,15 @@ window.localisation.en = {
|
||||
|
||||
notification_settings_update: 'Settings updated',
|
||||
notification_settings_update_desc:
|
||||
'Send a notification when server settings have been updated',
|
||||
'Notify when server settings have been updated',
|
||||
|
||||
notification_server_start_stop: 'Server Start/Stop',
|
||||
notification_server_start_stop_desc:
|
||||
'Send a notification when the server has been started/stopped',
|
||||
'Notify when the server has been started/stopped',
|
||||
|
||||
notification_watchdog_limit: 'Watchdog Limit Notification',
|
||||
notification_watchdog_limit_desc:
|
||||
'Send a notification when the watchdog limit has been reached (does not affect the funding source)',
|
||||
'Notify when the watchdog limit has been reached (does not affect the funding source)',
|
||||
|
||||
notification_server_status: 'Server Status',
|
||||
notification_server_status_desc:
|
||||
@@ -338,31 +334,29 @@ window.localisation.en = {
|
||||
|
||||
notification_incoming_payment: 'Incoming Payments',
|
||||
notification_incoming_payment_desc:
|
||||
'Send a notification when a wallet has received a payment above the specified amount (sats)',
|
||||
'Notify when a wallet has received a payment above the specified amount (sats)',
|
||||
|
||||
notification_outgoing_payment: 'Outgoing Payments',
|
||||
notification_outgoing_payment_desc:
|
||||
'Send a notification when a wallet has sent a payment above the specified amount (sats)',
|
||||
'Notify when a wallet has sent a payment above the specified amount (sats)',
|
||||
|
||||
notification_credit_debit: 'Credit / Debit',
|
||||
notification_credit_debit_desc:
|
||||
'Send a notification when a wallet has been credited/debited by the superuser',
|
||||
'Notify when a wallet has been credited/debited by the superuser',
|
||||
|
||||
notification_balance_delta_changed: 'Balance Delta Changed',
|
||||
notification_balance_delta_changed_desc:
|
||||
'Send a notification when the difference between the node balance and the LNbits balance has changed by more than the specified amount (in sats). Set to 0 to disable. This runs every minute.',
|
||||
'Notify when the difference between the node balance and the LNbits balance has changed by more than the specified amount (in sats). Set to 0 to disable. This runs every minute.',
|
||||
|
||||
watchdog_introduction:
|
||||
"Watchdog is a feature that allows you to automatically switch the LNbits funding source to VoidWallet if your node balance is lower than the LNbits balance by a certain threshold. This can help prevent overspending and keep your node's funds safe.",
|
||||
enable_watchdog: 'Enable Watchdog',
|
||||
enable_watchdog: 'Enable Watchdog Switch',
|
||||
enable_watchdog_desc:
|
||||
'You will need to re-enable this manually after an update.',
|
||||
watchdog_interval: 'Watchdog Check Interval',
|
||||
'If enabled it will change your funding source to VoidWallet automatically if your balance is lower than the LNbits balance. You will need to enable manually after an update.',
|
||||
watchdog_interval: 'Watchdog Interval',
|
||||
watchdog_interval_desc:
|
||||
'How often LNbits should check for a killswitch signal in the watchdog threshold delta value [node_balance - lnbits_balance] (in minutes).',
|
||||
watchdog_delta: 'Watchdog Threshold Delta',
|
||||
'How often the background task should check for a killswitch signal in the watchdog delta [node_balance - lnbits_balance] (in minutes).',
|
||||
watchdog_delta: 'Watchdog Delta',
|
||||
watchdog_delta_desc:
|
||||
"The LNbit's > Node balance delta threshold. If this threshold is exceeded, the funding source is changed to VoidWallet.",
|
||||
'Limit before killswitch changes funding source to VoidWallet [lnbits_balance - node_balance > delta]',
|
||||
status: 'Status',
|
||||
notification_source: 'Notification Source',
|
||||
notification_source_label:
|
||||
@@ -373,12 +367,12 @@ window.localisation.en = {
|
||||
releases: 'Releases',
|
||||
watchdog: 'Watchdog',
|
||||
server_logs: 'Server Logs',
|
||||
ip_blocker: 'IP Blacklist/Whitelist',
|
||||
ip_blocker: 'IP Blocker',
|
||||
security: 'Security',
|
||||
security_tools: 'Security Tools',
|
||||
security_tools: 'Security tools',
|
||||
block_access_hint: 'Block access by IP',
|
||||
allow_access_hint: 'Allow access by IP (will override blocked IPs)',
|
||||
enter_ip: 'Enter an IP address and press enter',
|
||||
enter_ip: 'Enter IP and hit enter',
|
||||
rate_limiter: 'Rate Limiter',
|
||||
callback_url_rules: 'Callback URL Rules',
|
||||
enter_callback_url_rule: 'Enter URL rule as regex and hit enter',
|
||||
@@ -392,16 +386,14 @@ window.localisation.en = {
|
||||
wallet_max_ballance: 'Wallet max balance in sats (0 to disable)',
|
||||
wallet_limit_secs_between_trans:
|
||||
'Min secs between transactions per wallet (0 to disable)',
|
||||
only_incoming_payments_allowed: 'Allow incoming payments only',
|
||||
only_incoming_payments_allowed: 'Only incoming payments allowed',
|
||||
disable_outgoing_payments: 'Disable outgoing payments',
|
||||
number_of_requests: 'Number of requests to allow',
|
||||
number_of_requests_hint:
|
||||
'Number of requests to allow per "time unit" for the rate limiter. Set to 0 to disable.',
|
||||
number_of_requests: 'Number of requests',
|
||||
time_unit: 'Time unit',
|
||||
minute: 'Minute',
|
||||
minute: 'minute',
|
||||
settings: 'Settings',
|
||||
second: 'Second',
|
||||
hour: 'Hour',
|
||||
second: 'second',
|
||||
hour: 'hour',
|
||||
disable_server_log: 'Disable Server Log',
|
||||
enable_server_log: 'Enable Server Log',
|
||||
coming_soon: 'Feature coming soon',
|
||||
@@ -473,12 +465,6 @@ window.localisation.en = {
|
||||
api_token_id: 'Token Id',
|
||||
toggle_gradient: 'Toggle Gradient',
|
||||
gradient_background: 'Gradient Background',
|
||||
rounded_ui: 'Rounded Cards & Buttons',
|
||||
toggle_rounded_ui: 'Toggle rounded corners for cards and buttons',
|
||||
card_gradient: 'Card Gradient',
|
||||
toggle_card_gradient: 'Toggle gradient on cards',
|
||||
card_shadow: 'Card Shadow',
|
||||
toggle_card_shadow: 'Toggle shadow on cards',
|
||||
language: 'Language',
|
||||
assets: 'Assets',
|
||||
max_asset_size_mb: 'Max Asset Size (MB)',
|
||||
@@ -537,34 +523,37 @@ window.localisation.en = {
|
||||
hash: 'Hash: ',
|
||||
welcome_lnbits: 'Welcome to LNbits',
|
||||
setup_su_account: 'Set up the Superuser account below.',
|
||||
first_install_token: 'First Install Token',
|
||||
first_install_token: 'First Install Token (optional)',
|
||||
create_ticker_converter: 'Create Currency Ticker Converter',
|
||||
enable_audit: 'Enable Audit',
|
||||
recommended: 'Recommended',
|
||||
audit_desc: 'Log HTTP requests according to the filters specified below.',
|
||||
audit_record_req: 'Log Request Body',
|
||||
audit_record_warning: 'Warning!',
|
||||
audit_record_req_warning_1: 'Sensitive data (like passwords) will be logged.',
|
||||
audit_record_req_warning_2:
|
||||
'The request body can be large. This can fill up your logs quickly.',
|
||||
audit_record_use: 'Use this with caution!',
|
||||
audit_ip: 'Log IP Address',
|
||||
audit_ip_desc: 'Log the IP address of users making requests to LNbits.',
|
||||
audit_path_params: 'Log Path Parameters',
|
||||
audit_query_params: 'Log Query Parameters',
|
||||
audit_desc: 'Record HTTP requests according with the specified filters',
|
||||
audit_record_req: 'Record Request Body',
|
||||
audit_record_warning: 'Warning: ',
|
||||
audit_record_req_warning_1:
|
||||
'confidential data (like passwords) will be logged.',
|
||||
audit_record_req_warning_2: 'the request body can have large size.',
|
||||
audit_record_use: 'Use it with caution.',
|
||||
audit_ip: 'Record IP Address',
|
||||
audit_ip_desc: 'Record the IP address of the client',
|
||||
audit_path_params: 'Record Path Parameters',
|
||||
audit_query_params: 'Record Query Parameters',
|
||||
audit_http_methods: 'Include HTTP Methods',
|
||||
audit_http_methods_hint:
|
||||
'List of HTTP methods to be logged. No value means all methods will be logged.',
|
||||
audit_http_methods_label: 'HTTP Methods to Log',
|
||||
'List of HTTP methods to be included. Empty lists means all.',
|
||||
audit_http_methods_label: 'HTTP Methods',
|
||||
audit_resp_codes: 'Include HTTP Response Codes',
|
||||
audit_resp_codes_hint:
|
||||
'List of HTTP codes to be included (regex match). Empty lists means all. Eg: 4.*, 5.*',
|
||||
audit_resp_codes_label: 'HTTP Response Codes to Log (regex)',
|
||||
audit_resp_codes_label: 'HTTP Response code (regex)',
|
||||
audit_paths: 'Include Paths',
|
||||
audit_paths_hint:
|
||||
'List of paths to be included (regex match). Empty list means all.',
|
||||
audit_paths_label: 'HTTP Paths to Log (regex)',
|
||||
audit_paths_label: 'HTTP Path (regex)',
|
||||
audit_paths_exclude: 'Exclude Paths',
|
||||
audit_paths_exclude_hint:
|
||||
'List of paths to be excluded (regex match). Empty list means none.',
|
||||
audit_paths_exclude_label: 'HTTP Paths to Exclude from Logging (regex)',
|
||||
audit_paths_exclude_label: 'HTTP Path (regex)',
|
||||
exchange_providers: 'Exchange Providers',
|
||||
admin_extensions: 'Admin Extensions',
|
||||
admin_extensions_label: 'Admin extensions',
|
||||
@@ -584,16 +573,16 @@ window.localisation.en = {
|
||||
misc_disable_extensions_builder_label:
|
||||
'Enable Extensions Builder for non admin users.',
|
||||
misc_hide_api: 'Hide API',
|
||||
misc_hide_api_label: 'Hides wallet API, extensions can choose to honor',
|
||||
misc_hide_api_label: 'Hides wallet api, extensions can choose to honor',
|
||||
wallets_management: 'Wallets Management',
|
||||
funding_source_info: 'Funding Source Information',
|
||||
funding_source: 'Funding source: {wallet_class}',
|
||||
node_balance: 'Node balance: {balance} sats',
|
||||
lnbits_balance: 'LNbits balance: {balance} sats',
|
||||
funding_reserve_percent: 'Funding reserve percentage: {percent} %',
|
||||
funding_source_info: 'Funding Source Info',
|
||||
funding_source: 'Funding Source: {wallet_class}',
|
||||
node_balance: 'Node Balance: {balance} sats',
|
||||
lnbits_balance: 'LNbits Balance: {balance} sats',
|
||||
funding_reserve_percent: 'Reserve Percent: {percent} %',
|
||||
node_management: 'Node Management',
|
||||
node_management_not_supported:
|
||||
'Node management is not supported by the active funding source',
|
||||
'Node Management not supported by active funding source',
|
||||
toggle_node_ui: 'Node UI',
|
||||
toggle_public_node_ui: 'Public Node UI',
|
||||
toggle_transactions_node_ui: 'Transactions Tab (Disable on large CLN nodes)',
|
||||
@@ -609,15 +598,16 @@ window.localisation.en = {
|
||||
payment_wait_time_tooltip:
|
||||
'Controls how long LNbits waits for an outgoing payment attempt to confirm before marking it as pending. Higher values help when paying slow-settling invoices (e.g., HODL invoices, Boltz). The payment will be rechecked later and updated automatically or manually.',
|
||||
server_management: 'Server Management',
|
||||
base_url_label: 'Base URL of the server',
|
||||
base_url: 'Base URL',
|
||||
base_url_label: 'Static/Base url for the server',
|
||||
authentication: 'Authentication',
|
||||
auth_token_expiry_label: 'Token expiry (minutes)',
|
||||
auth_token_expiry_label: 'Token expire minutes',
|
||||
auth_token_expiry_hint: 'Time in minutes until the token expires',
|
||||
auth_authentication_cache_label: 'Cache time (minutes)',
|
||||
auth_authentication_cache_hint:
|
||||
'Time in minutes to cache successful authentication (0 to disable)',
|
||||
auth_allowed_methods_label: 'Allowed authorization methods',
|
||||
auth_allowed_methods_hint: 'Select allowed authorization methods',
|
||||
auth_allowed_methods_hint: 'Select authorization methods',
|
||||
auth_nostr_label: 'Nostr Request URL',
|
||||
auth_nostr_hint: 'Absolute URL that the clients will use to login.',
|
||||
auth_google_ci_label: 'Google Client ID',
|
||||
@@ -637,27 +627,31 @@ window.localisation.en = {
|
||||
auth_keycloak_custom_icon_label: 'Keycloak Custom Icon (URL)',
|
||||
currency_settings: 'Currency Settings',
|
||||
allowed_currencies: 'Allowed Currencies',
|
||||
allowed_currencies_hint:
|
||||
'Set the allowed fiat currencies for the exchange features',
|
||||
default_account_currency: 'Default Accounting Currency',
|
||||
default_account_currency_hint:
|
||||
'The default currency to use for accounting features.',
|
||||
allowed_currencies_hint: 'Limit the number of available fiat currencies',
|
||||
default_account_currency: 'Default Account Currency',
|
||||
default_account_currency_hint: 'Default currency for accounting',
|
||||
min_incoming_payment_amount: 'Min Incoming Payment Amount',
|
||||
min_incoming_payment_amount_desc:
|
||||
'Minimum amount allowed for generating an invoice',
|
||||
max_incoming_payment_amount: 'Maximum Incoming Payment Amount',
|
||||
max_incoming_payment_amount: 'Max Incoming Payment Amount',
|
||||
max_incoming_payment_amount_desc:
|
||||
'Maximum amount allowed when generating an invoice',
|
||||
max_outgoing_payment_amount: 'Maximum Outgoing Payment Amount',
|
||||
'Maximum amount allowed for generating an invoice',
|
||||
max_outgoing_payment_amount: 'Max Outgoing Payment Amount',
|
||||
max_outgoing_payment_amount_desc:
|
||||
'Maximum amount allowed when making a payment',
|
||||
service_fees: 'Service Fees',
|
||||
'Maximum amount allowed for making a payment',
|
||||
service_fee: 'Service Fee',
|
||||
service_fee_label: 'Service Fee Charged Per Transaction',
|
||||
service_fee_max_label: 'Maximum Service Fee Limit',
|
||||
fee_wallet_label: 'Service Fee Wallet ID',
|
||||
fee_wallet_hint: 'The ID of the wallet to which to send service funds',
|
||||
disable_fee: 'Disable Service Fees for Internal Payments',
|
||||
service_fee_label: 'Service fee (%)',
|
||||
service_fee_hint: 'Fee charged per tx (%)',
|
||||
service_fee_max: 'Service Fee Max',
|
||||
service_fee_max_label: 'Service fee max (sats)',
|
||||
service_fee_max_hint: 'Max service fee to charge in (sats)',
|
||||
fee_wallet: 'Fee Wallet',
|
||||
fee_wallet_label: 'Fee wallet (wallet ID)',
|
||||
fee_wallet_hint: 'Wallet ID to send funds to',
|
||||
disable_fee: 'Disable Fee',
|
||||
disable_fee_internal: 'Disable Service Fee for Internal Payments',
|
||||
disable_fee_internal_desc:
|
||||
'Disable Service Fee for Internal Lightning Payments',
|
||||
ui_management: 'UI Management',
|
||||
ui_site_title: 'Site Title',
|
||||
ui_changing_remove_lnbits_elements:
|
||||
@@ -670,7 +664,6 @@ window.localisation.en = {
|
||||
ui_site_description_hint: 'Use plain text, Markdown, or raw HTML',
|
||||
ui_default_wallet_name: 'Default Wallet Name',
|
||||
ui_default_theme: 'Default Theme',
|
||||
wallet_featured_button_title: 'Wallet - Featured Button',
|
||||
wallet_featured_button_label: 'Wallet Featured Button Label',
|
||||
wallet_featured_button_label_hint:
|
||||
'Show featured button on the wallet homepage',
|
||||
@@ -679,38 +672,33 @@ window.localisation.en = {
|
||||
'On click the button will open this URL. Leave empty to hide the button.',
|
||||
wallet_featured_button_icon: 'Featured Button Icon',
|
||||
wallet_featured_button_icon_hint:
|
||||
'Icon shown on the featured button (Quasar icon name e.g. "bolt")',
|
||||
'Icon shown on the featured button (check quasar icons)',
|
||||
lnbits_wallet: 'LNbits wallet',
|
||||
denomination: 'Denomination',
|
||||
denomination_hint: 'The name for the FakeWallet token',
|
||||
denomination_error: 'Denomination must be 3 characters, or `sats`',
|
||||
ui_qr_code_logo: 'QR Code/Favicon Logo',
|
||||
ui_qr_code_logo_hint: 'QR code icon and favicon logo URL',
|
||||
ui_qr_code_logo_hint: 'QR code and favicon logo url',
|
||||
ui_apple_touch_icon: 'Apple Touch Icon',
|
||||
ui_apple_touch_icon_hint: 'Apple touch icon URL',
|
||||
ui_apple_touch_icon_hint: 'Apple touch icon url',
|
||||
ui_custom_image: 'Custom Image',
|
||||
ui_custom_image_label: 'URL to custom image',
|
||||
ui_custom_image_hint:
|
||||
'This image is shown on the LNbits homepage and login screen.',
|
||||
ui_custom_badge_title: 'Custom Badge Settings',
|
||||
ui_custom_badge_desc: 'Show a custom badge in the header of LNbits',
|
||||
ui_custom_badge: 'Custom Badge Text',
|
||||
ui_custom_image_hint: 'Image showed at homepage/login',
|
||||
ui_custom_badge: 'Custom Badge',
|
||||
ui_custom_badge_label: "Custom Badge 'USE WITH CAUTION'",
|
||||
ui_custom_badge_color_label: 'Custom Badge Color',
|
||||
themes: 'Themes',
|
||||
themes_hint: 'Choose themes available for users',
|
||||
custom_logo: 'Custom Logo',
|
||||
custom_logo_hint: 'URL to logo image',
|
||||
ad_space_section_title: 'Advertisement Space',
|
||||
ad_space_section_desc:
|
||||
'Configure the advertisement space on the wallet sidebar.',
|
||||
ad_space_title: 'Advertisement Space Title',
|
||||
ad_space_title_hint: 'Title shown above the advertisement space',
|
||||
ad_space_title: 'Ad Space Title',
|
||||
ad_space_title_label: 'Supported by',
|
||||
ad_slots: 'Advertisement Slots',
|
||||
ad_slots_hint:
|
||||
'Advertisement image filepaths in CSV format, extensions can choose to honor. Format: url;img_light_url;img_dark_url, url..',
|
||||
ads_enabled: 'Enable Advertisement',
|
||||
ads_disabled: 'Disabled Advertisement',
|
||||
'Ad url and image filepaths in CSV format, extensions can choose to honor',
|
||||
ad_slots_label: 'url;img_light_url;img_dark_url, url...',
|
||||
ads_enabled: 'Ads Enabled',
|
||||
ads_disabled: 'Ads Disabled',
|
||||
user_management: 'User Management',
|
||||
admin_users: 'Admin Users',
|
||||
admin_users_hint: 'Users with admin privileges',
|
||||
|
||||
@@ -228,48 +228,6 @@ window.app.component('lnbits-admin-funding-sources', {
|
||||
spark_token: 'Token'
|
||||
}
|
||||
],
|
||||
[
|
||||
'SparkL2Wallet',
|
||||
'Spark (L2)',
|
||||
{
|
||||
spark_l2_external_endpoint: {
|
||||
label: 'External Sidecar Endpoint',
|
||||
hint: 'Make sure to also specify the API key if your sidecar requires authentication.',
|
||||
value: ''
|
||||
},
|
||||
spark_l2_mnemonic: {
|
||||
label: 'External Sidecar Mnemonic',
|
||||
hint: 'Mnemonic for the Spark wallet on the external sidecar. Required if the side car does not have its own mnemonic.',
|
||||
value: ''
|
||||
},
|
||||
spark_l2_external_api_key: {
|
||||
label: 'External Sidecar API Key',
|
||||
hint: 'API key for authenticating with the external sidecar if it requires authentication.',
|
||||
value: ''
|
||||
},
|
||||
spark_l2_network: {
|
||||
label: 'Network',
|
||||
value: 'MAINNET',
|
||||
hint: 'The network to use for the Spark wallet.',
|
||||
advanced: true
|
||||
},
|
||||
spark_l2_pay_wait_ms: {
|
||||
label: 'Payment Wait Time (ms)',
|
||||
hint: 'The time to wait for a payment to be processed before considering it failed.',
|
||||
advanced: true
|
||||
},
|
||||
spark_l2_pay_poll_ms: {
|
||||
label: 'Payment Poll Time (ms)',
|
||||
hint: 'The time to wait between polling for payment status updates.',
|
||||
advanced: true
|
||||
},
|
||||
spark_l2_stream_keepalive_ms: {
|
||||
label: 'Stream Keepalive Time (ms)',
|
||||
hint: 'The time to wait between sending keepalive messages to the Spark sidecar to keep the connection open.',
|
||||
advanced: true
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'NWCWallet',
|
||||
'Nostr Wallet Connect',
|
||||
|
||||
@@ -17,13 +17,6 @@ window.app.component('lnbits-manage-wallet-list', {
|
||||
} else {
|
||||
this.activeWalletId = null
|
||||
}
|
||||
},
|
||||
'g.user.wallets': {
|
||||
handler() {
|
||||
this.paymentEvents()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
@@ -45,30 +45,6 @@ window.app.component('lnbits-theme', {
|
||||
document.body.classList.remove('gradient-bg')
|
||||
}
|
||||
},
|
||||
'g.cardRoundedChoice'(val) {
|
||||
this.$q.localStorage.set('lnbits.cardRounded', val)
|
||||
if (val === true) {
|
||||
document.body.classList.add('rounded-ui')
|
||||
} else {
|
||||
document.body.classList.remove('rounded-ui')
|
||||
}
|
||||
},
|
||||
'g.cardGradientChoice'(val) {
|
||||
this.$q.localStorage.set('lnbits.cardGradient', val)
|
||||
if (val === true) {
|
||||
document.body.classList.add('card-gradient')
|
||||
} else {
|
||||
document.body.classList.remove('card-gradient')
|
||||
}
|
||||
},
|
||||
'g.cardShadowChoice'(val) {
|
||||
this.$q.localStorage.set('lnbits.cardShadow', val)
|
||||
if (val === true) {
|
||||
document.body.classList.add('card-shadow')
|
||||
} else {
|
||||
document.body.classList.remove('card-shadow')
|
||||
}
|
||||
},
|
||||
'g.mobileSimple'(val) {
|
||||
this.$q.localStorage.set('lnbits.mobileSimple', val)
|
||||
if (val === true) {
|
||||
@@ -141,15 +117,6 @@ window.app.component('lnbits-theme', {
|
||||
if (this.g.gradientChoice === true) {
|
||||
document.body.classList.add('gradient-bg')
|
||||
}
|
||||
if (this.g.cardRoundedChoice === true) {
|
||||
document.body.classList.add('rounded-ui')
|
||||
}
|
||||
if (this.g.cardGradientChoice === true) {
|
||||
document.body.classList.add('card-gradient')
|
||||
}
|
||||
if (this.g.cardShadowChoice === true) {
|
||||
document.body.classList.add('card-shadow')
|
||||
}
|
||||
if (this.g.bgimageChoice !== '') {
|
||||
document.body.classList.add('bg-image')
|
||||
document.body.style.setProperty(
|
||||
|
||||
@@ -71,10 +71,6 @@ window.app.component('lnbits-wallet-extra', {
|
||||
'lnbits.exchangeRate.' + this.g.wallet.currency,
|
||||
this.g.exchangeRate
|
||||
)
|
||||
if (this.g.exchangeRate <= 0) {
|
||||
this.g.fiatTracking = false
|
||||
this.g.isFiatPriority = false
|
||||
}
|
||||
})
|
||||
.catch(e => console.error(e))
|
||||
}
|
||||
|
||||
@@ -20,15 +20,6 @@ window.g = Vue.reactive({
|
||||
themeChoice: localStore('lnbits.theme', SETTINGS.defaultTheme),
|
||||
borderChoice: localStore('lnbits.border', SETTINGS.defaultBorder),
|
||||
gradientChoice: localStore('lnbits.gradientBg', SETTINGS.defaultGradient),
|
||||
cardRoundedChoice: localStore(
|
||||
'lnbits.cardRounded',
|
||||
SETTINGS.defaultCardRounded
|
||||
),
|
||||
cardGradientChoice: localStore(
|
||||
'lnbits.cardGradient',
|
||||
SETTINGS.defaultCardGradient
|
||||
),
|
||||
cardShadowChoice: localStore('lnbits.cardShadow', SETTINGS.defaultCardShadow),
|
||||
reactionChoice: localStore('lnbits.reactions', SETTINGS.defaultReaction),
|
||||
bgimageChoice: localStore(
|
||||
'lnbits.backgroundImage',
|
||||
@@ -40,7 +31,7 @@ window.g = Vue.reactive({
|
||||
mobileSimple: localStore('lnbits.mobileSimple', true),
|
||||
walletFlip: localStore('lnbits.walletFlip', false),
|
||||
lastActiveWallet: localStore('lnbits.lastActiveWallet', null),
|
||||
darkChoice: localStore('lnbits.darkMode', SETTINGS.defaultDark),
|
||||
darkChoice: localStore('lnbits.darkMode', true),
|
||||
// cookie vars
|
||||
isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'),
|
||||
isUserImpersonated: !!Quasar.Cookies.get('is_lnbits_user_impersonated'),
|
||||
|
||||
@@ -40,7 +40,13 @@ window.PageAccount = {
|
||||
}
|
||||
],
|
||||
defaultSiteCustomisation: {
|
||||
locale: 'en'
|
||||
locale: 'en',
|
||||
themeChoice: 'salvador',
|
||||
bgimageChoice: '',
|
||||
gradientChoice: true,
|
||||
darkChoice: true,
|
||||
borderChoice: 'hard-border',
|
||||
reactionChoice: 'confettiBothSides'
|
||||
},
|
||||
reactionOptions: [
|
||||
'None',
|
||||
@@ -692,20 +698,6 @@ window.PageAccount = {
|
||||
} catch (e) {
|
||||
LNbits.utils.notifyApiError(e)
|
||||
}
|
||||
},
|
||||
resetThemeDefaults() {
|
||||
const defaults = {
|
||||
themeChoice: this.g.settings.defaultTheme,
|
||||
borderChoice: this.g.settings.defaultBorder,
|
||||
gradientChoice: this.g.settings.defaultGradient,
|
||||
bgimageChoice: this.g.settings.defaultBgimage || '',
|
||||
reactionChoice: this.g.settings.defaultReaction,
|
||||
darkChoice: this.g.settings.defaultDark,
|
||||
cardRoundedChoice: this.g.settings.defaultCardRounded,
|
||||
cardGradientChoice: this.g.settings.defaultCardGradient,
|
||||
cardShadowChoice: this.g.settings.defaultCardShadow
|
||||
}
|
||||
this.siteCustomisationChanged(defaults)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -131,22 +131,6 @@ window._lnbitsUtils = {
|
||||
currency: currency || 'sat'
|
||||
}).format(value)
|
||||
},
|
||||
getCurrencySymbol(currency) {
|
||||
const code = (currency || '').toUpperCase()
|
||||
if (code === 'BTC' || code === 'XBT' || code === 'SAT' || code === 'SATS') {
|
||||
return '₿'
|
||||
}
|
||||
try {
|
||||
const parts = new Intl.NumberFormat(window.i18n.global.locale, {
|
||||
style: 'currency',
|
||||
currency: code
|
||||
}).formatToParts(0)
|
||||
const symbolPart = parts.find(part => part.type === 'currency')
|
||||
return symbolPart?.value || code || '¤'
|
||||
} catch (e) {
|
||||
return code || '¤'
|
||||
}
|
||||
},
|
||||
formatSat(value) {
|
||||
return new Intl.NumberFormat(window.i18n.global.locale).format(value)
|
||||
},
|
||||
@@ -160,7 +144,7 @@ window._lnbitsUtils = {
|
||||
return null
|
||||
}
|
||||
},
|
||||
async notifyApiError(error) {
|
||||
notifyApiError(error) {
|
||||
if (!error.response) {
|
||||
return console.error(error)
|
||||
}
|
||||
@@ -170,10 +154,6 @@ window._lnbitsUtils = {
|
||||
500: 'negative'
|
||||
}
|
||||
let messages = error.response.data.detail
|
||||
if (!messages) {
|
||||
const text = await error.response.data?.text()
|
||||
messages = this.parseJSONSafe(text)?.detail
|
||||
}
|
||||
if (messages) {
|
||||
messages = Array.isArray(messages)
|
||||
? messages.map(e => e.msg + ` (${e.loc?.join('/')})`)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
@use 'themes';
|
||||
@use 'borders';
|
||||
@use 'background';
|
||||
@use 'cards';
|
||||
@use 'home';
|
||||
|
||||
[v-cloak] {
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
@use 'sass:color';
|
||||
@use 'sass:map';
|
||||
@use 'themes';
|
||||
|
||||
$card-radius: 10px;
|
||||
|
||||
body.rounded-ui {
|
||||
.q-card,
|
||||
.q-btn {
|
||||
border-radius: $card-radius;
|
||||
}
|
||||
}
|
||||
|
||||
@each $theme, $colors in themes.$themes {
|
||||
$primary: map.get($colors, primary);
|
||||
$secondary: map.get($colors, secondary);
|
||||
$dark: map.get($colors, dark);
|
||||
|
||||
body[data-theme='#{$theme}'].card-gradient {
|
||||
.q-card:not(
|
||||
.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark
|
||||
) {
|
||||
background-image: linear-gradient(
|
||||
135deg,
|
||||
color.adjust($primary, $alpha: -0.92),
|
||||
color.adjust($secondary, $alpha: -0.94)
|
||||
);
|
||||
}
|
||||
.q-drawer {
|
||||
background-image: linear-gradient(
|
||||
165deg,
|
||||
color.adjust($primary, $alpha: -0.94),
|
||||
color.adjust($secondary, $alpha: -0.96)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
body[data-theme='#{$theme}'].card-gradient.body--dark {
|
||||
.q-card:not(
|
||||
.q-dialog .q-card,
|
||||
.lnbits__dialog-card,
|
||||
.q-dialog-plugin--dark
|
||||
) {
|
||||
background-image: linear-gradient(
|
||||
135deg,
|
||||
color.adjust($dark, $lightness: 4%),
|
||||
color.adjust($primary, $alpha: -0.93)
|
||||
);
|
||||
}
|
||||
.q-drawer {
|
||||
background-image: linear-gradient(
|
||||
165deg,
|
||||
color.adjust($dark, $lightness: 3%),
|
||||
color.adjust($primary, $alpha: -0.95)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body.card-shadow {
|
||||
.q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark) {
|
||||
filter: drop-shadow(0 10px 24px rgba(0, 0, 0, 0.18));
|
||||
}
|
||||
}
|
||||
|
||||
body.card-shadow.body--dark {
|
||||
.q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark) {
|
||||
filter: drop-shadow(0 12px 28px rgba(0, 0, 0, 0.45));
|
||||
}
|
||||
}
|
||||
Vendored
+904
-786
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,9 @@
|
||||
<h6 class="q-my-none q-mb-sm">Assets</h6>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-3">
|
||||
<p>
|
||||
<span v-text="$t('max_asset_size_mb')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
@@ -15,6 +18,9 @@
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-9">
|
||||
<p>
|
||||
<span v-text="$t('assets_allowed_mime_types')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
v-model.number="newAllowedAssetMimeType"
|
||||
@@ -50,8 +56,11 @@
|
||||
</div>
|
||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||
<h6 class="q-my-none q-mb-sm">Thumbnails</h6>
|
||||
<div class="row q-col-gutter-md q-my-lg">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-3">
|
||||
<p>
|
||||
<span v-text="$t('thumbnail_width')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
@@ -62,7 +71,10 @@
|
||||
:hint="$t('thumbnail_width_desc')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-12 col-md-3">
|
||||
<p>
|
||||
<span v-text="$t('thumbnail_height')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
@@ -73,7 +85,10 @@
|
||||
:hint="$t('thumbnail_height_desc')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-12 col-md-3">
|
||||
<p>
|
||||
<span v-text="$t('thumbnail_format')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
v-model.number="formData.lnbits_asset_thumbnail_format"
|
||||
@@ -86,6 +101,9 @@
|
||||
<h6 class="q-my-none q-mb-sm">Users</h6>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-3">
|
||||
<p>
|
||||
<span v-text="$t('max_assets_per_user')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
@@ -97,6 +115,9 @@
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-9">
|
||||
<p>
|
||||
<span v-text="$t('assets_no_limit_users')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
v-model="newNoLimitUser"
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</div>
|
||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||
<div class="col-md-3 col-sm-12 q-pr-sm">
|
||||
<q-item tag="label" v-ripple>
|
||||
<q-item-section avatar>
|
||||
<q-toggle
|
||||
@@ -62,7 +62,7 @@
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||
<div class="col-md-3 col-sm-12 q-pr-sm">
|
||||
<q-item tag="label" v-ripple>
|
||||
<q-item-section avatar>
|
||||
<q-toggle
|
||||
@@ -83,7 +83,7 @@
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||
<div class="col-md-3 col-sm-12 q-pr-sm">
|
||||
<q-item tag="label" v-ripple>
|
||||
<q-item-section avatar>
|
||||
<q-toggle
|
||||
@@ -104,7 +104,7 @@
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||
<div class="col-md-3 col-sm-12 q-pr-sm">
|
||||
<q-item tag="label" v-ripple>
|
||||
<q-item-section avatar>
|
||||
<q-toggle
|
||||
@@ -129,6 +129,7 @@
|
||||
<q-separator class="q-mb-xl q-mt-sm"></q-separator>
|
||||
<div class="row q-mb-lg">
|
||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||
<p><span v-text="$t('audit_http_methods')"></span></p>
|
||||
<q-select
|
||||
filled
|
||||
v-model="formData.lnbits_audit_http_methods"
|
||||
@@ -147,6 +148,7 @@
|
||||
></q-select>
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||
<p><span v-text="$t('audit_resp_codes')"></span></p>
|
||||
<q-input
|
||||
class="q-mb-md"
|
||||
filled
|
||||
@@ -175,6 +177,7 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-sm-12 q-pr-sm">
|
||||
<p><span v-text="$t('audit_paths')"></span></p>
|
||||
<q-input
|
||||
class="q-mb-md"
|
||||
filled
|
||||
@@ -201,6 +204,9 @@
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-md-6 col-sm-12">
|
||||
<p>
|
||||
<span v-text="$t('audit_paths_exclude')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
class="q-mb-md"
|
||||
filled
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<q-input
|
||||
class="q-mb-md"
|
||||
filled
|
||||
v-model="formData.lnbits_exchange_history_refresh_interval_seconds"
|
||||
type="number"
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
:hint="$t('extension_builder_manifest_url_hint')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 q-pb-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<p>
|
||||
<span v-text="$t('reviews_url')"></span>
|
||||
</p>
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
</ul>
|
||||
</q-card-section>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item :label="$t('service_fees')">
|
||||
<q-expansion-item :label="$t('service_fee')">
|
||||
<q-card-section>
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-sm-12">
|
||||
@@ -140,6 +140,7 @@
|
||||
v-model="formData.stripe_limits.service_fee_percent"
|
||||
@update:model-value="formData.touch = null"
|
||||
:label="$t('service_fee_label')"
|
||||
:hint="$t('service_fee_hint')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
@@ -150,7 +151,8 @@
|
||||
min="0"
|
||||
v-model="formData.stripe_limits.service_max_fee_sats"
|
||||
@update:model-value="formData.touch = null"
|
||||
:label="$t('service_fee_max_label')"
|
||||
:label="$t('service_fee_max')"
|
||||
:hint="$t('service_fee_max_hint')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-md-4 col-sm-12">
|
||||
@@ -406,6 +408,7 @@
|
||||
<li><code>CHECKOUT.ORDER.APPROVED</code></li>
|
||||
<li><code>PAYMENT.CAPTURE.COMPLETED</code></li>
|
||||
<li><code>PAYMENT.SALE.COMPLETED</code></li>
|
||||
<li><code>BILLING.SUBSCRIPTION.PAYMENT.SUCCEEDED</code></li>
|
||||
</ul>
|
||||
</q-card-section>
|
||||
</q-expansion-item>
|
||||
|
||||
@@ -165,11 +165,6 @@
|
||||
<h6 class="q-mt-lg q-mb-sm">
|
||||
<p v-text="$t('watchdog')"></p>
|
||||
</h6>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12">
|
||||
<p v-text="$t('watchdog_introduction')"></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<q-item tag="label" v-ripple>
|
||||
@@ -244,7 +239,6 @@
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.lnbits_watchdog_delta"
|
||||
:suffix="$t('sats')"
|
||||
type="number"
|
||||
/>
|
||||
</q-item-section>
|
||||
|
||||
@@ -12,14 +12,11 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<p>
|
||||
<span v-text="$t('funding_source')"></span>
|
||||
<small><span v-text="$t('requires_server_restart')"></span></small>
|
||||
</p>
|
||||
<p>Active Funding<small> (Requires server restart)</small></p>
|
||||
<q-select
|
||||
filled
|
||||
v-model="formData.lnbits_backend_wallet_class"
|
||||
:hint="$t('funding_source_info')"
|
||||
hint="Select the active funding wallet"
|
||||
:options="sortedAllowedFundingSources"
|
||||
:option-label="item => getFundingSourceLabel(item)"
|
||||
></q-select>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template id="lnbits-admin-security">
|
||||
<q-card-section class="q-pa-none">
|
||||
<h6 class="q-my-none q-mb-sm">
|
||||
<h6 class="q-my-none">
|
||||
<span v-text="$t('server_management')"></span>
|
||||
</h6>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('base_url')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
v-model.number="formData.lnbits_baseurl"
|
||||
@@ -16,7 +17,7 @@
|
||||
<h6 class="q-my-none q-mb-sm">
|
||||
<span v-text="$t('authentication')"></span>
|
||||
</h6>
|
||||
<div class="row q-col-gutter-sm q-mb-md">
|
||||
<div class="row q-col-gutter-sm">
|
||||
<div class="col-12 col-md-6">
|
||||
<q-input
|
||||
filled
|
||||
@@ -304,19 +305,12 @@
|
||||
type="number"
|
||||
v-model.number="formData.lnbits_rate_limit_no"
|
||||
:label="$t('number_of_requests')"
|
||||
:hint="$t('number_of_requests_hint')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<q-select
|
||||
filled
|
||||
:options="[
|
||||
{label: $t('second'), value: 'second'},
|
||||
{label: $t('minute'), value: 'minute'},
|
||||
{label: $t('hour'), value: 'hour'}
|
||||
]"
|
||||
emit-value
|
||||
map-options
|
||||
:options="[$t('second'), $t('minute'), $t('hour')]"
|
||||
v-model="formData.lnbits_rate_limit_unit"
|
||||
:label="$t('time_unit')"
|
||||
></q-select>
|
||||
|
||||
@@ -38,11 +38,14 @@
|
||||
</div>
|
||||
|
||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||
<h6 class="q-my-none q-mb-sm">
|
||||
<h6 class="q-my-none">
|
||||
<span v-text="$t('payments')"></span>
|
||||
</h6>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-12 col-md-3">
|
||||
<p>
|
||||
<span v-text="$t('max_outgoing_payment_amount')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
@@ -54,7 +57,10 @@
|
||||
></q-input>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-12 col-md-3">
|
||||
<p>
|
||||
<span v-text="$t('max_incoming_payment_amount')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
@@ -69,7 +75,7 @@
|
||||
</div>
|
||||
|
||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||
<h6 class="q-my-none q-mb-sm">
|
||||
<h6 class="q-my-none">
|
||||
<span v-text="$t('wallet_limiter')"></span>
|
||||
</h6>
|
||||
<div class="row q-col-gutter-md">
|
||||
@@ -107,32 +113,35 @@
|
||||
</div>
|
||||
|
||||
<q-separator class="q-mb-lg q-mt-sm"></q-separator>
|
||||
<h6 class="q-my-none q-mb-sm">
|
||||
<span v-text="$t('service_fees')"></span>
|
||||
<h6 class="q-my-none">
|
||||
<span v-text="$t('service_fee')"></span>
|
||||
</h6>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('service_fee')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
v-model.number="formData.lnbits_service_fee"
|
||||
:label="$t('service_fee_label')"
|
||||
step="0.1"
|
||||
:suffix="'%'"
|
||||
:hint="$t('service_fee_hint')"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('service_fee_max')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="number"
|
||||
v-model.number="formData.lnbits_service_fee_max"
|
||||
:label="$t('service_fee_max_label')"
|
||||
:suffix="$t('sats')"
|
||||
:hint="$t('service_fee_max_hint')"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('fee_wallet')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
v-model="formData.lnbits_service_fee_wallet"
|
||||
@@ -142,9 +151,16 @@
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p>
|
||||
<span v-text="$t('disable_fee_internal')"></span>
|
||||
</p>
|
||||
<q-item tag="label" v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label v-text="$t('disable_fee')"></q-item-label>
|
||||
<q-item-label
|
||||
caption
|
||||
v-text="$t('disable_fee_internal_desc')"
|
||||
></q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section avatar>
|
||||
<q-toggle
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
<br />
|
||||
<div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-md-5">
|
||||
<p><span v-text="$t('ui_site_title')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
@@ -17,7 +18,8 @@
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-md-5">
|
||||
<p><span v-text="$t('ui_site_tagline')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
@@ -26,9 +28,7 @@
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-12">
|
||||
<div class="col-12 col-md-2 q-mt-xl">
|
||||
<q-toggle
|
||||
:tip="$t('ui_toggle_elements_tip')"
|
||||
v-model="formData.lnbits_show_home_page_elements"
|
||||
@@ -40,66 +40,78 @@
|
||||
></q-toggle>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
<span v-text="$t('ui_site_description')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
v-model="formData.lnbits_site_description"
|
||||
filled
|
||||
type="textarea"
|
||||
:hint="$t('ui_site_description_hint')"
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12">
|
||||
<q-input
|
||||
v-model="formData.lnbits_site_description"
|
||||
filled
|
||||
type="textarea"
|
||||
:label="$t('ui_site_description')"
|
||||
:hint="$t('ui_site_description_hint')"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<p>
|
||||
<span v-text="$t('ui_default_wallet_name')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_default_wallet_name"
|
||||
:label="$t('ui_default_wallet_name')"
|
||||
:label="$t('lnbits_wallet')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<p><span v-text="$t('ui_qr_code_logo')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_qr_logo"
|
||||
:label="$t('ui_qr_code_logo')"
|
||||
label="https://example.com/image.svg"
|
||||
:hint="$t('ui_qr_code_logo_hint')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<p><span v-text="$t('ui_apple_touch_icon')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_apple_touch_icon"
|
||||
:label="$t('ui_apple_touch_icon')"
|
||||
label="https://example.com/image.png"
|
||||
:hint="$t('ui_apple_touch_icon_hint')"
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-12">
|
||||
<p><span v-text="$t('wallet_featured_button_title')"></span></p>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<p>
|
||||
<span v-text="$t('wallet_featured_button_label')"></span>
|
||||
</p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_wallet_featured_button_label"
|
||||
:label="$t('wallet_featured_button_label')"
|
||||
label="Loop to Onchain"
|
||||
:hint="$t('wallet_featured_button_label_hint')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<p><span v-text="$t('wallet_featured_button_url')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_wallet_featured_button_url"
|
||||
:label="$t('wallet_featured_button_url')"
|
||||
label="/boltz"
|
||||
:hint="$t('wallet_featured_button_url_hint')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-4">
|
||||
<p><span v-text="$t('wallet_featured_button_icon')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
@@ -109,27 +121,21 @@
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-col-gutter-md q-mt-md">
|
||||
<div class="col-12 col-md-12">
|
||||
<h6 class="q-my-sm">
|
||||
<span v-text="$t('ui_custom_badge_title')"></span>
|
||||
</h6>
|
||||
<p class="q-mb-none">
|
||||
<span v-text="$t('ui_custom_badge_desc')"></span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('ui_custom_badge')"></span></p>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-md-8">
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
tip="Custom Badge"
|
||||
v-model.trim="formData.lnbits_custom_badge"
|
||||
:label="$t('ui_custom_badge')"
|
||||
:label="$t('ui_custom_badge_label')"
|
||||
></q-input>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="col-12 col-md-4">
|
||||
<q-select
|
||||
filled
|
||||
v-model="formData.lnbits_custom_badge_color"
|
||||
@@ -139,8 +145,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<h6 class="q-my-sm"><span v-text="$t('ui_custom_image')"></span></h6>
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('ui_custom_image')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
@@ -151,8 +157,10 @@
|
||||
></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('themes')"></span></p>
|
||||
<q-select
|
||||
filled
|
||||
v-model="formData.lnbits_theme_options"
|
||||
@@ -164,26 +172,41 @@
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('custom_logo')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_custom_logo"
|
||||
:label="$t('custom_logo')"
|
||||
label="https://example.com/image.png"
|
||||
:hint="$t('custom_logo_hint')"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-12">
|
||||
<h6 class="q-my-sm">
|
||||
<span v-text="$t('ad_space_section_title')"></span>
|
||||
</h6>
|
||||
<p class="q-my-none">
|
||||
<span v-text="$t('ad_space_section_desc')"></span>
|
||||
</p>
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('ad_space_title')"></span></p>
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_ad_space_title"
|
||||
:label="$t('ad_space_title_label')"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-12">
|
||||
<div class="col-12 col-md-6">
|
||||
<p><span v-text="$t('ad_slots')"></span></p>
|
||||
<q-input
|
||||
class="q-mb-md"
|
||||
filled
|
||||
v-model="formData.lnbits_ad_space"
|
||||
type="text"
|
||||
:label="$t('ad_slots_label')"
|
||||
:hint="$t('ad_slots_hint')"
|
||||
><q-tooltip>
|
||||
format {url};{img-light};{img-dark},{url};{img-light};{img-dark}"
|
||||
</q-tooltip>
|
||||
</q-input>
|
||||
<q-toggle
|
||||
v-model="formData.lnbits_ad_space_enabled"
|
||||
:label="
|
||||
@@ -192,30 +215,8 @@
|
||||
: $t('ads_disabled')
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<q-input
|
||||
filled
|
||||
type="text"
|
||||
v-model="formData.lnbits_ad_space_title"
|
||||
:label="$t('ad_space_title')"
|
||||
:hint="$t('ad_space_title_hint')"
|
||||
></q-input>
|
||||
<br />
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<q-input
|
||||
class="q-mb-md"
|
||||
filled
|
||||
v-model="formData.lnbits_ad_space"
|
||||
type="text"
|
||||
:label="$t('ad_slots')"
|
||||
:hint="$t('ad_slots_hint')"
|
||||
><q-tooltip>
|
||||
format {url};{img-light};{img-dark},{url};{img-light};{img-dark}"
|
||||
</q-tooltip>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md q-my-md">
|
||||
<div class="col-12" v-text="$t('ui_default_theme')"></div>
|
||||
@@ -256,50 +257,12 @@
|
||||
>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-col-gutter-md q-mb-md">
|
||||
<div class="col-12 col-sm-6 col-lg-2">
|
||||
<div class="col-12 col-sm-6 col-lg-3">
|
||||
<q-toggle
|
||||
type="bool"
|
||||
v-model="formData.lnbits_default_gradient"
|
||||
color="primary"
|
||||
:label="$t('gradient_background')"
|
||||
>
|
||||
</q-toggle>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-lg-2">
|
||||
<q-toggle
|
||||
type="bool"
|
||||
v-model="formData.lnbits_default_dark"
|
||||
color="primary"
|
||||
:label="$t('toggle_darkmode')"
|
||||
>
|
||||
</q-toggle>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-lg-2">
|
||||
<q-toggle
|
||||
type="bool"
|
||||
v-model="formData.lnbits_default_card_rounded"
|
||||
color="primary"
|
||||
:label="$t('rounded_ui')"
|
||||
>
|
||||
</q-toggle>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-lg-2">
|
||||
<q-toggle
|
||||
type="bool"
|
||||
v-model="formData.lnbits_default_card_gradient"
|
||||
color="primary"
|
||||
:label="$t('card_gradient')"
|
||||
>
|
||||
</q-toggle>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-lg-2">
|
||||
<q-toggle
|
||||
type="bool"
|
||||
v-model="formData.lnbits_default_card_shadow"
|
||||
color="primary"
|
||||
:label="$t('card_shadow')"
|
||||
label="Gradient Background"
|
||||
>
|
||||
</q-toggle>
|
||||
</div>
|
||||
|
||||
@@ -27,9 +27,12 @@
|
||||
></q-btn>
|
||||
<q-toolbar-title>
|
||||
<q-btn flat no-caps dense class="q-mr-sm" size="lg" type="a" href="/">
|
||||
<q-avatar v-if="g.settings.customLogo" height="30px">
|
||||
<img alt="Logo" :src="g.settings.customLogo" />
|
||||
</q-avatar>
|
||||
<q-img
|
||||
v-if="customLogo"
|
||||
height="30px"
|
||||
alt="Logo"
|
||||
:src="customLogo"
|
||||
></q-img>
|
||||
<span v-else-if="g.settings.siteTitle == 'LNbits'"
|
||||
><strong>LN</strong>bits</span
|
||||
>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -465,69 +465,6 @@
|
||||
</q-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mb-md">
|
||||
<div class="col-4">
|
||||
<span v-text="$t('rounded_ui')"></span>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<q-toggle
|
||||
dense
|
||||
flat
|
||||
round
|
||||
icon="rounded_corner"
|
||||
v-model="g.cardRoundedChoice"
|
||||
@update:model-value="
|
||||
siteCustomisationChanged({cardRoundedChoice: $event})
|
||||
"
|
||||
>
|
||||
<q-tooltip
|
||||
><span v-text="$t('toggle_rounded_ui')"></span
|
||||
></q-tooltip>
|
||||
</q-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mb-md">
|
||||
<div class="col-4">
|
||||
<span v-text="$t('card_gradient')"></span>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<q-toggle
|
||||
dense
|
||||
flat
|
||||
round
|
||||
icon="gradient"
|
||||
v-model="g.cardGradientChoice"
|
||||
@update:model-value="
|
||||
siteCustomisationChanged({cardGradientChoice: $event})
|
||||
"
|
||||
>
|
||||
<q-tooltip
|
||||
><span v-text="$t('toggle_card_gradient')"></span
|
||||
></q-tooltip>
|
||||
</q-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mb-md">
|
||||
<div class="col-4">
|
||||
<span v-text="$t('card_shadow')"></span>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<q-toggle
|
||||
dense
|
||||
flat
|
||||
round
|
||||
icon="blur_on"
|
||||
v-model="g.cardShadowChoice"
|
||||
@update:model-value="
|
||||
siteCustomisationChanged({cardShadowChoice: $event})
|
||||
"
|
||||
>
|
||||
<q-tooltip
|
||||
><span v-text="$t('toggle_card_shadow')"></span
|
||||
></q-tooltip>
|
||||
</q-toggle>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row q-mb-md">
|
||||
<div class="col-4">
|
||||
@@ -602,7 +539,9 @@
|
||||
</div>
|
||||
<q-card-section>
|
||||
<q-btn
|
||||
@click="resetThemeDefaults"
|
||||
@click="
|
||||
siteCustomisationChanged(defaultSiteCustomisation)
|
||||
"
|
||||
:label="$t('reset_defaults')"
|
||||
filled
|
||||
color="primary"
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
"
|
||||
:clickable="!!reviewsUrl"
|
||||
@click="openReviews(extension)"
|
||||
></lnbits-extension-rating>
|
||||
/>
|
||||
<q-btn-group size="xs" style="margin: 5px 0">
|
||||
<q-btn
|
||||
v-if="extension.hasFreeRelease"
|
||||
|
||||
@@ -55,7 +55,6 @@
|
||||
></q-input>
|
||||
<q-input
|
||||
filled
|
||||
v-if="g.settings.hasFirstInstallToken"
|
||||
v-model.trim="loginData.firstInstallToken"
|
||||
:type="loginData.isPwd ? 'password' : 'text'"
|
||||
:label="$t('first_install_token')"
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
</q-card>
|
||||
</div>
|
||||
<div
|
||||
v-show="!g.mobileSimple || !$q.screen.lt.md"
|
||||
v-if="!g.mobileSimple || !$q.screen.lt.md"
|
||||
class="col-12 col-md-5 q-gutter-y-md"
|
||||
>
|
||||
<lnbits-wallet-extra
|
||||
|
||||
@@ -292,8 +292,7 @@ async def btc_rates(currency: str) -> list[tuple[str, float]]:
|
||||
async def btc_price(currency: str) -> float:
|
||||
rates = await btc_rates(currency)
|
||||
if not rates:
|
||||
logger.warning("Could not fetch any Bitcoin price.")
|
||||
return 0.0
|
||||
raise ValueError("Could not fetch any Bitcoin price.")
|
||||
elif len(rates) == 1:
|
||||
logger.warning("Could only fetch one Bitcoin price.")
|
||||
|
||||
@@ -307,8 +306,7 @@ async def get_fiat_rate_and_price_satoshis(currency: str) -> tuple[float, float]
|
||||
f"btc-price-{currency}",
|
||||
settings.lnbits_exchange_rate_cache_seconds,
|
||||
)
|
||||
rate = float(100_000_000 / price) if price > 0 else 0.0
|
||||
return rate, price
|
||||
return float(100_000_000 / price), price
|
||||
|
||||
|
||||
async def get_fiat_rate_satoshis(currency: str) -> float:
|
||||
@@ -318,13 +316,9 @@ async def get_fiat_rate_satoshis(currency: str) -> float:
|
||||
|
||||
async def fiat_amount_as_satoshis(amount: float, currency: str) -> int:
|
||||
rate = await get_fiat_rate_satoshis(currency)
|
||||
if rate > 0:
|
||||
return int(amount * rate)
|
||||
raise ValueError(f"Could not get exchange rate for {currency}.")
|
||||
return int(amount * (rate))
|
||||
|
||||
|
||||
async def satoshis_amount_as_fiat(amount: float, currency: str) -> float:
|
||||
rate = await get_fiat_rate_satoshis(currency)
|
||||
if rate > 0:
|
||||
return float(amount / rate)
|
||||
raise ValueError(f"Could not get exchange rate for {currency}.")
|
||||
return float(amount / rate)
|
||||
|
||||
@@ -29,7 +29,6 @@ from .nwc import NWCWallet
|
||||
from .opennode import OpenNodeWallet
|
||||
from .phoenixd import PhoenixdWallet
|
||||
from .spark import SparkWallet
|
||||
from .sparkl2 import SparkL2Wallet
|
||||
from .strike import StrikeWallet
|
||||
from .void import VoidWallet
|
||||
from .zbd import ZBDWallet
|
||||
@@ -76,7 +75,6 @@ __all__ = [
|
||||
"NWCWallet",
|
||||
"OpenNodeWallet",
|
||||
"PhoenixdWallet",
|
||||
"SparkL2Wallet",
|
||||
"SparkWallet",
|
||||
"StrikeWallet",
|
||||
"VoidWallet",
|
||||
|
||||
@@ -1,355 +0,0 @@
|
||||
import asyncio
|
||||
import hashlib
|
||||
import json
|
||||
import uuid
|
||||
from collections.abc import AsyncGenerator
|
||||
from pathlib import Path
|
||||
from typing import Any, cast
|
||||
|
||||
import httpx
|
||||
from bolt11 import decode as bolt11_decode
|
||||
from coincurve.keys import PrivateKey
|
||||
from embit.bip39 import mnemonic_from_bytes, mnemonic_is_valid
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.helpers import normalize_endpoint
|
||||
from lnbits.settings import settings
|
||||
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentFailedStatus,
|
||||
PaymentPendingStatus,
|
||||
PaymentResponse,
|
||||
PaymentStatus,
|
||||
PaymentSuccessStatus,
|
||||
StatusResponse,
|
||||
Wallet,
|
||||
)
|
||||
|
||||
|
||||
class SparkSidecarError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SparkL2Wallet(Wallet):
|
||||
"""
|
||||
Spark L2 funding source via a local sidecar service.
|
||||
|
||||
Required settings/env:
|
||||
- SPARK_L2_ENDPOINT (default http://127.0.0.1:8765)
|
||||
Optional:
|
||||
- SPARK_L2_API_KEY
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self._status = "Initializing"
|
||||
self._sidecar_path = Path(settings.lnbits_data_folder, "light_spark")
|
||||
|
||||
self.pending_invoices: list[str] = []
|
||||
|
||||
self.endpoint = "http://127.0.0.1:8765"
|
||||
self._api_key = uuid.uuid4().hex
|
||||
|
||||
if settings.spark_l2_external_endpoint:
|
||||
self.endpoint = normalize_endpoint(
|
||||
cast(str, settings.spark_l2_external_endpoint)
|
||||
)
|
||||
logger.info(f"Using external Spark sidecar endpoint: {self.endpoint}")
|
||||
else:
|
||||
logger.error("No Spark sidecar endpoint configuration found.")
|
||||
|
||||
if settings.spark_l2_external_api_key:
|
||||
self._api_key = cast(str, settings.spark_l2_external_api_key)
|
||||
logger.info("Using external Spark sidecar API.")
|
||||
else:
|
||||
logger.warning("No Spark sidecar API key configured.")
|
||||
|
||||
headers = {"User-Agent": settings.user_agent, "X-Api-Key": self._api_key}
|
||||
|
||||
self.client = httpx.AsyncClient(
|
||||
base_url=self.endpoint,
|
||||
headers=headers,
|
||||
timeout=60,
|
||||
)
|
||||
|
||||
async def cleanup(self):
|
||||
try:
|
||||
await self.client.aclose()
|
||||
except RuntimeError as e:
|
||||
logger.warning(f"Error closing wallet connection: {e}")
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
try:
|
||||
res = await self._request("POST", "/v1/balance")
|
||||
status = res.get("status")
|
||||
if status == "missing_mnemonic":
|
||||
await self._check_sidecar_mnemonic()
|
||||
return StatusResponse("Spark sidecar mnemonic not set", 0)
|
||||
|
||||
balance_msat = res.get("balance_msat")
|
||||
if balance_msat is not None:
|
||||
return StatusResponse(None, int(balance_msat))
|
||||
balance_sats = res.get("balance_sats")
|
||||
if balance_sats is None:
|
||||
return StatusResponse("Spark sidecar: missing balance.", 0)
|
||||
return StatusResponse(None, int(balance_sats) * 1000)
|
||||
except Exception as e:
|
||||
logger.warning(e)
|
||||
return StatusResponse(f"Spark sidecar status error: {e}", 0)
|
||||
|
||||
async def create_invoice(
|
||||
self,
|
||||
amount: int,
|
||||
memo: str | None = None,
|
||||
description_hash: bytes | None = None,
|
||||
unhashed_description: bytes | None = None,
|
||||
**kwargs,
|
||||
) -> InvoiceResponse:
|
||||
expiry = kwargs.get("expiry")
|
||||
expiry_secs = int(expiry) if expiry else None
|
||||
|
||||
description_hash_hex = None
|
||||
if description_hash:
|
||||
description_hash_hex = description_hash.hex()
|
||||
elif unhashed_description:
|
||||
description_hash_hex = hashlib.sha256(unhashed_description).hexdigest()
|
||||
|
||||
try:
|
||||
payload = {
|
||||
"amount_sats": int(amount),
|
||||
"memo": (memo or "") if not description_hash_hex else None,
|
||||
"description_hash": description_hash_hex,
|
||||
"expiry_seconds": expiry_secs,
|
||||
}
|
||||
res = await self._request("POST", "/v1/invoices", payload)
|
||||
bolt11 = res.get("payment_request")
|
||||
checking_id = res.get("checking_id")
|
||||
if not bolt11 or not checking_id:
|
||||
return InvoiceResponse(
|
||||
ok=False,
|
||||
error_message="Spark sidecar invoice response missing fields.",
|
||||
)
|
||||
self.pending_invoices.append(checking_id)
|
||||
|
||||
return InvoiceResponse(
|
||||
ok=True,
|
||||
payment_request=bolt11,
|
||||
checking_id=checking_id,
|
||||
preimage=res.get("preimage", None),
|
||||
)
|
||||
except Exception as e:
|
||||
return InvoiceResponse(ok=False, error_message=str(e))
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
try:
|
||||
max_fee_sats = (int(fee_limit_msat) + 999) // 1000
|
||||
|
||||
payment_hash = None
|
||||
try:
|
||||
payment_hash = bolt11_decode(bolt11).payment_hash
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
payment_hash = None
|
||||
payload = {
|
||||
"bolt11": bolt11,
|
||||
"max_fee_sats": max_fee_sats,
|
||||
"payment_hash": payment_hash,
|
||||
}
|
||||
res = await self._request("POST", "/v1/payments", payload)
|
||||
checking_id = payment_hash or res.get("checking_id")
|
||||
if not checking_id:
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
error_message="Spark sidecar payment response missing checking_id.",
|
||||
)
|
||||
status = res.get("status")
|
||||
fee_msat = res.get("fee_msat")
|
||||
ok = None
|
||||
if status:
|
||||
ok = self._map_payment_ok(status)
|
||||
return PaymentResponse(
|
||||
ok=ok,
|
||||
checking_id=checking_id,
|
||||
fee_msat=int(fee_msat) if fee_msat is not None else None,
|
||||
preimage=res.get("preimage"),
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return PaymentResponse(ok=False, error_message=str(e))
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
try:
|
||||
res = await self._request("GET", f"/v1/invoices/{checking_id}")
|
||||
status = res.get("status")
|
||||
if not status:
|
||||
return PaymentPendingStatus()
|
||||
return self._map_invoice_status(status)
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentPendingStatus()
|
||||
|
||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
try:
|
||||
res = await self._request("GET", f"/v1/payments/{checking_id}")
|
||||
status = res.get("status")
|
||||
fee_msat = res.get("fee_msat")
|
||||
preimage = res.get("preimage")
|
||||
if not status:
|
||||
return PaymentPendingStatus()
|
||||
mapped = self._map_payment_status(status)
|
||||
if mapped.success:
|
||||
return PaymentSuccessStatus(
|
||||
fee_msat=int(fee_msat) if fee_msat is not None else None,
|
||||
preimage=preimage,
|
||||
)
|
||||
if mapped.failed:
|
||||
return PaymentFailedStatus()
|
||||
return PaymentPendingStatus()
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
return PaymentPendingStatus()
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
stream_path = "/v1/invoices/stream"
|
||||
while settings.lnbits_running:
|
||||
try:
|
||||
async with self.client.stream("GET", stream_path, timeout=None) as r:
|
||||
if r.status_code in {404, 405}:
|
||||
logger.warning(
|
||||
"Spark sidecar invoice stream not available, "
|
||||
"falling back to polling."
|
||||
)
|
||||
async for checking_id in self._poll_pending_invoices():
|
||||
yield checking_id
|
||||
return
|
||||
r.raise_for_status()
|
||||
logger.info("connected to Spark sidecar invoice stream.")
|
||||
async for line in r.aiter_lines():
|
||||
if not line or not line.startswith("data:"):
|
||||
continue
|
||||
data = json.loads(line[5:].strip())
|
||||
checking_id = data.get("checking_id")
|
||||
if not checking_id:
|
||||
continue
|
||||
yield checking_id
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
"lost connection to Spark sidecar invoice stream: "
|
||||
f"'{exc}' retrying in 5 seconds"
|
||||
)
|
||||
await asyncio.sleep(5)
|
||||
|
||||
async def _request(
|
||||
self, method: str, path: str, json_data: dict[str, Any] | None = None
|
||||
) -> dict[str, Any]:
|
||||
error_message = None
|
||||
try:
|
||||
r = await self.client.request(method, path, json=json_data, timeout=30)
|
||||
r.raise_for_status()
|
||||
j = r.json()
|
||||
except (httpx.RequestError, httpx.HTTPStatusError, json.JSONDecodeError) as exc:
|
||||
if isinstance(exc, httpx.HTTPStatusError) and exc.response is not None:
|
||||
try:
|
||||
error_json = exc.response.json()
|
||||
if "error" in error_json:
|
||||
error_message = error_json["error"]
|
||||
except Exception as json_exc:
|
||||
logger.error(
|
||||
f"Failed to parse Spark error response as JSON: {json_exc}"
|
||||
)
|
||||
raise SparkSidecarError(
|
||||
error_message or f"Spark sidecar request error: '{exc}'"
|
||||
) from exc
|
||||
|
||||
if error_message or j.get("error"):
|
||||
raise SparkSidecarError(
|
||||
error_message or f"Spark sidecar error: {j['error']}"
|
||||
)
|
||||
return j
|
||||
|
||||
async def _poll_pending_invoices(self) -> AsyncGenerator[str, None]:
|
||||
while settings.lnbits_running:
|
||||
for invoice in list(self.pending_invoices):
|
||||
try:
|
||||
status = await self.get_invoice_status(invoice)
|
||||
if status.paid:
|
||||
yield invoice
|
||||
self.pending_invoices.remove(invoice)
|
||||
elif status.failed:
|
||||
self.pending_invoices.remove(invoice)
|
||||
except Exception as exc:
|
||||
logger.error(f"could not get status of invoice {invoice}: '{exc}' ")
|
||||
await asyncio.sleep(5)
|
||||
|
||||
def _map_invoice_status(self, status: str) -> PaymentStatus:
|
||||
success = {
|
||||
"LIGHTNING_PAYMENT_RECEIVED",
|
||||
"TRANSFER_COMPLETED",
|
||||
"PAYMENT_PREIMAGE_RECOVERED",
|
||||
}
|
||||
failed = {
|
||||
"TRANSFER_FAILED",
|
||||
"PAYMENT_PREIMAGE_RECOVERING_FAILED",
|
||||
"REFUND_SIGNING_FAILED",
|
||||
"REFUND_SIGNING_COMMITMENTS_QUERYING_FAILED",
|
||||
"TRANSFER_CREATION_FAILED",
|
||||
}
|
||||
if status in success:
|
||||
return PaymentSuccessStatus()
|
||||
if status in failed:
|
||||
return PaymentFailedStatus()
|
||||
return PaymentPendingStatus()
|
||||
|
||||
def _map_payment_status(self, status: str) -> PaymentStatus:
|
||||
success = {
|
||||
"LIGHTNING_PAYMENT_SUCCEEDED",
|
||||
"TRANSFER_COMPLETED",
|
||||
"PREIMAGE_PROVIDED",
|
||||
}
|
||||
failed = {
|
||||
"LIGHTNING_PAYMENT_FAILED",
|
||||
"TRANSFER_FAILED",
|
||||
"PREIMAGE_PROVIDING_FAILED",
|
||||
"USER_TRANSFER_VALIDATION_FAILED",
|
||||
"USER_SWAP_RETURN_FAILED",
|
||||
}
|
||||
if status in success:
|
||||
return PaymentSuccessStatus()
|
||||
if status in failed:
|
||||
return PaymentFailedStatus()
|
||||
return PaymentPendingStatus()
|
||||
|
||||
def _map_payment_ok(self, status: str) -> bool | None:
|
||||
mapped = self._map_payment_status(status)
|
||||
if mapped.success:
|
||||
return True
|
||||
if mapped.failed:
|
||||
return False
|
||||
return None
|
||||
|
||||
async def _check_sidecar_mnemonic(self):
|
||||
if settings.spark_l2_mnemonic:
|
||||
valid = mnemonic_is_valid(settings.spark_l2_mnemonic)
|
||||
if not valid:
|
||||
logger.warning("SPARK_L2_MNEMONIC is set but invalid. Please recheck!")
|
||||
return
|
||||
await self._set_sidecar_mnemonic(settings.spark_l2_mnemonic)
|
||||
return
|
||||
|
||||
logger.info("SPARK_L2_MNEMONIC is not set, one will be generated for you.")
|
||||
mnemonic = mnemonic_from_bytes(PrivateKey().secret)
|
||||
await self._set_sidecar_mnemonic(mnemonic)
|
||||
|
||||
async def _set_sidecar_mnemonic(self, mnemonic: str):
|
||||
logger.info("Checking 'SPARK_L2_MNEMONIC' on the Spark sidecar.")
|
||||
payload = {"mnemonic": mnemonic}
|
||||
resp = await self._request("POST", "/v1/mnemonic", payload)
|
||||
status = resp.get("status")
|
||||
logger.info(f"Spark sidecar mnemonic status: {status}")
|
||||
if status == "set":
|
||||
logger.info("Updating 'SPARK_L2_MNEMONIC' mnemonic settings.")
|
||||
from lnbits.core.crud.settings import set_settings_field
|
||||
|
||||
await set_settings_field("spark_l2_mnemonic", mnemonic)
|
||||
else:
|
||||
logger.info("Nothing to do for 'SPARK_L2_MNEMONIC' on the Spark sidecar.")
|
||||
Generated
+16
-22
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "lnbits",
|
||||
"dependencies": {
|
||||
"axios": "^1.13.5",
|
||||
"axios": "^1.13.2",
|
||||
"chart.js": "^4.5.1",
|
||||
"moment": "^2.30.1",
|
||||
"nostr-tools": "^2.18.2",
|
||||
@@ -730,17 +730,16 @@
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"license": "MIT"
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
|
||||
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
@@ -929,7 +928,6 @@
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
@@ -975,7 +973,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
@@ -1071,16 +1068,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.11",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
|
||||
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
|
||||
"version": "1.15.6",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
|
||||
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
@@ -1091,9 +1087,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
@@ -1368,7 +1364,6 @@
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
@@ -1377,7 +1372,6 @@
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
@@ -1386,9 +1380,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
||||
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
"terser": "^5.44.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.13.5",
|
||||
"axios": "^1.13.2",
|
||||
"chart.js": "^4.5.1",
|
||||
"moment": "^2.30.1",
|
||||
"nostr-tools": "^2.18.2",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "lnbits"
|
||||
version = "1.5.1"
|
||||
version = "1.4.1"
|
||||
requires-python = ">=3.10,<3.13"
|
||||
description = "LNbits, free and open-source Lightning wallet and accounts system."
|
||||
authors = [{ name = "Alan Bits", email = "alan@lnbits.com" }]
|
||||
|
||||
@@ -708,7 +708,7 @@ async def test_user_activation(
|
||||
f"/users/api/v1/user/{user_id}/activate",
|
||||
headers={"Authorization": f"Bearer {superuser_token}"},
|
||||
)
|
||||
|
||||
print("### response", response.text)
|
||||
assert response.status_code == 200
|
||||
assert response.json().get("message") == "User activated."
|
||||
|
||||
|
||||
@@ -1247,7 +1247,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "lnbits"
|
||||
version = "1.5.1"
|
||||
version = "1.4.1"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiosqlite" },
|
||||
@@ -1725,50 +1725,50 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "12.1.1"
|
||||
version = "12.1.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/30/5bd3d794762481f8c8ae9c80e7b76ecea73b916959eb587521358ef0b2f9/pillow-12.1.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1f1625b72740fdda5d77b4def688eb8fd6490975d06b909fd19f13f391e077e0", size = 5304099, upload-time = "2026-02-11T04:20:06.13Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/c1/aab9e8f3eeb4490180e357955e15c2ef74b31f64790ff356c06fb6cf6d84/pillow-12.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:178aa072084bd88ec759052feca8e56cbb14a60b39322b99a049e58090479713", size = 4657880, upload-time = "2026-02-11T04:20:09.291Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/0a/9879e30d56815ad529d3985aeff5af4964202425c27261a6ada10f7cbf53/pillow-12.1.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b66e95d05ba806247aaa1561f080abc7975daf715c30780ff92a20e4ec546e1b", size = 6222587, upload-time = "2026-02-11T04:20:10.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/5f/a1b72ff7139e4f89014e8d451442c74a774d5c43cd938fb0a9f878576b37/pillow-12.1.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:89c7e895002bbe49cdc5426150377cbbc04767d7547ed145473f496dfa40408b", size = 8027678, upload-time = "2026-02-11T04:20:12.455Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/c2/c7cb187dac79a3d22c3ebeae727abee01e077c8c7d930791dc592f335153/pillow-12.1.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a5cbdcddad0af3da87cb16b60d23648bc3b51967eb07223e9fed77a82b457c4", size = 6335777, upload-time = "2026-02-11T04:20:14.441Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/7b/f9b09a7804ec7336effb96c26d37c29d27225783dc1501b7d62dcef6ae25/pillow-12.1.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9f51079765661884a486727f0729d29054242f74b46186026582b4e4769918e4", size = 7027140, upload-time = "2026-02-11T04:20:16.387Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/98/b2/2fa3c391550bd421b10849d1a2144c44abcd966daadd2f7c12e19ea988c4/pillow-12.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:99c1506ea77c11531d75e3a412832a13a71c7ebc8192ab9e4b2e355555920e3e", size = 6449855, upload-time = "2026-02-11T04:20:18.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/96/ff/9caf4b5b950c669263c39e96c78c0d74a342c71c4f43fd031bb5cb7ceac9/pillow-12.1.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36341d06738a9f66c8287cf8b876d24b18db9bd8740fa0672c74e259ad408cff", size = 7151329, upload-time = "2026-02-11T04:20:20.646Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/f8/4b24841f582704da675ca535935bccb32b00a6da1226820845fac4a71136/pillow-12.1.1-cp310-cp310-win32.whl", hash = "sha256:6c52f062424c523d6c4db85518774cc3d50f5539dd6eed32b8f6229b26f24d40", size = 6325574, upload-time = "2026-02-11T04:20:22.43Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/f9/9f6b01c0881d7036063aa6612ef04c0e2cad96be21325a1e92d0203f8e91/pillow-12.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:c6008de247150668a705a6338156efb92334113421ceecf7438a12c9a12dab23", size = 7032347, upload-time = "2026-02-11T04:20:23.932Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/79/13/c7922edded3dcdaf10c59297540b72785620abc0538872c819915746757d/pillow-12.1.1-cp310-cp310-win_arm64.whl", hash = "sha256:1a9b0ee305220b392e1124a764ee4265bd063e54a751a6b62eff69992f457fa9", size = 2453457, upload-time = "2026-02-11T04:20:25.392Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/46/5da1ec4a5171ee7bf1a0efa064aba70ba3d6e0788ce3f5acd1375d23c8c0/pillow-12.1.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:e879bb6cd5c73848ef3b2b48b8af9ff08c5b71ecda8048b7dd22d8a33f60be32", size = 5304084, upload-time = "2026-02-11T04:20:27.501Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/78/93/a29e9bc02d1cf557a834da780ceccd54e02421627200696fcf805ebdc3fb/pillow-12.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:365b10bb9417dd4498c0e3b128018c4a624dc11c7b97d8cc54effe3b096f4c38", size = 4657866, upload-time = "2026-02-11T04:20:29.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/13/84/583a4558d492a179d31e4aae32eadce94b9acf49c0337c4ce0b70e0a01f2/pillow-12.1.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d4ce8e329c93845720cd2014659ca67eac35f6433fd3050393d85f3ecef0dad5", size = 6232148, upload-time = "2026-02-11T04:20:31.329Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/e2/53c43334bbbb2d3b938978532fbda8e62bb6e0b23a26ce8592f36bcc4987/pillow-12.1.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc354a04072b765eccf2204f588a7a532c9511e8b9c7f900e1b64e3e33487090", size = 8038007, upload-time = "2026-02-11T04:20:34.225Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b8/a6/3d0e79c8a9d58150dd98e199d7c1c56861027f3829a3a60b3c2784190180/pillow-12.1.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7e7976bf1910a8116b523b9f9f58bf410f3e8aa330cd9a2bb2953f9266ab49af", size = 6345418, upload-time = "2026-02-11T04:20:35.858Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:597bd9c8419bc7c6af5604e55847789b69123bbe25d65cc6ad3012b4f3c98d8b", size = 7034590, upload-time = "2026-02-11T04:20:37.91Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/bf/e6f65d3db8a8bbfeaf9e13cc0417813f6319863a73de934f14b2229ada18/pillow-12.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2c1fc0f2ca5f96a3c8407e41cca26a16e46b21060fe6d5b099d2cb01412222f5", size = 6458655, upload-time = "2026-02-11T04:20:39.496Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/c2/66091f3f34a25894ca129362e510b956ef26f8fb67a0e6417bc5744e56f1/pillow-12.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:578510d88c6229d735855e1f278aa305270438d36a05031dfaae5067cc8eb04d", size = 7159286, upload-time = "2026-02-11T04:20:41.139Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/5a/24bc8eb526a22f957d0cec6243146744966d40857e3d8deb68f7902ca6c1/pillow-12.1.1-cp311-cp311-win32.whl", hash = "sha256:7311c0a0dcadb89b36b7025dfd8326ecfa36964e29913074d47382706e516a7c", size = 6328663, upload-time = "2026-02-11T04:20:43.184Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:fbfa2a7c10cc2623f412753cddf391c7f971c52ca40a3f65dc5039b2939e8563", size = 7031448, upload-time = "2026-02-11T04:20:44.696Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/70/f76296f53610bd17b2e7d31728b8b7825e3ac3b5b3688b51f52eab7c0818/pillow-12.1.1-cp311-cp311-win_arm64.whl", hash = "sha256:b81b5e3511211631b3f672a595e3221252c90af017e399056d0faabb9538aa80", size = 2453651, upload-time = "2026-02-11T04:20:46.243Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/56/11/5d43209aa4cb58e0cc80127956ff1796a68b928e6324bbf06ef4db34367b/pillow-12.1.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:600fd103672b925fe62ed08e0d874ea34d692474df6f4bf7ebe148b30f89f39f", size = 5228606, upload-time = "2026-02-11T04:22:52.106Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/d5/3b005b4e4fda6698b371fa6c21b097d4707585d7db99e98d9b0b87ac612a/pillow-12.1.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:665e1b916b043cef294bc54d47bf02d87e13f769bc4bc5fa225a24b3a6c5aca9", size = 4622321, upload-time = "2026-02-11T04:22:53.827Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/36/ed3ea2d594356fd8037e5a01f6156c74bc8d92dbb0fa60746cc96cabb6e8/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:495c302af3aad1ca67420ddd5c7bd480c8867ad173528767d906428057a11f0e", size = 5247579, upload-time = "2026-02-11T04:22:56.094Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/9a/9cc3e029683cf6d20ae5085da0dafc63148e3252c2f13328e553aaa13cfb/pillow-12.1.1-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8fd420ef0c52c88b5a035a0886f367748c72147b2b8f384c9d12656678dfdfa9", size = 6989094, upload-time = "2026-02-11T04:22:58.288Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/98/fc53ab36da80b88df0967896b6c4b4cd948a0dc5aa40a754266aa3ae48b3/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f975aa7ef9684ce7e2c18a3aa8f8e2106ce1e46b94ab713d156b2898811651d3", size = 5313850, upload-time = "2026-02-11T04:23:00.554Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/30/02/00fa585abfd9fe9d73e5f6e554dc36cc2b842898cbfc46d70353dae227f8/pillow-12.1.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8089c852a56c2966cf18835db62d9b34fef7ba74c726ad943928d494fa7f4735", size = 5963343, upload-time = "2026-02-11T04:23:02.934Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/26/c56ce33ca856e358d27fda9676c055395abddb82c35ac0f593877ed4562e/pillow-12.1.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:cb9bb857b2d057c6dfc72ac5f3b44836924ba15721882ef103cecb40d002d80e", size = 7029880, upload-time = "2026-02-11T04:23:04.783Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2769,14 +2769,14 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.1.6"
|
||||
version = "3.1.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/70/1469ef1d3542ae7c2c7b72bd5e3a4e6ee69d7978fa8a3af05a38eca5becf/werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67", size = 864754, upload-time = "2026-01-08T17:49:23.247Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/e4/8d97cca767bcc1be76d16fb76951608305561c6e056811587f36cb1316a8/werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc", size = 225025, upload-time = "2026-01-08T17:49:21.859Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user