diff --git a/lnbits/core/services.py b/lnbits/core/services.py index d623b1183..73bfa0610 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -27,8 +27,6 @@ from .crud import ( update_payment_status, get_wallet_payment, ) - - async def create_invoice( *, wallet_id: str, diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py index bb9b0203a..56c22b31a 100644 --- a/lnbits/extensions/satspay/crud.py +++ b/lnbits/extensions/satspay/crud.py @@ -8,34 +8,56 @@ from lnbits.helpers import urlsafe_short_hash from quart import jsonify import httpx - +from lnbits.core.services import create_invoice, check_invoice_status +from ..watchonly.crud import get_watch_wallet, get_derive_address ###############CHARGES########################## -async def create_charge(walletid: str, user: str, title: Optional[str] = None, time: Optional[int] = None, amount: Optional[int] = None) -> Charges: - wallet = await get_watch_wallet(walletid) - address = await get_derive_address(walletid, wallet[4] + 1) - +async def create_charge(user: str, description: Optional[str] = None, onchainwallet: Optional[str] = None, lnbitswallet: Optional[str] = None, webhook: Optional[str] = None, time: Optional[int] = None, amount: Optional[int] = None) -> Charges: charge_id = urlsafe_short_hash() + if onchainwallet: + wallet = await get_watch_wallet(onchainwallet) + onchainaddress = await get_derive_address(onchainwallet, wallet[4] + 1) + else: + onchainaddress = None + if lnbitswallet: + payment_hash, payment_request = await create_invoice( + wallet_id=lnbitswallet, + amount=amount, + memo=charge_id) + else: + payment_hash = None + payment_request = None await db.execute( """ INSERT INTO charges ( id, user, - title, - wallet, - address, - time_to_pay, + description, + onchainwallet, + onchainaddress, + lnbitswallet, + payment_request, + payment_hash, + webhook, + time, amount, - balance + balance, + paid ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, - (charge_id, user, title, walletid, address, time, amount, 0), + (charge_id, user, description, onchainwallet, onchainaddress, lnbitswallet, payment_request, payment_hash, webhook, time, amount, 0, False), ) return await get_charge(charge_id) +async def update_charge(charge_id: str, **kwargs) -> Optional[Charges]: + q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) + await db.execute(f"UPDATE charges SET {q} WHERE id = ?", (*kwargs.values(), wallet_id)) + row = await db.fetchone("SELECT * FROM charges WHERE id = ?", (wallet_id,)) + return Charges.from_row(row) if row else None + async def get_charge(charge_id: str) -> Charges: row = await db.fetchone("SELECT * FROM charges WHERE id = ?", (charge_id,)) @@ -45,7 +67,7 @@ async def get_charge(charge_id: str) -> Charges: async def get_charges(user: str) -> List[Charges]: rows = await db.fetchall("SELECT * FROM charges WHERE user = ?", (user,)) for row in rows: - await check_address_balance(row.address) + await check_address_balance(row.id) rows = await db.fetchall("SELECT * FROM charges WHERE user = ?", (user,)) return [charges.from_row(row) for row in rows] @@ -53,15 +75,23 @@ async def get_charges(user: str) -> List[Charges]: async def delete_charge(charge_id: str) -> None: await db.execute("DELETE FROM charges WHERE id = ?", (charge_id,)) -async def check_address_balance(address: str) -> List[Charges]: - address_data = await get_address(address) - mempool = await get_mempool(address_data.user) - - try: - async with httpx.AsyncClient() as client: - r = await client.get(mempool.endpoint + "/api/address/" + address) - except Exception: - pass - - amount_paid = r.json()['chain_stats']['funded_txo_sum'] - r.json()['chain_stats']['spent_txo_sum'] - print(amount_paid) +async def check_address_balance(charge_id: str) -> List[Charges]: + charge = await get_charge(charge_id) + if charge.onchainaddress: + mempool = await get_mempool(charge.user) + try: + async with httpx.AsyncClient() as client: + r = await client.get(mempool.endpoint + "/api/address/" + charge.onchainaddress) + respAmount = r.json()['chain_stats']['funded_txo_sum'] + if (charge.balance + respAmount) >= charge.balance: + return await update_charge(charge_id = charge_id, balance = (charge.balance + respAmount), paid = True) + else: + return await update_charge(charge_id = charge_id, balance = (charge.balance + respAmount), paid = False) + except Exception: + pass + if charge.lnbitswallet: + invoice_status = await check_invoice_status(charge.lnbitswallet, charge.payment_hash) + if invoice_status.paid: + return await update_charge(charge_id = charge_id, balance = charge.balance, paid = True) + row = await db.fetchone("SELECT * FROM charges WHERE id = ?", (charge_id,)) + return Charges.from_row(row) if row else None diff --git a/lnbits/extensions/satspay/migrations.py b/lnbits/extensions/satspay/migrations.py index ff0bf81be..fddcb5867 100644 --- a/lnbits/extensions/satspay/migrations.py +++ b/lnbits/extensions/satspay/migrations.py @@ -9,13 +9,19 @@ async def m001_initial(db): CREATE TABLE IF NOT EXISTS charges ( id TEXT NOT NULL PRIMARY KEY, user TEXT, - title TEXT, - wallet TEXT NOT NULL, - address TEXT NOT NULL, - time_to_pay INTEGER, + description TEXT, + onchainwallet TEXT, + onchainaddress TEXT, + lnbitswallet TEXT, + lnbitskey TEXT, + payment_request TEXT, + payment_hash TEXT, + webhook TEXT, + time INTEGER, amount INTEGER, balance INTEGER DEFAULT 0, - time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now')) + paid BOOLEAN, + timestamp TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now')) ); """ ) \ No newline at end of file diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py index ea1afe427..013dc1d7f 100644 --- a/lnbits/extensions/satspay/models.py +++ b/lnbits/extensions/satspay/models.py @@ -4,13 +4,18 @@ from typing import NamedTuple class Charges(NamedTuple): id: str user: str - wallet: str - title: str - address: str - time_to_pay: str + description: str + onchainwallet: str + onchainaddress: str + lnbitswallet: str + payment_request: str + payment_hash: str + webhook: str + time: str amount: int balance: int - time: int + paid: bool + timestamp: int @classmethod def from_row(cls, row: Row) -> "Payments": diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html index 7eab8bf1f..31879c55c 100644 --- a/lnbits/extensions/satspay/templates/satspay/index.html +++ b/lnbits/extensions/satspay/templates/satspay/index.html @@ -215,7 +215,7 @@
- +
@@ -223,7 +223,7 @@ filled dense emit-value - v-model="formDialog.data.wallet" + v-model="formDialogCharge.data.lnbitswallet" :options="g.user.walletOptions" label="Wallet *" > @@ -442,10 +442,11 @@ LNbits.api .request( 'GET', - '/satspay/api/v1/ChargeLinks', + '/satspay/api/v1/charges', this.g.user.wallets[0].inkey ) .then(function (response) { + console.log(response.data) var i var now = parseInt(new Date() / 1000) for (i = 0; i < response.data.length; i++) { @@ -468,8 +469,9 @@ }, sendFormDataCharge: function () { var self = this - var wallet = self.g.user.wallets[0] + var wallet = self.g.user.wallets[0].inkey var data = self.formDialogCharge.data + console.log(data) data.amount = parseInt(data.amount) data.time = parseInt(data.time) if (data.id) { @@ -501,10 +503,11 @@ var self = this LNbits.api - .request('POST', '/satspay/api/v1/Charge', wallet.inkey, data) + .request('POST', '/satspay/api/v1/charge', wallet, data) .then(function (response) { self.ChargeLinks.push(mapCharge(response.data)) self.formDialogCharge.show = false + self.formDialogCharge.data = null }) .catch(function (error) { LNbits.utils.notifyApiError(error) diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py index 2617cc56c..c12ec2e93 100644 --- a/lnbits/extensions/satspay/views_api.py +++ b/lnbits/extensions/satspay/views_api.py @@ -81,6 +81,7 @@ async def api_wallet_delete(wallet_id): async def api_charges_retrieve(): charges = await get_charges(g.wallet.user) + print(charges) if not charges: return ( jsonify(""), @@ -106,8 +107,10 @@ async def api_charge_retrieve(charge_id): @api_check_wallet_key("invoice") @api_validate_post_request( schema={ - "walletid": {"type": "string", "empty": False, "required": True}, - "title": {"type": "string", "empty": False, "required": True}, + "onchainwallet": {"type": "string", "empty": False, "required": True}, + "lnbitswallet": {"type": "string", "empty": False, "required": True}, + "description": {"type": "string", "empty": False, "required": True}, + "webhook": {"type": "string", "empty": False, "required": True}, "time": {"type": "integer", "min": 1, "required": True}, "amount": {"type": "integer", "min": 1, "required": True}, } @@ -117,7 +120,6 @@ async def api_charge_create_or_update(charge_id=None): if not charge_id: charge = await create_charge(user = g.wallet.user, **g.data) return jsonify(charge), HTTPStatus.CREATED - else: charge = await update_charge(user = g.wallet.user, **g.data) return jsonify(charge), HTTPStatus.OK diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index ceff089f0..86e63a8c5 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -8,11 +8,12 @@ class Wallets(NamedTuple): title: str address_no: int balance: int - + @classmethod def from_row(cls, row: Row) -> "Wallets": return cls(**dict(row)) + class Mempool(NamedTuple): user: str endpoint: str diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index d46b06d2c..8b241d5aa 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -285,6 +285,22 @@ ) return obj } + var mapAddresses = function (obj) { + obj._data = _.clone(obj) + obj.date = Quasar.utils.date.formatDate( + new Date(obj.time * 1000), + 'YYYY-MM-DD HH:mm' + ) + return obj + } + var mapTxs = function (obj) { + obj._data = _.clone(obj) + obj.date = Quasar.utils.date.formatDate( + new Date(obj.time * 1000), + 'YYYY-MM-DD HH:mm' + ) + return obj + } var mapCharge = function (obj) { obj._data = _.clone(obj) obj.date = Quasar.utils.date.formatDate( @@ -304,6 +320,8 @@ balance: null, checker: null, walletLinks: [], + AddressesLinks: [], + txsLinks: [], ChargeLinks: [], currentaddress: "", Addresses: { @@ -322,13 +340,6 @@ label: 'Title', field: 'title' }, - - { - name: 'amount', - align: 'left', - label: 'Amount', - field: 'amount' - }, { name: 'masterpub', align: 'left', @@ -397,26 +408,64 @@ }, methods: { + getAddressDetails: function (address){ + LNbits.api + .request( + 'GET', + '/watchonly/api/v1/mempool/' + address, + this.g.user.wallets[0].inkey + ) + .then(function (response) { + + return reponse.data + }) + .catch(function (error) { + + LNbits.utils.notifyApiError(error) + }) + }, + getAddressTxs: function (address){ + LNbits.api + .request( + 'GET', + '/watchonly/api/v1/mempool/txs/' + address, + this.g.user.wallets[0].inkey + ) + .then(function (response) { + + self.txsLinks = response.data.map(function (obj) { + console.log(obj) + return mapTxs(obj) + + }) + }) + .catch(function (error) { + + LNbits.utils.notifyApiError(error) + }) + }, getAddresses: function (walletID) { var self = this - LNbits.api - .request( - 'GET', - '/watchonly/api/v1/addresses/' + walletID, - this.g.user.wallets[0].inkey - ) - .then(function (response) { + .request( + 'GET', + '/watchonly/api/v1/addresses/' + walletID, + this.g.user.wallets[0].inkey + ) + .then(function (response) { + self.Addresses.data = response.data + self.currentaddress = self.Addresses.data[self.Addresses.data.length - 1].address + self.AddressesLinks = response.data.map(function (obj) { + console.log(obj) + + return mapAddresses(obj) - self.Addresses.data = response.data - self.currentaddress = self.Addresses.data[self.Addresses.data.length - 1].address - console.log(self.currentaddress) - - }) - - .catch(function (error) { - LNbits.utils.notifyApiError(error) - }) + }) + }) + .catch(function (error) { + + LNbits.utils.notifyApiError(error) + }) }, getFreshAddress: function (walletID) { var self = this @@ -428,16 +477,11 @@ this.g.user.wallets[0].inkey ) .then(function (response) { - console.log(response.data) self.Addresses.data = response.data self.currentaddress = self.Addresses.data[self.Addresses.data.length - 1].address - }) - }, - - - getMempool: function () { + getMempool: function () { var self = this LNbits.api @@ -447,9 +491,7 @@ this.g.user.wallets[0].inkey ) .then(function (response) { - console.log(response.data.endpoint) self.mempool.endpoint = response.data.endpoint - console.log(this.mempool.endpoint) }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -482,15 +524,19 @@ this.g.user.wallets[0].inkey ) .then(function (response) { + console.log(response) + self.walletLinks = response.data.map(function (obj) { - - return mapWalletLink(obj) + self.getAddresses(obj.id) + return mapWalletLink(obj) }) }) .catch(function (error) { LNbits.utils.notifyApiError(error) }) }, + + closeFormDialog: function () { this.formDialog.data = { is_unique: false @@ -547,7 +593,6 @@ .then(function (response) { self.walletLinks.push(mapWalletLink(response.data)) self.formDialog.show = false - console.log(response.data[1][1]) }) .catch(function (error) { LNbits.utils.notifyApiError(error) @@ -556,7 +601,6 @@ deleteWalletLink: function (linkId) { var self = this var link = _.findWhere(this.walletLinks, {id: linkId}) - console.log(self.g.user.wallets[0].adminkey) LNbits.utils .confirmDialog('Are you sure you want to delete this pay link?') .onOk(function () { @@ -582,13 +626,13 @@ }, created: function () { if (this.g.user.wallets.length) { - var getWalletLinks = this.getWalletLinks - getWalletLinks() var getMempool = this.getMempool getMempool() - + var getWalletLinks = this.getWalletLinks + getWalletLinks() + + } - } }) diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 032498583..549e24d0b 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -1,8 +1,7 @@ import hashlib -from quart import g, jsonify, url_for +from quart import g, jsonify, url_for, request from http import HTTPStatus -import httpx - +import httpx, json from lnbits.core.crud import get_user from lnbits.decorators import api_check_wallet_key, api_validate_post_request @@ -38,6 +37,7 @@ async def api_wallets_retrieve(): @api_check_wallet_key("invoice") async def api_wallet_retrieve(wallet_id): wallet = await get_watch_wallet(wallet_id) + addresses = await api_get_addresses(wallet_id) if not wallet: return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND @@ -130,4 +130,63 @@ async def api_get_mempool(): mempool = await get_mempool(g.wallet.user) if not mempool: mempool = await create_mempool(user=g.wallet.user) - return jsonify(mempool._asdict()), HTTPStatus.OK \ No newline at end of file + return jsonify(mempool._asdict()), HTTPStatus.OK + +@watchonly_ext.route("/api/v1/mempool/
", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_get_mempool_wallet_balance(address): + mempool = await get_mempool(g.wallet.user) + if not mempool: + mempool = await create_mempool(user=g.wallet.user) + url = ( + mempool.endpoint + + "/api/address/" + + address + ) + header = { + "Content-Type": "application/json", + } + async with httpx.AsyncClient() as client: + try: + r = await client.get( + url, + headers=header, + timeout=40, + ) + mp_response = json.loads(r.text) + print(mp_response) + except AssertionError: + mp_response = "Error occured" + return jsonify(mp_response), HTTPStatus.OK + +@watchonly_ext.route("/api/v1/mempool/txs/
", methods=["GET"]) +@api_check_wallet_key("invoice") +async def api_get_mempool_wallet_txs(address): + mempool = await get_mempool(g.wallet.user) + if not mempool: + mempool = await create_mempool(user=g.wallet.user) + url = ( + mempool.endpoint + + "/api/address/" + + address + + "/txs" + ) + header = { + "Content-Type": "application/json", + } + async with httpx.AsyncClient() as client: + try: + r = await client.get( + url, + headers=header, + timeout=40, + ) + mp_response = json.loads(r.text) + print(mp_response) + except AssertionError: + mp_response = "Error occured" + return jsonify(mp_response), HTTPStatus.OK + + + + \ No newline at end of file