feat: store onchain charge config with the charge

This commit is contained in:
Vlad Stan 2022-11-25 10:48:57 +02:00
parent 4e984eae56
commit 5440d4ab19
9 changed files with 73 additions and 47 deletions

View File

@ -1,6 +1,8 @@
import json
from typing import List, Optional
import httpx
from loguru import logger
from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment
@ -18,6 +20,10 @@ from .models import Charges, CreateCharge
async def create_charge(user: str, data: CreateCharge) -> Charges:
charge_id = urlsafe_short_hash()
if data.onchainwallet:
config = await get_config(user)
data.extra = json.dumps(
{"mempool_endpoint": config.mempool_endpoint, "network": config.network}
)
onchain = await get_fresh_address(data.onchainwallet)
onchainaddress = onchain.address
else:
@ -48,9 +54,10 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
completelinktext,
time,
amount,
balance
balance,
extra
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
charge_id,
@ -67,6 +74,7 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
data.time,
data.amount,
0,
data.extra,
),
)
return await get_charge(charge_id)
@ -100,31 +108,27 @@ async def delete_charge(charge_id: str) -> None:
async def check_address_balance(charge_id: str) -> Optional[Charges]:
charge = await get_charge(charge_id)
if not charge.paid:
if charge.onchainaddress:
config = await get_charge_config(charge_id)
endpoint = (
f"{charge.config['mempool_endpoint']}/testnet"
if charge.config["network"] == "Testnet"
else charge.config["mempool_endpoint"]
)
try:
async with httpx.AsyncClient() as client:
r = await client.get(
config.mempool_endpoint
+ "/api/address/"
+ charge.onchainaddress
endpoint + "/api/address/" + charge.onchainaddress
)
respAmount = r.json()["chain_stats"]["funded_txo_sum"]
if respAmount > charge.balance:
await update_charge(charge_id=charge_id, balance=respAmount)
except Exception:
pass
except Exception as e:
logger.warning(e)
if charge.lnbitswallet:
invoice_status = await api_payment(charge.payment_hash)
if invoice_status["paid"]:
return await update_charge(charge_id=charge_id, balance=charge.amount)
return await get_charge(charge_id)
async def get_charge_config(charge_id: str):
row = await db.fetchone(
"""SELECT "user" FROM satspay.charges WHERE id = ?""", (charge_id,)
)
return await get_config(row.user)

View File

@ -1,8 +1,8 @@
from .models import Charges
def compact_charge(charge: Charges):
return {
def public_charge(charge: Charges):
c = {
"id": charge.id,
"description": charge.description,
"onchainaddress": charge.onchainaddress,
@ -13,5 +13,12 @@ def compact_charge(charge: Charges):
"balance": charge.balance,
"paid": charge.paid,
"timestamp": charge.timestamp,
"completelink": charge.completelink, # should be secret?
"time_elapsed": charge.time_elapsed,
"time_left": charge.time_left,
"paid": charge.paid,
}
if charge.paid:
c["completelink"] = charge.completelink
return c

View File

@ -26,3 +26,14 @@ async def m001_initial(db):
);
"""
)
async def m002_add_charge_extra_data(db):
"""
Add 'exta' for storing various config about the charge
"""
await db.execute(
"""ALTER TABLE satspay.charges
ADD COLUMN extra TEXT DEFAULT '{"mempool_endpoint": "https://mempool.space", "network": "Mainnet"}';
"""
)

View File

@ -1,3 +1,4 @@
import json
from datetime import datetime, timedelta
from sqlite3 import Row
from typing import Optional
@ -15,6 +16,7 @@ class CreateCharge(BaseModel):
completelinktext: str = Query(None)
time: int = Query(..., ge=1)
amount: int = Query(..., ge=1)
extra: str = "{}"
class Charges(BaseModel):
@ -28,6 +30,7 @@ class Charges(BaseModel):
webhook: Optional[str]
completelink: Optional[str]
completelinktext: Optional[str] = "Back to Merchant"
extra: str = "{}"
time: int
amount: int
balance: int
@ -54,3 +57,7 @@ class Charges(BaseModel):
return True
else:
return False
@property
def config(self):
return json.loads(self.extra)

View File

@ -8,7 +8,7 @@ from lnbits.extensions.satspay.crud import check_address_balance, get_charge
from lnbits.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener
from .helpers import compact_charge
from .helpers import public_charge
from .models import Charges
@ -43,7 +43,7 @@ async def call_webhook(charge: Charges):
try:
r = await client.post(
charge.webhook,
json=compact_charge(charge),
json=public_charge(charge),
timeout=40,
)
except AssertionError:

View File

@ -109,7 +109,7 @@
<q-btn
flat
disable
v-if="!charge.lnbitswallet || charge.time_elapsed"
v-if="!charge.payment_request || charge.time_elapsed"
style="color: primary; width: 100%"
label="lightning⚡"
>
@ -131,7 +131,7 @@
<q-btn
flat
disable
v-if="!charge.onchainwallet || charge.time_elapsed"
v-if="!charge.onchainaddress || charge.time_elapsed"
style="color: primary; width: 100%"
label="onchain⛓"
>
@ -222,7 +222,7 @@
<div class="col text-center">
<a
style="color: unset"
:href="mempoolEndpoint + '/address/' + charge.onchainaddress"
:href="'https://' + mempoolHostname + '/address/' + charge.onchainaddress"
target="_blank"
><span
class="text-subtitle1"
@ -336,7 +336,7 @@
},
methods: {
checkBalances: async function () {
if (!this.charge.lnbitswallet && this.charge.hasOnchainStaleBalance)
if (!this.charge.payment_request && this.charge.hasOnchainStaleBalance)
return
try {
const {data} = await LNbits.api.request(
@ -438,7 +438,7 @@
}
},
created: async function () {
if (this.charge.lnbitswallet) this.payInvoice()
if (this.charge.payment_request) this.payInvoice()
else this.payOnchain()
await this.checkBalances()

View File

@ -412,7 +412,8 @@
onchainwallet: null,
rescanning: false,
mempool: {
endpoint: ''
endpoint: '',
network: 'Mainnet'
},
chargesTable: {
@ -519,7 +520,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,7 +539,9 @@
'/watchonly/api/v1/config',
this.g.user.wallets[0].inkey
)
console.log('### data', data)
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) {
@ -697,8 +700,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)

View File

@ -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")
@ -31,16 +31,15 @@ async def display(request: Request, charge_id: str):
status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
)
onchainwallet_config = await get_charge_config(charge_id)
if onchainwallet_config:
mempool_endpoint = onchainwallet_config.mempool_endpoint
network = onchainwallet_config.network
view_data = {
"request": request,
"charge_data": public_charge(charge),
}
if "mempool_endpoint" in charge.config:
view_data["mempool_endpoint"] = charge.config["mempool_endpoint"]
view_data["network"] = charge.config["network"]
return satspay_renderer().TemplateResponse(
"satspay/display.html",
{
"request": request,
"charge_data": charge.dict(),
"mempool_endpoint": mempool_endpoint,
"network": network,
},
view_data,
)

View File

@ -19,7 +19,7 @@ from .crud import (
get_charges,
update_charge,
)
from .helpers import compact_charge
from .helpers import public_charge
from .models import CreateCharge
#############################CHARGES##########################
@ -118,9 +118,4 @@ async def api_charge_balance(charge_id):
status_code=HTTPStatus.NOT_FOUND, detail="Charge does not exist."
)
return {
**compact_charge(charge),
**{"time_elapsed": charge.time_elapsed},
**{"time_left": charge.time_left},
**{"paid": charge.paid},
}
return {**public_charge(charge)}