feat: store onchain charge config with the charge

This commit is contained in:
Vlad Stan 2022-11-25 10:48:57 +02:00 committed by dni ⚡
parent bb16870c66
commit 871d716539
9 changed files with 73 additions and 47 deletions

View file

@ -1,6 +1,8 @@
import json
from typing import List, Optional from typing import List, Optional
import httpx import httpx
from loguru import logger
from lnbits.core.services import create_invoice from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment 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: async def create_charge(user: str, data: CreateCharge) -> Charges:
charge_id = urlsafe_short_hash() charge_id = urlsafe_short_hash()
if data.onchainwallet: 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) onchain = await get_fresh_address(data.onchainwallet)
onchainaddress = onchain.address onchainaddress = onchain.address
else: else:
@ -48,9 +54,10 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
completelinktext, completelinktext,
time, time,
amount, amount,
balance balance,
extra
) )
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", """,
( (
charge_id, charge_id,
@ -67,6 +74,7 @@ async def create_charge(user: str, data: CreateCharge) -> Charges:
data.time, data.time,
data.amount, data.amount,
0, 0,
data.extra,
), ),
) )
return await get_charge(charge_id) 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]: async def check_address_balance(charge_id: str) -> Optional[Charges]:
charge = await get_charge(charge_id) charge = await get_charge(charge_id)
if not charge.paid: if not charge.paid:
if charge.onchainaddress: 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: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.get( r = await client.get(
config.mempool_endpoint endpoint + "/api/address/" + charge.onchainaddress
+ "/api/address/"
+ charge.onchainaddress
) )
respAmount = r.json()["chain_stats"]["funded_txo_sum"] respAmount = r.json()["chain_stats"]["funded_txo_sum"]
if respAmount > charge.balance: if respAmount > charge.balance:
await update_charge(charge_id=charge_id, balance=respAmount) await update_charge(charge_id=charge_id, balance=respAmount)
except Exception: except Exception as e:
pass logger.warning(e)
if charge.lnbitswallet: if charge.lnbitswallet:
invoice_status = await api_payment(charge.payment_hash) invoice_status = await api_payment(charge.payment_hash)
if invoice_status["paid"]: if invoice_status["paid"]:
return await update_charge(charge_id=charge_id, balance=charge.amount) return await update_charge(charge_id=charge_id, balance=charge.amount)
return await get_charge(charge_id) 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 from .models import Charges
def compact_charge(charge: Charges): def public_charge(charge: Charges):
return { c = {
"id": charge.id, "id": charge.id,
"description": charge.description, "description": charge.description,
"onchainaddress": charge.onchainaddress, "onchainaddress": charge.onchainaddress,
@ -13,5 +13,12 @@ def compact_charge(charge: Charges):
"balance": charge.balance, "balance": charge.balance,
"paid": charge.paid, "paid": charge.paid,
"timestamp": charge.timestamp, "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 datetime import datetime, timedelta
from sqlite3 import Row from sqlite3 import Row
from typing import Optional from typing import Optional
@ -15,6 +16,7 @@ class CreateCharge(BaseModel):
completelinktext: str = Query(None) completelinktext: str = Query(None)
time: int = Query(..., ge=1) time: int = Query(..., ge=1)
amount: int = Query(..., ge=1) amount: int = Query(..., ge=1)
extra: str = "{}"
class Charges(BaseModel): class Charges(BaseModel):
@ -28,6 +30,7 @@ class Charges(BaseModel):
webhook: Optional[str] webhook: Optional[str]
completelink: Optional[str] completelink: Optional[str]
completelinktext: Optional[str] = "Back to Merchant" completelinktext: Optional[str] = "Back to Merchant"
extra: str = "{}"
time: int time: int
amount: int amount: int
balance: int balance: int
@ -54,3 +57,7 @@ class Charges(BaseModel):
return True return True
else: else:
return False 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.helpers import get_current_extension_name
from lnbits.tasks import register_invoice_listener from lnbits.tasks import register_invoice_listener
from .helpers import compact_charge from .helpers import public_charge
from .models import Charges from .models import Charges
@ -43,7 +43,7 @@ async def call_webhook(charge: Charges):
try: try:
r = await client.post( r = await client.post(
charge.webhook, charge.webhook,
json=compact_charge(charge), json=public_charge(charge),
timeout=40, timeout=40,
) )
except AssertionError: except AssertionError:

View file

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

View file

@ -412,7 +412,8 @@
onchainwallet: null, onchainwallet: null,
rescanning: false, rescanning: false,
mempool: { mempool: {
endpoint: '' endpoint: '',
network: 'Mainnet'
}, },
chargesTable: { chargesTable: {
@ -519,7 +520,7 @@
try { try {
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(
'GET', 'GET',
'/watchonly/api/v1/wallet', `/watchonly/api/v1/wallet?network=${this.mempool.network}`,
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
this.walletLinks = data.map(w => ({ this.walletLinks = data.map(w => ({
@ -538,7 +539,9 @@
'/watchonly/api/v1/config', '/watchonly/api/v1/config',
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
console.log('### data', data)
this.mempool.endpoint = data.mempool_endpoint this.mempool.endpoint = data.mempool_endpoint
this.mempool.network = data.network || 'Mainnet'
const url = new URL(this.mempool.endpoint) const url = new URL(this.mempool.endpoint)
this.mempool.hostname = url.hostname this.mempool.hostname = url.hostname
} catch (error) { } catch (error) {
@ -697,8 +700,8 @@
}, },
created: async function () { created: async function () {
await this.getCharges() await this.getCharges()
await this.getWalletLinks()
await this.getWalletConfig() await this.getWalletConfig()
await this.getWalletLinks()
setInterval(() => this.refreshActiveChargesBalance(), 10 * 2000) setInterval(() => this.refreshActiveChargesBalance(), 10 * 2000)
await this.rescanOnchainAddresses() await this.rescanOnchainAddresses()
setInterval(() => this.rescanOnchainAddresses(), 10 * 1000) setInterval(() => this.rescanOnchainAddresses(), 10 * 1000)

View file

@ -6,12 +6,12 @@ from starlette.exceptions import HTTPException
from starlette.requests import Request from starlette.requests import Request
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
from lnbits.core.crud import get_wallet
from lnbits.core.models import User from lnbits.core.models import User
from lnbits.decorators import check_user_exists from lnbits.decorators import check_user_exists
from lnbits.extensions.satspay.helpers import public_charge
from . import satspay_ext, satspay_renderer from . import satspay_ext, satspay_renderer
from .crud import get_charge, get_charge_config from .crud import get_charge
templates = Jinja2Templates(directory="templates") 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." status_code=HTTPStatus.NOT_FOUND, detail="Charge link does not exist."
) )
onchainwallet_config = await get_charge_config(charge_id) view_data = {
if onchainwallet_config: "request": request,
mempool_endpoint = onchainwallet_config.mempool_endpoint "charge_data": public_charge(charge),
network = onchainwallet_config.network }
if "mempool_endpoint" in charge.config:
view_data["mempool_endpoint"] = charge.config["mempool_endpoint"]
view_data["network"] = charge.config["network"]
return satspay_renderer().TemplateResponse( return satspay_renderer().TemplateResponse(
"satspay/display.html", "satspay/display.html",
{ view_data,
"request": request,
"charge_data": charge.dict(),
"mempool_endpoint": mempool_endpoint,
"network": network,
},
) )

View file

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