@@ -303,7 +311,8 @@
data() {
return {
charge: JSON.parse('{{charge_data | tojson}}'),
- mempool_endpoint: '{{mempool_endpoint}}',
+ mempoolEndpoint: '{{mempool_endpoint}}',
+ network: '{{network}}',
pendingFunds: 0,
ws: null,
newProgress: 0.4,
@@ -316,19 +325,19 @@
cancelListener: () => {}
}
},
+ computed: {
+ mempoolHostname: function () {
+ let hostname = new URL(this.mempoolEndpoint).hostname
+ if (this.network === 'Testnet') {
+ hostname += '/testnet'
+ }
+ return hostname
+ }
+ },
methods: {
- startPaymentNotifier() {
- this.cancelListener()
- if (!this.lnbitswallet) return
- this.cancelListener = LNbits.events.onInvoicePaid(
- this.wallet,
- payment => {
- this.checkInvoiceBalance()
- }
- )
- },
checkBalances: async function () {
- if (this.charge.hasStaleBalance) return
+ if (!this.charge.payment_request && this.charge.hasOnchainStaleBalance)
+ return
try {
const {data} = await LNbits.api.request(
'GET',
@@ -345,7 +354,7 @@
const {
bitcoin: {addresses: addressesAPI}
} = mempoolJS({
- hostname: new URL(this.mempool_endpoint).hostname
+ hostname: new URL(this.mempoolEndpoint).hostname
})
try {
@@ -353,7 +362,8 @@
address: this.charge.onchainaddress
})
const newBalance = utxos.reduce((t, u) => t + u.value, 0)
- this.charge.hasStaleBalance = this.charge.balance === newBalance
+ this.charge.hasOnchainStaleBalance =
+ this.charge.balance === newBalance
this.pendingFunds = utxos
.filter(u => !u.status.confirmed)
@@ -388,10 +398,10 @@
const {
bitcoin: {websocket}
} = mempoolJS({
- hostname: new URL(this.mempool_endpoint).hostname
+ hostname: new URL(this.mempoolEndpoint).hostname
})
- this.ws = new WebSocket('wss://mempool.space/api/v1/ws')
+ this.ws = new WebSocket(`wss://${this.mempoolHostname}/api/v1/ws`)
this.ws.addEventListener('open', x => {
if (this.charge.onchainaddress) {
this.trackAddress(this.charge.onchainaddress)
@@ -428,13 +438,10 @@
}
},
created: async function () {
- if (this.charge.lnbitswallet) this.payInvoice()
+ if (this.charge.payment_request) this.payInvoice()
else this.payOnchain()
- await this.checkBalances()
- // empty for onchain
- this.wallet.inkey = '{{ wallet_inkey }}'
- this.startPaymentNotifier()
+ await this.checkBalances()
if (!this.charge.paid) {
this.loopRefresh()
diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html
index 396200cf..60c4d519 100644
--- a/lnbits/extensions/satspay/templates/satspay/index.html
+++ b/lnbits/extensions/satspay/templates/satspay/index.html
@@ -203,9 +203,14 @@
:href="props.row.webhook"
target="_blank"
style="color: unset; text-decoration: none"
- >{{props.row.webhook || props.row.webhook}}{{props.row.webhook}}
+
+
+ {{props.row.webhook_message }}
+
+
ID:
@@ -409,10 +414,11 @@
balance: null,
walletLinks: [],
chargeLinks: [],
- onchainwallet: '',
+ onchainwallet: null,
rescanning: false,
mempool: {
- endpoint: ''
+ endpoint: '',
+ network: 'Mainnet'
},
chargesTable: {
@@ -505,6 +511,7 @@
methods: {
cancelCharge: function (data) {
this.formDialogCharge.data.description = ''
+ this.formDialogCharge.data.onchain = false
this.formDialogCharge.data.onchainwallet = ''
this.formDialogCharge.data.lnbitswallet = ''
this.formDialogCharge.data.time = null
@@ -518,7 +525,7 @@
try {
const {data} = await LNbits.api.request(
'GET',
- '/watchonly/api/v1/wallet',
+ `/watchonly/api/v1/wallet?network=${this.mempool.network}`,
this.g.user.wallets[0].inkey
)
this.walletLinks = data.map(w => ({
@@ -538,6 +545,7 @@
this.g.user.wallets[0].inkey
)
this.mempool.endpoint = data.mempool_endpoint
+ this.mempool.network = data.network || 'Mainnet'
const url = new URL(this.mempool.endpoint)
this.mempool.hostname = url.hostname
} catch (error) {
@@ -577,7 +585,8 @@
const data = this.formDialogCharge.data
data.amount = parseInt(data.amount)
data.time = parseInt(data.time)
- data.onchainwallet = this.onchainwallet?.id
+ data.lnbitswallet = data.lnbits ? data.lnbitswallet : null
+ data.onchainwallet = data.onchain ? this.onchainwallet?.id : null
this.createCharge(wallet, data)
},
refreshActiveChargesBalance: async function () {
@@ -695,8 +704,8 @@
},
created: async function () {
await this.getCharges()
- await this.getWalletLinks()
await this.getWalletConfig()
+ await this.getWalletLinks()
setInterval(() => this.refreshActiveChargesBalance(), 10 * 2000)
await this.rescanOnchainAddresses()
setInterval(() => this.rescanOnchainAddresses(), 10 * 1000)
diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py
index b789bf8f..7b769a20 100644
--- a/lnbits/extensions/satspay/views.py
+++ b/lnbits/extensions/satspay/views.py
@@ -6,12 +6,12 @@ from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import HTMLResponse
-from lnbits.core.crud import get_wallet
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
+from lnbits.extensions.satspay.helpers import public_charge
from . import satspay_ext, satspay_renderer
-from .crud import get_charge, get_charge_config
+from .crud import get_charge
templates = Jinja2Templates(directory="templates")
@@ -30,18 +30,13 @@ async def display(request: Request, charge_id: str):
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
)
- wallet = await get_wallet(charge.lnbitswallet)
- onchainwallet_config = await get_charge_config(charge_id)
- inkey = wallet.inkey if wallet else None
- mempool_endpoint = (
- onchainwallet_config.mempool_endpoint if onchainwallet_config else None
- )
+
return satspay_renderer().TemplateResponse(
"satspay/display.html",
{
"request": request,
- "charge_data": charge.dict(),
- "wallet_inkey": inkey,
- "mempool_endpoint": mempool_endpoint,
+ "charge_data": public_charge(charge),
+ "mempool_endpoint": charge.config.mempool_endpoint,
+ "network": charge.config.network,
},
)
diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py
index 73c87e7c..bfff55a2 100644
--- a/lnbits/extensions/satspay/views_api.py
+++ b/lnbits/extensions/satspay/views_api.py
@@ -1,6 +1,6 @@
+import json
from http import HTTPStatus
-import httpx
from fastapi.params import Depends
from starlette.exceptions import HTTPException
@@ -20,7 +20,7 @@ from .crud import (
get_charges,
update_charge,
)
-from .helpers import compact_charge
+from .helpers import call_webhook, public_charge
from .models import CreateCharge
#############################CHARGES##########################
@@ -58,6 +58,7 @@ async def api_charges_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
**{"time_elapsed": charge.time_elapsed},
**{"time_left": charge.time_left},
**{"paid": charge.paid},
+ **{"webhook_message": charge.config.webhook_message},
}
for charge in await get_charges(wallet.wallet.user)
]
@@ -94,7 +95,7 @@ async def api_charge_delete(charge_id, wallet: WalletTypeInfo = Depends(get_key_
)
await delete_charge(charge_id)
- raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+ return "", HTTPStatus.NO_CONTENT
#############################BALANCE##########################
@@ -119,19 +120,9 @@ async def api_charge_balance(charge_id):
status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist."
)
- if charge.paid and charge.webhook:
- async with httpx.AsyncClient() as client:
- try:
- r = await client.post(
- charge.webhook,
- json=compact_charge(charge),
- timeout=40,
- )
- except AssertionError:
- charge.webhook = None
- return {
- **compact_charge(charge),
- **{"time_elapsed": charge.time_elapsed},
- **{"time_left": charge.time_left},
- **{"paid": charge.paid},
- }
+ if charge.must_call_webhook():
+ resp = await call_webhook(charge)
+ extra = {**charge.config.dict(), **resp}
+ await update_charge(charge_id=charge.id, extra=json.dumps(extra))
+
+ return {**public_charge(charge)}
diff --git a/lnbits/extensions/scrub/views_api.py b/lnbits/extensions/scrub/views_api.py
index 3714a304..cc55c15d 100644
--- a/lnbits/extensions/scrub/views_api.py
+++ b/lnbits/extensions/scrub/views_api.py
@@ -109,4 +109,4 @@ async def api_link_delete(link_id, wallet: WalletTypeInfo = Depends(require_admi
)
await delete_scrub_link(link_id)
- raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+ return "", HTTPStatus.NO_CONTENT
diff --git a/lnbits/extensions/splitpayments/templates/splitpayments/index.html b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
index 5862abc1..1cceb7ba 100644
--- a/lnbits/extensions/splitpayments/templates/splitpayments/index.html
+++ b/lnbits/extensions/splitpayments/templates/splitpayments/index.html
@@ -31,14 +31,20 @@
style="flex-wrap: nowrap"
v-for="(target, t) in targets"
>
-
+ option-label="name"
+ style="width: 1000px"
+ new-value-mode="add-unique"
+ use-input
+ input-debounce="0"
+ emit-value
+ >
Hit enter to add values
+ >Hit enter to add values
+
+ You can leave this blank. A default rounding option is available
+ (round amount to a value)
+
+
-
{% raw %}{{ famount }}{% endraw %}
+ {% raw %}{{ amountFormatted }}{% endraw %}
{% raw %}{{ fsat }}{% endraw %} sat
@@ -148,6 +148,14 @@
+
+
+
-
{% raw %}{{ famount }}{% endraw %}
+
+ {% raw %}{{ amountWithTipFormatted }}{% endraw %}
+
{% raw %}{{ fsat }}
sat
( + {{ tipAmountSat }} tip)( + {{ tipAmountFormatted }} tip)
{% endraw %}
@@ -204,19 +214,48 @@
style="padding: 10px; margin: 3px"
unelevated
@click="processTipSelection(tip)"
- size="xl"
+ size="lg"
:outline="!($q.dark.isActive)"
rounded
color="primary"
- v-for="tip in this.tip_options"
+ v-for="tip in tip_options.filter(f => f != 'Round')"
:key="tip"
>{% raw %}{{ tip }}{% endraw %}%
-
-
-
No, thanks
+
+
+
+
+ Ok
+
+ No, thanks
Close
@@ -256,6 +295,38 @@
style="font-size: min(90vw, 40em)"
>
+
+
+
+
+
+
+
+
+
+
+ No paid invoices
+
+
+
+ {%raw%}
+
+ {{payment.amount / 1000}} sats
+ Hash: {{payment.checking_id.slice(0, 30)}}...
+
+
+ {{payment.dateFrom}}
+
+
+ {%endraw%}
+
+
+
+
{% endblock %} {% block styles %}
@@ -294,8 +365,13 @@
exchangeRate: null,
stack: [],
tipAmount: 0.0,
+ tipRounding: null,
hasNFC: false,
nfcTagReading: false,
+ lastPaymentsDialog: {
+ show: false,
+ data: []
+ },
invoiceDialog: {
show: false,
data: null,
@@ -310,32 +386,81 @@
},
complete: {
show: false
- }
+ },
+ rounding: false
}
},
computed: {
amount: function () {
if (!this.stack.length) return 0.0
- return (Number(this.stack.join('')) / 100).toFixed(2)
+ return Number(this.stack.join('') / 100)
},
- famount: function () {
- return LNbits.utils.formatCurrency(this.amount, this.currency)
+ amountFormatted: function () {
+ return LNbits.utils.formatCurrency(
+ this.amount.toFixed(2),
+ this.currency
+ )
+ },
+ amountWithTipFormatted: function () {
+ return LNbits.utils.formatCurrency(
+ (this.amount + this.tipAmount).toFixed(2),
+ this.currency
+ )
},
sat: function () {
if (!this.exchangeRate) return 0
- return Math.ceil(
- ((this.amount - this.tipAmount) / this.exchangeRate) * 100000000
- )
+ return Math.ceil((this.amount / this.exchangeRate) * 100000000)
},
tipAmountSat: function () {
if (!this.exchangeRate) return 0
return Math.ceil((this.tipAmount / this.exchangeRate) * 100000000)
},
+ tipAmountFormatted: function () {
+ return LNbits.utils.formatSat(this.tipAmountSat)
+ },
fsat: function () {
return LNbits.utils.formatSat(this.sat)
+ },
+ isRoundValid() {
+ return this.tipRounding > this.amount
+ },
+ roundToSugestion() {
+ switch (true) {
+ case this.amount > 50:
+ toNext = 10
+ break
+ case this.amount > 6:
+ toNext = 5
+ break
+ case this.amount > 2.5:
+ toNext = 1
+ break
+ default:
+ toNext = 0.5
+ break
+ }
+
+ return Math.ceil(this.amount / toNext) * toNext
}
},
methods: {
+ setRounding() {
+ this.rounding = true
+ this.tipRounding = this.roundToSugestion
+ this.$nextTick(() => this.$refs.inputRounding.focus())
+ },
+ calculatePercent() {
+ let change = ((this.tipRounding - this.amount) / this.amount) * 100
+ if (change < 0) {
+ this.$q.notify({
+ type: 'warning',
+ message: 'Amount with tip must be greater than initial amount.'
+ })
+ this.tipRounding = this.roundToSugestion
+ return
+ }
+ this.processTipSelection(change)
+ },
closeInvoiceDialog: function () {
this.stack = []
this.tipAmount = 0.0
@@ -348,30 +473,18 @@
processTipSelection: function (selectedTipOption) {
this.tipDialog.show = false
- if (selectedTipOption) {
- const tipAmount = parseFloat(
- parseFloat((selectedTipOption / 100) * this.amount)
- )
- const subtotal = parseFloat(this.amount)
- const grandTotal = parseFloat((tipAmount + subtotal).toFixed(2))
- const totalString = grandTotal.toFixed(2).toString()
-
- this.stack = []
- for (var i = 0; i < totalString.length; i++) {
- const char = totalString[i]
-
- if (char !== '.') {
- this.stack.push(char)
- }
- }
-
- this.tipAmount = tipAmount
+ if (!selectedTipOption) {
+ this.tipAmount = 0.0
+ return this.showInvoice()
}
+ this.tipAmount = (selectedTipOption / 100) * this.amount
this.showInvoice()
},
submitForm: function () {
if (this.tip_options && this.tip_options.length) {
+ this.rounding = false
+ this.tipRounding = null
this.showTipModal()
} else {
this.showInvoice()
@@ -520,6 +633,24 @@
self.exchangeRate =
response.data.data['BTC' + self.currency][self.currency]
})
+ },
+ getLastPayments() {
+ return axios
+ .get(`/tpos/api/v1/tposs/${this.tposId}/invoices`)
+ .then(res => {
+ if (res.data && res.data.length) {
+ let last = [...res.data]
+ this.lastPaymentsDialog.data = last.map(obj => {
+ obj.dateFrom = moment(obj.time * 1000).fromNow()
+ return obj
+ })
+ }
+ })
+ .catch(e => console.error(e))
+ },
+ showLastPayments() {
+ this.getLastPayments()
+ this.lastPaymentsDialog.show = true
}
},
created: function () {
@@ -529,10 +660,26 @@
'{{ tpos.tip_options | tojson }}' == 'null'
? null
: JSON.parse('{{ tpos.tip_options }}')
+
+ if ('{{ tpos.tip_wallet }}') {
+ this.tip_options.push('Round')
+ }
setInterval(function () {
getRates()
}, 120000)
}
})
+
{% endblock %}
diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py
index b7f14b98..fe63a247 100644
--- a/lnbits/extensions/tpos/views_api.py
+++ b/lnbits/extensions/tpos/views_api.py
@@ -7,7 +7,8 @@ from lnurl import decode as decode_lnurl
from loguru import logger
from starlette.exceptions import HTTPException
-from lnbits.core.crud import get_user
+from lnbits.core.crud import get_latest_payments_by_extension, get_user
+from lnbits.core.models import Payment
from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
@@ -51,7 +52,7 @@ async def api_tpos_delete(
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your TPoS.")
await delete_tpos(tpos_id)
- raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+ return "", HTTPStatus.NO_CONTENT
@tpos_ext.post("/api/v1/tposs/{tpos_id}/invoices", status_code=HTTPStatus.CREATED)
@@ -81,6 +82,30 @@ async def api_tpos_create_invoice(
return {"payment_hash": payment_hash, "payment_request": payment_request}
+@tpos_ext.get("/api/v1/tposs/{tpos_id}/invoices")
+async def api_tpos_get_latest_invoices(tpos_id: str = None):
+ try:
+ payments = [
+ Payment.from_row(row)
+ for row in await get_latest_payments_by_extension(
+ ext_name="tpos", ext_id=tpos_id
+ )
+ ]
+
+ except Exception as e:
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+
+ return [
+ {
+ "checking_id": payment.checking_id,
+ "amount": payment.amount,
+ "time": payment.time,
+ "pending": payment.pending,
+ }
+ for payment in payments
+ ]
+
+
@tpos_ext.post(
"/api/v1/tposs/{tpos_id}/invoices/{payment_request}/pay", status_code=HTTPStatus.OK
)
diff --git a/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html b/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html
index c65ad1c4..0df5bebf 100644
--- a/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html
+++ b/lnbits/extensions/watchonly/static/components/fee-rate/fee-rate.html
@@ -6,6 +6,7 @@
filled
dense
v-model.number="feeRate"
+ step="any"
:rules="[val => !!val || 'Field is required']"
type="number"
label="sats/vbyte"
diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py
index 660e5b7d..5737e54f 100644
--- a/lnbits/extensions/withdraw/lnurl.py
+++ b/lnbits/extensions/withdraw/lnurl.py
@@ -9,7 +9,7 @@ from fastapi import HTTPException
from fastapi.param_functions import Query
from loguru import logger
from starlette.requests import Request
-from starlette.responses import HTMLResponse # type: ignore
+from starlette.responses import HTMLResponse
from lnbits.core.services import pay_invoice
@@ -51,10 +51,24 @@ async def api_lnurl_response(request: Request, unique_hash):
# CALLBACK
-@withdraw_ext.get("/api/v1/lnurl/cb/{unique_hash}", name="withdraw.api_lnurl_callback")
+@withdraw_ext.get(
+ "/api/v1/lnurl/cb/{unique_hash}",
+ name="withdraw.api_lnurl_callback",
+ summary="lnurl withdraw callback",
+ description="""
+ This enpoints allows you to put unique_hash, k1
+ and a payment_request to get your payment_request paid.
+ """,
+ response_description="JSON with status",
+ responses={
+ 200: {"description": "status: OK"},
+ 400: {"description": "k1 is wrong or link open time or withdraw not working."},
+ 404: {"description": "withdraw link not found."},
+ 405: {"description": "withdraw link is spent."},
+ },
+)
async def api_lnurl_callback(
unique_hash,
- request: Request,
k1: str = Query(...),
pr: str = Query(...),
id_unique_hash=None,
@@ -63,19 +77,22 @@ async def api_lnurl_callback(
now = int(datetime.now().timestamp())
if not link:
raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found"
+ status_code=HTTPStatus.NOT_FOUND, detail="withdraw not found."
)
if link.is_spent:
raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Withdraw is spent."
+ status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail="withdraw is spent."
)
if link.k1 != k1:
- raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Bad request.")
+ raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="k1 is wrong.")
if now < link.open_time:
- return {"status": "ERROR", "reason": f"Wait {link.open_time - now} seconds."}
+ raise HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST,
+ detail=f"wait link open_time {link.open_time - now} seconds.",
+ )
usescsv = ""
@@ -95,7 +112,7 @@ async def api_lnurl_callback(
usescsv = ",".join(useslist)
if not found:
raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="LNURL-withdraw not found."
+ status_code=HTTPStatus.NOT_FOUND, detail="withdraw not found."
)
else:
usescsv = usescsv[1:]
@@ -144,7 +161,9 @@ async def api_lnurl_callback(
except Exception as e:
await update_withdraw_link(link.id, **changesback)
logger.error(traceback.format_exc())
- return {"status": "ERROR", "reason": "Link not working"}
+ raise HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST, detail=f"withdraw not working. {str(e)}"
+ )
# FOR LNURLs WHICH ARE UNIQUE
diff --git a/lnbits/extensions/withdraw/static/js/index.js b/lnbits/extensions/withdraw/static/js/index.js
index 943e9024..a3eaa593 100644
--- a/lnbits/extensions/withdraw/static/js/index.js
+++ b/lnbits/extensions/withdraw/static/js/index.js
@@ -290,8 +290,12 @@ new Vue({
})
}
},
- exportCSV: function () {
- LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls)
+ exportCSV() {
+ LNbits.utils.exportCSV(
+ this.withdrawLinksTable.columns,
+ this.withdrawLinks,
+ 'withdraw-links'
+ )
}
},
created: function () {
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index e213240c..83876160 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -163,6 +163,7 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates:
)
if settings.LNBITS_AD_SPACE:
+ t.env.globals["AD_TITLE"] = settings.LNBITS_AD_SPACE_TITLE
t.env.globals["AD_SPACE"] = settings.LNBITS_AD_SPACE
t.env.globals["HIDE_API"] = settings.LNBITS_HIDE_API
t.env.globals["SITE_TITLE"] = settings.LNBITS_SITE_TITLE
diff --git a/lnbits/server.py b/lnbits/server.py
index e9849851..7aaaa964 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -1,9 +1,7 @@
-import time
-
import click
import uvicorn
-from lnbits.settings import HOST, PORT
+from lnbits.settings import FORWARDED_ALLOW_IPS, HOST, PORT
@click.command(
@@ -14,10 +12,20 @@ from lnbits.settings import HOST, PORT
)
@click.option("--port", default=PORT, help="Port to listen on")
@click.option("--host", default=HOST, help="Host to run LNBits on")
+@click.option(
+ "--forwarded-allow-ips", default=FORWARDED_ALLOW_IPS, help="Allowed proxy servers"
+)
@click.option("--ssl-keyfile", default=None, help="Path to SSL keyfile")
@click.option("--ssl-certfile", default=None, help="Path to SSL certificate")
@click.pass_context
-def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str):
+def main(
+ ctx,
+ port: int,
+ host: str,
+ forwarded_allow_ips: str,
+ ssl_keyfile: str,
+ ssl_certfile: str,
+):
"""Launched with `poetry run lnbits` at root level"""
# this beautiful beast parses all command line arguments and passes them to the uvicorn server
d = dict()
@@ -37,6 +45,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str):
"lnbits.__main__:app",
port=port,
host=host,
+ forwarded_allow_ips=forwarded_allow_ips,
ssl_keyfile=ssl_keyfile,
ssl_certfile=ssl_certfile,
**d
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 3f4e31cc..17fce293 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -18,6 +18,8 @@ DEBUG = env.bool("DEBUG", default=False)
HOST = env.str("HOST", default="127.0.0.1")
PORT = env.int("PORT", default=5000)
+FORWARDED_ALLOW_IPS = env.str("FORWARDED_ALLOW_IPS", default="127.0.0.1")
+
LNBITS_PATH = path.dirname(path.realpath(__file__))
LNBITS_DATA_FOLDER = env.str(
"LNBITS_DATA_FOLDER", default=path.join(LNBITS_PATH, "data")
@@ -38,6 +40,9 @@ LNBITS_DISABLED_EXTENSIONS: List[str] = [
for x in env.list("LNBITS_DISABLED_EXTENSIONS", default=[], subcast=str)
]
+LNBITS_AD_SPACE_TITLE = env.str(
+ "LNBITS_AD_SPACE_TITLE", default="Optional Advert Space"
+)
LNBITS_AD_SPACE = [x.strip(" ") for x in env.list("LNBITS_AD_SPACE", default=[])]
LNBITS_HIDE_API = env.bool("LNBITS_HIDE_API", default=False)
LNBITS_SITE_TITLE = env.str("LNBITS_SITE_TITLE", default="LNbits")
diff --git a/lnbits/static/images/lnbits-shop-dark.png b/lnbits/static/images/lnbits-shop-dark.png
new file mode 100644
index 00000000..3dd677dc
Binary files /dev/null and b/lnbits/static/images/lnbits-shop-dark.png differ
diff --git a/lnbits/static/images/lnbits-shop-light.png b/lnbits/static/images/lnbits-shop-light.png
new file mode 100644
index 00000000..96607cb4
Binary files /dev/null and b/lnbits/static/images/lnbits-shop-light.png differ
diff --git a/lnbits/tasks.py b/lnbits/tasks.py
index 94e43dcf..de3c69aa 100644
--- a/lnbits/tasks.py
+++ b/lnbits/tasks.py
@@ -124,7 +124,7 @@ async def check_pending_payments():
while True:
async with db.connect() as conn:
- logger.debug(
+ logger.info(
f"Task: checking all pending payments (incoming={incoming}, outgoing={outgoing}) of last 15 days"
)
start_time: float = time.time()
@@ -140,15 +140,15 @@ async def check_pending_payments():
for payment in pending_payments:
await payment.check_status(conn=conn)
- logger.debug(
+ logger.info(
f"Task: pending check finished for {len(pending_payments)} payments (took {time.time() - start_time:0.3f} s)"
)
# we delete expired invoices once upon the first pending check
if incoming:
- logger.debug("Task: deleting all expired invoices")
+ logger.info("Task: deleting all expired invoices")
start_time: float = time.time()
await delete_expired_invoices(conn=conn)
- logger.debug(
+ logger.info(
f"Task: expired invoice deletion finished (took {time.time() - start_time:0.3f} s)"
)
diff --git a/lnbits/templates/base.html b/lnbits/templates/base.html
index 67241bb5..ef270371 100644
--- a/lnbits/templates/base.html
+++ b/lnbits/templates/base.html
@@ -199,6 +199,18 @@
>
+
+ API DOCS
+ View LNbits Swagger API docs
+