From 947dc50d2e6598e52e36d86741d6c12a9cd6b67d Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Mon, 12 Sep 2022 17:14:24 +0200
Subject: [PATCH 001/696] readme update
---
lnbits/extensions/boltcards/README.md | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index f9c59409..a7302906 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -10,18 +10,17 @@ This extension allows you to link your Bolt Card (or other compatible NXP NTAG d
## About the keys
-Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In the empty state they all should be set to zeros (00000000000000000000000000000000). For this extension only two keys need to be set:
+Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In the empty state they all should be set to zeros (00000000000000000000000000000000). For this extension only two keys need to be set, but for the security reasons all five keys should be changed from default (empty) state. The keys directly needed by this extension are:
-One for encrypting the card UID and the counter (p parameter), let's called it meta key, key #01 or K1.
+- One for encrypting the card UID and the counter (p parameter), let's called it meta key, key #01 or K1.
-One for calculating CMAC (c parameter), let's called it file key, key #02 or K2.
+- One for calculating CMAC (c parameter), let's called it file key, key #02 or K2.
-The key #00, K0 (also know as auth key) is skipped to be use as authentification key. Is not needed by this extension, but can be filled in order to write the keys in cooperation with bolt-nfc-android-app.
+The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with bolt-nfc-android-app. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually.
***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***
## Setting the card - bolt-nfc-android-app (easy way)
-So far, regarding the keys, the app can only write a new key set on an empty card (with zero keys). **When you write non zero (and 'non debug') keys, they can't be rewrite with this app.** You have to do it on your computer.
- Read the card with the app. Note UID so you can fill it in the extension later.
- Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}`
@@ -35,10 +34,10 @@ So far, regarding the keys, the app can only write a new key set on an empty car
- If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field.
- Advanced Options
- Card Keys (k0, k1, k2) will be automatically generated if not explicitly set.
- - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in debug mode.
- - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead.
+ - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default state.
+ - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead.
- Click CREATE CARD button
-- Click the QR code button next to a card to view its details. You can scan the QR code with the Android app to import the keys.
+- Click the QR code button next to a card to view its details. Backup the keys! You can scan the QR code with the Android app to import the keys.
- Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. You can then paste this into the Android app to import the keys.
- Tap the NFC card to write the keys to the card.
@@ -48,7 +47,7 @@ Follow the guide.
The URI should be `lnurlw://YOUR-DOMAIN.COM/boltcards/api/v1/scan/{YOUR_card_external_id}?p=00000000000000000000000000000000&c=0000000000000000`
-Then fill up the card parameters in the extension. Card Auth key (K0) can be omitted. Initical counter can be 0.
+Then fill up the card parameters in the extension. Card Auth key (K0) can be filled just for the record. Initical counter can be 0.
## Setting the card - android NXP app (hard way)
- If you don't know the card ID, use NXP TagInfo app to find it out.
From e1eb0895890c068a29ee32f4fe7910698522da0e Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Mon, 12 Sep 2022 22:44:22 +0200
Subject: [PATCH 002/696] readme minor changes
---
lnbits/extensions/boltcards/README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index a7302906..8e093f3e 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -34,7 +34,7 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio
- If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field.
- Advanced Options
- Card Keys (k0, k1, k2) will be automatically generated if not explicitly set.
- - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default state.
+ - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state.
- GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead.
- Click CREATE CARD button
- Click the QR code button next to a card to view its details. Backup the keys! You can scan the QR code with the Android app to import the keys.
@@ -47,7 +47,7 @@ Follow the guide.
The URI should be `lnurlw://YOUR-DOMAIN.COM/boltcards/api/v1/scan/{YOUR_card_external_id}?p=00000000000000000000000000000000&c=0000000000000000`
-Then fill up the card parameters in the extension. Card Auth key (K0) can be filled just for the record. Initical counter can be 0.
+Then fill up the card parameters in the extension. Card Auth key (K0) can be filled in the extension just for the record. Initical counter can be 0.
## Setting the card - android NXP app (hard way)
- If you don't know the card ID, use NXP TagInfo app to find it out.
From 3660d96295ea75be08cd1e55b7899da60cd66eb9 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Tue, 20 Sep 2022 15:53:57 +0200
Subject: [PATCH 003/696] rename setting app + delete msg
---
lnbits/extensions/boltcards/README.md | 6 +++---
lnbits/extensions/boltcards/static/js/index.js | 2 +-
lnbits/extensions/boltcards/templates/boltcards/index.html | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index 8e093f3e..c6d12311 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -6,7 +6,7 @@ This extension allows you to link your Bolt Card (or other compatible NXP NTAG d
**Disclaimer:** ***Use this only if you either know what you are doing or are a reckless lightning pioneer. Only you are responsible for all your sats, cards and other devices. Always backup all your card keys!***
-***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNBits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [bolt-nfc-android-app](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp).
+***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNBits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [Boltcard NFC Card Creator](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp).
## About the keys
@@ -16,11 +16,11 @@ Up to five 16-byte keys can be stored on the card, numbered from 00 to 04. In th
- One for calculating CMAC (c parameter), let's called it file key, key #02 or K2.
-The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with bolt-nfc-android-app. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually.
+The key #00, K0 (also know as auth key) is skipped to be used as authentification key. It is not needed by this extension, but should be filled in order to write the keys in cooperation with Boltcard NFC Card Creator. In this case also K3 is set to same value as K1 and K4 as K2, so all keys are changed from default values. Keep that in your mind in case you need to reset the keys manually.
***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***
-## Setting the card - bolt-nfc-android-app (easy way)
+## Setting the card - Boltcard NFC Card Creator (easy way)
- Read the card with the app. Note UID so you can fill it in the extension later.
- Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}`
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index 11df222a..2ecde39d 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -398,7 +398,7 @@ new Vue({
let cards = _.findWhere(this.cards, {id: cardId})
LNbits.utils
- .confirmDialog('Are you sure you want to delete this card')
+ .confirmDialog('Are you sure you want to delete this card? Without access to the card keys you won\'t be able to reset them in the future!')
.onOk(function () {
LNbits.api
.request(
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index 55cc1e5e..3e07024c 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -370,7 +370,7 @@
bolt-nfc-android-app Boltcard NFC Card Creator)
From 632d35682d84c981b926ef47f8e6fc9d274b948e Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 23 Sep 2022 15:17:21 +0200
Subject: [PATCH 004/696] payment notification webhook
---
lnbits/extensions/boltcards/crud.py | 6 +++--
lnbits/extensions/boltcards/lnurl.py | 23 ++++++++++++++++++-
lnbits/extensions/boltcards/migrations.py | 8 +++++++
lnbits/extensions/boltcards/models.py | 2 ++
.../extensions/boltcards/static/js/index.js | 8 +++++--
.../boltcards/templates/boltcards/index.html | 12 +++++++++-
6 files changed, 53 insertions(+), 6 deletions(-)
diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py
index c541346e..39ee3f40 100644
--- a/lnbits/extensions/boltcards/crud.py
+++ b/lnbits/extensions/boltcards/crud.py
@@ -27,9 +27,10 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card:
k0,
k1,
k2,
- otp
+ otp,
+ webhook_url
)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
card_id,
@@ -45,6 +46,7 @@ async def create_card(data: CreateCardData, wallet_id: str) -> Card:
data.k1,
data.k2,
secrets.token_hex(16),
+ data.webhook_url,
),
)
card = await get_card(card_id)
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index 6fb9ad8d..be3e09d8 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -8,6 +8,7 @@ from io import BytesIO
from typing import Optional
from urllib.parse import urlparse
+import httpx
from embit import bech32, compact
from fastapi import Request
from fastapi.param_functions import Query
@@ -119,12 +120,32 @@ async def lnurl_callback(
invoice = bolt11.decode(pr)
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
try:
- await pay_invoice(
+ payment_hash = await pay_invoice(
wallet_id=card.wallet,
payment_request=pr,
max_sat=card.tx_limit,
extra={"tag": "boltcard", "tag": hit.id},
)
+
+ if card.webhook_url:
+ async with httpx.AsyncClient() as client:
+ try:
+ r = await client.post(
+ card.webhook_url,
+ json={
+ "notification": "card_payment",
+ "payment_hash": payment_hash,
+ "payment_request": pr,
+ "card_external_id": card.external_id,
+ "card_name": card.card_name,
+ "amount": int(invoice.amount_msat / 1000),
+ },
+ timeout=40,
+ )
+ except Exception as exc:
+ # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
+ logger.error("Caught exception when dispatching webhook url:", exc)
+
return {"status": "OK"}
except:
return {"status": "ERROR", "reason": f"Payment failed"}
diff --git a/lnbits/extensions/boltcards/migrations.py b/lnbits/extensions/boltcards/migrations.py
index 08126013..25a59fdb 100644
--- a/lnbits/extensions/boltcards/migrations.py
+++ b/lnbits/extensions/boltcards/migrations.py
@@ -58,3 +58,11 @@ async def m001_initial(db):
);
"""
)
+
+
+async def m002_add_webhook(db):
+ await db.execute(
+ """
+ ALTER TABLE boltcards.cards ADD COLUMN webhook_url TEXT NOT NULL DEFAULT '';
+ """
+ )
diff --git a/lnbits/extensions/boltcards/models.py b/lnbits/extensions/boltcards/models.py
index 47ca1df0..8e6f77c9 100644
--- a/lnbits/extensions/boltcards/models.py
+++ b/lnbits/extensions/boltcards/models.py
@@ -30,6 +30,7 @@ class Card(BaseModel):
prev_k1: str
prev_k2: str
otp: str
+ webhook_url: str
time: int
def from_row(cls, row: Row) -> "Card":
@@ -56,6 +57,7 @@ class CreateCardData(BaseModel):
prev_k0: str = Query(ZERO_KEY)
prev_k1: str = Query(ZERO_KEY)
prev_k2: str = Query(ZERO_KEY)
+ webhook_url: str = Query(...)
class Hit(BaseModel):
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index 2ecde39d..e13c14fb 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -23,6 +23,7 @@ new Vue({
cardDialog: {
show: false,
data: {
+ webhook_url: '',
counter: 1,
k0: '',
k1: '',
@@ -270,7 +271,8 @@ new Vue({
k1: card.k1,
k2: card.k2,
k3: card.k1,
- k4: card.k2
+ k4: card.k2,
+ webhook_url: card.webhook_url
}
this.qrCodeDialog.show = true
},
@@ -398,7 +400,9 @@ new Vue({
let cards = _.findWhere(this.cards, {id: cardId})
LNbits.utils
- .confirmDialog('Are you sure you want to delete this card? Without access to the card keys you won\'t be able to reset them in the future!')
+ .confirmDialog(
+ "Are you sure you want to delete this card? Without access to the card keys you won't be able to reset them in the future!"
+ )
.onOk(function () {
LNbits.api
.request(
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index 3e07024c..f795e454 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -283,7 +283,7 @@
v-model="toggleAdvanced"
label="Show advanced options"
>
-
+
Zero if you don't know.
+
+
Lock key: {{ qrCodeDialog.data.k0 }}
Meta key: {{ qrCodeDialog.data.k1 }}
File key: {{ qrCodeDialog.data.k2 }}
+ Notification webhook: {{ qrCodeDialog.data.webhook_url
+ }}
Date: Fri, 23 Sep 2022 16:19:58 +0200
Subject: [PATCH 005/696] Update README.md
Updated for Boltcard NFC Card Creator v0.1.1
---
lnbits/extensions/boltcards/README.md | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index c6d12311..5140ecc2 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -21,10 +21,7 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio
***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***
## Setting the card - Boltcard NFC Card Creator (easy way)
-
-- Read the card with the app. Note UID so you can fill it in the extension later.
-- Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}`
- - `{external_id}` should be replaced with the External ID found in the LNBits dialog.
+Updated for v0.1.1
- Add new card in the extension.
- Set a max sats per transaction. Any transaction greater than this amount will be rejected.
@@ -32,14 +29,16 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio
- Set a card name. This is just for your reference inside LNBits.
- Set the card UID. This is the unique identifier on your NFC card and is 7 bytes.
- If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field.
+ - Otherwise read it with the Android app (Advanced -> Read NFC) and paste it to the field.
- Advanced Options
- Card Keys (k0, k1, k2) will be automatically generated if not explicitly set.
- - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state.
- - GENERATE KEY button fill the keys randomly. If there is "debug" in the card name, a debug set of keys is filled instead.
+ - Set to 16 bytes of 0s (00000000000000000000000000000000) to leave the keys in default (empty) state (this is unsecure).
+ - GENERATE KEY button fill the keys randomly.
- Click CREATE CARD button
-- Click the QR code button next to a card to view its details. Backup the keys! You can scan the QR code with the Android app to import the keys.
-- Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. You can then paste this into the Android app to import the keys.
-- Tap the NFC card to write the keys to the card.
+- Click the QR code button next to a card to view its details. Backup the keys now! They'll be comfortable in your password manager.
+ - Now you can scan the QR code with the Android app (Create Bolt Card -> SCAN QR CODE).
+ - Or you can Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. Then paste it into the Android app (Create Bolt Card -> PASTE AUTH URL).
+- Click WRITE CARD NOW and tap the NFC card to set it up. DO NOT REMOVE THE CARD PREMATURELY!
## Setting the card - computer (hard way)
@@ -69,4 +68,4 @@ Then fill up the card parameters in the extension. Card Auth key (K0) can be fil
- Save & Write
- Scan with compatible Wallet
-This app afaik cannot change the keys. If you cannot change them any other way, leave them empty in the extension dialog and remember you're not secure. Card Auth key (K0) can be omitted anyway. Initical counter can be 0.
+This app afaik cannot change the keys. If you cannot change them any other way, leave them empty in the extension dialog and remember you're not secured. Card Auth key (K0) can be omitted anyway. Initical counter can be 0.
From ea2d66fbd628681618dbd411f544f63ee8692a5f Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 23 Sep 2022 17:11:19 +0200
Subject: [PATCH 006/696] refund notification
---
lnbits/extensions/boltcards/tasks.py | 22 +++++++++++++++++++++-
1 file changed, 21 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/boltcards/tasks.py b/lnbits/extensions/boltcards/tasks.py
index 1b51c98b..27929efc 100644
--- a/lnbits/extensions/boltcards/tasks.py
+++ b/lnbits/extensions/boltcards/tasks.py
@@ -2,12 +2,13 @@ import asyncio
import json
import httpx
+from loguru import logger
from lnbits.core import db as core_db
from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener
-from .crud import create_refund, get_hit
+from .crud import create_refund, get_card, get_hit
async def wait_for_paid_invoices():
@@ -34,6 +35,25 @@ async def on_invoice_paid(payment: Payment) -> None:
)
await mark_webhook_sent(payment, 1)
+ card = await get_card(hit.card_id)
+ if card.webhook_url:
+ async with httpx.AsyncClient() as client:
+ try:
+ r = await client.post(
+ card.webhook_url,
+ json={
+ "notification": "card_refund",
+ "payment_hash": payment.payment_hash,
+ "payment_request": payment.bolt11,
+ "card_external_id": card.external_id,
+ "card_name": card.card_name,
+ "amount": int(payment.amount / 1000),
+ },
+ timeout=40,
+ )
+ except Exception as exc:
+ logger.error("Caught exception when dispatching webhook url:", exc)
+
async def mark_webhook_sent(payment: Payment, status: int) -> None:
payment.extra["wh_status"] = status
From 8b3af03519c06437325e32e78a71febf9f91d3a1 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Sun, 25 Sep 2022 17:01:22 +0200
Subject: [PATCH 007/696] webhook to pay_invoice
---
lnbits/core/services.py | 31 ++++++++++++++++-
lnbits/extensions/boltcards/lnurl.py | 42 ++++++++----------------
lnbits/extensions/boltcards/views_api.py | 5 ---
lnbits/extensions/withdraw/lnurl.py | 30 +++++++----------
4 files changed, 56 insertions(+), 52 deletions(-)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index 10693f4b..aeb4f938 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -2,7 +2,7 @@ import asyncio
import json
from binascii import unhexlify
from io import BytesIO
-from typing import Dict, Optional, Tuple
+from typing import Dict, Optional, Tuple, Union
from urllib.parse import parse_qs, urlparse
import httpx
@@ -102,6 +102,7 @@ async def pay_invoice(
extra: Optional[Dict] = None,
description: str = "",
conn: Optional[Connection] = None,
+ webhook: Optional[Union[str, tuple]] = None,
) -> str:
"""
Pay a Lightning invoice.
@@ -231,6 +232,34 @@ async def pay_invoice(
f"didn't receive checking_id from backend, payment may be stuck in database: {temp_id}"
)
+ if type(webhook) is str:
+ webhook_url = webhook
+ elif type(webhook) is tuple:
+ webhook_url = webhook[0]
+ additionals = webhook[1]
+ else:
+ webhook_url = None
+
+ if webhook_url:
+ async with httpx.AsyncClient() as client:
+ try:
+ json = {
+ "payment_hash": invoice.payment_hash,
+ "payment_request": payment_request,
+ "amount": int(invoice.amount_msat / 1000),
+ }
+ if type(additionals) is dict:
+ json.update(additionals)
+
+ r = await client.post(
+ webhook_url,
+ json=json,
+ timeout=40,
+ )
+ except Exception as exc:
+ # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
+ logger.error("Caught exception when dispatching webhook url:", exc)
+
return invoice.payment_hash
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index be3e09d8..320b1666 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -1,19 +1,12 @@
-import base64
-import hashlib
-import hmac
import json
import secrets
from http import HTTPStatus
-from io import BytesIO
-from typing import Optional
from urllib.parse import urlparse
-import httpx
from embit import bech32, compact
from fastapi import Request
from fastapi.param_functions import Query
from fastapi.params import Depends, Query
-from lnurl import Lnurl, LnurlWithdrawResponse
from lnurl import encode as lnurl_encode # type: ignore
from lnurl.types import LnurlPayMetadata # type: ignore
from loguru import logger
@@ -34,7 +27,6 @@ from .crud import (
get_hit,
get_hits_today,
spend_hit,
- update_card,
update_card_counter,
update_card_otp,
)
@@ -120,32 +112,26 @@ async def lnurl_callback(
invoice = bolt11.decode(pr)
hit = await spend_hit(id=hit.id, amount=int(invoice.amount_msat / 1000))
try:
- payment_hash = await pay_invoice(
+ webhook = (
+ (
+ card.webhook_url,
+ {
+ "notification": "card_payment",
+ "card_external_id": card.external_id,
+ "card_name": card.card_name,
+ },
+ )
+ if card.webhook_url
+ else None
+ )
+ await pay_invoice(
wallet_id=card.wallet,
payment_request=pr,
max_sat=card.tx_limit,
extra={"tag": "boltcard", "tag": hit.id},
+ webhook=webhook,
)
- if card.webhook_url:
- async with httpx.AsyncClient() as client:
- try:
- r = await client.post(
- card.webhook_url,
- json={
- "notification": "card_payment",
- "payment_hash": payment_hash,
- "payment_request": pr,
- "card_external_id": card.external_id,
- "card_name": card.card_name,
- "amount": int(invoice.amount_msat / 1000),
- },
- timeout=40,
- )
- except Exception as exc:
- # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
- logger.error("Caught exception when dispatching webhook url:", exc)
-
return {"status": "OK"}
except:
return {"status": "ERROR", "reason": f"Payment failed"}
diff --git a/lnbits/extensions/boltcards/views_api.py b/lnbits/extensions/boltcards/views_api.py
index 7b8357cf..2fc11dbc 100644
--- a/lnbits/extensions/boltcards/views_api.py
+++ b/lnbits/extensions/boltcards/views_api.py
@@ -12,21 +12,16 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import boltcards_ext
from .crud import (
create_card,
- create_hit,
delete_card,
enable_disable_card,
get_card,
- get_card_by_otp,
get_card_by_uid,
get_cards,
get_hits,
get_refunds,
update_card,
- update_card_counter,
- update_card_otp,
)
from .models import CreateCardData
-from .nxp424 import decryptSUN, getSunMAC
@boltcards_ext.get("/api/v1/cards")
diff --git a/lnbits/extensions/withdraw/lnurl.py b/lnbits/extensions/withdraw/lnurl.py
index 18a99599..3379fd17 100644
--- a/lnbits/extensions/withdraw/lnurl.py
+++ b/lnbits/extensions/withdraw/lnurl.py
@@ -3,7 +3,6 @@ import traceback
from datetime import datetime
from http import HTTPStatus
-import httpx
import shortuuid # type: ignore
from fastapi import HTTPException
from fastapi.param_functions import Query
@@ -115,29 +114,24 @@ async def api_lnurl_callback(
payment_request = pr
- payment_hash = await pay_invoice(
+ webhook = (
+ (
+ link.webhook_url,
+ {
+ "lnurlw": link.id,
+ },
+ )
+ if link.webhook_url
+ else None
+ )
+ await pay_invoice(
wallet_id=link.wallet,
payment_request=payment_request,
max_sat=link.max_withdrawable,
extra={"tag": "withdraw"},
+ webhook=webhook,
)
- if link.webhook_url:
- async with httpx.AsyncClient() as client:
- try:
- r = await client.post(
- link.webhook_url,
- json={
- "payment_hash": payment_hash,
- "payment_request": payment_request,
- "lnurlw": link.id,
- },
- timeout=40,
- )
- except Exception as exc:
- # webhook fails shouldn't cause the lnurlw to fail since invoice is already paid
- logger.error("Caught exception when dispatching webhook url:", exc)
-
return {"status": "OK"}
except Exception as e:
From 0bede387f5ed5620781c50b689fc7016ad5d15af Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Sun, 25 Sep 2022 18:11:25 +0200
Subject: [PATCH 008/696] webhook to pay_invoice/fix
---
lnbits/core/services.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index aeb4f938..4f937ec3 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -232,6 +232,7 @@ async def pay_invoice(
f"didn't receive checking_id from backend, payment may be stuck in database: {temp_id}"
)
+ additionals = None
if type(webhook) is str:
webhook_url = webhook
elif type(webhook) is tuple:
From 5c6cd70d3bf08720a094c4aac263f53c583664e9 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Mon, 26 Sep 2022 11:47:00 +0200
Subject: [PATCH 009/696] disabling card with just otp which is also part of
notify
---
lnbits/extensions/boltcards/crud.py | 10 ++++++++--
lnbits/extensions/boltcards/lnurl.py | 3 +++
lnbits/extensions/boltcards/views_api.py | 18 ++++++++++++++++++
3 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/boltcards/crud.py b/lnbits/extensions/boltcards/crud.py
index 39ee3f40..0724ea04 100644
--- a/lnbits/extensions/boltcards/crud.py
+++ b/lnbits/extensions/boltcards/crud.py
@@ -114,8 +114,14 @@ async def get_card_by_external_id(external_id: str) -> Optional[Card]:
return Card.parse_obj(card)
-async def get_card_by_otp(otp: str) -> Optional[Card]:
- row = await db.fetchone("SELECT * FROM boltcards.cards WHERE otp = ?", (otp,))
+async def get_card_by_otp(otp: str, half: bool = False) -> Optional[Card]:
+ if half and len(otp) == 16:
+ otp = "%" + otp
+ row = await db.fetchone(
+ "SELECT * FROM boltcards.cards WHERE otp LIKE ?", (otp,)
+ )
+ else:
+ row = await db.fetchone("SELECT * FROM boltcards.cards WHERE otp = ?", (otp,))
if not row:
return None
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index 320b1666..064bde2c 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -119,6 +119,9 @@ async def lnurl_callback(
"notification": "card_payment",
"card_external_id": card.external_id,
"card_name": card.card_name,
+ "card_otp": card.otp[
+ -16:
+ ], # actually only half of the OTP is sent (full otp reveals the keys)
},
)
if card.webhook_url
diff --git a/lnbits/extensions/boltcards/views_api.py b/lnbits/extensions/boltcards/views_api.py
index 2fc11dbc..d7f5cf7c 100644
--- a/lnbits/extensions/boltcards/views_api.py
+++ b/lnbits/extensions/boltcards/views_api.py
@@ -15,11 +15,13 @@ from .crud import (
delete_card,
enable_disable_card,
get_card,
+ get_card_by_otp,
get_card_by_uid,
get_cards,
get_hits,
get_refunds,
update_card,
+ update_card_otp,
)
from .models import CreateCardData
@@ -111,6 +113,22 @@ async def enable_card(
return card.dict()
+@boltcards_ext.post("/api/v1/disablecard")
+async def disble_card_with_otp(a):
+ if len(a) < 16:
+ raise HTTPException(detail="Invalid OTP.", status_code=HTTPStatus.BAD_REQUEST)
+ card = await get_card_by_otp(a, half=True)
+ if not card:
+ raise HTTPException(detail="No card found.", status_code=HTTPStatus.NOT_FOUND)
+
+ new_otp = secrets.token_hex(16)
+ await update_card_otp(new_otp, card.id)
+
+ card = await enable_disable_card(enable=False, id=card.id)
+
+ return {"status": "OK"}
+
+
@boltcards_ext.delete("/api/v1/cards/{card_id}")
async def api_card_delete(card_id, wallet: WalletTypeInfo = Depends(require_admin_key)):
card = await get_card(card_id)
From 3b70d45091e6d69aac04a207c59e1115eba8c76f Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Tue, 4 Oct 2022 09:56:15 +0200
Subject: [PATCH 010/696] .env variable for STARTUP_INVOICE_EXPIRY_CHECK
---
.env.example | 2 ++
lnbits/settings.py | 1 +
lnbits/tasks.py | 4 ++--
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/.env.example b/.env.example
index 93b82325..1c7d1529 100644
--- a/.env.example
+++ b/.env.example
@@ -32,6 +32,8 @@ LNBITS_SERVICE_FEE="0.0"
LNBITS_RESERVE_FEE_MIN=2000
# value in percent
LNBITS_RESERVE_FEE_PERCENT=1.0
+# check invoice expiry on every startup (can be slow on large instances)
+STARTUP_INVOICE_EXPIRY_CHECK=True
# Change theme
LNBITS_SITE_TITLE="LNbits"
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 3f4e31cc..9fc3c197 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -64,6 +64,7 @@ PREFER_SECURE_URLS = env.bool("LNBITS_FORCE_HTTPS", default=True)
RESERVE_FEE_MIN = env.int("LNBITS_RESERVE_FEE_MIN", default=2000)
RESERVE_FEE_PERCENT = env.float("LNBITS_RESERVE_FEE_PERCENT", default=1.0)
SERVICE_FEE = env.float("LNBITS_SERVICE_FEE", default=0.0)
+STARTUP_INVOICE_EXPIRY_CHECK = env.bool("STARTUP_INVOICE_EXPIRY_CHECK", default=True)
try:
LNBITS_COMMIT = (
diff --git a/lnbits/tasks.py b/lnbits/tasks.py
index 94e43dcf..26758303 100644
--- a/lnbits/tasks.py
+++ b/lnbits/tasks.py
@@ -15,7 +15,7 @@ from lnbits.core.crud import (
get_standalone_payment,
)
from lnbits.core.services import redeem_lnurl_withdraw
-from lnbits.settings import WALLET
+from lnbits.settings import WALLET, STARTUP_INVOICE_EXPIRY_CHECK
from .core import db
@@ -144,7 +144,7 @@ async def check_pending_payments():
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:
+ if incoming and STARTUP_INVOICE_EXPIRY_CHECK:
logger.debug("Task: deleting all expired invoices")
start_time: float = time.time()
await delete_expired_invoices(conn=conn)
From 166530eb0c985575a140124fb4c9e5a23ee9e5a7 Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Mar 2022 05:03:32 +0000
Subject: [PATCH 011/696] Added old admin extension
---
.env.example | 12 +-
lnbits/extensions/admin/README.md | 11 +
lnbits/extensions/admin/__init__.py | 10 +
lnbits/extensions/admin/config.json | 6 +
lnbits/extensions/admin/crud.py | 59 ++
lnbits/extensions/admin/migrations.py | 256 ++++++++
lnbits/extensions/admin/models.py | 38 ++
.../admin/templates/admin/index.html | 565 ++++++++++++++++++
lnbits/extensions/admin/views.py | 20 +
lnbits/extensions/admin/views_api.py | 41 ++
10 files changed, 1013 insertions(+), 5 deletions(-)
create mode 100644 lnbits/extensions/admin/README.md
create mode 100644 lnbits/extensions/admin/__init__.py
create mode 100644 lnbits/extensions/admin/config.json
create mode 100644 lnbits/extensions/admin/crud.py
create mode 100644 lnbits/extensions/admin/migrations.py
create mode 100644 lnbits/extensions/admin/models.py
create mode 100644 lnbits/extensions/admin/templates/admin/index.html
create mode 100644 lnbits/extensions/admin/views.py
create mode 100644 lnbits/extensions/admin/views_api.py
diff --git a/.env.example b/.env.example
index 93b82325..68e25ad1 100644
--- a/.env.example
+++ b/.env.example
@@ -3,10 +3,12 @@ PORT=5000
DEBUG=false
-LNBITS_ALLOWED_USERS=""
-LNBITS_ADMIN_USERS=""
-# Extensions only admin can access
-LNBITS_ADMIN_EXTENSIONS="ngrok"
+LNBITS_ADMIN_USERS="" # User IDs seperated by comma
+LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access
+LNBITS_ADMIN_UI=false # Extensions only admin can access
+
+LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
+
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
# csv ad image filepaths or urls, extensions can choose to honor
@@ -91,4 +93,4 @@ LNBITS_DENOMINATION=sats
# EclairWallet
ECLAIR_URL=http://127.0.0.1:8283
-ECLAIR_PASS=eclairpw
\ No newline at end of file
+ECLAIR_PASS=eclairpw
diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md
new file mode 100644
index 00000000..27729459
--- /dev/null
+++ b/lnbits/extensions/admin/README.md
@@ -0,0 +1,11 @@
+Example Extension
+*tagline*
+This is an example extension to help you organise and build you own.
+
+Try to include an image
+
+
+
+If your extension has API endpoints, include useful ones here
+
+curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"
diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py
new file mode 100644
index 00000000..d5f26c90
--- /dev/null
+++ b/lnbits/extensions/admin/__init__.py
@@ -0,0 +1,10 @@
+from quart import Blueprint
+from lnbits.db import Database
+
+db = Database("ext_admin")
+
+admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates")
+
+
+from .views_api import * # noqa
+from .views import * # noqa
diff --git a/lnbits/extensions/admin/config.json b/lnbits/extensions/admin/config.json
new file mode 100644
index 00000000..69661733
--- /dev/null
+++ b/lnbits/extensions/admin/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Admin",
+ "short_description": "Manage your LNbits install",
+ "icon": "build",
+ "contributors": ["benarc"]
+}
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
new file mode 100644
index 00000000..cb8f9b5b
--- /dev/null
+++ b/lnbits/extensions/admin/crud.py
@@ -0,0 +1,59 @@
+from typing import List, Optional
+
+from . import db
+from .models import Admin, Funding
+from lnbits.settings import *
+from lnbits.helpers import urlsafe_short_hash
+from lnbits.core.crud import create_payment
+from lnbits.db import Connection
+
+
+def update_wallet_balance(wallet_id: str, amount: int) -> str:
+ temp_id = f"temp_{urlsafe_short_hash()}"
+ internal_id = f"internal_{urlsafe_short_hash()}"
+ create_payment(
+ wallet_id=wallet_id,
+ checking_id=internal_id,
+ payment_request="admin_internal",
+ payment_hash="admin_internal",
+ amount=amount * 1000,
+ memo="Admin top up",
+ pending=False,
+ )
+ return "success"
+
+
+async def update_admin(
+) -> Optional[Admin]:
+ if not CLightningWallet:
+ print("poo")
+ await db.execute(
+ """
+ UPDATE admin
+ SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ?
+ WHERE 1
+ """,
+ (
+
+ ),
+ )
+ row = await db.fetchone("SELECT * FROM admin WHERE 1")
+ return Admin.from_row(row) if row else None
+
+async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]:
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id)
+ )
+ row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,))
+ return Jukebox(**row) if row else None
+
+async def get_admin() -> List[Admin]:
+ row = await db.fetchone("SELECT * FROM admin WHERE 1")
+ return Admin.from_row(row) if row else None
+
+
+async def get_funding() -> List[Funding]:
+ rows = await db.fetchall("SELECT * FROM funding")
+
+ return [Funding.from_row(row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
new file mode 100644
index 00000000..82d934cb
--- /dev/null
+++ b/lnbits/extensions/admin/migrations.py
@@ -0,0 +1,256 @@
+from sqlalchemy.exc import OperationalError # type: ignore
+from os import getenv
+from lnbits.helpers import urlsafe_short_hash
+
+
+async def m001_create_admin_table(db):
+ user = None
+ site_title = None
+ site_tagline = None
+ site_description = None
+ allowed_users = None
+ admin_user = None
+ default_wallet_name = None
+ data_folder = None
+ disabled_ext = None
+ force_https = True
+ service_fee = 0
+ funding_source = ""
+
+ if getenv("LNBITS_SITE_TITLE"):
+ site_title = getenv("LNBITS_SITE_TITLE")
+
+ if getenv("LNBITS_SITE_TAGLINE"):
+ site_tagline = getenv("LNBITS_SITE_TAGLINE")
+
+ if getenv("LNBITS_SITE_DESCRIPTION"):
+ site_description = getenv("LNBITS_SITE_DESCRIPTION")
+
+ if getenv("LNBITS_ALLOWED_USERS"):
+ allowed_users = getenv("LNBITS_ALLOWED_USERS")
+
+ if getenv("LNBITS_ADMIN_USER"):
+ admin_user = getenv("LNBITS_ADMIN_USER")
+
+ if getenv("LNBITS_DEFAULT_WALLET_NAME"):
+ default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
+
+ if getenv("LNBITS_DATA_FOLDER"):
+ data_folder = getenv("LNBITS_DATA_FOLDER")
+
+ if getenv("LNBITS_DISABLED_EXTENSIONS"):
+ disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
+
+ if getenv("LNBITS_FORCE_HTTPS"):
+ force_https = getenv("LNBITS_FORCE_HTTPS")
+
+ if getenv("LNBITS_SERVICE_FEE"):
+ service_fee = getenv("LNBITS_SERVICE_FEE")
+
+ if getenv("LNBITS_BACKEND_WALLET_CLASS"):
+ funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
+
+ await db.execute(
+ """
+ CREATE TABLE IF NOT EXISTS admin (
+ user TEXT,
+ site_title TEXT,
+ site_tagline TEXT,
+ site_description TEXT,
+ admin_user TEXT,
+ allowed_users TEXT,
+ default_wallet_name TEXT,
+ data_folder TEXT,
+ disabled_ext TEXT,
+ force_https BOOLEAN,
+ service_fee INT,
+ funding_source TEXT
+ );
+ """
+ )
+ await db.execute(
+ """
+ INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ user,
+ site_title,
+ site_tagline,
+ site_description,
+ admin_user,
+ allowed_users,
+ default_wallet_name,
+ data_folder,
+ disabled_ext,
+ force_https,
+ service_fee,
+ funding_source,
+ ),
+ )
+
+
+async def m001_create_funding_table(db):
+
+ funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS")
+
+ # Make the funding table, if it does not already exist
+ await db.execute(
+ """
+ CREATE TABLE IF NOT EXISTS funding (
+ id TEXT PRIMARY KEY,
+ backend_wallet TEXT,
+ endpoint TEXT,
+ port INT,
+ read_key TEXT,
+ invoice_key TEXT,
+ admin_key TEXT,
+ cert TEXT,
+ balance INT,
+ selected INT
+ );
+ """
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, selected)
+ VALUES (?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "CLightningWallet",
+ getenv("CLIGHTNING_RPC"),
+ 1 if funding_wallet == "CLightningWallet" else 0,
+ ),
+ )
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "SparkWallet",
+ getenv("SPARK_URL"),
+ getenv("SPARK_TOKEN"),
+ 1 if funding_wallet == "SparkWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LnbitsWallet",
+ getenv("LNBITS_ENDPOINT"),
+ getenv("LNBITS_KEY"),
+ 1 if funding_wallet == "LnbitsWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LndWallet",
+ getenv("LND_GRPC_ENDPOINT"),
+ getenv("LND_GRPC_PORT"),
+ getenv("LND_GRPC_MACAROON"),
+ getenv("LND_GRPC_CERT"),
+ 1 if funding_wallet == "LndWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LndRestWallet",
+ getenv("LND_REST_ENDPOINT"),
+ getenv("LND_REST_MACAROON"),
+ getenv("LND_REST_CERT"),
+ 1 if funding_wallet == "LndWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LNPayWallet",
+ getenv("LNPAY_API_ENDPOINT"),
+ getenv("LNPAY_WALLET_KEY"),
+ getenv("LNPAY_API_KEY"), # this is going in as the cert
+ 1 if funding_wallet == "LNPayWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LntxbotWallet",
+ getenv("LNTXBOT_API_ENDPOINT"),
+ getenv("LNTXBOT_KEY"),
+ 1 if funding_wallet == "LntxbotWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "OpenNodeWallet",
+ getenv("OPENNODE_API_ENDPOINT"),
+ getenv("OPENNODE_KEY"),
+ 1 if funding_wallet == "OpenNodeWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "SparkWallet",
+ getenv("SPARK_URL"),
+ getenv("SPARK_TOKEN"),
+ 1 if funding_wallet == "SparkWallet" else 0,
+ ),
+ )
+
+ ## PLACEHOLDER FOR ECLAIR WALLET
+ # await db.execute(
+ # """
+ # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ # VALUES (?, ?, ?, ?, ?)
+ # """,
+ # (
+ # urlsafe_short_hash(),
+ # "EclairWallet",
+ # getenv("ECLAIR_URL"),
+ # getenv("ECLAIR_PASS"),
+ # 1 if funding_wallet == "EclairWallet" else 0,
+ # ),
+ # )
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
new file mode 100644
index 00000000..c38f17f4
--- /dev/null
+++ b/lnbits/extensions/admin/models.py
@@ -0,0 +1,38 @@
+from typing import NamedTuple
+from sqlite3 import Row
+
+class Admin(NamedTuple):
+ user: str
+ site_title: str
+ site_tagline: str
+ site_description:str
+ allowed_users: str
+ admin_user: str
+ default_wallet_name: str
+ data_folder: str
+ disabled_ext: str
+ force_https: str
+ service_fee: str
+ funding_source: str
+
+ @classmethod
+ def from_row(cls, row: Row) -> "Admin":
+ data = dict(row)
+ return cls(**data)
+
+class Funding(NamedTuple):
+ id: str
+ backend_wallet: str
+ endpoint: str
+ port: str
+ read_key: str
+ invoice_key: str
+ admin_key: str
+ cert: str
+ balance: int
+ selected: int
+
+ @classmethod
+ def from_row(cls, row: Row) -> "Funding":
+ data = dict(row)
+ return cls(**data)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
new file mode 100644
index 00000000..87cf09ef
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -0,0 +1,565 @@
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
+%} {% block page %}
+
+Admin
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wallet topup
+
+
+
+
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+{% endblock %}
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
new file mode 100644
index 00000000..5e17919c
--- /dev/null
+++ b/lnbits/extensions/admin/views.py
@@ -0,0 +1,20 @@
+from quart import g, render_template, request, jsonify
+import json
+
+from lnbits.decorators import check_user_exists, validate_uuids
+from lnbits.extensions.admin import admin_ext
+from lnbits.core.crud import get_user, create_account
+from .crud import get_admin, get_funding
+from lnbits.settings import WALLET
+
+
+@admin_ext.route("/")
+@validate_uuids(["usr"], required=True)
+@check_user_exists()
+async def index():
+ user_id = g.user
+ admin = await get_admin()
+
+ funding = [{**funding._asdict()} for funding in await get_funding()]
+
+ return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
new file mode 100644
index 00000000..2a61b6f5
--- /dev/null
+++ b/lnbits/extensions/admin/views_api.py
@@ -0,0 +1,41 @@
+from quart import jsonify, g, request
+from http import HTTPStatus
+from .crud import update_wallet_balance
+from lnbits.extensions.admin import admin_ext
+from lnbits.decorators import api_check_wallet_key, api_validate_post_request
+from lnbits.core.crud import get_wallet
+from .crud import get_admin,update_admin
+import json
+
+@admin_ext.route("/api/v1/admin//", methods=["GET"])
+@api_check_wallet_key("admin")
+async def api_update_balance(wallet_id, topup_amount):
+ print(g.data.wallet)
+ try:
+ wallet = await get_wallet(wallet_id)
+ except:
+ return (
+ jsonify({"error": "Not allowed: not an admin"}),
+ HTTPStatus.FORBIDDEN,
+ )
+ print(wallet)
+ print(topup_amount)
+ return jsonify({"status": "Success"}), HTTPStatus.OK
+
+
+@admin_ext.route("/api/v1/admin/", methods=["POST"])
+@api_check_wallet_key("admin")
+@api_validate_post_request(schema={})
+async def api_update_admin():
+ body = await request.get_json()
+ admin = await get_admin()
+ print(g.wallet[2])
+ print(body["admin_user"])
+ if not admin.admin_user == g.wallet[2] and admin.admin_user != None:
+ return (
+ jsonify({"error": "Not allowed: not an admin"}),
+ HTTPStatus.FORBIDDEN,
+ )
+ updated = await update_admin(body)
+ print(updated)
+ return jsonify({"status": "Success"}), HTTPStatus.OK
\ No newline at end of file
From 68eee00b45170db4e35fa6fdafba85373958e9fa Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Mar 2022 05:11:55 +0000
Subject: [PATCH 012/696] old admin setup UI
---
lnbits/core/templates/core/admin.html | 717 ++++++++++++++++++++++++++
1 file changed, 717 insertions(+)
create mode 100644 lnbits/core/templates/core/admin.html
diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html
new file mode 100644
index 00000000..e8176555
--- /dev/null
+++ b/lnbits/core/templates/core/admin.html
@@ -0,0 +1,717 @@
+{% extends "public.html" %} {% from "macros.jinja" import window_vars with
+context %} {% block page %}
+
+
+
+
+
+ Welcome to LNbits
+
+
+ Fill in the information below to setup your LNbits instance. Details
+ can be changed later.
+
+
+
+
+
+
+ Branding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Service settings
+
+
+
+ Funding source information (at least one required) *if installed through RaspiBlitz, MyNode, etc, details
+ should be filled in for you
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ View project in GitHub
+ Donate
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }}
+
+{% endblock %}
From 65e1f19ed1124340d2a9ba97b4420c099896488c Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:18:09 +0000
Subject: [PATCH 013/696] convert to FastAPI
---
lnbits/extensions/admin/__init__.py | 11 +++-
lnbits/extensions/admin/crud.py | 50 +++++++--------
lnbits/extensions/admin/migrations.py | 23 ++++---
lnbits/extensions/admin/models.py | 51 ++++++++++------
.../admin/templates/admin/index.html | 23 +++++--
lnbits/extensions/admin/views.py | 39 ++++++++----
lnbits/extensions/admin/views_api.py | 61 ++++++++++---------
7 files changed, 151 insertions(+), 107 deletions(-)
diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py
index d5f26c90..6a56b2bb 100644
--- a/lnbits/extensions/admin/__init__.py
+++ b/lnbits/extensions/admin/__init__.py
@@ -1,10 +1,15 @@
-from quart import Blueprint
+from fastapi import APIRouter
+
from lnbits.db import Database
+from lnbits.helpers import template_renderer
db = Database("ext_admin")
-admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates")
+admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"])
+
+def admin_renderer():
+ return template_renderer(["lnbits/extensions/admin/templates"])
-from .views_api import * # noqa
from .views import * # noqa
+from .views_api import * # noqa
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index cb8f9b5b..872d6c97 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,11 +1,11 @@
from typing import List, Optional
+from lnbits.core.crud import create_payment
+from lnbits.helpers import urlsafe_short_hash
+from lnbits.settings import *
+
from . import db
from .models import Admin, Funding
-from lnbits.settings import *
-from lnbits.helpers import urlsafe_short_hash
-from lnbits.core.crud import create_payment
-from lnbits.db import Connection
def update_wallet_balance(wallet_id: str, amount: int) -> str:
@@ -22,38 +22,30 @@ def update_wallet_balance(wallet_id: str, amount: int) -> str:
)
return "success"
-
-async def update_admin(
-) -> Optional[Admin]:
- if not CLightningWallet:
- print("poo")
- await db.execute(
- """
- UPDATE admin
- SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ?
- WHERE 1
- """,
- (
-
- ),
- )
- row = await db.fetchone("SELECT * FROM admin WHERE 1")
- return Admin.from_row(row) if row else None
-
-async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]:
+async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ print("UPDATE", q)
await db.execute(
- f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id)
+ f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
- row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,))
- return Jukebox(**row) if row else None
+ row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,))
+ assert row, "Newly updated settings couldn't be retrieved"
+ return Admin(**row) if row else None
+
+# async def update_admin(user: str, **kwargs) -> Optional[Admin]:
+# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+# await db.execute(
+# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user)
+# )
+# new_settings = await get_admin()
+# return new_settings
async def get_admin() -> List[Admin]:
- row = await db.fetchone("SELECT * FROM admin WHERE 1")
- return Admin.from_row(row) if row else None
+ row = await db.fetchone("SELECT * FROM admin")
+ return Admin(**row) if row else None
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM funding")
- return [Funding.from_row(row) for row in rows]
+ return [Funding(**row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 82d934cb..13b76923 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -1,5 +1,7 @@
-from sqlalchemy.exc import OperationalError # type: ignore
from os import getenv
+
+from sqlalchemy.exc import OperationalError # type: ignore
+
from lnbits.helpers import urlsafe_short_hash
@@ -9,7 +11,7 @@ async def m001_create_admin_table(db):
site_tagline = None
site_description = None
allowed_users = None
- admin_user = None
+ admin_users = None
default_wallet_name = None
data_folder = None
disabled_ext = None
@@ -29,8 +31,9 @@ async def m001_create_admin_table(db):
if getenv("LNBITS_ALLOWED_USERS"):
allowed_users = getenv("LNBITS_ALLOWED_USERS")
- if getenv("LNBITS_ADMIN_USER"):
- admin_user = getenv("LNBITS_ADMIN_USER")
+ if getenv("LNBITS_ADMIN_USERS"):
+ admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
+ user = admin_users.split(',')[0]
if getenv("LNBITS_DEFAULT_WALLET_NAME"):
default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
@@ -53,32 +56,32 @@ async def m001_create_admin_table(db):
await db.execute(
"""
CREATE TABLE IF NOT EXISTS admin (
- user TEXT,
+ "user" TEXT,
site_title TEXT,
site_tagline TEXT,
site_description TEXT,
- admin_user TEXT,
+ admin_users TEXT,
allowed_users TEXT,
default_wallet_name TEXT,
data_folder TEXT,
disabled_ext TEXT,
force_https BOOLEAN,
- service_fee INT,
+ service_fee REAL,
funding_source TEXT
);
"""
)
await db.execute(
"""
- INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
+ INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
- user,
+ user.strip(),
site_title,
site_tagline,
site_description,
- admin_user,
+ admin_users[1:],
allowed_users,
default_wallet_name,
data_folder,
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index c38f17f4..4080ff01 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -1,18 +1,35 @@
-from typing import NamedTuple
from sqlite3 import Row
+from typing import List, Optional
-class Admin(NamedTuple):
+from fastapi import Query
+from pydantic import BaseModel
+
+
+class UpdateAdminSettings(BaseModel):
+ site_title: Optional[str]
+ site_tagline: Optional[str]
+ site_description: Optional[str]
+ allowed_users: Optional[str]
+ admin_users: Optional[str]
+ default_wallet_name: Optional[str]
+ data_folder: Optional[str]
+ disabled_ext: Optional[str]
+ force_https: Optional[bool]
+ service_fee: Optional[float]
+ funding_source: Optional[str]
+
+class Admin(BaseModel):
user: str
- site_title: str
- site_tagline: str
- site_description:str
- allowed_users: str
- admin_user: str
+ site_title: Optional[str]
+ site_tagline: Optional[str]
+ site_description: Optional[str]
+ allowed_users: Optional[str]
+ admin_users: str
default_wallet_name: str
data_folder: str
disabled_ext: str
- force_https: str
- service_fee: str
+ force_https: Optional[bool] = Query(True)
+ service_fee: float
funding_source: str
@classmethod
@@ -20,16 +37,16 @@ class Admin(NamedTuple):
data = dict(row)
return cls(**data)
-class Funding(NamedTuple):
+class Funding(BaseModel):
id: str
backend_wallet: str
- endpoint: str
- port: str
- read_key: str
- invoice_key: str
- admin_key: str
- cert: str
- balance: int
+ endpoint: str = Query(None)
+ port: str = Query(None)
+ read_key: str = Query(None)
+ invoice_key: str = Query(None)
+ admin_key: str = Query(None)
+ cert: str = Query(None)
+ balance: int = Query(None)
selected: int
@classmethod
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 87cf09ef..a6b45625 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -87,7 +87,7 @@
@@ -442,13 +442,14 @@
site_title: '{{admin.site_title}}',
tagline: '{{admin.site_tagline}}',
description: '{{admin.site_description}}',
- admin_user: '{{admin.admin_user}}',
- service_fee: parseInt('{{admin.service_fee}}'),
+ admin_users: '{{admin.admin_users}}',
+ service_fee: parseFloat('{{admin.service_fee}}'),
default_wallet_name: '{{admin.default_wallet_name}}',
data_folder: '{{admin.data_folder}}',
funding_source_primary: '{{admin.funding_source}}',
disabled_ext: '{{admin.disabled_ext}}'.split(','),
edited: [],
+ funding: {},
senddata: {}
}
},
@@ -528,15 +529,27 @@
},
UpdateLNbits: function () {
var self = this
- console.log(self.data.admin)
+ let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin
+ let data = {
+ site_title,
+ site_tagline: this.data.admin.tagline,
+ site_description: this.data.admin.description,
+ admin_users: admin_users.toString(),
+ default_wallet_name,
+ data_folder,
+ disabled_ext: disabled_ext.toString(),
+ service_fee,
+ funding_source: funding_source_primary}
+ console.log(data)
LNbits.api
.request(
'POST',
'/admin/api/v1/admin/',
self.g.user.wallets[0].adminkey,
- self.data.admin
+ data
)
.then(function (response) {
+ console.log(response.data)
self.$q.notify({
type: 'positive',
message:
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 5e17919c..00a0c99f 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -1,20 +1,33 @@
-from quart import g, render_template, request, jsonify
-import json
+from email.policy import default
+from os import getenv
-from lnbits.decorators import check_user_exists, validate_uuids
+from fastapi import Request
+from fastapi.params import Depends
+from fastapi.templating import Jinja2Templates
+from starlette.responses import HTMLResponse
+
+from lnbits.core.models import User
+from lnbits.decorators import check_user_exists
from lnbits.extensions.admin import admin_ext
-from lnbits.core.crud import get_user, create_account
+from lnbits.requestvars import g
+
+from . import admin_ext, admin_renderer
from .crud import get_admin, get_funding
-from lnbits.settings import WALLET
+templates = Jinja2Templates(directory="templates")
-@admin_ext.route("/")
-@validate_uuids(["usr"], required=True)
-@check_user_exists()
-async def index():
- user_id = g.user
+@admin_ext.get("/", response_class=HTMLResponse)
+async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
+ print(g())
+ funding = [f.dict() for f in await get_funding()]
- funding = [{**funding._asdict()} for funding in await get_funding()]
-
- return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding)
+ print("ADMIN", admin.dict())
+ return admin_renderer().TemplateResponse(
+ "admin/index.html", {
+ "request": request,
+ "user": user.dict(),
+ "admin": admin.dict(),
+ "funding": funding
+ }
+ )
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 2a61b6f5..b2c65be2 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,41 +1,42 @@
-from quart import jsonify, g, request
from http import HTTPStatus
-from .crud import update_wallet_balance
-from lnbits.extensions.admin import admin_ext
-from lnbits.decorators import api_check_wallet_key, api_validate_post_request
-from lnbits.core.crud import get_wallet
-from .crud import get_admin,update_admin
-import json
-@admin_ext.route("/api/v1/admin//", methods=["GET"])
-@api_check_wallet_key("admin")
-async def api_update_balance(wallet_id, topup_amount):
- print(g.data.wallet)
+from fastapi import Body, Depends, Request
+from starlette.exceptions import HTTPException
+
+from lnbits.core.crud import get_wallet
+from lnbits.decorators import WalletTypeInfo, require_admin_key
+from lnbits.extensions.admin import admin_ext
+from lnbits.extensions.admin.models import Admin, UpdateAdminSettings
+
+from .crud import get_admin, update_admin, update_wallet_balance
+
+
+@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
+async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)):
+ print(g.wallet)
try:
wallet = await get_wallet(wallet_id)
except:
- return (
- jsonify({"error": "Not allowed: not an admin"}),
- HTTPStatus.FORBIDDEN,
- )
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
print(wallet)
print(topup_amount)
- return jsonify({"status": "Success"}), HTTPStatus.OK
+ return {"status": "Success"}
-@admin_ext.route("/api/v1/admin/", methods=["POST"])
-@api_check_wallet_key("admin")
-@api_validate_post_request(schema={})
-async def api_update_admin():
- body = await request.get_json()
+@admin_ext.post("/api/v1/admin/", status_code=HTTPStatus.OK)
+async def api_update_admin(
+ request: Request,
+ data: UpdateAdminSettings = Body(...),
+ g: WalletTypeInfo = Depends(require_admin_key)
+ ):
admin = await get_admin()
- print(g.wallet[2])
- print(body["admin_user"])
- if not admin.admin_user == g.wallet[2] and admin.admin_user != None:
- return (
- jsonify({"error": "Not allowed: not an admin"}),
- HTTPStatus.FORBIDDEN,
- )
- updated = await update_admin(body)
+ print(data)
+ if not admin.user == g.wallet.user:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
+ updated = await update_admin(user=g.wallet.user, **data.dict())
print(updated)
- return jsonify({"status": "Success"}), HTTPStatus.OK
\ No newline at end of file
+ return {"status": "Success"}
From 23d770a07489c3d74cc0c4c4d3b4a3b4b00fc393 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:18:58 +0000
Subject: [PATCH 014/696] remove core admin html (renamed for now)
---
lnbits/core/templates/core/core_admin.html | 717 +++++++++++++++++++++
1 file changed, 717 insertions(+)
create mode 100644 lnbits/core/templates/core/core_admin.html
diff --git a/lnbits/core/templates/core/core_admin.html b/lnbits/core/templates/core/core_admin.html
new file mode 100644
index 00000000..835fc00a
--- /dev/null
+++ b/lnbits/core/templates/core/core_admin.html
@@ -0,0 +1,717 @@
+{% extends "public.html" %} {% from "macros.jinja" import window_vars with
+context %} {% block page %}
+
+
+
+
+
+ Welcome to LNbits
+
+
+ Fill in the information below to setup your LNbits instance. Details
+ can be changed later.
+
+
+
+
+
+
+ Branding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Service settings
+
+
+
+ Funding source information (at least one required) *if installed through RaspiBlitz, MyNode, etc, details
+ should be filled in for you
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ View project in GitHub
+ Donate
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }}
+
+{% endblock %}
From 165ab6d0b50b49cad69b46100bce0f87fd6f7257 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:21:38 +0000
Subject: [PATCH 015/696] typo
---
.env.example | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.env.example b/.env.example
index 68e25ad1..bd189484 100644
--- a/.env.example
+++ b/.env.example
@@ -5,7 +5,7 @@ DEBUG=false
LNBITS_ADMIN_USERS="" # User IDs seperated by comma
LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access
-LNBITS_ADMIN_UI=false # Extensions only admin can access
+LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS
LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
From 844e11edeb441fd2e7c7b40d11c25cc4019471bf Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:22:23 +0000
Subject: [PATCH 016/696] add admin_ui env
---
lnbits/settings.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 3f4e31cc..43cb87cb 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,5 +1,6 @@
import importlib
import subprocess
+from email.policy import default
from os import path
from typing import List
@@ -27,6 +28,7 @@ LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None)
LNBITS_ALLOWED_USERS: List[str] = [
x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str)
]
+LNBITS_ADMIN_UI = env.bool("LNBITS_ADMIN_UI", default=False)
LNBITS_ADMIN_USERS: List[str] = [
x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str)
]
From 4336613028e05dae5b6adf3b543903f6fc365a44 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:23:16 +0000
Subject: [PATCH 017/696] add db config at startup
---
lnbits/commands.py | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 0f7454f2..8c39c338 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -52,6 +52,25 @@ def bundle_vendored():
with open(outputpath, "w") as f:
f.write(output)
+async def get_admin_settings():
+ from lnbits.extensions.admin.models import Admin
+
+ async with core_db.connect() as conn:
+
+ if conn.type == SQLITE:
+ exists = await conn.fetchone(
+ "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
+ )
+ elif conn.type in {POSTGRES, COCKROACH}:
+ exists = await conn.fetchone(
+ "SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
+ )
+ if not exists:
+ return False
+
+ row = await conn.fetchone("SELECT * from admin")
+
+ return Admin(**row) if row else None
async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them."""
From 32a6a6ae2fb9fa3ba01c24f31067a5115feca4e3 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:23:53 +0000
Subject: [PATCH 018/696] get admin settings at startup
---
lnbits/app.py | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 51482538..6a66b99e 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -18,6 +18,7 @@ from loguru import logger
import lnbits.settings
from lnbits.core.tasks import register_task_listeners
+from .commands import get_admin_settings
from .core import core_app
from .core.views.generic import core_html_routes
from .helpers import (
@@ -42,6 +43,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"""Create application factory.
:param config_object: The configuration object to use.
"""
+<<<<<<< HEAD
configure_logger()
app = FastAPI(
@@ -53,6 +55,14 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
},
)
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
+=======
+ app = FastAPI()
+
+ if lnbits.settings.LNBITS_ADMIN_UI:
+ check_settings(app)
+
+ app.mount("/static", StaticFiles(directory="lnbits/static"), name="static")
+>>>>>>> e3a1b3ae (get admin settings at startup)
app.mount(
"/core/static",
StaticFiles(packages=[("lnbits.core", "static")]),
@@ -64,7 +74,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
app.add_middleware(
CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"]
)
-
g().config = lnbits.settings
g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}"
@@ -102,6 +111,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app
+def check_settings(app: FastAPI):
+ @app.on_event("startup")
+ async def check_settings_admin():
+ while True:
+ admin_set = await get_admin_settings()
+ if admin_set :
+ break
+ print("ERROR:", admin_set)
+ await asyncio.sleep(5)
+ # admin_set = await get_admin_settings()
+ g().admin_conf = admin_set
+
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
async def check_wallet_status():
From f245f3188b7f35b6f9388e9caa12d3d6a0b20c3e Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:24:11 +0000
Subject: [PATCH 019/696] remove core admin.html
---
lnbits/core/templates/core/admin.html | 717 --------------------------
1 file changed, 717 deletions(-)
delete mode 100644 lnbits/core/templates/core/admin.html
diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html
deleted file mode 100644
index e8176555..00000000
--- a/lnbits/core/templates/core/admin.html
+++ /dev/null
@@ -1,717 +0,0 @@
-{% extends "public.html" %} {% from "macros.jinja" import window_vars with
-context %} {% block page %}
-
-
-
-
-
- Welcome to LNbits
-
-
- Fill in the information below to setup your LNbits instance. Details
- can be changed later.
-
-
-
-
-
-
- Branding
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Service settings
-
-
-
- Funding source information (at least one required) *if installed through RaspiBlitz, MyNode, etc, details
- should be filled in for you
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- View project in GitHub
- Donate
-
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(funding) }}
-
-{% endblock %}
From de21f0216161ac9f45cf17f42f5be708404156d0 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 18 Mar 2022 16:55:31 +0000
Subject: [PATCH 020/696] refactor ui
---
.../admin/templates/admin/index.html | 727 +++++++++++++++++-
1 file changed, 712 insertions(+), 15 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index a6b45625..65ac9f33 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1,6 +1,670 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
+
+
+
+
+
+
+ tab = val.name"
+ >
+ tab = val.name"
+ >
+ tab = val.name"
+ >
+ tab = val.name"
+ >
+
+
+
+
+
+
+
+ Wallets Management
+
+
+
+
+
Funding Source Info
+
+ {%raw%}
+ Funding Source: {{data.admin.funding_source}}
+ Balance: {{data.admin.balance / 1000}} sats
+ {%endraw%}
+
+
+
+
+
+
+
+
+ TopUp a wallet
+
+
+
+
+
+
+
+
+
Funding Sources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+ User Management
+
+
+ Super Admin: {% raw
+ %}{{this.data.admin.user}}{% endraw %}
+
+
+
+
Admin Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
Allowed Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
+
+
Disabled Extensions
+
+
+
+
+
+ Save
+
+
+
+
+
+ Server Management
+
+
+
+
+
Server Info
+
+ {%raw%}
+ SQlite: {{data.admin.data_folder}}
+ Postgres: {{data.admin.database_url}}
+ {%endraw%}
+
+
+
+
+
+
+
+
Miscelaneous
+
+
+ Force HTTPS
+ Prefer secure URLs
+
+
+
+
+
+
+
+ Hide API
+ Hides wallet api, extensions can choose to honor
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+ UI Management
+
+
+
+
+
+
+
+
Default Wallet Name
+
+
+
+
+
+
+
+
+
Advertisement Slots
+
+
+
+
+ {% raw %}
+
+ {{ space.slice(0, 8) + " ... " + space.slice(-8) }}
+
+ {% endraw %}
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+
+
+
+
Admin
-
+
@@ -426,6 +1090,7 @@
return {
wallet: {data: {}},
cancel: {},
+ tab: 'funding',
data: {
funding_source: [
'CLightningWallet',
@@ -436,24 +1101,14 @@
'LnbitsWallet',
'OpenNodeWallet'
],
-
+
admin: {
- user: '{{ user.id }}',
- site_title: '{{admin.site_title}}',
- tagline: '{{admin.site_tagline}}',
- description: '{{admin.site_description}}',
- admin_users: '{{admin.admin_users}}',
- service_fee: parseFloat('{{admin.service_fee}}'),
- default_wallet_name: '{{admin.default_wallet_name}}',
- data_folder: '{{admin.data_folder}}',
- funding_source_primary: '{{admin.funding_source}}',
- disabled_ext: '{{admin.disabled_ext}}'.split(','),
edited: [],
funding: {},
senddata: {}
}
},
-
+ themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'],
options: [
'bleskomat',
'captcha',
@@ -489,9 +1144,51 @@
for (i = 0; i < funding.length; i++) {
self.data.admin.funding[funding[i].backend_wallet] = funding[i]
}
- console.log(self.data.admin)
+ let settings = JSON.parse('{{ settings | tojson|safe }}')
+ settings.balance = '{{ balance }}'
+ this.data.admin = {...this.data.admin, ...settings}
+ console.log(this.g.user)
},
methods: {
+ addAdminUser(){
+ let addUser = this.data.admin_users_add
+ let admin_users = this.data.admin.admin_users
+ if(addUser.length && !admin_users.includes(addUser)){
+ admin_users.push(addUser)
+ this.data.admin.admin_users = admin_users
+ this.data.admin_users_add = ""
+ }
+ },
+ removeAdminUser(user){
+ let admin_users = this.data.admin.admin_users
+ this.data.admin.admin_users = admin_users.filter(u => u !== user)
+ },
+ addAllowedUser(){
+ let addUser = this.data.allowed_users_add
+ let allowed_users = this.data.admin.allowed_users
+ if(addUser.length && !allowed_users.includes(addUser)){
+ allowed_users.push(addUser)
+ this.data.admin.allowed_users = allowed_users
+ this.data.allowed_users_add = ""
+ }
+ },
+ removeAllowedUser(user){
+ let allowed_users = this.data.admin.allowed_users
+ this.data.admin.allowed_users = allowed_users.filter(u => u !== user)
+ },
+ addAdSpace(){
+ let adSpace = this.data.ad_space_add
+ let spaces = this.data.admin.ad_space
+ if(adSpace.length && !spaces.includes(adSpace)){
+ spaces.push(adSpace)
+ this.data.admin.ad_space = spaces
+ this.data.ad_space_add = ""
+ }
+ },
+ removeAdSpace(ad){
+ let spaces = this.data.admin.ad_space
+ this.data.admin.ad_space = spaces.filter(s => s !== ad)
+ },
topupWallet: function () {
var self = this
LNbits.api
From 582cc52ac61236ca7ae219b49e20d0954e983411 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 18 Mar 2022 16:59:06 +0000
Subject: [PATCH 021/696] make it work from g()
---
lnbits/app.py | 34 +++---
lnbits/config.py | 62 ++++++++++
lnbits/extensions/admin/crud.py | 2 +-
lnbits/extensions/admin/migrations.py | 162 +++++++++++++++++---------
lnbits/extensions/admin/models.py | 27 +++--
lnbits/extensions/admin/views.py | 8 +-
lnbits/settings.py | 2 +-
7 files changed, 218 insertions(+), 79 deletions(-)
create mode 100644 lnbits/config.py
diff --git a/lnbits/app.py b/lnbits/app.py
index 6a66b99e..ccac1e00 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -19,6 +19,7 @@ import lnbits.settings
from lnbits.core.tasks import register_task_listeners
from .commands import get_admin_settings
+from .config import WALLET, conf
from .core import core_app
from .core.views.generic import core_html_routes
from .helpers import (
@@ -29,7 +30,8 @@ from .helpers import (
url_for_vendored,
)
from .requestvars import g
-from .settings import WALLET
+
+# from .settings import WALLET
from .tasks import (
catch_everything_and_restart,
check_pending_payments,
@@ -43,7 +45,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"""Create application factory.
:param config_object: The configuration object to use.
"""
-<<<<<<< HEAD
configure_logger()
app = FastAPI(
@@ -55,20 +56,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
},
)
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
-=======
- app = FastAPI()
-
- if lnbits.settings.LNBITS_ADMIN_UI:
- check_settings(app)
-
- app.mount("/static", StaticFiles(directory="lnbits/static"), name="static")
->>>>>>> e3a1b3ae (get admin settings at startup)
app.mount(
"/core/static",
StaticFiles(packages=[("lnbits.core", "static")]),
name="core_static",
)
+ if lnbits.settings.LNBITS_ADMIN_UI:
+ g().admin_conf = conf
+ check_settings(app)
+
+ g().WALLET = WALLET
+
origins = ["*"]
app.add_middleware(
@@ -110,18 +109,27 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app
-
def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
+
+ def removeEmptyString(arr):
+ return list(filter(None, arr))
+
while True:
admin_set = await get_admin_settings()
if admin_set :
break
print("ERROR:", admin_set)
await asyncio.sleep(5)
- # admin_set = await get_admin_settings()
- g().admin_conf = admin_set
+
+ admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(','))
+ admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(','))
+ admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(','))
+ admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(','))
+ admin_set.theme = removeEmptyString(admin_set.theme.split(','))
+ admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(','))
+ g().admin_conf = conf.copy(update=admin_set.dict())
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
diff --git a/lnbits/config.py b/lnbits/config.py
new file mode 100644
index 00000000..02e8cf53
--- /dev/null
+++ b/lnbits/config.py
@@ -0,0 +1,62 @@
+import importlib
+import json
+from os import getenv, path
+from typing import List, Optional
+
+from pydantic import BaseSettings, Field, validator
+
+wallets_module = importlib.import_module("lnbits.wallets")
+wallet_class = getattr(
+ wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet")
+)
+
+WALLET = wallet_class()
+
+def list_parse_fallback(v):
+ try:
+ return json.loads(v)
+ except Exception as e:
+ return v.replace(' ','').split(',')
+
+class Settings(BaseSettings):
+ # users
+ admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS")
+ allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS")
+ admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS")
+ disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS")
+ funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS")
+ # ops
+ data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER")
+ database_url: str = Field(default=None, env="LNBITS_DATABASE_URL")
+ force_https: bool = Field(default=True, env="LNBITS_FORCE_HTTPS")
+ service_fee: float = Field(default=0, env="LNBITS_SERVICE_FEE")
+ hide_api: bool = Field(default=False, env="LNBITS_HIDE_API")
+ denomination: str = Field(default="sats", env="LNBITS_DENOMINATION")
+ # Change theme
+ site_title: str = Field(default=None, env="LNBITS_SITE_TITLE")
+ site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE")
+ site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
+ default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME")
+ theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS")
+ ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
+ # .env
+ env: Optional[str]
+ debug: Optional[str]
+ host: Optional[str]
+ port: Optional[str]
+ lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
+
+ # @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True)
+ # def validate(cls, val):
+ # print(val)
+ # return val.split(',')
+
+ class Config:
+ env_file = ".env"
+ env_file_encoding = "utf-8"
+ case_sensitive = False
+ json_loads = list_parse_fallback
+
+
+conf = Settings()
+WALLET = wallet_class()
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 872d6c97..6fccb8ee 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -40,7 +40,7 @@ async def update_admin(user: str, **kwargs) -> Admin:
# new_settings = await get_admin()
# return new_settings
-async def get_admin() -> List[Admin]:
+async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin")
return Admin(**row) if row else None
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 13b76923..574f772d 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -2,93 +2,151 @@ from os import getenv
from sqlalchemy.exc import OperationalError # type: ignore
+from lnbits.config import conf
from lnbits.helpers import urlsafe_short_hash
async def m001_create_admin_table(db):
- user = None
- site_title = None
- site_tagline = None
- site_description = None
- allowed_users = None
- admin_users = None
- default_wallet_name = None
- data_folder = None
- disabled_ext = None
- force_https = True
- service_fee = 0
- funding_source = ""
+ # users/server
+ user = conf.admin_users[0]
+ admin_users = ",".join(conf.admin_users)
+ allowed_users = ",".join(conf.allowed_users)
+ admin_ext = ",".join(conf.admin_ext)
+ disabled_ext = ",".join(conf.disabled_ext)
+ funding_source = conf.funding_source
+ #operational
+ data_folder = conf.data_folder
+ database_url = conf.database_url
+ force_https = conf.force_https
+ service_fee = conf.service_fee
+ hide_api = conf.hide_api
+ denomination = conf.denomination
+ # Theme'ing
+ site_title = conf.site_title
+ site_tagline = conf.site_tagline
+ site_description = conf.site_description
+ default_wallet_name = conf.default_wallet_name
+ theme = ",".join(conf.theme)
+ ad_space = ",".join(conf.ad_space)
- if getenv("LNBITS_SITE_TITLE"):
- site_title = getenv("LNBITS_SITE_TITLE")
+ # if getenv("LNBITS_ADMIN_EXTENSIONS"):
+ # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS")
- if getenv("LNBITS_SITE_TAGLINE"):
- site_tagline = getenv("LNBITS_SITE_TAGLINE")
+ # if getenv("LNBITS_DATABASE_URL"):
+ # database_url = getenv("LNBITS_DATABASE_URL")
- if getenv("LNBITS_SITE_DESCRIPTION"):
- site_description = getenv("LNBITS_SITE_DESCRIPTION")
+ # if getenv("LNBITS_HIDE_API"):
+ # hide_api = getenv("LNBITS_HIDE_API")
- if getenv("LNBITS_ALLOWED_USERS"):
- allowed_users = getenv("LNBITS_ALLOWED_USERS")
+ # if getenv("LNBITS_THEME_OPTIONS"):
+ # theme = getenv("LNBITS_THEME_OPTIONS")
- if getenv("LNBITS_ADMIN_USERS"):
- admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
- user = admin_users.split(',')[0]
+ # if getenv("LNBITS_AD_SPACE"):
+ # ad_space = getenv("LNBITS_AD_SPACE")
- if getenv("LNBITS_DEFAULT_WALLET_NAME"):
- default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
+ # if getenv("LNBITS_SITE_TITLE"):
+ # site_title = getenv("LNBITS_SITE_TITLE")
- if getenv("LNBITS_DATA_FOLDER"):
- data_folder = getenv("LNBITS_DATA_FOLDER")
+ # if getenv("LNBITS_SITE_TAGLINE"):
+ # site_tagline = getenv("LNBITS_SITE_TAGLINE")
- if getenv("LNBITS_DISABLED_EXTENSIONS"):
- disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
+ # if getenv("LNBITS_SITE_DESCRIPTION"):
+ # site_description = getenv("LNBITS_SITE_DESCRIPTION")
- if getenv("LNBITS_FORCE_HTTPS"):
- force_https = getenv("LNBITS_FORCE_HTTPS")
+ # if getenv("LNBITS_ALLOWED_USERS"):
+ # allowed_users = getenv("LNBITS_ALLOWED_USERS")
- if getenv("LNBITS_SERVICE_FEE"):
- service_fee = getenv("LNBITS_SERVICE_FEE")
+ # if getenv("LNBITS_ADMIN_USERS"):
+ # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
+ # user = admin_users.split(',')[0]
+
+ # if getenv("LNBITS_DEFAULT_WALLET_NAME"):
+ # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
- if getenv("LNBITS_BACKEND_WALLET_CLASS"):
- funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
+ # if getenv("LNBITS_DATA_FOLDER"):
+ # data_folder = getenv("LNBITS_DATA_FOLDER")
+
+ # if getenv("LNBITS_DISABLED_EXTENSIONS"):
+ # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
+
+ # if getenv("LNBITS_FORCE_HTTPS"):
+ # force_https = getenv("LNBITS_FORCE_HTTPS")
+
+ # if getenv("LNBITS_SERVICE_FEE"):
+ # service_fee = getenv("LNBITS_SERVICE_FEE")
+
+ # if getenv("LNBITS_DENOMINATION"):
+ # denomination = getenv("LNBITS_DENOMINATION", "sats")
+
+ # if getenv("LNBITS_BACKEND_WALLET_CLASS"):
+ # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
await db.execute(
"""
CREATE TABLE IF NOT EXISTS admin (
- "user" TEXT,
+ "user" TEXT PRIMARY KEY,
+ admin_users TEXT,
+ allowed_users TEXT,
+ admin_ext TEXT,
+ disabled_ext TEXT,
+ funding_source TEXT,
+ data_folder TEXT,
+ database_url TEXT,
+ force_https BOOLEAN,
+ service_fee REAL,
+ hide_api BOOLEAN,
+ denomination TEXT,
site_title TEXT,
site_tagline TEXT,
site_description TEXT,
- admin_users TEXT,
- allowed_users TEXT,
default_wallet_name TEXT,
- data_folder TEXT,
- disabled_ext TEXT,
- force_https BOOLEAN,
- service_fee REAL,
- funding_source TEXT
+ theme TEXT,
+ ad_space TEXT
);
"""
)
await db.execute(
"""
- INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """,
- (
- user.strip(),
+ INSERT INTO admin (
+ "user",
+ admin_users,
+ allowed_users,
+ admin_ext,
+ disabled_ext,
+ funding_source,
+ data_folder,
+ database_url,
+ force_https,
+ service_fee,
+ hide_api,
+ denomination,
site_title,
site_tagline,
site_description,
- admin_users[1:],
- allowed_users,
default_wallet_name,
- data_folder,
+ theme,
+ ad_space)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ user,
+ admin_users,
+ allowed_users,
+ admin_ext,
disabled_ext,
+ funding_source,
+ data_folder,
+ database_url,
force_https,
service_fee,
- funding_source,
+ hide_api,
+ denomination,
+ site_title,
+ site_tagline,
+ site_description,
+ default_wallet_name,
+ theme,
+ ad_space,
),
)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 4080ff01..f7c64de5 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -2,7 +2,7 @@ from sqlite3 import Row
from typing import List, Optional
from fastapi import Query
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
class UpdateAdminSettings(BaseModel):
@@ -19,18 +19,27 @@ class UpdateAdminSettings(BaseModel):
funding_source: Optional[str]
class Admin(BaseModel):
+ # users
user: str
+ admin_users: Optional[str]
+ allowed_users: Optional[str]
+ admin_ext: Optional[str]
+ disabled_ext: Optional[str]
+ funding_source: Optional[str]
+ # ops
+ data_folder: Optional[str]
+ database_url: Optional[str]
+ force_https: bool = Field(default=True)
+ service_fee: float = Field(default=0)
+ hide_api: bool = Field(default=False)
+ # Change theme
site_title: Optional[str]
site_tagline: Optional[str]
site_description: Optional[str]
- allowed_users: Optional[str]
- admin_users: str
- default_wallet_name: str
- data_folder: str
- disabled_ext: str
- force_https: Optional[bool] = Query(True)
- service_fee: float
- funding_source: str
+ default_wallet_name: Optional[str]
+ denomination: str = Field(default="sats")
+ theme: Optional[str]
+ ad_space: Optional[str]
@classmethod
def from_row(cls, row: Row) -> "Admin":
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 00a0c99f..105f05a1 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -19,15 +19,17 @@ templates = Jinja2Templates(directory="templates")
@admin_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
- print(g())
funding = [f.dict() for f in await get_funding()]
-
+ error, balance = await g().WALLET.status()
print("ADMIN", admin.dict())
+ print(g().admin_conf)
return admin_renderer().TemplateResponse(
"admin/index.html", {
"request": request,
"user": user.dict(),
"admin": admin.dict(),
- "funding": funding
+ "funding": funding,
+ "settings": g().admin_conf.dict(),
+ "balance": balance
}
)
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 43cb87cb..ed5c77f7 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -4,7 +4,7 @@ from email.policy import default
from os import path
from typing import List
-from environs import Env # type: ignore
+from environs import Env
env = Env()
env.read_env()
From 66a7f53b976ae98a1e18cff8305da1299e524b69 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 10:28:07 +0000
Subject: [PATCH 022/696] topup wallet endpoint
---
lnbits/extensions/admin/crud.py | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 6fccb8ee..683558f9 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,26 +1,31 @@
+import json
from typing import List, Optional
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
from lnbits.settings import *
+from lnbits.tasks import internal_invoice_queue
from . import db
from .models import Admin, Funding
-def update_wallet_balance(wallet_id: str, amount: int) -> str:
+async def update_wallet_balance(wallet_id: str, amount: int) -> str:
temp_id = f"temp_{urlsafe_short_hash()}"
internal_id = f"internal_{urlsafe_short_hash()}"
- create_payment(
+
+ payment = await create_payment(
wallet_id=wallet_id,
checking_id=internal_id,
payment_request="admin_internal",
payment_hash="admin_internal",
- amount=amount * 1000,
+ amount=amount*1000,
memo="Admin top up",
pending=False,
)
- return "success"
+ # manually send this for now
+ await internal_invoice_queue.put(internal_id)
+ return payment
async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
From 663c7ebd2f52c4cc0ea57839d921e3730d4decef Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 10:28:44 +0000
Subject: [PATCH 023/696] update admin settings in db
---
lnbits/extensions/admin/models.py | 29 +++++++++++++++++-----------
lnbits/extensions/admin/views_api.py | 8 ++++----
2 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index f7c64de5..36d9b815 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -6,17 +6,24 @@ from pydantic import BaseModel, Field
class UpdateAdminSettings(BaseModel):
- site_title: Optional[str]
- site_tagline: Optional[str]
- site_description: Optional[str]
- allowed_users: Optional[str]
- admin_users: Optional[str]
- default_wallet_name: Optional[str]
- data_folder: Optional[str]
- disabled_ext: Optional[str]
- force_https: Optional[bool]
- service_fee: Optional[float]
- funding_source: Optional[str]
+ # users
+ admin_users: str = Query(None)
+ allowed_users: str = Query(None)
+ admin_ext: str = Query(None)
+ disabled_ext: str = Query(None)
+ funding_source: str = Query(None)
+ # ops
+ force_https: bool = Query(None)
+ service_fee: float = Query(None, ge=0)
+ hide_api: bool = Query(None)
+ # Change theme
+ site_title: str = Query(None)
+ site_tagline: str = Query(None)
+ site_description: str = Query(None)
+ default_wallet_name: str = Query(None)
+ denomination: str = Query(None)
+ theme: str = Query(None)
+ ad_space: str = Query(None)
class Admin(BaseModel):
# users
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index b2c65be2..cb526aa5 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -12,16 +12,16 @@ from .crud import get_admin, update_admin, update_wallet_balance
@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
-async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)):
- print(g.wallet)
+async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)):
try:
wallet = await get_wallet(wallet_id)
except:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
)
- print(wallet)
- print(topup_amount)
+
+ await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
+
return {"status": "Success"}
From bc090190fca9a170566e1d605bdbdbd3536c70f5 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 10:29:18 +0000
Subject: [PATCH 024/696] update settings and topup logic
---
.../admin/templates/admin/index.html | 91 ++++++++++++-------
1 file changed, 57 insertions(+), 34 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 65ac9f33..e9ddc7c4 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -30,7 +30,7 @@
-
+
@@ -61,7 +61,7 @@
@@ -577,7 +577,6 @@
Site Description
s !== ad)
},
- topupWallet: function () {
- var self = this
+ topupWallet() {
LNbits.api
.request(
'GET',
'/admin/api/v1/admin/' +
- self.wallet.id +
+ this.wallet.data.id +
'/' +
- self.wallet.data.amount,
- self.g.user.wallets[0].adminkey
+ this.wallet.data.amount,
+ this.g.user.wallets[0].adminkey
)
- .then(function (response) {
- self.$q.notify({
+ .then((response) => {
+ this.$q.notify({
type: 'positive',
message:
- 'Success! Added ' +
- self.wallet.amount +
- ' to ' +
- self.wallet.id,
+ 'Success! Added ' +
+ this.wallet.data.amount +
+ ' to ' +
+ this.wallet.data.id,
icon: null
})
+ this.wallet.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -1224,36 +1224,59 @@
self.data.admin.edited.push(source)
console.log(self.data.admin.edited)
},
- UpdateLNbits: function () {
- var self = this
- let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin
+ UpdateLNbits() {
+ let {
+ admin_users,
+ allowed_users,
+ admin_ext,
+ disabled_ext,
+ funding_source,
+ force_https,
+ service_fee,
+ hide_api,
+ site_title,
+ site_tagline,
+ site_description,
+ default_wallet_name,
+ denomination,
+ theme,
+ ad_space
+ } = this.data.admin
+ //console.log("this", this.data.admin)
let data = {
- site_title,
- site_tagline: this.data.admin.tagline,
- site_description: this.data.admin.description,
- admin_users: admin_users.toString(),
- default_wallet_name,
- data_folder,
+ admin_users: admin_users.toString(),
+ allowed_users: allowed_users.toString(),
+ admin_ext: admin_ext.toString(),
disabled_ext: disabled_ext.toString(),
- service_fee,
- funding_source: funding_source_primary}
+ funding_source,
+ force_https,
+ service_fee,
+ hide_api,
+ site_title,
+ site_tagline,
+ site_description,
+ default_wallet_name,
+ denomination,
+ theme: theme.toString(),
+ ad_space: ad_space.toString()
+ }
console.log(data)
LNbits.api
.request(
'POST',
'/admin/api/v1/admin/',
- self.g.user.wallets[0].adminkey,
+ this.g.user.wallets[0].adminkey,
data
)
- .then(function (response) {
+ .then(response => {
console.log(response.data)
- self.$q.notify({
+ this.$q.notify({
type: 'positive',
message:
'Success! Added ' +
- self.wallet.amount +
+ this.wallet.amount +
' to ' +
- self.wallet.id,
+ this.wallet.id,
icon: null
})
})
From 313574df1991c47b6a0dbc390f8d839278e200d4 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:34:47 +0000
Subject: [PATCH 025/696] make removeEmptyString fn as helper fn
---
lnbits/app.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index ccac1e00..5df439dc 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -26,6 +26,7 @@ from .helpers import (
get_css_vendored,
get_js_vendored,
get_valid_extensions,
+ removeEmptyString,
template_renderer,
url_for_vendored,
)
@@ -113,9 +114,6 @@ def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
- def removeEmptyString(arr):
- return list(filter(None, arr))
-
while True:
admin_set = await get_admin_settings()
if admin_set :
From edfa98f00e7111096321d259fca4cd2eb36ac3d5 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:35:15 +0000
Subject: [PATCH 026/696] add some defaults
---
lnbits/config.py | 6 +++---
lnbits/extensions/admin/models.py | 8 ++++----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index 02e8cf53..b2fbfff1 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -33,10 +33,10 @@ class Settings(BaseSettings):
hide_api: bool = Field(default=False, env="LNBITS_HIDE_API")
denomination: str = Field(default="sats", env="LNBITS_DENOMINATION")
# Change theme
- site_title: str = Field(default=None, env="LNBITS_SITE_TITLE")
- site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE")
+ site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE")
+ site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE")
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
- default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME")
+ default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 36d9b815..0f25679d 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -17,11 +17,11 @@ class UpdateAdminSettings(BaseModel):
service_fee: float = Query(None, ge=0)
hide_api: bool = Query(None)
# Change theme
- site_title: str = Query(None)
- site_tagline: str = Query(None)
+ site_title: str = Query("LNbits")
+ site_tagline: str = Query("free and open-source lightning wallet")
site_description: str = Query(None)
- default_wallet_name: str = Query(None)
- denomination: str = Query(None)
+ default_wallet_name: str = Query("LNbits wallet")
+ denomination: str = Query("sats")
theme: str = Query(None)
ad_space: str = Query(None)
From ba6bda39ab4a6c50631658d9bee85329c4784950 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:36:04 +0000
Subject: [PATCH 027/696] removeEmtpy sting as helper fn
---
lnbits/helpers.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index e213240c..e456f715 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -154,8 +154,20 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s
url = f"{base}{endpoint}{url_params}"
return url
+def removeEmptyString(arr):
+ return list(filter(None, arr))
def template_renderer(additional_folders: List = []) -> Jinja2Templates:
+ if(settings.LNBITS_ADMIN_UI):
+ _ = g().admin_conf
+ settings.LNBITS_AD_SPACE = _.ad_space
+ settings.LNBITS_HIDE_API = _.hide_api
+ settings.LNBITS_SITE_TITLE = _.site_title
+ settings.LNBITS_DENOMINATION = _.denomination
+ settings.LNBITS_SITE_TAGLINE = _.site_tagline
+ settings.LNBITS_SITE_DESCRIPTION = _.site_description
+ settings.LNBITS_THEME_OPTIONS = _.theme
+
t = Jinja2Templates(
loader=jinja2.FileSystemLoader(
["lnbits/templates", "lnbits/core/templates", *additional_folders]
From 0a211a2fb2d1fdf7c135cf637b768c8fc2855a64 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:41:11 +0000
Subject: [PATCH 028/696] cleanup
---
lnbits/extensions/admin/crud.py | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 683558f9..e14ad194 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,9 +1,7 @@
-import json
-from typing import List, Optional
+from typing import List
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
-from lnbits.settings import *
from lnbits.tasks import internal_invoice_queue
from . import db
@@ -37,14 +35,6 @@ async def update_admin(user: str, **kwargs) -> Admin:
assert row, "Newly updated settings couldn't be retrieved"
return Admin(**row) if row else None
-# async def update_admin(user: str, **kwargs) -> Optional[Admin]:
-# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
-# await db.execute(
-# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user)
-# )
-# new_settings = await get_admin()
-# return new_settings
-
async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin")
return Admin(**row) if row else None
From 5ec7f21650897699f39723bbe93edd3d53da1fd2 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:42:28 +0000
Subject: [PATCH 029/696] make string to list
---
lnbits/extensions/admin/views_api.py | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index cb526aa5..1d4e6a9c 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,5 +1,6 @@
from http import HTTPStatus
+# from config import conf
from fastapi import Body, Depends, Request
from starlette.exceptions import HTTPException
@@ -7,6 +8,8 @@ from lnbits.core.crud import get_wallet
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import Admin, UpdateAdminSettings
+from lnbits.helpers import removeEmptyString
+from lnbits.requestvars import g
from .crud import get_admin, update_admin, update_wallet_balance
@@ -19,7 +22,7 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
)
-
+
await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
return {"status": "Success"}
@@ -29,14 +32,24 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D
async def api_update_admin(
request: Request,
data: UpdateAdminSettings = Body(...),
- g: WalletTypeInfo = Depends(require_admin_key)
+ w: WalletTypeInfo = Depends(require_admin_key)
):
admin = await get_admin()
print(data)
- if not admin.user == g.wallet.user:
+ if not admin.user == w.wallet.user:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
)
- updated = await update_admin(user=g.wallet.user, **data.dict())
- print(updated)
+ updated = await update_admin(user=w.wallet.user, **data.dict())
+
+ updated.admin_users = removeEmptyString(updated.admin_users.split(','))
+ updated.allowed_users = removeEmptyString(updated.allowed_users.split(','))
+ updated.admin_ext = removeEmptyString(updated.admin_ext.split(','))
+ updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(','))
+ updated.theme = removeEmptyString(updated.theme.split(','))
+ updated.ad_space = removeEmptyString(updated.ad_space.split(','))
+
+ g().admin_conf = g().admin_conf.copy(update=updated.dict())
+
+ print(g().admin_conf)
return {"status": "Success"}
From 4d16c296aa1d8b8375e3f131e459a2f2f80f0d3b Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:42:47 +0000
Subject: [PATCH 030/696] success message
---
lnbits/extensions/admin/templates/admin/index.html | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index e9ddc7c4..9aa4f12a 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1273,10 +1273,7 @@
this.$q.notify({
type: 'positive',
message:
- 'Success! Added ' +
- this.wallet.amount +
- ' to ' +
- this.wallet.id,
+ 'Success! Settings changed!',
icon: null
})
})
From 2c48e3aa5f1e561e4106c60bfe0e4266a86711b3 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 14 Apr 2022 10:42:26 +0100
Subject: [PATCH 031/696] allow html to be passed to description
---
lnbits/core/templates/core/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html
index f769b44f..03cf706f 100644
--- a/lnbits/core/templates/core/index.html
+++ b/lnbits/core/templates/core/index.html
@@ -82,7 +82,7 @@
>
-
{{SITE_DESCRIPTION}}
+
{{SITE_DESCRIPTION | safe}}
From f16ead4f7303787d945ab9dc39550ae3bc0ec611 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 14 Apr 2022 16:37:13 +0100
Subject: [PATCH 032/696] update funding wallets
---
lnbits/extensions/admin/crud.py | 12 +
.../admin/templates/admin/index.html | 523 ++++++++++--------
lnbits/extensions/admin/views.py | 3 +-
lnbits/extensions/admin/views_api.py | 19 +-
4 files changed, 315 insertions(+), 242 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index e14ad194..dd39e8e4 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -39,6 +39,18 @@ async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin")
return Admin(**row) if row else None
+async def update_funding(data: Funding) -> Funding:
+ await db.execute(
+ """
+ UPDATE funding
+ SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ?
+ WHERE id = ?
+ """,
+ (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,),
+ )
+ row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,))
+ assert row, "Newly updated settings couldn't be retrieved"
+ return Funding(**row) if row else None
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM funding")
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 9aa4f12a..d56b3d79 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -31,11 +31,11 @@
-
-
-
- Wallets Management
-
+
+
+
+ Wallets Management
+
@@ -62,43 +62,96 @@
-
TopUp a wallet
-
-
-
-
-
-
-
-
+
TopUp a wallet
+
Funding Sources
-
+ {% raw %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+ User Management
+
+
+ Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %}
+
+
Admin Users
+ hint="Users with admin privileges"
+ >
{% raw %}
-
-
-
- {% raw %}
-
Allowed Users
+
- {{ user }}
-
- {% endraw %}
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
+
Disabled Extensions
+
+
+
+
+
+ Save
+
+
+
+
+
+ Server Management
-
-
-
-
-
Disabled Extensions
-
-
-
-
-
- Save
-
-
-
-
-
- Server Management
-
Server Info
{%raw%}
- SQlite: {{data.admin.data_folder}}
- Postgres: {{data.admin.database_url}}
+
+ SQlite: {{data.admin.data_folder}}
+
+
+ Postgres: {{data.admin.database_url}}
+
{%endraw%}
@@ -520,7 +576,10 @@
Hide API
- Hides wallet api, extensions can choose to honor
+ Hides wallet api, extensions can choose to
+ honor
-
-
-
- Save
-
-
-
-
-
- UI Management
-
+
+
+
+ Save
+
+
+
+
+
+ UI Management
+
+
Default Wallet Name
@@ -628,12 +682,15 @@
@keydown.enter="addAdSpace"
type="text"
label="Ad image URL"
- hint="Ad image filepaths or urls, extensions can choose to honor">
+ hint="Ad image filepaths or urls, extensions can choose to honor"
+ >
{% raw %}
-
-
-
-
- Save
-
-
-
-
-
+
+
+
+ Save
+
+
+
+
+
-
-
-Admin
-
+
+
-
-
-
-
- Wallet topup
-
-
-
-
-
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }}
@@ -1100,14 +1114,22 @@
'LnbitsWallet',
'OpenNodeWallet'
],
-
+
admin: {
edited: [],
- funding: {},
+ funding: [],
senddata: {}
}
},
- themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'],
+ themes: [
+ 'classic',
+ 'bitcoin',
+ 'flamingo',
+ 'mint',
+ 'autumn',
+ 'monochrome',
+ 'salvador'
+ ],
options: [
'bleskomat',
'captcha',
@@ -1139,10 +1161,13 @@
self.cancel.on = true
}
funding = JSON.parse(String('{{ funding | tojson|safe }}'))
- var i
+ funding.map(f => {
+ this.data.admin.funding.push(f)
+ })
+ /*var i
for (i = 0; i < funding.length; i++) {
self.data.admin.funding[funding[i].backend_wallet] = funding[i]
- }
+ }*/
let settings = JSON.parse('{{ settings | tojson|safe }}')
settings.balance = '{{ balance }}'
this.data.admin = {...this.data.admin, ...settings}
@@ -1150,42 +1175,42 @@
console.log(settings)
},
methods: {
- addAdminUser(){
+ addAdminUser() {
let addUser = this.data.admin_users_add
let admin_users = this.data.admin.admin_users
- if(addUser.length && !admin_users.includes(addUser)){
+ if (addUser.length && !admin_users.includes(addUser)) {
admin_users.push(addUser)
this.data.admin.admin_users = admin_users
- this.data.admin_users_add = ""
+ this.data.admin_users_add = ''
}
},
- removeAdminUser(user){
+ removeAdminUser(user) {
let admin_users = this.data.admin.admin_users
this.data.admin.admin_users = admin_users.filter(u => u !== user)
},
- addAllowedUser(){
+ addAllowedUser() {
let addUser = this.data.allowed_users_add
let allowed_users = this.data.admin.allowed_users
- if(addUser.length && !allowed_users.includes(addUser)){
+ if (addUser.length && !allowed_users.includes(addUser)) {
allowed_users.push(addUser)
this.data.admin.allowed_users = allowed_users
- this.data.allowed_users_add = ""
+ this.data.allowed_users_add = ''
}
},
- removeAllowedUser(user){
+ removeAllowedUser(user) {
let allowed_users = this.data.admin.allowed_users
this.data.admin.allowed_users = allowed_users.filter(u => u !== user)
},
- addAdSpace(){
+ addAdSpace() {
let adSpace = this.data.ad_space_add
let spaces = this.data.admin.ad_space
- if(adSpace.length && !spaces.includes(adSpace)){
+ if (adSpace.length && !spaces.includes(adSpace)) {
spaces.push(adSpace)
this.data.admin.ad_space = spaces
- this.data.ad_space_add = ""
+ this.data.ad_space_add = ''
}
},
- removeAdSpace(ad){
+ removeAdSpace(ad) {
let spaces = this.data.admin.ad_space
this.data.admin.ad_space = spaces.filter(s => s !== ad)
},
@@ -1199,14 +1224,14 @@
this.wallet.data.amount,
this.g.user.wallets[0].adminkey
)
- .then((response) => {
+ .then(response => {
this.$q.notify({
type: 'positive',
message:
- 'Success! Added ' +
- this.wallet.data.amount +
- ' to ' +
- this.wallet.data.id,
+ 'Success! Added ' +
+ this.wallet.data.amount +
+ ' to ' +
+ this.wallet.data.id,
icon: null
})
this.wallet.data = {}
@@ -1224,6 +1249,29 @@
self.data.admin.edited.push(source)
console.log(self.data.admin.edited)
},
+ updateFunding(fund) {
+ let data = this.data.admin.funding.find(v => v.backend_wallet == fund)
+
+ LNbits.api
+ .request(
+ 'POST',
+ '/admin/api/v1/admin/funding',
+ this.g.user.wallets[0].adminkey,
+ data
+ )
+ .then(response => {
+ //let wallet = response.data.backend_wallet
+ //this.data.admin.funding[wallet] = response.data
+ //this.data.admin.funding[wallet].endpoint = response.data.endpoint
+ //console.log(this.data.admin.funding)
+ //console.log(this.data.admin)
+ this.$q.notify({
+ type: 'positive',
+ message: `Success! ${response.data.backend_wallet} changed!`,
+ icon: null
+ })
+ })
+ },
UpdateLNbits() {
let {
admin_users,
@@ -1272,8 +1320,7 @@
console.log(response.data)
this.$q.notify({
type: 'positive',
- message:
- 'Success! Settings changed!',
+ message: 'Success! Settings changed!',
icon: null
})
})
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 105f05a1..24b8ca85 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -21,8 +21,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
funding = [f.dict() for f in await get_funding()]
error, balance = await g().WALLET.status()
- print("ADMIN", admin.dict())
- print(g().admin_conf)
+
return admin_renderer().TemplateResponse(
"admin/index.html", {
"request": request,
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 1d4e6a9c..b797dc2d 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -7,11 +7,11 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_wallet
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.admin import admin_ext
-from lnbits.extensions.admin.models import Admin, UpdateAdminSettings
+from lnbits.extensions.admin.models import Admin, Funding, UpdateAdminSettings
from lnbits.helpers import removeEmptyString
from lnbits.requestvars import g
-from .crud import get_admin, update_admin, update_wallet_balance
+from .crud import get_admin, update_admin, update_funding, update_wallet_balance
@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
@@ -53,3 +53,18 @@ async def api_update_admin(
print(g().admin_conf)
return {"status": "Success"}
+
+@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK)
+async def api_update_funding(
+ request: Request,
+ data: Funding = Body(...),
+ w: WalletTypeInfo = Depends(require_admin_key)
+ ):
+ admin = await get_admin()
+
+ if not admin.user == w.wallet.user:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
+ funding = await update_funding(data=data)
+ return funding
From 5a3ad81c315f42a7b482714e943a1b0a72028e93 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 18 Apr 2022 14:25:06 +0100
Subject: [PATCH 033/696] allow user settings without restart
---
lnbits/core/views/generic.py | 7 ++++++-
lnbits/decorators.py | 8 ++++++++
lnbits/helpers.py | 3 +++
3 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 31a7b030..83648c44 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -15,7 +15,9 @@ from lnbits.core import db
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from lnbits.helpers import template_renderer, url_for
+from lnbits.requestvars import g
from lnbits.settings import (
+ LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
LNBITS_CUSTOM_LOGO,
@@ -37,7 +39,6 @@ from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
-
@core_html_routes.get("/favicon.ico", response_class=FileResponse)
async def favicon():
return FileResponse("lnbits/core/static/favicon.ico")
@@ -119,6 +120,10 @@ async def wallet(
wallet_name = nme
service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE
+ if LNBITS_ADMIN_UI:
+ LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+ LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
+
if not user_id:
user = await get_user((await create_account()).id)
logger.info(f"Create user {user.id}") # type: ignore
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index d4aa63ae..f951163f 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -16,6 +16,7 @@ from lnbits.core.models import User, Wallet
from lnbits.requestvars import g
from lnbits.settings import (
LNBITS_ADMIN_EXTENSIONS,
+ LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
)
@@ -138,6 +139,9 @@ async def get_key_type(
detail="Invoice (or Admin) key required.",
)
+ if LNBITS_ADMIN_UI:
+ LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+
for typenr, WalletChecker in zip(
[0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker]
):
@@ -231,6 +235,10 @@ async def check_user_exists(usr: UUID4) -> User:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
)
+
+ if LNBITS_ADMIN_UI:
+ LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+ LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
raise HTTPException(
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index e456f715..1167143f 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -24,6 +24,9 @@ class Extension(NamedTuple):
class ExtensionManager:
def __init__(self):
+ if settings.LNBITS_ADMIN_UI:
+ settings.LNBITS_DISABLED_EXTENSIONS = g().admin_conf.disabled_ext
+ settings.LNBITS_ADMIN_EXTENSIONS = g().admin_conf.admin_ext
self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS
self._admin_only: List[str] = [
x.strip(" ") for x in settings.LNBITS_ADMIN_EXTENSIONS
From 1ff8a9fce5c15d421a8c37a3f8bc908a4b249510 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 18 Apr 2022 14:25:26 +0100
Subject: [PATCH 034/696] advert for server restart option
---
lnbits/extensions/admin/templates/admin/index.html | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d56b3d79..089c5f1c 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -51,7 +51,9 @@
-
Active Funding
+
+ Active Funding (Requires server restart)
+
Date: Thu, 21 Apr 2022 11:08:26 +0100
Subject: [PATCH 035/696] cleanup prints and console logs
---
lnbits/extensions/admin/crud.py | 2 +-
lnbits/extensions/admin/templates/admin/index.html | 4 ++--
lnbits/extensions/admin/views_api.py | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index dd39e8e4..f866bc1a 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -27,7 +27,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
- print("UPDATE", q)
+ # print("UPDATE", q)
await db.execute(
f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 089c5f1c..584d3a33 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1310,7 +1310,7 @@
theme: theme.toString(),
ad_space: ad_space.toString()
}
- console.log(data)
+ //console.log(data)
LNbits.api
.request(
'POST',
@@ -1319,7 +1319,7 @@
data
)
.then(response => {
- console.log(response.data)
+ //console.log(response.data)
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index b797dc2d..c0650c8a 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -35,7 +35,7 @@ async def api_update_admin(
w: WalletTypeInfo = Depends(require_admin_key)
):
admin = await get_admin()
- print(data)
+ # print(data)
if not admin.user == w.wallet.user:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
@@ -51,7 +51,7 @@ async def api_update_admin(
g().admin_conf = g().admin_conf.copy(update=updated.dict())
- print(g().admin_conf)
+ # print(g().admin_conf)
return {"status": "Success"}
@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK)
From b0f9c82e1b0b16e3e1d499f5173a0368e5d9c93e Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 10:49:21 +0100
Subject: [PATCH 036/696] create first user on fresh install
---
.env.example | 4 ++--
lnbits/config.py | 3 ++-
lnbits/extensions/admin/README.md | 15 ++++++++-------
lnbits/extensions/admin/migrations.py | 15 ++++++++++++++-
4 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/.env.example b/.env.example
index bd189484..7a49d5c5 100644
--- a/.env.example
+++ b/.env.example
@@ -4,8 +4,8 @@ PORT=5000
DEBUG=false
LNBITS_ADMIN_USERS="" # User IDs seperated by comma
-LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access
-LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS
+LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access
+LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
diff --git a/lnbits/config.py b/lnbits/config.py
index b2fbfff1..3ce51c3c 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -19,6 +19,7 @@ def list_parse_fallback(v):
return v.replace(' ','').split(',')
class Settings(BaseSettings):
+ admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI")
# users
admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS")
allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS")
@@ -37,7 +38,7 @@ class Settings(BaseSettings):
site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE")
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
- theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS")
+ theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
env: Optional[str]
diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md
index 27729459..6cf073a1 100644
--- a/lnbits/extensions/admin/README.md
+++ b/lnbits/extensions/admin/README.md
@@ -1,11 +1,12 @@
-Example Extension
-*tagline*
-This is an example extension to help you organise and build you own.
+# Admin Extension
-Try to include an image
-
+## Dashboard to manage LNbits from the UI
+With AdminUI you can manage your LNbits from the UI
-If your extension has API endpoints, include useful ones here
+
-curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"
+## Before you start
+
+**This extension doesn't discard the need for the `.env` file!**
+In the .env file, set the `LNBITS_ADMIN_USERS` variable to include at least your user id.
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 574f772d..0e22e667 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -6,9 +6,22 @@ from lnbits.config import conf
from lnbits.helpers import urlsafe_short_hash
+async def get_admin_user():
+ if(conf.admin_users[0]):
+ return conf.admin_users[0]
+ from lnbits.core.crud import create_account, get_user
+ print("Seems like there's no admin users yet. Let's create an account for you!")
+ account = await create_account()
+ user = account.id
+ assert user, "Newly created user couldn't be retrieved"
+ print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.")
+ return user
+
+
+
async def m001_create_admin_table(db):
# users/server
- user = conf.admin_users[0]
+ user = await get_admin_user()
admin_users = ",".join(conf.admin_users)
allowed_users = ",".join(conf.allowed_users)
admin_ext = ",".join(conf.admin_ext)
From 2f2d70f9a8cdd3edc59cb3c71b841b6823309dcf Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 12:29:58 +0100
Subject: [PATCH 037/696] fix schemas for admin
---
lnbits/commands.py | 11 ++++++++---
lnbits/extensions/admin/crud.py | 12 ++++++------
lnbits/extensions/admin/migrations.py | 26 +++++++++++++-------------
3 files changed, 27 insertions(+), 22 deletions(-)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 8c39c338..7d9b49e2 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -55,8 +55,12 @@ def bundle_vendored():
async def get_admin_settings():
from lnbits.extensions.admin.models import Admin
- async with core_db.connect() as conn:
+ try:
+ ext_db = importlib.import_module(f"lnbits.extensions.admin").db
+ except:
+ return False
+ async with ext_db.connect() as conn:
if conn.type == SQLITE:
exists = await conn.fetchone(
"SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
@@ -65,11 +69,12 @@ async def get_admin_settings():
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
+ print("EXISTS", exists)
if not exists:
return False
- row = await conn.fetchone("SELECT * from admin")
-
+ row = await conn.fetchone("SELECT * from admin.admin")
+
return Admin(**row) if row else None
async def migrate_databases():
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index f866bc1a..67fbc614 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -29,30 +29,30 @@ async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
# print("UPDATE", q)
await db.execute(
- f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
+ f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
- row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,))
+ row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,))
assert row, "Newly updated settings couldn't be retrieved"
return Admin(**row) if row else None
async def get_admin() -> Admin:
- row = await db.fetchone("SELECT * FROM admin")
+ row = await db.fetchone("SELECT * FROM admin.admin")
return Admin(**row) if row else None
async def update_funding(data: Funding) -> Funding:
await db.execute(
"""
- UPDATE funding
+ UPDATE admin.funding
SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ?
WHERE id = ?
""",
(data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,),
)
- row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,))
+ row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,))
assert row, "Newly updated settings couldn't be retrieved"
return Funding(**row) if row else None
async def get_funding() -> List[Funding]:
- rows = await db.fetchall("SELECT * FROM funding")
+ rows = await db.fetchall("SELECT * FROM admin.funding")
return [Funding(**row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 0e22e667..c94d140b 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -96,7 +96,7 @@ async def m001_create_admin_table(db):
await db.execute(
"""
- CREATE TABLE IF NOT EXISTS admin (
+ CREATE TABLE IF NOT EXISTS admin.admin (
"user" TEXT PRIMARY KEY,
admin_users TEXT,
allowed_users TEXT,
@@ -120,7 +120,7 @@ async def m001_create_admin_table(db):
)
await db.execute(
"""
- INSERT INTO admin (
+ INSERT INTO admin.admin (
"user",
admin_users,
allowed_users,
@@ -171,7 +171,7 @@ async def m001_create_funding_table(db):
# Make the funding table, if it does not already exist
await db.execute(
"""
- CREATE TABLE IF NOT EXISTS funding (
+ CREATE TABLE IF NOT EXISTS admin.funding (
id TEXT PRIMARY KEY,
backend_wallet TEXT,
endpoint TEXT,
@@ -188,7 +188,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, selected)
VALUES (?, ?, ?, ?)
""",
(
@@ -200,7 +200,7 @@ async def m001_create_funding_table(db):
)
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -214,7 +214,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -228,7 +228,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
@@ -244,7 +244,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
@@ -259,7 +259,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
@@ -274,7 +274,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -288,7 +288,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -302,7 +302,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -317,7 +317,7 @@ async def m001_create_funding_table(db):
## PLACEHOLDER FOR ECLAIR WALLET
# await db.execute(
# """
- # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
# VALUES (?, ?, ?, ?, ?)
# """,
# (
From 08e54de99b72eec2c8f7850ef0d346c15bf64705 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 12:53:47 +0100
Subject: [PATCH 038/696] fix sqlite and show user account
---
lnbits/app.py | 1 +
lnbits/commands.py | 2 +-
lnbits/extensions/admin/migrations.py | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 5df439dc..f066163f 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -117,6 +117,7 @@ def check_settings(app: FastAPI):
while True:
admin_set = await get_admin_settings()
if admin_set :
+ print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
break
print("ERROR:", admin_set)
await asyncio.sleep(5)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 7d9b49e2..763a5b90 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -69,7 +69,7 @@ async def get_admin_settings():
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
- print("EXISTS", exists)
+
if not exists:
return False
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index c94d140b..6c5b507d 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -15,6 +15,7 @@ async def get_admin_user():
user = account.id
assert user, "Newly created user couldn't be retrieved"
print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.")
+ conf.admin_users.insert(0, user)
return user
From 1adfb674ccb6bf44ad6e3680c795ed085bd20d8e Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 15:35:04 +0100
Subject: [PATCH 039/696] cleanup and info to user on startup
---
lnbits/app.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index f066163f..950b6140 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -113,13 +113,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
-
while True:
admin_set = await get_admin_settings()
if admin_set :
- print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
break
- print("ERROR:", admin_set)
+ print("Waiting for admin settings... retrying in 5 seconds!")
await asyncio.sleep(5)
admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(','))
@@ -129,6 +127,7 @@ def check_settings(app: FastAPI):
admin_set.theme = removeEmptyString(admin_set.theme.split(','))
admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(','))
g().admin_conf = conf.copy(update=admin_set.dict())
+ print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
From 363bc85e3b73967c26b9809f1d71664ec8719d8d Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 8 Jun 2022 11:00:43 +0100
Subject: [PATCH 040/696] add custom logo
---
lnbits/config.py | 1 +
lnbits/extensions/admin/migrations.py | 58 ++-----------------
lnbits/extensions/admin/models.py | 2 +
.../admin/templates/admin/index.html | 20 +++++--
lnbits/helpers.py | 3 +-
5 files changed, 26 insertions(+), 58 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index 3ce51c3c..d07ca044 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -39,6 +39,7 @@ class Settings(BaseSettings):
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS")
+ custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
env: Optional[str]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 6c5b507d..aad66f02 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -41,60 +41,9 @@ async def m001_create_admin_table(db):
site_description = conf.site_description
default_wallet_name = conf.default_wallet_name
theme = ",".join(conf.theme)
+ custom_logo = conf.custom_logo
ad_space = ",".join(conf.ad_space)
- # if getenv("LNBITS_ADMIN_EXTENSIONS"):
- # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS")
-
- # if getenv("LNBITS_DATABASE_URL"):
- # database_url = getenv("LNBITS_DATABASE_URL")
-
- # if getenv("LNBITS_HIDE_API"):
- # hide_api = getenv("LNBITS_HIDE_API")
-
- # if getenv("LNBITS_THEME_OPTIONS"):
- # theme = getenv("LNBITS_THEME_OPTIONS")
-
- # if getenv("LNBITS_AD_SPACE"):
- # ad_space = getenv("LNBITS_AD_SPACE")
-
- # if getenv("LNBITS_SITE_TITLE"):
- # site_title = getenv("LNBITS_SITE_TITLE")
-
- # if getenv("LNBITS_SITE_TAGLINE"):
- # site_tagline = getenv("LNBITS_SITE_TAGLINE")
-
- # if getenv("LNBITS_SITE_DESCRIPTION"):
- # site_description = getenv("LNBITS_SITE_DESCRIPTION")
-
- # if getenv("LNBITS_ALLOWED_USERS"):
- # allowed_users = getenv("LNBITS_ALLOWED_USERS")
-
- # if getenv("LNBITS_ADMIN_USERS"):
- # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
- # user = admin_users.split(',')[0]
-
- # if getenv("LNBITS_DEFAULT_WALLET_NAME"):
- # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
-
- # if getenv("LNBITS_DATA_FOLDER"):
- # data_folder = getenv("LNBITS_DATA_FOLDER")
-
- # if getenv("LNBITS_DISABLED_EXTENSIONS"):
- # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
-
- # if getenv("LNBITS_FORCE_HTTPS"):
- # force_https = getenv("LNBITS_FORCE_HTTPS")
-
- # if getenv("LNBITS_SERVICE_FEE"):
- # service_fee = getenv("LNBITS_SERVICE_FEE")
-
- # if getenv("LNBITS_DENOMINATION"):
- # denomination = getenv("LNBITS_DENOMINATION", "sats")
-
- # if getenv("LNBITS_BACKEND_WALLET_CLASS"):
- # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
-
await db.execute(
"""
CREATE TABLE IF NOT EXISTS admin.admin (
@@ -115,6 +64,7 @@ async def m001_create_admin_table(db):
site_description TEXT,
default_wallet_name TEXT,
theme TEXT,
+ custom_logo TEXT,
ad_space TEXT
);
"""
@@ -139,8 +89,9 @@ async def m001_create_admin_table(db):
site_description,
default_wallet_name,
theme,
+ custom_logo,
ad_space)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
user,
@@ -160,6 +111,7 @@ async def m001_create_admin_table(db):
site_description,
default_wallet_name,
theme,
+ custom_logo,
ad_space,
),
)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 0f25679d..3b17e720 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -23,6 +23,7 @@ class UpdateAdminSettings(BaseModel):
default_wallet_name: str = Query("LNbits wallet")
denomination: str = Query("sats")
theme: str = Query(None)
+ custom_logo: str = Query(None)
ad_space: str = Query(None)
class Admin(BaseModel):
@@ -46,6 +47,7 @@ class Admin(BaseModel):
default_wallet_name: Optional[str]
denomination: str = Field(default="sats")
theme: Optional[str]
+ custom_logo: Optional[str]
ad_space: Optional[str]
@classmethod
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 584d3a33..d9790051 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -705,6 +705,19 @@
+
@@ -718,10 +731,7 @@
-
+
@@ -1292,6 +1323,8 @@
disabled_ext,
funding_source,
force_https,
+ reserve_fee_min,
+ reserve_fee_pct,
service_fee,
hide_api,
site_title,
@@ -1311,6 +1344,8 @@
disabled_ext: disabled_ext.toString(),
funding_source,
force_https,
+ reserve_fee_min,
+ reserve_fee_pct,
service_fee,
hide_api,
site_title,
diff --git a/lnbits/settings.py b/lnbits/settings.py
index ed5c77f7..8e5c321a 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,6 +1,5 @@
import importlib
import subprocess
-from email.policy import default
from os import path
from typing import List
From fcf05a7dd19b831ee986578efad9b2d9094e69bd Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 8 Jun 2022 15:38:28 +0100
Subject: [PATCH 042/696] calle's semantics
---
lnbits/extensions/admin/templates/admin/index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 832629bc..d34b9068 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -67,7 +67,7 @@
-
Minimum wallet reserve
+
Fee reserve
From faab389e3fa21671313b66236e66fccbf1f70a1d Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 5 Jul 2022 16:25:02 +0100
Subject: [PATCH 043/696] blacked
---
lnbits/extensions/admin/__init__.py | 1 +
lnbits/extensions/admin/crud.py | 23 ++++++++++++---
lnbits/extensions/admin/migrations.py | 10 ++++---
lnbits/extensions/admin/models.py | 2 ++
lnbits/extensions/admin/views.py | 10 ++++---
lnbits/extensions/admin/views_api.py | 41 ++++++++++++++-------------
6 files changed, 56 insertions(+), 31 deletions(-)
diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py
index 6a56b2bb..24b91fe2 100644
--- a/lnbits/extensions/admin/__init__.py
+++ b/lnbits/extensions/admin/__init__.py
@@ -7,6 +7,7 @@ db = Database("ext_admin")
admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"])
+
def admin_renderer():
return template_renderer(["lnbits/extensions/admin/templates"])
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 67fbc614..0d7019cc 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -11,13 +11,13 @@ from .models import Admin, Funding
async def update_wallet_balance(wallet_id: str, amount: int) -> str:
temp_id = f"temp_{urlsafe_short_hash()}"
internal_id = f"internal_{urlsafe_short_hash()}"
-
+
payment = await create_payment(
wallet_id=wallet_id,
checking_id=internal_id,
payment_request="admin_internal",
payment_hash="admin_internal",
- amount=amount*1000,
+ amount=amount * 1000,
memo="Admin top up",
pending=False,
)
@@ -25,6 +25,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
await internal_invoice_queue.put(internal_id)
return payment
+
async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
# print("UPDATE", q)
@@ -35,23 +36,37 @@ async def update_admin(user: str, **kwargs) -> Admin:
assert row, "Newly updated settings couldn't be retrieved"
return Admin(**row) if row else None
+
async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin.admin")
return Admin(**row) if row else None
+
async def update_funding(data: Funding) -> Funding:
await db.execute(
"""
UPDATE admin.funding
SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ?
WHERE id = ?
- """,
- (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,),
+ """,
+ (
+ data.backend_wallet,
+ data.endpoint,
+ data.port,
+ data.read_key,
+ data.invoice_key,
+ data.admin_key,
+ data.cert,
+ data.balance,
+ data.selected,
+ data.id,
+ ),
)
row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,))
assert row, "Newly updated settings couldn't be retrieved"
return Funding(**row) if row else None
+
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM admin.funding")
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index f3663435..388f5ec6 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -7,19 +7,21 @@ from lnbits.helpers import urlsafe_short_hash
async def get_admin_user():
- if(conf.admin_users[0]):
+ if conf.admin_users[0]:
return conf.admin_users[0]
from lnbits.core.crud import create_account, get_user
+
print("Seems like there's no admin users yet. Let's create an account for you!")
account = await create_account()
user = account.id
assert user, "Newly created user couldn't be retrieved"
- print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.")
+ print(
+ f"Your newly created account/user id is: {user}. This will be the Super Admin user."
+ )
conf.admin_users.insert(0, user)
return user
-
async def m001_create_admin_table(db):
# users/server
user = await get_admin_user()
@@ -28,7 +30,7 @@ async def m001_create_admin_table(db):
admin_ext = ",".join(conf.admin_ext)
disabled_ext = ",".join(conf.disabled_ext)
funding_source = conf.funding_source
- #operational
+ # operational
data_folder = conf.data_folder
database_url = conf.database_url
force_https = conf.force_https
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 3d8efdcd..6e95d68f 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -28,6 +28,7 @@ class UpdateAdminSettings(BaseModel):
custom_logo: str = Query(None)
ad_space: str = Query(None)
+
class Admin(BaseModel):
# users
user: str
@@ -59,6 +60,7 @@ class Admin(BaseModel):
data = dict(row)
return cls(**data)
+
class Funding(BaseModel):
id: str
backend_wallet: str
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 24b8ca85..ceda5192 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -16,19 +16,21 @@ from .crud import get_admin, get_funding
templates = Jinja2Templates(directory="templates")
+
@admin_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
funding = [f.dict() for f in await get_funding()]
error, balance = await g().WALLET.status()
-
+
return admin_renderer().TemplateResponse(
- "admin/index.html", {
+ "admin/index.html",
+ {
"request": request,
"user": user.dict(),
"admin": admin.dict(),
"funding": funding,
"settings": g().admin_conf.dict(),
- "balance": balance
- }
+ "balance": balance,
+ },
)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index c0650c8a..784ad97f 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -15,16 +15,18 @@ from .crud import get_admin, update_admin, update_funding, update_wallet_balance
@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
-async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)):
+async def api_update_balance(
+ wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)
+):
try:
wallet = await get_wallet(wallet_id)
except:
raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
- )
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
-
+
return {"status": "Success"}
@@ -32,39 +34,40 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D
async def api_update_admin(
request: Request,
data: UpdateAdminSettings = Body(...),
- w: WalletTypeInfo = Depends(require_admin_key)
- ):
+ w: WalletTypeInfo = Depends(require_admin_key),
+):
admin = await get_admin()
# print(data)
if not admin.user == w.wallet.user:
raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
- )
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
updated = await update_admin(user=w.wallet.user, **data.dict())
- updated.admin_users = removeEmptyString(updated.admin_users.split(','))
- updated.allowed_users = removeEmptyString(updated.allowed_users.split(','))
- updated.admin_ext = removeEmptyString(updated.admin_ext.split(','))
- updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(','))
- updated.theme = removeEmptyString(updated.theme.split(','))
- updated.ad_space = removeEmptyString(updated.ad_space.split(','))
+ updated.admin_users = removeEmptyString(updated.admin_users.split(","))
+ updated.allowed_users = removeEmptyString(updated.allowed_users.split(","))
+ updated.admin_ext = removeEmptyString(updated.admin_ext.split(","))
+ updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(","))
+ updated.theme = removeEmptyString(updated.theme.split(","))
+ updated.ad_space = removeEmptyString(updated.ad_space.split(","))
g().admin_conf = g().admin_conf.copy(update=updated.dict())
-
+
# print(g().admin_conf)
return {"status": "Success"}
+
@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK)
async def api_update_funding(
request: Request,
data: Funding = Body(...),
- w: WalletTypeInfo = Depends(require_admin_key)
- ):
+ w: WalletTypeInfo = Depends(require_admin_key),
+):
admin = await get_admin()
if not admin.user == w.wallet.user:
raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
- )
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
funding = await update_funding(data=data)
return funding
From c319b84d7299eb5f7d942214fab2f21deffb38f6 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:28:13 +0100
Subject: [PATCH 044/696] Had to add a couple of tries
---
lnbits/core/views/generic.py | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 83648c44..63f7af68 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -133,12 +133,19 @@ async def wallet(
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User does not exist."}
)
- if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
- return template_renderer().TemplateResponse(
- "error.html", {"request": request, "err": "User not authorized."}
- )
- if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
- user.admin = True
+ try:
+ if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
+ return template_renderer().TemplateResponse(
+ "error.html", {"request": request, "err": "User not authorized."}
+ )
+ except:
+ pass
+
+ try:
+ if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
+ user.admin = True
+ except:
+ pass
if not wallet_id:
if user.wallets and not wallet_name: # type: ignore
wallet = user.wallets[0] # type: ignore
From d8a13ed29d323f5233c6d9ba5cb59f9ef352d90c Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:31:31 +0100
Subject: [PATCH 045/696] Added couple more tries
---
lnbits/decorators.py | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index f951163f..a810892d 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -239,13 +239,16 @@ async def check_user_exists(usr: UUID4) -> User:
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
-
- if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
-
- if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
- g().user.admin = True
-
+ try:
+ if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ except:
+ pass
+ try:
+ if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
+ g().user.admin = True
+ except:
+ pass
return g().user
From 55a44030283b34b6d61c0d441b779b135b449c3a Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:35:06 +0100
Subject: [PATCH 046/696] Reverted try
---
lnbits/decorators.py | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index a810892d..904ca1c2 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -239,16 +239,12 @@ async def check_user_exists(usr: UUID4) -> User:
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
- try:
- if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
- except:
- pass
- try:
- if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
- g().user.admin = True
+ if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
+ g().user.admin = True
except:
pass
return g().user
From a932f8a3d0c43692f6ec2b1ae9284c279ab7396c Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:37:07 +0100
Subject: [PATCH 047/696] reverted other try
---
lnbits/core/views/generic.py | 19 ++++++-------------
lnbits/decorators.py | 2 --
2 files changed, 6 insertions(+), 15 deletions(-)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 63f7af68..83648c44 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -133,19 +133,12 @@ async def wallet(
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User does not exist."}
)
- try:
- if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
- return template_renderer().TemplateResponse(
- "error.html", {"request": request, "err": "User not authorized."}
- )
- except:
- pass
-
- try:
- if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
- user.admin = True
- except:
- pass
+ if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
+ return template_renderer().TemplateResponse(
+ "error.html", {"request": request, "err": "User not authorized."}
+ )
+ if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
+ user.admin = True
if not wallet_id:
if user.wallets and not wallet_name: # type: ignore
wallet = user.wallets[0] # type: ignore
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index 904ca1c2..dd26d8fe 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -245,6 +245,4 @@ async def check_user_exists(usr: UUID4) -> User:
)
if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
g().user.admin = True
- except:
- pass
return g().user
From c32e0cbecb147918b2c95ad29bcaf8876855ac0b Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 21 Sep 2022 18:40:46 +0100
Subject: [PATCH 048/696] fix main merge missing settings
---
lnbits/app.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lnbits/app.py b/lnbits/app.py
index 950b6140..49ef3d1b 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -56,6 +56,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE",
},
)
+ if lnbits.settings.LNBITS_ADMIN_UI:
+ g().admin_conf = conf
+ check_settings(app)
+
+ g().WALLET = WALLET
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
app.mount(
"/core/static",
From e4c310d197fbb6ddb9a7d5528aab66fe60608313 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 10:46:11 +0200
Subject: [PATCH 049/696] format
---
lnbits/app.py | 22 +++++++++++++---------
lnbits/commands.py | 6 +++++-
lnbits/config.py | 25 ++++++++++++++++++-------
lnbits/core/views/generic.py | 1 +
lnbits/decorators.py | 2 +-
lnbits/helpers.py | 8 +++++---
6 files changed, 43 insertions(+), 21 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 49ef3d1b..00ed6d8d 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -115,24 +115,28 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app
+
def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
while True:
admin_set = await get_admin_settings()
- if admin_set :
+ if admin_set:
break
print("Waiting for admin settings... retrying in 5 seconds!")
await asyncio.sleep(5)
-
- admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(','))
- admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(','))
- admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(','))
- admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(','))
- admin_set.theme = removeEmptyString(admin_set.theme.split(','))
- admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(','))
+
+ admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(","))
+ admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(","))
+ admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(","))
+ admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(","))
+ admin_set.theme = removeEmptyString(admin_set.theme.split(","))
+ admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(","))
g().admin_conf = conf.copy(update=admin_set.dict())
- print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
+ print(
+ f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}"
+ )
+
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 763a5b90..86868f1f 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -5,6 +5,7 @@ import re
import warnings
import click
+from genericpath import exists
from loguru import logger
from .core import db as core_db
@@ -52,6 +53,7 @@ def bundle_vendored():
with open(outputpath, "w") as f:
f.write(output)
+
async def get_admin_settings():
from lnbits.extensions.admin.models import Admin
@@ -61,6 +63,7 @@ async def get_admin_settings():
return False
async with ext_db.connect() as conn:
+
if conn.type == SQLITE:
exists = await conn.fetchone(
"SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
@@ -69,7 +72,7 @@ async def get_admin_settings():
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
-
+
if not exists:
return False
@@ -77,6 +80,7 @@ async def get_admin_settings():
return Admin(**row) if row else None
+
async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them."""
diff --git a/lnbits/config.py b/lnbits/config.py
index 37b700fd..cf26ad21 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -6,17 +6,19 @@ from typing import List, Optional
from pydantic import BaseSettings, Field
wallets_module = importlib.import_module("lnbits.wallets")
-wallet_class = getattr(
+wallet_class = getattr(
wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet")
)
WALLET = wallet_class()
+
def list_parse_fallback(v):
try:
return json.loads(v)
except Exception as e:
- return v.replace(' ','').split(',')
+ return v.replace(" ", "").split(",")
+
class Settings(BaseSettings):
admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI")
@@ -24,7 +26,9 @@ class Settings(BaseSettings):
admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS")
allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS")
admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS")
- disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS")
+ disabled_ext: List[str] = Field(
+ default_factory=list, env="LNBITS_DISABLED_EXTENSIONS"
+ )
funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS")
# ops
data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER")
@@ -37,10 +41,17 @@ class Settings(BaseSettings):
denomination: str = Field(default="sats", env="LNBITS_DENOMINATION")
# Change theme
site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE")
- site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE")
+ site_tagline: str = Field(
+ default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE"
+ )
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
- default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
- theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS")
+ default_wallet_name: str = Field(
+ default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME"
+ )
+ theme: List[str] = Field(
+ default=["classic, flamingo, mint, salvador, monochrome, autumn"],
+ env="LNBITS_THEME_OPTIONS",
+ )
custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
@@ -48,7 +59,7 @@ class Settings(BaseSettings):
debug: Optional[str]
host: Optional[str]
port: Optional[str]
- lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
+ lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
# @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True)
# def validate(cls, val):
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 83648c44..3a1fbdfc 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -39,6 +39,7 @@ from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
+
@core_html_routes.get("/favicon.ico", response_class=FileResponse)
async def favicon():
return FileResponse("lnbits/core/static/favicon.ico")
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index dd26d8fe..58b025aa 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -235,7 +235,7 @@ async def check_user_exists(usr: UUID4) -> User:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
)
-
+
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index 7bd1b54a..f4255c86 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -157,11 +157,13 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s
url = f"{base}{endpoint}{url_params}"
return url
+
def removeEmptyString(arr):
return list(filter(None, arr))
+
def template_renderer(additional_folders: List = []) -> Jinja2Templates:
- if(settings.LNBITS_ADMIN_UI):
+ if settings.LNBITS_ADMIN_UI:
_ = g().admin_conf
settings.LNBITS_AD_SPACE = _.ad_space
settings.LNBITS_HIDE_API = _.hide_api
@@ -170,8 +172,8 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates:
settings.LNBITS_SITE_TAGLINE = _.site_tagline
settings.LNBITS_SITE_DESCRIPTION = _.site_description
settings.LNBITS_THEME_OPTIONS = _.theme
- settings.LNBITS_CUSTOM_LOGO = _.custom_logo
-
+ settings.LNBITS_CUSTOM_LOGO = _.custom_logo
+
t = Jinja2Templates(
loader=jinja2.FileSystemLoader(
["lnbits/templates", "lnbits/core/templates", *additional_folders]
From 6c2a9b2258087302487716e3d35b1b2387bb58ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 11:47:24 +0200
Subject: [PATCH 050/696] format black
---
lnbits/app.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/lnbits/app.py b/lnbits/app.py
index 00ed6d8d..6ae75d7a 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -56,6 +56,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE",
},
)
+
if lnbits.settings.LNBITS_ADMIN_UI:
g().admin_conf = conf
check_settings(app)
From 7c05d4c354be10d502b7c453f96902f76f9c15d8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 15:29:12 +0200
Subject: [PATCH 051/696] fix AD_SPACE
---
lnbits/core/templates/core/wallet.html | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html
index bccdc2b4..189b060b 100644
--- a/lnbits/core/templates/core/wallet.html
+++ b/lnbits/core/templates/core/wallet.html
@@ -385,12 +385,9 @@
- {% endif %} {% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD =
- ADS.split(';') %}
+ {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %}
- {% endfor %} {% endif %}
From 415165c6fe7ec2affda20078c81e12b4fc2944de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 15:55:37 +0200
Subject: [PATCH 052/696] fix some javascript errors when adding users
---
lnbits/extensions/admin/templates/admin/index.html | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d34b9068..1e881cb6 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1221,7 +1221,7 @@
addAdminUser() {
let addUser = this.data.admin_users_add
let admin_users = this.data.admin.admin_users
- if (addUser.length && !admin_users.includes(addUser)) {
+ if (addUser && addUser.length && !admin_users.includes(addUser)) {
admin_users.push(addUser)
this.data.admin.admin_users = admin_users
this.data.admin_users_add = ''
@@ -1234,7 +1234,7 @@
addAllowedUser() {
let addUser = this.data.allowed_users_add
let allowed_users = this.data.admin.allowed_users
- if (addUser.length && !allowed_users.includes(addUser)) {
+ if (addUser && addUser.length && !allowed_users.includes(addUser)) {
allowed_users.push(addUser)
this.data.admin.allowed_users = allowed_users
this.data.allowed_users_add = ''
@@ -1336,7 +1336,6 @@
custom_logo,
ad_space
} = this.data.admin
- //console.log("this", this.data.admin)
let data = {
admin_users: admin_users.toString(),
allowed_users: allowed_users.toString(),
From 850ed85311fea5c5e10bca8cdeaaa0a3dffbe71f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 16:04:55 +0200
Subject: [PATCH 053/696] fix ADMIN_UI=false errors
---
lnbits/core/views/generic.py | 3 +++
lnbits/decorators.py | 6 ++++++
2 files changed, 9 insertions(+)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 3a1fbdfc..db4fac43 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -124,6 +124,9 @@ async def wallet(
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
+ else:
+ LNBITS_ADMIN_USERS = []
+ LNBITS_ALLOWED_USERS = []
if not user_id:
user = await get_user((await create_account()).id)
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index 58b025aa..5a3c0a5c 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -141,6 +141,8 @@ async def get_key_type(
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+ else:
+ LNBITS_ADMIN_USERS = []
for typenr, WalletChecker in zip(
[0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker]
@@ -239,6 +241,10 @@ async def check_user_exists(usr: UUID4) -> User:
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
+ else:
+ LNBITS_ADMIN_USERS = []
+ LNBITS_ALLOWED_USERS = []
+
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
From 393d1a8204f04498aca712604f5646dcb30d7746 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 16:14:17 +0200
Subject: [PATCH 054/696] prettier
---
lnbits/core/templates/core/wallet.html | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html
index 189b060b..22c65bf3 100644
--- a/lnbits/core/templates/core/wallet.html
+++ b/lnbits/core/templates/core/wallet.html
@@ -386,8 +386,7 @@
{% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %}
-
- {% endfor %} {% endif %}
From 622d0f50da4b319765175c97613ab31e5e1dd722 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 18:35:47 +0200
Subject: [PATCH 055/696] fix migration tests
---
lnbits/extensions/admin/migrations.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 388f5ec6..196c9fc0 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -7,7 +7,7 @@ from lnbits.helpers import urlsafe_short_hash
async def get_admin_user():
- if conf.admin_users[0]:
+ if len(conf.admin_users) > 0:
return conf.admin_users[0]
from lnbits.core.crud import create_account, get_user
From 26769d94984c5d7c4f56b1bd06ca4ce3e376453c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 26 Sep 2022 16:54:19 +0200
Subject: [PATCH 056/696] change comments to use multiple lines
---
.env.example | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/.env.example b/.env.example
index 7a49d5c5..c1ac7497 100644
--- a/.env.example
+++ b/.env.example
@@ -3,11 +3,15 @@ PORT=5000
DEBUG=false
-LNBITS_ADMIN_USERS="" # User IDs seperated by comma
-LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access
-LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
+# User IDs seperated by comma
+LNBITS_ADMIN_USERS=""
+# Extensions only admin can access
+LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
+# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
+LNBITS_ADMIN_UI=false
-LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
+# Restricts access, User IDs seperated by comma
+LNBITS_ALLOWED_USERS=""
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
From cadb6b21617c77fac36ea55937a03c223ec5aa56 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 26 Sep 2022 16:59:30 +0200
Subject: [PATCH 057/696] fix merge error
---
lnbits/app.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 6ae75d7a..60c09038 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -57,10 +57,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
},
)
- if lnbits.settings.LNBITS_ADMIN_UI:
- g().admin_conf = conf
- check_settings(app)
-
g().WALLET = WALLET
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
app.mount(
From a78ebbacd700c7975662424e07bb042a2da380f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 14:14:36 +0200
Subject: [PATCH 058/696] use logger in app.py
---
lnbits/app.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 60c09038..d7bd3ea6 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -31,8 +31,6 @@ from .helpers import (
url_for_vendored,
)
from .requestvars import g
-
-# from .settings import WALLET
from .tasks import (
catch_everything_and_restart,
check_pending_payments,
@@ -120,7 +118,7 @@ def check_settings(app: FastAPI):
admin_set = await get_admin_settings()
if admin_set:
break
- print("Waiting for admin settings... retrying in 5 seconds!")
+ logger.info("Waiting for admin settings... retrying in 5 seconds!")
await asyncio.sleep(5)
admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(","))
@@ -130,7 +128,7 @@ def check_settings(app: FastAPI):
admin_set.theme = removeEmptyString(admin_set.theme.split(","))
admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(","))
g().admin_conf = conf.copy(update=admin_set.dict())
- print(
+ logger.info(
f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}"
)
From 8be9b950fdff51e8c9d94d4123cae0dfb650dd3b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 14:14:57 +0200
Subject: [PATCH 059/696] fix config
---
lnbits/config.py | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index cf26ad21..874effae 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -17,7 +17,11 @@ def list_parse_fallback(v):
try:
return json.loads(v)
except Exception as e:
- return v.replace(" ", "").split(",")
+ replaced = v.replace(" ", "")
+ if replaced:
+ return replaced.split(",")
+ else:
+ return []
class Settings(BaseSettings):
@@ -48,10 +52,7 @@ class Settings(BaseSettings):
default_wallet_name: str = Field(
default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME"
)
- theme: List[str] = Field(
- default=["classic, flamingo, mint, salvador, monochrome, autumn"],
- env="LNBITS_THEME_OPTIONS",
- )
+ theme: List[str] = Field(default_factory=list, env="LNBITS_THEME_OPTIONS")
custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
@@ -74,4 +75,5 @@ class Settings(BaseSettings):
conf = Settings()
+print(conf)
WALLET = wallet_class()
From f2e494384e96587b0b0fb2a9de8825aa9115ee21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 14:17:20 +0200
Subject: [PATCH 060/696] concatenate first migrations script
fixup
---
lnbits/config.py | 1 -
lnbits/extensions/admin/migrations.py | 7 +++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index 874effae..fe8dabf9 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -75,5 +75,4 @@ class Settings(BaseSettings):
conf = Settings()
-print(conf)
WALLET = wallet_class()
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 196c9fc0..2d48a8e4 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -23,6 +23,8 @@ async def get_admin_user():
async def m001_create_admin_table(db):
+
+
# users/server
user = await get_admin_user()
admin_users = ",".join(conf.admin_users)
@@ -78,7 +80,7 @@ async def m001_create_admin_table(db):
await db.execute(
"""
INSERT INTO admin.admin (
- "user",
+ "user",
admin_users,
allowed_users,
admin_ext,
@@ -126,9 +128,6 @@ async def m001_create_admin_table(db):
),
)
-
-async def m001_create_funding_table(db):
-
funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS")
# Make the funding table, if it does not already exist
From 9757fad8684d166ce9ac04e6a69b0ff820c4de61 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 16:37:08 +0200
Subject: [PATCH 061/696] add restart button to frontend
---
.../admin/templates/admin/index.html | 28 ++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 1e881cb6..319ca3f0 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -65,6 +65,14 @@
:options="data.funding_source"
>
+
+
+
Fee reserve
@@ -89,7 +97,7 @@
>
-
+
@@ -1257,6 +1265,24 @@
let spaces = this.data.admin.ad_space
this.data.admin.ad_space = spaces.filter(s => s !== ad)
},
+ restartServer() {
+ LNbits.api
+ .request(
+ 'GET',
+ '/admin/api/v1/admin/restart/',
+ this.g.user.wallets[0].adminkey
+ )
+ .then(response => {
+ this.$q.notify({
+ type: 'positive',
+ message: 'Success! Restarted Server',
+ icon: null
+ })
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
topupWallet() {
LNbits.api
.request(
From 66739f4246bdc6d3b347f54f675c00937601935e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 3 Oct 2022 16:34:52 +0200
Subject: [PATCH 062/696] make extension use new settings
---
lnbits/extensions/boltz/boltz.py | 14 ++++++--------
lnbits/extensions/boltz/mempool.py | 15 ++++++---------
lnbits/extensions/boltz/views_api.py | 4 ++--
lnbits/extensions/lndhub/views_api.py | 4 ++--
lnbits/extensions/tpos/views.py | 14 +++++++-------
5 files changed, 23 insertions(+), 28 deletions(-)
diff --git a/lnbits/extensions/boltz/boltz.py b/lnbits/extensions/boltz/boltz.py
index ac99d4f4..424d0bf7 100644
--- a/lnbits/extensions/boltz/boltz.py
+++ b/lnbits/extensions/boltz/boltz.py
@@ -12,7 +12,7 @@ from loguru import logger
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.helpers import urlsafe_short_hash
-from lnbits.settings import BOLTZ_NETWORK, BOLTZ_URL
+from lnbits.settings import settings
from .crud import update_swap_status
from .mempool import (
@@ -33,9 +33,7 @@ from .models import (
)
from .utils import check_balance, get_timestamp, req_wrap
-net = NETWORKS[BOLTZ_NETWORK]
-logger.trace(f"BOLTZ_URL: {BOLTZ_URL}")
-logger.trace(f"Bitcoin Network: {net['name']}")
+net = NETWORKS[settings.boltz_network]
async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
@@ -62,7 +60,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
res = req_wrap(
"post",
- f"{BOLTZ_URL}/createswap",
+ f"{settings.boltz_url}/createswap",
json={
"type": "submarine",
"pairId": "BTC/BTC",
@@ -129,7 +127,7 @@ async def create_reverse_swap(
res = req_wrap(
"post",
- f"{BOLTZ_URL}/createswap",
+ f"{settings.boltz_url}/createswap",
json={
"type": "reversesubmarine",
"pairId": "BTC/BTC",
@@ -409,7 +407,7 @@ def check_boltz_limits(amount):
def get_boltz_pairs():
res = req_wrap(
"get",
- f"{BOLTZ_URL}/getpairs",
+ f"{settings.boltz_url}/getpairs",
headers={"Content-Type": "application/json"},
)
return res.json()
@@ -418,7 +416,7 @@ def get_boltz_pairs():
def get_boltz_status(boltzid):
res = req_wrap(
"post",
- f"{BOLTZ_URL}/swapstatus",
+ f"{settings.boltz_url}/swapstatus",
json={"id": boltzid},
)
return res.json()
diff --git a/lnbits/extensions/boltz/mempool.py b/lnbits/extensions/boltz/mempool.py
index a44c0f02..a64cadad 100644
--- a/lnbits/extensions/boltz/mempool.py
+++ b/lnbits/extensions/boltz/mempool.py
@@ -7,14 +7,11 @@ import websockets
from embit.transaction import Transaction
from loguru import logger
-from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS
+from lnbits.settings import settings
from .utils import req_wrap
-logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}")
-logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}")
-
-websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws"
+websocket_url = f"{settings.boltz_mempool_space_url_ws}/api/v1/ws"
async def wait_for_websocket_message(send, message_string):
@@ -33,7 +30,7 @@ async def wait_for_websocket_message(send, message_string):
def get_mempool_tx(address):
res = req_wrap(
"get",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/address/{address}/txs",
+ f"{settings.boltz_mempool_space_url}/api/address/{address}/txs",
headers={"Content-Type": "text/plain"},
)
txs = res.json()
@@ -70,7 +67,7 @@ def get_fee_estimation() -> int:
def get_mempool_fees() -> int:
res = req_wrap(
"get",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/v1/fees/recommended",
+ f"{settings.boltz_mempool_space_url}/api/v1/fees/recommended",
headers={"Content-Type": "text/plain"},
)
fees = res.json()
@@ -80,7 +77,7 @@ def get_mempool_fees() -> int:
def get_mempool_blockheight() -> int:
res = req_wrap(
"get",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/blocks/tip/height",
+ f"{settings.boltz_mempool_space_url}/api/blocks/tip/height",
headers={"Content-Type": "text/plain"},
)
return int(res.text)
@@ -91,7 +88,7 @@ async def send_onchain_tx(tx: Transaction):
logger.debug(f"Boltz - mempool sending onchain tx...")
req_wrap(
"post",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/tx",
+ f"{settings.boltz_mempool_space_url}/api/tx",
headers={"Content-Type": "text/plain"},
content=raw,
)
diff --git a/lnbits/extensions/boltz/views_api.py b/lnbits/extensions/boltz/views_api.py
index a4b7d318..18ca14cb 100644
--- a/lnbits/extensions/boltz/views_api.py
+++ b/lnbits/extensions/boltz/views_api.py
@@ -14,7 +14,7 @@ from starlette.requests import Request
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL
+from lnbits.settings import settings
from . import boltz_ext
from .boltz import (
@@ -55,7 +55,7 @@ from .utils import check_balance
response_model=str,
)
async def api_mempool_url():
- return BOLTZ_MEMPOOL_SPACE_URL
+ return settings.boltz_mempool_space_url
# NORMAL SWAP
diff --git a/lnbits/extensions/lndhub/views_api.py b/lnbits/extensions/lndhub/views_api.py
index 8cbe5a6b..b2328c39 100644
--- a/lnbits/extensions/lndhub/views_api.py
+++ b/lnbits/extensions/lndhub/views_api.py
@@ -12,7 +12,7 @@ from lnbits import bolt11
from lnbits.core.crud import delete_expired_invoices, get_payments
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.decorators import WalletTypeInfo
-from lnbits.settings import LNBITS_SITE_TITLE, WALLET
+from lnbits.settings import WALLET, settings
from . import lndhub_ext
from .decorators import check_wallet, require_admin_key
@@ -56,7 +56,7 @@ async def lndhub_addinvoice(
_, pr = await create_invoice(
wallet_id=wallet.wallet.id,
amount=int(data.amt),
- memo=data.memo or LNBITS_SITE_TITLE,
+ memo=data.memo or settings.lnbits_site_title,
extra={"tag": "lndhub"},
)
except:
diff --git a/lnbits/extensions/tpos/views.py b/lnbits/extensions/tpos/views.py
index e1f1d21e..dac129a9 100644
--- a/lnbits/extensions/tpos/views.py
+++ b/lnbits/extensions/tpos/views.py
@@ -8,7 +8,7 @@ from starlette.responses import HTMLResponse
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
-from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE
+from lnbits.settings import settings
from . import tpos_ext, tpos_renderer
from .crud import get_tpos
@@ -50,12 +50,12 @@ async def manifest(tpos_id: str):
)
return {
- "short_name": LNBITS_SITE_TITLE,
- "name": tpos.name + " - " + LNBITS_SITE_TITLE,
+ "short_name": settings.lnbits_site_title,
+ "name": tpos.name + " - " + settings.lnbits_site_title,
"icons": [
{
- "src": LNBITS_CUSTOM_LOGO
- if LNBITS_CUSTOM_LOGO
+ "src": settings.lnbits_custom_logo
+ if settings.lnbits_custom_logo
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
"type": "image/png",
"sizes": "900x900",
@@ -69,9 +69,9 @@ async def manifest(tpos_id: str):
"theme_color": "#1F2234",
"shortcuts": [
{
- "name": tpos.name + " - " + LNBITS_SITE_TITLE,
+ "name": tpos.name + " - " + settings.lnbits_site_title,
"short_name": tpos.name,
- "description": tpos.name + " - " + LNBITS_SITE_TITLE,
+ "description": tpos.name + " - " + settings.lnbits_site_title,
"url": "/tpos/" + tpos_id,
}
],
From 2b2884142fd9c2ac6fbfa3cbd8916aaf2225068a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 3 Oct 2022 16:35:26 +0200
Subject: [PATCH 063/696] remove enviroms
---
pyproject.toml | 1 -
1 file changed, 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index 19dac860..7a69ec43 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,6 @@ charset-normalizer = "2.0.6"
click = "8.0.1"
ecdsa = "0.17.0"
embit = "0.4.9"
-environs = "9.3.3"
fastapi = "0.78.0"
h11 = "0.12.0"
httpcore = "0.15.0"
From 840ede1bd66fd437e342bbefc0880de6fc2f216c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 3 Oct 2022 16:36:14 +0200
Subject: [PATCH 064/696] fix admin
---
lnbits/extensions/admin/crud.py | 24 +-
lnbits/extensions/admin/migrations.py | 337 +----
lnbits/extensions/admin/models.py | 58 +-
.../admin/templates/admin/_tab_funding.html | 158 +++
.../admin/templates/admin/_tab_server.html | 78 ++
.../admin/templates/admin/_tab_theme.html | 122 ++
.../admin/templates/admin/_tab_users.html | 96 ++
.../admin/templates/admin/index.html | 1133 +----------------
lnbits/extensions/admin/views.py | 16 +-
lnbits/extensions/admin/views_api.py | 28 +-
10 files changed, 576 insertions(+), 1474 deletions(-)
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_funding.html
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_server.html
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_theme.html
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_users.html
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 0d7019cc..e4cb5d77 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -2,10 +2,11 @@ from typing import List
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
+from lnbits.settings import Settings
from lnbits.tasks import internal_invoice_queue
from . import db
-from .models import Admin, Funding
+from .models import Funding
async def update_wallet_balance(wallet_id: str, amount: int) -> str:
@@ -23,26 +24,26 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
)
# manually send this for now
await internal_invoice_queue.put(internal_id)
- return payment
-async def update_admin(user: str, **kwargs) -> Admin:
+async def update_settings(user: str, **kwargs) -> Settings:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
# print("UPDATE", q)
await db.execute(
- f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
+ f'UPDATE admin.settings SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
- row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,))
+ row = await db.fetchone('SELECT * FROM admin.settings WHERE "user" = ?', (user,))
assert row, "Newly updated settings couldn't be retrieved"
- return Admin(**row) if row else None
-
-
-async def get_admin() -> Admin:
- row = await db.fetchone("SELECT * FROM admin.admin")
- return Admin(**row) if row else None
+ return Settings(**row) if row else None
async def update_funding(data: Funding) -> Funding:
+ await db.execute(
+ """
+ UPDATE admin.settings SET funding_source = ? WHERE user = ?
+ """,
+ (data.backend_wallet, data.user),
+ )
await db.execute(
"""
UPDATE admin.funding
@@ -69,5 +70,4 @@ async def update_funding(data: Funding) -> Funding:
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM admin.funding")
-
return [Funding(**row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 2d48a8e4..8f6c76a0 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -1,292 +1,57 @@
-from os import getenv
-
-from sqlalchemy.exc import OperationalError # type: ignore
-
-from lnbits.config import conf
-from lnbits.helpers import urlsafe_short_hash
-
-
-async def get_admin_user():
- if len(conf.admin_users) > 0:
- return conf.admin_users[0]
- from lnbits.core.crud import create_account, get_user
-
- print("Seems like there's no admin users yet. Let's create an account for you!")
- account = await create_account()
- user = account.id
- assert user, "Newly created user couldn't be retrieved"
- print(
- f"Your newly created account/user id is: {user}. This will be the Super Admin user."
- )
- conf.admin_users.insert(0, user)
- return user
-
-
-async def m001_create_admin_table(db):
-
-
- # users/server
- user = await get_admin_user()
- admin_users = ",".join(conf.admin_users)
- allowed_users = ",".join(conf.allowed_users)
- admin_ext = ",".join(conf.admin_ext)
- disabled_ext = ",".join(conf.disabled_ext)
- funding_source = conf.funding_source
- # operational
- data_folder = conf.data_folder
- database_url = conf.database_url
- force_https = conf.force_https
- reserve_fee_min = conf.reserve_fee_min
- reserve_fee_pct = conf.reserve_fee_pct
- service_fee = conf.service_fee
- hide_api = conf.hide_api
- denomination = conf.denomination
- # Theme'ing
- site_title = conf.site_title
- site_tagline = conf.site_tagline
- site_description = conf.site_description
- default_wallet_name = conf.default_wallet_name
- theme = ",".join(conf.theme)
- custom_logo = conf.custom_logo
- ad_space = ",".join(conf.ad_space)
-
+async def m001_create_admin_settings_table(db):
await db.execute(
"""
- CREATE TABLE IF NOT EXISTS admin.admin (
- "user" TEXT PRIMARY KEY,
- admin_users TEXT,
- allowed_users TEXT,
- admin_ext TEXT,
- disabled_ext TEXT,
- funding_source TEXT,
- data_folder TEXT,
- database_url TEXT,
- force_https BOOLEAN,
- reserve_fee_min INT,
- reserve_fee_pct REAL,
- service_fee REAL,
- hide_api BOOLEAN,
- denomination TEXT,
- site_title TEXT,
- site_tagline TEXT,
- site_description TEXT,
- default_wallet_name TEXT,
- theme TEXT,
- custom_logo TEXT,
- ad_space TEXT
- );
- """
- )
- await db.execute(
- """
- INSERT INTO admin.admin (
- "user",
- admin_users,
- allowed_users,
- admin_ext,
- disabled_ext,
- funding_source,
- data_folder,
- database_url,
- force_https,
- reserve_fee_min,
- reserve_fee_pct,
- service_fee,
- hide_api,
- denomination,
- site_title,
- site_tagline,
- site_description,
- default_wallet_name,
- theme,
- custom_logo,
- ad_space)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """,
- (
- user,
- admin_users,
- allowed_users,
- admin_ext,
- disabled_ext,
- funding_source,
- data_folder,
- database_url,
- force_https,
- reserve_fee_min,
- reserve_fee_pct,
- service_fee,
- hide_api,
- denomination,
- site_title,
- site_tagline,
- site_description,
- default_wallet_name,
- theme,
- custom_logo,
- ad_space,
- ),
- )
-
- funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS")
-
- # Make the funding table, if it does not already exist
- await db.execute(
- """
- CREATE TABLE IF NOT EXISTS admin.funding (
- id TEXT PRIMARY KEY,
- backend_wallet TEXT,
- endpoint TEXT,
+ CREATE TABLE IF NOT EXISTS admin.settings (
+ lnbits_admin_ui TEXT,
+ debug TEXT,
+ host TEXT,
port INT,
- read_key TEXT,
- invoice_key TEXT,
- admin_key TEXT,
- cert TEXT,
- balance INT,
- selected INT
+ lnbits_path TEXT,
+ lnbits_commit TEXT,
+ lnbits_admin_users TEXT,
+ lnbits_allowed_users TEXT,
+ lnbits_allowed_funding_sources TEXT,
+ lnbits_admin_extensions TEXT,
+ lnbits_disabled_extensions TEXT,
+ lnbits_site_title TEXT,
+ lnbits_site_tagline TEXT,
+ lnbits_site_description TEXT,
+ lnbits_default_wallet_name TEXT,
+ lnbits_theme_options TEXT,
+ lnbits_custom_logo TEXT,
+ lnbits_ad_space TEXT,
+ lnbits_data_folder TEXT,
+ lnbits_database_url TEXT,
+ lnbits_force_https TEXT,
+ lnbits_reserve_fee_min TEXT,
+ lnbits_reserve_fee_percent TEXT,
+ lnbits_service_fee TEXT,
+ lnbits_hide_api TEXT,
+ lnbits_denomination TEXT,
+ lnbits_backend_wallet_class TEXT,
+ fake_wallet_secret TEXT,
+ lnbits_endpoint TEXT,
+ lnbits_key TEXT,
+ cliche_endpoint TEXT,
+ corelightning_rpc TEXT,
+ eclair_url TEXT,
+ eclair_pass TEXT,
+ lnd_rest_endpoint TEXT,
+ lnd_rest_cert TEXT,
+ lnd_rest_macaroon TEXT,
+ lnpay_api_endpoint TEXT,
+ lnpay_api_key TEXT,
+ lnpay_wallet_key TEXT,
+ lntxbot_api_endpoint TEXT,
+ lntxbot_key TEXT,
+ opennode_api_endpoint TEXT,
+ opennode_key TEXT,
+ spark_url TEXT,
+ spark_token TEXT,
+ boltz_network TEXT,
+ boltz_url TEXT,
+ boltz_mempool_space_url TEXT,
+ boltz_mempool_space_url_ws TEXT
);
"""
)
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, selected)
- VALUES (?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "CLightningWallet",
- getenv("CLIGHTNING_RPC"),
- 1 if funding_wallet == "CLightningWallet" else 0,
- ),
- )
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "SparkWallet",
- getenv("SPARK_URL"),
- getenv("SPARK_TOKEN"),
- 1 if funding_wallet == "SparkWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LnbitsWallet",
- getenv("LNBITS_ENDPOINT"),
- getenv("LNBITS_KEY"),
- 1 if funding_wallet == "LnbitsWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
- VALUES (?, ?, ?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LndWallet",
- getenv("LND_GRPC_ENDPOINT"),
- getenv("LND_GRPC_PORT"),
- getenv("LND_GRPC_MACAROON"),
- getenv("LND_GRPC_CERT"),
- 1 if funding_wallet == "LndWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
- VALUES (?, ?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LndRestWallet",
- getenv("LND_REST_ENDPOINT"),
- getenv("LND_REST_MACAROON"),
- getenv("LND_REST_CERT"),
- 1 if funding_wallet == "LndWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
- VALUES (?, ?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LNPayWallet",
- getenv("LNPAY_API_ENDPOINT"),
- getenv("LNPAY_WALLET_KEY"),
- getenv("LNPAY_API_KEY"), # this is going in as the cert
- 1 if funding_wallet == "LNPayWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LntxbotWallet",
- getenv("LNTXBOT_API_ENDPOINT"),
- getenv("LNTXBOT_KEY"),
- 1 if funding_wallet == "LntxbotWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "OpenNodeWallet",
- getenv("OPENNODE_API_ENDPOINT"),
- getenv("OPENNODE_KEY"),
- 1 if funding_wallet == "OpenNodeWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "SparkWallet",
- getenv("SPARK_URL"),
- getenv("SPARK_TOKEN"),
- 1 if funding_wallet == "SparkWallet" else 0,
- ),
- )
-
- ## PLACEHOLDER FOR ECLAIR WALLET
- # await db.execute(
- # """
- # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- # VALUES (?, ?, ?, ?, ?)
- # """,
- # (
- # urlsafe_short_hash(),
- # "EclairWallet",
- # getenv("ECLAIR_URL"),
- # getenv("ECLAIR_PASS"),
- # 1 if funding_wallet == "EclairWallet" else 0,
- # ),
- # )
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 6e95d68f..ef57cadd 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -29,36 +29,36 @@ class UpdateAdminSettings(BaseModel):
ad_space: str = Query(None)
-class Admin(BaseModel):
- # users
- user: str
- admin_users: Optional[str]
- allowed_users: Optional[str]
- admin_ext: Optional[str]
- disabled_ext: Optional[str]
- funding_source: Optional[str]
- # ops
- data_folder: Optional[str]
- database_url: Optional[str]
- force_https: bool = Field(default=True)
- reserve_fee_min: Optional[int]
- reserve_fee_pct: Optional[float]
- service_fee: float = Optional[float]
- hide_api: bool = Field(default=False)
- # Change theme
- site_title: Optional[str]
- site_tagline: Optional[str]
- site_description: Optional[str]
- default_wallet_name: Optional[str]
- denomination: str = Field(default="sats")
- theme: Optional[str]
- custom_logo: Optional[str]
- ad_space: Optional[str]
+# class Admin(BaseModel):
+# # users
+# user: str
+# admin_users: Optional[str]
+# allowed_users: Optional[str]
+# admin_ext: Optional[str]
+# disabled_ext: Optional[str]
+# funding_source: Optional[str]
+# # ops
+# data_folder: Optional[str]
+# database_url: Optional[str]
+# force_https: bool = Field(default=True)
+# reserve_fee_min: Optional[int]
+# reserve_fee_pct: Optional[float]
+# service_fee: float = Optional[float]
+# hide_api: bool = Field(default=False)
+# # Change theme
+# site_title: Optional[str]
+# site_tagline: Optional[str]
+# site_description: Optional[str]
+# default_wallet_name: Optional[str]
+# denomination: str = Field(default="sats")
+# theme: Optional[str]
+# custom_logo: Optional[str]
+# ad_space: Optional[str]
- @classmethod
- def from_row(cls, row: Row) -> "Admin":
- data = dict(row)
- return cls(**data)
+# @classmethod
+# def from_row(cls, row: Row) -> "Admin":
+# data = dict(row)
+# return cls(**data)
class Funding(BaseModel):
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
new file mode 100644
index 00000000..2ed0aae2
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -0,0 +1,158 @@
+
+
+ Wallets Management
+
+
+
+
+
Funding Source Info
+
+ {%raw%}
+
+ Funding Source: {{data.settings.lnbits_backend_wallet_class}}
+
+ Balance: {{data.balance / 1000}} sats
+ {%endraw%}
+
+
+
+
+
+
+
+
+
Active Funding (Requires server restart)
+
+
+
+
+
+
+
+
+
+
+
TopUp a wallet
+
+
+
+
+
+
+
+
Funding Sources
+ {% raw %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_server.html b/lnbits/extensions/admin/templates/admin/_tab_server.html
new file mode 100644
index 00000000..2924e6a4
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_server.html
@@ -0,0 +1,78 @@
+
+
+ Server Management
+
+
+
+
+
Server Info
+
+ {%raw%}
+
+ SQlite: {{data.settings.lnbits_data_folder}}
+
+
+ Postgres: {{data.settings.lnbits_database_url}}
+
+ {%endraw%}
+
+
+
+
+
+
+
+
Miscelaneous
+
+
+ Force HTTPS
+ Prefer secure URLs
+
+
+
+
+
+
+
+ Hide API
+ Hides wallet api, extensions can choose to honor
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html
new file mode 100644
index 00000000..41dc0447
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html
@@ -0,0 +1,122 @@
+
+
+ UI Management
+
+
+
+
+
+
+
+
Default Wallet Name
+
+
+
+
+
+
+
+
+
Advertisement Slots
+
+
+
+
+ {% raw %}
+
+ {{ space.slice(0, 8) + " ... " + space.slice(-8) }}
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+ Save
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html
new file mode 100644
index 00000000..3eb53ac4
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_users.html
@@ -0,0 +1,96 @@
+
+
+ User Management
+
+
+ Super Admin: {% raw %}{{this.data.settings.lnbits_admin_users[0]}}{%
+ endraw %}
+
+
+
+
Admin Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
Allowed Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
+
+
Disabled Extensions
+
+
+
+
+
+ Save
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 319ca3f0..87e89321 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -29,1118 +29,16 @@
-
-
-
- Wallets Management
-
-
-
-
-
Funding Source Info
-
- {%raw%}
- Funding Source: {{data.admin.funding_source}}
- Balance: {{data.admin.balance / 1000}} sats
- {%endraw%}
-
-
-
-
-
-
-
-
-
- Active Funding
- (Requires server restart)
-
-
-
-
-
-
-
-
-
-
-
-
-
TopUp a wallet
-
-
-
-
-
-
-
-
-
Funding Sources
- {% raw %}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% endraw %}
-
-
-
-
-
-
- User Management
-
-
- Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %}
-
-
-
-
Admin Users
-
-
-
-
- {% raw %}
-
- {{ user }}
-
- {% endraw %}
-
-
-
-
-
Allowed Users
-
-
-
-
- {% raw %}
-
- {{ user }}
-
- {% endraw %}
-
-
-
-
-
-
-
Disabled Extensions
-
-
-
-
-
- Save
-
-
-
-
-
- Server Management
-
-
-
-
-
Server Info
-
- {%raw%}
-
- SQlite: {{data.admin.data_folder}}
-
-
- Postgres: {{data.admin.database_url}}
-
- {%endraw%}
-
-
-
-
-
-
-
-
Miscelaneous
-
-
- Force HTTPS
- Prefer secure URLs
-
-
-
-
-
-
-
- Hide API
- Hides wallet api, extensions can choose to
- honor
-
-
-
-
-
-
-
-
-
-
-
- Save
-
-
-
-
-
- UI Management
-
-
-
-
-
-
-
-
Default Wallet Name
-
-
-
-
-
-
-
-
-
Advertisement Slots
-
-
-
-
- {% raw %}
-
- {{ space.slice(0, 8) + " ... " + space.slice(-8) }}
-
- {% endraw %}
-
-
-
-
-
-
-
-
- Save
-
-
-
+ {% include "admin/_tab_funding.html" %} {% include
+ "admin/_tab_users.html" %} {% include "admin/_tab_server.html" %} {%
+ include "admin/_tab_theme.html" %}
-
-
-
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }}
{% endblock %}
From 3778990000848fc74c23e4fdab3696f73edda4b1 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 7 Oct 2022 19:24:07 +0100
Subject: [PATCH 106/696] make saving possible (possible will change in future)
---
.../admin/templates/admin/index.html | 36 ++++++++++++-------
1 file changed, 24 insertions(+), 12 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 72352651..18df16a9 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -121,8 +121,11 @@
created: function () {
this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data
this.balance = +'{{ balance|safe }}'
- this.formData = this.settings //model
+ this.formData = _.clone(this.settings) //model
+ //this.formData.lnbits_ad_space = "hdh"
console.log(this.formData)
+ console.log(_.isEqual(this.settings, this.formData))
+
},
methods: {
addAdminUser() {
@@ -206,18 +209,27 @@
},
updateSettings() {
let data = {
- ...this.settings,
- ...this.formData
+ lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class,
+ lnbits_admin_users: this.formData.lnbits_admin_users.toString(),
+ lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(),
+ lnbits_admin_ext: this.formData.lnbits_admin_ext,
+ lnbits_disabled_ext: this.formData.lnbits_disabled_ext,
+ lnbits_funding_source: this.formData.lnbits_funding_source,
+ lnbits_force_https: this.formData.lnbits_force_https,
+ lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min,
+ lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent,
+ lnbits_service_fee: this.formData.lnbits_service_fee,
+ lnbits_hide_api: this.formData.lnbits_hide_api,
+ lnbits_site_title: this.formData.lnbits_site_title,
+ lnbits_site_tagline: this.formData.lnbits_site_tagline,
+ lnbits_site_description: this.formData.lnbits_site_description,
+ lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name,
+ lnbits_denomination: this.formData.lnbits_denomination,
+ lnbits_theme: this.formData.lnbits_theme,
+ lnbits_custom_logo: this.formData.lnbits_custom_logo,
+ lnbits_ad_space: this.formData.lnbits_ad_space.toString()
}
- /*
- const formElement = document.getElementById('settings_form')
- const formData = new FormData(formElement)
- const data = {}
- formData.forEach((value, key) => (data[key] = value))
- // only for debugging
- for (const [key, value] of formData) {
- console.log(`${key}: ${value}\n`)
- }*/
+ console.log(data)
LNbits.api
.request(
'PUT',
From 3fdafca0a15bb9931fbfe3a61b7a228c91b2d855 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 7 Oct 2022 19:44:03 +0100
Subject: [PATCH 107/696] some more refining
---
lnbits/extensions/admin/models.py | 10 +++++-----
.../extensions/admin/templates/admin/_tab_users.html | 2 ++
lnbits/extensions/admin/templates/admin/index.html | 12 +++++++++---
3 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 13a6cd23..45cd990d 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -4,10 +4,10 @@ from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: str = Query(None)
- lnbits_allowed_users: str = Query(None)
- lnbits_admin_ext: str = Query(None)
- lnbits_disabled_ext: str = Query(None)
+ lnbits_admin_users: str = Query(None) #this should be List[str] ??
+ lnbits_allowed_users: str = Query(None) #this should be List[str] ??
+ lnbits_admin_ext: str = Query(None) #this should be List[str] ??
+ lnbits_disabled_ext: str = Query(None) #this should be List[str] ??
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -21,4 +21,4 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: str = Query(None)
+ lnbits_ad_space: str = Query(None) #this should be List[str] ??
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html
index c396ba7d..08b08a62 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_users.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_users.html
@@ -71,6 +71,7 @@
multiple
hint="Extensions only user with admin privileges can use"
label="Admin extensions"
+ :options="g.extensions.map(e => e.name)"
>
@@ -79,6 +80,7 @@
-
+
Date: Mon, 10 Oct 2022 12:17:35 +0100
Subject: [PATCH 108/696] get saved data and alert when data changed
---
.../admin/templates/admin/index.html | 18 +++++++-----------
lnbits/extensions/admin/views_api.py | 5 +++--
2 files changed, 10 insertions(+), 13 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 4754656d..4e401cb4 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -3,7 +3,7 @@
-
+
{
+ this.settings = response.data.settings
+ this.formData = _.clone(this.settings)
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index c8120564..c2079e37 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -43,8 +43,9 @@ async def api_update_settings(
user: User = Depends(check_admin),
data: UpdateSettings = Body(...),
):
- await update_settings(data)
- return {"status": "Success"}
+ settings = await update_settings(data)
+ logger.debug(settings)
+ return {"status": "Success", "settings": settings.dict()}
@admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK)
From a3b05e26b718db4969884e642416b2eac119f506 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 10 Oct 2022 12:23:19 +0100
Subject: [PATCH 109/696] cleanup and typing fix for data
---
lnbits/extensions/admin/models.py | 12 +++++-----
.../admin/templates/admin/index.html | 22 ++-----------------
lnbits/extensions/admin/views_api.py | 1 -
3 files changed, 9 insertions(+), 26 deletions(-)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 45cd990d..94fa56bb 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -1,13 +1,15 @@
+from typing import List
+
from fastapi import Query
from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: str = Query(None) #this should be List[str] ??
- lnbits_allowed_users: str = Query(None) #this should be List[str] ??
- lnbits_admin_ext: str = Query(None) #this should be List[str] ??
- lnbits_disabled_ext: str = Query(None) #this should be List[str] ??
+ lnbits_admin_users: List[str] = Query(None)
+ lnbits_allowed_users: List[str] = Query(None)
+ lnbits_admin_ext: List[str] = Query(None)
+ lnbits_disabled_ext: List[str] = Query(None)
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -21,4 +23,4 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: str = Query(None) #this should be List[str] ??
+ lnbits_ad_space: List[str] = Query(None)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 4e401cb4..d8111595 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -209,26 +209,8 @@
})
},
updateSettings() {
- let data = {
- lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class,
- lnbits_admin_users: this.formData.lnbits_admin_users.toString(),
- lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(),
- lnbits_admin_ext: this.formData.lnbits_admin_ext,
- lnbits_disabled_ext: this.formData.lnbits_disabled_ext,
- lnbits_funding_source: this.formData.lnbits_funding_source,
- lnbits_force_https: this.formData.lnbits_force_https,
- lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min,
- lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent,
- lnbits_service_fee: this.formData.lnbits_service_fee,
- lnbits_hide_api: this.formData.lnbits_hide_api,
- lnbits_site_title: this.formData.lnbits_site_title,
- lnbits_site_tagline: this.formData.lnbits_site_tagline,
- lnbits_site_description: this.formData.lnbits_site_description,
- lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name,
- lnbits_denomination: this.formData.lnbits_denomination,
- lnbits_theme: this.formData.lnbits_theme,
- lnbits_custom_logo: this.formData.lnbits_custom_logo,
- lnbits_ad_space: this.formData.lnbits_ad_space.toString()
+ let data = {
+ ...this.formData
}
LNbits.api
.request(
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index c2079e37..19b52e35 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -44,7 +44,6 @@ async def api_update_settings(
data: UpdateSettings = Body(...),
):
settings = await update_settings(data)
- logger.debug(settings)
return {"status": "Success", "settings": settings.dict()}
From 91a5f7d2143ac4e2eb4f58607a062c1386bea6e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 10 Oct 2022 23:27:46 +0200
Subject: [PATCH 110/696] add callback for saas app
---
lnbits/app.py | 2 +-
lnbits/extensions/admin/migrations.py | 3 +++
lnbits/extensions/admin/models.py | 10 ++++-----
lnbits/extensions/admin/views_api.py | 5 -----
lnbits/settings.py | 32 +++++++++++++++++++++++----
5 files changed, 37 insertions(+), 15 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index a8371950..6fa5cf5d 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -64,7 +64,7 @@ def create_app() -> FastAPI:
# TODO: why those 2?
g().config = settings
- # g().base_url = f"http://{settings.host}:{settings.port}"
+ g().base_url = f"http://{settings.host}:{settings.port}"
app.add_middleware(GZipMiddleware, minimum_size=1000)
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index c4bc98d8..ea698c27 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -6,6 +6,9 @@ async def m001_create_admin_settings_table(db):
debug TEXT,
host TEXT,
port INTEGER,
+ lnbits_saas_instance_id TEXT,
+ lnbits_saas_callback TEXT,
+ lnbits_saas_secret TEXT,
lnbits_path TEXT,
lnbits_commit TEXT,
lnbits_admin_users TEXT,
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 94fa56bb..ada84e9d 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -6,10 +6,10 @@ from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: List[str] = Query(None)
- lnbits_allowed_users: List[str] = Query(None)
- lnbits_admin_ext: List[str] = Query(None)
- lnbits_disabled_ext: List[str] = Query(None)
+ lnbits_admin_users: List[str] = Query(None)
+ lnbits_allowed_users: List[str] = Query(None)
+ lnbits_admin_ext: List[str] = Query(None)
+ lnbits_disabled_ext: List[str] = Query(None)
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -23,4 +23,4 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: List[str] = Query(None)
+ lnbits_ad_space: List[str] = Query(None)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 19b52e35..ae2959bc 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -53,8 +53,3 @@ async def api_delete_settings(
):
await delete_settings()
return {"status": "Success"}
-
-
-@admin_ext.get("/api/v1/backup/", status_code=HTTPStatus.OK)
-async def api_backup(user: User = Depends(check_admin)):
- return {"status": "not implemented"}
diff --git a/lnbits/settings.py b/lnbits/settings.py
index ffcdcc0a..f183211c 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,6 +1,7 @@
import importlib
import json
import subprocess
+import httpx
from os import path
from sqlite3 import Row
from typing import List, Optional
@@ -9,6 +10,7 @@ from loguru import logger
from pydantic import BaseSettings, Field, validator
+
def list_parse_fallback(v):
try:
return json.loads(v)
@@ -34,6 +36,11 @@ class Settings(BaseSettings):
lnbits_path: str = Field(default=".")
lnbits_commit: str = Field(default="unknown")
+ # saas
+ lnbits_saas_callback: Optional[str] = Field(default=None)
+ lnbits_saas_secret: Optional[str] = Field(default=None)
+ lnbits_saas_instance_id: Optional[str] = Field(default=None)
+
# users
lnbits_admin_users: List[str] = Field(default=[])
lnbits_allowed_users: List[str] = Field(default=[])
@@ -230,11 +237,28 @@ async def check_admin_settings():
http = "https" if settings.lnbits_force_https else "http"
user = settings.lnbits_admin_users[0]
- logger.warning(
- f" ✔️ Access admin user account at: {http}://{settings.host}:{settings.port}/wallet?usr={user}"
- )
+
+ admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
+ logger.warning(f"✔️ Access admin user account at: {admin_url}")
+
+ if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id:
+ with httpx.Client() as client:
+ headers = {
+ "Content-Type": "application/json; charset=utf-8",
+ "X-API-KEY": settings.lnbits_saas_secret
+ }
+ payload = {
+ "instance_id": settings.lnbits_saas_instance_id,
+ "adminuser": user
+ }
+ try:
+ r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload)
+ logger.warning("sent admin user to saas application")
+ except:
+ logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}")
+
except:
- logger.warning("admin.settings tables does not exist.")
+ logger.error("admin.settings tables does not exist.")
raise
From c9ead25d50cc1c8cd019e51d7147c4febb71b635 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Wed, 12 Oct 2022 13:08:59 +0200
Subject: [PATCH 111/696] bugfixes and fix topup wallet
---
lnbits/app.py | 1 +
.../admin/templates/admin/_tab_funding.html | 4 +--
.../admin/templates/admin/index.html | 28 +++++++++------
lnbits/extensions/admin/views_api.py | 36 ++++++++++---------
lnbits/server.py | 1 +
lnbits/settings.py | 29 ++++++++++-----
lnbits/wallets/fake.py | 2 +-
7 files changed, 60 insertions(+), 41 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 6fa5cf5d..49ad8d77 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -84,6 +84,7 @@ async def check_funding_source() -> None:
def signal_handler(signal, frame):
logger.debug(f"SIGINT received, terminating LNbits.")
sys.exit(1)
+
signal.signal(signal.SIGINT, signal_handler)
WALLET = get_wallet_class()
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
index 8b5456f1..34162a9f 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_funding.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -8,9 +8,7 @@
Funding Source Info
{%raw%}
-
- Funding Source: {{settings.lnbits_backend_wallet_class}}
-
+ Funding Source: {{settings.lnbits_backend_wallet_class}}
Balance: {{balance / 1000}} sats
{%endraw%}
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d8111595..d3e93a71 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -3,7 +3,13 @@
-
+
u !== user
- )
+ this.settings.lnbits_admin_users = admin_users.filter(u => u !== user)
},
addAllowedUser() {
let addUser = this.formData.allowed_users_add
@@ -155,7 +159,9 @@
},
removeAllowedUser(user) {
let allowed_users = this.settings.lnbits_allowed_users
- this.settings.lnbits_allowed_users = allowed_users.filter(u => u !== user)
+ this.settings.lnbits_allowed_users = allowed_users.filter(
+ u => u !== user
+ )
},
addAdSpace() {
let adSpace = this.formData.ad_space_add
@@ -187,10 +193,10 @@
topupWallet() {
LNbits.api
.request(
- 'POST',
+ 'PUT',
'/admin/api/v1/topup/?usr=' + this.g.user.id,
- this.wallet.id,
- this.wallet.amount
+ this.g.user.wallets[0].adminkey,
+ this.wallet
)
.then(response => {
this.$q.notify({
@@ -209,7 +215,7 @@
})
},
updateSettings() {
- let data = {
+ let data = {
...this.formData
}
LNbits.api
@@ -262,7 +268,7 @@
LNbits.utils.notifyApiError(error)
})
}
- },
+ }
})
{% endblock %}
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index ae2959bc..63ed5b3c 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,7 +1,6 @@
from http import HTTPStatus
-from fastapi import Body, Depends, Request
-from loguru import logger
+from fastapi import Body, Depends, Query
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_wallet
@@ -9,47 +8,50 @@ from lnbits.core.models import User
from lnbits.decorators import check_admin
from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import UpdateSettings
-from lnbits.requestvars import g
from lnbits.server import server_restart
-from lnbits.settings import settings
from .crud import delete_settings, update_settings, update_wallet_balance
-@admin_ext.get("/api/v1/restart/", status_code=HTTPStatus.OK)
-async def api_restart_server(user: User = Depends(check_admin)):
+@admin_ext.get(
+ "/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
+async def api_restart_server() -> dict[str, str]:
server_restart.set()
return {"status": "Success"}
-@admin_ext.put("/api/v1/topup/", status_code=HTTPStatus.OK)
+@admin_ext.put(
+ "/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
async def api_update_balance(
- wallet_id, topup_amount: int, user: User = Depends(check_admin)
-):
+ id: str = Body(...), amount: int = Body(...)
+) -> dict[str, str]:
try:
- wallet = await get_wallet(wallet_id)
+ await get_wallet(id)
except:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist."
)
- await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
+ await update_wallet_balance(wallet_id=id, amount=int(amount))
return {"status": "Success"}
-@admin_ext.put("/api/v1/settings/", status_code=HTTPStatus.OK)
+@admin_ext.put(
+ "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
async def api_update_settings(
- user: User = Depends(check_admin),
data: UpdateSettings = Body(...),
):
settings = await update_settings(data)
return {"status": "Success", "settings": settings.dict()}
-@admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK)
-async def api_delete_settings(
- user: User = Depends(check_admin),
-):
+@admin_ext.delete(
+ "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
+async def api_delete_settings() -> dict[str, str]:
await delete_settings()
return {"status": "Success"}
diff --git a/lnbits/server.py b/lnbits/server.py
index 79af8112..6d4cd2e7 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -52,6 +52,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload:
port=port,
host=host,
reload=reload,
+ forwarded_allow_ips="*",
ssl_keyfile=ssl_keyfile,
ssl_certfile=ssl_certfile,
**d
diff --git a/lnbits/settings.py b/lnbits/settings.py
index f183211c..61dbd6f2 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,20 +1,19 @@
import importlib
import json
import subprocess
-import httpx
from os import path
from sqlite3 import Row
from typing import List, Optional
+import httpx
from loguru import logger
from pydantic import BaseSettings, Field, validator
-
def list_parse_fallback(v):
try:
return json.loads(v)
- except Exception as e:
+ except Exception:
replaced = v.replace(" ", "")
if replaced:
return replaced.split(",")
@@ -238,24 +237,36 @@ async def check_admin_settings():
http = "https" if settings.lnbits_force_https else "http"
user = settings.lnbits_admin_users[0]
- admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
+ admin_url = (
+ f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
+ )
logger.warning(f"✔️ Access admin user account at: {admin_url}")
- if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id:
+ if (
+ settings.lnbits_saas_callback
+ and settings.lnbits_saas_secret
+ and settings.lnbits_saas_instance_id
+ ):
with httpx.Client() as client:
headers = {
"Content-Type": "application/json; charset=utf-8",
- "X-API-KEY": settings.lnbits_saas_secret
+ "X-API-KEY": settings.lnbits_saas_secret,
}
payload = {
"instance_id": settings.lnbits_saas_instance_id,
- "adminuser": user
+ "adminuser": user,
}
try:
- r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload)
+ client.post(
+ settings.lnbits_saas_callback,
+ headers=headers,
+ json=payload,
+ )
logger.warning("sent admin user to saas application")
except:
- logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}")
+ logger.error(
+ f"error sending admin user to saas: {settings.lnbits_saas_callback}"
+ )
except:
logger.error("admin.settings tables does not exist.")
diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py
index 73458e8c..94ff5f48 100644
--- a/lnbits/wallets/fake.py
+++ b/lnbits/wallets/fake.py
@@ -19,7 +19,6 @@ from .base import (
class FakeWallet(Wallet):
- queue: asyncio.Queue = asyncio.Queue(0)
secret: str = settings.fake_wallet_secret
privkey: str = hashlib.pbkdf2_hmac(
"sha256",
@@ -98,6 +97,7 @@ class FakeWallet(Wallet):
return PaymentStatus(None)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
+ self.queue: asyncio.Queue = asyncio.Queue(0)
while True:
value: Invoice = await self.queue.get()
yield value.payment_hash
From 9a48b174c62bd1908be3a8698c36b34aa78a3188 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 12 Oct 2022 19:04:46 +0100
Subject: [PATCH 112/696] add funding sources options
---
lnbits/app.py | 1 +
lnbits/extensions/admin/models.py | 41 +++-
.../admin/templates/admin/_tab_funding.html | 25 +-
.../admin/templates/admin/index.html | 217 +++++++++++++++++-
4 files changed, 259 insertions(+), 25 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index a8371950..50f218b7 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -84,6 +84,7 @@ async def check_funding_source() -> None:
def signal_handler(signal, frame):
logger.debug(f"SIGINT received, terminating LNbits.")
sys.exit(1)
+
signal.signal(signal.SIGINT, signal_handler)
WALLET = get_wallet_class()
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 94fa56bb..d9d2b22f 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -6,10 +6,10 @@ from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: List[str] = Query(None)
- lnbits_allowed_users: List[str] = Query(None)
- lnbits_admin_ext: List[str] = Query(None)
- lnbits_disabled_ext: List[str] = Query(None)
+ lnbits_admin_users: List[str] = Query(None)
+ lnbits_allowed_users: List[str] = Query(None)
+ lnbits_admin_ext: List[str] = Query(None)
+ lnbits_disabled_ext: List[str] = Query(None)
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -23,4 +23,35 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: List[str] = Query(None)
+ lnbits_ad_space: List[str] = Query(None)
+
+ # funding sources
+ fake_wallet_secret: str = Query(None)
+ lnbits_endpoint: str = Query(None)
+ lnbits_key: str = Query(None)
+ cliche_endpoint: str = Query(None)
+ corelightning_rpc: str = Query(None)
+ eclair_url: str = Query(None)
+ eclair_pass: str = Query(None)
+ lnd_rest_endpoint: str = Query(None)
+ lnd_rest_cert: str = Query(None)
+ lnd_rest_macaroon: str = Query(None)
+ lnd_rest_macaroon_encrypted: str = Query(None)
+ lnd_cert: str = Query(None)
+ lnd_admin_macaroon: str = Query(None)
+ lnd_invoice_macaroon: str = Query(None)
+ lnd_grpc_endpoint: str = Query(None)
+ lnd_grpc_cert: str = Query(None)
+ lnd_grpc_port: int = Query(None, ge=0)
+ lnd_grpc_admin_macaroon: str = Query(None)
+ lnd_grpc_invoice_macaroon: str = Query(None)
+ lnd_grpc_macaroon_encrypted: str = Query(None)
+ lnpay_api_endpoint: str = Query(None)
+ lnpay_api_key: str = Query(None)
+ lnpay_wallet_key: str = Query(None)
+ lntxbot_api_endpoint: str = Query(None)
+ lntxbot_key: str = Query(None)
+ opennode_api_endpoint: str = Query(None)
+ opennode_key: str = Query(None)
+ spark_url: str = Query(None)
+ spark_token: str = Query(None)
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
index 8b5456f1..a523d4e5 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_funding.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -8,9 +8,7 @@
Funding Source Info
{%raw%}
-
- Funding Source: {{settings.lnbits_backend_wallet_class}}
-
+ Funding Source: {{settings.lnbits_backend_wallet_class}}
Balance: {{balance / 1000}} sats
{%endraw%}
@@ -60,21 +58,30 @@
- Funding Sources
+ Funding Sources (Requires server restart)
-
+
-
-
+
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d8111595..ccaddda4 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -3,7 +3,13 @@
-
+
u !== user
- )
+ this.settings.lnbits_admin_users = admin_users.filter(u => u !== user)
},
addAllowedUser() {
let addUser = this.formData.allowed_users_add
@@ -155,7 +335,9 @@
},
removeAllowedUser(user) {
let allowed_users = this.settings.lnbits_allowed_users
- this.settings.lnbits_allowed_users = allowed_users.filter(u => u !== user)
+ this.settings.lnbits_allowed_users = allowed_users.filter(
+ u => u !== user
+ )
},
addAdSpace() {
let adSpace = this.formData.ad_space_add
@@ -208,8 +390,19 @@
LNbits.utils.notifyApiError(error)
})
},
+ updateFundingData(){
+ this.settings.lnbits_allowed_funding_sources.map(f => {
+ let opts = this.funding_sources.get(f)
+ if (!opts) return
+
+ Object.keys(opts).forEach(e => {
+ opts[e].value = this.settings[e]
+ })
+ })
+ console.log("funding", this.funding_sources)
+ },
updateSettings() {
- let data = {
+ let data = {
...this.formData
}
LNbits.api
@@ -222,11 +415,13 @@
.then(response => {
this.settings = response.data.settings
this.formData = _.clone(this.settings)
+ this.updateFundingData()
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
icon: null
})
+ console.log(this.settings)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -262,7 +457,7 @@
LNbits.utils.notifyApiError(error)
})
}
- },
+ }
})
{% endblock %}
From dde28e66a24e09517b668e291ed58a6eb7df37e2 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 13 Oct 2022 15:52:02 +0100
Subject: [PATCH 113/696] added tooltips, moved reset, and warnings
---
.../admin/templates/admin/index.html | 97 ++++++++++++-------
1 file changed, 61 insertions(+), 36 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 575b377f..7d268301 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1,8 +1,14 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
-
-
+
+
+ Save your changes
-
-
-
+
+
+ Restart the server for changes to take effect
+
+
+
+
+ Add funds to a wallet.
+
+ > -->
+
+ Delete all settings and reset to defaults.
+
@@ -121,6 +136,7 @@
show: false
},
tab: 'funding',
+ needsRestart: false,
funding_sources: new Map([
['VoidWallet', null],
[
@@ -302,13 +318,12 @@
this.balance = +'{{ balance|safe }}'
this.formData = _.clone(this.settings) //model
this.updateFundingData()
-
console.log(this.settings)
},
computed: {
checkChanges() {
return !_.isEqual(this.settings, this.formData)
- },
+ }
},
methods: {
addAdminUser() {
@@ -361,6 +376,7 @@
message: 'Success! Restarted Server',
icon: null
})
+ this.needsRestart = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -390,16 +406,15 @@
LNbits.utils.notifyApiError(error)
})
},
- updateFundingData(){
+ updateFundingData() {
this.settings.lnbits_allowed_funding_sources.map(f => {
let opts = this.funding_sources.get(f)
if (!opts) return
-
+
Object.keys(opts).forEach(e => {
opts[e].value = this.settings[e]
})
})
- console.log("funding", this.funding_sources)
},
updateSettings() {
let data = {
@@ -415,31 +430,41 @@
.then(response => {
this.settings = response.data.settings
this.formData = _.clone(this.settings)
+ this.needsRestart = true
this.updateFundingData()
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
icon: null
})
- console.log(this.settings)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteSettings() {
- LNbits.api
- .request('DELETE', '/admin/api/v1/settings/?usr=' + this.g.user.id)
- .then(response => {
- this.$q.notify({
- type: 'positive',
- message:
- 'Success! Restored settings to defaults, restart required!',
- icon: null
- })
- })
- .catch(function (error) {
- LNbits.utils.notifyApiError(error)
+ LNbits.utils
+ .confirmDialog(
+ 'Are you sure you want to restore settings to default?'
+ )
+ .onOk(() => {
+ LNbits.api
+ .request(
+ 'DELETE',
+ '/admin/api/v1/settings/?usr=' + this.g.user.id
+ )
+ .then(response => {
+ this.$q.notify({
+ type: 'positive',
+ message:
+ 'Success! Restored settings to defaults, restart required!',
+ icon: null
+ })
+ this.needsRestart = true
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
})
},
downloadBackup() {
From fb2dee73955aef2700bdf52decb6a570440beb23 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 13 Oct 2022 15:52:26 +0100
Subject: [PATCH 114/696] moved to columns
---
.../admin/templates/admin/_tab_funding.html | 54 +++++++++----------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
index a523d4e5..a69ecb47 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_funding.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -27,38 +27,38 @@
:options="settings.lnbits_allowed_funding_sources"
>
-
+
-
-
-
- Funding Sources (Requires server restart)
+
+ Funding Sources (Requires server restart)
+
Date: Thu, 13 Oct 2022 15:52:39 +0100
Subject: [PATCH 115/696] themes not displaying fixed
---
lnbits/extensions/admin/templates/admin/_tab_theme.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html
index 46bf83e9..c327733f 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_theme.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html
@@ -63,7 +63,7 @@
Themes
Date: Fri, 21 Oct 2022 10:00:47 +0200
Subject: [PATCH 116/696] add loop to uvicorn
---
lnbits/server.py | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/lnbits/server.py b/lnbits/server.py
index 6d4cd2e7..eb7c12b1 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -1,12 +1,7 @@
-import asyncio
-
import uvloop
-
uvloop.install()
-import contextlib
import multiprocessing as mp
-import sys
import time
import click
@@ -49,6 +44,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload:
while True:
config = uvicorn.Config(
"lnbits.__main__:app",
+ loop="uvloop",
port=port,
host=host,
reload=reload,
@@ -65,9 +61,10 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload:
server_restart.clear()
server.should_exit = True
server.force_exit = True
+ time.sleep(3)
process.terminate()
process.join()
- time.sleep(3)
+ time.sleep(1)
server_restart = mp.Event()
From 1b675f295bd2ec1a69ae972cce9b70f0371eefdc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Fri, 21 Oct 2022 11:13:40 +0200
Subject: [PATCH 117/696] add get settings endpoint with only values you can
also save
---
lnbits/app.py | 6 +----
lnbits/extensions/admin/crud.py | 12 +++++++++-
lnbits/extensions/admin/models.py | 6 ++++-
.../admin/templates/admin/index.html | 22 +++++++++++++++----
lnbits/extensions/admin/views_api.py | 7 +++++-
lnbits/server.py | 1 +
6 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 49ad8d77..959a8168 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -62,10 +62,6 @@ def create_app() -> FastAPI:
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
- # TODO: why those 2?
- g().config = settings
- g().base_url = f"http://{settings.host}:{settings.port}"
-
app.add_middleware(GZipMiddleware, minimum_size=1000)
register_startup(app)
@@ -174,7 +170,7 @@ def register_assets(app: FastAPI):
@app.on_event("startup")
async def vendored_assets_variable():
- if g().config.debug:
+ if settings.debug:
g().VENDORED_JS = map(url_for_vendored, get_js_vendored())
g().VENDORED_CSS = map(url_for_vendored, get_css_vendored())
else:
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index cc937b5e..2ce91612 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -6,7 +6,7 @@ from lnbits.settings import Settings, read_only_variables
from lnbits.tasks import internal_invoice_queue
from . import db
-from .models import UpdateSettings
+from .models import AdminSettings, UpdateSettings
async def update_wallet_balance(wallet_id: str, amount: int) -> str:
@@ -26,6 +26,16 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
await internal_invoice_queue.put(internal_id)
+async def get_settings() -> AdminSettings:
+ row = await db.fetchone("SELECT * FROM admin.settings")
+ all_settings = Settings(**row)
+ settings = AdminSettings()
+ for key, value in row.items():
+ if hasattr(settings, key):
+ setattr(settings, key, getattr(all_settings, key))
+ return settings
+
+
async def update_settings(data: UpdateSettings) -> Settings:
fields = []
for key, value in data.dict(exclude_none=True).items():
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index d9d2b22f..31811659 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import List, Optional
from fastapi import Query
from pydantic import BaseModel
@@ -55,3 +55,7 @@ class UpdateSettings(BaseModel):
opennode_key: str = Query(None)
spark_url: str = Query(None)
spark_token: str = Query(None)
+
+
+class AdminSettings(UpdateSettings):
+ lnbits_allowed_funding_sources: Optional[List[str]]
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 7d268301..10391261 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -314,11 +314,8 @@
}
},
created: function () {
- this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data
+ this.getSettings()
this.balance = +'{{ balance|safe }}'
- this.formData = _.clone(this.settings) //model
- this.updateFundingData()
- console.log(this.settings)
},
computed: {
checkChanges() {
@@ -416,6 +413,23 @@
})
})
},
+ getSettings() {
+ LNbits.api
+ .request(
+ 'GET',
+ '/admin/api/v1/settings/?usr=' + this.g.user.id,
+ this.g.user.wallets[0].adminkey
+ )
+ .then(response => {
+ this.settings = response.data
+ this.formData = _.clone(this.settings)
+ this.updateFundingData()
+ console.log(this.settings)
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
updateSettings() {
let data = {
...this.formData
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 63ed5b3c..57d62ed4 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -10,7 +10,7 @@ from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import UpdateSettings
from lnbits.server import server_restart
-from .crud import delete_settings, update_settings, update_wallet_balance
+from .crud import delete_settings, get_settings, update_settings, update_wallet_balance
@admin_ext.get(
@@ -21,6 +21,11 @@ async def api_restart_server() -> dict[str, str]:
return {"status": "Success"}
+@admin_ext.get("/api/v1/settings/", dependencies=[Depends(check_admin)])
+async def api_get_settings() -> UpdateSettings:
+ return await get_settings()
+
+
@admin_ext.put(
"/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
)
diff --git a/lnbits/server.py b/lnbits/server.py
index eb7c12b1..ecf7ff62 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -1,4 +1,5 @@
import uvloop
+
uvloop.install()
import multiprocessing as mp
From 49c58cc8d0cb48fd874ba40d28c4331a065424df Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Mon, 24 Oct 2022 11:48:34 +0200
Subject: [PATCH 118/696] return a reason of failed payment to the pos; disable
a change of wallet in the form
---
lnbits/extensions/boltcards/lnurl.py | 4 ++--
lnbits/extensions/boltcards/templates/boltcards/index.html | 1 +
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/boltcards/lnurl.py b/lnbits/extensions/boltcards/lnurl.py
index 064bde2c..cd4c6ba4 100644
--- a/lnbits/extensions/boltcards/lnurl.py
+++ b/lnbits/extensions/boltcards/lnurl.py
@@ -136,8 +136,8 @@ async def lnurl_callback(
)
return {"status": "OK"}
- except:
- return {"status": "ERROR", "reason": f"Payment failed"}
+ except Exception as exc:
+ return {"status": "ERROR", "reason": f"Payment failed - {exc}"}
# /boltcards/api/v1/auth?a=00000000000000000000000000000000
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index f795e454..7b9713e2 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -215,6 +215,7 @@
emit-value
v-model="cardDialog.data.wallet"
:options="g.user.walletOptions"
+ :disable="cardDialog.data.id != null"
label="Wallet *"
>
From 708f855c1d485639dfa559c2f2a26a5a9f41c1d7 Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Mar 2022 05:03:32 +0000
Subject: [PATCH 119/696] Added old admin extension
---
.env.example | 14 +-
lnbits/extensions/admin/README.md | 11 +
lnbits/extensions/admin/__init__.py | 10 +
lnbits/extensions/admin/config.json | 6 +
lnbits/extensions/admin/crud.py | 59 ++
lnbits/extensions/admin/migrations.py | 256 ++++++++
lnbits/extensions/admin/models.py | 38 ++
.../admin/templates/admin/index.html | 565 ++++++++++++++++++
lnbits/extensions/admin/views.py | 20 +
lnbits/extensions/admin/views_api.py | 41 ++
10 files changed, 1014 insertions(+), 6 deletions(-)
create mode 100644 lnbits/extensions/admin/README.md
create mode 100644 lnbits/extensions/admin/__init__.py
create mode 100644 lnbits/extensions/admin/config.json
create mode 100644 lnbits/extensions/admin/crud.py
create mode 100644 lnbits/extensions/admin/migrations.py
create mode 100644 lnbits/extensions/admin/models.py
create mode 100644 lnbits/extensions/admin/templates/admin/index.html
create mode 100644 lnbits/extensions/admin/views.py
create mode 100644 lnbits/extensions/admin/views_api.py
diff --git a/.env.example b/.env.example
index 987c6ca6..bfaeb515 100644
--- a/.env.example
+++ b/.env.example
@@ -3,17 +3,19 @@ PORT=5000
DEBUG=false
-LNBITS_ALLOWED_USERS=""
-LNBITS_ADMIN_USERS=""
-# Extensions only admin can access
-LNBITS_ADMIN_EXTENSIONS="ngrok"
+LNBITS_ADMIN_USERS="" # User IDs seperated by comma
+LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access
+LNBITS_ADMIN_UI=false # Extensions only admin can access
+
+LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
+
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
# csv ad image filepaths or urls, extensions can choose to honor
LNBITS_AD_SPACE=""
# Hides wallet api, extensions can choose to honor
-LNBITS_HIDE_API=false
+LNBITS_HIDE_API=false
# Disable extensions for all users, use "all" to disable all extensions
LNBITS_DISABLED_EXTENSIONS="amilk"
@@ -67,7 +69,7 @@ LNBITS_KEY=LNBITS_ADMIN_KEY
LND_REST_ENDPOINT=https://127.0.0.1:8080/
LND_REST_CERT="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/tls.cert"
LND_REST_MACAROON="/home/bob/.config/Zap/lnd/bitcoin/mainnet/wallet-1/data/chain/bitcoin/mainnet/admin.macaroon or HEXSTRING"
-# To use an AES-encrypted macaroon, set
+# To use an AES-encrypted macaroon, set
# LND_REST_MACAROON_ENCRYPTED="eNcRyPtEdMaCaRoOn"
# LNPayWallet
diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md
new file mode 100644
index 00000000..27729459
--- /dev/null
+++ b/lnbits/extensions/admin/README.md
@@ -0,0 +1,11 @@
+Example Extension
+*tagline*
+This is an example extension to help you organise and build you own.
+
+Try to include an image
+
+
+
+If your extension has API endpoints, include useful ones here
+
+curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"
diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py
new file mode 100644
index 00000000..d5f26c90
--- /dev/null
+++ b/lnbits/extensions/admin/__init__.py
@@ -0,0 +1,10 @@
+from quart import Blueprint
+from lnbits.db import Database
+
+db = Database("ext_admin")
+
+admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates")
+
+
+from .views_api import * # noqa
+from .views import * # noqa
diff --git a/lnbits/extensions/admin/config.json b/lnbits/extensions/admin/config.json
new file mode 100644
index 00000000..69661733
--- /dev/null
+++ b/lnbits/extensions/admin/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Admin",
+ "short_description": "Manage your LNbits install",
+ "icon": "build",
+ "contributors": ["benarc"]
+}
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
new file mode 100644
index 00000000..cb8f9b5b
--- /dev/null
+++ b/lnbits/extensions/admin/crud.py
@@ -0,0 +1,59 @@
+from typing import List, Optional
+
+from . import db
+from .models import Admin, Funding
+from lnbits.settings import *
+from lnbits.helpers import urlsafe_short_hash
+from lnbits.core.crud import create_payment
+from lnbits.db import Connection
+
+
+def update_wallet_balance(wallet_id: str, amount: int) -> str:
+ temp_id = f"temp_{urlsafe_short_hash()}"
+ internal_id = f"internal_{urlsafe_short_hash()}"
+ create_payment(
+ wallet_id=wallet_id,
+ checking_id=internal_id,
+ payment_request="admin_internal",
+ payment_hash="admin_internal",
+ amount=amount * 1000,
+ memo="Admin top up",
+ pending=False,
+ )
+ return "success"
+
+
+async def update_admin(
+) -> Optional[Admin]:
+ if not CLightningWallet:
+ print("poo")
+ await db.execute(
+ """
+ UPDATE admin
+ SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ?
+ WHERE 1
+ """,
+ (
+
+ ),
+ )
+ row = await db.fetchone("SELECT * FROM admin WHERE 1")
+ return Admin.from_row(row) if row else None
+
+async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]:
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id)
+ )
+ row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,))
+ return Jukebox(**row) if row else None
+
+async def get_admin() -> List[Admin]:
+ row = await db.fetchone("SELECT * FROM admin WHERE 1")
+ return Admin.from_row(row) if row else None
+
+
+async def get_funding() -> List[Funding]:
+ rows = await db.fetchall("SELECT * FROM funding")
+
+ return [Funding.from_row(row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
new file mode 100644
index 00000000..82d934cb
--- /dev/null
+++ b/lnbits/extensions/admin/migrations.py
@@ -0,0 +1,256 @@
+from sqlalchemy.exc import OperationalError # type: ignore
+from os import getenv
+from lnbits.helpers import urlsafe_short_hash
+
+
+async def m001_create_admin_table(db):
+ user = None
+ site_title = None
+ site_tagline = None
+ site_description = None
+ allowed_users = None
+ admin_user = None
+ default_wallet_name = None
+ data_folder = None
+ disabled_ext = None
+ force_https = True
+ service_fee = 0
+ funding_source = ""
+
+ if getenv("LNBITS_SITE_TITLE"):
+ site_title = getenv("LNBITS_SITE_TITLE")
+
+ if getenv("LNBITS_SITE_TAGLINE"):
+ site_tagline = getenv("LNBITS_SITE_TAGLINE")
+
+ if getenv("LNBITS_SITE_DESCRIPTION"):
+ site_description = getenv("LNBITS_SITE_DESCRIPTION")
+
+ if getenv("LNBITS_ALLOWED_USERS"):
+ allowed_users = getenv("LNBITS_ALLOWED_USERS")
+
+ if getenv("LNBITS_ADMIN_USER"):
+ admin_user = getenv("LNBITS_ADMIN_USER")
+
+ if getenv("LNBITS_DEFAULT_WALLET_NAME"):
+ default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
+
+ if getenv("LNBITS_DATA_FOLDER"):
+ data_folder = getenv("LNBITS_DATA_FOLDER")
+
+ if getenv("LNBITS_DISABLED_EXTENSIONS"):
+ disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
+
+ if getenv("LNBITS_FORCE_HTTPS"):
+ force_https = getenv("LNBITS_FORCE_HTTPS")
+
+ if getenv("LNBITS_SERVICE_FEE"):
+ service_fee = getenv("LNBITS_SERVICE_FEE")
+
+ if getenv("LNBITS_BACKEND_WALLET_CLASS"):
+ funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
+
+ await db.execute(
+ """
+ CREATE TABLE IF NOT EXISTS admin (
+ user TEXT,
+ site_title TEXT,
+ site_tagline TEXT,
+ site_description TEXT,
+ admin_user TEXT,
+ allowed_users TEXT,
+ default_wallet_name TEXT,
+ data_folder TEXT,
+ disabled_ext TEXT,
+ force_https BOOLEAN,
+ service_fee INT,
+ funding_source TEXT
+ );
+ """
+ )
+ await db.execute(
+ """
+ INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ user,
+ site_title,
+ site_tagline,
+ site_description,
+ admin_user,
+ allowed_users,
+ default_wallet_name,
+ data_folder,
+ disabled_ext,
+ force_https,
+ service_fee,
+ funding_source,
+ ),
+ )
+
+
+async def m001_create_funding_table(db):
+
+ funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS")
+
+ # Make the funding table, if it does not already exist
+ await db.execute(
+ """
+ CREATE TABLE IF NOT EXISTS funding (
+ id TEXT PRIMARY KEY,
+ backend_wallet TEXT,
+ endpoint TEXT,
+ port INT,
+ read_key TEXT,
+ invoice_key TEXT,
+ admin_key TEXT,
+ cert TEXT,
+ balance INT,
+ selected INT
+ );
+ """
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, selected)
+ VALUES (?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "CLightningWallet",
+ getenv("CLIGHTNING_RPC"),
+ 1 if funding_wallet == "CLightningWallet" else 0,
+ ),
+ )
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "SparkWallet",
+ getenv("SPARK_URL"),
+ getenv("SPARK_TOKEN"),
+ 1 if funding_wallet == "SparkWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LnbitsWallet",
+ getenv("LNBITS_ENDPOINT"),
+ getenv("LNBITS_KEY"),
+ 1 if funding_wallet == "LnbitsWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LndWallet",
+ getenv("LND_GRPC_ENDPOINT"),
+ getenv("LND_GRPC_PORT"),
+ getenv("LND_GRPC_MACAROON"),
+ getenv("LND_GRPC_CERT"),
+ 1 if funding_wallet == "LndWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LndRestWallet",
+ getenv("LND_REST_ENDPOINT"),
+ getenv("LND_REST_MACAROON"),
+ getenv("LND_REST_CERT"),
+ 1 if funding_wallet == "LndWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LNPayWallet",
+ getenv("LNPAY_API_ENDPOINT"),
+ getenv("LNPAY_WALLET_KEY"),
+ getenv("LNPAY_API_KEY"), # this is going in as the cert
+ 1 if funding_wallet == "LNPayWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "LntxbotWallet",
+ getenv("LNTXBOT_API_ENDPOINT"),
+ getenv("LNTXBOT_KEY"),
+ 1 if funding_wallet == "LntxbotWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "OpenNodeWallet",
+ getenv("OPENNODE_API_ENDPOINT"),
+ getenv("OPENNODE_KEY"),
+ 1 if funding_wallet == "OpenNodeWallet" else 0,
+ ),
+ )
+
+ await db.execute(
+ """
+ INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ urlsafe_short_hash(),
+ "SparkWallet",
+ getenv("SPARK_URL"),
+ getenv("SPARK_TOKEN"),
+ 1 if funding_wallet == "SparkWallet" else 0,
+ ),
+ )
+
+ ## PLACEHOLDER FOR ECLAIR WALLET
+ # await db.execute(
+ # """
+ # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ # VALUES (?, ?, ?, ?, ?)
+ # """,
+ # (
+ # urlsafe_short_hash(),
+ # "EclairWallet",
+ # getenv("ECLAIR_URL"),
+ # getenv("ECLAIR_PASS"),
+ # 1 if funding_wallet == "EclairWallet" else 0,
+ # ),
+ # )
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
new file mode 100644
index 00000000..c38f17f4
--- /dev/null
+++ b/lnbits/extensions/admin/models.py
@@ -0,0 +1,38 @@
+from typing import NamedTuple
+from sqlite3 import Row
+
+class Admin(NamedTuple):
+ user: str
+ site_title: str
+ site_tagline: str
+ site_description:str
+ allowed_users: str
+ admin_user: str
+ default_wallet_name: str
+ data_folder: str
+ disabled_ext: str
+ force_https: str
+ service_fee: str
+ funding_source: str
+
+ @classmethod
+ def from_row(cls, row: Row) -> "Admin":
+ data = dict(row)
+ return cls(**data)
+
+class Funding(NamedTuple):
+ id: str
+ backend_wallet: str
+ endpoint: str
+ port: str
+ read_key: str
+ invoice_key: str
+ admin_key: str
+ cert: str
+ balance: int
+ selected: int
+
+ @classmethod
+ def from_row(cls, row: Row) -> "Funding":
+ data = dict(row)
+ return cls(**data)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
new file mode 100644
index 00000000..87cf09ef
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -0,0 +1,565 @@
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
+%} {% block page %}
+
+Admin
+
+
+
+
+
+
+ Settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Wallet topup
+
+
+
+
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+{% endblock %}
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
new file mode 100644
index 00000000..5e17919c
--- /dev/null
+++ b/lnbits/extensions/admin/views.py
@@ -0,0 +1,20 @@
+from quart import g, render_template, request, jsonify
+import json
+
+from lnbits.decorators import check_user_exists, validate_uuids
+from lnbits.extensions.admin import admin_ext
+from lnbits.core.crud import get_user, create_account
+from .crud import get_admin, get_funding
+from lnbits.settings import WALLET
+
+
+@admin_ext.route("/")
+@validate_uuids(["usr"], required=True)
+@check_user_exists()
+async def index():
+ user_id = g.user
+ admin = await get_admin()
+
+ funding = [{**funding._asdict()} for funding in await get_funding()]
+
+ return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
new file mode 100644
index 00000000..2a61b6f5
--- /dev/null
+++ b/lnbits/extensions/admin/views_api.py
@@ -0,0 +1,41 @@
+from quart import jsonify, g, request
+from http import HTTPStatus
+from .crud import update_wallet_balance
+from lnbits.extensions.admin import admin_ext
+from lnbits.decorators import api_check_wallet_key, api_validate_post_request
+from lnbits.core.crud import get_wallet
+from .crud import get_admin,update_admin
+import json
+
+@admin_ext.route("/api/v1/admin//", methods=["GET"])
+@api_check_wallet_key("admin")
+async def api_update_balance(wallet_id, topup_amount):
+ print(g.data.wallet)
+ try:
+ wallet = await get_wallet(wallet_id)
+ except:
+ return (
+ jsonify({"error": "Not allowed: not an admin"}),
+ HTTPStatus.FORBIDDEN,
+ )
+ print(wallet)
+ print(topup_amount)
+ return jsonify({"status": "Success"}), HTTPStatus.OK
+
+
+@admin_ext.route("/api/v1/admin/", methods=["POST"])
+@api_check_wallet_key("admin")
+@api_validate_post_request(schema={})
+async def api_update_admin():
+ body = await request.get_json()
+ admin = await get_admin()
+ print(g.wallet[2])
+ print(body["admin_user"])
+ if not admin.admin_user == g.wallet[2] and admin.admin_user != None:
+ return (
+ jsonify({"error": "Not allowed: not an admin"}),
+ HTTPStatus.FORBIDDEN,
+ )
+ updated = await update_admin(body)
+ print(updated)
+ return jsonify({"status": "Success"}), HTTPStatus.OK
\ No newline at end of file
From a3b1d9528c92008b42116904f5ebbdf8d9360173 Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Mar 2022 05:11:55 +0000
Subject: [PATCH 120/696] old admin setup UI
---
lnbits/core/templates/core/admin.html | 717 ++++++++++++++++++++++++++
1 file changed, 717 insertions(+)
create mode 100644 lnbits/core/templates/core/admin.html
diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html
new file mode 100644
index 00000000..e8176555
--- /dev/null
+++ b/lnbits/core/templates/core/admin.html
@@ -0,0 +1,717 @@
+{% extends "public.html" %} {% from "macros.jinja" import window_vars with
+context %} {% block page %}
+
+
+
+
+
+ Welcome to LNbits
+
+
+ Fill in the information below to setup your LNbits instance. Details
+ can be changed later.
+
+
+
+
+
+
+ Branding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Service settings
+
+
+
+ Funding source information (at least one required) *if installed through RaspiBlitz, MyNode, etc, details
+ should be filled in for you
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ View project in GitHub
+ Donate
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }}
+
+{% endblock %}
From b325566302f079575e478d9ca9eeff47a8f10a1a Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:18:09 +0000
Subject: [PATCH 121/696] convert to FastAPI
---
lnbits/extensions/admin/__init__.py | 11 +++-
lnbits/extensions/admin/crud.py | 50 +++++++--------
lnbits/extensions/admin/migrations.py | 23 ++++---
lnbits/extensions/admin/models.py | 51 ++++++++++------
.../admin/templates/admin/index.html | 23 +++++--
lnbits/extensions/admin/views.py | 39 ++++++++----
lnbits/extensions/admin/views_api.py | 61 ++++++++++---------
7 files changed, 151 insertions(+), 107 deletions(-)
diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py
index d5f26c90..6a56b2bb 100644
--- a/lnbits/extensions/admin/__init__.py
+++ b/lnbits/extensions/admin/__init__.py
@@ -1,10 +1,15 @@
-from quart import Blueprint
+from fastapi import APIRouter
+
from lnbits.db import Database
+from lnbits.helpers import template_renderer
db = Database("ext_admin")
-admin_ext: Blueprint = Blueprint("admin", __name__, static_folder="static", template_folder="templates")
+admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"])
+
+def admin_renderer():
+ return template_renderer(["lnbits/extensions/admin/templates"])
-from .views_api import * # noqa
from .views import * # noqa
+from .views_api import * # noqa
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index cb8f9b5b..872d6c97 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,11 +1,11 @@
from typing import List, Optional
+from lnbits.core.crud import create_payment
+from lnbits.helpers import urlsafe_short_hash
+from lnbits.settings import *
+
from . import db
from .models import Admin, Funding
-from lnbits.settings import *
-from lnbits.helpers import urlsafe_short_hash
-from lnbits.core.crud import create_payment
-from lnbits.db import Connection
def update_wallet_balance(wallet_id: str, amount: int) -> str:
@@ -22,38 +22,30 @@ def update_wallet_balance(wallet_id: str, amount: int) -> str:
)
return "success"
-
-async def update_admin(
-) -> Optional[Admin]:
- if not CLightningWallet:
- print("poo")
- await db.execute(
- """
- UPDATE admin
- SET user = ?, site_title = ?, site_tagline = ?, site_description = ?, allowed_users = ?, default_wallet_name = ?, data_folder = ?, disabled_ext = ?, force_https = ?, service_fee = ?, funding_source = ?
- WHERE 1
- """,
- (
-
- ),
- )
- row = await db.fetchone("SELECT * FROM admin WHERE 1")
- return Admin.from_row(row) if row else None
-
-async def update_admin(admin_id: str, **kwargs) -> Optional[Admin]:
+async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ print("UPDATE", q)
await db.execute(
- f"UPDATE jukebox.jukebox SET {q} WHERE id = ?", (*kwargs.values(), juke_id)
+ f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
- row = await db.fetchone("SELECT * FROM jukebox.jukebox WHERE id = ?", (juke_id,))
- return Jukebox(**row) if row else None
+ row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,))
+ assert row, "Newly updated settings couldn't be retrieved"
+ return Admin(**row) if row else None
+
+# async def update_admin(user: str, **kwargs) -> Optional[Admin]:
+# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+# await db.execute(
+# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user)
+# )
+# new_settings = await get_admin()
+# return new_settings
async def get_admin() -> List[Admin]:
- row = await db.fetchone("SELECT * FROM admin WHERE 1")
- return Admin.from_row(row) if row else None
+ row = await db.fetchone("SELECT * FROM admin")
+ return Admin(**row) if row else None
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM funding")
- return [Funding.from_row(row) for row in rows]
+ return [Funding(**row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 82d934cb..13b76923 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -1,5 +1,7 @@
-from sqlalchemy.exc import OperationalError # type: ignore
from os import getenv
+
+from sqlalchemy.exc import OperationalError # type: ignore
+
from lnbits.helpers import urlsafe_short_hash
@@ -9,7 +11,7 @@ async def m001_create_admin_table(db):
site_tagline = None
site_description = None
allowed_users = None
- admin_user = None
+ admin_users = None
default_wallet_name = None
data_folder = None
disabled_ext = None
@@ -29,8 +31,9 @@ async def m001_create_admin_table(db):
if getenv("LNBITS_ALLOWED_USERS"):
allowed_users = getenv("LNBITS_ALLOWED_USERS")
- if getenv("LNBITS_ADMIN_USER"):
- admin_user = getenv("LNBITS_ADMIN_USER")
+ if getenv("LNBITS_ADMIN_USERS"):
+ admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
+ user = admin_users.split(',')[0]
if getenv("LNBITS_DEFAULT_WALLET_NAME"):
default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
@@ -53,32 +56,32 @@ async def m001_create_admin_table(db):
await db.execute(
"""
CREATE TABLE IF NOT EXISTS admin (
- user TEXT,
+ "user" TEXT,
site_title TEXT,
site_tagline TEXT,
site_description TEXT,
- admin_user TEXT,
+ admin_users TEXT,
allowed_users TEXT,
default_wallet_name TEXT,
data_folder TEXT,
disabled_ext TEXT,
force_https BOOLEAN,
- service_fee INT,
+ service_fee REAL,
funding_source TEXT
);
"""
)
await db.execute(
"""
- INSERT INTO admin (user, site_title, site_tagline, site_description, admin_user, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
+ INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
- user,
+ user.strip(),
site_title,
site_tagline,
site_description,
- admin_user,
+ admin_users[1:],
allowed_users,
default_wallet_name,
data_folder,
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index c38f17f4..4080ff01 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -1,18 +1,35 @@
-from typing import NamedTuple
from sqlite3 import Row
+from typing import List, Optional
-class Admin(NamedTuple):
+from fastapi import Query
+from pydantic import BaseModel
+
+
+class UpdateAdminSettings(BaseModel):
+ site_title: Optional[str]
+ site_tagline: Optional[str]
+ site_description: Optional[str]
+ allowed_users: Optional[str]
+ admin_users: Optional[str]
+ default_wallet_name: Optional[str]
+ data_folder: Optional[str]
+ disabled_ext: Optional[str]
+ force_https: Optional[bool]
+ service_fee: Optional[float]
+ funding_source: Optional[str]
+
+class Admin(BaseModel):
user: str
- site_title: str
- site_tagline: str
- site_description:str
- allowed_users: str
- admin_user: str
+ site_title: Optional[str]
+ site_tagline: Optional[str]
+ site_description: Optional[str]
+ allowed_users: Optional[str]
+ admin_users: str
default_wallet_name: str
data_folder: str
disabled_ext: str
- force_https: str
- service_fee: str
+ force_https: Optional[bool] = Query(True)
+ service_fee: float
funding_source: str
@classmethod
@@ -20,16 +37,16 @@ class Admin(NamedTuple):
data = dict(row)
return cls(**data)
-class Funding(NamedTuple):
+class Funding(BaseModel):
id: str
backend_wallet: str
- endpoint: str
- port: str
- read_key: str
- invoice_key: str
- admin_key: str
- cert: str
- balance: int
+ endpoint: str = Query(None)
+ port: str = Query(None)
+ read_key: str = Query(None)
+ invoice_key: str = Query(None)
+ admin_key: str = Query(None)
+ cert: str = Query(None)
+ balance: int = Query(None)
selected: int
@classmethod
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 87cf09ef..a6b45625 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -87,7 +87,7 @@
@@ -442,13 +442,14 @@
site_title: '{{admin.site_title}}',
tagline: '{{admin.site_tagline}}',
description: '{{admin.site_description}}',
- admin_user: '{{admin.admin_user}}',
- service_fee: parseInt('{{admin.service_fee}}'),
+ admin_users: '{{admin.admin_users}}',
+ service_fee: parseFloat('{{admin.service_fee}}'),
default_wallet_name: '{{admin.default_wallet_name}}',
data_folder: '{{admin.data_folder}}',
funding_source_primary: '{{admin.funding_source}}',
disabled_ext: '{{admin.disabled_ext}}'.split(','),
edited: [],
+ funding: {},
senddata: {}
}
},
@@ -528,15 +529,27 @@
},
UpdateLNbits: function () {
var self = this
- console.log(self.data.admin)
+ let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin
+ let data = {
+ site_title,
+ site_tagline: this.data.admin.tagline,
+ site_description: this.data.admin.description,
+ admin_users: admin_users.toString(),
+ default_wallet_name,
+ data_folder,
+ disabled_ext: disabled_ext.toString(),
+ service_fee,
+ funding_source: funding_source_primary}
+ console.log(data)
LNbits.api
.request(
'POST',
'/admin/api/v1/admin/',
self.g.user.wallets[0].adminkey,
- self.data.admin
+ data
)
.then(function (response) {
+ console.log(response.data)
self.$q.notify({
type: 'positive',
message:
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 5e17919c..00a0c99f 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -1,20 +1,33 @@
-from quart import g, render_template, request, jsonify
-import json
+from email.policy import default
+from os import getenv
-from lnbits.decorators import check_user_exists, validate_uuids
+from fastapi import Request
+from fastapi.params import Depends
+from fastapi.templating import Jinja2Templates
+from starlette.responses import HTMLResponse
+
+from lnbits.core.models import User
+from lnbits.decorators import check_user_exists
from lnbits.extensions.admin import admin_ext
-from lnbits.core.crud import get_user, create_account
+from lnbits.requestvars import g
+
+from . import admin_ext, admin_renderer
from .crud import get_admin, get_funding
-from lnbits.settings import WALLET
+templates = Jinja2Templates(directory="templates")
-@admin_ext.route("/")
-@validate_uuids(["usr"], required=True)
-@check_user_exists()
-async def index():
- user_id = g.user
+@admin_ext.get("/", response_class=HTMLResponse)
+async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
+ print(g())
+ funding = [f.dict() for f in await get_funding()]
- funding = [{**funding._asdict()} for funding in await get_funding()]
-
- return await render_template("admin/index.html", user=g.user, admin=admin, funding=funding)
+ print("ADMIN", admin.dict())
+ return admin_renderer().TemplateResponse(
+ "admin/index.html", {
+ "request": request,
+ "user": user.dict(),
+ "admin": admin.dict(),
+ "funding": funding
+ }
+ )
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 2a61b6f5..b2c65be2 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,41 +1,42 @@
-from quart import jsonify, g, request
from http import HTTPStatus
-from .crud import update_wallet_balance
-from lnbits.extensions.admin import admin_ext
-from lnbits.decorators import api_check_wallet_key, api_validate_post_request
-from lnbits.core.crud import get_wallet
-from .crud import get_admin,update_admin
-import json
-@admin_ext.route("/api/v1/admin//", methods=["GET"])
-@api_check_wallet_key("admin")
-async def api_update_balance(wallet_id, topup_amount):
- print(g.data.wallet)
+from fastapi import Body, Depends, Request
+from starlette.exceptions import HTTPException
+
+from lnbits.core.crud import get_wallet
+from lnbits.decorators import WalletTypeInfo, require_admin_key
+from lnbits.extensions.admin import admin_ext
+from lnbits.extensions.admin.models import Admin, UpdateAdminSettings
+
+from .crud import get_admin, update_admin, update_wallet_balance
+
+
+@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
+async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)):
+ print(g.wallet)
try:
wallet = await get_wallet(wallet_id)
except:
- return (
- jsonify({"error": "Not allowed: not an admin"}),
- HTTPStatus.FORBIDDEN,
- )
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
print(wallet)
print(topup_amount)
- return jsonify({"status": "Success"}), HTTPStatus.OK
+ return {"status": "Success"}
-@admin_ext.route("/api/v1/admin/", methods=["POST"])
-@api_check_wallet_key("admin")
-@api_validate_post_request(schema={})
-async def api_update_admin():
- body = await request.get_json()
+@admin_ext.post("/api/v1/admin/", status_code=HTTPStatus.OK)
+async def api_update_admin(
+ request: Request,
+ data: UpdateAdminSettings = Body(...),
+ g: WalletTypeInfo = Depends(require_admin_key)
+ ):
admin = await get_admin()
- print(g.wallet[2])
- print(body["admin_user"])
- if not admin.admin_user == g.wallet[2] and admin.admin_user != None:
- return (
- jsonify({"error": "Not allowed: not an admin"}),
- HTTPStatus.FORBIDDEN,
- )
- updated = await update_admin(body)
+ print(data)
+ if not admin.user == g.wallet.user:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
+ updated = await update_admin(user=g.wallet.user, **data.dict())
print(updated)
- return jsonify({"status": "Success"}), HTTPStatus.OK
\ No newline at end of file
+ return {"status": "Success"}
From b4885de9e2fcf598994dd5ca2360cc29629aaa23 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:18:58 +0000
Subject: [PATCH 122/696] remove core admin html (renamed for now)
---
lnbits/core/templates/core/core_admin.html | 717 +++++++++++++++++++++
1 file changed, 717 insertions(+)
create mode 100644 lnbits/core/templates/core/core_admin.html
diff --git a/lnbits/core/templates/core/core_admin.html b/lnbits/core/templates/core/core_admin.html
new file mode 100644
index 00000000..835fc00a
--- /dev/null
+++ b/lnbits/core/templates/core/core_admin.html
@@ -0,0 +1,717 @@
+{% extends "public.html" %} {% from "macros.jinja" import window_vars with
+context %} {% block page %}
+
+
+
+
+
+ Welcome to LNbits
+
+
+ Fill in the information below to setup your LNbits instance. Details
+ can be changed later.
+
+
+
+
+
+
+ Branding
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Service settings
+
+
+
+ Funding source information (at least one required) *if installed through RaspiBlitz, MyNode, etc, details
+ should be filled in for you
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ View project in GitHub
+ Donate
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(funding) }}
+
+{% endblock %}
From c41b4e714c2e20c36d6a262c77966598b1fd85c4 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:21:38 +0000
Subject: [PATCH 123/696] typo
---
.env.example | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.env.example b/.env.example
index bfaeb515..4192f82e 100644
--- a/.env.example
+++ b/.env.example
@@ -5,7 +5,7 @@ DEBUG=false
LNBITS_ADMIN_USERS="" # User IDs seperated by comma
LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access
-LNBITS_ADMIN_UI=false # Extensions only admin can access
+LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS
LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
From 922206b365cff523a12bf5a8014b544aa88398f3 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:22:23 +0000
Subject: [PATCH 124/696] add admin_ui env
---
lnbits/settings.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 3f4e31cc..43cb87cb 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,5 +1,6 @@
import importlib
import subprocess
+from email.policy import default
from os import path
from typing import List
@@ -27,6 +28,7 @@ LNBITS_DATABASE_URL = env.str("LNBITS_DATABASE_URL", default=None)
LNBITS_ALLOWED_USERS: List[str] = [
x.strip(" ") for x in env.list("LNBITS_ALLOWED_USERS", default=[], subcast=str)
]
+LNBITS_ADMIN_UI = env.bool("LNBITS_ADMIN_UI", default=False)
LNBITS_ADMIN_USERS: List[str] = [
x.strip(" ") for x in env.list("LNBITS_ADMIN_USERS", default=[], subcast=str)
]
From b47ed3068105f2526365f6708efe5134a5fe1c0e Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:23:16 +0000
Subject: [PATCH 125/696] add db config at startup
---
lnbits/commands.py | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 0f7454f2..8c39c338 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -52,6 +52,25 @@ def bundle_vendored():
with open(outputpath, "w") as f:
f.write(output)
+async def get_admin_settings():
+ from lnbits.extensions.admin.models import Admin
+
+ async with core_db.connect() as conn:
+
+ if conn.type == SQLITE:
+ exists = await conn.fetchone(
+ "SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
+ )
+ elif conn.type in {POSTGRES, COCKROACH}:
+ exists = await conn.fetchone(
+ "SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
+ )
+ if not exists:
+ return False
+
+ row = await conn.fetchone("SELECT * from admin")
+
+ return Admin(**row) if row else None
async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them."""
From 4bf17c5df2d07443c972be9356be710e4578c79b Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:23:53 +0000
Subject: [PATCH 126/696] get admin settings at startup
---
lnbits/app.py | 23 ++++++++++++++++++++++-
1 file changed, 22 insertions(+), 1 deletion(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 075828ef..7ff9e4eb 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -18,6 +18,7 @@ from loguru import logger
import lnbits.settings
from lnbits.core.tasks import register_task_listeners
+from .commands import get_admin_settings
from .core import core_app
from .core.views.generic import core_html_routes
from .helpers import (
@@ -42,6 +43,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"""Create application factory.
:param config_object: The configuration object to use.
"""
+<<<<<<< HEAD
configure_logger()
app = FastAPI(
@@ -53,6 +55,14 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
},
)
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
+=======
+ app = FastAPI()
+
+ if lnbits.settings.LNBITS_ADMIN_UI:
+ check_settings(app)
+
+ app.mount("/static", StaticFiles(directory="lnbits/static"), name="static")
+>>>>>>> e3a1b3ae (get admin settings at startup)
app.mount(
"/core/static",
StaticFiles(packages=[("lnbits.core", "static")]),
@@ -64,7 +74,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
app.add_middleware(
CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"]
)
-
g().config = lnbits.settings
g().base_url = f"http://{lnbits.settings.HOST}:{lnbits.settings.PORT}"
@@ -101,6 +110,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app
+def check_settings(app: FastAPI):
+ @app.on_event("startup")
+ async def check_settings_admin():
+ while True:
+ admin_set = await get_admin_settings()
+ if admin_set :
+ break
+ print("ERROR:", admin_set)
+ await asyncio.sleep(5)
+ # admin_set = await get_admin_settings()
+ g().admin_conf = admin_set
+
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
async def check_wallet_status():
From 48d6a89e5ba9e1e082a2ff189b3307d685f521e4 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Sat, 12 Mar 2022 14:24:11 +0000
Subject: [PATCH 127/696] remove core admin.html
---
lnbits/core/templates/core/admin.html | 717 --------------------------
1 file changed, 717 deletions(-)
delete mode 100644 lnbits/core/templates/core/admin.html
diff --git a/lnbits/core/templates/core/admin.html b/lnbits/core/templates/core/admin.html
deleted file mode 100644
index e8176555..00000000
--- a/lnbits/core/templates/core/admin.html
+++ /dev/null
@@ -1,717 +0,0 @@
-{% extends "public.html" %} {% from "macros.jinja" import window_vars with
-context %} {% block page %}
-
-
-
-
-
- Welcome to LNbits
-
-
- Fill in the information below to setup your LNbits instance. Details
- can be changed later.
-
-
-
-
-
-
- Branding
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Service settings
-
-
-
- Funding source information (at least one required) *if installed through RaspiBlitz, MyNode, etc, details
- should be filled in for you
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- View project in GitHub
- Donate
-
-
-
-
-
-{% endblock %} {% block scripts %} {{ window_vars(funding) }}
-
-{% endblock %}
From 87bee88de403723b9d98f64716c573e21289a50a Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 18 Mar 2022 16:55:31 +0000
Subject: [PATCH 128/696] refactor ui
---
.../admin/templates/admin/index.html | 727 +++++++++++++++++-
1 file changed, 712 insertions(+), 15 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index a6b45625..65ac9f33 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1,6 +1,670 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
+
+
+
+
+
+
+ tab = val.name"
+ >
+ tab = val.name"
+ >
+ tab = val.name"
+ >
+ tab = val.name"
+ >
+
+
+
+
+
+
+
+ Wallets Management
+
+
+
+
+
Funding Source Info
+
+ {%raw%}
+ Funding Source: {{data.admin.funding_source}}
+ Balance: {{data.admin.balance / 1000}} sats
+ {%endraw%}
+
+
+
+
+
+
+
+
+ TopUp a wallet
+
+
+
+
+
+
+
+
+
Funding Sources
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+ User Management
+
+
+ Super Admin: {% raw
+ %}{{this.data.admin.user}}{% endraw %}
+
+
+
+
Admin Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
Allowed Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
+
+
Disabled Extensions
+
+
+
+
+
+ Save
+
+
+
+
+
+ Server Management
+
+
+
+
+
Server Info
+
+ {%raw%}
+ SQlite: {{data.admin.data_folder}}
+ Postgres: {{data.admin.database_url}}
+ {%endraw%}
+
+
+
+
+
+
+
+
Miscelaneous
+
+
+ Force HTTPS
+ Prefer secure URLs
+
+
+
+
+
+
+
+ Hide API
+ Hides wallet api, extensions can choose to honor
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+ UI Management
+
+
+
+
+
+
+
+
Default Wallet Name
+
+
+
+
+
+
+
+
+
Advertisement Slots
+
+
+
+
+ {% raw %}
+
+ {{ space.slice(0, 8) + " ... " + space.slice(-8) }}
+
+ {% endraw %}
+
+
+
+
+
+
+
+ Save
+
+
+
+
+
+
+
+
+
Admin
-
+
@@ -426,6 +1090,7 @@
return {
wallet: {data: {}},
cancel: {},
+ tab: 'funding',
data: {
funding_source: [
'CLightningWallet',
@@ -436,24 +1101,14 @@
'LnbitsWallet',
'OpenNodeWallet'
],
-
+
admin: {
- user: '{{ user.id }}',
- site_title: '{{admin.site_title}}',
- tagline: '{{admin.site_tagline}}',
- description: '{{admin.site_description}}',
- admin_users: '{{admin.admin_users}}',
- service_fee: parseFloat('{{admin.service_fee}}'),
- default_wallet_name: '{{admin.default_wallet_name}}',
- data_folder: '{{admin.data_folder}}',
- funding_source_primary: '{{admin.funding_source}}',
- disabled_ext: '{{admin.disabled_ext}}'.split(','),
edited: [],
funding: {},
senddata: {}
}
},
-
+ themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'],
options: [
'bleskomat',
'captcha',
@@ -489,9 +1144,51 @@
for (i = 0; i < funding.length; i++) {
self.data.admin.funding[funding[i].backend_wallet] = funding[i]
}
- console.log(self.data.admin)
+ let settings = JSON.parse('{{ settings | tojson|safe }}')
+ settings.balance = '{{ balance }}'
+ this.data.admin = {...this.data.admin, ...settings}
+ console.log(this.g.user)
},
methods: {
+ addAdminUser(){
+ let addUser = this.data.admin_users_add
+ let admin_users = this.data.admin.admin_users
+ if(addUser.length && !admin_users.includes(addUser)){
+ admin_users.push(addUser)
+ this.data.admin.admin_users = admin_users
+ this.data.admin_users_add = ""
+ }
+ },
+ removeAdminUser(user){
+ let admin_users = this.data.admin.admin_users
+ this.data.admin.admin_users = admin_users.filter(u => u !== user)
+ },
+ addAllowedUser(){
+ let addUser = this.data.allowed_users_add
+ let allowed_users = this.data.admin.allowed_users
+ if(addUser.length && !allowed_users.includes(addUser)){
+ allowed_users.push(addUser)
+ this.data.admin.allowed_users = allowed_users
+ this.data.allowed_users_add = ""
+ }
+ },
+ removeAllowedUser(user){
+ let allowed_users = this.data.admin.allowed_users
+ this.data.admin.allowed_users = allowed_users.filter(u => u !== user)
+ },
+ addAdSpace(){
+ let adSpace = this.data.ad_space_add
+ let spaces = this.data.admin.ad_space
+ if(adSpace.length && !spaces.includes(adSpace)){
+ spaces.push(adSpace)
+ this.data.admin.ad_space = spaces
+ this.data.ad_space_add = ""
+ }
+ },
+ removeAdSpace(ad){
+ let spaces = this.data.admin.ad_space
+ this.data.admin.ad_space = spaces.filter(s => s !== ad)
+ },
topupWallet: function () {
var self = this
LNbits.api
From 8c1c7d13b87ba7b7408dccd719a9a9c66e6e4325 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 18 Mar 2022 16:59:06 +0000
Subject: [PATCH 129/696] make it work from g()
---
lnbits/app.py | 34 +++---
lnbits/config.py | 62 ++++++++++
lnbits/extensions/admin/crud.py | 2 +-
lnbits/extensions/admin/migrations.py | 162 +++++++++++++++++---------
lnbits/extensions/admin/models.py | 27 +++--
lnbits/extensions/admin/views.py | 8 +-
lnbits/settings.py | 2 +-
7 files changed, 218 insertions(+), 79 deletions(-)
create mode 100644 lnbits/config.py
diff --git a/lnbits/app.py b/lnbits/app.py
index 7ff9e4eb..1ffedb54 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -19,6 +19,7 @@ import lnbits.settings
from lnbits.core.tasks import register_task_listeners
from .commands import get_admin_settings
+from .config import WALLET, conf
from .core import core_app
from .core.views.generic import core_html_routes
from .helpers import (
@@ -29,7 +30,8 @@ from .helpers import (
url_for_vendored,
)
from .requestvars import g
-from .settings import WALLET
+
+# from .settings import WALLET
from .tasks import (
catch_everything_and_restart,
check_pending_payments,
@@ -43,7 +45,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"""Create application factory.
:param config_object: The configuration object to use.
"""
-<<<<<<< HEAD
configure_logger()
app = FastAPI(
@@ -55,20 +56,18 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
},
)
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
-=======
- app = FastAPI()
-
- if lnbits.settings.LNBITS_ADMIN_UI:
- check_settings(app)
-
- app.mount("/static", StaticFiles(directory="lnbits/static"), name="static")
->>>>>>> e3a1b3ae (get admin settings at startup)
app.mount(
"/core/static",
StaticFiles(packages=[("lnbits.core", "static")]),
name="core_static",
)
+ if lnbits.settings.LNBITS_ADMIN_UI:
+ g().admin_conf = conf
+ check_settings(app)
+
+ g().WALLET = WALLET
+
origins = ["*"]
app.add_middleware(
@@ -109,18 +108,27 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app
-
def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
+
+ def removeEmptyString(arr):
+ return list(filter(None, arr))
+
while True:
admin_set = await get_admin_settings()
if admin_set :
break
print("ERROR:", admin_set)
await asyncio.sleep(5)
- # admin_set = await get_admin_settings()
- g().admin_conf = admin_set
+
+ admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(','))
+ admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(','))
+ admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(','))
+ admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(','))
+ admin_set.theme = removeEmptyString(admin_set.theme.split(','))
+ admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(','))
+ g().admin_conf = conf.copy(update=admin_set.dict())
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
diff --git a/lnbits/config.py b/lnbits/config.py
new file mode 100644
index 00000000..02e8cf53
--- /dev/null
+++ b/lnbits/config.py
@@ -0,0 +1,62 @@
+import importlib
+import json
+from os import getenv, path
+from typing import List, Optional
+
+from pydantic import BaseSettings, Field, validator
+
+wallets_module = importlib.import_module("lnbits.wallets")
+wallet_class = getattr(
+ wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet")
+)
+
+WALLET = wallet_class()
+
+def list_parse_fallback(v):
+ try:
+ return json.loads(v)
+ except Exception as e:
+ return v.replace(' ','').split(',')
+
+class Settings(BaseSettings):
+ # users
+ admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS")
+ allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS")
+ admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS")
+ disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS")
+ funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS")
+ # ops
+ data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER")
+ database_url: str = Field(default=None, env="LNBITS_DATABASE_URL")
+ force_https: bool = Field(default=True, env="LNBITS_FORCE_HTTPS")
+ service_fee: float = Field(default=0, env="LNBITS_SERVICE_FEE")
+ hide_api: bool = Field(default=False, env="LNBITS_HIDE_API")
+ denomination: str = Field(default="sats", env="LNBITS_DENOMINATION")
+ # Change theme
+ site_title: str = Field(default=None, env="LNBITS_SITE_TITLE")
+ site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE")
+ site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
+ default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME")
+ theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS")
+ ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
+ # .env
+ env: Optional[str]
+ debug: Optional[str]
+ host: Optional[str]
+ port: Optional[str]
+ lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
+
+ # @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True)
+ # def validate(cls, val):
+ # print(val)
+ # return val.split(',')
+
+ class Config:
+ env_file = ".env"
+ env_file_encoding = "utf-8"
+ case_sensitive = False
+ json_loads = list_parse_fallback
+
+
+conf = Settings()
+WALLET = wallet_class()
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 872d6c97..6fccb8ee 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -40,7 +40,7 @@ async def update_admin(user: str, **kwargs) -> Admin:
# new_settings = await get_admin()
# return new_settings
-async def get_admin() -> List[Admin]:
+async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin")
return Admin(**row) if row else None
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 13b76923..574f772d 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -2,93 +2,151 @@ from os import getenv
from sqlalchemy.exc import OperationalError # type: ignore
+from lnbits.config import conf
from lnbits.helpers import urlsafe_short_hash
async def m001_create_admin_table(db):
- user = None
- site_title = None
- site_tagline = None
- site_description = None
- allowed_users = None
- admin_users = None
- default_wallet_name = None
- data_folder = None
- disabled_ext = None
- force_https = True
- service_fee = 0
- funding_source = ""
+ # users/server
+ user = conf.admin_users[0]
+ admin_users = ",".join(conf.admin_users)
+ allowed_users = ",".join(conf.allowed_users)
+ admin_ext = ",".join(conf.admin_ext)
+ disabled_ext = ",".join(conf.disabled_ext)
+ funding_source = conf.funding_source
+ #operational
+ data_folder = conf.data_folder
+ database_url = conf.database_url
+ force_https = conf.force_https
+ service_fee = conf.service_fee
+ hide_api = conf.hide_api
+ denomination = conf.denomination
+ # Theme'ing
+ site_title = conf.site_title
+ site_tagline = conf.site_tagline
+ site_description = conf.site_description
+ default_wallet_name = conf.default_wallet_name
+ theme = ",".join(conf.theme)
+ ad_space = ",".join(conf.ad_space)
- if getenv("LNBITS_SITE_TITLE"):
- site_title = getenv("LNBITS_SITE_TITLE")
+ # if getenv("LNBITS_ADMIN_EXTENSIONS"):
+ # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS")
- if getenv("LNBITS_SITE_TAGLINE"):
- site_tagline = getenv("LNBITS_SITE_TAGLINE")
+ # if getenv("LNBITS_DATABASE_URL"):
+ # database_url = getenv("LNBITS_DATABASE_URL")
- if getenv("LNBITS_SITE_DESCRIPTION"):
- site_description = getenv("LNBITS_SITE_DESCRIPTION")
+ # if getenv("LNBITS_HIDE_API"):
+ # hide_api = getenv("LNBITS_HIDE_API")
- if getenv("LNBITS_ALLOWED_USERS"):
- allowed_users = getenv("LNBITS_ALLOWED_USERS")
+ # if getenv("LNBITS_THEME_OPTIONS"):
+ # theme = getenv("LNBITS_THEME_OPTIONS")
- if getenv("LNBITS_ADMIN_USERS"):
- admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
- user = admin_users.split(',')[0]
+ # if getenv("LNBITS_AD_SPACE"):
+ # ad_space = getenv("LNBITS_AD_SPACE")
- if getenv("LNBITS_DEFAULT_WALLET_NAME"):
- default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
+ # if getenv("LNBITS_SITE_TITLE"):
+ # site_title = getenv("LNBITS_SITE_TITLE")
- if getenv("LNBITS_DATA_FOLDER"):
- data_folder = getenv("LNBITS_DATA_FOLDER")
+ # if getenv("LNBITS_SITE_TAGLINE"):
+ # site_tagline = getenv("LNBITS_SITE_TAGLINE")
- if getenv("LNBITS_DISABLED_EXTENSIONS"):
- disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
+ # if getenv("LNBITS_SITE_DESCRIPTION"):
+ # site_description = getenv("LNBITS_SITE_DESCRIPTION")
- if getenv("LNBITS_FORCE_HTTPS"):
- force_https = getenv("LNBITS_FORCE_HTTPS")
+ # if getenv("LNBITS_ALLOWED_USERS"):
+ # allowed_users = getenv("LNBITS_ALLOWED_USERS")
- if getenv("LNBITS_SERVICE_FEE"):
- service_fee = getenv("LNBITS_SERVICE_FEE")
+ # if getenv("LNBITS_ADMIN_USERS"):
+ # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
+ # user = admin_users.split(',')[0]
+
+ # if getenv("LNBITS_DEFAULT_WALLET_NAME"):
+ # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
- if getenv("LNBITS_BACKEND_WALLET_CLASS"):
- funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
+ # if getenv("LNBITS_DATA_FOLDER"):
+ # data_folder = getenv("LNBITS_DATA_FOLDER")
+
+ # if getenv("LNBITS_DISABLED_EXTENSIONS"):
+ # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
+
+ # if getenv("LNBITS_FORCE_HTTPS"):
+ # force_https = getenv("LNBITS_FORCE_HTTPS")
+
+ # if getenv("LNBITS_SERVICE_FEE"):
+ # service_fee = getenv("LNBITS_SERVICE_FEE")
+
+ # if getenv("LNBITS_DENOMINATION"):
+ # denomination = getenv("LNBITS_DENOMINATION", "sats")
+
+ # if getenv("LNBITS_BACKEND_WALLET_CLASS"):
+ # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
await db.execute(
"""
CREATE TABLE IF NOT EXISTS admin (
- "user" TEXT,
+ "user" TEXT PRIMARY KEY,
+ admin_users TEXT,
+ allowed_users TEXT,
+ admin_ext TEXT,
+ disabled_ext TEXT,
+ funding_source TEXT,
+ data_folder TEXT,
+ database_url TEXT,
+ force_https BOOLEAN,
+ service_fee REAL,
+ hide_api BOOLEAN,
+ denomination TEXT,
site_title TEXT,
site_tagline TEXT,
site_description TEXT,
- admin_users TEXT,
- allowed_users TEXT,
default_wallet_name TEXT,
- data_folder TEXT,
- disabled_ext TEXT,
- force_https BOOLEAN,
- service_fee REAL,
- funding_source TEXT
+ theme TEXT,
+ ad_space TEXT
);
"""
)
await db.execute(
"""
- INSERT INTO admin ("user", site_title, site_tagline, site_description, admin_users, allowed_users, default_wallet_name, data_folder, disabled_ext, force_https, service_fee, funding_source)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """,
- (
- user.strip(),
+ INSERT INTO admin (
+ "user",
+ admin_users,
+ allowed_users,
+ admin_ext,
+ disabled_ext,
+ funding_source,
+ data_folder,
+ database_url,
+ force_https,
+ service_fee,
+ hide_api,
+ denomination,
site_title,
site_tagline,
site_description,
- admin_users[1:],
- allowed_users,
default_wallet_name,
- data_folder,
+ theme,
+ ad_space)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ user,
+ admin_users,
+ allowed_users,
+ admin_ext,
disabled_ext,
+ funding_source,
+ data_folder,
+ database_url,
force_https,
service_fee,
- funding_source,
+ hide_api,
+ denomination,
+ site_title,
+ site_tagline,
+ site_description,
+ default_wallet_name,
+ theme,
+ ad_space,
),
)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 4080ff01..f7c64de5 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -2,7 +2,7 @@ from sqlite3 import Row
from typing import List, Optional
from fastapi import Query
-from pydantic import BaseModel
+from pydantic import BaseModel, Field
class UpdateAdminSettings(BaseModel):
@@ -19,18 +19,27 @@ class UpdateAdminSettings(BaseModel):
funding_source: Optional[str]
class Admin(BaseModel):
+ # users
user: str
+ admin_users: Optional[str]
+ allowed_users: Optional[str]
+ admin_ext: Optional[str]
+ disabled_ext: Optional[str]
+ funding_source: Optional[str]
+ # ops
+ data_folder: Optional[str]
+ database_url: Optional[str]
+ force_https: bool = Field(default=True)
+ service_fee: float = Field(default=0)
+ hide_api: bool = Field(default=False)
+ # Change theme
site_title: Optional[str]
site_tagline: Optional[str]
site_description: Optional[str]
- allowed_users: Optional[str]
- admin_users: str
- default_wallet_name: str
- data_folder: str
- disabled_ext: str
- force_https: Optional[bool] = Query(True)
- service_fee: float
- funding_source: str
+ default_wallet_name: Optional[str]
+ denomination: str = Field(default="sats")
+ theme: Optional[str]
+ ad_space: Optional[str]
@classmethod
def from_row(cls, row: Row) -> "Admin":
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 00a0c99f..105f05a1 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -19,15 +19,17 @@ templates = Jinja2Templates(directory="templates")
@admin_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
- print(g())
funding = [f.dict() for f in await get_funding()]
-
+ error, balance = await g().WALLET.status()
print("ADMIN", admin.dict())
+ print(g().admin_conf)
return admin_renderer().TemplateResponse(
"admin/index.html", {
"request": request,
"user": user.dict(),
"admin": admin.dict(),
- "funding": funding
+ "funding": funding,
+ "settings": g().admin_conf.dict(),
+ "balance": balance
}
)
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 43cb87cb..ed5c77f7 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -4,7 +4,7 @@ from email.policy import default
from os import path
from typing import List
-from environs import Env # type: ignore
+from environs import Env
env = Env()
env.read_env()
From a72ed98997656a709b00d171c27c387c26dfe0bd Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 10:28:07 +0000
Subject: [PATCH 130/696] topup wallet endpoint
---
lnbits/extensions/admin/crud.py | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 6fccb8ee..683558f9 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,26 +1,31 @@
+import json
from typing import List, Optional
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
from lnbits.settings import *
+from lnbits.tasks import internal_invoice_queue
from . import db
from .models import Admin, Funding
-def update_wallet_balance(wallet_id: str, amount: int) -> str:
+async def update_wallet_balance(wallet_id: str, amount: int) -> str:
temp_id = f"temp_{urlsafe_short_hash()}"
internal_id = f"internal_{urlsafe_short_hash()}"
- create_payment(
+
+ payment = await create_payment(
wallet_id=wallet_id,
checking_id=internal_id,
payment_request="admin_internal",
payment_hash="admin_internal",
- amount=amount * 1000,
+ amount=amount*1000,
memo="Admin top up",
pending=False,
)
- return "success"
+ # manually send this for now
+ await internal_invoice_queue.put(internal_id)
+ return payment
async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
From 42a9af986ab3620bdb61270961c6adbda4a66c95 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 10:28:44 +0000
Subject: [PATCH 131/696] update admin settings in db
---
lnbits/extensions/admin/models.py | 29 +++++++++++++++++-----------
lnbits/extensions/admin/views_api.py | 8 ++++----
2 files changed, 22 insertions(+), 15 deletions(-)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index f7c64de5..36d9b815 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -6,17 +6,24 @@ from pydantic import BaseModel, Field
class UpdateAdminSettings(BaseModel):
- site_title: Optional[str]
- site_tagline: Optional[str]
- site_description: Optional[str]
- allowed_users: Optional[str]
- admin_users: Optional[str]
- default_wallet_name: Optional[str]
- data_folder: Optional[str]
- disabled_ext: Optional[str]
- force_https: Optional[bool]
- service_fee: Optional[float]
- funding_source: Optional[str]
+ # users
+ admin_users: str = Query(None)
+ allowed_users: str = Query(None)
+ admin_ext: str = Query(None)
+ disabled_ext: str = Query(None)
+ funding_source: str = Query(None)
+ # ops
+ force_https: bool = Query(None)
+ service_fee: float = Query(None, ge=0)
+ hide_api: bool = Query(None)
+ # Change theme
+ site_title: str = Query(None)
+ site_tagline: str = Query(None)
+ site_description: str = Query(None)
+ default_wallet_name: str = Query(None)
+ denomination: str = Query(None)
+ theme: str = Query(None)
+ ad_space: str = Query(None)
class Admin(BaseModel):
# users
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index b2c65be2..cb526aa5 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -12,16 +12,16 @@ from .crud import get_admin, update_admin, update_wallet_balance
@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
-async def api_update_balance(wallet_id, topup_amount, g: WalletTypeInfo = Depends(require_admin_key)):
- print(g.wallet)
+async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)):
try:
wallet = await get_wallet(wallet_id)
except:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
)
- print(wallet)
- print(topup_amount)
+
+ await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
+
return {"status": "Success"}
From 2ebb4448d5ff8daa3b1c4729ce33dd4c5c744c7b Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 10:29:18 +0000
Subject: [PATCH 132/696] update settings and topup logic
---
.../admin/templates/admin/index.html | 91 ++++++++++++-------
1 file changed, 57 insertions(+), 34 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 65ac9f33..e9ddc7c4 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -30,7 +30,7 @@
-
+
@@ -61,7 +61,7 @@
@@ -577,7 +577,6 @@
Site Description
s !== ad)
},
- topupWallet: function () {
- var self = this
+ topupWallet() {
LNbits.api
.request(
'GET',
'/admin/api/v1/admin/' +
- self.wallet.id +
+ this.wallet.data.id +
'/' +
- self.wallet.data.amount,
- self.g.user.wallets[0].adminkey
+ this.wallet.data.amount,
+ this.g.user.wallets[0].adminkey
)
- .then(function (response) {
- self.$q.notify({
+ .then((response) => {
+ this.$q.notify({
type: 'positive',
message:
- 'Success! Added ' +
- self.wallet.amount +
- ' to ' +
- self.wallet.id,
+ 'Success! Added ' +
+ this.wallet.data.amount +
+ ' to ' +
+ this.wallet.data.id,
icon: null
})
+ this.wallet.data = {}
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -1224,36 +1224,59 @@
self.data.admin.edited.push(source)
console.log(self.data.admin.edited)
},
- UpdateLNbits: function () {
- var self = this
- let {site_title, admin_users, default_wallet_name, data_folder, disabled_ext, service_fee, funding_source_primary} = this.data.admin
+ UpdateLNbits() {
+ let {
+ admin_users,
+ allowed_users,
+ admin_ext,
+ disabled_ext,
+ funding_source,
+ force_https,
+ service_fee,
+ hide_api,
+ site_title,
+ site_tagline,
+ site_description,
+ default_wallet_name,
+ denomination,
+ theme,
+ ad_space
+ } = this.data.admin
+ //console.log("this", this.data.admin)
let data = {
- site_title,
- site_tagline: this.data.admin.tagline,
- site_description: this.data.admin.description,
- admin_users: admin_users.toString(),
- default_wallet_name,
- data_folder,
+ admin_users: admin_users.toString(),
+ allowed_users: allowed_users.toString(),
+ admin_ext: admin_ext.toString(),
disabled_ext: disabled_ext.toString(),
- service_fee,
- funding_source: funding_source_primary}
+ funding_source,
+ force_https,
+ service_fee,
+ hide_api,
+ site_title,
+ site_tagline,
+ site_description,
+ default_wallet_name,
+ denomination,
+ theme: theme.toString(),
+ ad_space: ad_space.toString()
+ }
console.log(data)
LNbits.api
.request(
'POST',
'/admin/api/v1/admin/',
- self.g.user.wallets[0].adminkey,
+ this.g.user.wallets[0].adminkey,
data
)
- .then(function (response) {
+ .then(response => {
console.log(response.data)
- self.$q.notify({
+ this.$q.notify({
type: 'positive',
message:
'Success! Added ' +
- self.wallet.amount +
+ this.wallet.amount +
' to ' +
- self.wallet.id,
+ this.wallet.id,
icon: null
})
})
From 3082a393436b88a24d860f87983560e758a02f66 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:34:47 +0000
Subject: [PATCH 133/696] make removeEmptyString fn as helper fn
---
lnbits/app.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 1ffedb54..c8f5c60a 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -26,6 +26,7 @@ from .helpers import (
get_css_vendored,
get_js_vendored,
get_valid_extensions,
+ removeEmptyString,
template_renderer,
url_for_vendored,
)
@@ -112,9 +113,6 @@ def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
- def removeEmptyString(arr):
- return list(filter(None, arr))
-
while True:
admin_set = await get_admin_settings()
if admin_set :
From c419bd27ebcd212da37f27994e630df08bdba7d4 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:35:15 +0000
Subject: [PATCH 134/696] add some defaults
---
lnbits/config.py | 6 +++---
lnbits/extensions/admin/models.py | 8 ++++----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index 02e8cf53..b2fbfff1 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -33,10 +33,10 @@ class Settings(BaseSettings):
hide_api: bool = Field(default=False, env="LNBITS_HIDE_API")
denomination: str = Field(default="sats", env="LNBITS_DENOMINATION")
# Change theme
- site_title: str = Field(default=None, env="LNBITS_SITE_TITLE")
- site_tagline: str = Field(default=None, env="LNBITS_SITE_TAGLINE")
+ site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE")
+ site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE")
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
- default_wallet_name: str = Field(default=None, env="LNBITS_DEFAULT_WALLET_NAME")
+ default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 36d9b815..0f25679d 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -17,11 +17,11 @@ class UpdateAdminSettings(BaseModel):
service_fee: float = Query(None, ge=0)
hide_api: bool = Query(None)
# Change theme
- site_title: str = Query(None)
- site_tagline: str = Query(None)
+ site_title: str = Query("LNbits")
+ site_tagline: str = Query("free and open-source lightning wallet")
site_description: str = Query(None)
- default_wallet_name: str = Query(None)
- denomination: str = Query(None)
+ default_wallet_name: str = Query("LNbits wallet")
+ denomination: str = Query("sats")
theme: str = Query(None)
ad_space: str = Query(None)
From 0897d0476356d1956711fc0f1c6448fbb88275fb Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:36:04 +0000
Subject: [PATCH 135/696] removeEmtpy sting as helper fn
---
lnbits/helpers.py | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index e213240c..e456f715 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -154,8 +154,20 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s
url = f"{base}{endpoint}{url_params}"
return url
+def removeEmptyString(arr):
+ return list(filter(None, arr))
def template_renderer(additional_folders: List = []) -> Jinja2Templates:
+ if(settings.LNBITS_ADMIN_UI):
+ _ = g().admin_conf
+ settings.LNBITS_AD_SPACE = _.ad_space
+ settings.LNBITS_HIDE_API = _.hide_api
+ settings.LNBITS_SITE_TITLE = _.site_title
+ settings.LNBITS_DENOMINATION = _.denomination
+ settings.LNBITS_SITE_TAGLINE = _.site_tagline
+ settings.LNBITS_SITE_DESCRIPTION = _.site_description
+ settings.LNBITS_THEME_OPTIONS = _.theme
+
t = Jinja2Templates(
loader=jinja2.FileSystemLoader(
["lnbits/templates", "lnbits/core/templates", *additional_folders]
From 8c93aa304f0d484e908cd40c0a851b95fbbfd992 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:41:11 +0000
Subject: [PATCH 136/696] cleanup
---
lnbits/extensions/admin/crud.py | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 683558f9..e14ad194 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,9 +1,7 @@
-import json
-from typing import List, Optional
+from typing import List
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
-from lnbits.settings import *
from lnbits.tasks import internal_invoice_queue
from . import db
@@ -37,14 +35,6 @@ async def update_admin(user: str, **kwargs) -> Admin:
assert row, "Newly updated settings couldn't be retrieved"
return Admin(**row) if row else None
-# async def update_admin(user: str, **kwargs) -> Optional[Admin]:
-# q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
-# await db.execute(
-# f"UPDATE admin SET {q} WHERE user = ?", (*kwargs.values(), user)
-# )
-# new_settings = await get_admin()
-# return new_settings
-
async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin")
return Admin(**row) if row else None
From 5ce73697d47aa6eb96bb6d69c7da1e4521bc4943 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:42:28 +0000
Subject: [PATCH 137/696] make string to list
---
lnbits/extensions/admin/views_api.py | 23 ++++++++++++++++++-----
1 file changed, 18 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index cb526aa5..1d4e6a9c 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,5 +1,6 @@
from http import HTTPStatus
+# from config import conf
from fastapi import Body, Depends, Request
from starlette.exceptions import HTTPException
@@ -7,6 +8,8 @@ from lnbits.core.crud import get_wallet
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import Admin, UpdateAdminSettings
+from lnbits.helpers import removeEmptyString
+from lnbits.requestvars import g
from .crud import get_admin, update_admin, update_wallet_balance
@@ -19,7 +22,7 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
)
-
+
await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
return {"status": "Success"}
@@ -29,14 +32,24 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D
async def api_update_admin(
request: Request,
data: UpdateAdminSettings = Body(...),
- g: WalletTypeInfo = Depends(require_admin_key)
+ w: WalletTypeInfo = Depends(require_admin_key)
):
admin = await get_admin()
print(data)
- if not admin.user == g.wallet.user:
+ if not admin.user == w.wallet.user:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
)
- updated = await update_admin(user=g.wallet.user, **data.dict())
- print(updated)
+ updated = await update_admin(user=w.wallet.user, **data.dict())
+
+ updated.admin_users = removeEmptyString(updated.admin_users.split(','))
+ updated.allowed_users = removeEmptyString(updated.allowed_users.split(','))
+ updated.admin_ext = removeEmptyString(updated.admin_ext.split(','))
+ updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(','))
+ updated.theme = removeEmptyString(updated.theme.split(','))
+ updated.ad_space = removeEmptyString(updated.ad_space.split(','))
+
+ g().admin_conf = g().admin_conf.copy(update=updated.dict())
+
+ print(g().admin_conf)
return {"status": "Success"}
From c299927e7ca1e16c8bc29a98b5af38ed402496de Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 22 Mar 2022 11:42:47 +0000
Subject: [PATCH 138/696] success message
---
lnbits/extensions/admin/templates/admin/index.html | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index e9ddc7c4..9aa4f12a 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1273,10 +1273,7 @@
this.$q.notify({
type: 'positive',
message:
- 'Success! Added ' +
- this.wallet.amount +
- ' to ' +
- this.wallet.id,
+ 'Success! Settings changed!',
icon: null
})
})
From 2a63fb191423b2f23de7f70e8197109a290f3689 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 14 Apr 2022 10:42:26 +0100
Subject: [PATCH 139/696] allow html to be passed to description
---
lnbits/core/templates/core/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/core/templates/core/index.html b/lnbits/core/templates/core/index.html
index 68a7b7ed..146fc6ad 100644
--- a/lnbits/core/templates/core/index.html
+++ b/lnbits/core/templates/core/index.html
@@ -82,7 +82,7 @@
>
-
{{SITE_DESCRIPTION}}
+
{{SITE_DESCRIPTION | safe}}
From 3fbdac127adf9459d8af4453a802248f7c3d94fc Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 14 Apr 2022 16:37:13 +0100
Subject: [PATCH 140/696] update funding wallets
---
lnbits/extensions/admin/crud.py | 12 +
.../admin/templates/admin/index.html | 523 ++++++++++--------
lnbits/extensions/admin/views.py | 3 +-
lnbits/extensions/admin/views_api.py | 19 +-
4 files changed, 315 insertions(+), 242 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index e14ad194..dd39e8e4 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -39,6 +39,18 @@ async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin")
return Admin(**row) if row else None
+async def update_funding(data: Funding) -> Funding:
+ await db.execute(
+ """
+ UPDATE funding
+ SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ?
+ WHERE id = ?
+ """,
+ (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,),
+ )
+ row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,))
+ assert row, "Newly updated settings couldn't be retrieved"
+ return Funding(**row) if row else None
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM funding")
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 9aa4f12a..d56b3d79 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -31,11 +31,11 @@
-
-
-
- Wallets Management
-
+
+
+
+ Wallets Management
+
@@ -62,43 +62,96 @@
-
TopUp a wallet
-
-
-
-
-
-
-
-
+
TopUp a wallet
+
Funding Sources
-
+ {% raw %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+ User Management
+
+
+ Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %}
+
+
Admin Users
+ hint="Users with admin privileges"
+ >
{% raw %}
-
-
-
- {% raw %}
-
Allowed Users
+
- {{ user }}
-
- {% endraw %}
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
+
Disabled Extensions
+
+
+
+
+
+ Save
+
+
+
+
+
+ Server Management
-
-
-
-
-
Disabled Extensions
-
-
-
-
-
- Save
-
-
-
-
-
- Server Management
-
Server Info
{%raw%}
- SQlite: {{data.admin.data_folder}}
- Postgres: {{data.admin.database_url}}
+
+ SQlite: {{data.admin.data_folder}}
+
+
+ Postgres: {{data.admin.database_url}}
+
{%endraw%}
@@ -520,7 +576,10 @@
Hide API
- Hides wallet api, extensions can choose to honor
+ Hides wallet api, extensions can choose to
+ honor
-
-
-
- Save
-
-
-
-
-
- UI Management
-
+
+
+
+ Save
+
+
+
+
+
+ UI Management
+
+
Default Wallet Name
@@ -628,12 +682,15 @@
@keydown.enter="addAdSpace"
type="text"
label="Ad image URL"
- hint="Ad image filepaths or urls, extensions can choose to honor">
+ hint="Ad image filepaths or urls, extensions can choose to honor"
+ >
{% raw %}
-
-
-
-
- Save
-
-
-
-
-
+
+
+
+ Save
+
+
+
+
+
-
-
-Admin
-
+
+
-
-
-
-
- Wallet topup
-
-
-
-
-
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }}
@@ -1100,14 +1114,22 @@
'LnbitsWallet',
'OpenNodeWallet'
],
-
+
admin: {
edited: [],
- funding: {},
+ funding: [],
senddata: {}
}
},
- themes: ['classic', 'bitcoin', 'flamingo', 'mint', 'autumn', 'monochrome', 'salvador'],
+ themes: [
+ 'classic',
+ 'bitcoin',
+ 'flamingo',
+ 'mint',
+ 'autumn',
+ 'monochrome',
+ 'salvador'
+ ],
options: [
'bleskomat',
'captcha',
@@ -1139,10 +1161,13 @@
self.cancel.on = true
}
funding = JSON.parse(String('{{ funding | tojson|safe }}'))
- var i
+ funding.map(f => {
+ this.data.admin.funding.push(f)
+ })
+ /*var i
for (i = 0; i < funding.length; i++) {
self.data.admin.funding[funding[i].backend_wallet] = funding[i]
- }
+ }*/
let settings = JSON.parse('{{ settings | tojson|safe }}')
settings.balance = '{{ balance }}'
this.data.admin = {...this.data.admin, ...settings}
@@ -1150,42 +1175,42 @@
console.log(settings)
},
methods: {
- addAdminUser(){
+ addAdminUser() {
let addUser = this.data.admin_users_add
let admin_users = this.data.admin.admin_users
- if(addUser.length && !admin_users.includes(addUser)){
+ if (addUser.length && !admin_users.includes(addUser)) {
admin_users.push(addUser)
this.data.admin.admin_users = admin_users
- this.data.admin_users_add = ""
+ this.data.admin_users_add = ''
}
},
- removeAdminUser(user){
+ removeAdminUser(user) {
let admin_users = this.data.admin.admin_users
this.data.admin.admin_users = admin_users.filter(u => u !== user)
},
- addAllowedUser(){
+ addAllowedUser() {
let addUser = this.data.allowed_users_add
let allowed_users = this.data.admin.allowed_users
- if(addUser.length && !allowed_users.includes(addUser)){
+ if (addUser.length && !allowed_users.includes(addUser)) {
allowed_users.push(addUser)
this.data.admin.allowed_users = allowed_users
- this.data.allowed_users_add = ""
+ this.data.allowed_users_add = ''
}
},
- removeAllowedUser(user){
+ removeAllowedUser(user) {
let allowed_users = this.data.admin.allowed_users
this.data.admin.allowed_users = allowed_users.filter(u => u !== user)
},
- addAdSpace(){
+ addAdSpace() {
let adSpace = this.data.ad_space_add
let spaces = this.data.admin.ad_space
- if(adSpace.length && !spaces.includes(adSpace)){
+ if (adSpace.length && !spaces.includes(adSpace)) {
spaces.push(adSpace)
this.data.admin.ad_space = spaces
- this.data.ad_space_add = ""
+ this.data.ad_space_add = ''
}
},
- removeAdSpace(ad){
+ removeAdSpace(ad) {
let spaces = this.data.admin.ad_space
this.data.admin.ad_space = spaces.filter(s => s !== ad)
},
@@ -1199,14 +1224,14 @@
this.wallet.data.amount,
this.g.user.wallets[0].adminkey
)
- .then((response) => {
+ .then(response => {
this.$q.notify({
type: 'positive',
message:
- 'Success! Added ' +
- this.wallet.data.amount +
- ' to ' +
- this.wallet.data.id,
+ 'Success! Added ' +
+ this.wallet.data.amount +
+ ' to ' +
+ this.wallet.data.id,
icon: null
})
this.wallet.data = {}
@@ -1224,6 +1249,29 @@
self.data.admin.edited.push(source)
console.log(self.data.admin.edited)
},
+ updateFunding(fund) {
+ let data = this.data.admin.funding.find(v => v.backend_wallet == fund)
+
+ LNbits.api
+ .request(
+ 'POST',
+ '/admin/api/v1/admin/funding',
+ this.g.user.wallets[0].adminkey,
+ data
+ )
+ .then(response => {
+ //let wallet = response.data.backend_wallet
+ //this.data.admin.funding[wallet] = response.data
+ //this.data.admin.funding[wallet].endpoint = response.data.endpoint
+ //console.log(this.data.admin.funding)
+ //console.log(this.data.admin)
+ this.$q.notify({
+ type: 'positive',
+ message: `Success! ${response.data.backend_wallet} changed!`,
+ icon: null
+ })
+ })
+ },
UpdateLNbits() {
let {
admin_users,
@@ -1272,8 +1320,7 @@
console.log(response.data)
this.$q.notify({
type: 'positive',
- message:
- 'Success! Settings changed!',
+ message: 'Success! Settings changed!',
icon: null
})
})
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 105f05a1..24b8ca85 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -21,8 +21,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
funding = [f.dict() for f in await get_funding()]
error, balance = await g().WALLET.status()
- print("ADMIN", admin.dict())
- print(g().admin_conf)
+
return admin_renderer().TemplateResponse(
"admin/index.html", {
"request": request,
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 1d4e6a9c..b797dc2d 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -7,11 +7,11 @@ from starlette.exceptions import HTTPException
from lnbits.core.crud import get_wallet
from lnbits.decorators import WalletTypeInfo, require_admin_key
from lnbits.extensions.admin import admin_ext
-from lnbits.extensions.admin.models import Admin, UpdateAdminSettings
+from lnbits.extensions.admin.models import Admin, Funding, UpdateAdminSettings
from lnbits.helpers import removeEmptyString
from lnbits.requestvars import g
-from .crud import get_admin, update_admin, update_wallet_balance
+from .crud import get_admin, update_admin, update_funding, update_wallet_balance
@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
@@ -53,3 +53,18 @@ async def api_update_admin(
print(g().admin_conf)
return {"status": "Success"}
+
+@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK)
+async def api_update_funding(
+ request: Request,
+ data: Funding = Body(...),
+ w: WalletTypeInfo = Depends(require_admin_key)
+ ):
+ admin = await get_admin()
+
+ if not admin.user == w.wallet.user:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
+ funding = await update_funding(data=data)
+ return funding
From a07fbf0187c72b45e0e102951faf3ed6cbfb75fc Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 18 Apr 2022 14:25:06 +0100
Subject: [PATCH 141/696] allow user settings without restart
---
lnbits/core/views/generic.py | 7 ++++++-
lnbits/decorators.py | 8 ++++++++
lnbits/helpers.py | 3 +++
3 files changed, 17 insertions(+), 1 deletion(-)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 31a7b030..83648c44 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -15,7 +15,9 @@ from lnbits.core import db
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from lnbits.helpers import template_renderer, url_for
+from lnbits.requestvars import g
from lnbits.settings import (
+ LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
LNBITS_CUSTOM_LOGO,
@@ -37,7 +39,6 @@ from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
-
@core_html_routes.get("/favicon.ico", response_class=FileResponse)
async def favicon():
return FileResponse("lnbits/core/static/favicon.ico")
@@ -119,6 +120,10 @@ async def wallet(
wallet_name = nme
service_fee = int(SERVICE_FEE) if int(SERVICE_FEE) == SERVICE_FEE else SERVICE_FEE
+ if LNBITS_ADMIN_UI:
+ LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+ LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
+
if not user_id:
user = await get_user((await create_account()).id)
logger.info(f"Create user {user.id}") # type: ignore
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index d4aa63ae..f951163f 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -16,6 +16,7 @@ from lnbits.core.models import User, Wallet
from lnbits.requestvars import g
from lnbits.settings import (
LNBITS_ADMIN_EXTENSIONS,
+ LNBITS_ADMIN_UI,
LNBITS_ADMIN_USERS,
LNBITS_ALLOWED_USERS,
)
@@ -138,6 +139,9 @@ async def get_key_type(
detail="Invoice (or Admin) key required.",
)
+ if LNBITS_ADMIN_UI:
+ LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+
for typenr, WalletChecker in zip(
[0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker]
):
@@ -231,6 +235,10 @@ async def check_user_exists(usr: UUID4) -> User:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
)
+
+ if LNBITS_ADMIN_UI:
+ LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+ LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
raise HTTPException(
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index e456f715..1167143f 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -24,6 +24,9 @@ class Extension(NamedTuple):
class ExtensionManager:
def __init__(self):
+ if settings.LNBITS_ADMIN_UI:
+ settings.LNBITS_DISABLED_EXTENSIONS = g().admin_conf.disabled_ext
+ settings.LNBITS_ADMIN_EXTENSIONS = g().admin_conf.admin_ext
self._disabled: List[str] = settings.LNBITS_DISABLED_EXTENSIONS
self._admin_only: List[str] = [
x.strip(" ") for x in settings.LNBITS_ADMIN_EXTENSIONS
From 931286b476bed767e3900a3b60cf10ab962c29e1 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 18 Apr 2022 14:25:26 +0100
Subject: [PATCH 142/696] advert for server restart option
---
lnbits/extensions/admin/templates/admin/index.html | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d56b3d79..089c5f1c 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -51,7 +51,9 @@
-
Active Funding
+
+ Active Funding (Requires server restart)
+
Date: Thu, 21 Apr 2022 11:08:26 +0100
Subject: [PATCH 143/696] cleanup prints and console logs
---
lnbits/extensions/admin/crud.py | 2 +-
lnbits/extensions/admin/templates/admin/index.html | 4 ++--
lnbits/extensions/admin/views_api.py | 4 ++--
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index dd39e8e4..f866bc1a 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -27,7 +27,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
- print("UPDATE", q)
+ # print("UPDATE", q)
await db.execute(
f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 089c5f1c..584d3a33 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1310,7 +1310,7 @@
theme: theme.toString(),
ad_space: ad_space.toString()
}
- console.log(data)
+ //console.log(data)
LNbits.api
.request(
'POST',
@@ -1319,7 +1319,7 @@
data
)
.then(response => {
- console.log(response.data)
+ //console.log(response.data)
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index b797dc2d..c0650c8a 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -35,7 +35,7 @@ async def api_update_admin(
w: WalletTypeInfo = Depends(require_admin_key)
):
admin = await get_admin()
- print(data)
+ # print(data)
if not admin.user == w.wallet.user:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
@@ -51,7 +51,7 @@ async def api_update_admin(
g().admin_conf = g().admin_conf.copy(update=updated.dict())
- print(g().admin_conf)
+ # print(g().admin_conf)
return {"status": "Success"}
@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK)
From 3f38a9094b9c9e5e3c6b29df67b3508efbc41be6 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 10:49:21 +0100
Subject: [PATCH 144/696] create first user on fresh install
---
.env.example | 4 ++--
lnbits/config.py | 3 ++-
lnbits/extensions/admin/README.md | 15 ++++++++-------
lnbits/extensions/admin/migrations.py | 15 ++++++++++++++-
4 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/.env.example b/.env.example
index 4192f82e..f0e21aa8 100644
--- a/.env.example
+++ b/.env.example
@@ -4,8 +4,8 @@ PORT=5000
DEBUG=false
LNBITS_ADMIN_USERS="" # User IDs seperated by comma
-LNBITS_ADMIN_EXTENSIONS="ngrok" # Extensions only admin can access
-LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS
+LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access
+LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
diff --git a/lnbits/config.py b/lnbits/config.py
index b2fbfff1..3ce51c3c 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -19,6 +19,7 @@ def list_parse_fallback(v):
return v.replace(' ','').split(',')
class Settings(BaseSettings):
+ admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI")
# users
admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS")
allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS")
@@ -37,7 +38,7 @@ class Settings(BaseSettings):
site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE")
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
- theme: List[str] = Field(default="classic, flamingo, mint, salvador, monochrome, autumn", env="LNBITS_THEME_OPTIONS")
+ theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
env: Optional[str]
diff --git a/lnbits/extensions/admin/README.md b/lnbits/extensions/admin/README.md
index 27729459..6cf073a1 100644
--- a/lnbits/extensions/admin/README.md
+++ b/lnbits/extensions/admin/README.md
@@ -1,11 +1,12 @@
-Example Extension
-*tagline*
-This is an example extension to help you organise and build you own.
+# Admin Extension
-Try to include an image
-
+## Dashboard to manage LNbits from the UI
+With AdminUI you can manage your LNbits from the UI
-If your extension has API endpoints, include useful ones here
+
-curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"
+## Before you start
+
+**This extension doesn't discard the need for the `.env` file!**
+In the .env file, set the `LNBITS_ADMIN_USERS` variable to include at least your user id.
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 574f772d..0e22e667 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -6,9 +6,22 @@ from lnbits.config import conf
from lnbits.helpers import urlsafe_short_hash
+async def get_admin_user():
+ if(conf.admin_users[0]):
+ return conf.admin_users[0]
+ from lnbits.core.crud import create_account, get_user
+ print("Seems like there's no admin users yet. Let's create an account for you!")
+ account = await create_account()
+ user = account.id
+ assert user, "Newly created user couldn't be retrieved"
+ print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.")
+ return user
+
+
+
async def m001_create_admin_table(db):
# users/server
- user = conf.admin_users[0]
+ user = await get_admin_user()
admin_users = ",".join(conf.admin_users)
allowed_users = ",".join(conf.allowed_users)
admin_ext = ",".join(conf.admin_ext)
From 0a29fb736093aeca3987122a7a78381b6f760499 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 12:29:58 +0100
Subject: [PATCH 145/696] fix schemas for admin
---
lnbits/commands.py | 11 ++++++++---
lnbits/extensions/admin/crud.py | 12 ++++++------
lnbits/extensions/admin/migrations.py | 26 +++++++++++++-------------
3 files changed, 27 insertions(+), 22 deletions(-)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 8c39c338..7d9b49e2 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -55,8 +55,12 @@ def bundle_vendored():
async def get_admin_settings():
from lnbits.extensions.admin.models import Admin
- async with core_db.connect() as conn:
+ try:
+ ext_db = importlib.import_module(f"lnbits.extensions.admin").db
+ except:
+ return False
+ async with ext_db.connect() as conn:
if conn.type == SQLITE:
exists = await conn.fetchone(
"SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
@@ -65,11 +69,12 @@ async def get_admin_settings():
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
+ print("EXISTS", exists)
if not exists:
return False
- row = await conn.fetchone("SELECT * from admin")
-
+ row = await conn.fetchone("SELECT * from admin.admin")
+
return Admin(**row) if row else None
async def migrate_databases():
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index f866bc1a..67fbc614 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -29,30 +29,30 @@ async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
# print("UPDATE", q)
await db.execute(
- f'UPDATE admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
+ f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
- row = await db.fetchone('SELECT * FROM admin WHERE "user" = ?', (user,))
+ row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,))
assert row, "Newly updated settings couldn't be retrieved"
return Admin(**row) if row else None
async def get_admin() -> Admin:
- row = await db.fetchone("SELECT * FROM admin")
+ row = await db.fetchone("SELECT * FROM admin.admin")
return Admin(**row) if row else None
async def update_funding(data: Funding) -> Funding:
await db.execute(
"""
- UPDATE funding
+ UPDATE admin.funding
SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ?
WHERE id = ?
""",
(data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,),
)
- row = await db.fetchone('SELECT * FROM funding WHERE "id" = ?', (data.id,))
+ row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,))
assert row, "Newly updated settings couldn't be retrieved"
return Funding(**row) if row else None
async def get_funding() -> List[Funding]:
- rows = await db.fetchall("SELECT * FROM funding")
+ rows = await db.fetchall("SELECT * FROM admin.funding")
return [Funding(**row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 0e22e667..c94d140b 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -96,7 +96,7 @@ async def m001_create_admin_table(db):
await db.execute(
"""
- CREATE TABLE IF NOT EXISTS admin (
+ CREATE TABLE IF NOT EXISTS admin.admin (
"user" TEXT PRIMARY KEY,
admin_users TEXT,
allowed_users TEXT,
@@ -120,7 +120,7 @@ async def m001_create_admin_table(db):
)
await db.execute(
"""
- INSERT INTO admin (
+ INSERT INTO admin.admin (
"user",
admin_users,
allowed_users,
@@ -171,7 +171,7 @@ async def m001_create_funding_table(db):
# Make the funding table, if it does not already exist
await db.execute(
"""
- CREATE TABLE IF NOT EXISTS funding (
+ CREATE TABLE IF NOT EXISTS admin.funding (
id TEXT PRIMARY KEY,
backend_wallet TEXT,
endpoint TEXT,
@@ -188,7 +188,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, selected)
VALUES (?, ?, ?, ?)
""",
(
@@ -200,7 +200,7 @@ async def m001_create_funding_table(db):
)
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -214,7 +214,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -228,7 +228,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
@@ -244,7 +244,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
@@ -259,7 +259,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, cert, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
@@ -274,7 +274,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -288,7 +288,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -302,7 +302,7 @@ async def m001_create_funding_table(db):
await db.execute(
"""
- INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
VALUES (?, ?, ?, ?, ?)
""",
(
@@ -317,7 +317,7 @@ async def m001_create_funding_table(db):
## PLACEHOLDER FOR ECLAIR WALLET
# await db.execute(
# """
- # INSERT INTO funding (id, backend_wallet, endpoint, admin_key, selected)
+ # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
# VALUES (?, ?, ?, ?, ?)
# """,
# (
From ac74cfaab7e70d8df8229241e040403d473f04e1 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 12:53:47 +0100
Subject: [PATCH 146/696] fix sqlite and show user account
---
lnbits/app.py | 1 +
lnbits/commands.py | 2 +-
lnbits/extensions/admin/migrations.py | 1 +
3 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index c8f5c60a..2b483758 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -116,6 +116,7 @@ def check_settings(app: FastAPI):
while True:
admin_set = await get_admin_settings()
if admin_set :
+ print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
break
print("ERROR:", admin_set)
await asyncio.sleep(5)
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 7d9b49e2..763a5b90 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -69,7 +69,7 @@ async def get_admin_settings():
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
- print("EXISTS", exists)
+
if not exists:
return False
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index c94d140b..6c5b507d 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -15,6 +15,7 @@ async def get_admin_user():
user = account.id
assert user, "Newly created user couldn't be retrieved"
print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.")
+ conf.admin_users.insert(0, user)
return user
From 1ebd557b1d544f6baab770111a4bf89f31abea66 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 16 May 2022 15:35:04 +0100
Subject: [PATCH 147/696] cleanup and info to user on startup
---
lnbits/app.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 2b483758..eaa33136 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -112,13 +112,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
-
while True:
admin_set = await get_admin_settings()
if admin_set :
- print(f"Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
break
- print("ERROR:", admin_set)
+ print("Waiting for admin settings... retrying in 5 seconds!")
await asyncio.sleep(5)
admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(','))
@@ -128,6 +126,7 @@ def check_settings(app: FastAPI):
admin_set.theme = removeEmptyString(admin_set.theme.split(','))
admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(','))
g().admin_conf = conf.copy(update=admin_set.dict())
+ print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
From e04e24faec18e01c306d86a6bd45f238a74e1d38 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 8 Jun 2022 11:00:43 +0100
Subject: [PATCH 148/696] add custom logo
---
lnbits/config.py | 1 +
lnbits/extensions/admin/migrations.py | 58 ++-----------------
lnbits/extensions/admin/models.py | 2 +
.../admin/templates/admin/index.html | 20 +++++--
lnbits/helpers.py | 3 +-
5 files changed, 26 insertions(+), 58 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index 3ce51c3c..d07ca044 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -39,6 +39,7 @@ class Settings(BaseSettings):
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS")
+ custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
env: Optional[str]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 6c5b507d..aad66f02 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -41,60 +41,9 @@ async def m001_create_admin_table(db):
site_description = conf.site_description
default_wallet_name = conf.default_wallet_name
theme = ",".join(conf.theme)
+ custom_logo = conf.custom_logo
ad_space = ",".join(conf.ad_space)
- # if getenv("LNBITS_ADMIN_EXTENSIONS"):
- # admin_ext = getenv("LNBITS_ADMIN_EXTENSIONS")
-
- # if getenv("LNBITS_DATABASE_URL"):
- # database_url = getenv("LNBITS_DATABASE_URL")
-
- # if getenv("LNBITS_HIDE_API"):
- # hide_api = getenv("LNBITS_HIDE_API")
-
- # if getenv("LNBITS_THEME_OPTIONS"):
- # theme = getenv("LNBITS_THEME_OPTIONS")
-
- # if getenv("LNBITS_AD_SPACE"):
- # ad_space = getenv("LNBITS_AD_SPACE")
-
- # if getenv("LNBITS_SITE_TITLE"):
- # site_title = getenv("LNBITS_SITE_TITLE")
-
- # if getenv("LNBITS_SITE_TAGLINE"):
- # site_tagline = getenv("LNBITS_SITE_TAGLINE")
-
- # if getenv("LNBITS_SITE_DESCRIPTION"):
- # site_description = getenv("LNBITS_SITE_DESCRIPTION")
-
- # if getenv("LNBITS_ALLOWED_USERS"):
- # allowed_users = getenv("LNBITS_ALLOWED_USERS")
-
- # if getenv("LNBITS_ADMIN_USERS"):
- # admin_users = "".join(getenv("LNBITS_ADMIN_USERS").split())
- # user = admin_users.split(',')[0]
-
- # if getenv("LNBITS_DEFAULT_WALLET_NAME"):
- # default_wallet_name = getenv("LNBITS_DEFAULT_WALLET_NAME")
-
- # if getenv("LNBITS_DATA_FOLDER"):
- # data_folder = getenv("LNBITS_DATA_FOLDER")
-
- # if getenv("LNBITS_DISABLED_EXTENSIONS"):
- # disabled_ext = getenv("LNBITS_DISABLED_EXTENSIONS")
-
- # if getenv("LNBITS_FORCE_HTTPS"):
- # force_https = getenv("LNBITS_FORCE_HTTPS")
-
- # if getenv("LNBITS_SERVICE_FEE"):
- # service_fee = getenv("LNBITS_SERVICE_FEE")
-
- # if getenv("LNBITS_DENOMINATION"):
- # denomination = getenv("LNBITS_DENOMINATION", "sats")
-
- # if getenv("LNBITS_BACKEND_WALLET_CLASS"):
- # funding_source = getenv("LNBITS_BACKEND_WALLET_CLASS")
-
await db.execute(
"""
CREATE TABLE IF NOT EXISTS admin.admin (
@@ -115,6 +64,7 @@ async def m001_create_admin_table(db):
site_description TEXT,
default_wallet_name TEXT,
theme TEXT,
+ custom_logo TEXT,
ad_space TEXT
);
"""
@@ -139,8 +89,9 @@ async def m001_create_admin_table(db):
site_description,
default_wallet_name,
theme,
+ custom_logo,
ad_space)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
user,
@@ -160,6 +111,7 @@ async def m001_create_admin_table(db):
site_description,
default_wallet_name,
theme,
+ custom_logo,
ad_space,
),
)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 0f25679d..3b17e720 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -23,6 +23,7 @@ class UpdateAdminSettings(BaseModel):
default_wallet_name: str = Query("LNbits wallet")
denomination: str = Query("sats")
theme: str = Query(None)
+ custom_logo: str = Query(None)
ad_space: str = Query(None)
class Admin(BaseModel):
@@ -46,6 +47,7 @@ class Admin(BaseModel):
default_wallet_name: Optional[str]
denomination: str = Field(default="sats")
theme: Optional[str]
+ custom_logo: Optional[str]
ad_space: Optional[str]
@classmethod
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 584d3a33..d9790051 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -705,6 +705,19 @@
+
@@ -718,10 +731,7 @@
-
+
@@ -1292,6 +1323,8 @@
disabled_ext,
funding_source,
force_https,
+ reserve_fee_min,
+ reserve_fee_pct,
service_fee,
hide_api,
site_title,
@@ -1311,6 +1344,8 @@
disabled_ext: disabled_ext.toString(),
funding_source,
force_https,
+ reserve_fee_min,
+ reserve_fee_pct,
service_fee,
hide_api,
site_title,
diff --git a/lnbits/settings.py b/lnbits/settings.py
index ed5c77f7..8e5c321a 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,6 +1,5 @@
import importlib
import subprocess
-from email.policy import default
from os import path
from typing import List
From 929d174ba4bc20c074ff7bfd2741521c846d005b Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 8 Jun 2022 15:38:28 +0100
Subject: [PATCH 150/696] calle's semantics
---
lnbits/extensions/admin/templates/admin/index.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 832629bc..d34b9068 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -67,7 +67,7 @@
-
Minimum wallet reserve
+
Fee reserve
From 8e0baf7b2dcc2eb018ed972aba856ff3aca41b18 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Tue, 5 Jul 2022 16:25:02 +0100
Subject: [PATCH 151/696] blacked
---
lnbits/extensions/admin/__init__.py | 1 +
lnbits/extensions/admin/crud.py | 23 ++++++++++++---
lnbits/extensions/admin/migrations.py | 10 ++++---
lnbits/extensions/admin/models.py | 2 ++
lnbits/extensions/admin/views.py | 10 ++++---
lnbits/extensions/admin/views_api.py | 41 ++++++++++++++-------------
6 files changed, 56 insertions(+), 31 deletions(-)
diff --git a/lnbits/extensions/admin/__init__.py b/lnbits/extensions/admin/__init__.py
index 6a56b2bb..24b91fe2 100644
--- a/lnbits/extensions/admin/__init__.py
+++ b/lnbits/extensions/admin/__init__.py
@@ -7,6 +7,7 @@ db = Database("ext_admin")
admin_ext: APIRouter = APIRouter(prefix="/admin", tags=["admin"])
+
def admin_renderer():
return template_renderer(["lnbits/extensions/admin/templates"])
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 67fbc614..0d7019cc 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -11,13 +11,13 @@ from .models import Admin, Funding
async def update_wallet_balance(wallet_id: str, amount: int) -> str:
temp_id = f"temp_{urlsafe_short_hash()}"
internal_id = f"internal_{urlsafe_short_hash()}"
-
+
payment = await create_payment(
wallet_id=wallet_id,
checking_id=internal_id,
payment_request="admin_internal",
payment_hash="admin_internal",
- amount=amount*1000,
+ amount=amount * 1000,
memo="Admin top up",
pending=False,
)
@@ -25,6 +25,7 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
await internal_invoice_queue.put(internal_id)
return payment
+
async def update_admin(user: str, **kwargs) -> Admin:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
# print("UPDATE", q)
@@ -35,23 +36,37 @@ async def update_admin(user: str, **kwargs) -> Admin:
assert row, "Newly updated settings couldn't be retrieved"
return Admin(**row) if row else None
+
async def get_admin() -> Admin:
row = await db.fetchone("SELECT * FROM admin.admin")
return Admin(**row) if row else None
+
async def update_funding(data: Funding) -> Funding:
await db.execute(
"""
UPDATE admin.funding
SET backend_wallet = ?, endpoint = ?, port = ?, read_key = ?, invoice_key = ?, admin_key = ?, cert = ?, balance = ?, selected = ?
WHERE id = ?
- """,
- (data.backend_wallet, data.endpoint, data.port, data.read_key, data.invoice_key, data.admin_key, data.cert, data.balance, data.selected, data.id,),
+ """,
+ (
+ data.backend_wallet,
+ data.endpoint,
+ data.port,
+ data.read_key,
+ data.invoice_key,
+ data.admin_key,
+ data.cert,
+ data.balance,
+ data.selected,
+ data.id,
+ ),
)
row = await db.fetchone('SELECT * FROM admin.funding WHERE "id" = ?', (data.id,))
assert row, "Newly updated settings couldn't be retrieved"
return Funding(**row) if row else None
+
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM admin.funding")
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index f3663435..388f5ec6 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -7,19 +7,21 @@ from lnbits.helpers import urlsafe_short_hash
async def get_admin_user():
- if(conf.admin_users[0]):
+ if conf.admin_users[0]:
return conf.admin_users[0]
from lnbits.core.crud import create_account, get_user
+
print("Seems like there's no admin users yet. Let's create an account for you!")
account = await create_account()
user = account.id
assert user, "Newly created user couldn't be retrieved"
- print(f"Your newly created account/user id is: {user}. This will be the Super Admin user.")
+ print(
+ f"Your newly created account/user id is: {user}. This will be the Super Admin user."
+ )
conf.admin_users.insert(0, user)
return user
-
async def m001_create_admin_table(db):
# users/server
user = await get_admin_user()
@@ -28,7 +30,7 @@ async def m001_create_admin_table(db):
admin_ext = ",".join(conf.admin_ext)
disabled_ext = ",".join(conf.disabled_ext)
funding_source = conf.funding_source
- #operational
+ # operational
data_folder = conf.data_folder
database_url = conf.database_url
force_https = conf.force_https
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 3d8efdcd..6e95d68f 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -28,6 +28,7 @@ class UpdateAdminSettings(BaseModel):
custom_logo: str = Query(None)
ad_space: str = Query(None)
+
class Admin(BaseModel):
# users
user: str
@@ -59,6 +60,7 @@ class Admin(BaseModel):
data = dict(row)
return cls(**data)
+
class Funding(BaseModel):
id: str
backend_wallet: str
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index 24b8ca85..ceda5192 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -16,19 +16,21 @@ from .crud import get_admin, get_funding
templates = Jinja2Templates(directory="templates")
+
@admin_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
admin = await get_admin()
funding = [f.dict() for f in await get_funding()]
error, balance = await g().WALLET.status()
-
+
return admin_renderer().TemplateResponse(
- "admin/index.html", {
+ "admin/index.html",
+ {
"request": request,
"user": user.dict(),
"admin": admin.dict(),
"funding": funding,
"settings": g().admin_conf.dict(),
- "balance": balance
- }
+ "balance": balance,
+ },
)
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index c0650c8a..784ad97f 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -15,16 +15,18 @@ from .crud import get_admin, update_admin, update_funding, update_wallet_balance
@admin_ext.get("/api/v1/admin/{wallet_id}/{topup_amount}", status_code=HTTPStatus.OK)
-async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)):
+async def api_update_balance(
+ wallet_id, topup_amount: int, g: WalletTypeInfo = Depends(require_admin_key)
+):
try:
wallet = await get_wallet(wallet_id)
except:
raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
- )
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
-
+
return {"status": "Success"}
@@ -32,39 +34,40 @@ async def api_update_balance(wallet_id, topup_amount: int, g: WalletTypeInfo = D
async def api_update_admin(
request: Request,
data: UpdateAdminSettings = Body(...),
- w: WalletTypeInfo = Depends(require_admin_key)
- ):
+ w: WalletTypeInfo = Depends(require_admin_key),
+):
admin = await get_admin()
# print(data)
if not admin.user == w.wallet.user:
raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
- )
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
updated = await update_admin(user=w.wallet.user, **data.dict())
- updated.admin_users = removeEmptyString(updated.admin_users.split(','))
- updated.allowed_users = removeEmptyString(updated.allowed_users.split(','))
- updated.admin_ext = removeEmptyString(updated.admin_ext.split(','))
- updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(','))
- updated.theme = removeEmptyString(updated.theme.split(','))
- updated.ad_space = removeEmptyString(updated.ad_space.split(','))
+ updated.admin_users = removeEmptyString(updated.admin_users.split(","))
+ updated.allowed_users = removeEmptyString(updated.allowed_users.split(","))
+ updated.admin_ext = removeEmptyString(updated.admin_ext.split(","))
+ updated.disabled_ext = removeEmptyString(updated.disabled_ext.split(","))
+ updated.theme = removeEmptyString(updated.theme.split(","))
+ updated.ad_space = removeEmptyString(updated.ad_space.split(","))
g().admin_conf = g().admin_conf.copy(update=updated.dict())
-
+
# print(g().admin_conf)
return {"status": "Success"}
+
@admin_ext.post("/api/v1/admin/funding/", status_code=HTTPStatus.OK)
async def api_update_funding(
request: Request,
data: Funding = Body(...),
- w: WalletTypeInfo = Depends(require_admin_key)
- ):
+ w: WalletTypeInfo = Depends(require_admin_key),
+):
admin = await get_admin()
if not admin.user == w.wallet.user:
raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
- )
+ status_code=HTTPStatus.FORBIDDEN, detail="Not allowed: not an admin"
+ )
funding = await update_funding(data=data)
return funding
From 429217f5a4876920277ad0719458daca52045b90 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:28:13 +0100
Subject: [PATCH 152/696] Had to add a couple of tries
---
lnbits/core/views/generic.py | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 83648c44..63f7af68 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -133,12 +133,19 @@ async def wallet(
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User does not exist."}
)
- if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
- return template_renderer().TemplateResponse(
- "error.html", {"request": request, "err": "User not authorized."}
- )
- if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
- user.admin = True
+ try:
+ if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
+ return template_renderer().TemplateResponse(
+ "error.html", {"request": request, "err": "User not authorized."}
+ )
+ except:
+ pass
+
+ try:
+ if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
+ user.admin = True
+ except:
+ pass
if not wallet_id:
if user.wallets and not wallet_name: # type: ignore
wallet = user.wallets[0] # type: ignore
From 1aa2f01d29a25dd5fffac385afd3b5814e10b45c Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:31:31 +0100
Subject: [PATCH 153/696] Added couple more tries
---
lnbits/decorators.py | 21 ++++++++++++---------
1 file changed, 12 insertions(+), 9 deletions(-)
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index f951163f..a810892d 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -239,13 +239,16 @@ async def check_user_exists(usr: UUID4) -> User:
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
-
- if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
-
- if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
- g().user.admin = True
-
+ try:
+ if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ except:
+ pass
+ try:
+ if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
+ g().user.admin = True
+ except:
+ pass
return g().user
From 10a065a7ca4f98c725e690d66f9c4f5c60d0c4e3 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:35:06 +0100
Subject: [PATCH 154/696] Reverted try
---
lnbits/decorators.py | 16 ++++++----------
1 file changed, 6 insertions(+), 10 deletions(-)
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index a810892d..904ca1c2 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -239,16 +239,12 @@ async def check_user_exists(usr: UUID4) -> User:
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
- try:
- if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
- raise HTTPException(
- status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
- )
- except:
- pass
- try:
- if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
- g().user.admin = True
+ if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
+ raise HTTPException(
+ status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
+ )
+ if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
+ g().user.admin = True
except:
pass
return g().user
From 3129692ab16fbd66727faba6fb04c87e56b6e0f7 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:37:07 +0100
Subject: [PATCH 155/696] reverted other try
---
lnbits/core/views/generic.py | 19 ++++++-------------
lnbits/decorators.py | 2 --
2 files changed, 6 insertions(+), 15 deletions(-)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 63f7af68..83648c44 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -133,19 +133,12 @@ async def wallet(
return template_renderer().TemplateResponse(
"error.html", {"request": request, "err": "User does not exist."}
)
- try:
- if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
- return template_renderer().TemplateResponse(
- "error.html", {"request": request, "err": "User not authorized."}
- )
- except:
- pass
-
- try:
- if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
- user.admin = True
- except:
- pass
+ if LNBITS_ALLOWED_USERS and user_id not in LNBITS_ALLOWED_USERS:
+ return template_renderer().TemplateResponse(
+ "error.html", {"request": request, "err": "User not authorized."}
+ )
+ if LNBITS_ADMIN_USERS and user_id in LNBITS_ADMIN_USERS:
+ user.admin = True
if not wallet_id:
if user.wallets and not wallet_name: # type: ignore
wallet = user.wallets[0] # type: ignore
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index 904ca1c2..dd26d8fe 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -245,6 +245,4 @@ async def check_user_exists(usr: UUID4) -> User:
)
if LNBITS_ADMIN_USERS and g().user.id in LNBITS_ADMIN_USERS:
g().user.admin = True
- except:
- pass
return g().user
From 42f6acd9f4f2f076cf278a843d48db11cbfb2b63 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 21 Sep 2022 18:40:46 +0100
Subject: [PATCH 156/696] fix main merge missing settings
---
lnbits/app.py | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/lnbits/app.py b/lnbits/app.py
index eaa33136..176c6bb9 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -56,6 +56,11 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE",
},
)
+ if lnbits.settings.LNBITS_ADMIN_UI:
+ g().admin_conf = conf
+ check_settings(app)
+
+ g().WALLET = WALLET
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
app.mount(
"/core/static",
From a6bdd8c575f74685c9f71dfd142c908543b1d210 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 10:46:11 +0200
Subject: [PATCH 157/696] format
---
lnbits/app.py | 22 +++++++++++++---------
lnbits/commands.py | 6 +++++-
lnbits/config.py | 25 ++++++++++++++++++-------
lnbits/core/views/generic.py | 1 +
lnbits/decorators.py | 2 +-
lnbits/helpers.py | 8 +++++---
6 files changed, 43 insertions(+), 21 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 176c6bb9..f4e44a0f 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -114,24 +114,28 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
return app
+
def check_settings(app: FastAPI):
@app.on_event("startup")
async def check_settings_admin():
while True:
admin_set = await get_admin_settings()
- if admin_set :
+ if admin_set:
break
print("Waiting for admin settings... retrying in 5 seconds!")
await asyncio.sleep(5)
-
- admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(','))
- admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(','))
- admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(','))
- admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(','))
- admin_set.theme = removeEmptyString(admin_set.theme.split(','))
- admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(','))
+
+ admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(","))
+ admin_set.allowed_users = removeEmptyString(admin_set.allowed_users.split(","))
+ admin_set.admin_ext = removeEmptyString(admin_set.admin_ext.split(","))
+ admin_set.disabled_ext = removeEmptyString(admin_set.disabled_ext.split(","))
+ admin_set.theme = removeEmptyString(admin_set.theme.split(","))
+ admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(","))
g().admin_conf = conf.copy(update=admin_set.dict())
- print(f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}")
+ print(
+ f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}"
+ )
+
def check_funding_source(app: FastAPI) -> None:
@app.on_event("startup")
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 763a5b90..86868f1f 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -5,6 +5,7 @@ import re
import warnings
import click
+from genericpath import exists
from loguru import logger
from .core import db as core_db
@@ -52,6 +53,7 @@ def bundle_vendored():
with open(outputpath, "w") as f:
f.write(output)
+
async def get_admin_settings():
from lnbits.extensions.admin.models import Admin
@@ -61,6 +63,7 @@ async def get_admin_settings():
return False
async with ext_db.connect() as conn:
+
if conn.type == SQLITE:
exists = await conn.fetchone(
"SELECT * FROM sqlite_master WHERE type='table' AND name='admin'"
@@ -69,7 +72,7 @@ async def get_admin_settings():
exists = await conn.fetchone(
"SELECT * FROM information_schema.tables WHERE table_name = 'admin'"
)
-
+
if not exists:
return False
@@ -77,6 +80,7 @@ async def get_admin_settings():
return Admin(**row) if row else None
+
async def migrate_databases():
"""Creates the necessary databases if they don't exist already; or migrates them."""
diff --git a/lnbits/config.py b/lnbits/config.py
index 37b700fd..cf26ad21 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -6,17 +6,19 @@ from typing import List, Optional
from pydantic import BaseSettings, Field
wallets_module = importlib.import_module("lnbits.wallets")
-wallet_class = getattr(
+wallet_class = getattr(
wallets_module, getenv("LNBITS_BACKEND_WALLET_CLASS", "VoidWallet")
)
WALLET = wallet_class()
+
def list_parse_fallback(v):
try:
return json.loads(v)
except Exception as e:
- return v.replace(' ','').split(',')
+ return v.replace(" ", "").split(",")
+
class Settings(BaseSettings):
admin_ui: bool = Field(default=True, env="LNBITS_ADMIN_UI")
@@ -24,7 +26,9 @@ class Settings(BaseSettings):
admin_users: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_USERS")
allowed_users: List[str] = Field(default_factory=list, env="LNBITS_ALLOWED_USERS")
admin_ext: List[str] = Field(default_factory=list, env="LNBITS_ADMIN_EXTENSIONS")
- disabled_ext: List[str] = Field(default_factory=list, env="LNBITS_DISABLED_EXTENSIONS")
+ disabled_ext: List[str] = Field(
+ default_factory=list, env="LNBITS_DISABLED_EXTENSIONS"
+ )
funding_source: str = Field(default="VoidWallet", env="LNBITS_BACKEND_WALLET_CLASS")
# ops
data_folder: str = Field(default=None, env="LNBITS_DATA_FOLDER")
@@ -37,10 +41,17 @@ class Settings(BaseSettings):
denomination: str = Field(default="sats", env="LNBITS_DENOMINATION")
# Change theme
site_title: str = Field(default="LNbits", env="LNBITS_SITE_TITLE")
- site_tagline: str = Field(default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE")
+ site_tagline: str = Field(
+ default="free and open-source lightning wallet", env="LNBITS_SITE_TAGLINE"
+ )
site_description: str = Field(default=None, env="LNBITS_SITE_DESCRIPTION")
- default_wallet_name: str = Field(default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME")
- theme: List[str] = Field(default=["classic, flamingo, mint, salvador, monochrome, autumn"], env="LNBITS_THEME_OPTIONS")
+ default_wallet_name: str = Field(
+ default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME"
+ )
+ theme: List[str] = Field(
+ default=["classic, flamingo, mint, salvador, monochrome, autumn"],
+ env="LNBITS_THEME_OPTIONS",
+ )
custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
@@ -48,7 +59,7 @@ class Settings(BaseSettings):
debug: Optional[str]
host: Optional[str]
port: Optional[str]
- lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
+ lnbits_path: Optional[str] = path.dirname(path.realpath(__file__))
# @validator('admin_users', 'allowed_users', 'admin_ext', 'disabled_ext', pre=True)
# def validate(cls, val):
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 83648c44..3a1fbdfc 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -39,6 +39,7 @@ from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
+
@core_html_routes.get("/favicon.ico", response_class=FileResponse)
async def favicon():
return FileResponse("lnbits/core/static/favicon.ico")
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index dd26d8fe..58b025aa 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -235,7 +235,7 @@ async def check_user_exists(usr: UUID4) -> User:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="User does not exist."
)
-
+
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index 7bd1b54a..f4255c86 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -157,11 +157,13 @@ def url_for(endpoint: str, external: Optional[bool] = False, **params: Any) -> s
url = f"{base}{endpoint}{url_params}"
return url
+
def removeEmptyString(arr):
return list(filter(None, arr))
+
def template_renderer(additional_folders: List = []) -> Jinja2Templates:
- if(settings.LNBITS_ADMIN_UI):
+ if settings.LNBITS_ADMIN_UI:
_ = g().admin_conf
settings.LNBITS_AD_SPACE = _.ad_space
settings.LNBITS_HIDE_API = _.hide_api
@@ -170,8 +172,8 @@ def template_renderer(additional_folders: List = []) -> Jinja2Templates:
settings.LNBITS_SITE_TAGLINE = _.site_tagline
settings.LNBITS_SITE_DESCRIPTION = _.site_description
settings.LNBITS_THEME_OPTIONS = _.theme
- settings.LNBITS_CUSTOM_LOGO = _.custom_logo
-
+ settings.LNBITS_CUSTOM_LOGO = _.custom_logo
+
t = Jinja2Templates(
loader=jinja2.FileSystemLoader(
["lnbits/templates", "lnbits/core/templates", *additional_folders]
From 635bcf66d6f8cf7a65322a41abed92153039cd9e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 11:47:24 +0200
Subject: [PATCH 158/696] format black
---
lnbits/app.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/lnbits/app.py b/lnbits/app.py
index f4e44a0f..118bea98 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -56,6 +56,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
"url": "https://raw.githubusercontent.com/lnbits/lnbits-legend/main/LICENSE",
},
)
+
if lnbits.settings.LNBITS_ADMIN_UI:
g().admin_conf = conf
check_settings(app)
From 11393ef7e9ed808d9034baa8515eabcd37918a02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 15:29:12 +0200
Subject: [PATCH 159/696] fix AD_SPACE
---
lnbits/core/templates/core/wallet.html | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html
index 4bf6067c..cc45eb68 100644
--- a/lnbits/core/templates/core/wallet.html
+++ b/lnbits/core/templates/core/wallet.html
@@ -385,12 +385,9 @@
- {% endif %} {% if AD_SPACE %} {% for ADS in AD_SPACE %} {% set AD =
- ADS.split(';') %}
+ {% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %}
- {% endfor %} {% endif %}
From 0beb112d5b4f938201adfc6fc2814717ddac20a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 15:55:37 +0200
Subject: [PATCH 160/696] fix some javascript errors when adding users
---
lnbits/extensions/admin/templates/admin/index.html | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d34b9068..1e881cb6 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1221,7 +1221,7 @@
addAdminUser() {
let addUser = this.data.admin_users_add
let admin_users = this.data.admin.admin_users
- if (addUser.length && !admin_users.includes(addUser)) {
+ if (addUser && addUser.length && !admin_users.includes(addUser)) {
admin_users.push(addUser)
this.data.admin.admin_users = admin_users
this.data.admin_users_add = ''
@@ -1234,7 +1234,7 @@
addAllowedUser() {
let addUser = this.data.allowed_users_add
let allowed_users = this.data.admin.allowed_users
- if (addUser.length && !allowed_users.includes(addUser)) {
+ if (addUser && addUser.length && !allowed_users.includes(addUser)) {
allowed_users.push(addUser)
this.data.admin.allowed_users = allowed_users
this.data.allowed_users_add = ''
@@ -1336,7 +1336,6 @@
custom_logo,
ad_space
} = this.data.admin
- //console.log("this", this.data.admin)
let data = {
admin_users: admin_users.toString(),
allowed_users: allowed_users.toString(),
From bfff5f3775cd090b454869c434042b4acdb37434 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 16:04:55 +0200
Subject: [PATCH 161/696] fix ADMIN_UI=false errors
---
lnbits/core/views/generic.py | 3 +++
lnbits/decorators.py | 6 ++++++
2 files changed, 9 insertions(+)
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 3a1fbdfc..db4fac43 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -124,6 +124,9 @@ async def wallet(
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
+ else:
+ LNBITS_ADMIN_USERS = []
+ LNBITS_ALLOWED_USERS = []
if not user_id:
user = await get_user((await create_account()).id)
diff --git a/lnbits/decorators.py b/lnbits/decorators.py
index 58b025aa..5a3c0a5c 100644
--- a/lnbits/decorators.py
+++ b/lnbits/decorators.py
@@ -141,6 +141,8 @@ async def get_key_type(
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
+ else:
+ LNBITS_ADMIN_USERS = []
for typenr, WalletChecker in zip(
[0, 1], [WalletAdminKeyChecker, WalletInvoiceKeyChecker]
@@ -239,6 +241,10 @@ async def check_user_exists(usr: UUID4) -> User:
if LNBITS_ADMIN_UI:
LNBITS_ADMIN_USERS = g().admin_conf.admin_users
LNBITS_ALLOWED_USERS = g().admin_conf.allowed_users
+ else:
+ LNBITS_ADMIN_USERS = []
+ LNBITS_ALLOWED_USERS = []
+
if LNBITS_ALLOWED_USERS and g().user.id not in LNBITS_ALLOWED_USERS:
raise HTTPException(
status_code=HTTPStatus.UNAUTHORIZED, detail="User not authorized."
From cede3317f3655c09b196c5293174a1dbaaaa18ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 16:14:17 +0200
Subject: [PATCH 162/696] prettier
---
lnbits/core/templates/core/wallet.html | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lnbits/core/templates/core/wallet.html b/lnbits/core/templates/core/wallet.html
index cc45eb68..5393007d 100644
--- a/lnbits/core/templates/core/wallet.html
+++ b/lnbits/core/templates/core/wallet.html
@@ -386,8 +386,7 @@
{% endif %} {% if AD_SPACE %} {% for AD in AD_SPACE %}
-
- {% endfor %} {% endif %}
From b442acc24f02c91971d29fbfe03dda9d5b5fc34c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 22 Sep 2022 18:35:47 +0200
Subject: [PATCH 163/696] fix migration tests
---
lnbits/extensions/admin/migrations.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 388f5ec6..196c9fc0 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -7,7 +7,7 @@ from lnbits.helpers import urlsafe_short_hash
async def get_admin_user():
- if conf.admin_users[0]:
+ if len(conf.admin_users) > 0:
return conf.admin_users[0]
from lnbits.core.crud import create_account, get_user
From 6db5fb16c85d3fd654afd55ee8d6969d243572bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 26 Sep 2022 16:54:19 +0200
Subject: [PATCH 164/696] change comments to use multiple lines
---
.env.example | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/.env.example b/.env.example
index f0e21aa8..673470b7 100644
--- a/.env.example
+++ b/.env.example
@@ -3,11 +3,15 @@ PORT=5000
DEBUG=false
-LNBITS_ADMIN_USERS="" # User IDs seperated by comma
-LNBITS_ADMIN_EXTENSIONS="ngrok, admin" # Extensions only admin can access
-LNBITS_ADMIN_UI=false # Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
+# User IDs seperated by comma
+LNBITS_ADMIN_USERS=""
+# Extensions only admin can access
+LNBITS_ADMIN_EXTENSIONS="ngrok, admin"
+# Enable Admin GUI, available for the first user in LNBITS_ADMIN_USERS if available
+LNBITS_ADMIN_UI=false
-LNBITS_ALLOWED_USERS="" # Restricts access, User IDs seperated by comma
+# Restricts access, User IDs seperated by comma
+LNBITS_ALLOWED_USERS=""
LNBITS_DEFAULT_WALLET_NAME="LNbits wallet"
From affec50a3da3ac812b5046c49483676e93962b2c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 26 Sep 2022 16:59:30 +0200
Subject: [PATCH 165/696] fix merge error
---
lnbits/app.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 118bea98..82a8f20e 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -57,10 +57,6 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
},
)
- if lnbits.settings.LNBITS_ADMIN_UI:
- g().admin_conf = conf
- check_settings(app)
-
g().WALLET = WALLET
app.mount("/static", StaticFiles(packages=[("lnbits", "static")]), name="static")
app.mount(
From 7aba2f989cdd78539ca22c69dc1a2e7d5ceb3d37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 14:14:36 +0200
Subject: [PATCH 166/696] use logger in app.py
---
lnbits/app.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 82a8f20e..e07d2ada 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -31,8 +31,6 @@ from .helpers import (
url_for_vendored,
)
from .requestvars import g
-
-# from .settings import WALLET
from .tasks import (
catch_everything_and_restart,
check_pending_payments,
@@ -119,7 +117,7 @@ def check_settings(app: FastAPI):
admin_set = await get_admin_settings()
if admin_set:
break
- print("Waiting for admin settings... retrying in 5 seconds!")
+ logger.info("Waiting for admin settings... retrying in 5 seconds!")
await asyncio.sleep(5)
admin_set.admin_users = removeEmptyString(admin_set.admin_users.split(","))
@@ -129,7 +127,7 @@ def check_settings(app: FastAPI):
admin_set.theme = removeEmptyString(admin_set.theme.split(","))
admin_set.ad_space = removeEmptyString(admin_set.ad_space.split(","))
g().admin_conf = conf.copy(update=admin_set.dict())
- print(
+ logger.info(
f" ✔️ Access admin user account at: http://{lnbits.settings.HOST}:{lnbits.settings.PORT}/wallet?usr={admin_set.user}"
)
From 212b8f9fdd67c73b16f2c0d97cd549bc7bb8f29e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 14:14:57 +0200
Subject: [PATCH 167/696] fix config
---
lnbits/config.py | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index cf26ad21..874effae 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -17,7 +17,11 @@ def list_parse_fallback(v):
try:
return json.loads(v)
except Exception as e:
- return v.replace(" ", "").split(",")
+ replaced = v.replace(" ", "")
+ if replaced:
+ return replaced.split(",")
+ else:
+ return []
class Settings(BaseSettings):
@@ -48,10 +52,7 @@ class Settings(BaseSettings):
default_wallet_name: str = Field(
default="LNbits wallet", env="LNBITS_DEFAULT_WALLET_NAME"
)
- theme: List[str] = Field(
- default=["classic, flamingo, mint, salvador, monochrome, autumn"],
- env="LNBITS_THEME_OPTIONS",
- )
+ theme: List[str] = Field(default_factory=list, env="LNBITS_THEME_OPTIONS")
custom_logo: str = Field(default=None, env="LNBITS_CUSTOM_LOGO")
ad_space: List[str] = Field(default_factory=list, env="LNBITS_AD_SPACE")
# .env
@@ -74,4 +75,5 @@ class Settings(BaseSettings):
conf = Settings()
+print(conf)
WALLET = wallet_class()
From b6a0a321844ff8a185f728b42f4ede3142334637 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 14:17:20 +0200
Subject: [PATCH 168/696] concatenate first migrations script
fixup
---
lnbits/config.py | 1 -
lnbits/extensions/admin/migrations.py | 7 +++----
2 files changed, 3 insertions(+), 5 deletions(-)
diff --git a/lnbits/config.py b/lnbits/config.py
index 874effae..fe8dabf9 100644
--- a/lnbits/config.py
+++ b/lnbits/config.py
@@ -75,5 +75,4 @@ class Settings(BaseSettings):
conf = Settings()
-print(conf)
WALLET = wallet_class()
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 196c9fc0..2d48a8e4 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -23,6 +23,8 @@ async def get_admin_user():
async def m001_create_admin_table(db):
+
+
# users/server
user = await get_admin_user()
admin_users = ",".join(conf.admin_users)
@@ -78,7 +80,7 @@ async def m001_create_admin_table(db):
await db.execute(
"""
INSERT INTO admin.admin (
- "user",
+ "user",
admin_users,
allowed_users,
admin_ext,
@@ -126,9 +128,6 @@ async def m001_create_admin_table(db):
),
)
-
-async def m001_create_funding_table(db):
-
funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS")
# Make the funding table, if it does not already exist
From 1eeb9de7de8c2409f9a1d6b828e051ea640870d0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 27 Sep 2022 16:37:08 +0200
Subject: [PATCH 169/696] add restart button to frontend
---
.../admin/templates/admin/index.html | 28 ++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 1e881cb6..319ca3f0 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -65,6 +65,14 @@
:options="data.funding_source"
>
+
+
+
Fee reserve
@@ -89,7 +97,7 @@
>
-
+
@@ -1257,6 +1265,24 @@
let spaces = this.data.admin.ad_space
this.data.admin.ad_space = spaces.filter(s => s !== ad)
},
+ restartServer() {
+ LNbits.api
+ .request(
+ 'GET',
+ '/admin/api/v1/admin/restart/',
+ this.g.user.wallets[0].adminkey
+ )
+ .then(response => {
+ this.$q.notify({
+ type: 'positive',
+ message: 'Success! Restarted Server',
+ icon: null
+ })
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
topupWallet() {
LNbits.api
.request(
From 82e322ae43658bef7735974324fd3bec7919b756 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 3 Oct 2022 16:34:52 +0200
Subject: [PATCH 170/696] make extension use new settings
---
lnbits/extensions/boltz/boltz.py | 14 ++++++--------
lnbits/extensions/boltz/mempool.py | 15 ++++++---------
lnbits/extensions/boltz/views_api.py | 4 ++--
lnbits/extensions/lndhub/views_api.py | 4 ++--
lnbits/extensions/tpos/views.py | 14 +++++++-------
5 files changed, 23 insertions(+), 28 deletions(-)
diff --git a/lnbits/extensions/boltz/boltz.py b/lnbits/extensions/boltz/boltz.py
index ac99d4f4..424d0bf7 100644
--- a/lnbits/extensions/boltz/boltz.py
+++ b/lnbits/extensions/boltz/boltz.py
@@ -12,7 +12,7 @@ from loguru import logger
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.helpers import urlsafe_short_hash
-from lnbits.settings import BOLTZ_NETWORK, BOLTZ_URL
+from lnbits.settings import settings
from .crud import update_swap_status
from .mempool import (
@@ -33,9 +33,7 @@ from .models import (
)
from .utils import check_balance, get_timestamp, req_wrap
-net = NETWORKS[BOLTZ_NETWORK]
-logger.trace(f"BOLTZ_URL: {BOLTZ_URL}")
-logger.trace(f"Bitcoin Network: {net['name']}")
+net = NETWORKS[settings.boltz_network]
async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
@@ -62,7 +60,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
res = req_wrap(
"post",
- f"{BOLTZ_URL}/createswap",
+ f"{settings.boltz_url}/createswap",
json={
"type": "submarine",
"pairId": "BTC/BTC",
@@ -129,7 +127,7 @@ async def create_reverse_swap(
res = req_wrap(
"post",
- f"{BOLTZ_URL}/createswap",
+ f"{settings.boltz_url}/createswap",
json={
"type": "reversesubmarine",
"pairId": "BTC/BTC",
@@ -409,7 +407,7 @@ def check_boltz_limits(amount):
def get_boltz_pairs():
res = req_wrap(
"get",
- f"{BOLTZ_URL}/getpairs",
+ f"{settings.boltz_url}/getpairs",
headers={"Content-Type": "application/json"},
)
return res.json()
@@ -418,7 +416,7 @@ def get_boltz_pairs():
def get_boltz_status(boltzid):
res = req_wrap(
"post",
- f"{BOLTZ_URL}/swapstatus",
+ f"{settings.boltz_url}/swapstatus",
json={"id": boltzid},
)
return res.json()
diff --git a/lnbits/extensions/boltz/mempool.py b/lnbits/extensions/boltz/mempool.py
index a44c0f02..a64cadad 100644
--- a/lnbits/extensions/boltz/mempool.py
+++ b/lnbits/extensions/boltz/mempool.py
@@ -7,14 +7,11 @@ import websockets
from embit.transaction import Transaction
from loguru import logger
-from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL, BOLTZ_MEMPOOL_SPACE_URL_WS
+from lnbits.settings import settings
from .utils import req_wrap
-logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL: {BOLTZ_MEMPOOL_SPACE_URL}")
-logger.trace(f"BOLTZ_MEMPOOL_SPACE_URL_WS: {BOLTZ_MEMPOOL_SPACE_URL_WS}")
-
-websocket_url = f"{BOLTZ_MEMPOOL_SPACE_URL_WS}/api/v1/ws"
+websocket_url = f"{settings.boltz_mempool_space_url_ws}/api/v1/ws"
async def wait_for_websocket_message(send, message_string):
@@ -33,7 +30,7 @@ async def wait_for_websocket_message(send, message_string):
def get_mempool_tx(address):
res = req_wrap(
"get",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/address/{address}/txs",
+ f"{settings.boltz_mempool_space_url}/api/address/{address}/txs",
headers={"Content-Type": "text/plain"},
)
txs = res.json()
@@ -70,7 +67,7 @@ def get_fee_estimation() -> int:
def get_mempool_fees() -> int:
res = req_wrap(
"get",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/v1/fees/recommended",
+ f"{settings.boltz_mempool_space_url}/api/v1/fees/recommended",
headers={"Content-Type": "text/plain"},
)
fees = res.json()
@@ -80,7 +77,7 @@ def get_mempool_fees() -> int:
def get_mempool_blockheight() -> int:
res = req_wrap(
"get",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/blocks/tip/height",
+ f"{settings.boltz_mempool_space_url}/api/blocks/tip/height",
headers={"Content-Type": "text/plain"},
)
return int(res.text)
@@ -91,7 +88,7 @@ async def send_onchain_tx(tx: Transaction):
logger.debug(f"Boltz - mempool sending onchain tx...")
req_wrap(
"post",
- f"{BOLTZ_MEMPOOL_SPACE_URL}/api/tx",
+ f"{settings.boltz_mempool_space_url}/api/tx",
headers={"Content-Type": "text/plain"},
content=raw,
)
diff --git a/lnbits/extensions/boltz/views_api.py b/lnbits/extensions/boltz/views_api.py
index a4b7d318..18ca14cb 100644
--- a/lnbits/extensions/boltz/views_api.py
+++ b/lnbits/extensions/boltz/views_api.py
@@ -14,7 +14,7 @@ from starlette.requests import Request
from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.settings import BOLTZ_MEMPOOL_SPACE_URL
+from lnbits.settings import settings
from . import boltz_ext
from .boltz import (
@@ -55,7 +55,7 @@ from .utils import check_balance
response_model=str,
)
async def api_mempool_url():
- return BOLTZ_MEMPOOL_SPACE_URL
+ return settings.boltz_mempool_space_url
# NORMAL SWAP
diff --git a/lnbits/extensions/lndhub/views_api.py b/lnbits/extensions/lndhub/views_api.py
index 8cbe5a6b..b2328c39 100644
--- a/lnbits/extensions/lndhub/views_api.py
+++ b/lnbits/extensions/lndhub/views_api.py
@@ -12,7 +12,7 @@ from lnbits import bolt11
from lnbits.core.crud import delete_expired_invoices, get_payments
from lnbits.core.services import create_invoice, pay_invoice
from lnbits.decorators import WalletTypeInfo
-from lnbits.settings import LNBITS_SITE_TITLE, WALLET
+from lnbits.settings import WALLET, settings
from . import lndhub_ext
from .decorators import check_wallet, require_admin_key
@@ -56,7 +56,7 @@ async def lndhub_addinvoice(
_, pr = await create_invoice(
wallet_id=wallet.wallet.id,
amount=int(data.amt),
- memo=data.memo or LNBITS_SITE_TITLE,
+ memo=data.memo or settings.lnbits_site_title,
extra={"tag": "lndhub"},
)
except:
diff --git a/lnbits/extensions/tpos/views.py b/lnbits/extensions/tpos/views.py
index e1f1d21e..dac129a9 100644
--- a/lnbits/extensions/tpos/views.py
+++ b/lnbits/extensions/tpos/views.py
@@ -8,7 +8,7 @@ from starlette.responses import HTMLResponse
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
-from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE
+from lnbits.settings import settings
from . import tpos_ext, tpos_renderer
from .crud import get_tpos
@@ -50,12 +50,12 @@ async def manifest(tpos_id: str):
)
return {
- "short_name": LNBITS_SITE_TITLE,
- "name": tpos.name + " - " + LNBITS_SITE_TITLE,
+ "short_name": settings.lnbits_site_title,
+ "name": tpos.name + " - " + settings.lnbits_site_title,
"icons": [
{
- "src": LNBITS_CUSTOM_LOGO
- if LNBITS_CUSTOM_LOGO
+ "src": settings.lnbits_custom_logo
+ if settings.lnbits_custom_logo
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
"type": "image/png",
"sizes": "900x900",
@@ -69,9 +69,9 @@ async def manifest(tpos_id: str):
"theme_color": "#1F2234",
"shortcuts": [
{
- "name": tpos.name + " - " + LNBITS_SITE_TITLE,
+ "name": tpos.name + " - " + settings.lnbits_site_title,
"short_name": tpos.name,
- "description": tpos.name + " - " + LNBITS_SITE_TITLE,
+ "description": tpos.name + " - " + settings.lnbits_site_title,
"url": "/tpos/" + tpos_id,
}
],
From 5aa9cdd45631379341f31d17d9484c5e3ad05a4b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 3 Oct 2022 16:35:26 +0200
Subject: [PATCH 171/696] remove enviroms
---
pyproject.toml | 1 -
1 file changed, 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index 7418de27..92c43dce 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,7 +20,6 @@ charset-normalizer = "2.0.6"
click = "8.0.1"
ecdsa = "0.17.0"
embit = "0.4.9"
-environs = "9.3.3"
fastapi = "0.78.0"
h11 = "0.12.0"
httpcore = "0.15.0"
From 6a2c7414783a5edb4b7932355f3fe145053332a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 3 Oct 2022 16:36:14 +0200
Subject: [PATCH 172/696] fix admin
---
lnbits/extensions/admin/crud.py | 24 +-
lnbits/extensions/admin/migrations.py | 337 +----
lnbits/extensions/admin/models.py | 58 +-
.../admin/templates/admin/_tab_funding.html | 158 +++
.../admin/templates/admin/_tab_server.html | 78 ++
.../admin/templates/admin/_tab_theme.html | 122 ++
.../admin/templates/admin/_tab_users.html | 96 ++
.../admin/templates/admin/index.html | 1133 +----------------
lnbits/extensions/admin/views.py | 16 +-
lnbits/extensions/admin/views_api.py | 28 +-
10 files changed, 576 insertions(+), 1474 deletions(-)
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_funding.html
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_server.html
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_theme.html
create mode 100644 lnbits/extensions/admin/templates/admin/_tab_users.html
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 0d7019cc..e4cb5d77 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -2,10 +2,11 @@ from typing import List
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
+from lnbits.settings import Settings
from lnbits.tasks import internal_invoice_queue
from . import db
-from .models import Admin, Funding
+from .models import Funding
async def update_wallet_balance(wallet_id: str, amount: int) -> str:
@@ -23,26 +24,26 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
)
# manually send this for now
await internal_invoice_queue.put(internal_id)
- return payment
-async def update_admin(user: str, **kwargs) -> Admin:
+async def update_settings(user: str, **kwargs) -> Settings:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
# print("UPDATE", q)
await db.execute(
- f'UPDATE admin.admin SET {q} WHERE "user" = ?', (*kwargs.values(), user)
+ f'UPDATE admin.settings SET {q} WHERE "user" = ?', (*kwargs.values(), user)
)
- row = await db.fetchone('SELECT * FROM admin.admin WHERE "user" = ?', (user,))
+ row = await db.fetchone('SELECT * FROM admin.settings WHERE "user" = ?', (user,))
assert row, "Newly updated settings couldn't be retrieved"
- return Admin(**row) if row else None
-
-
-async def get_admin() -> Admin:
- row = await db.fetchone("SELECT * FROM admin.admin")
- return Admin(**row) if row else None
+ return Settings(**row) if row else None
async def update_funding(data: Funding) -> Funding:
+ await db.execute(
+ """
+ UPDATE admin.settings SET funding_source = ? WHERE user = ?
+ """,
+ (data.backend_wallet, data.user),
+ )
await db.execute(
"""
UPDATE admin.funding
@@ -69,5 +70,4 @@ async def update_funding(data: Funding) -> Funding:
async def get_funding() -> List[Funding]:
rows = await db.fetchall("SELECT * FROM admin.funding")
-
return [Funding(**row) for row in rows]
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 2d48a8e4..8f6c76a0 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -1,292 +1,57 @@
-from os import getenv
-
-from sqlalchemy.exc import OperationalError # type: ignore
-
-from lnbits.config import conf
-from lnbits.helpers import urlsafe_short_hash
-
-
-async def get_admin_user():
- if len(conf.admin_users) > 0:
- return conf.admin_users[0]
- from lnbits.core.crud import create_account, get_user
-
- print("Seems like there's no admin users yet. Let's create an account for you!")
- account = await create_account()
- user = account.id
- assert user, "Newly created user couldn't be retrieved"
- print(
- f"Your newly created account/user id is: {user}. This will be the Super Admin user."
- )
- conf.admin_users.insert(0, user)
- return user
-
-
-async def m001_create_admin_table(db):
-
-
- # users/server
- user = await get_admin_user()
- admin_users = ",".join(conf.admin_users)
- allowed_users = ",".join(conf.allowed_users)
- admin_ext = ",".join(conf.admin_ext)
- disabled_ext = ",".join(conf.disabled_ext)
- funding_source = conf.funding_source
- # operational
- data_folder = conf.data_folder
- database_url = conf.database_url
- force_https = conf.force_https
- reserve_fee_min = conf.reserve_fee_min
- reserve_fee_pct = conf.reserve_fee_pct
- service_fee = conf.service_fee
- hide_api = conf.hide_api
- denomination = conf.denomination
- # Theme'ing
- site_title = conf.site_title
- site_tagline = conf.site_tagline
- site_description = conf.site_description
- default_wallet_name = conf.default_wallet_name
- theme = ",".join(conf.theme)
- custom_logo = conf.custom_logo
- ad_space = ",".join(conf.ad_space)
-
+async def m001_create_admin_settings_table(db):
await db.execute(
"""
- CREATE TABLE IF NOT EXISTS admin.admin (
- "user" TEXT PRIMARY KEY,
- admin_users TEXT,
- allowed_users TEXT,
- admin_ext TEXT,
- disabled_ext TEXT,
- funding_source TEXT,
- data_folder TEXT,
- database_url TEXT,
- force_https BOOLEAN,
- reserve_fee_min INT,
- reserve_fee_pct REAL,
- service_fee REAL,
- hide_api BOOLEAN,
- denomination TEXT,
- site_title TEXT,
- site_tagline TEXT,
- site_description TEXT,
- default_wallet_name TEXT,
- theme TEXT,
- custom_logo TEXT,
- ad_space TEXT
- );
- """
- )
- await db.execute(
- """
- INSERT INTO admin.admin (
- "user",
- admin_users,
- allowed_users,
- admin_ext,
- disabled_ext,
- funding_source,
- data_folder,
- database_url,
- force_https,
- reserve_fee_min,
- reserve_fee_pct,
- service_fee,
- hide_api,
- denomination,
- site_title,
- site_tagline,
- site_description,
- default_wallet_name,
- theme,
- custom_logo,
- ad_space)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- """,
- (
- user,
- admin_users,
- allowed_users,
- admin_ext,
- disabled_ext,
- funding_source,
- data_folder,
- database_url,
- force_https,
- reserve_fee_min,
- reserve_fee_pct,
- service_fee,
- hide_api,
- denomination,
- site_title,
- site_tagline,
- site_description,
- default_wallet_name,
- theme,
- custom_logo,
- ad_space,
- ),
- )
-
- funding_wallet = getenv("LNBITS_BACKEND_WALLET_CLASS")
-
- # Make the funding table, if it does not already exist
- await db.execute(
- """
- CREATE TABLE IF NOT EXISTS admin.funding (
- id TEXT PRIMARY KEY,
- backend_wallet TEXT,
- endpoint TEXT,
+ CREATE TABLE IF NOT EXISTS admin.settings (
+ lnbits_admin_ui TEXT,
+ debug TEXT,
+ host TEXT,
port INT,
- read_key TEXT,
- invoice_key TEXT,
- admin_key TEXT,
- cert TEXT,
- balance INT,
- selected INT
+ lnbits_path TEXT,
+ lnbits_commit TEXT,
+ lnbits_admin_users TEXT,
+ lnbits_allowed_users TEXT,
+ lnbits_allowed_funding_sources TEXT,
+ lnbits_admin_extensions TEXT,
+ lnbits_disabled_extensions TEXT,
+ lnbits_site_title TEXT,
+ lnbits_site_tagline TEXT,
+ lnbits_site_description TEXT,
+ lnbits_default_wallet_name TEXT,
+ lnbits_theme_options TEXT,
+ lnbits_custom_logo TEXT,
+ lnbits_ad_space TEXT,
+ lnbits_data_folder TEXT,
+ lnbits_database_url TEXT,
+ lnbits_force_https TEXT,
+ lnbits_reserve_fee_min TEXT,
+ lnbits_reserve_fee_percent TEXT,
+ lnbits_service_fee TEXT,
+ lnbits_hide_api TEXT,
+ lnbits_denomination TEXT,
+ lnbits_backend_wallet_class TEXT,
+ fake_wallet_secret TEXT,
+ lnbits_endpoint TEXT,
+ lnbits_key TEXT,
+ cliche_endpoint TEXT,
+ corelightning_rpc TEXT,
+ eclair_url TEXT,
+ eclair_pass TEXT,
+ lnd_rest_endpoint TEXT,
+ lnd_rest_cert TEXT,
+ lnd_rest_macaroon TEXT,
+ lnpay_api_endpoint TEXT,
+ lnpay_api_key TEXT,
+ lnpay_wallet_key TEXT,
+ lntxbot_api_endpoint TEXT,
+ lntxbot_key TEXT,
+ opennode_api_endpoint TEXT,
+ opennode_key TEXT,
+ spark_url TEXT,
+ spark_token TEXT,
+ boltz_network TEXT,
+ boltz_url TEXT,
+ boltz_mempool_space_url TEXT,
+ boltz_mempool_space_url_ws TEXT
);
"""
)
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, selected)
- VALUES (?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "CLightningWallet",
- getenv("CLIGHTNING_RPC"),
- 1 if funding_wallet == "CLightningWallet" else 0,
- ),
- )
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "SparkWallet",
- getenv("SPARK_URL"),
- getenv("SPARK_TOKEN"),
- 1 if funding_wallet == "SparkWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LnbitsWallet",
- getenv("LNBITS_ENDPOINT"),
- getenv("LNBITS_KEY"),
- 1 if funding_wallet == "LnbitsWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, port, admin_key, cert, selected)
- VALUES (?, ?, ?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LndWallet",
- getenv("LND_GRPC_ENDPOINT"),
- getenv("LND_GRPC_PORT"),
- getenv("LND_GRPC_MACAROON"),
- getenv("LND_GRPC_CERT"),
- 1 if funding_wallet == "LndWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
- VALUES (?, ?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LndRestWallet",
- getenv("LND_REST_ENDPOINT"),
- getenv("LND_REST_MACAROON"),
- getenv("LND_REST_CERT"),
- 1 if funding_wallet == "LndWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, cert, selected)
- VALUES (?, ?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LNPayWallet",
- getenv("LNPAY_API_ENDPOINT"),
- getenv("LNPAY_WALLET_KEY"),
- getenv("LNPAY_API_KEY"), # this is going in as the cert
- 1 if funding_wallet == "LNPayWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "LntxbotWallet",
- getenv("LNTXBOT_API_ENDPOINT"),
- getenv("LNTXBOT_KEY"),
- 1 if funding_wallet == "LntxbotWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "OpenNodeWallet",
- getenv("OPENNODE_API_ENDPOINT"),
- getenv("OPENNODE_KEY"),
- 1 if funding_wallet == "OpenNodeWallet" else 0,
- ),
- )
-
- await db.execute(
- """
- INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- urlsafe_short_hash(),
- "SparkWallet",
- getenv("SPARK_URL"),
- getenv("SPARK_TOKEN"),
- 1 if funding_wallet == "SparkWallet" else 0,
- ),
- )
-
- ## PLACEHOLDER FOR ECLAIR WALLET
- # await db.execute(
- # """
- # INSERT INTO admin.funding (id, backend_wallet, endpoint, admin_key, selected)
- # VALUES (?, ?, ?, ?, ?)
- # """,
- # (
- # urlsafe_short_hash(),
- # "EclairWallet",
- # getenv("ECLAIR_URL"),
- # getenv("ECLAIR_PASS"),
- # 1 if funding_wallet == "EclairWallet" else 0,
- # ),
- # )
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 6e95d68f..ef57cadd 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -29,36 +29,36 @@ class UpdateAdminSettings(BaseModel):
ad_space: str = Query(None)
-class Admin(BaseModel):
- # users
- user: str
- admin_users: Optional[str]
- allowed_users: Optional[str]
- admin_ext: Optional[str]
- disabled_ext: Optional[str]
- funding_source: Optional[str]
- # ops
- data_folder: Optional[str]
- database_url: Optional[str]
- force_https: bool = Field(default=True)
- reserve_fee_min: Optional[int]
- reserve_fee_pct: Optional[float]
- service_fee: float = Optional[float]
- hide_api: bool = Field(default=False)
- # Change theme
- site_title: Optional[str]
- site_tagline: Optional[str]
- site_description: Optional[str]
- default_wallet_name: Optional[str]
- denomination: str = Field(default="sats")
- theme: Optional[str]
- custom_logo: Optional[str]
- ad_space: Optional[str]
+# class Admin(BaseModel):
+# # users
+# user: str
+# admin_users: Optional[str]
+# allowed_users: Optional[str]
+# admin_ext: Optional[str]
+# disabled_ext: Optional[str]
+# funding_source: Optional[str]
+# # ops
+# data_folder: Optional[str]
+# database_url: Optional[str]
+# force_https: bool = Field(default=True)
+# reserve_fee_min: Optional[int]
+# reserve_fee_pct: Optional[float]
+# service_fee: float = Optional[float]
+# hide_api: bool = Field(default=False)
+# # Change theme
+# site_title: Optional[str]
+# site_tagline: Optional[str]
+# site_description: Optional[str]
+# default_wallet_name: Optional[str]
+# denomination: str = Field(default="sats")
+# theme: Optional[str]
+# custom_logo: Optional[str]
+# ad_space: Optional[str]
- @classmethod
- def from_row(cls, row: Row) -> "Admin":
- data = dict(row)
- return cls(**data)
+# @classmethod
+# def from_row(cls, row: Row) -> "Admin":
+# data = dict(row)
+# return cls(**data)
class Funding(BaseModel):
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
new file mode 100644
index 00000000..2ed0aae2
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -0,0 +1,158 @@
+
+
+ Wallets Management
+
+
+
+
+
Funding Source Info
+
+ {%raw%}
+
+ Funding Source: {{data.settings.lnbits_backend_wallet_class}}
+
+ Balance: {{data.balance / 1000}} sats
+ {%endraw%}
+
+
+
+
+
+
+
+
+
Active Funding (Requires server restart)
+
+
+
+
+
+
+
+
+
+
+
TopUp a wallet
+
+
+
+
+
+
+
+
Funding Sources
+ {% raw %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_server.html b/lnbits/extensions/admin/templates/admin/_tab_server.html
new file mode 100644
index 00000000..2924e6a4
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_server.html
@@ -0,0 +1,78 @@
+
+
+ Server Management
+
+
+
+
+
Server Info
+
+ {%raw%}
+
+ SQlite: {{data.settings.lnbits_data_folder}}
+
+
+ Postgres: {{data.settings.lnbits_database_url}}
+
+ {%endraw%}
+
+
+
+
+
+
+
+
Miscelaneous
+
+
+ Force HTTPS
+ Prefer secure URLs
+
+
+
+
+
+
+
+ Hide API
+ Hides wallet api, extensions can choose to honor
+
+
+
+
+
+
+
+
+
+
+
+ Save
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html
new file mode 100644
index 00000000..41dc0447
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html
@@ -0,0 +1,122 @@
+
+
+ UI Management
+
+
+
+
+
+
+
+
Default Wallet Name
+
+
+
+
+
+
+
+
+
Advertisement Slots
+
+
+
+
+ {% raw %}
+
+ {{ space.slice(0, 8) + " ... " + space.slice(-8) }}
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+ Save
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html
new file mode 100644
index 00000000..3eb53ac4
--- /dev/null
+++ b/lnbits/extensions/admin/templates/admin/_tab_users.html
@@ -0,0 +1,96 @@
+
+
+ User Management
+
+
+ Super Admin: {% raw %}{{this.data.settings.lnbits_admin_users[0]}}{%
+ endraw %}
+
+
+
+
Admin Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
Allowed Users
+
+
+
+
+ {% raw %}
+
+ {{ user }}
+
+ {% endraw %}
+
+
+
+
+
+
+
Disabled Extensions
+
+
+
+
+
+ Save
+
+
+
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 319ca3f0..87e89321 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -29,1118 +29,16 @@
-
-
-
- Wallets Management
-
-
-
-
-
Funding Source Info
-
- {%raw%}
- Funding Source: {{data.admin.funding_source}}
- Balance: {{data.admin.balance / 1000}} sats
- {%endraw%}
-
-
-
-
-
-
-
-
-
- Active Funding
- (Requires server restart)
-
-
-
-
-
-
-
-
-
-
-
-
-
TopUp a wallet
-
-
-
-
-
-
-
-
-
Funding Sources
- {% raw %}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% endraw %}
-
-
-
-
-
-
- User Management
-
-
- Super Admin: {% raw %}{{this.data.admin.user}}{% endraw %}
-
-
-
-
Admin Users
-
-
-
-
- {% raw %}
-
- {{ user }}
-
- {% endraw %}
-
-
-
-
-
Allowed Users
-
-
-
-
- {% raw %}
-
- {{ user }}
-
- {% endraw %}
-
-
-
-
-
-
-
Disabled Extensions
-
-
-
-
-
- Save
-
-
-
-
-
- Server Management
-
-
-
-
-
Server Info
-
- {%raw%}
-
- SQlite: {{data.admin.data_folder}}
-
-
- Postgres: {{data.admin.database_url}}
-
- {%endraw%}
-
-
-
-
-
-
-
-
Miscelaneous
-
-
- Force HTTPS
- Prefer secure URLs
-
-
-
-
-
-
-
- Hide API
- Hides wallet api, extensions can choose to
- honor
-
-
-
-
-
-
-
-
-
-
-
- Save
-
-
-
-
-
- UI Management
-
-
-
-
-
-
-
-
Default Wallet Name
-
-
-
-
-
-
-
-
-
Advertisement Slots
-
-
-
-
- {% raw %}
-
- {{ space.slice(0, 8) + " ... " + space.slice(-8) }}
-
- {% endraw %}
-
-
-
-
-
-
-
-
- Save
-
-
-
+ {% include "admin/_tab_funding.html" %} {% include
+ "admin/_tab_users.html" %} {% include "admin/_tab_server.html" %} {%
+ include "admin/_tab_theme.html" %}
-
-
-
-
-
{% endblock %} {% block scripts %} {{ window_vars(user) }}
{% endblock %}
From 04b37458983094ff9deec4ba010ec2a93c002cd9 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 7 Oct 2022 19:24:07 +0100
Subject: [PATCH 198/696] make saving possible (possible will change in future)
---
.../admin/templates/admin/index.html | 36 ++++++++++++-------
1 file changed, 24 insertions(+), 12 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 72352651..18df16a9 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -121,8 +121,11 @@
created: function () {
this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data
this.balance = +'{{ balance|safe }}'
- this.formData = this.settings //model
+ this.formData = _.clone(this.settings) //model
+ //this.formData.lnbits_ad_space = "hdh"
console.log(this.formData)
+ console.log(_.isEqual(this.settings, this.formData))
+
},
methods: {
addAdminUser() {
@@ -206,18 +209,27 @@
},
updateSettings() {
let data = {
- ...this.settings,
- ...this.formData
+ lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class,
+ lnbits_admin_users: this.formData.lnbits_admin_users.toString(),
+ lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(),
+ lnbits_admin_ext: this.formData.lnbits_admin_ext,
+ lnbits_disabled_ext: this.formData.lnbits_disabled_ext,
+ lnbits_funding_source: this.formData.lnbits_funding_source,
+ lnbits_force_https: this.formData.lnbits_force_https,
+ lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min,
+ lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent,
+ lnbits_service_fee: this.formData.lnbits_service_fee,
+ lnbits_hide_api: this.formData.lnbits_hide_api,
+ lnbits_site_title: this.formData.lnbits_site_title,
+ lnbits_site_tagline: this.formData.lnbits_site_tagline,
+ lnbits_site_description: this.formData.lnbits_site_description,
+ lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name,
+ lnbits_denomination: this.formData.lnbits_denomination,
+ lnbits_theme: this.formData.lnbits_theme,
+ lnbits_custom_logo: this.formData.lnbits_custom_logo,
+ lnbits_ad_space: this.formData.lnbits_ad_space.toString()
}
- /*
- const formElement = document.getElementById('settings_form')
- const formData = new FormData(formElement)
- const data = {}
- formData.forEach((value, key) => (data[key] = value))
- // only for debugging
- for (const [key, value] of formData) {
- console.log(`${key}: ${value}\n`)
- }*/
+ console.log(data)
LNbits.api
.request(
'PUT',
From cc42df12f4d3927c854675f114aff0b75bef6d19 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Fri, 7 Oct 2022 19:44:03 +0100
Subject: [PATCH 199/696] some more refining
---
lnbits/extensions/admin/models.py | 10 +++++-----
.../extensions/admin/templates/admin/_tab_users.html | 2 ++
lnbits/extensions/admin/templates/admin/index.html | 12 +++++++++---
3 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 13a6cd23..45cd990d 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -4,10 +4,10 @@ from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: str = Query(None)
- lnbits_allowed_users: str = Query(None)
- lnbits_admin_ext: str = Query(None)
- lnbits_disabled_ext: str = Query(None)
+ lnbits_admin_users: str = Query(None) #this should be List[str] ??
+ lnbits_allowed_users: str = Query(None) #this should be List[str] ??
+ lnbits_admin_ext: str = Query(None) #this should be List[str] ??
+ lnbits_disabled_ext: str = Query(None) #this should be List[str] ??
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -21,4 +21,4 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: str = Query(None)
+ lnbits_ad_space: str = Query(None) #this should be List[str] ??
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html
index c396ba7d..08b08a62 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_users.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_users.html
@@ -71,6 +71,7 @@
multiple
hint="Extensions only user with admin privileges can use"
label="Admin extensions"
+ :options="g.extensions.map(e => e.name)"
>
@@ -79,6 +80,7 @@
-
+
Date: Mon, 10 Oct 2022 12:17:35 +0100
Subject: [PATCH 200/696] get saved data and alert when data changed
---
.../admin/templates/admin/index.html | 18 +++++++-----------
lnbits/extensions/admin/views_api.py | 5 +++--
2 files changed, 10 insertions(+), 13 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 4754656d..4e401cb4 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -3,7 +3,7 @@
-
+
{
+ this.settings = response.data.settings
+ this.formData = _.clone(this.settings)
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index c8120564..c2079e37 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -43,8 +43,9 @@ async def api_update_settings(
user: User = Depends(check_admin),
data: UpdateSettings = Body(...),
):
- await update_settings(data)
- return {"status": "Success"}
+ settings = await update_settings(data)
+ logger.debug(settings)
+ return {"status": "Success", "settings": settings.dict()}
@admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK)
From 001f6589bb3c173c9ada0f86ec0a2eca08f3b051 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 10 Oct 2022 12:23:19 +0100
Subject: [PATCH 201/696] cleanup and typing fix for data
---
lnbits/extensions/admin/models.py | 12 +++++-----
.../admin/templates/admin/index.html | 22 ++-----------------
lnbits/extensions/admin/views_api.py | 1 -
3 files changed, 9 insertions(+), 26 deletions(-)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 45cd990d..94fa56bb 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -1,13 +1,15 @@
+from typing import List
+
from fastapi import Query
from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: str = Query(None) #this should be List[str] ??
- lnbits_allowed_users: str = Query(None) #this should be List[str] ??
- lnbits_admin_ext: str = Query(None) #this should be List[str] ??
- lnbits_disabled_ext: str = Query(None) #this should be List[str] ??
+ lnbits_admin_users: List[str] = Query(None)
+ lnbits_allowed_users: List[str] = Query(None)
+ lnbits_admin_ext: List[str] = Query(None)
+ lnbits_disabled_ext: List[str] = Query(None)
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -21,4 +23,4 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: str = Query(None) #this should be List[str] ??
+ lnbits_ad_space: List[str] = Query(None)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 4e401cb4..d8111595 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -209,26 +209,8 @@
})
},
updateSettings() {
- let data = {
- lnbits_backend_wallet_class: this.formData.lnbits_backend_wallet_class,
- lnbits_admin_users: this.formData.lnbits_admin_users.toString(),
- lnbits_allowed_users: this.formData.lnbits_allowed_users.toString(),
- lnbits_admin_ext: this.formData.lnbits_admin_ext,
- lnbits_disabled_ext: this.formData.lnbits_disabled_ext,
- lnbits_funding_source: this.formData.lnbits_funding_source,
- lnbits_force_https: this.formData.lnbits_force_https,
- lnbits_reserve_fee_min: this.formData.lnbits_reserve_fee_min,
- lnbits_reserve_fee_percent: this.formData.lnbits_reserve_fee_percent,
- lnbits_service_fee: this.formData.lnbits_service_fee,
- lnbits_hide_api: this.formData.lnbits_hide_api,
- lnbits_site_title: this.formData.lnbits_site_title,
- lnbits_site_tagline: this.formData.lnbits_site_tagline,
- lnbits_site_description: this.formData.lnbits_site_description,
- lnbits_default_wallet_name: this.formData.lnbits_default_wallet_name,
- lnbits_denomination: this.formData.lnbits_denomination,
- lnbits_theme: this.formData.lnbits_theme,
- lnbits_custom_logo: this.formData.lnbits_custom_logo,
- lnbits_ad_space: this.formData.lnbits_ad_space.toString()
+ let data = {
+ ...this.formData
}
LNbits.api
.request(
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index c2079e37..19b52e35 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -44,7 +44,6 @@ async def api_update_settings(
data: UpdateSettings = Body(...),
):
settings = await update_settings(data)
- logger.debug(settings)
return {"status": "Success", "settings": settings.dict()}
From 1cc54ff4b7e19366499743fc9cb881bb58ccf2c6 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Wed, 12 Oct 2022 19:04:46 +0100
Subject: [PATCH 202/696] add funding sources options
---
lnbits/app.py | 1 +
lnbits/extensions/admin/models.py | 41 +++-
.../admin/templates/admin/_tab_funding.html | 25 +-
.../admin/templates/admin/index.html | 217 +++++++++++++++++-
4 files changed, 259 insertions(+), 25 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index a8371950..50f218b7 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -84,6 +84,7 @@ async def check_funding_source() -> None:
def signal_handler(signal, frame):
logger.debug(f"SIGINT received, terminating LNbits.")
sys.exit(1)
+
signal.signal(signal.SIGINT, signal_handler)
WALLET = get_wallet_class()
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 94fa56bb..d9d2b22f 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -6,10 +6,10 @@ from pydantic import BaseModel
class UpdateSettings(BaseModel):
lnbits_backend_wallet_class: str = Query(None)
- lnbits_admin_users: List[str] = Query(None)
- lnbits_allowed_users: List[str] = Query(None)
- lnbits_admin_ext: List[str] = Query(None)
- lnbits_disabled_ext: List[str] = Query(None)
+ lnbits_admin_users: List[str] = Query(None)
+ lnbits_allowed_users: List[str] = Query(None)
+ lnbits_admin_ext: List[str] = Query(None)
+ lnbits_disabled_ext: List[str] = Query(None)
lnbits_funding_source: str = Query(None)
lnbits_force_https: bool = Query(None)
lnbits_reserve_fee_min: int = Query(None, ge=0)
@@ -23,4 +23,35 @@ class UpdateSettings(BaseModel):
lnbits_denomination: str = Query(None)
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
- lnbits_ad_space: List[str] = Query(None)
+ lnbits_ad_space: List[str] = Query(None)
+
+ # funding sources
+ fake_wallet_secret: str = Query(None)
+ lnbits_endpoint: str = Query(None)
+ lnbits_key: str = Query(None)
+ cliche_endpoint: str = Query(None)
+ corelightning_rpc: str = Query(None)
+ eclair_url: str = Query(None)
+ eclair_pass: str = Query(None)
+ lnd_rest_endpoint: str = Query(None)
+ lnd_rest_cert: str = Query(None)
+ lnd_rest_macaroon: str = Query(None)
+ lnd_rest_macaroon_encrypted: str = Query(None)
+ lnd_cert: str = Query(None)
+ lnd_admin_macaroon: str = Query(None)
+ lnd_invoice_macaroon: str = Query(None)
+ lnd_grpc_endpoint: str = Query(None)
+ lnd_grpc_cert: str = Query(None)
+ lnd_grpc_port: int = Query(None, ge=0)
+ lnd_grpc_admin_macaroon: str = Query(None)
+ lnd_grpc_invoice_macaroon: str = Query(None)
+ lnd_grpc_macaroon_encrypted: str = Query(None)
+ lnpay_api_endpoint: str = Query(None)
+ lnpay_api_key: str = Query(None)
+ lnpay_wallet_key: str = Query(None)
+ lntxbot_api_endpoint: str = Query(None)
+ lntxbot_key: str = Query(None)
+ opennode_api_endpoint: str = Query(None)
+ opennode_key: str = Query(None)
+ spark_url: str = Query(None)
+ spark_token: str = Query(None)
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
index 8b5456f1..a523d4e5 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_funding.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -8,9 +8,7 @@
Funding Source Info
{%raw%}
-
- Funding Source: {{settings.lnbits_backend_wallet_class}}
-
+ Funding Source: {{settings.lnbits_backend_wallet_class}}
Balance: {{balance / 1000}} sats
{%endraw%}
@@ -60,21 +58,30 @@
- Funding Sources
+ Funding Sources (Requires server restart)
-
+
-
-
+
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index d8111595..ccaddda4 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -3,7 +3,13 @@
-
+
u !== user
- )
+ this.settings.lnbits_admin_users = admin_users.filter(u => u !== user)
},
addAllowedUser() {
let addUser = this.formData.allowed_users_add
@@ -155,7 +335,9 @@
},
removeAllowedUser(user) {
let allowed_users = this.settings.lnbits_allowed_users
- this.settings.lnbits_allowed_users = allowed_users.filter(u => u !== user)
+ this.settings.lnbits_allowed_users = allowed_users.filter(
+ u => u !== user
+ )
},
addAdSpace() {
let adSpace = this.formData.ad_space_add
@@ -208,8 +390,19 @@
LNbits.utils.notifyApiError(error)
})
},
+ updateFundingData(){
+ this.settings.lnbits_allowed_funding_sources.map(f => {
+ let opts = this.funding_sources.get(f)
+ if (!opts) return
+
+ Object.keys(opts).forEach(e => {
+ opts[e].value = this.settings[e]
+ })
+ })
+ console.log("funding", this.funding_sources)
+ },
updateSettings() {
- let data = {
+ let data = {
...this.formData
}
LNbits.api
@@ -222,11 +415,13 @@
.then(response => {
this.settings = response.data.settings
this.formData = _.clone(this.settings)
+ this.updateFundingData()
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
icon: null
})
+ console.log(this.settings)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -262,7 +457,7 @@
LNbits.utils.notifyApiError(error)
})
}
- },
+ }
})
{% endblock %}
From 761fc427defc590913124f1ad2f1b518633fbad4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Mon, 10 Oct 2022 23:27:46 +0200
Subject: [PATCH 203/696] add callback for saas app
---
lnbits/app.py | 2 +-
lnbits/extensions/admin/migrations.py | 3 +++
lnbits/extensions/admin/views_api.py | 5 -----
lnbits/settings.py | 32 +++++++++++++++++++++++----
4 files changed, 32 insertions(+), 10 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 50f218b7..49ad8d77 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -64,7 +64,7 @@ def create_app() -> FastAPI:
# TODO: why those 2?
g().config = settings
- # g().base_url = f"http://{settings.host}:{settings.port}"
+ g().base_url = f"http://{settings.host}:{settings.port}"
app.add_middleware(GZipMiddleware, minimum_size=1000)
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index c4bc98d8..ea698c27 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -6,6 +6,9 @@ async def m001_create_admin_settings_table(db):
debug TEXT,
host TEXT,
port INTEGER,
+ lnbits_saas_instance_id TEXT,
+ lnbits_saas_callback TEXT,
+ lnbits_saas_secret TEXT,
lnbits_path TEXT,
lnbits_commit TEXT,
lnbits_admin_users TEXT,
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 19b52e35..ae2959bc 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -53,8 +53,3 @@ async def api_delete_settings(
):
await delete_settings()
return {"status": "Success"}
-
-
-@admin_ext.get("/api/v1/backup/", status_code=HTTPStatus.OK)
-async def api_backup(user: User = Depends(check_admin)):
- return {"status": "not implemented"}
diff --git a/lnbits/settings.py b/lnbits/settings.py
index ffcdcc0a..f183211c 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,6 +1,7 @@
import importlib
import json
import subprocess
+import httpx
from os import path
from sqlite3 import Row
from typing import List, Optional
@@ -9,6 +10,7 @@ from loguru import logger
from pydantic import BaseSettings, Field, validator
+
def list_parse_fallback(v):
try:
return json.loads(v)
@@ -34,6 +36,11 @@ class Settings(BaseSettings):
lnbits_path: str = Field(default=".")
lnbits_commit: str = Field(default="unknown")
+ # saas
+ lnbits_saas_callback: Optional[str] = Field(default=None)
+ lnbits_saas_secret: Optional[str] = Field(default=None)
+ lnbits_saas_instance_id: Optional[str] = Field(default=None)
+
# users
lnbits_admin_users: List[str] = Field(default=[])
lnbits_allowed_users: List[str] = Field(default=[])
@@ -230,11 +237,28 @@ async def check_admin_settings():
http = "https" if settings.lnbits_force_https else "http"
user = settings.lnbits_admin_users[0]
- logger.warning(
- f" ✔️ Access admin user account at: {http}://{settings.host}:{settings.port}/wallet?usr={user}"
- )
+
+ admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
+ logger.warning(f"✔️ Access admin user account at: {admin_url}")
+
+ if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id:
+ with httpx.Client() as client:
+ headers = {
+ "Content-Type": "application/json; charset=utf-8",
+ "X-API-KEY": settings.lnbits_saas_secret
+ }
+ payload = {
+ "instance_id": settings.lnbits_saas_instance_id,
+ "adminuser": user
+ }
+ try:
+ r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload)
+ logger.warning("sent admin user to saas application")
+ except:
+ logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}")
+
except:
- logger.warning("admin.settings tables does not exist.")
+ logger.error("admin.settings tables does not exist.")
raise
From 620fd2569655aa0f45b486201bcac75654f28326 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Wed, 12 Oct 2022 13:08:59 +0200
Subject: [PATCH 204/696] bugfixes and fix topup wallet
---
.../admin/templates/admin/index.html | 6 ++--
lnbits/extensions/admin/views_api.py | 36 ++++++++++---------
lnbits/server.py | 1 +
lnbits/settings.py | 29 ++++++++++-----
lnbits/wallets/fake.py | 2 +-
5 files changed, 44 insertions(+), 30 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index ccaddda4..575b377f 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -369,10 +369,10 @@
topupWallet() {
LNbits.api
.request(
- 'POST',
+ 'PUT',
'/admin/api/v1/topup/?usr=' + this.g.user.id,
- this.wallet.id,
- this.wallet.amount
+ this.g.user.wallets[0].adminkey,
+ this.wallet
)
.then(response => {
this.$q.notify({
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index ae2959bc..63ed5b3c 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,7 +1,6 @@
from http import HTTPStatus
-from fastapi import Body, Depends, Request
-from loguru import logger
+from fastapi import Body, Depends, Query
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_wallet
@@ -9,47 +8,50 @@ from lnbits.core.models import User
from lnbits.decorators import check_admin
from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import UpdateSettings
-from lnbits.requestvars import g
from lnbits.server import server_restart
-from lnbits.settings import settings
from .crud import delete_settings, update_settings, update_wallet_balance
-@admin_ext.get("/api/v1/restart/", status_code=HTTPStatus.OK)
-async def api_restart_server(user: User = Depends(check_admin)):
+@admin_ext.get(
+ "/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
+async def api_restart_server() -> dict[str, str]:
server_restart.set()
return {"status": "Success"}
-@admin_ext.put("/api/v1/topup/", status_code=HTTPStatus.OK)
+@admin_ext.put(
+ "/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
async def api_update_balance(
- wallet_id, topup_amount: int, user: User = Depends(check_admin)
-):
+ id: str = Body(...), amount: int = Body(...)
+) -> dict[str, str]:
try:
- wallet = await get_wallet(wallet_id)
+ await get_wallet(id)
except:
raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="wallet does not exist."
)
- await update_wallet_balance(wallet_id=wallet_id, amount=int(topup_amount))
+ await update_wallet_balance(wallet_id=id, amount=int(amount))
return {"status": "Success"}
-@admin_ext.put("/api/v1/settings/", status_code=HTTPStatus.OK)
+@admin_ext.put(
+ "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
async def api_update_settings(
- user: User = Depends(check_admin),
data: UpdateSettings = Body(...),
):
settings = await update_settings(data)
return {"status": "Success", "settings": settings.dict()}
-@admin_ext.delete("/api/v1/settings/", status_code=HTTPStatus.OK)
-async def api_delete_settings(
- user: User = Depends(check_admin),
-):
+@admin_ext.delete(
+ "/api/v1/settings/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
+)
+async def api_delete_settings() -> dict[str, str]:
await delete_settings()
return {"status": "Success"}
diff --git a/lnbits/server.py b/lnbits/server.py
index 79af8112..6d4cd2e7 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -52,6 +52,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload:
port=port,
host=host,
reload=reload,
+ forwarded_allow_ips="*",
ssl_keyfile=ssl_keyfile,
ssl_certfile=ssl_certfile,
**d
diff --git a/lnbits/settings.py b/lnbits/settings.py
index f183211c..61dbd6f2 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -1,20 +1,19 @@
import importlib
import json
import subprocess
-import httpx
from os import path
from sqlite3 import Row
from typing import List, Optional
+import httpx
from loguru import logger
from pydantic import BaseSettings, Field, validator
-
def list_parse_fallback(v):
try:
return json.loads(v)
- except Exception as e:
+ except Exception:
replaced = v.replace(" ", "")
if replaced:
return replaced.split(",")
@@ -238,24 +237,36 @@ async def check_admin_settings():
http = "https" if settings.lnbits_force_https else "http"
user = settings.lnbits_admin_users[0]
- admin_url = f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
+ admin_url = (
+ f"{http}://{settings.host}:{settings.port}/wallet?usr={user}"
+ )
logger.warning(f"✔️ Access admin user account at: {admin_url}")
- if settings.lnbits_saas_callback and settings.lnbits_saas_secret and settings.lnbits_saas_instance_id:
+ if (
+ settings.lnbits_saas_callback
+ and settings.lnbits_saas_secret
+ and settings.lnbits_saas_instance_id
+ ):
with httpx.Client() as client:
headers = {
"Content-Type": "application/json; charset=utf-8",
- "X-API-KEY": settings.lnbits_saas_secret
+ "X-API-KEY": settings.lnbits_saas_secret,
}
payload = {
"instance_id": settings.lnbits_saas_instance_id,
- "adminuser": user
+ "adminuser": user,
}
try:
- r = client.post(settings.lnbits_saas_callback, headers=headers, json=payload)
+ client.post(
+ settings.lnbits_saas_callback,
+ headers=headers,
+ json=payload,
+ )
logger.warning("sent admin user to saas application")
except:
- logger.error(f"error sending admin user to saas: {settings.lnbits_saas_callback}")
+ logger.error(
+ f"error sending admin user to saas: {settings.lnbits_saas_callback}"
+ )
except:
logger.error("admin.settings tables does not exist.")
diff --git a/lnbits/wallets/fake.py b/lnbits/wallets/fake.py
index 73458e8c..94ff5f48 100644
--- a/lnbits/wallets/fake.py
+++ b/lnbits/wallets/fake.py
@@ -19,7 +19,6 @@ from .base import (
class FakeWallet(Wallet):
- queue: asyncio.Queue = asyncio.Queue(0)
secret: str = settings.fake_wallet_secret
privkey: str = hashlib.pbkdf2_hmac(
"sha256",
@@ -98,6 +97,7 @@ class FakeWallet(Wallet):
return PaymentStatus(None)
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
+ self.queue: asyncio.Queue = asyncio.Queue(0)
while True:
value: Invoice = await self.queue.get()
yield value.payment_hash
From 9dbcd89c6ebdba4db6c0bc7d70d570a8ee5fffaa Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 13 Oct 2022 15:52:02 +0100
Subject: [PATCH 205/696] added tooltips, moved reset, and warnings
---
.../admin/templates/admin/index.html | 97 ++++++++++++-------
1 file changed, 61 insertions(+), 36 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 575b377f..7d268301 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -1,8 +1,14 @@
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
%} {% block page %}
-
-
+
+
+ Save your changes
-
-
-
+
+
+ Restart the server for changes to take effect
+
+
+
+
+ Add funds to a wallet.
+
+ > -->
+
+ Delete all settings and reset to defaults.
+
@@ -121,6 +136,7 @@
show: false
},
tab: 'funding',
+ needsRestart: false,
funding_sources: new Map([
['VoidWallet', null],
[
@@ -302,13 +318,12 @@
this.balance = +'{{ balance|safe }}'
this.formData = _.clone(this.settings) //model
this.updateFundingData()
-
console.log(this.settings)
},
computed: {
checkChanges() {
return !_.isEqual(this.settings, this.formData)
- },
+ }
},
methods: {
addAdminUser() {
@@ -361,6 +376,7 @@
message: 'Success! Restarted Server',
icon: null
})
+ this.needsRestart = false
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -390,16 +406,15 @@
LNbits.utils.notifyApiError(error)
})
},
- updateFundingData(){
+ updateFundingData() {
this.settings.lnbits_allowed_funding_sources.map(f => {
let opts = this.funding_sources.get(f)
if (!opts) return
-
+
Object.keys(opts).forEach(e => {
opts[e].value = this.settings[e]
})
})
- console.log("funding", this.funding_sources)
},
updateSettings() {
let data = {
@@ -415,31 +430,41 @@
.then(response => {
this.settings = response.data.settings
this.formData = _.clone(this.settings)
+ this.needsRestart = true
this.updateFundingData()
this.$q.notify({
type: 'positive',
message: 'Success! Settings changed!',
icon: null
})
- console.log(this.settings)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
deleteSettings() {
- LNbits.api
- .request('DELETE', '/admin/api/v1/settings/?usr=' + this.g.user.id)
- .then(response => {
- this.$q.notify({
- type: 'positive',
- message:
- 'Success! Restored settings to defaults, restart required!',
- icon: null
- })
- })
- .catch(function (error) {
- LNbits.utils.notifyApiError(error)
+ LNbits.utils
+ .confirmDialog(
+ 'Are you sure you want to restore settings to default?'
+ )
+ .onOk(() => {
+ LNbits.api
+ .request(
+ 'DELETE',
+ '/admin/api/v1/settings/?usr=' + this.g.user.id
+ )
+ .then(response => {
+ this.$q.notify({
+ type: 'positive',
+ message:
+ 'Success! Restored settings to defaults, restart required!',
+ icon: null
+ })
+ this.needsRestart = true
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
})
},
downloadBackup() {
From 31a7a87774e5fcd87962440bc8c20510f607a328 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Thu, 13 Oct 2022 15:52:26 +0100
Subject: [PATCH 206/696] moved to columns
---
.../admin/templates/admin/_tab_funding.html | 54 +++++++++----------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/_tab_funding.html b/lnbits/extensions/admin/templates/admin/_tab_funding.html
index a523d4e5..a69ecb47 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_funding.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_funding.html
@@ -27,38 +27,38 @@
:options="settings.lnbits_allowed_funding_sources"
>
-
+
-
-
-
- Funding Sources (Requires server restart)
+
+ Funding Sources (Requires server restart)
+
Date: Thu, 13 Oct 2022 15:52:39 +0100
Subject: [PATCH 207/696] themes not displaying fixed
---
lnbits/extensions/admin/templates/admin/_tab_theme.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/admin/templates/admin/_tab_theme.html b/lnbits/extensions/admin/templates/admin/_tab_theme.html
index 46bf83e9..c327733f 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_theme.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_theme.html
@@ -63,7 +63,7 @@
Themes
Date: Fri, 21 Oct 2022 10:00:47 +0200
Subject: [PATCH 208/696] add loop to uvicorn
---
lnbits/server.py | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/lnbits/server.py b/lnbits/server.py
index 6d4cd2e7..eb7c12b1 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -1,12 +1,7 @@
-import asyncio
-
import uvloop
-
uvloop.install()
-import contextlib
import multiprocessing as mp
-import sys
import time
import click
@@ -49,6 +44,7 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload:
while True:
config = uvicorn.Config(
"lnbits.__main__:app",
+ loop="uvloop",
port=port,
host=host,
reload=reload,
@@ -65,9 +61,10 @@ def main(ctx, port: int, host: str, ssl_keyfile: str, ssl_certfile: str, reload:
server_restart.clear()
server.should_exit = True
server.force_exit = True
+ time.sleep(3)
process.terminate()
process.join()
- time.sleep(3)
+ time.sleep(1)
server_restart = mp.Event()
From b14b9f3b3a48f5a2544864783de7129e8ae5bfb3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Fri, 21 Oct 2022 11:13:40 +0200
Subject: [PATCH 209/696] add get settings endpoint with only values you can
also save
---
lnbits/app.py | 6 +----
lnbits/extensions/admin/crud.py | 12 +++++++++-
lnbits/extensions/admin/models.py | 6 ++++-
.../admin/templates/admin/index.html | 22 +++++++++++++++----
lnbits/extensions/admin/views_api.py | 7 +++++-
lnbits/server.py | 1 +
6 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/lnbits/app.py b/lnbits/app.py
index 49ad8d77..959a8168 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -62,10 +62,6 @@ def create_app() -> FastAPI:
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
- # TODO: why those 2?
- g().config = settings
- g().base_url = f"http://{settings.host}:{settings.port}"
-
app.add_middleware(GZipMiddleware, minimum_size=1000)
register_startup(app)
@@ -174,7 +170,7 @@ def register_assets(app: FastAPI):
@app.on_event("startup")
async def vendored_assets_variable():
- if g().config.debug:
+ if settings.debug:
g().VENDORED_JS = map(url_for_vendored, get_js_vendored())
g().VENDORED_CSS = map(url_for_vendored, get_css_vendored())
else:
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index cc937b5e..2ce91612 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -6,7 +6,7 @@ from lnbits.settings import Settings, read_only_variables
from lnbits.tasks import internal_invoice_queue
from . import db
-from .models import UpdateSettings
+from .models import AdminSettings, UpdateSettings
async def update_wallet_balance(wallet_id: str, amount: int) -> str:
@@ -26,6 +26,16 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> str:
await internal_invoice_queue.put(internal_id)
+async def get_settings() -> AdminSettings:
+ row = await db.fetchone("SELECT * FROM admin.settings")
+ all_settings = Settings(**row)
+ settings = AdminSettings()
+ for key, value in row.items():
+ if hasattr(settings, key):
+ setattr(settings, key, getattr(all_settings, key))
+ return settings
+
+
async def update_settings(data: UpdateSettings) -> Settings:
fields = []
for key, value in data.dict(exclude_none=True).items():
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index d9d2b22f..31811659 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import List, Optional
from fastapi import Query
from pydantic import BaseModel
@@ -55,3 +55,7 @@ class UpdateSettings(BaseModel):
opennode_key: str = Query(None)
spark_url: str = Query(None)
spark_token: str = Query(None)
+
+
+class AdminSettings(UpdateSettings):
+ lnbits_allowed_funding_sources: Optional[List[str]]
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 7d268301..10391261 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -314,11 +314,8 @@
}
},
created: function () {
- this.settings = JSON.parse('{{ settings|tojson|safe }}') //DB data
+ this.getSettings()
this.balance = +'{{ balance|safe }}'
- this.formData = _.clone(this.settings) //model
- this.updateFundingData()
- console.log(this.settings)
},
computed: {
checkChanges() {
@@ -416,6 +413,23 @@
})
})
},
+ getSettings() {
+ LNbits.api
+ .request(
+ 'GET',
+ '/admin/api/v1/settings/?usr=' + this.g.user.id,
+ this.g.user.wallets[0].adminkey
+ )
+ .then(response => {
+ this.settings = response.data
+ this.formData = _.clone(this.settings)
+ this.updateFundingData()
+ console.log(this.settings)
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
updateSettings() {
let data = {
...this.formData
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 63ed5b3c..57d62ed4 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -10,7 +10,7 @@ from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import UpdateSettings
from lnbits.server import server_restart
-from .crud import delete_settings, update_settings, update_wallet_balance
+from .crud import delete_settings, get_settings, update_settings, update_wallet_balance
@admin_ext.get(
@@ -21,6 +21,11 @@ async def api_restart_server() -> dict[str, str]:
return {"status": "Success"}
+@admin_ext.get("/api/v1/settings/", dependencies=[Depends(check_admin)])
+async def api_get_settings() -> UpdateSettings:
+ return await get_settings()
+
+
@admin_ext.put(
"/api/v1/topup/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
)
diff --git a/lnbits/server.py b/lnbits/server.py
index eb7c12b1..ecf7ff62 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -1,4 +1,5 @@
import uvloop
+
uvloop.install()
import multiprocessing as mp
From 8fbf10909961c13b6c39008ae4ce3aaa750b9a51 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 25 Oct 2022 09:25:37 +0200
Subject: [PATCH 210/696] fix poetry lockCCC
---
poetry.lock | 24 +-----------------------
1 file changed, 1 insertion(+), 23 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 45968390..bbce3c5c 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -258,24 +258,6 @@ category = "main"
optional = false
python-versions = "*"
-[[package]]
-name = "environs"
-version = "9.3.3"
-description = "simplified environment variable parsing"
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-marshmallow = ">=3.0.0"
-python-dotenv = "*"
-
-[package.extras]
-dev = ["dj-database-url", "dj-email-url", "django-cache-url", "flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)", "pytest", "tox"]
-django = ["dj-database-url", "dj-email-url", "django-cache-url"]
-lint = ["flake8 (==3.9.2)", "flake8-bugbear (==21.4.3)", "mypy (==0.910)", "pre-commit (>=2.4,<3.0)"]
-tests = ["dj-database-url", "dj-email-url", "django-cache-url", "pytest"]
-
[[package]]
name = "fastapi"
version = "0.78.0"
@@ -1051,7 +1033,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (
[metadata]
lock-version = "1.1"
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
-content-hash = "c4a01d5bfc24a8008348b6bd954717354554310afaaecbfc2a14222ad25aca42"
+content-hash = "e798b36b5941b43ee249bc196fcfb28d8ee712947336d21467651c672ba0106b"
[metadata.files]
aiofiles = [
@@ -1307,10 +1289,6 @@ enum34 = [
{file = "enum34-1.1.10-py3-none-any.whl", hash = "sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328"},
{file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"},
]
-environs = [
- {file = "environs-9.3.3-py2.py3-none-any.whl", hash = "sha256:ee5466156b50fe03aa9fec6e720feea577b5bf515d7f21b2c46608272557ba26"},
- {file = "environs-9.3.3.tar.gz", hash = "sha256:72b867ff7b553076cdd90f3ee01ecc1cf854987639c9c459f0ed0d3d44ae490c"},
-]
fastapi = [
{file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
{file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
From 678c269a9124a967300a34d75542ed0c0c35150a Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 18 Nov 2022 16:57:39 +0100
Subject: [PATCH 211/696] wipe card
---
.../extensions/boltcards/static/js/index.js | 16 ++-
.../boltcards/templates/boltcards/index.html | 102 ++++++++++++++----
2 files changed, 97 insertions(+), 21 deletions(-)
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index e13c14fb..1949050b 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -150,6 +150,7 @@ new Vue({
},
qrCodeDialog: {
show: false,
+ wipe: false,
data: null
}
}
@@ -260,9 +261,10 @@ new Vue({
})
})
},
- openQrCodeDialog(cardId) {
+ openQrCodeDialog(cardId, wipe) {
var card = _.findWhere(this.cards, {id: cardId})
this.qrCodeDialog.data = {
+ id: card.id,
link: window.location.origin + '/boltcards/api/v1/auth?a=' + card.otp,
name: card.card_name,
uid: card.uid,
@@ -274,6 +276,18 @@ new Vue({
k4: card.k2,
webhook_url: card.webhook_url
}
+ this.qrCodeDialog.data_wipe = JSON.stringify({
+ action: 'wipe',
+ id: 1,
+ k0: card.k0,
+ k1: card.k1,
+ k2: card.k2,
+ k3: card.k1,
+ k4: card.k2,
+ uid: card.uid,
+ version: 1
+ })
+ this.qrCodeDialog.wipe = wipe
this.qrCodeDialog.show = true
},
addCardOpen: function () {
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index 7b9713e2..b80e9685 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -48,6 +48,7 @@
+
@@ -58,7 +59,7 @@
dense
icon="qr_code"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
- @click="openQrCodeDialog(props.row.id)"
+ @click="openQrCodeDialog(props.row.id, false)"
>
Card key credentials
@@ -99,7 +100,7 @@
flat
dense
size="xs"
- @click="deleteCard(props.row.id)"
+ @click="openQrCodeDialog(props.row.id, true)"
icon="cancel"
color="pink"
>
@@ -367,21 +368,61 @@
{% raw %}
-
-
-
-
- (Keys for
- Boltcard NFC Card Creator )
-
+
+
+
+
+
+ (QR for create the card in
+ Boltcard NFC Card Creator )
+
+
+
+
+
+ (QR for for wipe the card in
+ Boltcard NFC Card Creator )
+
+
+
+
+
+
Name: {{ qrCodeDialog.data.name }}
UID: {{ qrCodeDialog.data.uid }}
@@ -398,11 +439,32 @@
outline
color="grey"
@click="copyText(qrCodeDialog.data.link)"
- label="Keys/Auth link"
+ label="Create link"
+ v-show="!qrCodeDialog.wipe"
>
+ Click to copy, then paste to NFC Card Creator
+
+
+ Click to copy, then paste to NFC Card Creator
+
+
+ Backup the keys, or wipe the card first!
- Click to copy, then add to NFC card
-
{% endraw %}
Close
From 8d05cddc877f2e00a7c542c034a91a6c13db5493 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Sat, 19 Nov 2022 13:50:53 +0100
Subject: [PATCH 212/696] wipe card readme
---
lnbits/extensions/boltcards/README.md | 17 +++++++++++++++--
lnbits/extensions/boltcards/static/js/index.js | 1 -
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index 5140ecc2..f612ae2f 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -21,7 +21,7 @@ The key #00, K0 (also know as auth key) is skipped to be used as authentificatio
***Always backup all keys that you're trying to write on the card. Without them you may not be able to change them in the future!***
## Setting the card - Boltcard NFC Card Creator (easy way)
-Updated for v0.1.1
+Updated for v0.1.2
- Add new card in the extension.
- Set a max sats per transaction. Any transaction greater than this amount will be rejected.
@@ -38,7 +38,20 @@ Updated for v0.1.1
- Click the QR code button next to a card to view its details. Backup the keys now! They'll be comfortable in your password manager.
- Now you can scan the QR code with the Android app (Create Bolt Card -> SCAN QR CODE).
- Or you can Click the "KEYS / AUTH LINK" button to copy the auth URL to the clipboard. Then paste it into the Android app (Create Bolt Card -> PASTE AUTH URL).
-- Click WRITE CARD NOW and tap the NFC card to set it up. DO NOT REMOVE THE CARD PREMATURELY!
+- Click WRITE CARD NOW and approach the NFC card to set it up. DO NOT REMOVE THE CARD PREMATURELY!
+
+## Erasing the card - Boltcard NFC Card Creator
+Updated for v0.1.2
+
+Since v0.1.2 of Boltcard NFC Card Creator it is possible not only reset the keys but also disable the SUN function and do the complete erase so the card can be use again as a static tag (or set as a new Bolt Card, ofc).
+
+- Click the QR code button next to a card to view its details and select WIPE
+- OR click the red cross icon on the right side to reach the same
+- In the android app (Advanced -> Reset Keys)
+ - Click SCAN QR CODE to scan the QR
+ - Or click WIPE DATA in LNbits to copy and paste in to the app (PASTE KEY JSON)
+- Click RESET CARD NOW and approach the NFC card to erase it. DO NOT REMOVE THE CARD PREMATURELY!
+- Now if there is all success the card can be safely delete from LNbits (but keep the keys backuped anyway; batter safe than brick).
## Setting the card - computer (hard way)
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index 1949050b..95a95afc 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -278,7 +278,6 @@ new Vue({
}
this.qrCodeDialog.data_wipe = JSON.stringify({
action: 'wipe',
- id: 1,
k0: card.k0,
k1: card.k1,
k2: card.k2,
From 95ad55775df5ad625997b568662f086d42deb13b Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Sun, 20 Nov 2022 09:51:54 +0100
Subject: [PATCH 213/696] fixes
---
.../boltcards/templates/boltcards/index.html | 37 ++++++++++---------
1 file changed, 19 insertions(+), 18 deletions(-)
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index b80e9685..15af9db5 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -379,16 +379,16 @@
:options="{width: 800}"
class="rounded-borders"
>
-
-
- (QR for create the card in
- Boltcard NFC Card Creator )
-
+
+ (QR for create the card in
+ Boltcard NFC Card Creator )
+
-
- (QR for for wipe the card in
- Boltcard NFC Card Creator )
-
+
+ (QR for for wipe the card in
+ Boltcard NFC Card Creator )
+
-
+
Date: Wed, 23 Nov 2022 21:47:26 +0000
Subject: [PATCH 214/696] Adds universal websocket manager any extension can
use
Connect to the `ws:///api/v1/ws/{item_id}` endpoint
POST data to the websocket with `https:///api/v1/ws/{item_id}`
---
lnbits/core/views/api.py | 46 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 45 insertions(+), 1 deletion(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 983d5a26..8f7c11a2 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -12,7 +12,7 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import async_timeout
import httpx
import pyqrcode
-from fastapi import Depends, Header, Query, Request
+from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect
from fastapi.exceptions import HTTPException
from fastapi.params import Body
from loguru import logger
@@ -697,3 +697,47 @@ async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)):
"delta_msats": delta,
"timestamp": int(time.time()),
}
+
+
+##################UNIVERSAL WEBSOCKET MANAGER########################
+
+class websocketConnectionManager:
+ def __init__(self):
+ self.active_connections: List[WebSocket] = []
+
+ async def connect(self, websocket: WebSocket, item_id: str):
+ await websocket.accept()
+ websocket.id = item_id
+ self.active_connections.append(websocket)
+
+ def disconnect(self, websocket: WebSocket):
+ self.active_connections.remove(websocket)
+
+ async def send_personal_message(self, message: str, item_id: str):
+ for connection in self.active_connections:
+ if connection.id == item_id:
+ await connection.send_text(message)
+
+ async def broadcast(self, message: str):
+ for connection in self.active_connections:
+ await connection.send_text(message)
+
+manager = websocketConnectionManager()
+
+@core_app.websocket("/api/v1/ws/{item_id}")
+async def websocket_endpoint(websocket: WebSocket, item_id: str):
+ await manager.connect(websocket, item_id)
+ try:
+ while True:
+ data = await websocket.receive_text()
+ except WebSocketDisconnect:
+ manager.disconnect(websocket)
+
+@core_app.post("/api/v1/ws/{item_id}")
+async def websocket_endpoint(item_id: str, data: str):
+ await updater(item_id, data)
+
+async def updater(item_id, data):
+ return await manager.send_personal_message(
+ f"{data}", item_id
+ )
\ No newline at end of file
From 51ca515d2631ebe9008b74876cd69552296c286b Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 23 Nov 2022 21:51:32 +0000
Subject: [PATCH 215/696] renamed for clarity
---
lnbits/core/views/api.py | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 8f7c11a2..41adf1a7 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -713,15 +713,11 @@ class websocketConnectionManager:
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
- async def send_personal_message(self, message: str, item_id: str):
+ async def send_data(self, message: str, item_id: str):
for connection in self.active_connections:
if connection.id == item_id:
await connection.send_text(message)
- async def broadcast(self, message: str):
- for connection in self.active_connections:
- await connection.send_text(message)
-
manager = websocketConnectionManager()
@core_app.websocket("/api/v1/ws/{item_id}")
@@ -738,6 +734,6 @@ async def websocket_endpoint(item_id: str, data: str):
await updater(item_id, data)
async def updater(item_id, data):
- return await manager.send_personal_message(
+ return await manager.send_data(
f"{data}", item_id
)
\ No newline at end of file
From 4b707b5a30da4f81cbfae103859296eae7006746 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 23 Nov 2022 22:22:33 +0000
Subject: [PATCH 216/696] updated function names
---
lnbits/core/views/api.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 41adf1a7..60a26651 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -721,7 +721,7 @@ class websocketConnectionManager:
manager = websocketConnectionManager()
@core_app.websocket("/api/v1/ws/{item_id}")
-async def websocket_endpoint(websocket: WebSocket, item_id: str):
+async def websocket_connect(websocket: WebSocket, item_id: str):
await manager.connect(websocket, item_id)
try:
while True:
@@ -730,7 +730,7 @@ async def websocket_endpoint(websocket: WebSocket, item_id: str):
manager.disconnect(websocket)
@core_app.post("/api/v1/ws/{item_id}")
-async def websocket_endpoint(item_id: str, data: str):
+async def websocket_update(item_id: str, data: str):
await updater(item_id, data)
async def updater(item_id, data):
From 2f08255e9289a601e73ee10c24a603f7d24f43a5 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 23 Nov 2022 22:27:09 +0000
Subject: [PATCH 217/696] added get option
---
lnbits/core/views/api.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 60a26651..a35b55ad 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -733,6 +733,10 @@ async def websocket_connect(websocket: WebSocket, item_id: str):
async def websocket_update(item_id: str, data: str):
await updater(item_id, data)
+@core_app.get("/api/v1/ws/{item_id}/{data}")
+async def websocket_update(item_id: str, data: str):
+ await updater(item_id, data)
+
async def updater(item_id, data):
return await manager.send_data(
f"{data}", item_id
From 152991fbec6b78b1d9949119419cc00d199f3068 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 23 Nov 2022 22:31:11 +0000
Subject: [PATCH 218/696] added try for return
---
lnbits/core/views/api.py | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index a35b55ad..b7bc7084 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -731,11 +731,19 @@ async def websocket_connect(websocket: WebSocket, item_id: str):
@core_app.post("/api/v1/ws/{item_id}")
async def websocket_update(item_id: str, data: str):
- await updater(item_id, data)
+ try:
+ await updater(item_id, data)
+ return {"sent": True, "data": data}
+ except:
+ return {"sent": False, "data": data}
@core_app.get("/api/v1/ws/{item_id}/{data}")
async def websocket_update(item_id: str, data: str):
- await updater(item_id, data)
+ try:
+ await updater(item_id, data)
+ return {"sent": True, "data": data}
+ except:
+ return {"sent": False, "data": data}
async def updater(item_id, data):
return await manager.send_data(
From cea4f9350c574a0b274ec967b3f7f493cd8c6374 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 23 Nov 2022 22:42:32 +0000
Subject: [PATCH 219/696] black
---
lnbits/core/views/api.py | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index b7bc7084..2cc7aacb 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -701,6 +701,7 @@ async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)):
##################UNIVERSAL WEBSOCKET MANAGER########################
+
class websocketConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
@@ -718,8 +719,10 @@ class websocketConnectionManager:
if connection.id == item_id:
await connection.send_text(message)
+
manager = websocketConnectionManager()
+
@core_app.websocket("/api/v1/ws/{item_id}")
async def websocket_connect(websocket: WebSocket, item_id: str):
await manager.connect(websocket, item_id)
@@ -729,6 +732,7 @@ async def websocket_connect(websocket: WebSocket, item_id: str):
except WebSocketDisconnect:
manager.disconnect(websocket)
+
@core_app.post("/api/v1/ws/{item_id}")
async def websocket_update(item_id: str, data: str):
try:
@@ -737,6 +741,7 @@ async def websocket_update(item_id: str, data: str):
except:
return {"sent": False, "data": data}
+
@core_app.get("/api/v1/ws/{item_id}/{data}")
async def websocket_update(item_id: str, data: str):
try:
@@ -745,7 +750,6 @@ async def websocket_update(item_id: str, data: str):
except:
return {"sent": False, "data": data}
+
async def updater(item_id, data):
- return await manager.send_data(
- f"{data}", item_id
- )
\ No newline at end of file
+ return await manager.send_data(f"{data}", item_id)
From fde128e96116797016b90cd01cbda12ff90cad1e Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 23 Nov 2022 23:35:02 +0000
Subject: [PATCH 220/696] Better naming
---
lnbits/core/views/api.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 2cc7aacb..e6520659 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -720,23 +720,23 @@ class websocketConnectionManager:
await connection.send_text(message)
-manager = websocketConnectionManager()
+websocketManager = websocketConnectionManager()
@core_app.websocket("/api/v1/ws/{item_id}")
async def websocket_connect(websocket: WebSocket, item_id: str):
- await manager.connect(websocket, item_id)
+ await websocketManager.connect(websocket, item_id)
try:
while True:
data = await websocket.receive_text()
except WebSocketDisconnect:
- manager.disconnect(websocket)
+ websocketManager.disconnect(websocket)
@core_app.post("/api/v1/ws/{item_id}")
async def websocket_update(item_id: str, data: str):
try:
- await updater(item_id, data)
+ await websocketUpdater(item_id, data)
return {"sent": True, "data": data}
except:
return {"sent": False, "data": data}
@@ -745,11 +745,11 @@ async def websocket_update(item_id: str, data: str):
@core_app.get("/api/v1/ws/{item_id}/{data}")
async def websocket_update(item_id: str, data: str):
try:
- await updater(item_id, data)
+ await websocketUpdater(item_id, data)
return {"sent": True, "data": data}
except:
return {"sent": False, "data": data}
-async def updater(item_id, data):
- return await manager.send_data(f"{data}", item_id)
+async def websocketUpdater(item_id, data):
+ return await websocketManager.send_data(f"{data}", item_id)
From f876f0659f6387f57597a17484c9b11126515490 Mon Sep 17 00:00:00 2001
From: ben
Date: Thu, 24 Nov 2022 00:21:39 +0000
Subject: [PATCH 221/696] Moved into correct files, and added payment example
---
lnbits/core/services.py | 24 +++++++++++++++++++++++-
lnbits/core/tasks.py | 4 +++-
lnbits/core/views/api.py | 27 ++-------------------------
3 files changed, 28 insertions(+), 27 deletions(-)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index 5d993b4c..72a0c84b 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -6,7 +6,7 @@ from typing import Dict, Optional, Tuple
from urllib.parse import parse_qs, urlparse
import httpx
-from fastapi import Depends
+from fastapi import Depends, WebSocket, WebSocketDisconnect
from lnurl import LnurlErrorResponse
from lnurl import decode as decode_lnurl # type: ignore
from loguru import logger
@@ -382,3 +382,25 @@ async def check_transaction_status(
# WARN: this same value must be used for balance check and passed to WALLET.pay_invoice(), it may cause a vulnerability if the values differ
def fee_reserve(amount_msat: int) -> int:
return max(int(RESERVE_FEE_MIN), int(amount_msat * RESERVE_FEE_PERCENT / 100.0))
+
+class websocketConnectionManager:
+ def __init__(self):
+ self.active_connections: List[WebSocket] = []
+
+ async def connect(self, websocket: WebSocket, item_id: str):
+ await websocket.accept()
+ websocket.id = item_id
+ self.active_connections.append(websocket)
+
+ def disconnect(self, websocket: WebSocket):
+ self.active_connections.remove(websocket)
+
+ async def send_data(self, message: str, item_id: str):
+ for connection in self.active_connections:
+ if connection.id == item_id:
+ await connection.send_text(message)
+
+websocketManager = websocketConnectionManager()
+
+async def websocketUpdater(item_id, data):
+ return await websocketManager.send_data(f"{data}", item_id)
\ No newline at end of file
diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py
index b57e2625..01d04a60 100644
--- a/lnbits/core/tasks.py
+++ b/lnbits/core/tasks.py
@@ -10,6 +10,7 @@ from lnbits.tasks import SseListenersDict, register_invoice_listener
from . import db
from .crud import get_balance_notify
from .models import Payment
+from .services import websocketUpdater
api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict(
"api_invoice_listeners"
@@ -38,6 +39,7 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
logger.trace("received invoice paid event")
# send information to sse channel
await dispatch_api_invoice_listeners(payment)
+ await websocketUpdater(payment.wallet_id, payment)
# dispatch webhook
if payment.webhook and not payment.webhook_status:
@@ -88,4 +90,4 @@ async def mark_webhook_sent(payment: Payment, status: int) -> None:
WHERE hash = ?
""",
(status, payment.payment_hash),
- )
+ )
\ No newline at end of file
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index e6520659..3465ef24 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -56,6 +56,8 @@ from ..services import (
create_invoice,
pay_invoice,
perform_lnurlauth,
+ websocketManager,
+ websocketUpdater
)
from ..tasks import api_invoice_listeners
@@ -702,27 +704,6 @@ async def api_auditor(wallet: WalletTypeInfo = Depends(get_key_type)):
##################UNIVERSAL WEBSOCKET MANAGER########################
-class websocketConnectionManager:
- def __init__(self):
- self.active_connections: List[WebSocket] = []
-
- async def connect(self, websocket: WebSocket, item_id: str):
- await websocket.accept()
- websocket.id = item_id
- self.active_connections.append(websocket)
-
- def disconnect(self, websocket: WebSocket):
- self.active_connections.remove(websocket)
-
- async def send_data(self, message: str, item_id: str):
- for connection in self.active_connections:
- if connection.id == item_id:
- await connection.send_text(message)
-
-
-websocketManager = websocketConnectionManager()
-
-
@core_app.websocket("/api/v1/ws/{item_id}")
async def websocket_connect(websocket: WebSocket, item_id: str):
await websocketManager.connect(websocket, item_id)
@@ -749,7 +730,3 @@ async def websocket_update(item_id: str, data: str):
return {"sent": True, "data": data}
except:
return {"sent": False, "data": data}
-
-
-async def websocketUpdater(item_id, data):
- return await websocketManager.send_data(f"{data}", item_id)
From c2a737ab84ae4260dcd3a7a2cb93e62ade72e9da Mon Sep 17 00:00:00 2001
From: ben
Date: Thu, 24 Nov 2022 00:34:46 +0000
Subject: [PATCH 222/696] Black
---
lnbits/core/services.py | 5 ++++-
lnbits/core/tasks.py | 2 +-
lnbits/core/views/api.py | 4 ++--
3 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index 72a0c84b..b03562d9 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -383,6 +383,7 @@ async def check_transaction_status(
def fee_reserve(amount_msat: int) -> int:
return max(int(RESERVE_FEE_MIN), int(amount_msat * RESERVE_FEE_PERCENT / 100.0))
+
class websocketConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
@@ -400,7 +401,9 @@ class websocketConnectionManager:
if connection.id == item_id:
await connection.send_text(message)
+
websocketManager = websocketConnectionManager()
+
async def websocketUpdater(item_id, data):
- return await websocketManager.send_data(f"{data}", item_id)
\ No newline at end of file
+ return await websocketManager.send_data(f"{data}", item_id)
diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py
index 01d04a60..734c3f01 100644
--- a/lnbits/core/tasks.py
+++ b/lnbits/core/tasks.py
@@ -90,4 +90,4 @@ async def mark_webhook_sent(payment: Payment, status: int) -> None:
WHERE hash = ?
""",
(status, payment.payment_hash),
- )
\ No newline at end of file
+ )
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 3465ef24..9a359c0a 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -56,8 +56,8 @@ from ..services import (
create_invoice,
pay_invoice,
perform_lnurlauth,
- websocketManager,
- websocketUpdater
+ websocketManager,
+ websocketUpdater,
)
from ..tasks import api_invoice_listeners
From dce4da96b71eeaa932bce183e436975b6cfb2e1f Mon Sep 17 00:00:00 2001
From: ben
Date: Thu, 24 Nov 2022 00:46:39 +0000
Subject: [PATCH 223/696] fixed function name clash
---
lnbits/core/views/api.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 9a359c0a..65d7c03e 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -715,7 +715,7 @@ async def websocket_connect(websocket: WebSocket, item_id: str):
@core_app.post("/api/v1/ws/{item_id}")
-async def websocket_update(item_id: str, data: str):
+async def websocket_update_post(item_id: str, data: str):
try:
await websocketUpdater(item_id, data)
return {"sent": True, "data": data}
@@ -724,7 +724,7 @@ async def websocket_update(item_id: str, data: str):
@core_app.get("/api/v1/ws/{item_id}/{data}")
-async def websocket_update(item_id: str, data: str):
+async def websocket_update_get(item_id: str, data: str):
try:
await websocketUpdater(item_id, data)
return {"sent": True, "data": data}
From 7b9245e62e356488926003de83176379e93f2cef Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Thu, 24 Nov 2022 04:52:26 +0100
Subject: [PATCH 224/696] Update index.html
typo
---
lnbits/extensions/boltcards/templates/boltcards/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/boltcards/templates/boltcards/index.html b/lnbits/extensions/boltcards/templates/boltcards/index.html
index 7097a43f..aefb22c6 100644
--- a/lnbits/extensions/boltcards/templates/boltcards/index.html
+++ b/lnbits/extensions/boltcards/templates/boltcards/index.html
@@ -401,7 +401,7 @@
>
- (QR for for wipe the card in
+ (QR for wipe the card in
Date: Thu, 24 Nov 2022 11:35:03 +0100
Subject: [PATCH 225/696] readd global baseurl needed for lnurlp
---
lnbits/app.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lnbits/app.py b/lnbits/app.py
index 959a8168..3218f48a 100644
--- a/lnbits/app.py
+++ b/lnbits/app.py
@@ -58,6 +58,10 @@ def create_app() -> FastAPI:
name="core_static",
)
+ # needed for lnurlw?
+ # g().config = settings
+ g().base_url = f"http://{settings.host}:{settings.port}"
+
app.add_middleware(
CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]
)
From 3cea3a0ba86f8c79d53690e4ad275bd00e5033de Mon Sep 17 00:00:00 2001
From: ben
Date: Thu, 24 Nov 2022 11:18:38 +0000
Subject: [PATCH 226/696] Removed payments websocket example
To be prob added back at a later date
---
lnbits/core/tasks.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/lnbits/core/tasks.py b/lnbits/core/tasks.py
index 734c3f01..b57e2625 100644
--- a/lnbits/core/tasks.py
+++ b/lnbits/core/tasks.py
@@ -10,7 +10,6 @@ from lnbits.tasks import SseListenersDict, register_invoice_listener
from . import db
from .crud import get_balance_notify
from .models import Payment
-from .services import websocketUpdater
api_invoice_listeners: Dict[str, asyncio.Queue] = SseListenersDict(
"api_invoice_listeners"
@@ -39,7 +38,6 @@ async def wait_for_paid_invoices(invoice_paid_queue: asyncio.Queue):
logger.trace("received invoice paid event")
# send information to sse channel
await dispatch_api_invoice_listeners(payment)
- await websocketUpdater(payment.wallet_id, payment)
# dispatch webhook
if payment.webhook and not payment.webhook_status:
From aefea9b8018101354d016feb13b139e590342921 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 24 Nov 2022 14:53:11 +0100
Subject: [PATCH 227/696] mypy and formatting
---
lnbits/extensions/admin/crud.py | 6 +++---
lnbits/extensions/admin/views.py | 2 +-
lnbits/extensions/admin/views_api.py | 6 ++++--
lnbits/server.py | 6 +++++-
4 files changed, 13 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 2ce91612..7f9779b0 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,4 +1,4 @@
-from typing import List
+from typing import List, Optional
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
@@ -9,7 +9,7 @@ from . import db
from .models import AdminSettings, UpdateSettings
-async def update_wallet_balance(wallet_id: str, amount: int) -> str:
+async def update_wallet_balance(wallet_id: str, amount: int) -> None:
temp_id = f"temp_{urlsafe_short_hash()}"
internal_id = f"internal_{urlsafe_short_hash()}"
@@ -36,7 +36,7 @@ async def get_settings() -> AdminSettings:
return settings
-async def update_settings(data: UpdateSettings) -> Settings:
+async def update_settings(data: UpdateSettings) -> Optional[Settings]:
fields = []
for key, value in data.dict(exclude_none=True).items():
if not key in read_only_variables:
diff --git a/lnbits/extensions/admin/views.py b/lnbits/extensions/admin/views.py
index aa933017..b1d68026 100644
--- a/lnbits/extensions/admin/views.py
+++ b/lnbits/extensions/admin/views.py
@@ -17,7 +17,7 @@ templates = Jinja2Templates(directory="templates")
@admin_ext.get("/", response_class=HTMLResponse)
-async def index(request: Request, user: User = Depends(check_admin)):
+async def index(request: Request, user: User = Depends(check_admin)): # type: ignore
WALLET = get_wallet_class()
error, balance = await WALLET.status()
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 57d62ed4..eb1eff80 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -1,6 +1,7 @@
from http import HTTPStatus
-from fastapi import Body, Depends, Query
+from fastapi import Body
+from fastapi.params import Depends
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_wallet
@@ -51,7 +52,8 @@ async def api_update_settings(
data: UpdateSettings = Body(...),
):
settings = await update_settings(data)
- return {"status": "Success", "settings": settings.dict()}
+ if settings:
+ return {"status": "Success", "settings": settings.dict()}
@admin_ext.delete(
diff --git a/lnbits/server.py b/lnbits/server.py
index 3d209961..70b15868 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -19,7 +19,11 @@ from lnbits.settings import set_cli_settings, settings
)
@click.option("--port", default=settings.port, help="Port to listen on")
@click.option("--host", default=settings.host, help="Host to run LNBits on")
-@click.option("--forwarded-allow-ips", default=settings.forwarded_allow_ips, help="Allowed proxy servers")
+@click.option(
+ "--forwarded-allow-ips",
+ default=settings.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
From acc27a471415570da08bd007e3718545187cf2c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Thu, 24 Nov 2022 15:43:15 +0100
Subject: [PATCH 228/696] fix lndgrpc regtest errors
---
lnbits/extensions/admin/migrations.py | 1 +
lnbits/extensions/admin/models.py | 1 +
lnbits/settings.py | 1 +
3 files changed, 3 insertions(+)
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index ea698c27..2a9424e6 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -47,6 +47,7 @@ async def m001_create_admin_settings_table(db):
lnd_grpc_port INTEGER,
lnd_grpc_admin_macaroon TEXT,
lnd_grpc_invoice_macaroon TEXT,
+ lnd_grpc_macaroon TEXT,
lnd_grpc_macaroon_encrypted TEXT,
lnpay_api_endpoint TEXT,
lnpay_api_key TEXT,
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 31811659..7440fae7 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -45,6 +45,7 @@ class UpdateSettings(BaseModel):
lnd_grpc_port: int = Query(None, ge=0)
lnd_grpc_admin_macaroon: str = Query(None)
lnd_grpc_invoice_macaroon: str = Query(None)
+ lnd_grpc_macaroon: str = Query(None)
lnd_grpc_macaroon_encrypted: str = Query(None)
lnpay_api_endpoint: str = Query(None)
lnpay_api_key: str = Query(None)
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 0e58f8c7..c3356938 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -102,6 +102,7 @@ class Settings(BaseSettings):
lnd_grpc_port: Optional[int] = Field(default=None)
lnd_grpc_admin_macaroon: Optional[str] = Field(default=None)
lnd_grpc_invoice_macaroon: Optional[str] = Field(default=None)
+ lnd_grpc_macaroon: Optional[str] = Field(default=None)
lnd_grpc_macaroon_encrypted: Optional[str] = Field(default=None)
lnpay_api_endpoint: Optional[str] = Field(default=None)
lnpay_api_key: Optional[str] = Field(default=None)
From 746e119046dad05207b3afd6f8c3c90944923b7c Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 28 Nov 2022 13:04:35 +0000
Subject: [PATCH 229/696] added vlads suggestion
---
lnbits/core/services.py | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index b03562d9..28552540 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -2,7 +2,7 @@ import asyncio
import json
from binascii import unhexlify
from io import BytesIO
-from typing import Dict, Optional, Tuple
+from typing import Dict, Optional, Tuple, List
from urllib.parse import parse_qs, urlparse
import httpx
@@ -384,25 +384,30 @@ def fee_reserve(amount_msat: int) -> int:
return max(int(RESERVE_FEE_MIN), int(amount_msat * RESERVE_FEE_PERCENT / 100.0))
-class websocketConnectionManager:
+class WebsocketConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
+ return
async def connect(self, websocket: WebSocket, item_id: str):
await websocket.accept()
- websocket.id = item_id
- self.active_connections.append(websocket)
+ if item_id not in self.active_connections:
+ websocket.id = item_id
+ self.active_connections.append(websocket)
+ return
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
+ return
async def send_data(self, message: str, item_id: str):
for connection in self.active_connections:
if connection.id == item_id:
await connection.send_text(message)
+ return
-websocketManager = websocketConnectionManager()
+websocketManager = WebsocketConnectionManager()
async def websocketUpdater(item_id, data):
From aefd1fad6926e94cdc57854d29731f40ea8d0787 Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 28 Nov 2022 13:13:45 +0000
Subject: [PATCH 230/696] isort
---
lnbits/core/services.py | 25 ++++++-------------
lnbits/core/views/api.py | 47 ++++++++++--------------------------
lnbits/core/views/generic.py | 22 +++++------------
3 files changed, 27 insertions(+), 67 deletions(-)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index 28552540..798680a2 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -2,7 +2,7 @@ import asyncio
import json
from binascii import unhexlify
from io import BytesIO
-from typing import Dict, Optional, Tuple, List
+from typing import Dict, List, Optional, Tuple
from urllib.parse import parse_qs, urlparse
import httpx
@@ -13,27 +13,18 @@ from loguru import logger
from lnbits import bolt11
from lnbits.db import Connection
-from lnbits.decorators import (
- WalletTypeInfo,
- get_key_type,
- require_admin_key,
- require_invoice_key,
-)
+from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key,
+ require_invoice_key)
from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.requestvars import g
-from lnbits.settings import FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT, WALLET
+from lnbits.settings import (FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT,
+ WALLET)
from lnbits.wallets.base import PaymentResponse, PaymentStatus
from . import db
-from .crud import (
- check_internal,
- create_payment,
- delete_wallet_payment,
- get_wallet,
- get_wallet_payment,
- update_payment_details,
- update_payment_status,
-)
+from .crud import (check_internal, create_payment, delete_wallet_payment,
+ get_wallet, get_wallet_payment, update_payment_details,
+ update_payment_status)
from .models import Payment
try:
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index ac56eb5e..c6c5f992 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -9,10 +9,10 @@ from io import BytesIO
from typing import Dict, List, Optional, Tuple, Union
from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
-import async_timeout
import httpx
import pyqrcode
-from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect
+from fastapi import (Depends, Header, Query, Request, WebSocket,
+ WebSocketDisconnect)
from fastapi.exceptions import HTTPException
from fastapi.params import Body
from loguru import logger
@@ -21,44 +21,23 @@ from pydantic.fields import Field
from sse_starlette.sse import EventSourceResponse, ServerSentEvent
from starlette.responses import HTMLResponse, StreamingResponse
+import async_timeout
from lnbits import bolt11, lnurl
from lnbits.core.models import Payment, Wallet
-from lnbits.decorators import (
- WalletTypeInfo,
- get_key_type,
- require_admin_key,
- require_invoice_key,
-)
+from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key,
+ require_invoice_key)
from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET
-from lnbits.utils.exchange_rates import (
- currencies,
- fiat_amount_as_satoshis,
- satoshis_amount_as_fiat,
-)
+from lnbits.utils.exchange_rates import (currencies, fiat_amount_as_satoshis,
+ satoshis_amount_as_fiat)
from .. import core_app, db
-from ..crud import (
- create_payment,
- get_payments,
- get_standalone_payment,
- get_total_balance,
- get_wallet,
- get_wallet_for_key,
- save_balance_check,
- update_payment_status,
- update_wallet,
-)
-from ..services import (
- InvoiceFailure,
- PaymentFailure,
- check_transaction_status,
- create_invoice,
- pay_invoice,
- perform_lnurlauth,
- websocketManager,
- websocketUpdater,
-)
+from ..crud import (create_payment, get_payments, get_standalone_payment,
+ get_total_balance, get_wallet, get_wallet_for_key,
+ save_balance_check, update_payment_status, update_wallet)
+from ..services import (InvoiceFailure, PaymentFailure,
+ check_transaction_status, create_invoice, pay_invoice,
+ perform_lnurlauth, websocketManager, websocketUpdater)
from ..tasks import api_invoice_listeners
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index 31a7b030..c16c8a41 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -15,24 +15,14 @@ from lnbits.core import db
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from lnbits.helpers import template_renderer, url_for
-from lnbits.settings import (
- LNBITS_ADMIN_USERS,
- LNBITS_ALLOWED_USERS,
- LNBITS_CUSTOM_LOGO,
- LNBITS_SITE_TITLE,
- SERVICE_FEE,
-)
+from lnbits.settings import (LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS,
+ LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE,
+ SERVICE_FEE)
from ...helpers import get_valid_extensions
-from ..crud import (
- create_account,
- create_wallet,
- delete_wallet,
- get_balance_check,
- get_user,
- save_balance_notify,
- update_user_extension,
-)
+from ..crud import (create_account, create_wallet, delete_wallet,
+ get_balance_check, get_user, save_balance_notify,
+ update_user_extension)
from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
From bb84f6b0e8ea74db4b615ab8ae4da0ef4d2849ab Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 28 Nov 2022 13:24:10 +0000
Subject: [PATCH 231/696] Black
---
lnbits/core/services.py | 23 ++++++++++++------
lnbits/core/views/api.py | 45 ++++++++++++++++++++++++++----------
lnbits/core/views/generic.py | 22 +++++++++++++-----
3 files changed, 65 insertions(+), 25 deletions(-)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index 798680a2..d0f6ebd3 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -13,18 +13,27 @@ from loguru import logger
from lnbits import bolt11
from lnbits.db import Connection
-from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key,
- require_invoice_key)
+from lnbits.decorators import (
+ WalletTypeInfo,
+ get_key_type,
+ require_admin_key,
+ require_invoice_key,
+)
from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.requestvars import g
-from lnbits.settings import (FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT,
- WALLET)
+from lnbits.settings import FAKE_WALLET, RESERVE_FEE_MIN, RESERVE_FEE_PERCENT, WALLET
from lnbits.wallets.base import PaymentResponse, PaymentStatus
from . import db
-from .crud import (check_internal, create_payment, delete_wallet_payment,
- get_wallet, get_wallet_payment, update_payment_details,
- update_payment_status)
+from .crud import (
+ check_internal,
+ create_payment,
+ delete_wallet_payment,
+ get_wallet,
+ get_wallet_payment,
+ update_payment_details,
+ update_payment_status,
+)
from .models import Payment
try:
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index c6c5f992..1a7ee8f5 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -11,8 +11,7 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import httpx
import pyqrcode
-from fastapi import (Depends, Header, Query, Request, WebSocket,
- WebSocketDisconnect)
+from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect
from fastapi.exceptions import HTTPException
from fastapi.params import Body
from loguru import logger
@@ -24,20 +23,42 @@ from starlette.responses import HTMLResponse, StreamingResponse
import async_timeout
from lnbits import bolt11, lnurl
from lnbits.core.models import Payment, Wallet
-from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key,
- require_invoice_key)
+from lnbits.decorators import (
+ WalletTypeInfo,
+ get_key_type,
+ require_admin_key,
+ require_invoice_key,
+)
from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET
-from lnbits.utils.exchange_rates import (currencies, fiat_amount_as_satoshis,
- satoshis_amount_as_fiat)
+from lnbits.utils.exchange_rates import (
+ currencies,
+ fiat_amount_as_satoshis,
+ satoshis_amount_as_fiat,
+)
from .. import core_app, db
-from ..crud import (create_payment, get_payments, get_standalone_payment,
- get_total_balance, get_wallet, get_wallet_for_key,
- save_balance_check, update_payment_status, update_wallet)
-from ..services import (InvoiceFailure, PaymentFailure,
- check_transaction_status, create_invoice, pay_invoice,
- perform_lnurlauth, websocketManager, websocketUpdater)
+from ..crud import (
+ create_payment,
+ get_payments,
+ get_standalone_payment,
+ get_total_balance,
+ get_wallet,
+ get_wallet_for_key,
+ save_balance_check,
+ update_payment_status,
+ update_wallet,
+)
+from ..services import (
+ InvoiceFailure,
+ PaymentFailure,
+ check_transaction_status,
+ create_invoice,
+ pay_invoice,
+ perform_lnurlauth,
+ websocketManager,
+ websocketUpdater,
+)
from ..tasks import api_invoice_listeners
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index c16c8a41..31a7b030 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -15,14 +15,24 @@ from lnbits.core import db
from lnbits.core.models import User
from lnbits.decorators import check_user_exists
from lnbits.helpers import template_renderer, url_for
-from lnbits.settings import (LNBITS_ADMIN_USERS, LNBITS_ALLOWED_USERS,
- LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE,
- SERVICE_FEE)
+from lnbits.settings import (
+ LNBITS_ADMIN_USERS,
+ LNBITS_ALLOWED_USERS,
+ LNBITS_CUSTOM_LOGO,
+ LNBITS_SITE_TITLE,
+ SERVICE_FEE,
+)
from ...helpers import get_valid_extensions
-from ..crud import (create_account, create_wallet, delete_wallet,
- get_balance_check, get_user, save_balance_notify,
- update_user_extension)
+from ..crud import (
+ create_account,
+ create_wallet,
+ delete_wallet,
+ get_balance_check,
+ get_user,
+ save_balance_notify,
+ update_user_extension,
+)
from ..services import pay_invoice, redeem_lnurl_withdraw
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
From 187d709098c4dd6819d682f210b2e501b37ad840 Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 28 Nov 2022 13:28:11 +0000
Subject: [PATCH 232/696] isort
---
lnbits/core/views/api.py | 45 +++++++++++-----------------------------
1 file changed, 12 insertions(+), 33 deletions(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 1a7ee8f5..c6c5f992 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -11,7 +11,8 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import httpx
import pyqrcode
-from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect
+from fastapi import (Depends, Header, Query, Request, WebSocket,
+ WebSocketDisconnect)
from fastapi.exceptions import HTTPException
from fastapi.params import Body
from loguru import logger
@@ -23,42 +24,20 @@ from starlette.responses import HTMLResponse, StreamingResponse
import async_timeout
from lnbits import bolt11, lnurl
from lnbits.core.models import Payment, Wallet
-from lnbits.decorators import (
- WalletTypeInfo,
- get_key_type,
- require_admin_key,
- require_invoice_key,
-)
+from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key,
+ require_invoice_key)
from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET
-from lnbits.utils.exchange_rates import (
- currencies,
- fiat_amount_as_satoshis,
- satoshis_amount_as_fiat,
-)
+from lnbits.utils.exchange_rates import (currencies, fiat_amount_as_satoshis,
+ satoshis_amount_as_fiat)
from .. import core_app, db
-from ..crud import (
- create_payment,
- get_payments,
- get_standalone_payment,
- get_total_balance,
- get_wallet,
- get_wallet_for_key,
- save_balance_check,
- update_payment_status,
- update_wallet,
-)
-from ..services import (
- InvoiceFailure,
- PaymentFailure,
- check_transaction_status,
- create_invoice,
- pay_invoice,
- perform_lnurlauth,
- websocketManager,
- websocketUpdater,
-)
+from ..crud import (create_payment, get_payments, get_standalone_payment,
+ get_total_balance, get_wallet, get_wallet_for_key,
+ save_balance_check, update_payment_status, update_wallet)
+from ..services import (InvoiceFailure, PaymentFailure,
+ check_transaction_status, create_invoice, pay_invoice,
+ perform_lnurlauth, websocketManager, websocketUpdater)
from ..tasks import api_invoice_listeners
From 5abb013a435b5989175187dc6b2dc7c9cdc971b0 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Tue, 29 Nov 2022 09:34:54 +0100
Subject: [PATCH 233/696] force download wipe json when deleting card
---
lnbits/extensions/boltcards/static/js/index.js | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/lnbits/extensions/boltcards/static/js/index.js b/lnbits/extensions/boltcards/static/js/index.js
index 95a95afc..1f32ad44 100644
--- a/lnbits/extensions/boltcards/static/js/index.js
+++ b/lnbits/extensions/boltcards/static/js/index.js
@@ -412,6 +412,12 @@ new Vue({
let self = this
let cards = _.findWhere(this.cards, {id: cardId})
+ Quasar.utils.exportFile(
+ cards.card_name + '.json',
+ this.qrCodeDialog.data_wipe,
+ 'application/json'
+ )
+
LNbits.utils
.confirmDialog(
"Are you sure you want to delete this card? Without access to the card keys you won't be able to reset them in the future!"
From 33631d6375a3d2354d454fe1be8fd2bd861948f9 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Tue, 29 Nov 2022 11:14:51 +0100
Subject: [PATCH 234/696] tpos identifies itself via user-agent
---
lnbits/extensions/tpos/views_api.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py
index fe63a247..811d2116 100644
--- a/lnbits/extensions/tpos/views_api.py
+++ b/lnbits/extensions/tpos/views_api.py
@@ -3,6 +3,7 @@ from http import HTTPStatus
import httpx
from fastapi import Query
from fastapi.params import Depends
+from lnbits.settings import LNBITS_COMMIT
from lnurl import decode as decode_lnurl
from loguru import logger
from starlette.exceptions import HTTPException
@@ -134,7 +135,8 @@ async def api_tpos_pay_invoice(
async with httpx.AsyncClient() as client:
try:
- r = await client.get(lnurl, follow_redirects=True)
+ headers = {"user-agent": f"lnbits/tpos commit {LNBITS_COMMIT[:7]}"}
+ r = await client.get(lnurl, follow_redirects=True, headers=headers)
if r.is_error:
lnurl_response = {"success": False, "detail": "Error loading"}
else:
@@ -145,6 +147,7 @@ async def api_tpos_pay_invoice(
r2 = await client.get(
resp["callback"],
follow_redirects=True,
+ headers=headers,
params={
"k1": resp["k1"],
"pr": payment_request,
From 9756e6fad8f762a260fc96027b385f953613cfd3 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Tue, 29 Nov 2022 11:28:17 +0100
Subject: [PATCH 235/696] formating
---
lnbits/extensions/tpos/views_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/tpos/views_api.py b/lnbits/extensions/tpos/views_api.py
index 811d2116..e13dee9b 100644
--- a/lnbits/extensions/tpos/views_api.py
+++ b/lnbits/extensions/tpos/views_api.py
@@ -3,7 +3,6 @@ from http import HTTPStatus
import httpx
from fastapi import Query
from fastapi.params import Depends
-from lnbits.settings import LNBITS_COMMIT
from lnurl import decode as decode_lnurl
from loguru import logger
from starlette.exceptions import HTTPException
@@ -13,6 +12,7 @@ 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
+from lnbits.settings import LNBITS_COMMIT
from . import tpos_ext
from .crud import create_tpos, delete_tpos, get_tpos, get_tposs
From 31d9f2e2ee6eb04c1a99cd8f7f54c9ecd607da12 Mon Sep 17 00:00:00 2001
From: ben
Date: Tue, 29 Nov 2022 11:09:54 +0000
Subject: [PATCH 236/696] Removed returns and reverted socket check so multiple
clients can join
---
lnbits/core/services.py | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/lnbits/core/services.py b/lnbits/core/services.py
index d0f6ebd3..8a88411a 100644
--- a/lnbits/core/services.py
+++ b/lnbits/core/services.py
@@ -387,24 +387,19 @@ def fee_reserve(amount_msat: int) -> int:
class WebsocketConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
- return
async def connect(self, websocket: WebSocket, item_id: str):
await websocket.accept()
- if item_id not in self.active_connections:
- websocket.id = item_id
- self.active_connections.append(websocket)
- return
+ websocket.id = item_id
+ self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
- return
async def send_data(self, message: str, item_id: str):
for connection in self.active_connections:
if connection.id == item_id:
await connection.send_text(message)
- return
websocketManager = WebsocketConnectionManager()
From 9a9733c1ce9d3e79b375c4319072384ceb5c667c Mon Sep 17 00:00:00 2001
From: ben
Date: Tue, 29 Nov 2022 11:23:34 +0000
Subject: [PATCH 237/696] Auto stash before merge of "universalwebsocket" and
"origin/universalwebsocket"
---
lnbits/core/views/api.py | 47 ++++++++++++++++++++++++++++------------
1 file changed, 33 insertions(+), 14 deletions(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index 83596778..b7d83565 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -11,10 +11,7 @@ from urllib.parse import ParseResult, parse_qs, urlencode, urlparse, urlunparse
import httpx
import pyqrcode
-
-from fastapi import (Depends, Header, Query, Request, WebSocket,
- WebSocketDisconnect)
-
+from fastapi import Depends, Header, Query, Request, WebSocket, WebSocketDisconnect
from fastapi.exceptions import HTTPException
from fastapi.params import Body
from loguru import logger
@@ -26,20 +23,42 @@ from starlette.responses import HTMLResponse, StreamingResponse
import async_timeout
from lnbits import bolt11, lnurl
from lnbits.core.models import Payment, Wallet
-from lnbits.decorators import (WalletTypeInfo, get_key_type, require_admin_key,
- require_invoice_key)
+from lnbits.decorators import (
+ WalletTypeInfo,
+ get_key_type,
+ require_admin_key,
+ require_invoice_key,
+)
from lnbits.helpers import url_for, urlsafe_short_hash
from lnbits.settings import LNBITS_ADMIN_USERS, LNBITS_SITE_TITLE, WALLET
-from lnbits.utils.exchange_rates import (currencies, fiat_amount_as_satoshis,
- satoshis_amount_as_fiat)
+from lnbits.utils.exchange_rates import (
+ currencies,
+ fiat_amount_as_satoshis,
+ satoshis_amount_as_fiat,
+)
from .. import core_app, db
-from ..crud import (create_payment, get_payments, get_standalone_payment,
- get_total_balance, get_wallet, get_wallet_for_key,
- save_balance_check, update_payment_status, update_wallet)
-from ..services import (InvoiceFailure, PaymentFailure,
- check_transaction_status, create_invoice, pay_invoice,
- perform_lnurlauth, websocketManager, websocketUpdater)
+from ..crud import (
+ create_payment,
+ get_payments,
+ get_standalone_payment,
+ get_total_balance,
+ get_wallet,
+ get_wallet_for_key,
+ save_balance_check,
+ update_payment_status,
+ update_wallet,
+)
+from ..services import (
+ InvoiceFailure,
+ PaymentFailure,
+ check_transaction_status,
+ create_invoice,
+ pay_invoice,
+ perform_lnurlauth,
+ websocketManager,
+ websocketUpdater,
+)
from ..tasks import api_invoice_listeners
From b49545f48bab1b7f3ebe360d9f3cc31daadf0a27 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 29 Nov 2022 20:29:08 +0100
Subject: [PATCH 238/696] exlude forwarded_allow_ips
---
lnbits/settings.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/settings.py b/lnbits/settings.py
index c3356938..67a1ca5a 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -21,7 +21,7 @@ def list_parse_fallback(v):
return []
-read_only_variables = ["host", "port", "lnbits_commit", "lnbits_path"]
+read_only_variables = ["host", "port", "lnbits_commit", "lnbits_path", "forwarded_allow_ips"]
class Settings(BaseSettings):
From 8b86936b079b403f4c92429b87cfdd16acd21522 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Tue, 29 Nov 2022 20:30:22 +0100
Subject: [PATCH 239/696] exlude forwarded_allow_ips
---
lnbits/extensions/admin/migrations.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index 2a9424e6..ddf4d4dd 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -6,6 +6,7 @@ async def m001_create_admin_settings_table(db):
debug TEXT,
host TEXT,
port INTEGER,
+ forwarded_allow_ips TEXT,
lnbits_saas_instance_id TEXT,
lnbits_saas_callback TEXT,
lnbits_saas_secret TEXT,
From 20b06e4922fca248b17229281d50cd48f2f3e970 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Wed, 30 Nov 2022 14:14:31 +0000
Subject: [PATCH 240/696] Corrected casing of LNbits (not LNBits) throughout
repo
---
docs/guide/installation.md | 4 ++--
lnbits/extensions/boltcards/README.md | 6 +++---
lnbits/extensions/invoices/templates/invoices/index.html | 2 +-
lnbits/extensions/jukebox/README.md | 4 ++--
lnbits/extensions/livestream/README.md | 4 ++--
lnbits/extensions/lndhub/README.md | 2 +-
lnbits/extensions/offlineshop/README.md | 4 ++--
lnbits/extensions/satspay/README.md | 2 +-
lnbits/extensions/splitpayments/README.md | 2 +-
lnbits/extensions/subdomains/README.md | 2 +-
lnbits/extensions/watchonly/README.md | 2 +-
lnbits/extensions/withdraw/README.md | 6 +++---
lnbits/server.py | 2 +-
tests/extensions/invoices/conftest.py | 2 +-
tests/extensions/invoices/test_invoices_api.py | 2 +-
15 files changed, 23 insertions(+), 23 deletions(-)
diff --git a/docs/guide/installation.md b/docs/guide/installation.md
index 072c4d91..aef26b4a 100644
--- a/docs/guide/installation.md
+++ b/docs/guide/installation.md
@@ -106,7 +106,7 @@ docker run --detach --publish 5000:5000 --name lnbits-legend --volume ${PWD}/.en
## Option 5: Fly.io
-Fly.io is a docker container hosting platform that has a generous free tier. You can host LNBits for free on Fly.io for personal use.
+Fly.io is a docker container hosting platform that has a generous free tier. You can host LNbits for free on Fly.io for personal use.
First, sign up for an account at [Fly.io](https://fly.io) (no credit card required).
@@ -169,7 +169,7 @@ kill_timeout = 30
...
```
-Next, create a volume to store the sqlite database for LNBits. Be sure to choose the same region for the volume that you chose earlier.
+Next, create a volume to store the sqlite database for LNbits. Be sure to choose the same region for the volume that you chose earlier.
```
fly volumes create lnbits_data --size 1
diff --git a/lnbits/extensions/boltcards/README.md b/lnbits/extensions/boltcards/README.md
index f9c59409..b86de62c 100644
--- a/lnbits/extensions/boltcards/README.md
+++ b/lnbits/extensions/boltcards/README.md
@@ -6,7 +6,7 @@ This extension allows you to link your Bolt Card (or other compatible NXP NTAG d
**Disclaimer:** ***Use this only if you either know what you are doing or are a reckless lightning pioneer. Only you are responsible for all your sats, cards and other devices. Always backup all your card keys!***
-***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNBits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [bolt-nfc-android-app](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp).
+***In order to use this extension you need to be able to setup your own card.*** That means writing a URL template pointing to your LNbits instance, configuring some SUN (SDM) settings and optionally changing the card's keys. There's a [guide](https://www.whitewolftech.com/articles/payment-card/) to set it up with a card reader connected to your computer. It can be done (without setting the keys) with [TagWriter app by NXP](https://play.google.com/store/apps/details?id=com.nxp.nfc.tagwriter) Android app. Last but not least, an OSS android app by name [bolt-nfc-android-app](https://github.com/boltcard/bolt-nfc-android-app) is being developed for these purposes. It's available from Google Play [here](https://play.google.com/store/apps/details?id=com.lightningnfcapp).
## About the keys
@@ -25,12 +25,12 @@ So far, regarding the keys, the app can only write a new key set on an empty car
- Read the card with the app. Note UID so you can fill it in the extension later.
- Write the link on the card. It shoud be like `YOUR_LNBITS_DOMAIN/boltcards/api/v1/scan/{external_id}`
- - `{external_id}` should be replaced with the External ID found in the LNBits dialog.
+ - `{external_id}` should be replaced with the External ID found in the LNbits dialog.
- Add new card in the extension.
- Set a max sats per transaction. Any transaction greater than this amount will be rejected.
- Set a max sats per day. After the card spends this amount of sats in a day, additional transactions will be rejected.
- - Set a card name. This is just for your reference inside LNBits.
+ - Set a card name. This is just for your reference inside LNbits.
- Set the card UID. This is the unique identifier on your NFC card and is 7 bytes.
- If on an Android device with a newish version of Chrome, you can click the icon next to the input and tap your card to autofill this field.
- Advanced Options
diff --git a/lnbits/extensions/invoices/templates/invoices/index.html b/lnbits/extensions/invoices/templates/invoices/index.html
index e3093e3c..4ef3b7f1 100644
--- a/lnbits/extensions/invoices/templates/invoices/index.html
+++ b/lnbits/extensions/invoices/templates/invoices/index.html
@@ -118,7 +118,7 @@
dense
v-model.trim="formDialog.data.company_name"
label="Company Name"
- placeholder="LNBits Labs"
+ placeholder="LNbits Labs"
>
-5. Open the LNBits subdomains extension and register your domain
+5. Open the LNbits subdomains extension and register your domain
6. Click on the button in the table to open the public form that was generated for your domain
- Extension also supports webhooks so you can get notified when someone buys a new subdomain\
diff --git a/lnbits/extensions/watchonly/README.md b/lnbits/extensions/watchonly/README.md
index 45abdb93..d154d8a3 100644
--- a/lnbits/extensions/watchonly/README.md
+++ b/lnbits/extensions/watchonly/README.md
@@ -4,7 +4,7 @@
Monitor an extended public key and generate deterministic fresh public keys with this simple watch only wallet. Invoice payments can also be generated, both through a publically shareable page and API.
-You can now use this wallet on the LNBits [SatsPayServer](https://github.com/lnbits/lnbits/blob/master/lnbits/extensions/satspay/README.md) extension
+You can now use this wallet on the LNbits [SatsPayServer](https://github.com/lnbits/lnbits/blob/master/lnbits/extensions/satspay/README.md) extension
Video demo
diff --git a/lnbits/extensions/withdraw/README.md b/lnbits/extensions/withdraw/README.md
index 7bf7c232..fce2c6e5 100644
--- a/lnbits/extensions/withdraw/README.md
+++ b/lnbits/extensions/withdraw/README.md
@@ -14,7 +14,7 @@ LNURL withdraw is a **very powerful tool** and should not have his use limited t
#### Quick Vouchers
-LNBits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes that you can print and distribute as rewards, onboarding people into Lightning Network, gifts, etc...
+LNbits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes that you can print and distribute as rewards, onboarding people into Lightning Network, gifts, etc...
1. Create Quick Vouchers\

@@ -37,12 +37,12 @@ LNBits Quick Vouchers allows you to easily create a batch of LNURLw's QR codes t
- set a title for the LNURLw (it will show up in users wallet)
- define the minimum and maximum a user can withdraw, if you want a fixed amount set them both to an equal value
- set how many times can the LNURLw be scanned, if it's a one time use or it can be scanned 100 times
- - LNBits has the "_Time between withdraws_" setting, you can define how long the LNURLw will be unavailable between scans
+ - LNbits has the "_Time between withdraws_" setting, you can define how long the LNURLw will be unavailable between scans
- you can set the time in _seconds, minutes or hours_
- the "_Use unique withdraw QR..._" reduces the chance of your LNURL withdraw being exploited and depleted by one person, by generating a new QR code every time it's scanned
2. Print, share or display your LNURLw link or it's QR code\

-**LNBits bonus:** If a user doesn't have a Lightning Network wallet and scans the LNURLw QR code with their smartphone camera, or a QR scanner app, they can follow the link provided to claim their satoshis and get an instant LNBits wallet!
+**LNbits bonus:** If a user doesn't have a Lightning Network wallet and scans the LNURLw QR code with their smartphone camera, or a QR scanner app, they can follow the link provided to claim their satoshis and get an instant LNbits wallet!

diff --git a/lnbits/server.py b/lnbits/server.py
index 7aaaa964..7a5c1947 100644
--- a/lnbits/server.py
+++ b/lnbits/server.py
@@ -11,7 +11,7 @@ from lnbits.settings import FORWARDED_ALLOW_IPS, 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("--host", default=HOST, help="Host to run LNbits on")
@click.option(
"--forwarded-allow-ips", default=FORWARDED_ALLOW_IPS, help="Allowed proxy servers"
)
diff --git a/tests/extensions/invoices/conftest.py b/tests/extensions/invoices/conftest.py
index 09ac42ec..00b9c237 100644
--- a/tests/extensions/invoices/conftest.py
+++ b/tests/extensions/invoices/conftest.py
@@ -22,7 +22,7 @@ async def accounting_invoice(invoices_wallet):
invoice_data = CreateInvoiceData(
status="open",
currency="USD",
- company_name="LNBits, Inc",
+ company_name="LNbits, Inc",
first_name="Ben",
last_name="Arc",
items=[{"amount": 10.20, "description": "Item costs 10.20"}],
diff --git a/tests/extensions/invoices/test_invoices_api.py b/tests/extensions/invoices/test_invoices_api.py
index eaadd07b..ed236a8f 100644
--- a/tests/extensions/invoices/test_invoices_api.py
+++ b/tests/extensions/invoices/test_invoices_api.py
@@ -20,7 +20,7 @@ async def test_invoices_api_create_invoice_valid(client, invoices_wallet):
query = {
"status": "open",
"currency": "EUR",
- "company_name": "LNBits, Inc.",
+ "company_name": "LNbits, Inc.",
"first_name": "Ben",
"last_name": "Arc",
"email": "ben@legend.arc",
From e740ad3cf8a5ccd5d20d6f70533136840bc41f1f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Wed, 30 Nov 2022 16:53:46 +0100
Subject: [PATCH 241/696] fix lnbitswallet issue, add ad_space_title, change
lntips funding source
---
lnbits/extensions/admin/migrations.py | 7 ++++++-
lnbits/extensions/admin/models.py | 5 +++++
lnbits/extensions/admin/views_api.py | 4 +++-
lnbits/settings.py | 13 ++++++++++++-
lnbits/wallets/lntips.py | 11 ++++++-----
5 files changed, 32 insertions(+), 8 deletions(-)
diff --git a/lnbits/extensions/admin/migrations.py b/lnbits/extensions/admin/migrations.py
index ddf4d4dd..45480a75 100644
--- a/lnbits/extensions/admin/migrations.py
+++ b/lnbits/extensions/admin/migrations.py
@@ -24,6 +24,7 @@ async def m001_create_admin_settings_table(db):
lnbits_theme_options TEXT,
lnbits_custom_logo TEXT,
lnbits_ad_space TEXT,
+ lnbits_ad_space_title TEXT,
lnbits_data_folder TEXT,
lnbits_database_url TEXT,
lnbits_force_https TEXT,
@@ -62,7 +63,11 @@ async def m001_create_admin_settings_table(db):
boltz_network TEXT,
boltz_url TEXT,
boltz_mempool_space_url TEXT,
- boltz_mempool_space_url_ws TEXT
+ boltz_mempool_space_url_ws TEXT,
+ lntips_api_endpoint TEXT,
+ lntips_api_key TEXT,
+ lntips_admin_key TEXT,
+ lntips_invoice_key TEXT
);
"""
)
diff --git a/lnbits/extensions/admin/models.py b/lnbits/extensions/admin/models.py
index 7440fae7..dc665531 100644
--- a/lnbits/extensions/admin/models.py
+++ b/lnbits/extensions/admin/models.py
@@ -24,6 +24,7 @@ class UpdateSettings(BaseModel):
lnbits_theme: str = Query(None)
lnbits_custom_logo: str = Query(None)
lnbits_ad_space: List[str] = Query(None)
+ lnbits_ad_space_title: str = Query(None)
# funding sources
fake_wallet_secret: str = Query(None)
@@ -56,6 +57,10 @@ class UpdateSettings(BaseModel):
opennode_key: str = Query(None)
spark_url: str = Query(None)
spark_token: str = Query(None)
+ lntips_api_endpoint: str = Query(None)
+ lntips_api_key: str = Query(None)
+ lntips_admin_key: str = Query(None)
+ lntips_invoice_key: str = Query(None)
class AdminSettings(UpdateSettings):
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index eb1eff80..dfd6497e 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -5,7 +5,6 @@ from fastapi.params import Depends
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_wallet
-from lnbits.core.models import User
from lnbits.decorators import check_admin
from lnbits.extensions.admin import admin_ext
from lnbits.extensions.admin.models import UpdateSettings
@@ -13,6 +12,8 @@ from lnbits.server import server_restart
from .crud import delete_settings, get_settings, update_settings, update_wallet_balance
+from lnbits.settings import settings, set_settings
+
@admin_ext.get(
"/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
@@ -52,6 +53,7 @@ async def api_update_settings(
data: UpdateSettings = Body(...),
):
settings = await update_settings(data)
+ set_settings(settings)
if settings:
return {"status": "Success", "settings": settings.dict()}
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 67a1ca5a..f84cfb4b 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -57,6 +57,7 @@ class Settings(BaseSettings):
)
lnbits_custom_logo: str = Field(default=None)
lnbits_ad_space: List[str] = Field(default=[])
+ lnbits_ad_space_title: str = Field(default="")
# ops
lnbits_data_folder: str = Field(default="./data")
@@ -79,8 +80,9 @@ class Settings(BaseSettings):
"LndWallet",
"LntxbotWallet",
"LNPayWallet",
- "LnbitsWallet",
+ "LNbitsWallet",
"OpenNodeWallet",
+ "LnTipsWallet",
]
)
fake_wallet_secret: str = Field(default="ToTheMoon1")
@@ -113,6 +115,10 @@ class Settings(BaseSettings):
opennode_key: Optional[str] = Field(default=None)
spark_url: Optional[str] = Field(default=None)
spark_token: Optional[str] = Field(default=None)
+ lntips_api_endpoint: Optional[str] = Field(default=None)
+ lntips_api_key: Optional[str] = Field(default=None)
+ lntips_admin_key: Optional[str] = Field(default=None)
+ lntips_invoice_key: Optional[str] = Field(default=None)
# boltz
boltz_network: str = Field(default="main")
@@ -170,6 +176,11 @@ if not settings.lnbits_admin_ui:
logger.debug(f"{key}: {value}")
+def set_settings(**kwargs):
+ for key, value in kwargs.items():
+ setattr(settings, key, value)
+
+
def set_cli_settings(**kwargs):
for key, value in kwargs.items():
setattr(settings, key, value)
diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py
index 54220c85..9dd43437 100644
--- a/lnbits/wallets/lntips.py
+++ b/lnbits/wallets/lntips.py
@@ -2,7 +2,6 @@ import asyncio
import hashlib
import json
import time
-from os import getenv
from typing import AsyncGenerator, Dict, Optional
import httpx
@@ -16,16 +15,18 @@ from .base import (
Wallet,
)
+from lnbits.settings import settings
+
class LnTipsWallet(Wallet):
def __init__(self):
- endpoint = getenv("LNTIPS_API_ENDPOINT")
+ endpoint = settings.lntips_api_endpoint
self.endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
key = (
- getenv("LNTIPS_API_KEY")
- or getenv("LNTIPS_ADMIN_KEY")
- or getenv("LNTIPS_INVOICE_KEY")
+ settings.lntips_api_key
+ or settings.lntips_admin_key
+ or settings.lntips_invoice_key
)
self.auth = {"Authorization": f"Basic {key}"}
From 86d2780c9f57df5dd9d4c59c554e3b923704c9c6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Wed, 30 Nov 2022 23:47:52 +0100
Subject: [PATCH 242/696] fix template typos and also update settings in memory
after a admin PUT
---
lnbits/core/crud.py | 4 ++--
lnbits/extensions/admin/crud.py | 10 ++++++----
.../extensions/admin/templates/admin/_tab_users.html | 8 +++-----
lnbits/extensions/admin/templates/admin/index.html | 2 +-
lnbits/extensions/admin/views_api.py | 2 --
lnbits/settings.py | 5 -----
6 files changed, 12 insertions(+), 19 deletions(-)
diff --git a/lnbits/core/crud.py b/lnbits/core/crud.py
index eb0d64de..6e498c60 100644
--- a/lnbits/core/crud.py
+++ b/lnbits/core/crud.py
@@ -232,8 +232,8 @@ async def get_wallet_payment(
async def get_latest_payments_by_extension(ext_name: str, ext_id: str, limit: int = 5):
rows = await db.fetchall(
f"""
- SELECT * FROM apipayments
- WHERE pending = 'false'
+ SELECT * FROM apipayments
+ WHERE pending = 'false'
AND extra LIKE ?
AND extra LIKE ?
ORDER BY time DESC LIMIT {limit}
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index 7f9779b0..c6c5a31a 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -1,4 +1,4 @@
-from typing import List, Optional
+from typing import Optional
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
@@ -7,10 +7,10 @@ from lnbits.tasks import internal_invoice_queue
from . import db
from .models import AdminSettings, UpdateSettings
+from lnbits.settings import settings
-async def update_wallet_balance(wallet_id: str, amount: int) -> None:
- temp_id = f"temp_{urlsafe_short_hash()}"
+async def update_wallet_balance(wallet_id: str, amount: int):
internal_id = f"internal_{urlsafe_short_hash()}"
payment = await create_payment(
@@ -25,6 +25,8 @@ async def update_wallet_balance(wallet_id: str, amount: int) -> None:
# manually send this for now
await internal_invoice_queue.put(internal_id)
+ return payment
+
async def get_settings() -> AdminSettings:
row = await db.fetchone("SELECT * FROM admin.settings")
@@ -39,6 +41,7 @@ async def get_settings() -> AdminSettings:
async def update_settings(data: UpdateSettings) -> Optional[Settings]:
fields = []
for key, value in data.dict(exclude_none=True).items():
+ setattr(settings, key, value)
if not key in read_only_variables:
if type(value) == list:
joined = ",".join(value)
@@ -52,7 +55,6 @@ async def update_settings(data: UpdateSettings) -> Optional[Settings]:
fields.append(f"{key} = '{value}'")
q = ", ".join(fields)
- print("UPDATE", q)
await db.execute(f"UPDATE admin.settings SET {q}")
row = await db.fetchone("SELECT * FROM admin.settings")
assert row, "Newly updated settings couldn't be retrieved"
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html
index 08b08a62..b0320fda 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_users.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_users.html
@@ -3,7 +3,7 @@
User Management
- Super Admin: {% raw %}{{settings.lnbits_admin_users[0]}}{% endraw %}
+ Super Admin: {{ settings.lnbits_admin_users[0] }}
@@ -19,18 +19,16 @@
- {% raw %}
- {{ user }}
+ {{ user.id }}
- {% endraw %}
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index 10391261..afe63cb4 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -263,7 +263,7 @@
}
],
[
- 'LnbitsWallet',
+ 'LNbitsWallet',
{
lnbits_endpoint: {
value: null,
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index dfd6497e..0dedb9fc 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -12,7 +12,6 @@ from lnbits.server import server_restart
from .crud import delete_settings, get_settings, update_settings, update_wallet_balance
-from lnbits.settings import settings, set_settings
@admin_ext.get(
@@ -53,7 +52,6 @@ async def api_update_settings(
data: UpdateSettings = Body(...),
):
settings = await update_settings(data)
- set_settings(settings)
if settings:
return {"status": "Success", "settings": settings.dict()}
diff --git a/lnbits/settings.py b/lnbits/settings.py
index f84cfb4b..4fb86846 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -176,11 +176,6 @@ if not settings.lnbits_admin_ui:
logger.debug(f"{key}: {value}")
-def set_settings(**kwargs):
- for key, value in kwargs.items():
- setattr(settings, key, value)
-
-
def set_cli_settings(**kwargs):
for key, value in kwargs.items():
setattr(settings, key, value)
From e3e62fc07303fe6ea27faa38ef32997fb7c0e550 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Wed, 30 Nov 2022 23:52:13 +0100
Subject: [PATCH 243/696] formatting
---
lnbits/extensions/admin/crud.py | 3 +--
lnbits/extensions/admin/templates/admin/_tab_users.html | 4 +---
lnbits/extensions/admin/views_api.py | 1 -
lnbits/settings.py | 8 +++++++-
lnbits/wallets/lntips.py | 4 ++--
5 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/lnbits/extensions/admin/crud.py b/lnbits/extensions/admin/crud.py
index c6c5a31a..e75854e0 100644
--- a/lnbits/extensions/admin/crud.py
+++ b/lnbits/extensions/admin/crud.py
@@ -2,12 +2,11 @@ from typing import Optional
from lnbits.core.crud import create_payment
from lnbits.helpers import urlsafe_short_hash
-from lnbits.settings import Settings, read_only_variables
+from lnbits.settings import Settings, read_only_variables, settings
from lnbits.tasks import internal_invoice_queue
from . import db
from .models import AdminSettings, UpdateSettings
-from lnbits.settings import settings
async def update_wallet_balance(wallet_id: str, amount: int):
diff --git a/lnbits/extensions/admin/templates/admin/_tab_users.html b/lnbits/extensions/admin/templates/admin/_tab_users.html
index b0320fda..4ae3f315 100644
--- a/lnbits/extensions/admin/templates/admin/_tab_users.html
+++ b/lnbits/extensions/admin/templates/admin/_tab_users.html
@@ -2,9 +2,7 @@
User Management
-
- Super Admin: {{ settings.lnbits_admin_users[0] }}
-
+ Super Admin: {{ settings.lnbits_admin_users[0] }}
Admin Users
diff --git a/lnbits/extensions/admin/views_api.py b/lnbits/extensions/admin/views_api.py
index 0dedb9fc..2d64be49 100644
--- a/lnbits/extensions/admin/views_api.py
+++ b/lnbits/extensions/admin/views_api.py
@@ -13,7 +13,6 @@ from lnbits.server import server_restart
from .crud import delete_settings, get_settings, update_settings, update_wallet_balance
-
@admin_ext.get(
"/api/v1/restart/", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
)
diff --git a/lnbits/settings.py b/lnbits/settings.py
index 4fb86846..edaab7d9 100644
--- a/lnbits/settings.py
+++ b/lnbits/settings.py
@@ -21,7 +21,13 @@ def list_parse_fallback(v):
return []
-read_only_variables = ["host", "port", "lnbits_commit", "lnbits_path", "forwarded_allow_ips"]
+read_only_variables = [
+ "host",
+ "port",
+ "lnbits_commit",
+ "lnbits_path",
+ "forwarded_allow_ips",
+]
class Settings(BaseSettings):
diff --git a/lnbits/wallets/lntips.py b/lnbits/wallets/lntips.py
index 9dd43437..f0d08cf1 100644
--- a/lnbits/wallets/lntips.py
+++ b/lnbits/wallets/lntips.py
@@ -7,6 +7,8 @@ from typing import AsyncGenerator, Dict, Optional
import httpx
from loguru import logger
+from lnbits.settings import settings
+
from .base import (
InvoiceResponse,
PaymentResponse,
@@ -15,8 +17,6 @@ from .base import (
Wallet,
)
-from lnbits.settings import settings
-
class LnTipsWallet(Wallet):
def __init__(self):
From dade3d19379160c2cecf1b1b187b64bef99909af Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Thu, 1 Dec 2022 10:01:16 +0000
Subject: [PATCH 244/696] add LnTipsWallet
---
lnbits/extensions/admin/templates/admin/index.html | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index afe63cb4..bb768511 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -309,6 +309,19 @@
label: 'Token'
}
}
+ ],
+ [
+ 'LnTipsWallet',
+ {
+ lntips_api_endpoint: {
+ value: null,
+ label: 'Endpoint'
+ },
+ lntips_api_key: {
+ value: null,
+ label: 'API Key'
+ }
+ }
]
])
}
From eca123b3f8464e26ddbfa136d592194012c94834 Mon Sep 17 00:00:00 2001
From: Tiago Vasconcelos
Date: Thu, 1 Dec 2022 10:57:42 +0000
Subject: [PATCH 245/696] only ask for restart on funding changes
---
lnbits/extensions/admin/templates/admin/index.html | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/admin/templates/admin/index.html b/lnbits/extensions/admin/templates/admin/index.html
index bb768511..ba8b49c0 100644
--- a/lnbits/extensions/admin/templates/admin/index.html
+++ b/lnbits/extensions/admin/templates/admin/index.html
@@ -437,7 +437,6 @@
this.settings = response.data
this.formData = _.clone(this.settings)
this.updateFundingData()
- console.log(this.settings)
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
@@ -455,9 +454,11 @@
data
)
.then(response => {
+ this.needsRestart =
+ this.settings.lnbits_backend_wallet_class !==
+ response.data.settings.lnbits_backend_wallet_class
this.settings = response.data.settings
this.formData = _.clone(this.settings)
- this.needsRestart = true
this.updateFundingData()
this.$q.notify({
type: 'positive',
From 93cc79e0c677e97e8e2d1600af7c7665cdeafc45 Mon Sep 17 00:00:00 2001
From: ben
Date: Thu, 15 Sep 2022 18:11:59 +0100
Subject: [PATCH 246/696] rebuild pages
From e54eb535a9cb3509e2c0e6c42196baef2c539b6e Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 16 Sep 2022 13:20:42 +0100
Subject: [PATCH 247/696] UI works well
---
lnbits/extensions/cashu/README.md | 11 +
lnbits/extensions/cashu/__init__.py | 25 +
lnbits/extensions/cashu/config.json | 6 +
lnbits/extensions/cashu/crud.py | 50 ++
lnbits/extensions/cashu/migrations.py | 33 +
lnbits/extensions/cashu/models.py | 34 +
lnbits/extensions/cashu/tasks.py | 70 ++
.../cashu/templates/cashu/_api_docs.html | 79 ++
.../cashu/templates/cashu/_cashu.html | 15 +
.../cashu/templates/cashu/index.html | 262 ++++++
.../cashu/templates/cashu/mint.html | 33 +
.../cashu/templates/cashu/wallet.html | 753 ++++++++++++++++++
lnbits/extensions/cashu/views.py | 69 ++
lnbits/extensions/cashu/views_api.py | 160 ++++
14 files changed, 1600 insertions(+)
create mode 100644 lnbits/extensions/cashu/README.md
create mode 100644 lnbits/extensions/cashu/__init__.py
create mode 100644 lnbits/extensions/cashu/config.json
create mode 100644 lnbits/extensions/cashu/crud.py
create mode 100644 lnbits/extensions/cashu/migrations.py
create mode 100644 lnbits/extensions/cashu/models.py
create mode 100644 lnbits/extensions/cashu/tasks.py
create mode 100644 lnbits/extensions/cashu/templates/cashu/_api_docs.html
create mode 100644 lnbits/extensions/cashu/templates/cashu/_cashu.html
create mode 100644 lnbits/extensions/cashu/templates/cashu/index.html
create mode 100644 lnbits/extensions/cashu/templates/cashu/mint.html
create mode 100644 lnbits/extensions/cashu/templates/cashu/wallet.html
create mode 100644 lnbits/extensions/cashu/views.py
create mode 100644 lnbits/extensions/cashu/views_api.py
diff --git a/lnbits/extensions/cashu/README.md b/lnbits/extensions/cashu/README.md
new file mode 100644
index 00000000..8f53b474
--- /dev/null
+++ b/lnbits/extensions/cashu/README.md
@@ -0,0 +1,11 @@
+# Cashu
+
+## Create ecash mint for pegging in/out of ecash
+
+
+
+### Usage
+
+1. Enable extension
+2. Create a Mint
+3. Share wallet
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
new file mode 100644
index 00000000..fa549ad2
--- /dev/null
+++ b/lnbits/extensions/cashu/__init__.py
@@ -0,0 +1,25 @@
+import asyncio
+
+from fastapi import APIRouter
+
+from lnbits.db import Database
+from lnbits.helpers import template_renderer
+from lnbits.tasks import catch_everything_and_restart
+
+db = Database("ext_cashu")
+
+cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["TPoS"])
+
+
+def cashu_renderer():
+ return template_renderer(["lnbits/extensions/cashu/templates"])
+
+
+from .tasks import wait_for_paid_invoices
+from .views import * # noqa
+from .views_api import * # noqa
+
+
+def cashu_start():
+ loop = asyncio.get_event_loop()
+ loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
new file mode 100644
index 00000000..c688b22c
--- /dev/null
+++ b/lnbits/extensions/cashu/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Cashu Ecash",
+ "short_description": "Ecash mints with LN peg in/out",
+ "icon": "approval",
+ "contributors": ["shinobi", "arcbtc", "calle"]
+}
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
new file mode 100644
index 00000000..ce83653f
--- /dev/null
+++ b/lnbits/extensions/cashu/crud.py
@@ -0,0 +1,50 @@
+from typing import List, Optional, Union
+
+from lnbits.helpers import urlsafe_short_hash
+
+from . import db
+from .models import Cashu, Pegs
+
+
+async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
+ cashu_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ cashu_id,
+ wallet_id,
+ data.name,
+ data.tickershort,
+ data.fraction,
+ data.maxsats,
+ data.coins
+ ),
+ )
+
+ cashu = await get_cashu(cashu_id)
+ assert cashu, "Newly created cashu couldn't be retrieved"
+ return cashu
+
+
+async def get_cashu(cashu_id: str) -> Optional[Cashu]:
+ row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
+ return Cashu(**row) if row else None
+
+
+async def get_cashus(wallet_ids: Union[str, List[str]]) -> List[Cashu]:
+ if isinstance(wallet_ids, str):
+ wallet_ids = [wallet_ids]
+
+ q = ",".join(["?"] * len(wallet_ids))
+ rows = await db.fetchall(
+ f"SELECT * FROM cashu.cashu WHERE wallet IN ({q})", (*wallet_ids,)
+ )
+
+ return [Cashu(**row) for row in rows]
+
+
+async def delete_cashu(cashu_id: str) -> None:
+ await db.execute("DELETE FROM cashu.cashu WHERE id = ?", (cashu_id,))
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
new file mode 100644
index 00000000..95dc4815
--- /dev/null
+++ b/lnbits/extensions/cashu/migrations.py
@@ -0,0 +1,33 @@
+async def m001_initial(db):
+ """
+ Initial cashu table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE cashu.cashu (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ name TEXT NOT NULL,
+ tickershort TEXT NOT NULL,
+ fraction BOOL,
+ maxsats INT,
+ coins INT
+
+ );
+ """
+ )
+
+ """
+ Initial cashus table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE cashu.pegs (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ inout BOOL NOT NULL,
+ amount INT
+ );
+ """
+ )
+
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
new file mode 100644
index 00000000..0de15362
--- /dev/null
+++ b/lnbits/extensions/cashu/models.py
@@ -0,0 +1,34 @@
+from sqlite3 import Row
+from typing import Optional
+
+from fastapi import Query
+from pydantic import BaseModel
+
+
+class Cashu(BaseModel):
+ id: str = Query(None)
+ name: str = Query(None)
+ wallet: str = Query(None)
+ tickershort: str
+ fraction: bool = Query(None)
+ maxsats: int = Query(0)
+ coins: int = Query(0)
+
+
+ @classmethod
+ def from_row(cls, row: Row) -> "TPoS":
+ return cls(**dict(row))
+
+class Pegs(BaseModel):
+ id: str
+ wallet: str
+ inout: str
+ amount: str
+
+
+ @classmethod
+ def from_row(cls, row: Row) -> "TPoS":
+ return cls(**dict(row))
+
+class PayLnurlWData(BaseModel):
+ lnurl: str
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
new file mode 100644
index 00000000..fe00a591
--- /dev/null
+++ b/lnbits/extensions/cashu/tasks.py
@@ -0,0 +1,70 @@
+import asyncio
+import json
+
+from lnbits.core import db as core_db
+from lnbits.core.crud import create_payment
+from lnbits.core.models import Payment
+from lnbits.helpers import urlsafe_short_hash
+from lnbits.tasks import internal_invoice_queue, register_invoice_listener
+
+from .crud import get_cashu
+
+
+async def wait_for_paid_invoices():
+ invoice_queue = asyncio.Queue()
+ register_invoice_listener(invoice_queue)
+
+ while True:
+ payment = await invoice_queue.get()
+ await on_invoice_paid(payment)
+
+
+async def on_invoice_paid(payment: Payment) -> None:
+ if payment.extra.get("tag") == "cashu" and payment.extra.get("tipSplitted"):
+ # already splitted, ignore
+ return
+
+ # now we make some special internal transfers (from no one to the receiver)
+ cashu = await get_cashu(payment.extra.get("cashuId"))
+ tipAmount = payment.extra.get("tipAmount")
+
+ if tipAmount is None:
+ # no tip amount
+ return
+
+ tipAmount = tipAmount * 1000
+ amount = payment.amount - tipAmount
+
+ # mark the original payment with one extra key, "splitted"
+ # (this prevents us from doing this process again and it's informative)
+ # and reduce it by the amount we're going to send to the producer
+ await core_db.execute(
+ """
+ UPDATE apipayments
+ SET extra = ?, amount = ?
+ WHERE hash = ?
+ AND checking_id NOT LIKE 'internal_%'
+ """,
+ (
+ json.dumps(dict(**payment.extra, tipSplitted=True)),
+ amount,
+ payment.payment_hash,
+ ),
+ )
+
+ # perform the internal transfer using the same payment_hash
+ internal_checking_id = f"internal_{urlsafe_short_hash()}"
+ await create_payment(
+ wallet_id=cashu.tip_wallet,
+ checking_id=internal_checking_id,
+ payment_request="",
+ payment_hash=payment.payment_hash,
+ amount=tipAmount,
+ memo=f"Tip for {payment.memo}",
+ pending=False,
+ extra={"tipSplitted": True},
+ )
+
+ # manually send this for now
+ await internal_invoice_queue.put(internal_checking_id)
+ return
diff --git a/lnbits/extensions/cashu/templates/cashu/_api_docs.html b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
new file mode 100644
index 00000000..7378eb08
--- /dev/null
+++ b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+ GET /cashu/api/v1/cashus
+ Headers
+ {"X-Api-Key": <invoice_key>}
+ Body (application/json)
+
+ Returns 200 OK (application/json)
+
+ [<cashu_object>, ...]
+ Curl example
+ curl -X GET {{ request.base_url }}cashu/api/v1/cashus -H "X-Api-Key:
+ <invoice_key>"
+
+
+
+
+
+
+
+ POST /cashu/api/v1/cashus
+ Headers
+ {"X-Api-Key": <invoice_key>}
+ Body (application/json)
+ {"name": <string>, "currency": <string*ie USD*>}
+
+ Returns 201 CREATED (application/json)
+
+ {"currency": <string>, "id": <string>, "name":
+ <string>, "wallet": <string>}
+ Curl example
+ curl -X POST {{ request.base_url }}cashu/api/v1/cashus -d '{"name":
+ <string>, "currency": <string>}' -H "Content-type:
+ application/json" -H "X-Api-Key: <admin_key>"
+
+
+
+
+
+
+
+
+ DELETE
+ /cashu/api/v1/cashus/<cashu_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+ Returns 204 NO CONTENT
+
+ Curl example
+ curl -X DELETE {{ request.base_url
+ }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key: <admin_key>"
+
+
+
+
+
diff --git a/lnbits/extensions/cashu/templates/cashu/_cashu.html b/lnbits/extensions/cashu/templates/cashu/_cashu.html
new file mode 100644
index 00000000..3c2a38f5
--- /dev/null
+++ b/lnbits/extensions/cashu/templates/cashu/_cashu.html
@@ -0,0 +1,15 @@
+
+
+
+
+ Make Ecash mints with peg in/out to a wallet, that can create and manage ecash.
+
+ Created by
+ Calle .
+
+
+
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
new file mode 100644
index 00000000..17b2a919
--- /dev/null
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -0,0 +1,262 @@
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
+%} {% block page %}
+
+
+
+
+ New Mint
+
+
+
+
+
+
+
+
Mints
+
+
+ Export to CSV
+
+
+
+ {% raw %}
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+ Shareable wallet page
+
+ Shareable mint page
+
+
+
+ {{ (col.name == 'tip_options' && col.value ?
+ JSON.parse(col.value).join(", ") : col.value) }}
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+ {{SITE_TITLE}} Cashu extension
+
+
+
+
+ {% include "cashu/_api_docs.html" %}
+
+ {% include "cashu/_cashu.html" %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Use with hedging extension to create a stablecoin!
+
+
+
+
+
+
+
+
+
+
+ Create Mint
+
+ Cancel
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+{% endblock %}
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
new file mode 100644
index 00000000..0f3e0e09
--- /dev/null
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -0,0 +1,33 @@
+{% extends "public.html" %} {% block page %}
+
+
+
+
+
+
+ {{ mint_name }}
+
+
+ Some data about mint here: * whether its online * Who to contact for support * etc...
+
+
+
+
+ {% endblock %} {% block scripts %}
+
+
+
+ {% endblock %}
+
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
new file mode 100644
index 00000000..a5d5f371
--- /dev/null
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -0,0 +1,753 @@
+{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Wallet {% endraw %}
+
+{% endblock %} {% block footer %}{% endblock %} {% block page_container %}
+
+
+
+
+
+
+
+
+
+ {% raw %} {{balanceAmount}}
+ {{tickershort}}{% endraw %}
+
+
+
+
+
+
+
+
+ Receive
+
+
+ Send
+
+
+ Peg in/out
+
+
+
+ scan
+
+
+
+
+
+
+
+
+
+
+
Transactions
+
+
{% raw %}
+ {% endraw %}
+ Mint details
+
+ Export to CSV
+
+
+ Show chart
+
+
+
+
+
+
+ {% raw %}
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+ Pending
+
+
+
+
+
+ #{{ props.row.tag }}
+
+
+ {{ props.row.memo }}
+
+
+ {{ props.row.date }}
+ {{ props.row.dateFrom }}
+
+ {% endraw %}
+ {% raw %} {{
+ parseFloat(String(props.row.fsat).replaceAll(",", "")) / 100
+ }}
+
+
+
+ {{ props.row.fsat }}
+
+
+ {{ props.row.fee }}
+
+
+
+
+
+
+
+
+ Invoice waiting to be paid
+
+
+
+ Copy invoice
+ Close
+
+
+
+
+ Payment Received
+
+
+
+
+ Payment Sent
+
+
+
+
+ Outgoing payment pending
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+ {% raw %}
+
+
+
+ {{receive.lnurl.domain}} is requesting an invoice:
+
+ {% endraw %} {% if LNBITS_DENOMINATION != 'sats' %}
+
+ {% else %}
+
+
+ {% endif %}
+
+
+ {% raw %}
+
+
+
+ Withdraw from {{receive.lnurl.domain}}
+
+ Create invoice
+
+ Cancel
+
+
+
+
+
+
+
+ Copy invoice
+ Close
+
+
+ {% endraw %}
+
+
+
+
+
+
+ {% raw %} {{ parseFloat(String(parse.invoice.fsat).replaceAll(",",
+ "")) / 100 }} {% endraw %} {{LNBITS_DENOMINATION}} {% raw %}
+
+
+ {{ parse.invoice.fsat }}{% endraw %} {{LNBITS_DENOMINATION}} {%
+ raw %}
+
+
+
+ Description: {{ parse.invoice.description }}
+ Expire date: {{ parse.invoice.expireDate }}
+ Hash: {{ parse.invoice.hash }}
+
+ {% endraw %}
+
+ Pay
+ Cancel
+
+
+ Not enough funds!
+ Cancel
+
+
+
+ {% raw %}
+
+
+ Authenticate with {{ parse.lnurlauth.domain }} ?
+
+
+
+ For every website and for every LNbits wallet, a new keypair
+ will be deterministically generated so your identity can't be
+ tied to your LNbits wallet or linked across websites. No other
+ data will be shared with {{ parse.lnurlauth.domain }}.
+
+ Your public key for {{ parse.lnurlauth.domain }} is:
+
+ {{ parse.lnurlauth.pubkey }}
+
+
+ Login
+ Cancel
+
+
+ {% endraw %}
+
+
+
+
+
+
+ Read
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Warning
+
+ BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots
+ and "Save to homescreen"/"Install app" !
+
+
+ Ecash is a bearer asset, meaning you have the funds saved on this
+ page, losing the page without exporting the page will mean you will
+ lose the funds.
+
+
+ Copy wallet URL
+ I understand
+
+
+
+
+
+
+{% endblock %} {% block styles %}
+
+{% endblock %} {% block scripts %}
+
+{% endblock %}
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
new file mode 100644
index 00000000..4ac1f1ce
--- /dev/null
+++ b/lnbits/extensions/cashu/views.py
@@ -0,0 +1,69 @@
+from http import HTTPStatus
+
+from fastapi import Request
+from fastapi.params import Depends
+from fastapi.templating import Jinja2Templates
+from starlette.exceptions import HTTPException
+from starlette.responses import HTMLResponse
+
+from lnbits.core.models import User
+from lnbits.decorators import check_user_exists
+from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE
+
+from . import cashu_ext, cashu_renderer
+from .crud import get_cashu
+
+templates = Jinja2Templates(directory="templates")
+
+
+@cashu_ext.get("/", response_class=HTMLResponse)
+async def index(request: Request, user: User = Depends(check_user_exists)):
+ return cashu_renderer().TemplateResponse(
+ "cashu/index.html", {"request": request, "user": user.dict()}
+ )
+
+
+@cashu_ext.get("/wallet")
+async def cashu(request: Request):
+ return cashu_renderer().TemplateResponse("cashu/wallet.html",{"request": request})
+
+@cashu_ext.get("/mint/{mintID}")
+async def cashu(request: Request, mintID):
+ cashu = await get_cashu(mintID)
+ return cashu_renderer().TemplateResponse("cashu/mint.html",{"request": request, "mint_name": cashu.name})
+
+@cashu_ext.get("/manifest/{cashu_id}.webmanifest")
+async def manifest(cashu_id: str):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+ )
+
+ return {
+ "short_name": LNBITS_SITE_TITLE,
+ "name": cashu.name + " - " + LNBITS_SITE_TITLE,
+ "icons": [
+ {
+ "src": LNBITS_CUSTOM_LOGO
+ if LNBITS_CUSTOM_LOGO
+ else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
+ "type": "image/png",
+ "sizes": "900x900",
+ }
+ ],
+ "start_url": "/cashu/" + cashu_id,
+ "background_color": "#1F2234",
+ "description": "Bitcoin Lightning tPOS",
+ "display": "standalone",
+ "scope": "/cashu/" + cashu_id,
+ "theme_color": "#1F2234",
+ "shortcuts": [
+ {
+ "name": cashu.name + " - " + LNBITS_SITE_TITLE,
+ "short_name": cashu.name,
+ "description": cashu.name + " - " + LNBITS_SITE_TITLE,
+ "url": "/cashu/" + cashu_id,
+ }
+ ],
+ }
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
new file mode 100644
index 00000000..0b16e4ba
--- /dev/null
+++ b/lnbits/extensions/cashu/views_api.py
@@ -0,0 +1,160 @@
+from http import HTTPStatus
+
+import httpx
+from fastapi import Query
+from fastapi.params import Depends
+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.services import create_invoice
+from lnbits.core.views.api import api_payment
+from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
+
+from . import cashu_ext
+from .crud import create_cashu, delete_cashu, get_cashu, get_cashus
+from .models import Cashu, Pegs, PayLnurlWData
+
+
+@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
+async def api_cashus(
+ all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
+):
+ wallet_ids = [wallet.wallet.id]
+ if all_wallets:
+ wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
+
+ return [cashu.dict() for cashu in await get_cashus(wallet_ids)]
+
+
+@cashu_ext.post("/api/v1/cashus", status_code=HTTPStatus.CREATED)
+async def api_cashu_create(
+ data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
+):
+ cashu = await create_cashu(wallet_id=wallet.wallet.id, data=data)
+ logger.debug(cashu)
+ return cashu.dict()
+
+
+@cashu_ext.delete("/api/v1/cashus/{cashu_id}")
+async def api_cashu_delete(
+ cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
+):
+ cashu = await get_cashu(cashu_id)
+
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+ )
+
+ if cashu.wallet != wallet.wallet.id:
+ raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your TPoS.")
+
+ await delete_cashu(cashu_id)
+ raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+
+
+@cashu_ext.post("/api/v1/cashus/{cashu_id}/invoices", status_code=HTTPStatus.CREATED)
+async def api_cashu_create_invoice(
+ amount: int = Query(..., ge=1), tipAmount: int = None, cashu_id: str = None
+):
+ cashu = await get_cashu(cashu_id)
+
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+ )
+
+ if tipAmount:
+ amount += tipAmount
+
+ try:
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=cashu.wallet,
+ amount=amount,
+ memo=f"{cashu.name}",
+ extra={"tag": "cashu", "tipAmount": tipAmount, "cashuId": cashu_id},
+ )
+ except Exception as e:
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+
+ return {"payment_hash": payment_hash, "payment_request": payment_request}
+
+
+@cashu_ext.post(
+ "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay", status_code=HTTPStatus.OK
+)
+async def api_cashu_pay_invoice(
+ lnurl_data: PayLnurlWData, payment_request: str = None, cashu_id: str = None
+):
+ cashu = await get_cashu(cashu_id)
+
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+ )
+
+ lnurl = (
+ lnurl_data.lnurl.replace("lnurlw://", "")
+ .replace("lightning://", "")
+ .replace("LIGHTNING://", "")
+ .replace("lightning:", "")
+ .replace("LIGHTNING:", "")
+ )
+
+ if lnurl.lower().startswith("lnurl"):
+ lnurl = decode_lnurl(lnurl)
+ else:
+ lnurl = "https://" + lnurl
+
+ async with httpx.AsyncClient() as client:
+ try:
+ r = await client.get(lnurl, follow_redirects=True)
+ if r.is_error:
+ lnurl_response = {"success": False, "detail": "Error loading"}
+ else:
+ resp = r.json()
+ if resp["tag"] != "withdrawRequest":
+ lnurl_response = {"success": False, "detail": "Wrong tag type"}
+ else:
+ r2 = await client.get(
+ resp["callback"],
+ follow_redirects=True,
+ params={
+ "k1": resp["k1"],
+ "pr": payment_request,
+ },
+ )
+ resp2 = r2.json()
+ if r2.is_error:
+ lnurl_response = {
+ "success": False,
+ "detail": "Error loading callback",
+ }
+ elif resp2["status"] == "ERROR":
+ lnurl_response = {"success": False, "detail": resp2["reason"]}
+ else:
+ lnurl_response = {"success": True, "detail": resp2}
+ except (httpx.ConnectError, httpx.RequestError):
+ lnurl_response = {"success": False, "detail": "Unexpected error occurred"}
+
+ return lnurl_response
+
+
+@cashu_ext.get(
+ "/api/v1/cashus/{cashu_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK
+)
+async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+ )
+ try:
+ status = await api_payment(payment_hash)
+
+ except Exception as exc:
+ logger.error(exc)
+ return {"paid": False}
+ return status
From f743feb232656d7d6f06ac738776c4d1a25a3f46 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 21 Sep 2022 15:16:38 +0100
Subject: [PATCH 248/696] Broke, started added private/public key
---
lnbits/extensions/cashu/crud.py | 26 +++++++++++++++++++++++---
lnbits/extensions/cashu/migrations.py | 5 +++--
lnbits/extensions/cashu/models.py | 3 ++-
lnbits/extensions/cashu/views_api.py | 12 +++++++++++-
4 files changed, 39 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index ce83653f..f50da111 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -5,13 +5,20 @@ from lnbits.helpers import urlsafe_short_hash
from . import db
from .models import Cashu, Pegs
+from embit import script
+from embit import ec
+from embit.networks import NETWORKS
+from binascii import unhexlify, hexlify
async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
cashu_id = urlsafe_short_hash()
+ prv = ec.PrivateKey.from_wif(urlsafe_short_hash())
+ pub = prv.get_public_key()
+
await db.execute(
"""
- INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins)
- VALUES (?, ?, ?, ?, ?, ?, ?)
+ INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins, prvkey, pubkey)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
cashu_id,
@@ -20,7 +27,9 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
data.tickershort,
data.fraction,
data.maxsats,
- data.coins
+ data.coins,
+ prv,
+ pub
),
)
@@ -29,6 +38,17 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
return cashu
+async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
+ if not wif:
+ prv = ec.PrivateKey.from_wif(urlsafe_short_hash())
+ else:
+ prv = ec.PrivateKey.from_wif(wif)
+ pub = prv.get_public_key()
+ await db.execute("UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", (hexlify(prv.serialize()), hexlify(pub.serialize()), cashu_id))
+ row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
+ return Cashu(**row) if row else None
+
+
async def get_cashu(cashu_id: str) -> Optional[Cashu]:
row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
return Cashu(**row) if row else None
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index 95dc4815..53420062 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -11,8 +11,9 @@ async def m001_initial(db):
tickershort TEXT NOT NULL,
fraction BOOL,
maxsats INT,
- coins INT
-
+ coins INT,
+ prvkey TEXT NOT NULL,
+ pubkey TEXT NOT NULL
);
"""
)
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index 0de15362..892abdf1 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -13,7 +13,8 @@ class Cashu(BaseModel):
fraction: bool = Query(None)
maxsats: int = Query(0)
coins: int = Query(0)
-
+ prvkey: str = Query(None)
+ pubkey: str = Query(None)
@classmethod
def from_row(cls, row: Row) -> "TPoS":
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 0b16e4ba..49383945 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -13,7 +13,7 @@ from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import cashu_ext
-from .crud import create_cashu, delete_cashu, get_cashu, get_cashus
+from .crud import create_cashu, delete_cashu, get_cashu, get_cashus, update_cashu_keys
from .models import Cashu, Pegs, PayLnurlWData
@@ -36,6 +36,16 @@ async def api_cashu_create(
logger.debug(cashu)
return cashu.dict()
+@cashu_ext.post("/api/v1/cashus/upodatekeys", status_code=HTTPStatus.CREATED)
+async def api_cashu_update_keys(
+ data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
+):
+ cashu = await get_cashu(data.id)
+
+ cashu = await create_cashu(wallet_id=wallet.wallet.id, data=data)
+ logger.debug(cashu)
+ return cashu.dict()
+
@cashu_ext.delete("/api/v1/cashus/{cashu_id}")
async def api_cashu_delete(
From 2ea636ec9ba8aa15f5744f7d9223b91004ca2514 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 29 Sep 2022 20:42:13 +0200
Subject: [PATCH 249/696] adjust versions
---
lnbits/extensions/cashu/__init__.py | 5 +-
lnbits/extensions/cashu/config.json | 8 +-
poetry.lock | 812 +++++++++++++++++-----------
pyproject.toml | 35 +-
4 files changed, 528 insertions(+), 332 deletions(-)
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index fa549ad2..d1a1d09c 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -6,9 +6,12 @@ from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
+from cashu.mint.router import router as cashu_router
+
db = Database("ext_cashu")
-cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["TPoS"])
+cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
+cashu_ext.include_router(router=cashu_router)
def cashu_renderer():
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
index c688b22c..d242a3a7 100644
--- a/lnbits/extensions/cashu/config.json
+++ b/lnbits/extensions/cashu/config.json
@@ -1,6 +1,6 @@
{
- "name": "Cashu Ecash",
- "short_description": "Ecash mints with LN peg in/out",
- "icon": "approval",
- "contributors": ["shinobi", "arcbtc", "calle"]
+ "name": "Cashu Mint",
+ "short_description": "Chaumian Ecash mint",
+ "icon": "donut_small_rounded",
+ "contributors": ["calle"]
}
diff --git a/poetry.lock b/poetry.lock
index bbce3c5c..bf757cfe 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -26,11 +26,11 @@ trio = ["trio (>=0.16,<0.22)"]
[[package]]
name = "asgiref"
-version = "3.4.1"
+version = "3.5.2"
description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
@@ -59,11 +59,11 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
[[package]]
name = "attrs"
-version = "21.2.0"
+version = "22.1.0"
description = "Classes Without Boilerplate"
category = "main"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.5"
[package.extras]
dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"]
@@ -122,7 +122,64 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
-name = "cerberus"
+name = "cashu"
+version = "0.1.11"
+description = "Ecash wallet and mint with Bitcoin Lightning support"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+anyio = {version = "3.6.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+attrs = {version = "22.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+bech32 = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+bitstring = {version = "3.1.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+certifi = {version = "2022.9.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+cffi = {version = "1.15.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+charset-normalizer = {version = "2.0.12", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+click = {version = "8.0.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+colorama = {version = "0.4.5", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and platform_system == \"Windows\" or python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
+ecdsa = {version = "0.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+environs = {version = "9.5.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+fastapi = {version = "0.83.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+h11 = {version = "0.12.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+idna = {version = "3.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+importlib-metadata = {version = "4.12.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
+iniconfig = {version = "1.1.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+loguru = {version = "0.6.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+marshmallow = {version = "3.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+outcome = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+packaging = {version = "21.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+pluggy = {version = "1.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+psycopg2-binary = {version = "2.9.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+py = {version = "1.11.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+pycparser = {version = "2.21", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+pydantic = {version = "1.10.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+pyparsing = {version = "3.0.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+pytest = {version = "7.1.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+pytest-asyncio = {version = "0.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+python-dotenv = {version = "0.21.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+represent = {version = "1.6.0.post0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+requests = {version = "2.27.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+secp256k1 = {version = "0.14.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+six = {version = "1.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+sniffio = {version = "1.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+sqlalchemy = {version = "1.3.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+sqlalchemy-aio = {version = "0.17.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+starlette = {version = "0.19.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+tomli = {version = "2.0.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+typing-extensions = {version = "4.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+urllib3 = {version = "1.26.12", markers = "python_version >= \"3.7\" and python_version < \"4\""}
+uvicorn = {version = "0.18.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
+win32-setctime = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
+zipp = {version = "3.8.1", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
+
+[package.source]
+type = "file"
+url = "../cashu/dist/cashu-0.1.11-py3-none-any.whl"
+
+[[package]]
+name = "Cerberus"
version = "1.3.4"
description = "Lightweight, extensible schema and data validation tool for Python dictionaries."
category = "main"
@@ -134,15 +191,15 @@ setuptools = "*"
[[package]]
name = "certifi"
-version = "2021.5.30"
+version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=3.6"
[[package]]
name = "cffi"
-version = "1.15.0"
+version = "1.15.1"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
@@ -153,7 +210,7 @@ pycparser = "*"
[[package]]
name = "charset-normalizer"
-version = "2.0.6"
+version = "2.0.12"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
@@ -164,7 +221,7 @@ unicode-backport = ["unicodedata2"]
[[package]]
name = "click"
-version = "8.0.1"
+version = "8.0.4"
description = "Composable command line interface toolkit"
category = "main"
optional = false
@@ -229,7 +286,7 @@ test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0
[[package]]
name = "ecdsa"
-version = "0.17.0"
+version = "0.18.0"
description = "ECDSA cryptographic signature library (pure python)"
category = "main"
optional = false
@@ -260,7 +317,7 @@ python-versions = "*"
[[package]]
name = "fastapi"
-version = "0.78.0"
+version = "0.83.0"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
category = "main"
optional = false
@@ -349,7 +406,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]]
name = "idna"
-version = "3.2"
+version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
@@ -357,11 +414,11 @@ python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
-version = "4.8.1"
+version = "4.12.0"
description = "Read metadata from Python packages"
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
@@ -376,7 +433,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517",
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
-category = "dev"
+category = "main"
optional = false
python-versions = "*"
@@ -395,8 +452,8 @@ plugins = ["setuptools"]
requirements-deprecated-finder = ["pip-api", "pipreqs"]
[[package]]
-name = "jinja2"
-version = "3.0.1"
+name = "Jinja2"
+version = "3.0.3"
description = "A very fast and expressive template engine."
category = "main"
optional = false
@@ -423,7 +480,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "loguru"
-version = "0.5.3"
+version = "0.6.0"
description = "Python logging made (stupidly) simple"
category = "main"
optional = false
@@ -437,16 +494,16 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"]
[[package]]
-name = "markupsafe"
-version = "2.0.1"
+name = "MarkupSafe"
+version = "2.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[[package]]
name = "marshmallow"
-version = "3.17.0"
+version = "3.18.0"
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
category = "main"
optional = false
@@ -503,11 +560,11 @@ python-versions = "*"
[[package]]
name = "outcome"
-version = "1.1.0"
+version = "1.2.0"
description = "Capture the outcome of Python function calls."
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
attrs = ">=19.2.0"
@@ -558,7 +615,7 @@ test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.6"
@@ -579,7 +636,7 @@ python-versions = ">=3.7"
[[package]]
name = "psycopg2-binary"
-version = "2.9.1"
+version = "2.9.3"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
category = "main"
optional = false
@@ -589,7 +646,7 @@ python-versions = ">=3.6"
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@@ -611,14 +668,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pydantic"
-version = "1.8.2"
-description = "Data validation and settings management using python 3.6 type hinting"
+version = "1.10.2"
+description = "Data validation and settings management using python type hints"
category = "main"
optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.7"
[package.dependencies]
-typing-extensions = ">=3.7.4.3"
+typing-extensions = ">=4.1.0"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
@@ -679,7 +736,7 @@ optional = false
python-versions = "*"
[[package]]
-name = "pyqrcode"
+name = "PyQRCode"
version = "1.2.1"
description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output."
category = "main"
@@ -714,7 +771,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
name = "pytest"
version = "7.1.3"
description = "pytest: simple powerful testing with Python"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.7"
@@ -735,7 +792,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.
name = "pytest-asyncio"
version = "0.19.0"
description = "Pytest support for asyncio"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.7"
@@ -763,17 +820,17 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale
[[package]]
name = "python-dotenv"
-version = "0.19.0"
+version = "0.21.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
-name = "pyyaml"
+name = "PyYAML"
version = "5.4.1"
description = "YAML parser and emitter for Python"
category = "main"
@@ -781,7 +838,7 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[[package]]
-name = "represent"
+name = "Represent"
version = "1.6.0.post0"
description = "Create __repr__ automatically or declaratively."
category = "main"
@@ -850,15 +907,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "sniffio"
-version = "1.2.0"
+version = "1.3.0"
description = "Sniff out which async library your code is running under"
category = "main"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
[[package]]
-name = "sqlalchemy"
-version = "1.3.23"
+name = "SQLAlchemy"
+version = "1.3.24"
description = "Database Abstraction Library"
category = "main"
optional = false
@@ -921,7 +978,7 @@ full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.7"
@@ -943,15 +1000,28 @@ python-versions = "*"
[[package]]
name = "typing-extensions"
-version = "3.10.0.2"
-description = "Backported and Experimental Type Hints for Python 3.5+"
+version = "4.3.0"
+description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
+
+[[package]]
+name = "urllib3"
+version = "1.26.12"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "uvicorn"
-version = "0.18.1"
+version = "0.18.3"
description = "The lightning-fast ASGI server."
category = "main"
optional = false
@@ -1020,11 +1090,11 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
name = "zipp"
-version = "3.5.0"
+version = "3.8.1"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.extras]
docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
@@ -1045,8 +1115,8 @@ anyio = [
{file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
]
asgiref = [
- {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"},
- {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"},
+ {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
+ {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
]
asn1crypto = [
{file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"},
@@ -1057,8 +1127,12 @@ async-timeout = [
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
]
attrs = [
- {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
- {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
+ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
+ {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
+]
+base58 = [
+ {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"},
+ {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"},
]
base58 = [
{file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"},
@@ -1096,72 +1170,125 @@ black = [
{file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
{file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
]
-cerberus = [
+cashu = [
+ {file = "cashu-0.1.11-py3-none-any.whl", hash = "sha256:d2c5a72648fa4487fdf694e5669dfaa8d62445d83cced33b5bc63eccbce6a00c"},
+]
+Cerberus = [
{file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"},
]
certifi = [
- {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
- {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
+ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+ {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
]
cffi = [
- {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
- {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
- {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
- {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
- {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
- {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
- {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
- {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
- {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
- {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
- {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
- {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
- {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
- {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
- {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
- {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
- {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
- {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
- {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
- {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
- {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
- {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
- {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
- {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
- {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
+ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
+ {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
+ {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
+ {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
+ {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
+ {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
+ {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
+ {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
+ {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
+ {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
+ {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
+ {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
+ {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
+ {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
+ {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
+ {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
+ {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
+ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
+ {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
]
charset-normalizer = [
- {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"},
- {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"},
+ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
+ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
]
click = [
- {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
- {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
+ {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
+ {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
+]
+coincurve = [
+ {file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"},
+ {file = "coincurve-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924"},
+ {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb"},
+ {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30dd44d1039f1d237aaa2da6d14a455ca88df3bcb00610b41f3253fdca1be97b"},
+ {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154e2eb5711db8c5ef52fcd80935b5a0e751c057bc6ffb215a7bb409aedef03"},
+ {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c71caffb97dd3d0c243beb62352669b1e5dafa3a4bccdbb27d36bd82f5e65d20"},
+ {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:747215254e51dd4dfbe6dded9235491263da5d88fe372d66541ca16b51ea078f"},
+ {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad2f6df39ba1e2b7b14bb984505ffa7d0a0ecdd697e8d7dbd19e04bc245c87ed"},
+ {file = "coincurve-17.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0503326963916c85b61d16f611ea0545f03c9e418fa8007c233c815429e381e8"},
+ {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1013c1597b65684ae1c3e42497f9ef5a04527fa6136a84a16b34602606428c74"},
+ {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4beef321fd6434448aab03a0c245f31c4e77f43b54b82108c0948d29852ac7e"},
+ {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f47806527d3184da3e8b146fac92a8ed567bbd225194f4517943d8cdc85f9542"},
+ {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51e56373ac79f4ec1cfc5da53d72c55f5e5ac28d848b0849ef5e687ace857888"},
+ {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d694ad194bee9e8792e2e75879dc5238d8a184010cde36c5ad518fcfe2cd8f2"},
+ {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74cedb3d3a1dc5abe0c9c2396e1b82cc64496babc5b42e007e72e185cb1edad8"},
+ {file = "coincurve-17.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:db874c5c1dcb1f3a19379773b5e8cffc777625a7a7a60dd9a67206e31e62e2e9"},
+ {file = "coincurve-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:896b01941254f0a218cf331a9bddfe2d43892f7f1ba10d6e372e2eb744a744c2"},
+ {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6aec70238dbe7a5d66b5f9438ff45b08eb5e0990d49c32ebb65247c5d5b89d7a"},
+ {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24284d17162569df917a640f19d9654ba3b43cf560ced8864f270da903f73a5"},
+ {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ea057f777842396d387103c606babeb3a1b4c6126769cc0a12044312fc6c465"},
+ {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b88642edf7f281649b0c0b6ffade051945ccceae4b885e40445634877d0b3049"},
+ {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a80a207131813b038351c5bdae8f20f5f774bbf53622081f208d040dd2b7528f"},
+ {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1ef72574aa423bc33665ef4be859164a478bad24d48442da874ef3dc39a474d"},
+ {file = "coincurve-17.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfd4fab857bcd975edc39111cb5f5c104f138dac2e9ace35ea8434d37bcea3be"},
+ {file = "coincurve-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73f39579dd651a9fc29da5a8fc0d8153d872bcbc166f876457baced1a1c01501"},
+ {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8852dc01af4f0fe941ffd04069f7e4fecdce9b867a016f823a02286a1a1f07b5"},
+ {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1bef812da1da202cdd601a256825abcf26d86e8634fac3ec3e615e3bb3ff08c"},
+ {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abbefc9ccb170cb255a31df32457c2e43084b9f37589d0694dacc2dea6ddaf7c"},
+ {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:abbd9d017a7638dc38a3b9bb4851f8801b7818d4e5ac22e0c75e373b3c1dbff0"},
+ {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e2c2e8a1f0b1f8e48049c891af4ae3cad65d115d358bde72f6b8abdbb8a23170"},
+ {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c571445b166c714af4f8155e38a894376c16c0431e88963f2fff474a9985d87"},
+ {file = "coincurve-17.0.0-py3-none-win32.whl", hash = "sha256:b956b0b2c85e25a7d00099970ff5d8338254b45e46f0a940f4a2379438ce0dde"},
+ {file = "coincurve-17.0.0-py3-none-win_amd64.whl", hash = "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e"},
+ {file = "coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"},
]
coincurve = [
{file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"},
@@ -1278,8 +1405,8 @@ cryptography = [
{file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"},
]
ecdsa = [
- {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"},
- {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"},
+ {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
+ {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
]
embit = [
{file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"},
@@ -1290,8 +1417,55 @@ enum34 = [
{file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"},
]
fastapi = [
- {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
- {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
+ {file = "fastapi-0.83.0-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"},
+ {file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"},
+]
+grpcio = [
+ {file = "grpcio-1.49.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:fd86040232e805b8e6378b2348c928490ee595b058ce9aaa27ed8e4b0f172b20"},
+ {file = "grpcio-1.49.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6fd0c9cede9552bf00f8c5791d257d5bf3790d7057b26c59df08be5e7a1e021d"},
+ {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d0d402e158d4e84e49c158cb5204119d55e1baf363ee98d6cb5dce321c3a065d"},
+ {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ceec743d42a627e64ea266059a62d214c5a3cdfcd0d7fe2b7a8e4e82527c7"},
+ {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2106d9c16527f0a85e2eea6e6b91a74fc99579c60dd810d8690843ea02bc0f5f"},
+ {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:52dd02b7e7868233c571b49bc38ebd347c3bb1ff8907bb0cb74cb5f00c790afc"},
+ {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:120fecba2ec5d14b5a15d11063b39783fda8dc8d24addd83196acb6582cabd9b"},
+ {file = "grpcio-1.49.1-cp310-cp310-win32.whl", hash = "sha256:f1a3b88e3c53c1a6e6bed635ec1bbb92201bb6a1f2db186179f7f3f244829788"},
+ {file = "grpcio-1.49.1-cp310-cp310-win_amd64.whl", hash = "sha256:a7d0017b92d3850abea87c1bdec6ea41104e71c77bca44c3e17f175c6700af62"},
+ {file = "grpcio-1.49.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:9fb17ff8c0d56099ac6ebfa84f670c5a62228d6b5c695cf21c02160c2ac1446b"},
+ {file = "grpcio-1.49.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:075f2d06e3db6b48a2157a1bcd52d6cbdca980dd18988fe6afdb41795d51625f"},
+ {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46d93a1b4572b461a227f1db6b8d35a88952db1c47e5fadcf8b8a2f0e1dd9201"},
+ {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc79b2b37d779ac42341ddef40ad5bf0966a64af412c89fc2b062e3ddabb093f"},
+ {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5f8b3a971c7820ea9878f3fd70086240a36aeee15d1b7e9ecbc2743b0e785568"},
+ {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49b301740cf5bc8fed4fee4c877570189ae3951432d79fa8e524b09353659811"},
+ {file = "grpcio-1.49.1-cp311-cp311-win32.whl", hash = "sha256:1c66a25afc6c71d357867b341da594a5587db5849b48f4b7d5908d236bb62ede"},
+ {file = "grpcio-1.49.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b6c3a95d27846f4145d6967899b3ab25fffc6ae99544415e1adcacef84842d2"},
+ {file = "grpcio-1.49.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:1cc400c8a2173d1c042997d98a9563e12d9bb3fb6ad36b7f355bc77c7663b8af"},
+ {file = "grpcio-1.49.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:34f736bd4d0deae90015c0e383885b431444fe6b6c591dea288173df20603146"},
+ {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:196082b9c89ebf0961dcd77cb114bed8171964c8e3063b9da2fb33536a6938ed"},
+ {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c9f89c42749890618cd3c2464e1fbf88446e3d2f67f1e334c8e5db2f3272bbd"},
+ {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64419cb8a5b612cdb1550c2fd4acbb7d4fb263556cf4625f25522337e461509e"},
+ {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8a5272061826e6164f96e3255405ef6f73b88fd3e8bef464c7d061af8585ac62"},
+ {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ea9d0172445241ad7cb49577314e39d0af2c5267395b3561d7ced5d70458a9f3"},
+ {file = "grpcio-1.49.1-cp37-cp37m-win32.whl", hash = "sha256:2070e87d95991473244c72d96d13596c751cb35558e11f5df5414981e7ed2492"},
+ {file = "grpcio-1.49.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fcedcab49baaa9db4a2d240ac81f2d57eb0052b1c6a9501b46b8ae912720fbf"},
+ {file = "grpcio-1.49.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:afbb3475cf7f4f7d380c2ca37ee826e51974f3e2665613996a91d6a58583a534"},
+ {file = "grpcio-1.49.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a4f9ba141380abde6c3adc1727f21529137a2552002243fa87c41a07e528245c"},
+ {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:cf0a1fb18a7204b9c44623dfbd1465b363236ce70c7a4ed30402f9f60d8b743b"},
+ {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17bb6fe72784b630728c6cff9c9d10ccc3b6d04e85da6e0a7b27fb1d135fac62"},
+ {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18305d5a082d1593b005a895c10041f833b16788e88b02bb81061f5ebcc465df"},
+ {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b6a1b39e59ac5a3067794a0e498911cf2e37e4b19ee9e9977dc5e7051714f13f"},
+ {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e20d59aafc086b1cc68400463bddda6e41d3e5ed30851d1e2e0f6a2e7e342d3"},
+ {file = "grpcio-1.49.1-cp38-cp38-win32.whl", hash = "sha256:e1e83233d4680863a421f3ee4a7a9b80d33cd27ee9ed7593bc93f6128302d3f2"},
+ {file = "grpcio-1.49.1-cp38-cp38-win_amd64.whl", hash = "sha256:221d42c654d2a41fa31323216279c73ed17d92f533bc140a3390cc1bd78bf63c"},
+ {file = "grpcio-1.49.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:fa9e6e61391e99708ac87fc3436f6b7b9c6b845dc4639b406e5e61901e1aacde"},
+ {file = "grpcio-1.49.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9b449e966ef518ce9c860d21f8afe0b0f055220d95bc710301752ac1db96dd6a"},
+ {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aa34d2ad9f24e47fa9a3172801c676e4037d862247e39030165fe83821a7aafd"},
+ {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5207f4eed1b775d264fcfe379d8541e1c43b878f2b63c0698f8f5c56c40f3d68"},
+ {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b24a74651438d45619ac67004638856f76cc13d78b7478f2457754cbcb1c8ad"},
+ {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fe763781669790dc8b9618e7e677c839c87eae6cf28b655ee1fa69ae04eea03f"},
+ {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2f2ff7ba0f8f431f32d4b4bc3a3713426949d3533b08466c4ff1b2b475932ca8"},
+ {file = "grpcio-1.49.1-cp39-cp39-win32.whl", hash = "sha256:08ff74aec8ff457a89b97152d36cb811dcc1d17cd5a92a65933524e363327394"},
+ {file = "grpcio-1.49.1-cp39-cp39-win_amd64.whl", hash = "sha256:274ffbb39717918c514b35176510ae9be06e1d93121e84d50b350861dcb9a705"},
+ {file = "grpcio-1.49.1.tar.gz", hash = "sha256:d4725fc9ec8e8822906ae26bb26f5546891aa7fbc3443de970cc556d43a5c99f"},
]
grpcio = [
{file = "grpcio-1.50.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974"},
@@ -1389,12 +1563,12 @@ httpx = [
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
]
idna = [
- {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
- {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
+ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
importlib-metadata = [
- {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
- {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
+ {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
+ {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
@@ -1404,92 +1578,63 @@ isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
-jinja2 = [
- {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
- {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
+Jinja2 = [
+ {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
+ {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
]
lnurl = [
{file = "lnurl-0.3.6-py3-none-any.whl", hash = "sha256:579982fd8c4d25bc84c61c74ec45cb7999fa1fa2426f5d5aeb0160ba333b9c92"},
{file = "lnurl-0.3.6.tar.gz", hash = "sha256:8af07460115a48f3122a5a9c9a6062bee3897d5f6ab4c9a60f6561a83a8234f6"},
]
loguru = [
- {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"},
- {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"},
+ {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"},
+ {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"},
]
-markupsafe = [
- {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
- {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
- {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
- {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
- {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
- {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
- {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
- {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
- {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
- {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
- {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
- {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
- {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
- {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
- {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
+MarkupSafe = [
+ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
+ {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
+ {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
+ {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
+ {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
+ {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
marshmallow = [
- {file = "marshmallow-3.17.0-py3-none-any.whl", hash = "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb"},
- {file = "marshmallow-3.17.0.tar.gz", hash = "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7"},
+ {file = "marshmallow-3.18.0-py3-none-any.whl", hash = "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104"},
+ {file = "marshmallow-3.18.0.tar.gz", hash = "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7"},
]
mock = [
{file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"},
@@ -1525,8 +1670,8 @@ mypy-extensions = [
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
outcome = [
- {file = "outcome-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958"},
- {file = "outcome-1.1.0.tar.gz", hash = "sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967"},
+ {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"},
+ {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
@@ -1565,42 +1710,62 @@ protobuf = [
{file = "protobuf-4.21.8.tar.gz", hash = "sha256:427426593b55ff106c84e4a88cac855175330cb6eb7e889e85aaa7b5652b686d"},
]
psycopg2-binary = [
- {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
- {file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"},
- {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:542875f62bc56e91c6eac05a0deadeae20e1730be4c6334d8f04c944fcd99759"},
- {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661509f51531ec125e52357a489ea3806640d0ca37d9dada461ffc69ee1e7b6e"},
- {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:d92272c7c16e105788efe2cfa5d680f07e34e0c29b03c1908f8636f55d5f915a"},
- {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:736b8797b58febabb85494142c627bd182b50d2a7ec65322983e71065ad3034c"},
- {file = "psycopg2_binary-2.9.1-cp310-cp310-win32.whl", hash = "sha256:ebccf1123e7ef66efc615a68295bf6fdba875a75d5bba10a05073202598085fc"},
- {file = "psycopg2_binary-2.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:1f6ca4a9068f5c5c57e744b4baa79f40e83e3746875cac3c45467b16326bab45"},
- {file = "psycopg2_binary-2.9.1-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76"},
- {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698"},
- {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616"},
- {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7"},
- {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e"},
- {file = "psycopg2_binary-2.9.1-cp36-cp36m-win32.whl", hash = "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d"},
- {file = "psycopg2_binary-2.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce"},
- {file = "psycopg2_binary-2.9.1-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a"},
- {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a"},
- {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0"},
- {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed"},
- {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f"},
- {file = "psycopg2_binary-2.9.1-cp37-cp37m-win32.whl", hash = "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e"},
- {file = "psycopg2_binary-2.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2"},
- {file = "psycopg2_binary-2.9.1-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140"},
- {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917"},
- {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90"},
- {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72"},
- {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34"},
- {file = "psycopg2_binary-2.9.1-cp38-cp38-win32.whl", hash = "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32"},
- {file = "psycopg2_binary-2.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf"},
- {file = "psycopg2_binary-2.9.1-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a"},
- {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4"},
- {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31"},
- {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd"},
- {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a"},
- {file = "psycopg2_binary-2.9.1-cp39-cp39-win32.whl", hash = "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975"},
- {file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"},
+ {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-win32.whl", hash = "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029"},
+ {file = "psycopg2_binary-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b"},
+ {file = "psycopg2_binary-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"},
+ {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"},
+ {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"},
+ {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"},
]
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
@@ -1640,28 +1805,54 @@ pycryptodomex = [
{file = "pycryptodomex-3.14.1.tar.gz", hash = "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"},
]
pydantic = [
- {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
- {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
- {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"},
- {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"},
- {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"},
- {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"},
- {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"},
- {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"},
- {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"},
- {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"},
- {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"},
- {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"},
- {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"},
- {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"},
- {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"},
- {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"},
- {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"},
- {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"},
- {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"},
- {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"},
- {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
- {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
+ {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"},
+ {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"},
+ {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"},
+ {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"},
+ {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"},
+ {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"},
+ {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"},
+ {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"},
+ {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"},
+ {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"},
+ {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"},
+ {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"},
+ {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"},
+ {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"},
+ {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"},
+ {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"},
+ {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"},
+ {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"},
+ {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"},
+ {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"},
+ {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"},
+ {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"},
+ {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"},
+ {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"},
+ {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"},
+ {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"},
+ {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"},
+ {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"},
+ {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"},
+ {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"},
+ {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"},
+ {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"},
+ {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"},
+ {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"},
+ {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
+ {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
+]
+pyln-bolt7 = [
+ {file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"},
+ {file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"},
+]
+pyln-client = [
+ {file = "pyln-client-0.12.1.tar.gz", hash = "sha256:f14fa7947b65ecde2753984452441cf41b7b25b1a0ba7beced48786fa54d2bfe"},
+ {file = "pyln_client-0.12.1-py3-none-any.whl", hash = "sha256:6b500bcc49e4028d50692b962d9c9f7e9ede920d718f9b9412f04f7db0aa0e63"},
+]
+pyln-proto = [
+ {file = "pyln-proto-0.12.0.tar.gz", hash = "sha256:3214d99d8385f2135a94937f0dc1da626a33b257e9ebc320841656edaefabbe5"},
+ {file = "pyln_proto-0.12.0-py3-none-any.whl", hash = "sha256:dedef5d8e476a9ade5a0b2eb919ccc37e4a57f2a78fdc399f1c5e0de17e41604"},
]
pyln-bolt7 = [
{file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"},
@@ -1682,7 +1873,7 @@ pyparsing = [
pypng = [
{file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"},
]
-pyqrcode = [
+PyQRCode = [
{file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"},
{file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"},
]
@@ -1707,10 +1898,10 @@ pytest-cov = [
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
]
python-dotenv = [
- {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
- {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
+ {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
+ {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
]
-pyyaml = [
+PyYAML = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
@@ -1741,10 +1932,14 @@ pyyaml = [
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
]
-represent = [
+Represent = [
{file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"},
{file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"},
]
+requests = [
+ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
+ {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
+]
rfc3986 = [
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
@@ -1787,48 +1982,44 @@ six = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
sniffio = [
- {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
- {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
+ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
]
-sqlalchemy = [
- {file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"},
- {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1"},
- {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5"},
- {file = "SQLAlchemy-1.3.23-cp27-cp27m-win32.whl", hash = "sha256:b4b0e44d586cd64b65b507fa116a3814a1a53d55dce4836d7c1a6eb2823ff8d1"},
- {file = "SQLAlchemy-1.3.23-cp27-cp27m-win_amd64.whl", hash = "sha256:6b8b8c80c7f384f06825612dd078e4a31f0185e8f1f6b8c19e188ff246334205"},
- {file = "SQLAlchemy-1.3.23-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9e9c25522933e569e8b53ccc644dc993cab87e922fb7e142894653880fdd419d"},
- {file = "SQLAlchemy-1.3.23-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a0e306e9bb76fd93b29ae3a5155298e4c1b504c7cbc620c09c20858d32d16234"},
- {file = "SQLAlchemy-1.3.23-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:6c9e6cc9237de5660bcddea63f332428bb83c8e2015c26777281f7ffbd2efb84"},
- {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:94f667d86be82dd4cb17d08de0c3622e77ca865320e0b95eae6153faa7b4ecaf"},
- {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:751934967f5336a3e26fc5993ccad1e4fee982029f9317eb6153bc0bc3d2d2da"},
- {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:63677d0c08524af4c5893c18dbe42141de7178001360b3de0b86217502ed3601"},
- {file = "SQLAlchemy-1.3.23-cp35-cp35m-win32.whl", hash = "sha256:ddfb511e76d016c3a160910642d57f4587dc542ce5ee823b0d415134790eeeb9"},
- {file = "SQLAlchemy-1.3.23-cp35-cp35m-win_amd64.whl", hash = "sha256:040bdfc1d76a9074717a3f43455685f781c581f94472b010cd6c4754754e1862"},
- {file = "SQLAlchemy-1.3.23-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d1a85dfc5dee741bf49cb9b6b6b8d2725a268e4992507cf151cba26b17d97c37"},
- {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:639940bbe1108ac667dcffc79925db2966826c270112e9159439ab6bb14f8d80"},
- {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e8a1750b44ad6422ace82bf3466638f1aa0862dbb9689690d5f2f48cce3476c8"},
- {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e5bb3463df697279e5459a7316ad5a60b04b0107f9392e88674d0ece70e9cf70"},
- {file = "SQLAlchemy-1.3.23-cp36-cp36m-win32.whl", hash = "sha256:e273367f4076bd7b9a8dc2e771978ef2bfd6b82526e80775a7db52bff8ca01dd"},
- {file = "SQLAlchemy-1.3.23-cp36-cp36m-win_amd64.whl", hash = "sha256:ac2244e64485c3778f012951fdc869969a736cd61375fde6096d08850d8be729"},
- {file = "SQLAlchemy-1.3.23-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:23927c3981d1ec6b4ea71eb99d28424b874d9c696a21e5fbd9fa322718be3708"},
- {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d90010304abb4102123d10cbad2cdf2c25a9f2e66a50974199b24b468509bad5"},
- {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a8bfc1e1afe523e94974132d7230b82ca7fa2511aedde1f537ec54db0399541a"},
- {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:269990b3ab53cb035d662dcde51df0943c1417bdab707dc4a7e4114a710504b4"},
- {file = "SQLAlchemy-1.3.23-cp37-cp37m-win32.whl", hash = "sha256:fdd2ed7395df8ac2dbb10cefc44737b66c6a5cd7755c92524733d7a443e5b7e2"},
- {file = "SQLAlchemy-1.3.23-cp37-cp37m-win_amd64.whl", hash = "sha256:6a939a868fdaa4b504e8b9d4a61f21aac11e3fecc8a8214455e144939e3d2aea"},
- {file = "SQLAlchemy-1.3.23-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:24f9569e82a009a09ce2d263559acb3466eba2617203170e4a0af91e75b4f075"},
- {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2578dbdbe4dbb0e5126fb37ffcd9793a25dcad769a95f171a2161030bea850ff"},
- {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1fe5d8d39118c2b018c215c37b73fd6893c3e1d4895be745ca8ff6eb83333ed3"},
- {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:c7dc052432cd5d060d7437e217dd33c97025287f99a69a50e2dc1478dd610d64"},
- {file = "SQLAlchemy-1.3.23-cp38-cp38-win32.whl", hash = "sha256:ecce8c021894a77d89808222b1ff9687ad84db54d18e4bd0500ca766737faaf6"},
- {file = "SQLAlchemy-1.3.23-cp38-cp38-win_amd64.whl", hash = "sha256:37b83bf81b4b85dda273aaaed5f35ea20ad80606f672d94d2218afc565fb0173"},
- {file = "SQLAlchemy-1.3.23-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:8be835aac18ec85351385e17b8665bd4d63083a7160a017bef3d640e8e65cadb"},
- {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6ec1044908414013ebfe363450c22f14698803ce97fbb47e53284d55c5165848"},
- {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:eab063a70cca4a587c28824e18be41d8ecc4457f8f15b2933584c6c6cccd30f0"},
- {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:baeb451ee23e264de3f577fee5283c73d9bbaa8cb921d0305c0bbf700094b65b"},
- {file = "SQLAlchemy-1.3.23-cp39-cp39-win32.whl", hash = "sha256:94208867f34e60f54a33a37f1c117251be91a47e3bfdb9ab8a7847f20886ad06"},
- {file = "SQLAlchemy-1.3.23-cp39-cp39-win_amd64.whl", hash = "sha256:f4d972139d5000105fcda9539a76452039434013570d6059993120dc2a65e447"},
- {file = "SQLAlchemy-1.3.23.tar.gz", hash = "sha256:6fca33672578666f657c131552c4ef8979c1606e494f78cd5199742dfb26918b"},
+SQLAlchemy = [
+ {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"},
+ {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"},
+ {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"},
+ {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"},
+ {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"},
+ {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"},
+ {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"},
+ {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"},
+ {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"},
+ {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"},
+ {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"},
+ {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"},
+ {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"},
+ {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"},
+ {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"},
+ {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"},
+ {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"},
+ {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"},
+ {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"},
+ {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"},
+ {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"},
+ {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"},
+ {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"},
+ {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"},
+ {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"},
+ {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"},
+ {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"},
+ {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"},
+ {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"},
+ {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"},
+ {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"},
+ {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"},
+ {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"},
+ {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"},
]
sqlalchemy-aio = [
{file = "sqlalchemy_aio-0.17.0-py3-none-any.whl", hash = "sha256:3f4aa392c38f032d6734826a4138a0f02ed3122d442ed142be1e5964f2a33b60"},
@@ -1876,13 +2067,16 @@ types-protobuf = [
{file = "types_protobuf-3.20.4.1-py3-none-any.whl", hash = "sha256:c227975ffd0f6a1eb1754e9a3aa9ca3b12265e63b462e9761e824c41fd25331c"},
]
typing-extensions = [
- {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
- {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
- {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
+ {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
+ {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
+]
+urllib3 = [
+ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
+ {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
]
uvicorn = [
- {file = "uvicorn-0.18.1-py3-none-any.whl", hash = "sha256:013c4ea0787cc2dc456ef4368e18c01982e6be57903e4d3183218e543eb889b7"},
- {file = "uvicorn-0.18.1.tar.gz", hash = "sha256:35703e6518105cfe53f16a5a9435db3e2e227d0784f1fd8fbc1214b1fdc108df"},
+ {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"},
+ {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"},
]
uvloop = [
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"},
@@ -1942,6 +2136,6 @@ win32-setctime = [
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
]
zipp = [
- {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"},
- {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"},
+ {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"},
+ {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"},
]
diff --git a/pyproject.toml b/pyproject.toml
index b99bb353..69d58edd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,8 +11,8 @@ script = "build.py"
[tool.poetry.dependencies]
python = "^3.10 | ^3.9 | ^3.8 | ^3.7"
aiofiles = "0.8.0"
-asgiref = "3.4.1"
-attrs = "21.2.0"
+asgiref = "^3.4.1"
+attrs = "22.1.0"
bech32 = "1.2.0"
bitstring = "3.1.9"
certifi = "2021.5.30"
@@ -22,19 +22,18 @@ ecdsa = "0.17.0"
embit = "0.4.9"
fastapi = "0.78.0"
h11 = "0.12.0"
-httpcore = "0.15.0"
httptools = "0.4.0"
httpx = "0.23.0"
-idna = "3.2"
-importlib-metadata = "4.8.1"
-jinja2 = "3.0.1"
+idna = "3.4"
+importlib-metadata = "^4.8.1"
+jinja2 = "3.0.3"
lnurl = "0.3.6"
-markupsafe = "2.0.1"
-marshmallow = "3.17.0"
-outcome = "1.1.0"
-psycopg2-binary = "2.9.1"
+markupsafe = "2.1.1"
+marshmallow = "3.18.0"
+outcome = "1.2.0"
+psycopg2-binary = "2.9.3"
pycryptodomex = "3.14.1"
-pydantic = "1.8.2"
+pydantic = "1.10.2"
pypng = "0.0.21"
pyqrcode = "1.2.1"
pyScss = "1.4.0"
@@ -45,18 +44,18 @@ rfc3986 = "1.5.0"
secp256k1 = "0.14.0"
shortuuid = "1.0.1"
six = "1.16.0"
-sniffio = "1.2.0"
-sqlalchemy = "1.3.23"
+sniffio = "1.3.0"
+sqlalchemy = "1.3.24"
sqlalchemy-aio = "0.17.0"
sse-starlette = "0.6.2"
-typing-extensions = "3.10.0.2"
-uvicorn = "0.18.1"
+typing-extensions = "4.3.0"
+uvicorn = "0.18.3"
uvloop = "0.16.0"
watchgod = "0.7"
websockets = "10.0"
-zipp = "3.5.0"
-loguru = "0.5.3"
-cffi = "1.15.0"
+zipp = "^3.5.0"
+loguru = "0.6.0"
+cffi = "1.15.1"
websocket-client = "1.3.3"
grpcio = "^1.49.1"
protobuf = "^4.21.6"
From 70ca997c114a4607a1c411336eb061b77daf7301 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 29 Sep 2022 20:42:13 +0200
Subject: [PATCH 250/696] Revert "adjust versions"
This reverts commit 4303af4f1456040f922c6283f1415052a8308ab5.
---
lnbits/extensions/cashu/__init__.py | 5 +-
lnbits/extensions/cashu/config.json | 8 +-
poetry.lock | 919 ++++++++++------------------
pyproject.toml | 35 +-
4 files changed, 360 insertions(+), 607 deletions(-)
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index d1a1d09c..fa549ad2 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -6,12 +6,9 @@ from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
-from cashu.mint.router import router as cashu_router
-
db = Database("ext_cashu")
-cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
-cashu_ext.include_router(router=cashu_router)
+cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["TPoS"])
def cashu_renderer():
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
index d242a3a7..c688b22c 100644
--- a/lnbits/extensions/cashu/config.json
+++ b/lnbits/extensions/cashu/config.json
@@ -1,6 +1,6 @@
{
- "name": "Cashu Mint",
- "short_description": "Chaumian Ecash mint",
- "icon": "donut_small_rounded",
- "contributors": ["calle"]
+ "name": "Cashu Ecash",
+ "short_description": "Ecash mints with LN peg in/out",
+ "icon": "approval",
+ "contributors": ["shinobi", "arcbtc", "calle"]
}
diff --git a/poetry.lock b/poetry.lock
index bf757cfe..079b2b99 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -26,25 +26,25 @@ trio = ["trio (>=0.16,<0.22)"]
[[package]]
name = "asgiref"
-version = "3.5.2"
+version = "3.4.1"
description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6"
[package.dependencies]
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
-tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
+tests = ["pytest", "pytest-asyncio", "mypy (>=0.800)"]
[[package]]
-name = "asn1crypto"
-version = "1.5.1"
-description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP"
-category = "main"
+name = "atomicwrites"
+version = "1.4.1"
+description = "Atomic file writes."
+category = "dev"
optional = false
-python-versions = "*"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "async-timeout"
@@ -59,11 +59,11 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
[[package]]
name = "attrs"
-version = "22.1.0"
+version = "21.2.0"
description = "Classes Without Boilerplate"
category = "main"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.extras]
dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"]
@@ -122,84 +122,24 @@ jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
-name = "cashu"
-version = "0.1.11"
-description = "Ecash wallet and mint with Bitcoin Lightning support"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-
-[package.dependencies]
-anyio = {version = "3.6.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-attrs = {version = "22.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-bech32 = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-bitstring = {version = "3.1.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-certifi = {version = "2022.9.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-cffi = {version = "1.15.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-charset-normalizer = {version = "2.0.12", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-click = {version = "8.0.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-colorama = {version = "0.4.5", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and platform_system == \"Windows\" or python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
-ecdsa = {version = "0.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-environs = {version = "9.5.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-fastapi = {version = "0.83.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-h11 = {version = "0.12.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-idna = {version = "3.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-importlib-metadata = {version = "4.12.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
-iniconfig = {version = "1.1.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-loguru = {version = "0.6.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-marshmallow = {version = "3.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-outcome = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-packaging = {version = "21.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-pluggy = {version = "1.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-psycopg2-binary = {version = "2.9.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-py = {version = "1.11.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-pycparser = {version = "2.21", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-pydantic = {version = "1.10.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-pyparsing = {version = "3.0.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-pytest = {version = "7.1.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-pytest-asyncio = {version = "0.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-python-dotenv = {version = "0.21.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-represent = {version = "1.6.0.post0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-requests = {version = "2.27.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-secp256k1 = {version = "0.14.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-six = {version = "1.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-sniffio = {version = "1.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-sqlalchemy = {version = "1.3.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-sqlalchemy-aio = {version = "0.17.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-starlette = {version = "0.19.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-tomli = {version = "2.0.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-typing-extensions = {version = "4.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-urllib3 = {version = "1.26.12", markers = "python_version >= \"3.7\" and python_version < \"4\""}
-uvicorn = {version = "0.18.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
-win32-setctime = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
-zipp = {version = "3.8.1", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
-
-[package.source]
-type = "file"
-url = "../cashu/dist/cashu-0.1.11-py3-none-any.whl"
-
-[[package]]
-name = "Cerberus"
+name = "cerberus"
version = "1.3.4"
description = "Lightweight, extensible schema and data validation tool for Python dictionaries."
category = "main"
optional = false
python-versions = ">=2.7"
-[package.dependencies]
-setuptools = "*"
-
[[package]]
name = "certifi"
-version = "2022.9.24"
+version = "2021.5.30"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = "*"
[[package]]
name = "cffi"
-version = "1.15.1"
+version = "1.15.0"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
@@ -210,7 +150,7 @@ pycparser = "*"
[[package]]
name = "charset-normalizer"
-version = "2.0.12"
+version = "2.0.6"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
@@ -221,7 +161,7 @@ unicode-backport = ["unicodedata2"]
[[package]]
name = "click"
-version = "8.0.4"
+version = "8.0.1"
description = "Composable command line interface toolkit"
category = "main"
optional = false
@@ -231,18 +171,6 @@ python-versions = ">=3.6"
colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
-[[package]]
-name = "coincurve"
-version = "17.0.0"
-description = "Cross-platform Python CFFI bindings for libsecp256k1"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-
-[package.dependencies]
-asn1crypto = "*"
-cffi = ">=1.3.0"
-
[[package]]
name = "colorama"
version = "0.4.6"
@@ -265,28 +193,9 @@ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.1
[package.extras]
toml = ["tomli"]
-[[package]]
-name = "cryptography"
-version = "36.0.2"
-description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
-category = "main"
-optional = false
-python-versions = ">=3.6"
-
-[package.dependencies]
-cffi = ">=1.12"
-
-[package.extras]
-docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"]
-docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
-pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
-sdist = ["setuptools_rust (>=0.11.4)"]
-ssh = ["bcrypt (>=3.1.5)"]
-test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
-
[[package]]
name = "ecdsa"
-version = "0.18.0"
+version = "0.17.0"
description = "ECDSA cryptographic signature library (pure python)"
category = "main"
optional = false
@@ -317,7 +226,7 @@ python-versions = "*"
[[package]]
name = "fastapi"
-version = "0.83.0"
+version = "0.78.0"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
category = "main"
optional = false
@@ -399,14 +308,14 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*"
[package.extras]
-brotli = ["brotli", "brotlicffi"]
-cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
+brotli = ["brotlicffi", "brotli"]
+cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
http2 = ["h2 (>=3,<5)"]
socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]]
name = "idna"
-version = "3.4"
+version = "3.2"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
@@ -414,11 +323,11 @@ python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
-version = "4.12.0"
+version = "4.8.1"
description = "Read metadata from Python packages"
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
@@ -433,7 +342,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517",
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
-category = "main"
+category = "dev"
optional = false
python-versions = "*"
@@ -452,8 +361,8 @@ plugins = ["setuptools"]
requirements-deprecated-finder = ["pip-api", "pipreqs"]
[[package]]
-name = "Jinja2"
-version = "3.0.3"
+name = "jinja2"
+version = "3.0.1"
description = "A very fast and expressive template engine."
category = "main"
optional = false
@@ -480,7 +389,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "loguru"
-version = "0.6.0"
+version = "0.5.3"
description = "Python logging made (stupidly) simple"
category = "main"
optional = false
@@ -494,16 +403,16 @@ win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"]
[[package]]
-name = "MarkupSafe"
-version = "2.1.1"
+name = "markupsafe"
+version = "2.0.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6"
[[package]]
name = "marshmallow"
-version = "3.18.0"
+version = "3.17.0"
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
category = "main"
optional = false
@@ -527,7 +436,7 @@ optional = false
python-versions = ">=3.6"
[package.extras]
-build = ["blurb", "twine", "wheel"]
+build = ["twine", "wheel", "blurb"]
docs = ["sphinx"]
test = ["pytest (<5.4)", "pytest-cov"]
@@ -560,11 +469,11 @@ python-versions = "*"
[[package]]
name = "outcome"
-version = "1.2.0"
+version = "1.1.0"
description = "Capture the outcome of Python function calls."
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6"
[package.dependencies]
attrs = ">=19.2.0"
@@ -580,24 +489,13 @@ python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
-[[package]]
-name = "pathlib2"
-version = "2.3.7.post1"
-description = "Object-oriented filesystem paths"
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-six = "*"
-
[[package]]
name = "pathspec"
-version = "0.10.1"
+version = "0.9.0"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
-python-versions = ">=3.7"
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]]
name = "platformdirs"
@@ -608,14 +506,14 @@ optional = false
python-versions = ">=3.7"
[package.extras]
-docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
-test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
+docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"]
+test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"]
[[package]]
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
-category = "main"
+category = "dev"
optional = false
python-versions = ">=3.6"
@@ -636,7 +534,7 @@ python-versions = ">=3.7"
[[package]]
name = "psycopg2-binary"
-version = "2.9.3"
+version = "2.9.1"
description = "psycopg2 - Python-PostgreSQL Database Adapter"
category = "main"
optional = false
@@ -646,7 +544,7 @@ python-versions = ">=3.6"
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
-category = "main"
+category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@@ -668,14 +566,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pydantic"
-version = "1.10.2"
-description = "Data validation and settings management using python type hints"
+version = "1.8.2"
+description = "Data validation and settings management using python 3.6 type hinting"
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6.1"
[package.dependencies]
-typing-extensions = ">=4.1.0"
+typing-extensions = ">=3.7.4.3"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
@@ -725,7 +623,7 @@ optional = false
python-versions = ">=3.6.8"
[package.extras]
-diagrams = ["jinja2", "railroad-diagrams"]
+diagrams = ["railroad-diagrams", "jinja2"]
[[package]]
name = "pypng"
@@ -736,7 +634,7 @@ optional = false
python-versions = "*"
[[package]]
-name = "PyQRCode"
+name = "pyqrcode"
version = "1.2.1"
description = "A QR code generator written purely in Python with SVG, EPS, PNG and terminal output."
category = "main"
@@ -755,8 +653,6 @@ optional = false
python-versions = "*"
[package.dependencies]
-enum34 = "*"
-pathlib2 = "*"
six = "*"
[[package]]
@@ -769,13 +665,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]]
name = "pytest"
-version = "7.1.3"
+version = "7.1.2"
description = "pytest: simple powerful testing with Python"
-category = "main"
+category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
+atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""}
attrs = ">=19.2.0"
colorama = {version = "*", markers = "sys_platform == \"win32\""}
importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""}
@@ -792,7 +689,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.
name = "pytest-asyncio"
version = "0.19.0"
description = "Pytest support for asyncio"
-category = "main"
+category = "dev"
optional = false
python-versions = ">=3.7"
@@ -801,7 +698,7 @@ pytest = ">=6.1.0"
typing-extensions = {version = ">=3.7.2", markers = "python_version < \"3.8\""}
[package.extras]
-testing = ["coverage (>=6.2)", "flaky (>=3.5.0)", "hypothesis (>=5.7.1)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
+testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)", "flaky (>=3.5.0)", "mypy (>=0.931)", "pytest-trio (>=0.7.0)"]
[[package]]
name = "pytest-cov"
@@ -816,21 +713,21 @@ coverage = {version = ">=5.2.1", extras = ["toml"]}
pytest = ">=4.6"
[package.extras]
-testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"]
+testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"]
[[package]]
name = "python-dotenv"
-version = "0.21.0"
+version = "0.19.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.5"
[package.extras]
cli = ["click (>=5.0)"]
[[package]]
-name = "PyYAML"
+name = "pyyaml"
version = "5.4.1"
description = "YAML parser and emitter for Python"
category = "main"
@@ -838,7 +735,7 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
[[package]]
-name = "Represent"
+name = "represent"
version = "1.6.0.post0"
description = "Create __repr__ automatically or declaratively."
category = "main"
@@ -907,15 +804,15 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "sniffio"
-version = "1.3.0"
+version = "1.2.0"
description = "Sniff out which async library your code is running under"
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.5"
[[package]]
-name = "SQLAlchemy"
-version = "1.3.24"
+name = "sqlalchemy"
+version = "1.3.23"
description = "Database Abstraction Library"
category = "main"
optional = false
@@ -926,7 +823,7 @@ mssql = ["pyodbc"]
mssql-pymssql = ["pymssql"]
mssql-pyodbc = ["pyodbc"]
mysql = ["mysqlclient"]
-oracle = ["cx_oracle"]
+oracle = ["cx-oracle"]
postgresql = ["psycopg2"]
postgresql-pg8000 = ["pg8000 (<1.16.6)"]
postgresql-psycopg2binary = ["psycopg2-binary"]
@@ -978,7 +875,7 @@ full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
-category = "main"
+category = "dev"
optional = false
python-versions = ">=3.7"
@@ -1000,28 +897,15 @@ python-versions = "*"
[[package]]
name = "typing-extensions"
-version = "4.3.0"
-description = "Backported and Experimental Type Hints for Python 3.7+"
+version = "3.10.0.2"
+description = "Backported and Experimental Type Hints for Python 3.5+"
category = "main"
optional = false
-python-versions = ">=3.7"
-
-[[package]]
-name = "urllib3"
-version = "1.26.12"
-description = "HTTP library with thread-safe connection pooling, file post, and more."
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
-
-[package.extras]
-brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
-secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
-socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
+python-versions = "*"
[[package]]
name = "uvicorn"
-version = "0.18.3"
+version = "0.18.1"
description = "The lightning-fast ASGI server."
category = "main"
optional = false
@@ -1044,9 +928,9 @@ optional = false
python-versions = ">=3.7"
[package.extras]
-dev = ["Cython (>=0.29.24,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
-docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
-test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=19.0.0,<19.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"]
+dev = ["Cython (>=0.29.24,<0.30.0)", "pytest (>=3.6.0)", "Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
+docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)"]
+test = ["aiohttp", "flake8 (>=3.9.2,<3.10.0)", "psutil", "pycodestyle (>=2.7.0,<2.8.0)", "pyOpenSSL (>=19.0.0,<19.1.0)", "mypy (>=0.800)"]
[[package]]
name = "watchgod"
@@ -1090,11 +974,11 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
name = "zipp"
-version = "3.8.1"
+version = "3.5.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.6"
[package.extras]
docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
@@ -1115,24 +999,19 @@ anyio = [
{file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
]
asgiref = [
- {file = "asgiref-3.5.2-py3-none-any.whl", hash = "sha256:1d2880b792ae8757289136f1db2b7b99100ce959b2aa57fd69dab783d05afac4"},
- {file = "asgiref-3.5.2.tar.gz", hash = "sha256:4a29362a6acebe09bf1d6640db38c1dc3d9217c68e6f9f6204d72667fc19a424"},
+ {file = "asgiref-3.4.1-py3-none-any.whl", hash = "sha256:ffc141aa908e6f175673e7b1b3b7af4fdb0ecb738fc5c8b88f69f055c2415214"},
+ {file = "asgiref-3.4.1.tar.gz", hash = "sha256:4ef1ab46b484e3c706329cedeff284a5d40824200638503f5768edb6de7d58e9"},
]
-asn1crypto = [
- {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"},
- {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"},
+atomicwrites = [
+ {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"},
]
async-timeout = [
{file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"},
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
]
attrs = [
- {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
- {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
-]
-base58 = [
- {file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"},
- {file = "base58-2.1.1.tar.gz", hash = "sha256:c5d0cb3f5b6e81e8e35da5754388ddcc6d0d14b6c6a132cb93d69ed580a7278c"},
+ {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
+ {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
]
base58 = [
{file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"},
@@ -1170,125 +1049,72 @@ black = [
{file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
{file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
]
-cashu = [
- {file = "cashu-0.1.11-py3-none-any.whl", hash = "sha256:d2c5a72648fa4487fdf694e5669dfaa8d62445d83cced33b5bc63eccbce6a00c"},
-]
-Cerberus = [
+cerberus = [
{file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"},
]
certifi = [
- {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
- {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
+ {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
+ {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
]
cffi = [
- {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
- {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
- {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
- {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
- {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
- {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
- {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
- {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
- {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
- {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
- {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
- {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
- {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
- {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
- {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
- {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
- {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
- {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
- {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
- {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
- {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
- {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
- {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
- {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
- {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
- {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
- {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
- {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
- {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
- {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
- {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
- {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
- {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
- {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
- {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
- {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
- {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
- {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
- {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
- {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
- {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
+ {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
+ {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
+ {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
+ {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
+ {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
+ {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
+ {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
+ {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
+ {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
+ {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
+ {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
+ {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
+ {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
+ {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
+ {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
+ {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
+ {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
+ {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
+ {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
+ {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
+ {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
+ {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
+ {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
+ {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
+ {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
+ {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
]
charset-normalizer = [
- {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
- {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
+ {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"},
+ {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"},
]
click = [
- {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
- {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
-]
-coincurve = [
- {file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"},
- {file = "coincurve-17.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:25dfa105beba24c8de886f8ed654bb1133866e4e22cfd7ea5ad8438cae6ed924"},
- {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:698efdd53e4fe1bbebaee9b75cbc851be617974c1c60098e9145cb7198ae97fb"},
- {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30dd44d1039f1d237aaa2da6d14a455ca88df3bcb00610b41f3253fdca1be97b"},
- {file = "coincurve-17.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154e2eb5711db8c5ef52fcd80935b5a0e751c057bc6ffb215a7bb409aedef03"},
- {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c71caffb97dd3d0c243beb62352669b1e5dafa3a4bccdbb27d36bd82f5e65d20"},
- {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:747215254e51dd4dfbe6dded9235491263da5d88fe372d66541ca16b51ea078f"},
- {file = "coincurve-17.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ad2f6df39ba1e2b7b14bb984505ffa7d0a0ecdd697e8d7dbd19e04bc245c87ed"},
- {file = "coincurve-17.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0503326963916c85b61d16f611ea0545f03c9e418fa8007c233c815429e381e8"},
- {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1013c1597b65684ae1c3e42497f9ef5a04527fa6136a84a16b34602606428c74"},
- {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4beef321fd6434448aab03a0c245f31c4e77f43b54b82108c0948d29852ac7e"},
- {file = "coincurve-17.0.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f47806527d3184da3e8b146fac92a8ed567bbd225194f4517943d8cdc85f9542"},
- {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:51e56373ac79f4ec1cfc5da53d72c55f5e5ac28d848b0849ef5e687ace857888"},
- {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3d694ad194bee9e8792e2e75879dc5238d8a184010cde36c5ad518fcfe2cd8f2"},
- {file = "coincurve-17.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:74cedb3d3a1dc5abe0c9c2396e1b82cc64496babc5b42e007e72e185cb1edad8"},
- {file = "coincurve-17.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:db874c5c1dcb1f3a19379773b5e8cffc777625a7a7a60dd9a67206e31e62e2e9"},
- {file = "coincurve-17.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:896b01941254f0a218cf331a9bddfe2d43892f7f1ba10d6e372e2eb744a744c2"},
- {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6aec70238dbe7a5d66b5f9438ff45b08eb5e0990d49c32ebb65247c5d5b89d7a"},
- {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d24284d17162569df917a640f19d9654ba3b43cf560ced8864f270da903f73a5"},
- {file = "coincurve-17.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ea057f777842396d387103c606babeb3a1b4c6126769cc0a12044312fc6c465"},
- {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b88642edf7f281649b0c0b6ffade051945ccceae4b885e40445634877d0b3049"},
- {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a80a207131813b038351c5bdae8f20f5f774bbf53622081f208d040dd2b7528f"},
- {file = "coincurve-17.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f1ef72574aa423bc33665ef4be859164a478bad24d48442da874ef3dc39a474d"},
- {file = "coincurve-17.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dfd4fab857bcd975edc39111cb5f5c104f138dac2e9ace35ea8434d37bcea3be"},
- {file = "coincurve-17.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:73f39579dd651a9fc29da5a8fc0d8153d872bcbc166f876457baced1a1c01501"},
- {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8852dc01af4f0fe941ffd04069f7e4fecdce9b867a016f823a02286a1a1f07b5"},
- {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1bef812da1da202cdd601a256825abcf26d86e8634fac3ec3e615e3bb3ff08c"},
- {file = "coincurve-17.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abbefc9ccb170cb255a31df32457c2e43084b9f37589d0694dacc2dea6ddaf7c"},
- {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:abbd9d017a7638dc38a3b9bb4851f8801b7818d4e5ac22e0c75e373b3c1dbff0"},
- {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e2c2e8a1f0b1f8e48049c891af4ae3cad65d115d358bde72f6b8abdbb8a23170"},
- {file = "coincurve-17.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8c571445b166c714af4f8155e38a894376c16c0431e88963f2fff474a9985d87"},
- {file = "coincurve-17.0.0-py3-none-win32.whl", hash = "sha256:b956b0b2c85e25a7d00099970ff5d8338254b45e46f0a940f4a2379438ce0dde"},
- {file = "coincurve-17.0.0-py3-none-win_amd64.whl", hash = "sha256:630388080da3026e0b0176cc6762eaabecba857ee3fc85767577dea063ea7c6e"},
- {file = "coincurve-17.0.0.tar.gz", hash = "sha256:68da55aff898702952fda3ee04fd6ed60bb6b91f919c69270786ed766b548b93"},
+ {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
+ {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
]
coincurve = [
{file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"},
@@ -1405,8 +1231,8 @@ cryptography = [
{file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"},
]
ecdsa = [
- {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
- {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
+ {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"},
+ {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"},
]
embit = [
{file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"},
@@ -1417,55 +1243,8 @@ enum34 = [
{file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"},
]
fastapi = [
- {file = "fastapi-0.83.0-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"},
- {file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"},
-]
-grpcio = [
- {file = "grpcio-1.49.1-cp310-cp310-linux_armv7l.whl", hash = "sha256:fd86040232e805b8e6378b2348c928490ee595b058ce9aaa27ed8e4b0f172b20"},
- {file = "grpcio-1.49.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6fd0c9cede9552bf00f8c5791d257d5bf3790d7057b26c59df08be5e7a1e021d"},
- {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:d0d402e158d4e84e49c158cb5204119d55e1baf363ee98d6cb5dce321c3a065d"},
- {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:822ceec743d42a627e64ea266059a62d214c5a3cdfcd0d7fe2b7a8e4e82527c7"},
- {file = "grpcio-1.49.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2106d9c16527f0a85e2eea6e6b91a74fc99579c60dd810d8690843ea02bc0f5f"},
- {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:52dd02b7e7868233c571b49bc38ebd347c3bb1ff8907bb0cb74cb5f00c790afc"},
- {file = "grpcio-1.49.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:120fecba2ec5d14b5a15d11063b39783fda8dc8d24addd83196acb6582cabd9b"},
- {file = "grpcio-1.49.1-cp310-cp310-win32.whl", hash = "sha256:f1a3b88e3c53c1a6e6bed635ec1bbb92201bb6a1f2db186179f7f3f244829788"},
- {file = "grpcio-1.49.1-cp310-cp310-win_amd64.whl", hash = "sha256:a7d0017b92d3850abea87c1bdec6ea41104e71c77bca44c3e17f175c6700af62"},
- {file = "grpcio-1.49.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:9fb17ff8c0d56099ac6ebfa84f670c5a62228d6b5c695cf21c02160c2ac1446b"},
- {file = "grpcio-1.49.1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:075f2d06e3db6b48a2157a1bcd52d6cbdca980dd18988fe6afdb41795d51625f"},
- {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46d93a1b4572b461a227f1db6b8d35a88952db1c47e5fadcf8b8a2f0e1dd9201"},
- {file = "grpcio-1.49.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc79b2b37d779ac42341ddef40ad5bf0966a64af412c89fc2b062e3ddabb093f"},
- {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5f8b3a971c7820ea9878f3fd70086240a36aeee15d1b7e9ecbc2743b0e785568"},
- {file = "grpcio-1.49.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:49b301740cf5bc8fed4fee4c877570189ae3951432d79fa8e524b09353659811"},
- {file = "grpcio-1.49.1-cp311-cp311-win32.whl", hash = "sha256:1c66a25afc6c71d357867b341da594a5587db5849b48f4b7d5908d236bb62ede"},
- {file = "grpcio-1.49.1-cp311-cp311-win_amd64.whl", hash = "sha256:6b6c3a95d27846f4145d6967899b3ab25fffc6ae99544415e1adcacef84842d2"},
- {file = "grpcio-1.49.1-cp37-cp37m-linux_armv7l.whl", hash = "sha256:1cc400c8a2173d1c042997d98a9563e12d9bb3fb6ad36b7f355bc77c7663b8af"},
- {file = "grpcio-1.49.1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:34f736bd4d0deae90015c0e383885b431444fe6b6c591dea288173df20603146"},
- {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:196082b9c89ebf0961dcd77cb114bed8171964c8e3063b9da2fb33536a6938ed"},
- {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c9f89c42749890618cd3c2464e1fbf88446e3d2f67f1e334c8e5db2f3272bbd"},
- {file = "grpcio-1.49.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64419cb8a5b612cdb1550c2fd4acbb7d4fb263556cf4625f25522337e461509e"},
- {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:8a5272061826e6164f96e3255405ef6f73b88fd3e8bef464c7d061af8585ac62"},
- {file = "grpcio-1.49.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ea9d0172445241ad7cb49577314e39d0af2c5267395b3561d7ced5d70458a9f3"},
- {file = "grpcio-1.49.1-cp37-cp37m-win32.whl", hash = "sha256:2070e87d95991473244c72d96d13596c751cb35558e11f5df5414981e7ed2492"},
- {file = "grpcio-1.49.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fcedcab49baaa9db4a2d240ac81f2d57eb0052b1c6a9501b46b8ae912720fbf"},
- {file = "grpcio-1.49.1-cp38-cp38-linux_armv7l.whl", hash = "sha256:afbb3475cf7f4f7d380c2ca37ee826e51974f3e2665613996a91d6a58583a534"},
- {file = "grpcio-1.49.1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a4f9ba141380abde6c3adc1727f21529137a2552002243fa87c41a07e528245c"},
- {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:cf0a1fb18a7204b9c44623dfbd1465b363236ce70c7a4ed30402f9f60d8b743b"},
- {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17bb6fe72784b630728c6cff9c9d10ccc3b6d04e85da6e0a7b27fb1d135fac62"},
- {file = "grpcio-1.49.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18305d5a082d1593b005a895c10041f833b16788e88b02bb81061f5ebcc465df"},
- {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b6a1b39e59ac5a3067794a0e498911cf2e37e4b19ee9e9977dc5e7051714f13f"},
- {file = "grpcio-1.49.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e20d59aafc086b1cc68400463bddda6e41d3e5ed30851d1e2e0f6a2e7e342d3"},
- {file = "grpcio-1.49.1-cp38-cp38-win32.whl", hash = "sha256:e1e83233d4680863a421f3ee4a7a9b80d33cd27ee9ed7593bc93f6128302d3f2"},
- {file = "grpcio-1.49.1-cp38-cp38-win_amd64.whl", hash = "sha256:221d42c654d2a41fa31323216279c73ed17d92f533bc140a3390cc1bd78bf63c"},
- {file = "grpcio-1.49.1-cp39-cp39-linux_armv7l.whl", hash = "sha256:fa9e6e61391e99708ac87fc3436f6b7b9c6b845dc4639b406e5e61901e1aacde"},
- {file = "grpcio-1.49.1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:9b449e966ef518ce9c860d21f8afe0b0f055220d95bc710301752ac1db96dd6a"},
- {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:aa34d2ad9f24e47fa9a3172801c676e4037d862247e39030165fe83821a7aafd"},
- {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5207f4eed1b775d264fcfe379d8541e1c43b878f2b63c0698f8f5c56c40f3d68"},
- {file = "grpcio-1.49.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b24a74651438d45619ac67004638856f76cc13d78b7478f2457754cbcb1c8ad"},
- {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fe763781669790dc8b9618e7e677c839c87eae6cf28b655ee1fa69ae04eea03f"},
- {file = "grpcio-1.49.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2f2ff7ba0f8f431f32d4b4bc3a3713426949d3533b08466c4ff1b2b475932ca8"},
- {file = "grpcio-1.49.1-cp39-cp39-win32.whl", hash = "sha256:08ff74aec8ff457a89b97152d36cb811dcc1d17cd5a92a65933524e363327394"},
- {file = "grpcio-1.49.1-cp39-cp39-win_amd64.whl", hash = "sha256:274ffbb39717918c514b35176510ae9be06e1d93121e84d50b350861dcb9a705"},
- {file = "grpcio-1.49.1.tar.gz", hash = "sha256:d4725fc9ec8e8822906ae26bb26f5546891aa7fbc3443de970cc556d43a5c99f"},
+ {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
+ {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
]
grpcio = [
{file = "grpcio-1.50.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974"},
@@ -1563,12 +1342,12 @@ httpx = [
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
]
idna = [
- {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
- {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+ {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
+ {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
]
importlib-metadata = [
- {file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
- {file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
+ {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
+ {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
@@ -1578,63 +1357,92 @@ isort = [
{file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"},
{file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"},
]
-Jinja2 = [
- {file = "Jinja2-3.0.3-py3-none-any.whl", hash = "sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8"},
- {file = "Jinja2-3.0.3.tar.gz", hash = "sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7"},
+jinja2 = [
+ {file = "Jinja2-3.0.1-py3-none-any.whl", hash = "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4"},
+ {file = "Jinja2-3.0.1.tar.gz", hash = "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4"},
]
lnurl = [
{file = "lnurl-0.3.6-py3-none-any.whl", hash = "sha256:579982fd8c4d25bc84c61c74ec45cb7999fa1fa2426f5d5aeb0160ba333b9c92"},
{file = "lnurl-0.3.6.tar.gz", hash = "sha256:8af07460115a48f3122a5a9c9a6062bee3897d5f6ab4c9a60f6561a83a8234f6"},
]
loguru = [
- {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"},
- {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"},
+ {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"},
+ {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"},
]
-MarkupSafe = [
- {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
- {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
- {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
- {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
- {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
- {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
+markupsafe = [
+ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
+ {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
]
marshmallow = [
- {file = "marshmallow-3.18.0-py3-none-any.whl", hash = "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104"},
- {file = "marshmallow-3.18.0.tar.gz", hash = "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7"},
+ {file = "marshmallow-3.17.0-py3-none-any.whl", hash = "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb"},
+ {file = "marshmallow-3.17.0.tar.gz", hash = "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7"},
]
mock = [
{file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"},
@@ -1670,20 +1478,16 @@ mypy-extensions = [
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
outcome = [
- {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"},
- {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"},
+ {file = "outcome-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958"},
+ {file = "outcome-1.1.0.tar.gz", hash = "sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
-pathlib2 = [
- {file = "pathlib2-2.3.7.post1-py2.py3-none-any.whl", hash = "sha256:5266a0fd000452f1b3467d782f079a4343c63aaa119221fbdc4e39577489ca5b"},
- {file = "pathlib2-2.3.7.post1.tar.gz", hash = "sha256:9fe0edad898b83c0c3e199c842b27ed216645d2e177757b2dd67384d4113c641"},
-]
pathspec = [
- {file = "pathspec-0.10.1-py3-none-any.whl", hash = "sha256:46846318467efc4556ccfd27816e004270a9eeeeb4d062ce5e6fc7a87c573f93"},
- {file = "pathspec-0.10.1.tar.gz", hash = "sha256:7ace6161b621d31e7902eb6b5ae148d12cfd23f4a249b9ffb6b9fee12084323d"},
+ {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"},
+ {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
@@ -1710,62 +1514,42 @@ protobuf = [
{file = "protobuf-4.21.8.tar.gz", hash = "sha256:427426593b55ff106c84e4a88cac855175330cb6eb7e889e85aaa7b5652b686d"},
]
psycopg2-binary = [
- {file = "psycopg2-binary-2.9.3.tar.gz", hash = "sha256:761df5313dc15da1502b21453642d7599d26be88bff659382f8f9747c7ebea4e"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:539b28661b71da7c0e428692438efbcd048ca21ea81af618d845e06ebfd29478"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e82d38390a03da28c7985b394ec3f56873174e2c88130e6966cb1c946508e65"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:57804fc02ca3ce0dbfbef35c4b3a4a774da66d66ea20f4bda601294ad2ea6092"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:083a55275f09a62b8ca4902dd11f4b33075b743cf0d360419e2051a8a5d5ff76"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:0a29729145aaaf1ad8bafe663131890e2111f13416b60e460dae0a96af5905c9"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a79d622f5206d695d7824cbf609a4f5b88ea6d6dab5f7c147fc6d333a8787e4"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:090f3348c0ab2cceb6dfbe6bf721ef61262ddf518cd6cc6ecc7d334996d64efa"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a9e1f75f96ea388fbcef36c70640c4efbe4650658f3d6a2967b4cc70e907352e"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c3ae8e75eb7160851e59adc77b3a19a976e50622e44fd4fd47b8b18208189d42"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-win32.whl", hash = "sha256:7b1e9b80afca7b7a386ef087db614faebbf8839b7f4db5eb107d0f1a53225029"},
- {file = "psycopg2_binary-2.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:8b344adbb9a862de0c635f4f0425b7958bf5a4b927c8594e6e8d261775796d53"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:e847774f8ffd5b398a75bc1c18fbb56564cda3d629fe68fd81971fece2d3c67e"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68641a34023d306be959101b345732360fc2ea4938982309b786f7be1b43a4a1"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3303f8807f342641851578ee7ed1f3efc9802d00a6f83c101d21c608cb864460"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:e3699852e22aa68c10de06524a3721ade969abf382da95884e6a10ff798f9281"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:526ea0378246d9b080148f2d6681229f4b5964543c170dd10bf4faaab6e0d27f"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b1c8068513f5b158cf7e29c43a77eb34b407db29aca749d3eb9293ee0d3103ca"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:15803fa813ea05bef089fa78835118b5434204f3a17cb9f1e5dbfd0b9deea5af"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:152f09f57417b831418304c7f30d727dc83a12761627bb826951692cc6491e57"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:404224e5fef3b193f892abdbf8961ce20e0b6642886cfe1fe1923f41aaa75c9d"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-win32.whl", hash = "sha256:1f6b813106a3abdf7b03640d36e24669234120c72e91d5cbaeb87c5f7c36c65b"},
- {file = "psycopg2_binary-2.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:2d872e3c9d5d075a2e104540965a1cf898b52274a5923936e5bfddb58c59c7c2"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:10bb90fb4d523a2aa67773d4ff2b833ec00857f5912bafcfd5f5414e45280fb1"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:874a52ecab70af13e899f7847b3e074eeb16ebac5615665db33bce8a1009cf33"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a29b3ca4ec9defec6d42bf5feb36bb5817ba3c0230dd83b4edf4bf02684cd0ae"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:12b11322ea00ad8db8c46f18b7dfc47ae215e4df55b46c67a94b4effbaec7094"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:53293533fcbb94c202b7c800a12c873cfe24599656b341f56e71dd2b557be063"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c381bda330ddf2fccbafab789d83ebc6c53db126e4383e73794c74eedce855ef"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d29409b625a143649d03d0fd7b57e4b92e0ecad9726ba682244b73be91d2fdb"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:183a517a3a63503f70f808b58bfbf962f23d73b6dccddae5aa56152ef2bcb232"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:15c4e4cfa45f5a60599d9cec5f46cd7b1b29d86a6390ec23e8eebaae84e64554"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-win32.whl", hash = "sha256:adf20d9a67e0b6393eac162eb81fb10bc9130a80540f4df7e7355c2dd4af9fba"},
- {file = "psycopg2_binary-2.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:2f9ffd643bc7349eeb664eba8864d9e01f057880f510e4681ba40a6532f93c71"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:def68d7c21984b0f8218e8a15d514f714d96904265164f75f8d3a70f9c295667"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffc08ca91c9ac09008870c9eb77b00a46b3378719584059c034b8945e26b272"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:280b0bb5cbfe8039205c7981cceb006156a675362a00fe29b16fbc264e242834"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:af9813db73395fb1fc211bac696faea4ca9ef53f32dc0cfa27e4e7cf766dcf24"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:63638d875be8c2784cfc952c9ac34e2b50e43f9f0a0660b65e2a87d656b3116c"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ffb7a888a047696e7f8240d649b43fb3644f14f0ee229077e7f6b9f9081635bd"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0c9d5450c566c80c396b7402895c4369a410cab5a82707b11aee1e624da7d004"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:d1c1b569ecafe3a69380a94e6ae09a4789bbb23666f3d3a08d06bbd2451f5ef1"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8fc53f9af09426a61db9ba357865c77f26076d48669f2e1bb24d85a22fb52307"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-win32.whl", hash = "sha256:6472a178e291b59e7f16ab49ec8b4f3bdada0a879c68d3817ff0963e722a82ce"},
- {file = "psycopg2_binary-2.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35168209c9d51b145e459e05c31a9eaeffa9a6b0fd61689b48e07464ffd1a83e"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:47133f3f872faf28c1e87d4357220e809dfd3fa7c64295a4a148bcd1e6e34ec9"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91920527dea30175cc02a1099f331aa8c1ba39bf8b7762b7b56cbf54bc5cce42"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:887dd9aac71765ac0d0bac1d0d4b4f2c99d5f5c1382d8b770404f0f3d0ce8a39"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:1f14c8b0942714eb3c74e1e71700cbbcb415acbc311c730370e70c578a44a25c"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:7af0dd86ddb2f8af5da57a976d27cd2cd15510518d582b478fbb2292428710b4"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:93cd1967a18aa0edd4b95b1dfd554cf15af657cb606280996d393dadc88c3c35"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bda845b664bb6c91446ca9609fc69f7db6c334ec5e4adc87571c34e4f47b7ddb"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:01310cf4cf26db9aea5158c217caa92d291f0500051a6469ac52166e1a16f5b7"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99485cab9ba0fa9b84f1f9e1fef106f44a46ef6afdeec8885e0b88d0772b49e8"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-win32.whl", hash = "sha256:46f0e0a6b5fa5851bbd9ab1bc805eef362d3a230fbdfbc209f4a236d0a7a990d"},
- {file = "psycopg2_binary-2.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:accfe7e982411da3178ec690baaceaad3c278652998b2c45828aaac66cd8285f"},
+ {file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
+ {file = "psycopg2_binary-2.9.1-cp310-cp310-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:24b0b6688b9f31a911f2361fe818492650795c9e5d3a1bc647acbd7440142a4f"},
+ {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:542875f62bc56e91c6eac05a0deadeae20e1730be4c6334d8f04c944fcd99759"},
+ {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661509f51531ec125e52357a489ea3806640d0ca37d9dada461ffc69ee1e7b6e"},
+ {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:d92272c7c16e105788efe2cfa5d680f07e34e0c29b03c1908f8636f55d5f915a"},
+ {file = "psycopg2_binary-2.9.1-cp310-cp310-manylinux_2_24_ppc64le.whl", hash = "sha256:736b8797b58febabb85494142c627bd182b50d2a7ec65322983e71065ad3034c"},
+ {file = "psycopg2_binary-2.9.1-cp310-cp310-win32.whl", hash = "sha256:ebccf1123e7ef66efc615a68295bf6fdba875a75d5bba10a05073202598085fc"},
+ {file = "psycopg2_binary-2.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:1f6ca4a9068f5c5c57e744b4baa79f40e83e3746875cac3c45467b16326bab45"},
+ {file = "psycopg2_binary-2.9.1-cp36-cp36m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:c250a7ec489b652c892e4f0a5d122cc14c3780f9f643e1a326754aedf82d9a76"},
+ {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aef9aee84ec78af51107181d02fe8773b100b01c5dfde351184ad9223eab3698"},
+ {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123c3fb684e9abfc47218d3784c7b4c47c8587951ea4dd5bc38b6636ac57f616"},
+ {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_aarch64.whl", hash = "sha256:995fc41ebda5a7a663a254a1dcac52638c3e847f48307b5416ee373da15075d7"},
+ {file = "psycopg2_binary-2.9.1-cp36-cp36m-manylinux_2_24_ppc64le.whl", hash = "sha256:fbb42a541b1093385a2d8c7eec94d26d30437d0e77c1d25dae1dcc46741a385e"},
+ {file = "psycopg2_binary-2.9.1-cp36-cp36m-win32.whl", hash = "sha256:20f1ab44d8c352074e2d7ca67dc00843067788791be373e67a0911998787ce7d"},
+ {file = "psycopg2_binary-2.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f6fac64a38f6768e7bc7b035b9e10d8a538a9fadce06b983fb3e6fa55ac5f5ce"},
+ {file = "psycopg2_binary-2.9.1-cp37-cp37m-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:1e3a362790edc0a365385b1ac4cc0acc429a0c0d662d829a50b6ce743ae61b5a"},
+ {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8559617b1fcf59a9aedba2c9838b5b6aa211ffedecabca412b92a1ff75aac1a"},
+ {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a36c7eb6152ba5467fb264d73844877be8b0847874d4822b7cf2d3c0cb8cdcb0"},
+ {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:2f62c207d1740b0bde5c4e949f857b044818f734a3d57f1d0d0edc65050532ed"},
+ {file = "psycopg2_binary-2.9.1-cp37-cp37m-manylinux_2_24_ppc64le.whl", hash = "sha256:cfc523edecddaef56f6740d7de1ce24a2fdf94fd5e704091856a201872e37f9f"},
+ {file = "psycopg2_binary-2.9.1-cp37-cp37m-win32.whl", hash = "sha256:1e85b74cbbb3056e3656f1cc4781294df03383127a8114cbc6531e8b8367bf1e"},
+ {file = "psycopg2_binary-2.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1473c0215b0613dd938db54a653f68251a45a78b05f6fc21af4326f40e8360a2"},
+ {file = "psycopg2_binary-2.9.1-cp38-cp38-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:35c4310f8febe41f442d3c65066ca93cccefd75013df3d8c736c5b93ec288140"},
+ {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c13d72ed6af7fd2c8acbd95661cf9477f94e381fce0792c04981a8283b52917"},
+ {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14db1752acdd2187d99cb2ca0a1a6dfe57fc65c3281e0f20e597aac8d2a5bd90"},
+ {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:aed4a9a7e3221b3e252c39d0bf794c438dc5453bc2963e8befe9d4cd324dff72"},
+ {file = "psycopg2_binary-2.9.1-cp38-cp38-manylinux_2_24_ppc64le.whl", hash = "sha256:da113b70f6ec40e7d81b43d1b139b9db6a05727ab8be1ee559f3a69854a69d34"},
+ {file = "psycopg2_binary-2.9.1-cp38-cp38-win32.whl", hash = "sha256:4235f9d5ddcab0b8dbd723dca56ea2922b485ea00e1dafacf33b0c7e840b3d32"},
+ {file = "psycopg2_binary-2.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:988b47ac70d204aed01589ed342303da7c4d84b56c2f4c4b8b00deda123372bf"},
+ {file = "psycopg2_binary-2.9.1-cp39-cp39-macosx_10_14_x86_64.macosx_10_9_intel.macosx_10_9_x86_64.macosx_10_10_intel.macosx_10_10_x86_64.whl", hash = "sha256:7360647ea04db2e7dff1648d1da825c8cf68dc5fbd80b8fb5b3ee9f068dcd21a"},
+ {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca86db5b561b894f9e5f115d6a159fff2a2570a652e07889d8a383b5fae66eb4"},
+ {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ced67f1e34e1a450cdb48eb53ca73b60aa0af21c46b9b35ac3e581cf9f00e31"},
+ {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:0f2e04bd2a2ab54fa44ee67fe2d002bb90cee1c0f1cc0ebc3148af7b02034cbd"},
+ {file = "psycopg2_binary-2.9.1-cp39-cp39-manylinux_2_24_ppc64le.whl", hash = "sha256:3242b9619de955ab44581a03a64bdd7d5e470cc4183e8fcadd85ab9d3756ce7a"},
+ {file = "psycopg2_binary-2.9.1-cp39-cp39-win32.whl", hash = "sha256:0b7dae87f0b729922e06f85f667de7bf16455d411971b2043bbd9577af9d1975"},
+ {file = "psycopg2_binary-2.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:b4d7679a08fea64573c969f6994a2631908bb2c0e69a7235648642f3d2e39a68"},
]
py = [
{file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"},
@@ -1805,54 +1589,28 @@ pycryptodomex = [
{file = "pycryptodomex-3.14.1.tar.gz", hash = "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"},
]
pydantic = [
- {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"},
- {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"},
- {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"},
- {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"},
- {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"},
- {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"},
- {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"},
- {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"},
- {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"},
- {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"},
- {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"},
- {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"},
- {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"},
- {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"},
- {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"},
- {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"},
- {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"},
- {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"},
- {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"},
- {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"},
- {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"},
- {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"},
- {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"},
- {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"},
- {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"},
- {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"},
- {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"},
- {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"},
- {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"},
- {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"},
- {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"},
- {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"},
- {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"},
- {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"},
- {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
- {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
-]
-pyln-bolt7 = [
- {file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"},
- {file = "pyln_bolt7-1.0.246-py3-none-any.whl", hash = "sha256:54d48ec27fdc8751762cb068b0a9f2757a58fb57933c6d8f8255d02c27eb63c5"},
-]
-pyln-client = [
- {file = "pyln-client-0.12.1.tar.gz", hash = "sha256:f14fa7947b65ecde2753984452441cf41b7b25b1a0ba7beced48786fa54d2bfe"},
- {file = "pyln_client-0.12.1-py3-none-any.whl", hash = "sha256:6b500bcc49e4028d50692b962d9c9f7e9ede920d718f9b9412f04f7db0aa0e63"},
-]
-pyln-proto = [
- {file = "pyln-proto-0.12.0.tar.gz", hash = "sha256:3214d99d8385f2135a94937f0dc1da626a33b257e9ebc320841656edaefabbe5"},
- {file = "pyln_proto-0.12.0-py3-none-any.whl", hash = "sha256:dedef5d8e476a9ade5a0b2eb919ccc37e4a57f2a78fdc399f1c5e0de17e41604"},
+ {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
+ {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
+ {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"},
+ {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"},
+ {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"},
+ {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"},
+ {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"},
+ {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"},
+ {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"},
+ {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"},
+ {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"},
+ {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"},
+ {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"},
+ {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"},
+ {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"},
+ {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"},
+ {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"},
+ {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"},
+ {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"},
+ {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"},
+ {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
+ {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
]
pyln-bolt7 = [
{file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"},
@@ -1873,7 +1631,7 @@ pyparsing = [
pypng = [
{file = "pypng-0.0.21-py3-none-any.whl", hash = "sha256:76f8a1539ec56451da7ab7121f12a361969fe0f2d48d703d198ce2a99d6c5afd"},
]
-PyQRCode = [
+pyqrcode = [
{file = "PyQRCode-1.2.1.tar.gz", hash = "sha256:fdbf7634733e56b72e27f9bce46e4550b75a3a2c420414035cae9d9d26b234d5"},
{file = "PyQRCode-1.2.1.zip", hash = "sha256:1b2812775fa6ff5c527977c4cd2ccb07051ca7d0bc0aecf937a43864abe5eff6"},
]
@@ -1886,8 +1644,8 @@ pysocks = [
{file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
]
pytest = [
- {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"},
- {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"},
+ {file = "pytest-7.1.2-py3-none-any.whl", hash = "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c"},
+ {file = "pytest-7.1.2.tar.gz", hash = "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45"},
]
pytest-asyncio = [
{file = "pytest-asyncio-0.19.0.tar.gz", hash = "sha256:ac4ebf3b6207259750bc32f4c1d8fcd7e79739edbc67ad0c58dd150b1d072fed"},
@@ -1898,10 +1656,10 @@ pytest-cov = [
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
]
python-dotenv = [
- {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
- {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
+ {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
+ {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
]
-PyYAML = [
+pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
{file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"},
{file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"},
@@ -1932,14 +1690,10 @@ PyYAML = [
{file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"},
{file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"},
]
-Represent = [
+represent = [
{file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"},
{file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"},
]
-requests = [
- {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
- {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
-]
rfc3986 = [
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
@@ -1982,44 +1736,48 @@ six = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
sniffio = [
- {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
- {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+ {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
+ {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
]
-SQLAlchemy = [
- {file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"},
- {file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"},
- {file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"},
- {file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"},
- {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"},
- {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"},
- {file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"},
- {file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"},
- {file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"},
- {file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"},
- {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"},
- {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"},
- {file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"},
- {file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"},
- {file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"},
- {file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"},
- {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"},
- {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"},
- {file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"},
- {file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"},
- {file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"},
- {file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"},
- {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"},
- {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"},
- {file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"},
- {file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"},
- {file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"},
- {file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"},
- {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"},
- {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"},
- {file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"},
- {file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"},
- {file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"},
- {file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"},
+sqlalchemy = [
+ {file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"},
+ {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:29cccc9606750fe10c5d0e8bd847f17a97f3850b8682aef1f56f5d5e1a5a64b1"},
+ {file = "SQLAlchemy-1.3.23-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:927ce09e49bff3104459e1451ce82983b0a3062437a07d883a4c66f0b344c9b5"},
+ {file = "SQLAlchemy-1.3.23-cp27-cp27m-win32.whl", hash = "sha256:b4b0e44d586cd64b65b507fa116a3814a1a53d55dce4836d7c1a6eb2823ff8d1"},
+ {file = "SQLAlchemy-1.3.23-cp27-cp27m-win_amd64.whl", hash = "sha256:6b8b8c80c7f384f06825612dd078e4a31f0185e8f1f6b8c19e188ff246334205"},
+ {file = "SQLAlchemy-1.3.23-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:9e9c25522933e569e8b53ccc644dc993cab87e922fb7e142894653880fdd419d"},
+ {file = "SQLAlchemy-1.3.23-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:a0e306e9bb76fd93b29ae3a5155298e4c1b504c7cbc620c09c20858d32d16234"},
+ {file = "SQLAlchemy-1.3.23-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:6c9e6cc9237de5660bcddea63f332428bb83c8e2015c26777281f7ffbd2efb84"},
+ {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:94f667d86be82dd4cb17d08de0c3622e77ca865320e0b95eae6153faa7b4ecaf"},
+ {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:751934967f5336a3e26fc5993ccad1e4fee982029f9317eb6153bc0bc3d2d2da"},
+ {file = "SQLAlchemy-1.3.23-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:63677d0c08524af4c5893c18dbe42141de7178001360b3de0b86217502ed3601"},
+ {file = "SQLAlchemy-1.3.23-cp35-cp35m-win32.whl", hash = "sha256:ddfb511e76d016c3a160910642d57f4587dc542ce5ee823b0d415134790eeeb9"},
+ {file = "SQLAlchemy-1.3.23-cp35-cp35m-win_amd64.whl", hash = "sha256:040bdfc1d76a9074717a3f43455685f781c581f94472b010cd6c4754754e1862"},
+ {file = "SQLAlchemy-1.3.23-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:d1a85dfc5dee741bf49cb9b6b6b8d2725a268e4992507cf151cba26b17d97c37"},
+ {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:639940bbe1108ac667dcffc79925db2966826c270112e9159439ab6bb14f8d80"},
+ {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e8a1750b44ad6422ace82bf3466638f1aa0862dbb9689690d5f2f48cce3476c8"},
+ {file = "SQLAlchemy-1.3.23-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:e5bb3463df697279e5459a7316ad5a60b04b0107f9392e88674d0ece70e9cf70"},
+ {file = "SQLAlchemy-1.3.23-cp36-cp36m-win32.whl", hash = "sha256:e273367f4076bd7b9a8dc2e771978ef2bfd6b82526e80775a7db52bff8ca01dd"},
+ {file = "SQLAlchemy-1.3.23-cp36-cp36m-win_amd64.whl", hash = "sha256:ac2244e64485c3778f012951fdc869969a736cd61375fde6096d08850d8be729"},
+ {file = "SQLAlchemy-1.3.23-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:23927c3981d1ec6b4ea71eb99d28424b874d9c696a21e5fbd9fa322718be3708"},
+ {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d90010304abb4102123d10cbad2cdf2c25a9f2e66a50974199b24b468509bad5"},
+ {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:a8bfc1e1afe523e94974132d7230b82ca7fa2511aedde1f537ec54db0399541a"},
+ {file = "SQLAlchemy-1.3.23-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:269990b3ab53cb035d662dcde51df0943c1417bdab707dc4a7e4114a710504b4"},
+ {file = "SQLAlchemy-1.3.23-cp37-cp37m-win32.whl", hash = "sha256:fdd2ed7395df8ac2dbb10cefc44737b66c6a5cd7755c92524733d7a443e5b7e2"},
+ {file = "SQLAlchemy-1.3.23-cp37-cp37m-win_amd64.whl", hash = "sha256:6a939a868fdaa4b504e8b9d4a61f21aac11e3fecc8a8214455e144939e3d2aea"},
+ {file = "SQLAlchemy-1.3.23-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:24f9569e82a009a09ce2d263559acb3466eba2617203170e4a0af91e75b4f075"},
+ {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2578dbdbe4dbb0e5126fb37ffcd9793a25dcad769a95f171a2161030bea850ff"},
+ {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1fe5d8d39118c2b018c215c37b73fd6893c3e1d4895be745ca8ff6eb83333ed3"},
+ {file = "SQLAlchemy-1.3.23-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:c7dc052432cd5d060d7437e217dd33c97025287f99a69a50e2dc1478dd610d64"},
+ {file = "SQLAlchemy-1.3.23-cp38-cp38-win32.whl", hash = "sha256:ecce8c021894a77d89808222b1ff9687ad84db54d18e4bd0500ca766737faaf6"},
+ {file = "SQLAlchemy-1.3.23-cp38-cp38-win_amd64.whl", hash = "sha256:37b83bf81b4b85dda273aaaed5f35ea20ad80606f672d94d2218afc565fb0173"},
+ {file = "SQLAlchemy-1.3.23-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:8be835aac18ec85351385e17b8665bd4d63083a7160a017bef3d640e8e65cadb"},
+ {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6ec1044908414013ebfe363450c22f14698803ce97fbb47e53284d55c5165848"},
+ {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:eab063a70cca4a587c28824e18be41d8ecc4457f8f15b2933584c6c6cccd30f0"},
+ {file = "SQLAlchemy-1.3.23-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:baeb451ee23e264de3f577fee5283c73d9bbaa8cb921d0305c0bbf700094b65b"},
+ {file = "SQLAlchemy-1.3.23-cp39-cp39-win32.whl", hash = "sha256:94208867f34e60f54a33a37f1c117251be91a47e3bfdb9ab8a7847f20886ad06"},
+ {file = "SQLAlchemy-1.3.23-cp39-cp39-win_amd64.whl", hash = "sha256:f4d972139d5000105fcda9539a76452039434013570d6059993120dc2a65e447"},
+ {file = "SQLAlchemy-1.3.23.tar.gz", hash = "sha256:6fca33672578666f657c131552c4ef8979c1606e494f78cd5199742dfb26918b"},
]
sqlalchemy-aio = [
{file = "sqlalchemy_aio-0.17.0-py3-none-any.whl", hash = "sha256:3f4aa392c38f032d6734826a4138a0f02ed3122d442ed142be1e5964f2a33b60"},
@@ -2067,16 +1825,13 @@ types-protobuf = [
{file = "types_protobuf-3.20.4.1-py3-none-any.whl", hash = "sha256:c227975ffd0f6a1eb1754e9a3aa9ca3b12265e63b462e9761e824c41fd25331c"},
]
typing-extensions = [
- {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
- {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
-]
-urllib3 = [
- {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
- {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
+ {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
+ {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
+ {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
]
uvicorn = [
- {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"},
- {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"},
+ {file = "uvicorn-0.18.1-py3-none-any.whl", hash = "sha256:013c4ea0787cc2dc456ef4368e18c01982e6be57903e4d3183218e543eb889b7"},
+ {file = "uvicorn-0.18.1.tar.gz", hash = "sha256:35703e6518105cfe53f16a5a9435db3e2e227d0784f1fd8fbc1214b1fdc108df"},
]
uvloop = [
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"},
@@ -2136,6 +1891,6 @@ win32-setctime = [
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
]
zipp = [
- {file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"},
- {file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"},
+ {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"},
+ {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"},
]
diff --git a/pyproject.toml b/pyproject.toml
index 69d58edd..b99bb353 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -11,8 +11,8 @@ script = "build.py"
[tool.poetry.dependencies]
python = "^3.10 | ^3.9 | ^3.8 | ^3.7"
aiofiles = "0.8.0"
-asgiref = "^3.4.1"
-attrs = "22.1.0"
+asgiref = "3.4.1"
+attrs = "21.2.0"
bech32 = "1.2.0"
bitstring = "3.1.9"
certifi = "2021.5.30"
@@ -22,18 +22,19 @@ ecdsa = "0.17.0"
embit = "0.4.9"
fastapi = "0.78.0"
h11 = "0.12.0"
+httpcore = "0.15.0"
httptools = "0.4.0"
httpx = "0.23.0"
-idna = "3.4"
-importlib-metadata = "^4.8.1"
-jinja2 = "3.0.3"
+idna = "3.2"
+importlib-metadata = "4.8.1"
+jinja2 = "3.0.1"
lnurl = "0.3.6"
-markupsafe = "2.1.1"
-marshmallow = "3.18.0"
-outcome = "1.2.0"
-psycopg2-binary = "2.9.3"
+markupsafe = "2.0.1"
+marshmallow = "3.17.0"
+outcome = "1.1.0"
+psycopg2-binary = "2.9.1"
pycryptodomex = "3.14.1"
-pydantic = "1.10.2"
+pydantic = "1.8.2"
pypng = "0.0.21"
pyqrcode = "1.2.1"
pyScss = "1.4.0"
@@ -44,18 +45,18 @@ rfc3986 = "1.5.0"
secp256k1 = "0.14.0"
shortuuid = "1.0.1"
six = "1.16.0"
-sniffio = "1.3.0"
-sqlalchemy = "1.3.24"
+sniffio = "1.2.0"
+sqlalchemy = "1.3.23"
sqlalchemy-aio = "0.17.0"
sse-starlette = "0.6.2"
-typing-extensions = "4.3.0"
-uvicorn = "0.18.3"
+typing-extensions = "3.10.0.2"
+uvicorn = "0.18.1"
uvloop = "0.16.0"
watchgod = "0.7"
websockets = "10.0"
-zipp = "^3.5.0"
-loguru = "0.6.0"
-cffi = "1.15.1"
+zipp = "3.5.0"
+loguru = "0.5.3"
+cffi = "1.15.0"
websocket-client = "1.3.3"
grpcio = "^1.49.1"
protobuf = "^4.21.6"
From 82fc156a04945b68845c0e9d2341bc7a24562726 Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 30 Sep 2022 12:25:48 +0100
Subject: [PATCH 251/696] Added the cashu router
---
lnbits/extensions/cashu/__init__.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index fa549ad2..ed67b134 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -6,20 +6,20 @@ from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
+from cashu.mint.router import router as cashu_router
+
db = Database("ext_cashu")
-cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["TPoS"])
-
+cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
+cashu_ext.include_router(router=cashu_router)
def cashu_renderer():
return template_renderer(["lnbits/extensions/cashu/templates"])
-
from .tasks import wait_for_paid_invoices
from .views import * # noqa
from .views_api import * # noqa
-
def cashu_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
From 485647d8a1907f41af86531df682faf56456e2a5 Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 30 Sep 2022 12:26:50 +0100
Subject: [PATCH 252/696] Extension authors
---
lnbits/extensions/cashu/config.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
index c688b22c..4f097e8b 100644
--- a/lnbits/extensions/cashu/config.json
+++ b/lnbits/extensions/cashu/config.json
@@ -2,5 +2,5 @@
"name": "Cashu Ecash",
"short_description": "Ecash mints with LN peg in/out",
"icon": "approval",
- "contributors": ["shinobi", "arcbtc", "calle"]
+ "contributors": ["arcbtc", "calle"]
}
From 7df673c72df52f319bbb0b18b53c90ac539e7255 Mon Sep 17 00:00:00 2001
From: ben
Date: Fri, 30 Sep 2022 14:23:03 +0100
Subject: [PATCH 253/696] started adding mint stuff
---
lnbits/extensions/cashu/crud.py | 59 ++++++
lnbits/extensions/cashu/ledger.py | 282 +++++++++++++++++++++++++++
lnbits/extensions/cashu/models.py | 105 +++++++++-
lnbits/extensions/cashu/views_api.py | 67 ++++++-
4 files changed, 510 insertions(+), 3 deletions(-)
create mode 100644 lnbits/extensions/cashu/ledger.py
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index f50da111..e1cdaf4e 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -68,3 +68,62 @@ async def get_cashus(wallet_ids: Union[str, List[str]]) -> List[Cashu]:
async def delete_cashu(cashu_id: str) -> None:
await db.execute("DELETE FROM cashu.cashu WHERE id = ?", (cashu_id,))
+
+
+
+###############MINT STUFF#################
+
+async def store_promise(
+ amount: int,
+ B_: str,
+ C_: str,
+ db: Database,
+ conn: Optional[Connection] = None,
+):
+
+ await (conn or db).execute(
+ """
+ INSERT INTO promises
+ (amount, B_b, C_b)
+ VALUES (?, ?, ?)
+ """,
+ (
+ amount,
+ str(B_),
+ str(C_),
+ ),
+ )
+
+
+async def get_proofs_used(
+ db: Database,
+ conn: Optional[Connection] = None,
+):
+
+ rows = await (conn or db).fetchall(
+ """
+ SELECT secret from proofs_used
+ """
+ )
+ return [row[0] for row in rows]
+
+
+async def invalidate_proof(
+ proof: Proof,
+ db: Database,
+ conn: Optional[Connection] = None,
+):
+
+ # we add the proof and secret to the used list
+ await (conn or db).execute(
+ """
+ INSERT INTO proofs_used
+ (amount, C, secret)
+ VALUES (?, ?, ?)
+ """,
+ (
+ proof.amount,
+ str(proof.C),
+ str(proof.secret),
+ ),
+ )
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py
new file mode 100644
index 00000000..cc5ef924
--- /dev/null
+++ b/lnbits/extensions/cashu/ledger.py
@@ -0,0 +1,282 @@
+import hashlib
+from typing import List, Set
+
+from models import BlindedMessage, BlindedSignature, Invoice, Proof
+from secp256k1 import PublicKey, PrivateKey
+
+from lnbits.core.services import check_transaction_status, create_invoice
+
+class Ledger:
+ def __init__(self, secret_key: str, db: str, MAX_ORDER: int = Query(64)):
+ self.proofs_used: Set[str] = set()
+
+ self.master_key: str = secret_key
+ self.keys: List[PrivateKey] = self._derive_keys(self.master_key)
+ self.pub_keys: List[PublicKey] = self._derive_pubkeys(self.keys)
+ self.db: Database = Database("mint", db)
+
+ async def load_used_proofs(self):
+ self.proofs_used = set(await get_proofs_used(db=self.db))
+
+ @staticmethod
+ def _derive_keys(master_key: str):
+ """Deterministic derivation of keys for 2^n values."""
+ return {
+ 2
+ ** i: PrivateKey(
+ hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
+ .hexdigest()
+ .encode("utf-8")[:32],
+ raw=True,
+ )
+ for i in range(MAX_ORDER)
+ }
+
+ @staticmethod
+ def _derive_pubkeys(keys: List[PrivateKey]):
+ return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
+
+ async def _generate_promises(self, amounts: List[int], B_s: List[str]):
+ """Generates promises that sum to the given amount."""
+ return [
+ await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True))
+ for (amount, B_) in zip(amounts, B_s)
+ ]
+
+ async def _generate_promise(self, amount: int, B_: PublicKey):
+ """Generates a promise for given amount and returns a pair (amount, C')."""
+ secret_key = self.keys[amount] # Get the correct key
+ C_ = step2_bob(B_, secret_key)
+ await store_promise(
+ amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db
+ )
+ return BlindedSignature(amount=amount, C_=C_.serialize().hex())
+
+ def _check_spendable(self, proof: Proof):
+ """Checks whether the proof was already spent."""
+ return not proof.secret in self.proofs_used
+
+ def _verify_proof(self, proof: Proof):
+ """Verifies that the proof of promise was issued by this ledger."""
+ if not self._check_spendable(proof):
+ raise Exception(f"tokens already spent. Secret: {proof.secret}")
+ secret_key = self.keys[proof.amount] # Get the correct key to check against
+ C = PublicKey(bytes.fromhex(proof.C), raw=True)
+ return verify(secret_key, C, proof.secret)
+
+ def _verify_outputs(
+ self, total: int, amount: int, output_data: List[BlindedMessage]
+ ):
+ """Verifies the expected split was correctly computed"""
+ fst_amt, snd_amt = total - amount, amount # we have two amounts to split to
+ fst_outputs = amount_split(fst_amt)
+ snd_outputs = amount_split(snd_amt)
+ expected = fst_outputs + snd_outputs
+ given = [o.amount for o in output_data]
+ return given == expected
+
+ def _verify_no_duplicates(
+ self, proofs: List[Proof], output_data: List[BlindedMessage]
+ ):
+ secrets = [p.secret for p in proofs]
+ if len(secrets) != len(list(set(secrets))):
+ return False
+ B_s = [od.B_ for od in output_data]
+ if len(B_s) != len(list(set(B_s))):
+ return False
+ return True
+
+ def _verify_split_amount(self, amount: int):
+ """Split amount like output amount can't be negative or too big."""
+ try:
+ self._verify_amount(amount)
+ except:
+ # For better error message
+ raise Exception("invalid split amount: " + str(amount))
+
+ def _verify_amount(self, amount: int):
+ """Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
+ valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
+ if not valid:
+ raise Exception("invalid amount: " + str(amount))
+ return amount
+
+ def _verify_equation_balanced(
+ self, proofs: List[Proof], outs: List[BlindedMessage]
+ ):
+ """Verify that Σoutputs - Σinputs = 0."""
+ sum_inputs = sum(self._verify_amount(p.amount) for p in proofs)
+ sum_outputs = sum(self._verify_amount(p.amount) for p in outs)
+ assert sum_outputs - sum_inputs == 0
+
+ def _get_output_split(self, amount: int):
+ """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
+ self._verify_amount(amount)
+ bits_amt = bin(amount)[::-1][:-2]
+ rv = []
+ for (pos, bit) in enumerate(bits_amt):
+ if bit == "1":
+ rv.append(2**pos)
+ return rv
+
+ async def _invalidate_proofs(self, proofs: List[Proof]):
+ """Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
+ # Mark proofs as used and prepare new promises
+ proof_msgs = set([p.secret for p in proofs])
+ self.proofs_used |= proof_msgs
+ # store in db
+ for p in proofs:
+ await invalidate_proof(p, db=self.db)
+
+ # Public methods
+ def get_pubkeys(self):
+ """Returns public keys for possible amounts."""
+ return {a: p.serialize().hex() for a, p in self.pub_keys.items()}
+
+ async def request_mint(self, amount):
+ """Returns Lightning invoice and stores it in the db."""
+ payment_request, payment_hash = payment_hash, payment_request = await create_invoice(
+ wallet_id=link.wallet,
+ amount=amount,
+ memo=link.description,
+ unhashed_description=link.description.encode("utf-8"),
+ extra={
+ "tag": "Cashu"
+ },
+ )
+
+ invoice = Invoice(
+ amount=amount, pr=payment_request, hash=payment_hash, issued=False
+ )
+ if not payment_request or not payment_hash:
+ raise Exception(f"Could not create Lightning invoice.")
+ await store_lightning_invoice(invoice, db=self.db)
+ return payment_request, payment_hash
+
+ async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None):
+ """Mints a promise for coins for B_."""
+ # check if lightning invoice was paid
+ if payment_hash and not await check_transaction_status(ayment_hash)
+ raise Exception("Lightning invoice not paid yet.")
+
+ for amount in amounts:
+ if amount not in [2**i for i in range(MAX_ORDER)]:
+ raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
+
+ promises = [
+ await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
+ ]
+ return promises
+
+ async def melt(self, proofs: List[Proof], amount: int, invoice: str):
+ """Invalidates proofs and pays a Lightning invoice."""
+ # if not LIGHTNING:
+ total = sum([p["amount"] for p in proofs])
+ # check that lightning fees are included
+ assert total + fee_reserve(amount * 1000) >= amount, Exception(
+ "provided proofs not enough for Lightning payment."
+ )
+
+ status, payment_hash = await pay_invoice(
+ wallet_id=link.wallet,
+ payment_request=invoice,
+ max_sat=amount,
+ extra={"tag": "Ecash melt"},
+ )
+
+ if status == True:
+ await self._invalidate_proofs(proofs)
+ return status, payment_hash
+
+ async def check_spendable(self, proofs: List[Proof]):
+ """Checks if all provided proofs are valid and still spendable (i.e. have not been spent)."""
+ return {i: self._check_spendable(p) for i, p in enumerate(proofs)}
+
+ async def split(
+ self, proofs: List[Proof], amount: int, output_data: List[BlindedMessage]
+ ):
+ """Consumes proofs and prepares new promises based on the amount split."""
+ self._verify_split_amount(amount)
+ # Verify proofs are valid
+ if not all([self._verify_proof(p) for p in proofs]):
+ return False
+
+ total = sum([p.amount for p in proofs])
+
+ if not self._verify_no_duplicates(proofs, output_data):
+ raise Exception("duplicate proofs or promises")
+ if amount > total:
+ raise Exception("split amount is higher than the total sum")
+ if not self._verify_outputs(total, amount, output_data):
+ raise Exception("split of promises is not as expected")
+
+ # Mark proofs as used and prepare new promises
+ await self._invalidate_proofs(proofs)
+
+ outs_fst = amount_split(total - amount)
+ outs_snd = amount_split(amount)
+ B_fst = [od.B_ for od in output_data[: len(outs_fst)]]
+ B_snd = [od.B_ for od in output_data[len(outs_fst) :]]
+ prom_fst, prom_snd = await self._generate_promises(
+ outs_fst, B_fst
+ ), await self._generate_promises(outs_snd, B_snd)
+ self._verify_equation_balanced(proofs, prom_fst + prom_snd)
+ return prom_fst, prom_snd
+
+
+#######FUNCTIONS###############
+def fee_reserve(amount_msat: int) -> int:
+ """Function for calculating the Lightning fee reserve"""
+ return max(
+ int(LIGHTNING_RESERVE_FEE_MIN), int(amount_msat * LIGHTNING_FEE_PERCENT / 100.0)
+ )
+
+def amount_split(amount):
+ """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
+ bits_amt = bin(amount)[::-1][:-2]
+ rv = []
+ for (pos, bit) in enumerate(bits_amt):
+ if bit == "1":
+ rv.append(2**pos)
+ return rv
+
+def hash_to_point(secret_msg):
+ """Generates x coordinate from the message hash and checks if the point lies on the curve.
+ If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
+ point = None
+ msg = secret_msg
+ while point is None:
+ _hash = hashlib.sha256(msg).hexdigest().encode("utf-8")
+ try:
+ # We construct compressed pub which has x coordinate encoded with even y
+ _hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes
+ _hash[0] = 0x02 # set first byte to represent even y coord
+ _hash = bytes(_hash)
+ point = PublicKey(_hash, raw=True)
+ except:
+ msg = _hash
+
+ return point
+
+
+def step1_alice(secret_msg):
+ secret_msg = secret_msg.encode("utf-8")
+ Y = hash_to_point(secret_msg)
+ r = PrivateKey()
+ B_ = Y + r.pubkey
+ return B_, r
+
+
+def step2_bob(B_, a):
+ C_ = B_.mult(a)
+ return C_
+
+
+def step3_alice(C_, r, A):
+ C = C_ - A.mult(r)
+ return C
+
+
+def verify(a, C, secret_msg):
+ Y = hash_to_point(secret_msg.encode("utf-8"))
+ return C == Y.mult(a)
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index 892abdf1..ba9303c5 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -31,5 +31,106 @@ class Pegs(BaseModel):
def from_row(cls, row: Row) -> "TPoS":
return cls(**dict(row))
-class PayLnurlWData(BaseModel):
- lnurl: str
\ No newline at end of file
+
+class Proof(BaseModel):
+ amount: int
+ secret: str
+ C: str
+ reserved: bool = False # whether this proof is reserved for sending
+ send_id: str = "" # unique ID of send attempt
+ time_created: str = ""
+ time_reserved: str = ""
+
+ @classmethod
+ def from_row(cls, row: Row):
+ return cls(
+ amount=row[0],
+ C=row[1],
+ secret=row[2],
+ reserved=row[3] or False,
+ send_id=row[4] or "",
+ time_created=row[5] or "",
+ time_reserved=row[6] or "",
+ )
+
+ @classmethod
+ def from_dict(cls, d: dict):
+ assert "secret" in d, "no secret in proof"
+ assert "amount" in d, "no amount in proof"
+ return cls(
+ amount=d.get("amount"),
+ C=d.get("C"),
+ secret=d.get("secret"),
+ reserved=d.get("reserved") or False,
+ send_id=d.get("send_id") or "",
+ time_created=d.get("time_created") or "",
+ time_reserved=d.get("time_reserved") or "",
+ )
+
+ def to_dict(self):
+ return dict(amount=self.amount, secret=self.secret, C=self.C)
+
+ def __getitem__(self, key):
+ return self.__getattribute__(key)
+
+ def __setitem__(self, key, val):
+ self.__setattr__(key, val)
+
+
+class Proofs(BaseModel):
+ """TODO: Use this model"""
+
+ proofs: List[Proof]
+
+
+class Invoice(BaseModel):
+ amount: int
+ pr: str
+ hash: str
+ issued: bool = False
+
+ @classmethod
+ def from_row(cls, row: Row):
+ return cls(
+ amount=int(row[0]),
+ pr=str(row[1]),
+ hash=str(row[2]),
+ issued=bool(row[3]),
+ )
+
+
+class BlindedMessage(BaseModel):
+ amount: int
+ B_: str
+
+
+class BlindedSignature(BaseModel):
+ amount: int
+ C_: str
+
+ @classmethod
+ def from_dict(cls, d: dict):
+ return cls(
+ amount=d["amount"],
+ C_=d["C_"],
+ )
+
+
+class MintPayloads(BaseModel):
+ blinded_messages: List[BlindedMessage] = []
+
+
+class SplitPayload(BaseModel):
+ proofs: List[Proof]
+ amount: int
+ output_data: MintPayloads
+
+
+class CheckPayload(BaseModel):
+ proofs: List[Proof]
+
+
+class MeltPayload(BaseModel):
+ proofs: List[Proof]
+ amount: int
+ invoice: str
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 49383945..5a1c500e 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -1,4 +1,6 @@
from http import HTTPStatus
+from secp256k1 import PublicKey
+from typing import Union
import httpx
from fastapi import Query
@@ -14,8 +16,9 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import cashu_ext
from .crud import create_cashu, delete_cashu, get_cashu, get_cashus, update_cashu_keys
-from .models import Cashu, Pegs, PayLnurlWData
+from .models import Cashu, Pegs, CheckPayload, MeltPayload, MintPayloads, SplitPayload
+import .ledger
@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
async def api_cashus(
@@ -168,3 +171,65 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
logger.error(exc)
return {"paid": False}
return status
+
+
+#################CASHU STUFF###################
+
+@cashu_ext.get("/keys")
+def keys():
+ """Get the public keys of the mint"""
+ return ledger.get_pubkeys()
+
+
+@cashu_ext.get("/mint")
+async def request_mint(amount: int = 0):
+ """Request minting of tokens. Server responds with a Lightning invoice."""
+ payment_request, payment_hash = await ledger.request_mint(amount)
+ print(f"Lightning invoice: {payment_request}")
+ return {"pr": payment_request, "hash": payment_hash}
+
+
+@cashu_ext.post("/mint")
+async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None):
+ amounts = []
+ B_s = []
+ for payload in payloads.blinded_messages:
+ amounts.append(payload.amount)
+ B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
+ try:
+ promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
+ return promises
+ except Exception as exc:
+ return {"error": str(exc)}
+
+
+@cashu_ext.post("/melt")
+async def melt(payload: MeltPayload):
+
+ ok, preimage = await ledger.melt(payload.proofs, payload.amount, payload.invoice)
+ return {"paid": ok, "preimage": preimage}
+
+
+@cashu_ext.post("/check")
+async def check_spendable(payload: CheckPayload):
+ return await ledger.check_spendable(payload.proofs)
+
+
+@cashu_ext.post("/split")
+async def split(payload: SplitPayload):
+ """
+ Requetst a set of tokens with amount "total" to be split into two
+ newly minted sets with amount "split" and "total-split".
+ """
+ proofs = payload.proofs
+ amount = payload.amount
+ output_data = payload.output_data.blinded_messages
+ try:
+ split_return = await ledger.split(proofs, amount, output_data)
+ except Exception as exc:
+ return {"error": str(exc)}
+ if not split_return:
+ """There was a problem with the split"""
+ raise Exception("could not split tokens.")
+ fst_promises, snd_promises = split_return
+ return {"fst": fst_promises, "snd": snd_promises}
\ No newline at end of file
From 11056f2a579f4c5b84a288688bd05ccac6a9619a Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 3 Oct 2022 10:45:13 +0100
Subject: [PATCH 254/696] Booting
---
lnbits/extensions/cashu/__init__.py | 3 ---
lnbits/extensions/cashu/crud.py | 15 ++++-----------
lnbits/extensions/cashu/ledger.py | 19 ++++++++++---------
lnbits/extensions/cashu/models.py | 4 +++-
lnbits/extensions/cashu/views_api.py | 4 ++--
5 files changed, 19 insertions(+), 26 deletions(-)
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index ed67b134..cf277664 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -6,12 +6,9 @@ from lnbits.db import Database
from lnbits.helpers import template_renderer
from lnbits.tasks import catch_everything_and_restart
-from cashu.mint.router import router as cashu_router
-
db = Database("ext_cashu")
cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
-cashu_ext.include_router(router=cashu_router)
def cashu_renderer():
return template_renderer(["lnbits/extensions/cashu/templates"])
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index e1cdaf4e..2892e6a4 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -3,7 +3,7 @@ from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
from . import db
-from .models import Cashu, Pegs
+from .models import Cashu, Pegs, Proof
from embit import script
from embit import ec
@@ -76,9 +76,7 @@ async def delete_cashu(cashu_id: str) -> None:
async def store_promise(
amount: int,
B_: str,
- C_: str,
- db: Database,
- conn: Optional[Connection] = None,
+ C_: str
):
await (conn or db).execute(
@@ -95,10 +93,7 @@ async def store_promise(
)
-async def get_proofs_used(
- db: Database,
- conn: Optional[Connection] = None,
-):
+async def get_proofs_used():
rows = await (conn or db).fetchall(
"""
@@ -109,9 +104,7 @@ async def get_proofs_used(
async def invalidate_proof(
- proof: Proof,
- db: Database,
- conn: Optional[Connection] = None,
+ proof: Proof
):
# we add the proof and secret to the used list
diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py
index cc5ef924..59cc3b92 100644
--- a/lnbits/extensions/cashu/ledger.py
+++ b/lnbits/extensions/cashu/ledger.py
@@ -1,22 +1,23 @@
import hashlib
from typing import List, Set
-from models import BlindedMessage, BlindedSignature, Invoice, Proof
+from .models import BlindedMessage, BlindedSignature, Invoice, Proof
from secp256k1 import PublicKey, PrivateKey
+from fastapi import Query
+
from lnbits.core.services import check_transaction_status, create_invoice
class Ledger:
- def __init__(self, secret_key: str, db: str, MAX_ORDER: int = Query(64)):
+ def __init__(self, secret_key: str, MAX_ORDER: int = Query(64)):
self.proofs_used: Set[str] = set()
self.master_key: str = secret_key
self.keys: List[PrivateKey] = self._derive_keys(self.master_key)
self.pub_keys: List[PublicKey] = self._derive_pubkeys(self.keys)
- self.db: Database = Database("mint", db)
async def load_used_proofs(self):
- self.proofs_used = set(await get_proofs_used(db=self.db))
+ self.proofs_used = set(await get_proofs_used)
@staticmethod
def _derive_keys(master_key: str):
@@ -48,7 +49,7 @@ class Ledger:
secret_key = self.keys[amount] # Get the correct key
C_ = step2_bob(B_, secret_key)
await store_promise(
- amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db
+ amount, B_=B_.serialize().hex(), C_=C_.serialize().hex()
)
return BlindedSignature(amount=amount, C_=C_.serialize().hex())
@@ -126,7 +127,7 @@ class Ledger:
self.proofs_used |= proof_msgs
# store in db
for p in proofs:
- await invalidate_proof(p, db=self.db)
+ await invalidate_proof(p)
# Public methods
def get_pubkeys(self):
@@ -150,13 +151,13 @@ class Ledger:
)
if not payment_request or not payment_hash:
raise Exception(f"Could not create Lightning invoice.")
- await store_lightning_invoice(invoice, db=self.db)
+ await store_lightning_invoice(invoice)
return payment_request, payment_hash
async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None):
"""Mints a promise for coins for B_."""
# check if lightning invoice was paid
- if payment_hash and not await check_transaction_status(ayment_hash)
+ if payment_hash and not await check_transaction_status(payment_hash):
raise Exception("Lightning invoice not paid yet.")
for amount in amounts:
@@ -224,7 +225,7 @@ class Ledger:
return prom_fst, prom_snd
-#######FUNCTIONS###############
+##############FUNCTIONS###############
def fee_reserve(amount_msat: int) -> int:
"""Function for calculating the Lightning fee reserve"""
return max(
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index ba9303c5..a673dfe7 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -1,5 +1,5 @@
from sqlite3 import Row
-from typing import Optional
+from typing import Optional, List
from fastapi import Query
from pydantic import BaseModel
@@ -31,6 +31,8 @@ class Pegs(BaseModel):
def from_row(cls, row: Row) -> "TPoS":
return cls(**dict(row))
+class PayLnurlWData(BaseModel):
+ lnurl: str
class Proof(BaseModel):
amount: int
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 5a1c500e..5cc6e271 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -16,9 +16,9 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import cashu_ext
from .crud import create_cashu, delete_cashu, get_cashu, get_cashus, update_cashu_keys
-from .models import Cashu, Pegs, CheckPayload, MeltPayload, MintPayloads, SplitPayload
+from .models import Cashu, Pegs, CheckPayload, MeltPayload, MintPayloads, SplitPayload, PayLnurlWData
-import .ledger
+from .ledger import Ledger, fee_reserve, amount_split, hash_to_point, step1_alice, step2_bob, step3_alice, verify
@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
async def api_cashus(
From f317a7cb2d9ddca205d38047d764b642cd3d17d2 Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 3 Oct 2022 14:43:02 +0100
Subject: [PATCH 255/696] Working through, getting functions working
---
lnbits/extensions/cashu/crud.py | 85 ++--
lnbits/extensions/cashu/ledger.py | 426 +++++++++---------
lnbits/extensions/cashu/migrations.py | 31 +-
lnbits/extensions/cashu/models.py | 9 +-
lnbits/extensions/cashu/tasks.py | 1 -
.../cashu/templates/cashu/index.html | 7 +-
lnbits/extensions/cashu/views.py | 1 -
lnbits/extensions/cashu/views_api.py | 51 ++-
8 files changed, 354 insertions(+), 257 deletions(-)
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 2892e6a4..7a9c25c3 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -1,24 +1,37 @@
+import os
+
from typing import List, Optional, Union
from lnbits.helpers import urlsafe_short_hash
from . import db
-from .models import Cashu, Pegs, Proof
+from .models import Cashu, Pegs, Proof, Promises
from embit import script
from embit import ec
from embit.networks import NETWORKS
+from embit import bip32
+from embit import bip39
from binascii import unhexlify, hexlify
+import random
+
+from loguru import logger
async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
cashu_id = urlsafe_short_hash()
- prv = ec.PrivateKey.from_wif(urlsafe_short_hash())
- pub = prv.get_public_key()
+
+ entropy = bytes([random.getrandbits(8) for i in range(16)])
+ mnemonic = bip39.mnemonic_from_bytes(entropy)
+ seed = bip39.mnemonic_to_seed(mnemonic)
+ root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
+
+ bip44_xprv = root.derive("m/44h/1h/0h")
+ bip44_xpub = bip44_xprv.to_public()
await db.execute(
"""
INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins, prvkey, pubkey)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
cashu_id,
@@ -28,8 +41,8 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
data.fraction,
data.maxsats,
data.coins,
- prv,
- pub
+ bip44_xprv.to_base58(),
+ bip44_xpub.to_base58()
),
)
@@ -39,17 +52,20 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
- if not wif:
- prv = ec.PrivateKey.from_wif(urlsafe_short_hash())
- else:
- prv = ec.PrivateKey.from_wif(wif)
- pub = prv.get_public_key()
- await db.execute("UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", (hexlify(prv.serialize()), hexlify(pub.serialize()), cashu_id))
+ entropy = bytes([random.getrandbits(8) for i in range(16)])
+ mnemonic = bip39.mnemonic_from_bytes(entropy)
+ seed = bip39.mnemonic_to_seed(mnemonic)
+ root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
+
+ bip44_xprv = root.derive("m/44h/1h/0h")
+ bip44_xpub = bip44_xprv.to_public()
+
+ await db.execute("UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", bip44_xprv.to_base58(), bip44_xpub.to_base58(), cashu_id)
row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
return Cashu(**row) if row else None
-async def get_cashu(cashu_id: str) -> Optional[Cashu]:
+async def get_cashu(cashu_id) -> Optional[Cashu]:
row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
return Cashu(**row) if row else None
@@ -66,57 +82,62 @@ async def get_cashus(wallet_ids: Union[str, List[str]]) -> List[Cashu]:
return [Cashu(**row) for row in rows]
-async def delete_cashu(cashu_id: str) -> None:
+async def delete_cashu(cashu_id) -> None:
await db.execute("DELETE FROM cashu.cashu WHERE id = ?", (cashu_id,))
-
+##########################################
###############MINT STUFF#################
+##########################################
async def store_promise(
amount: int,
B_: str,
- C_: str
+ C_: str,
+ cashu_id
):
+ promise_id = urlsafe_short_hash()
await (conn or db).execute(
"""
- INSERT INTO promises
- (amount, B_b, C_b)
- VALUES (?, ?, ?)
+ INSERT INTO cashu.promises
+ (id, amount, B_b, C_b, cashu_id)
+ VALUES (?, ?, ?, ?, ?)
""",
(
+ promise_id,
amount,
str(B_),
str(C_),
+ cashu_id
),
)
+async def get_promises(cashu_id) -> Optional[Cashu]:
+ row = await db.fetchall("SELECT * FROM cashu.promises WHERE cashu_id = ?", (promises_id,))
+ return Promises(**row) if row else None
-async def get_proofs_used():
-
- rows = await (conn or db).fetchall(
- """
- SELECT secret from proofs_used
- """
- )
+async def get_proofs_used(cashu_id):
+ rows = await db.fetchall("SELECT secret from cashu.proofs_used WHERE id = ?", (cashu_id,))
return [row[0] for row in rows]
async def invalidate_proof(
- proof: Proof
+ proof: Proof,
+ cashu_id
):
-
- # we add the proof and secret to the used list
+ invalidate_proof_id = urlsafe_short_hash()
await (conn or db).execute(
"""
- INSERT INTO proofs_used
- (amount, C, secret)
- VALUES (?, ?, ?)
+ INSERT INTO cashu.proofs_used
+ (id, amount, C, secret, cashu_id)
+ VALUES (?, ?, ?, ?, ?)
""",
(
+ invalidate_proof_id,
proof.amount,
str(proof.C),
str(proof.secret),
+ cashu_id
),
)
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py
index 59cc3b92..404f7ee8 100644
--- a/lnbits/extensions/cashu/ledger.py
+++ b/lnbits/extensions/cashu/ledger.py
@@ -5,234 +5,239 @@ from .models import BlindedMessage, BlindedSignature, Invoice, Proof
from secp256k1 import PublicKey, PrivateKey
from fastapi import Query
-
+from .crud import get_cashu
from lnbits.core.services import check_transaction_status, create_invoice
-class Ledger:
- def __init__(self, secret_key: str, MAX_ORDER: int = Query(64)):
- self.proofs_used: Set[str] = set()
-
- self.master_key: str = secret_key
- self.keys: List[PrivateKey] = self._derive_keys(self.master_key)
- self.pub_keys: List[PublicKey] = self._derive_pubkeys(self.keys)
-
- async def load_used_proofs(self):
- self.proofs_used = set(await get_proofs_used)
-
- @staticmethod
- def _derive_keys(master_key: str):
- """Deterministic derivation of keys for 2^n values."""
- return {
- 2
- ** i: PrivateKey(
- hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
- .hexdigest()
- .encode("utf-8")[:32],
- raw=True,
- )
- for i in range(MAX_ORDER)
- }
-
- @staticmethod
- def _derive_pubkeys(keys: List[PrivateKey]):
- return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
-
- async def _generate_promises(self, amounts: List[int], B_s: List[str]):
- """Generates promises that sum to the given amount."""
- return [
- await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True))
- for (amount, B_) in zip(amounts, B_s)
- ]
-
- async def _generate_promise(self, amount: int, B_: PublicKey):
- """Generates a promise for given amount and returns a pair (amount, C')."""
- secret_key = self.keys[amount] # Get the correct key
- C_ = step2_bob(B_, secret_key)
- await store_promise(
- amount, B_=B_.serialize().hex(), C_=C_.serialize().hex()
+def _derive_keys(master_key: str, cashu_id: str = Query(None)):
+ """Deterministic derivation of keys for 2^n values."""
+ return {
+ 2
+ ** i: PrivateKey(
+ hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
+ .hexdigest()
+ .encode("utf-8")[:32],
+ raw=True,
)
- return BlindedSignature(amount=amount, C_=C_.serialize().hex())
+ for i in range(MAX_ORDER)
+ }
- def _check_spendable(self, proof: Proof):
- """Checks whether the proof was already spent."""
- return not proof.secret in self.proofs_used
+def _derive_pubkeys(keys: List[PrivateKey], cashu_id: str = Query(None)):
+ return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
- def _verify_proof(self, proof: Proof):
- """Verifies that the proof of promise was issued by this ledger."""
- if not self._check_spendable(proof):
- raise Exception(f"tokens already spent. Secret: {proof.secret}")
- secret_key = self.keys[proof.amount] # Get the correct key to check against
- C = PublicKey(bytes.fromhex(proof.C), raw=True)
- return verify(secret_key, C, proof.secret)
+async def _generate_promises(amounts: List[int], B_s: List[str], cashu_id: str = Query(None)):
+ """Generates promises that sum to the given amount."""
+ return [
+ await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True))
+ for (amount, B_) in zip(amounts, B_s)
+ ]
- def _verify_outputs(
- self, total: int, amount: int, output_data: List[BlindedMessage]
- ):
- """Verifies the expected split was correctly computed"""
- fst_amt, snd_amt = total - amount, amount # we have two amounts to split to
- fst_outputs = amount_split(fst_amt)
- snd_outputs = amount_split(snd_amt)
- expected = fst_outputs + snd_outputs
- given = [o.amount for o in output_data]
- return given == expected
+async def _generate_promise(amount: int, B_: PublicKey, cashu_id: str = Query(None)):
+ """Generates a promise for given amount and returns a pair (amount, C')."""
+ secret_key = self.keys[amount] # Get the correct key
+ C_ = step2_bob(B_, secret_key)
+ await store_promise(
+ amount, B_=B_.serialize().hex(), C_=C_.serialize().hex()
+ )
+ return BlindedSignature(amount=amount, C_=C_.serialize().hex())
- def _verify_no_duplicates(
- self, proofs: List[Proof], output_data: List[BlindedMessage]
- ):
- secrets = [p.secret for p in proofs]
- if len(secrets) != len(list(set(secrets))):
- return False
- B_s = [od.B_ for od in output_data]
- if len(B_s) != len(list(set(B_s))):
- return False
- return True
+def _check_spendable(proof: Proof, cashu_id: str = Query(None)):
+ """Checks whether the proof was already spent."""
+ return not proof.secret in self.proofs_used
- def _verify_split_amount(self, amount: int):
- """Split amount like output amount can't be negative or too big."""
- try:
- self._verify_amount(amount)
- except:
- # For better error message
- raise Exception("invalid split amount: " + str(amount))
+def _verify_proof(proof: Proof, cashu_id: str = Query(None)):
+ """Verifies that the proof of promise was issued by this ledger."""
+ if not self._check_spendable(proof):
+ raise Exception(f"tokens already spent. Secret: {proof.secret}")
+ secret_key = self.keys[proof.amount] # Get the correct key to check against
+ C = PublicKey(bytes.fromhex(proof.C), raw=True)
+ return verify(secret_key, C, proof.secret)
- def _verify_amount(self, amount: int):
- """Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
- valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
- if not valid:
- raise Exception("invalid amount: " + str(amount))
- return amount
+def _verify_outputs(total: int, amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)):
+ """Verifies the expected split was correctly computed"""
+ fst_amt, snd_amt = total - amount, amount # we have two amounts to split to
+ fst_outputs = amount_split(fst_amt)
+ snd_outputs = amount_split(snd_amt)
+ expected = fst_outputs + snd_outputs
+ given = [o.amount for o in output_data]
+ return given == expected
- def _verify_equation_balanced(
- self, proofs: List[Proof], outs: List[BlindedMessage]
- ):
- """Verify that Σoutputs - Σinputs = 0."""
- sum_inputs = sum(self._verify_amount(p.amount) for p in proofs)
- sum_outputs = sum(self._verify_amount(p.amount) for p in outs)
- assert sum_outputs - sum_inputs == 0
+def _verify_no_duplicates(proofs: List[Proof], output_data: List[BlindedMessage], cashu_id: str = Query(None)):
+ secrets = [p.secret for p in proofs]
+ if len(secrets) != len(list(set(secrets))):
+ return False
+ B_s = [od.B_ for od in output_data]
+ if len(B_s) != len(list(set(B_s))):
+ return False
+ return True
- def _get_output_split(self, amount: int):
- """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
+def _verify_split_amount(amount: int, cashu_id: str = Query(None)):
+ """Split amount like output amount can't be negative or too big."""
+ try:
self._verify_amount(amount)
- bits_amt = bin(amount)[::-1][:-2]
- rv = []
- for (pos, bit) in enumerate(bits_amt):
- if bit == "1":
- rv.append(2**pos)
- return rv
+ except:
+ # For better error message
+ raise Exception("invalid split amount: " + str(amount))
- async def _invalidate_proofs(self, proofs: List[Proof]):
- """Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
- # Mark proofs as used and prepare new promises
- proof_msgs = set([p.secret for p in proofs])
- self.proofs_used |= proof_msgs
- # store in db
- for p in proofs:
- await invalidate_proof(p)
+def _verify_amount(amount: int, cashu_id: str = Query(None)):
+ """Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
+ valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
+ if not valid:
+ raise Exception("invalid amount: " + str(amount))
+ return amount
- # Public methods
- def get_pubkeys(self):
- """Returns public keys for possible amounts."""
- return {a: p.serialize().hex() for a, p in self.pub_keys.items()}
+def _verify_equation_balanced(proofs: List[Proof], outs: List[BlindedMessage], cashu_id: str = Query(None)):
+ """Verify that Σoutputs - Σinputs = 0."""
+ sum_inputs = sum(self._verify_amount(p.amount) for p in proofs)
+ sum_outputs = sum(self._verify_amount(p.amount) for p in outs)
+ assert sum_outputs - sum_inputs == 0
- async def request_mint(self, amount):
- """Returns Lightning invoice and stores it in the db."""
- payment_request, payment_hash = payment_hash, payment_request = await create_invoice(
- wallet_id=link.wallet,
- amount=amount,
- memo=link.description,
- unhashed_description=link.description.encode("utf-8"),
- extra={
- "tag": "Cashu"
- },
- )
+def _get_output_split(amount: int, cashu_id: str):
+ """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
+ self._verify_amount(amount)
+ bits_amt = bin(amount)[::-1][:-2]
+ rv = []
+ for (pos, bit) in enumerate(bits_amt):
+ if bit == "1":
+ rv.append(2**pos)
+ return rv
- invoice = Invoice(
- amount=amount, pr=payment_request, hash=payment_hash, issued=False
- )
- if not payment_request or not payment_hash:
- raise Exception(f"Could not create Lightning invoice.")
- await store_lightning_invoice(invoice)
- return payment_request, payment_hash
+async def _invalidate_proofs(proofs: List[Proof], cashu_id: str = Query(None)):
+ """Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
+ # Mark proofs as used and prepare new promises
+ proof_msgs = set([p.secret for p in proofs])
+ self.proofs_used |= proof_msgs
+ # store in db
+ for p in proofs:
+ await invalidate_proof(p)
- async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None):
- """Mints a promise for coins for B_."""
- # check if lightning invoice was paid
- if payment_hash and not await check_transaction_status(payment_hash):
+def get_pubkeys(cashu_id: str = Query(None)):
+ """Returns public keys for possible amounts."""
+ return {a: p.serialize().hex() for a, p in self.pub_keys.items()}
+
+async def request_mint(amount, cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
+ """Returns Lightning invoice and stores it in the db."""
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=cashu.wallet,
+ amount=amount,
+ memo=cashu.name,
+ unhashed_description=cashu.name.encode("utf-8"),
+ extra={
+ "tag": "Cashu"
+ },
+ )
+
+ invoice = Invoice(
+ amount=amount, pr=payment_request, hash=payment_hash, issued=False
+ )
+ if not payment_request or not payment_hash:
+ raise Exception(f"Could not create Lightning invoice.")
+ return payment_request, payment_hash
+
+async def mint(B_s: List[PublicKey], amounts: List[int], payment_hash: str = Query(None), cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
+ """Mints a promise for coins for B_."""
+ # check if lightning invoice was paid
+ if payment_hash:
+ if not await check_transaction_status(wallet_id=cashu.wallet, payment_hash=payment_hash):
raise Exception("Lightning invoice not paid yet.")
- for amount in amounts:
- if amount not in [2**i for i in range(MAX_ORDER)]:
- raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
+ for amount in amounts:
+ if amount not in [2**i for i in range(MAX_ORDER)]:
+ raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
- promises = [
- await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
- ]
- return promises
+ promises = [
+ await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
+ ]
+ return promises
- async def melt(self, proofs: List[Proof], amount: int, invoice: str):
- """Invalidates proofs and pays a Lightning invoice."""
- # if not LIGHTNING:
- total = sum([p["amount"] for p in proofs])
- # check that lightning fees are included
- assert total + fee_reserve(amount * 1000) >= amount, Exception(
- "provided proofs not enough for Lightning payment."
- )
+async def melt(proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
+ """Invalidates proofs and pays a Lightning invoice."""
+ # if not LIGHTNING:
+ total = sum([p["amount"] for p in proofs])
+ # check that lightning fees are included
+ assert total + fee_reserve(amount * 1000) >= amount, Exception(
+ "provided proofs not enough for Lightning payment."
+ )
- status, payment_hash = await pay_invoice(
- wallet_id=link.wallet,
- payment_request=invoice,
- max_sat=amount,
- extra={"tag": "Ecash melt"},
- )
+ status, payment_hash = await pay_invoice(
+ wallet_id=link.wallet,
+ payment_request=invoice,
+ max_sat=amount,
+ extra={"tag": "Ecash melt"},
+ )
- if status == True:
- await self._invalidate_proofs(proofs)
- return status, payment_hash
-
- async def check_spendable(self, proofs: List[Proof]):
- """Checks if all provided proofs are valid and still spendable (i.e. have not been spent)."""
- return {i: self._check_spendable(p) for i, p in enumerate(proofs)}
-
- async def split(
- self, proofs: List[Proof], amount: int, output_data: List[BlindedMessage]
- ):
- """Consumes proofs and prepares new promises based on the amount split."""
- self._verify_split_amount(amount)
- # Verify proofs are valid
- if not all([self._verify_proof(p) for p in proofs]):
- return False
-
- total = sum([p.amount for p in proofs])
-
- if not self._verify_no_duplicates(proofs, output_data):
- raise Exception("duplicate proofs or promises")
- if amount > total:
- raise Exception("split amount is higher than the total sum")
- if not self._verify_outputs(total, amount, output_data):
- raise Exception("split of promises is not as expected")
-
- # Mark proofs as used and prepare new promises
+ if status == True:
await self._invalidate_proofs(proofs)
+ return status, payment_hash
- outs_fst = amount_split(total - amount)
- outs_snd = amount_split(amount)
- B_fst = [od.B_ for od in output_data[: len(outs_fst)]]
- B_snd = [od.B_ for od in output_data[len(outs_fst) :]]
- prom_fst, prom_snd = await self._generate_promises(
- outs_fst, B_fst
- ), await self._generate_promises(outs_snd, B_snd)
- self._verify_equation_balanced(proofs, prom_fst + prom_snd)
- return prom_fst, prom_snd
+async def check_spendable(proofs: List[Proof], cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
+ """Checks if all provided proofs are valid and still spendable (i.e. have not been spent)."""
+ return {i: self._check_spendable(p) for i, p in enumerate(proofs)}
+
+async def split(proofs: List[Proof], amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
+ """Consumes proofs and prepares new promises based on the amount split."""
+ self._verify_split_amount(amount)
+ # Verify proofs are valid
+ if not all([self._verify_proof(p) for p in proofs]):
+ return False
+
+ total = sum([p.amount for p in proofs])
+
+ if not self._verify_no_duplicates(proofs, output_data):
+ raise Exception("duplicate proofs or promises")
+ if amount > total:
+ raise Exception("split amount is higher than the total sum")
+ if not self._verify_outputs(total, amount, output_data):
+ raise Exception("split of promises is not as expected")
+
+ # Mark proofs as used and prepare new promises
+ await self._invalidate_proofs(proofs)
+
+ outs_fst = amount_split(total - amount)
+ outs_snd = amount_split(amount)
+ B_fst = [od.B_ for od in output_data[: len(outs_fst)]]
+ B_snd = [od.B_ for od in output_data[len(outs_fst) :]]
+ prom_fst, prom_snd = await self._generate_promises(
+ outs_fst, B_fst
+ ), await self._generate_promises(outs_snd, B_snd)
+ self._verify_equation_balanced(proofs, prom_fst + prom_snd)
+ return prom_fst, prom_snd
-##############FUNCTIONS###############
-def fee_reserve(amount_msat: int) -> int:
+async def fee_reserve(amount_msat: int, cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
"""Function for calculating the Lightning fee reserve"""
return max(
int(LIGHTNING_RESERVE_FEE_MIN), int(amount_msat * LIGHTNING_FEE_PERCENT / 100.0)
)
-def amount_split(amount):
+async def amount_split(amount, cashu_id: str):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
"""Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
bits_amt = bin(amount)[::-1][:-2]
rv = []
@@ -241,7 +246,11 @@ def amount_split(amount):
rv.append(2**pos)
return rv
-def hash_to_point(secret_msg):
+async def hash_to_point(secret_msg, cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
"""Generates x coordinate from the message hash and checks if the point lies on the curve.
If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
point = None
@@ -260,24 +269,39 @@ def hash_to_point(secret_msg):
return point
-def step1_alice(secret_msg):
+async def step1_alice(secret_msg, cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
secret_msg = secret_msg.encode("utf-8")
Y = hash_to_point(secret_msg)
r = PrivateKey()
B_ = Y + r.pubkey
return B_, r
+async def step2_bob(B_, a, cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
-def step2_bob(B_, a):
C_ = B_.mult(a)
return C_
-def step3_alice(C_, r, A):
+async def step3_alice(C_, r, A, cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
C = C_ - A.mult(r)
return C
-def verify(a, C, secret_msg):
+async def verify(a, C, secret_msg, cashu_id: str = Query(None)):
+ cashu = await get_cashu(cashu_id)
+ if not cashu:
+ raise Exception(f"Could not find Cashu")
+
Y = hash_to_point(secret_msg.encode("utf-8"))
return C == Y.mult(a)
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index 53420062..f7d8f4f0 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -8,7 +8,7 @@ async def m001_initial(db):
id TEXT PRIMARY KEY,
wallet TEXT NOT NULL,
name TEXT NOT NULL,
- tickershort TEXT NOT NULL,
+ tickershort TEXT DEFAULT 'sats',
fraction BOOL,
maxsats INT,
coins INT,
@@ -32,3 +32,32 @@ async def m001_initial(db):
"""
)
+ """
+ Initial cashus table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE cashu.promises (
+ id TEXT PRIMARY KEY,
+ amount INT,
+ B_b TEXT NOT NULL,
+ C_b TEXT NOT NULL,
+ cashu_id TEXT NOT NULL
+ );
+ """
+ )
+
+ """
+ Initial cashus table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE cashu.proofs_used (
+ id TEXT PRIMARY KEY,
+ amount INT,
+ C TEXT NOT NULL,
+ secret TEXT NOT NULL,
+ cashu_id TEXT NOT NULL
+ );
+ """
+ )
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index a673dfe7..094966ff 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -9,7 +9,7 @@ class Cashu(BaseModel):
id: str = Query(None)
name: str = Query(None)
wallet: str = Query(None)
- tickershort: str
+ tickershort: str = Query(None)
fraction: bool = Query(None)
maxsats: int = Query(0)
coins: int = Query(0)
@@ -34,6 +34,13 @@ class Pegs(BaseModel):
class PayLnurlWData(BaseModel):
lnurl: str
+class Promises(BaseModel):
+ id: str
+ amount: int
+ B_b: str
+ C_b: str
+ cashu_id: str
+
class Proof(BaseModel):
amount: int
secret: str
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index fe00a591..5fbdde8e 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -9,7 +9,6 @@ from lnbits.tasks import internal_invoice_queue, register_invoice_listener
from .crud import get_cashu
-
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue)
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index 17b2a919..3cd57d45 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -80,13 +80,10 @@
-
-
Create Mint
+ :disable="formDialog.data.wallet == null || formDialog.data.name == null" type="submit">Create Mint
Cancel
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 4ac1f1ce..655ed028 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -22,7 +22,6 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
"cashu/index.html", {"request": request, "user": user.dict()}
)
-
@cashu_ext.get("/wallet")
async def cashu(request: Request):
return cashu_renderer().TemplateResponse("cashu/wallet.html",{"request": request})
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 5cc6e271..391aeda1 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -15,10 +15,25 @@ from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import cashu_ext
-from .crud import create_cashu, delete_cashu, get_cashu, get_cashus, update_cashu_keys
-from .models import Cashu, Pegs, CheckPayload, MeltPayload, MintPayloads, SplitPayload, PayLnurlWData
+from .ledger import get_pubkeys, request_mint, mint
-from .ledger import Ledger, fee_reserve, amount_split, hash_to_point, step1_alice, step2_bob, step3_alice, verify
+from .crud import (
+ create_cashu,
+ delete_cashu,
+ get_cashu,
+ get_cashus,
+ update_cashu_keys
+)
+
+from .models import (
+ Cashu,
+ Pegs,
+ CheckPayload,
+ MeltPayload,
+ MintPayloads,
+ SplitPayload,
+ PayLnurlWData
+)
@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
async def api_cashus(
@@ -173,50 +188,54 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
return status
-#################CASHU STUFF###################
+########################################
+#################MINT###################
+########################################
@cashu_ext.get("/keys")
-def keys():
+def keys(cashu_id: str):
"""Get the public keys of the mint"""
- return ledger.get_pubkeys()
+ return get_pubkeys(cashu_id)
@cashu_ext.get("/mint")
-async def request_mint(amount: int = 0):
+async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
"""Request minting of tokens. Server responds with a Lightning invoice."""
- payment_request, payment_hash = await ledger.request_mint(amount)
+ payment_request, payment_hash = await request_mint(amount, cashu_id)
print(f"Lightning invoice: {payment_request}")
return {"pr": payment_request, "hash": payment_hash}
@cashu_ext.post("/mint")
-async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None):
+async def mint_coins(payloads: MintPayloads, payment_hash: Union[str, None] = None, cashu_id: str = Query(None)):
amounts = []
B_s = []
for payload in payloads.blinded_messages:
amounts.append(payload.amount)
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
+ promises = await mint(B_s, amounts, payment_hash, cashu_id)
+ logger.debug(promises)
try:
- promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
+ promises = await mint(B_s, amounts, payment_hash, cashu_id)
return promises
except Exception as exc:
return {"error": str(exc)}
@cashu_ext.post("/melt")
-async def melt(payload: MeltPayload):
+async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
- ok, preimage = await ledger.melt(payload.proofs, payload.amount, payload.invoice)
+ ok, preimage = await melt(payload.proofs, payload.amount, payload.invoice, cashu_id)
return {"paid": ok, "preimage": preimage}
@cashu_ext.post("/check")
-async def check_spendable(payload: CheckPayload):
- return await ledger.check_spendable(payload.proofs)
+async def check_spendable_coins(payload: CheckPayload, cashu_id: str = Query(None)):
+ return await check_spendable(payload.proofs, cashu_id)
@cashu_ext.post("/split")
-async def split(payload: SplitPayload):
+async def spli_coinst(payload: SplitPayload, cashu_id: str = Query(None)):
"""
Requetst a set of tokens with amount "total" to be split into two
newly minted sets with amount "split" and "total-split".
@@ -225,7 +244,7 @@ async def split(payload: SplitPayload):
amount = payload.amount
output_data = payload.output_data.blinded_messages
try:
- split_return = await ledger.split(proofs, amount, output_data)
+ split_return = await split(proofs, amount, output_data)
except Exception as exc:
return {"error": str(exc)}
if not split_return:
From b7cd0f4d453c878012333938e52436ba69f06a29 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 10:09:16 +0300
Subject: [PATCH 256/696] feat: generate keys
---
lnbits/extensions/cashu/core/secp.py | 52 ++++++++++++++++++++++++++
lnbits/extensions/cashu/mint.py | 12 ++++++
lnbits/extensions/cashu/mint_helper.py | 22 +++++++++++
lnbits/extensions/cashu/views_api.py | 23 ++++++++++--
4 files changed, 105 insertions(+), 4 deletions(-)
create mode 100644 lnbits/extensions/cashu/core/secp.py
create mode 100644 lnbits/extensions/cashu/mint.py
create mode 100644 lnbits/extensions/cashu/mint_helper.py
diff --git a/lnbits/extensions/cashu/core/secp.py b/lnbits/extensions/cashu/core/secp.py
new file mode 100644
index 00000000..33416434
--- /dev/null
+++ b/lnbits/extensions/cashu/core/secp.py
@@ -0,0 +1,52 @@
+from secp256k1 import PrivateKey, PublicKey
+
+
+# We extend the public key to define some operations on points
+# Picked from https://github.com/WTRMQDev/secp256k1-zkp-py/blob/master/secp256k1_zkp/__init__.py
+class PublicKeyExt(PublicKey):
+ def __add__(self, pubkey2):
+ if isinstance(pubkey2, PublicKey):
+ new_pub = PublicKey()
+ new_pub.combine([self.public_key, pubkey2.public_key])
+ return new_pub
+ else:
+ raise TypeError("Cant add pubkey and %s" % pubkey2.__class__)
+
+ def __neg__(self):
+ serialized = self.serialize()
+ first_byte, remainder = serialized[:1], serialized[1:]
+ # flip odd/even byte
+ first_byte = {b"\x03": b"\x02", b"\x02": b"\x03"}[first_byte]
+ return PublicKey(first_byte + remainder, raw=True)
+
+ def __sub__(self, pubkey2):
+ if isinstance(pubkey2, PublicKey):
+ return self + (-pubkey2)
+ else:
+ raise TypeError("Can't add pubkey and %s" % pubkey2.__class__)
+
+ def mult(self, privkey):
+ if isinstance(privkey, PrivateKey):
+ return self.tweak_mul(privkey.private_key)
+ else:
+ raise TypeError("Can't multiply with non privatekey")
+
+ def __eq__(self, pubkey2):
+ if isinstance(pubkey2, PublicKey):
+ seq1 = self.to_data()
+ seq2 = pubkey2.to_data()
+ return seq1 == seq2
+ else:
+ raise TypeError("Can't compare pubkey and %s" % pubkey2.__class__)
+
+ def to_data(self):
+ return [self.public_key.data[i] for i in range(64)]
+
+
+# Horrible monkeypatching
+PublicKey.__add__ = PublicKeyExt.__add__
+PublicKey.__neg__ = PublicKeyExt.__neg__
+PublicKey.__sub__ = PublicKeyExt.__sub__
+PublicKey.mult = PublicKeyExt.mult
+PublicKey.__eq__ = PublicKeyExt.__eq__
+PublicKey.to_data = PublicKeyExt.to_data
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
new file mode 100644
index 00000000..703bf426
--- /dev/null
+++ b/lnbits/extensions/cashu/mint.py
@@ -0,0 +1,12 @@
+
+from .crud import get_cashu
+from .mint_helper import derive_keys, derive_pubkeys
+
+
+def get_pubkeys(xpriv: str):
+ """Returns public keys for possible amounts."""
+
+ keys = derive_keys(xpriv)
+ pub_keys = derive_pubkeys(keys)
+
+ return {a: p.serialize().hex() for a, p in pub_keys.items()}
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
new file mode 100644
index 00000000..30e66b03
--- /dev/null
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -0,0 +1,22 @@
+import hashlib
+from typing import List, Set
+from .core.secp import PrivateKey, PublicKey
+
+# todo: extract const
+MAX_ORDER = 64
+
+def derive_keys(master_key: str):
+ """Deterministic derivation of keys for 2^n values."""
+ return {
+ 2
+ ** i: PrivateKey(
+ hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
+ .hexdigest()
+ .encode("utf-8")[:32],
+ raw=True,
+ )
+ for i in range(MAX_ORDER)
+ }
+
+def derive_pubkeys(keys: List[PrivateKey]):
+ return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 391aeda1..65323715 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -15,7 +15,8 @@ from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import cashu_ext
-from .ledger import get_pubkeys, request_mint, mint
+from .ledger import request_mint, mint
+from .mint import get_pubkeys
from .crud import (
create_cashu,
@@ -35,6 +36,11 @@ from .models import (
PayLnurlWData
)
+########################################
+#################MINT CRUD##############
+########################################
+
+# todo: use /mints
@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
async def api_cashus(
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
@@ -83,6 +89,9 @@ async def api_cashu_delete(
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+########################################
+#################????###################
+########################################
@cashu_ext.post("/api/v1/cashus/{cashu_id}/invoices", status_code=HTTPStatus.CREATED)
async def api_cashu_create_invoice(
amount: int = Query(..., ge=1), tipAmount: int = None, cashu_id: str = None
@@ -192,10 +201,16 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
#################MINT###################
########################################
-@cashu_ext.get("/keys")
-def keys(cashu_id: str):
+@cashu_ext.get("/api/v1/mint/keys/{cashu_id}", status_code=HTTPStatus.OK)
+async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)):
"""Get the public keys of the mint"""
- return get_pubkeys(cashu_id)
+ print('############################')
+ mint = await get_cashu(cashu_id)
+ if mint is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
+ return get_pubkeys(mint.prvkey)
@cashu_ext.get("/mint")
From 7720525eb82ffd9190f8f46133a63de7baebb774 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 10:23:42 +0300
Subject: [PATCH 257/696] feat: generate invoice for amount
---
lnbits/extensions/cashu/mint.py | 15 +++++++++++++--
lnbits/extensions/cashu/views_api.py | 22 +++++++++++++++++++---
2 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index 703bf426..70b6895e 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -1,5 +1,5 @@
-from .crud import get_cashu
+from .models import Cashu
from .mint_helper import derive_keys, derive_pubkeys
@@ -9,4 +9,15 @@ def get_pubkeys(xpriv: str):
keys = derive_keys(xpriv)
pub_keys = derive_pubkeys(keys)
- return {a: p.serialize().hex() for a, p in pub_keys.items()}
\ No newline at end of file
+ return {a: p.serialize().hex() for a, p in pub_keys.items()}
+
+async def request_mint(mint: Cashu, amount):
+ """Returns Lightning invoice and stores it in the db."""
+ payment_request, checking_id = await self._request_lightning_invoice(amount)
+ invoice = Invoice(
+ amount=amount, pr=payment_request, hash=checking_id, issued=False
+ )
+ if not payment_request or not checking_id:
+ raise Exception(f"Could not create Lightning invoice.")
+ await store_lightning_invoice(invoice, db=self.db)
+ return payment_request, checking_id
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 65323715..a4f8a2d8 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -204,7 +204,6 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
@cashu_ext.get("/api/v1/mint/keys/{cashu_id}", status_code=HTTPStatus.OK)
async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)):
"""Get the public keys of the mint"""
- print('############################')
mint = await get_cashu(cashu_id)
if mint is None:
raise HTTPException(
@@ -213,10 +212,27 @@ async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(ge
return get_pubkeys(mint.prvkey)
-@cashu_ext.get("/mint")
+@cashu_ext.get("/api/v1/mint/{cashu_id}")
async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
"""Request minting of tokens. Server responds with a Lightning invoice."""
- payment_request, payment_hash = await request_mint(amount, cashu_id)
+ print('############################')
+ cashu = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
+
+ try:
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=cashu.wallet,
+ amount=amount,
+ memo=f"{cashu.name}",
+ extra={"tag": "cashu"},
+ )
+ except Exception as e:
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+
+
print(f"Lightning invoice: {payment_request}")
return {"pr": payment_request, "hash": payment_hash}
From 2c54c240ba316e8ffa4457e7cdc56faf072b2f75 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 11:08:46 +0300
Subject: [PATCH 258/696] feat: store generated invoices per mint
---
lnbits/extensions/cashu/core/base.py | 167 ++++++++++++++++++++++++++
lnbits/extensions/cashu/crud.py | 52 +++++++-
lnbits/extensions/cashu/migrations.py | 15 +++
lnbits/extensions/cashu/mint.py | 29 +++--
lnbits/extensions/cashu/views_api.py | 42 ++++---
5 files changed, 276 insertions(+), 29 deletions(-)
create mode 100644 lnbits/extensions/cashu/core/base.py
diff --git a/lnbits/extensions/cashu/core/base.py b/lnbits/extensions/cashu/core/base.py
new file mode 100644
index 00000000..0aa02442
--- /dev/null
+++ b/lnbits/extensions/cashu/core/base.py
@@ -0,0 +1,167 @@
+from sqlite3 import Row
+from typing import List, Union
+
+from pydantic import BaseModel
+
+
+class CashuError(BaseModel):
+ code = "000"
+ error = "CashuError"
+
+
+class P2SHScript(BaseModel):
+ script: str
+ signature: str
+ address: Union[str, None] = None
+
+ @classmethod
+ def from_row(cls, row: Row):
+ return cls(
+ address=row[0],
+ script=row[1],
+ signature=row[2],
+ used=row[3],
+ )
+
+
+class Proof(BaseModel):
+ amount: int
+ secret: str = ""
+ C: str
+ script: Union[P2SHScript, None] = None
+ reserved: bool = False # whether this proof is reserved for sending
+ send_id: str = "" # unique ID of send attempt
+ time_created: str = ""
+ time_reserved: str = ""
+
+ @classmethod
+ def from_row(cls, row: Row):
+ return cls(
+ amount=row[0],
+ C=row[1],
+ secret=row[2],
+ reserved=row[3] or False,
+ send_id=row[4] or "",
+ time_created=row[5] or "",
+ time_reserved=row[6] or "",
+ )
+
+ @classmethod
+ def from_dict(cls, d: dict):
+ assert "amount" in d, "no amount in proof"
+ return cls(
+ amount=d.get("amount"),
+ C=d.get("C"),
+ secret=d.get("secret") or "",
+ reserved=d.get("reserved") or False,
+ send_id=d.get("send_id") or "",
+ time_created=d.get("time_created") or "",
+ time_reserved=d.get("time_reserved") or "",
+ )
+
+ def to_dict(self):
+ return dict(amount=self.amount, secret=self.secret, C=self.C)
+
+ def to_dict_no_secret(self):
+ return dict(amount=self.amount, C=self.C)
+
+ def __getitem__(self, key):
+ return self.__getattribute__(key)
+
+ def __setitem__(self, key, val):
+ self.__setattr__(key, val)
+
+
+class Proofs(BaseModel):
+ """TODO: Use this model"""
+
+ proofs: List[Proof]
+
+
+class Invoice(BaseModel):
+ amount: int
+ pr: str
+ hash: str
+ issued: bool = False
+
+ @classmethod
+ def from_row(cls, row: Row):
+ return cls(
+ amount=int(row[0]),
+ pr=str(row[1]),
+ hash=str(row[2]),
+ issued=bool(row[3]),
+ )
+
+
+class BlindedMessage(BaseModel):
+ amount: int
+ B_: str
+
+
+class BlindedSignature(BaseModel):
+ amount: int
+ C_: str
+
+ @classmethod
+ def from_dict(cls, d: dict):
+ return cls(
+ amount=d["amount"],
+ C_=d["C_"],
+ )
+
+
+class MintRequest(BaseModel):
+ blinded_messages: List[BlindedMessage] = []
+
+
+class GetMintResponse(BaseModel):
+ pr: str
+ hash: str
+
+
+class GetMeltResponse(BaseModel):
+ paid: Union[bool, None]
+ preimage: Union[str, None]
+
+
+class SplitRequest(BaseModel):
+ proofs: List[Proof]
+ amount: int
+ output_data: Union[
+ MintRequest, None
+ ] = None # backwards compatibility with clients < v0.2.2
+ outputs: Union[MintRequest, None] = None
+
+ def __init__(self, **data):
+ super().__init__(**data)
+ self.backwards_compatibility_v021()
+
+ def backwards_compatibility_v021(self):
+ # before v0.2.2: output_data, after: outputs
+ if self.output_data:
+ self.outputs = self.output_data
+ self.output_data = None
+
+
+class PostSplitResponse(BaseModel):
+ fst: List[BlindedSignature]
+ snd: List[BlindedSignature]
+
+
+class CheckRequest(BaseModel):
+ proofs: List[Proof]
+
+
+class CheckFeesRequest(BaseModel):
+ pr: str
+
+
+class CheckFeesResponse(BaseModel):
+ fee: Union[int, None]
+
+
+class MeltRequest(BaseModel):
+ proofs: List[Proof]
+ amount: int = None # deprecated
+ invoice: str
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 7a9c25c3..c991a8ec 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -1,6 +1,7 @@
import os
from typing import List, Optional, Union
+from .core.base import Invoice
from lnbits.helpers import urlsafe_short_hash
@@ -98,7 +99,7 @@ async def store_promise(
):
promise_id = urlsafe_short_hash()
- await (conn or db).execute(
+ await db.execute(
"""
INSERT INTO cashu.promises
(id, amount, B_b, C_b, cashu_id)
@@ -140,4 +141,51 @@ async def invalidate_proof(
str(proof.secret),
cashu_id
),
- )
\ No newline at end of file
+ )
+
+
+
+
+
+
+########################################
+############ MINT INVOICES #############
+########################################
+
+
+async def store_lightning_invoice(cashu_id: str, invoice: Invoice):
+ await db.execute(
+ """
+ INSERT INTO cashu.invoices
+ (cashu_id, amount, pr, hash, issued)
+ VALUES (?, ?, ?, ?, ?)
+ """,
+ (
+ cashu_id,
+ invoice.amount,
+ invoice.pr,
+ invoice.hash,
+ invoice.issued,
+ ),
+ )
+
+async def get_lightning_invoice(cashu_id: str, hash: str):
+ row = await db.fetchone(
+ """
+ SELECT * from invoices
+ WHERE cashu_id =? AND hash = ?
+ """,
+ (cashu_id, hash,),
+ )
+ return Invoice.from_row(row)
+
+
+async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
+ await db.execute(
+ "UPDATE invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
+ (
+ issued,
+ cashu_id,
+ hash,
+ ),
+ )
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index f7d8f4f0..3f1df660 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -60,4 +60,19 @@ async def m001_initial(db):
cashu_id TEXT NOT NULL
);
"""
+ )
+
+ await db.execute(
+ """
+ CREATE TABLE IF NOT EXISTS cashu.invoices (
+ cashu_id TEXT NOT NULL,
+ amount INTEGER NOT NULL,
+ pr TEXT NOT NULL,
+ hash TEXT NOT NULL,
+ issued BOOL NOT NULL,
+
+ UNIQUE (hash)
+
+ );
+ """
)
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index 70b6895e..fca096ed 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -11,13 +11,22 @@ def get_pubkeys(xpriv: str):
return {a: p.serialize().hex() for a, p in pub_keys.items()}
-async def request_mint(mint: Cashu, amount):
- """Returns Lightning invoice and stores it in the db."""
- payment_request, checking_id = await self._request_lightning_invoice(amount)
- invoice = Invoice(
- amount=amount, pr=payment_request, hash=checking_id, issued=False
- )
- if not payment_request or not checking_id:
- raise Exception(f"Could not create Lightning invoice.")
- await store_lightning_invoice(invoice, db=self.db)
- return payment_request, checking_id
\ No newline at end of file
+# async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None):
+# """Mints a promise for coins for B_."""
+# # check if lightning invoice was paid
+# if LIGHTNING:
+# try:
+# paid = await self._check_lightning_invoice(payment_hash)
+# except:
+# raise Exception("could not check invoice.")
+# if not paid:
+# raise Exception("Lightning invoice not paid yet.")
+
+# for amount in amounts:
+# if amount not in [2**i for i in range(MAX_ORDER)]:
+# raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
+
+# promises = [
+# await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
+# ]
+# return promises
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index a4f8a2d8..c0bc59f3 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -13,6 +13,7 @@ from lnbits.core.crud import get_user
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
+from .core.base import CashuError
from . import cashu_ext
from .ledger import request_mint, mint
@@ -22,12 +23,13 @@ from .crud import (
create_cashu,
delete_cashu,
get_cashu,
- get_cashus,
- update_cashu_keys
+ get_cashus,
+ store_lightning_invoice,
)
from .models import (
- Cashu,
+ Cashu,
+ Invoice,
Pegs,
CheckPayload,
MeltPayload,
@@ -215,7 +217,7 @@ async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(ge
@cashu_ext.get("/api/v1/mint/{cashu_id}")
async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
"""Request minting of tokens. Server responds with a Lightning invoice."""
- print('############################')
+ print('############################ amount', amount)
cashu = await get_cashu(cashu_id)
if cashu is None:
raise HTTPException(
@@ -229,28 +231,34 @@ async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
memo=f"{cashu.name}",
extra={"tag": "cashu"},
)
+ invoice = Invoice(
+ amount=amount, pr=payment_request, hash=payment_hash, issued=False
+ )
+ await store_lightning_invoice(cashu_id, invoice)
except Exception as e:
- raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+ logger.error(e)
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(cashu_id))
-
- print(f"Lightning invoice: {payment_request}")
return {"pr": payment_request, "hash": payment_hash}
@cashu_ext.post("/mint")
async def mint_coins(payloads: MintPayloads, payment_hash: Union[str, None] = None, cashu_id: str = Query(None)):
+ """
+ Requests the minting of tokens belonging to a paid payment request.
+
+ Call this endpoint after `GET /mint`.
+ """
amounts = []
B_s = []
- for payload in payloads.blinded_messages:
- amounts.append(payload.amount)
- B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
- promises = await mint(B_s, amounts, payment_hash, cashu_id)
- logger.debug(promises)
- try:
- promises = await mint(B_s, amounts, payment_hash, cashu_id)
- return promises
- except Exception as exc:
- return {"error": str(exc)}
+ # for payload in payloads.blinded_messages:
+ # amounts.append(payload.amount)
+ # B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
+ # try:
+ # promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
+ # return promises
+ # except Exception as exc:
+ # return CashuError(error=str(exc))
@cashu_ext.post("/melt")
From 104aca33ff875bf676661f89255454cb3e4d898c Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 11:17:02 +0300
Subject: [PATCH 259/696] chore: format
---
lnbits/extensions/cashu/__init__.py | 3 +
lnbits/extensions/cashu/crud.py | 84 +-
lnbits/extensions/cashu/ledger.py | 100 +-
lnbits/extensions/cashu/migrations.py | 2 +-
lnbits/extensions/cashu/mint.py | 8 +-
lnbits/extensions/cashu/mint_helper.py | 5 +-
lnbits/extensions/cashu/models.py | 9 +-
lnbits/extensions/cashu/tasks.py | 1 +
.../cashu/templates/cashu/_api_docs.html | 3 +-
.../cashu/templates/cashu/_cashu.html | 7 +-
.../cashu/templates/cashu/index.html | 160 ++-
.../cashu/templates/cashu/mint.html | 13 +-
.../cashu/templates/cashu/wallet.html | 1104 ++++++++++-------
lnbits/extensions/cashu/views.py | 9 +-
lnbits/extensions/cashu/views_api.py | 94 +-
15 files changed, 992 insertions(+), 610 deletions(-)
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index cf277664..bd7d5513 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -10,13 +10,16 @@ db = Database("ext_cashu")
cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
+
def cashu_renderer():
return template_renderer(["lnbits/extensions/cashu/templates"])
+
from .tasks import wait_for_paid_invoices
from .views import * # noqa
from .views_api import * # noqa
+
def cashu_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index c991a8ec..448614ac 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -1,22 +1,18 @@
import os
-
+import random
+from binascii import hexlify, unhexlify
from typing import List, Optional, Union
-from .core.base import Invoice
+
+from embit import bip32, bip39, ec, script
+from embit.networks import NETWORKS
+from loguru import logger
from lnbits.helpers import urlsafe_short_hash
from . import db
-from .models import Cashu, Pegs, Proof, Promises
+from .core.base import Invoice
+from .models import Cashu, Pegs, Promises, Proof
-from embit import script
-from embit import ec
-from embit.networks import NETWORKS
-from embit import bip32
-from embit import bip39
-from binascii import unhexlify, hexlify
-import random
-
-from loguru import logger
async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
cashu_id = urlsafe_short_hash()
@@ -25,7 +21,7 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
mnemonic = bip39.mnemonic_from_bytes(entropy)
seed = bip39.mnemonic_to_seed(mnemonic)
root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
-
+
bip44_xprv = root.derive("m/44h/1h/0h")
bip44_xpub = bip44_xprv.to_public()
@@ -43,7 +39,7 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
data.maxsats,
data.coins,
bip44_xprv.to_base58(),
- bip44_xpub.to_base58()
+ bip44_xpub.to_base58(),
),
)
@@ -57,11 +53,16 @@ async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
mnemonic = bip39.mnemonic_from_bytes(entropy)
seed = bip39.mnemonic_to_seed(mnemonic)
root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
-
+
bip44_xprv = root.derive("m/44h/1h/0h")
bip44_xpub = bip44_xprv.to_public()
- await db.execute("UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", bip44_xprv.to_base58(), bip44_xpub.to_base58(), cashu_id)
+ await db.execute(
+ "UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?",
+ bip44_xprv.to_base58(),
+ bip44_xpub.to_base58(),
+ cashu_id,
+ )
row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
return Cashu(**row) if row else None
@@ -91,12 +92,8 @@ async def delete_cashu(cashu_id) -> None:
###############MINT STUFF#################
##########################################
-async def store_promise(
- amount: int,
- B_: str,
- C_: str,
- cashu_id
-):
+
+async def store_promise(amount: int, B_: str, C_: str, cashu_id):
promise_id = urlsafe_short_hash()
await db.execute(
@@ -105,28 +102,25 @@ async def store_promise(
(id, amount, B_b, C_b, cashu_id)
VALUES (?, ?, ?, ?, ?)
""",
- (
- promise_id,
- amount,
- str(B_),
- str(C_),
- cashu_id
- ),
+ (promise_id, amount, str(B_), str(C_), cashu_id),
)
+
async def get_promises(cashu_id) -> Optional[Cashu]:
- row = await db.fetchall("SELECT * FROM cashu.promises WHERE cashu_id = ?", (promises_id,))
+ row = await db.fetchall(
+ "SELECT * FROM cashu.promises WHERE cashu_id = ?", (promises_id,)
+ )
return Promises(**row) if row else None
+
async def get_proofs_used(cashu_id):
- rows = await db.fetchall("SELECT secret from cashu.proofs_used WHERE id = ?", (cashu_id,))
+ rows = await db.fetchall(
+ "SELECT secret from cashu.proofs_used WHERE id = ?", (cashu_id,)
+ )
return [row[0] for row in rows]
-async def invalidate_proof(
- proof: Proof,
- cashu_id
-):
+async def invalidate_proof(proof: Proof, cashu_id):
invalidate_proof_id = urlsafe_short_hash()
await (conn or db).execute(
"""
@@ -134,20 +128,10 @@ async def invalidate_proof(
(id, amount, C, secret, cashu_id)
VALUES (?, ?, ?, ?, ?)
""",
- (
- invalidate_proof_id,
- proof.amount,
- str(proof.C),
- str(proof.secret),
- cashu_id
- ),
+ (invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id),
)
-
-
-
-
########################################
############ MINT INVOICES #############
########################################
@@ -169,18 +153,22 @@ async def store_lightning_invoice(cashu_id: str, invoice: Invoice):
),
)
+
async def get_lightning_invoice(cashu_id: str, hash: str):
row = await db.fetchone(
"""
SELECT * from invoices
WHERE cashu_id =? AND hash = ?
""",
- (cashu_id, hash,),
+ (
+ cashu_id,
+ hash,
+ ),
)
return Invoice.from_row(row)
-async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
+async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
await db.execute(
"UPDATE invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
(
diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py
index 404f7ee8..a28dc97a 100644
--- a/lnbits/extensions/cashu/ledger.py
+++ b/lnbits/extensions/cashu/ledger.py
@@ -1,13 +1,15 @@
import hashlib
from typing import List, Set
-from .models import BlindedMessage, BlindedSignature, Invoice, Proof
-from secp256k1 import PublicKey, PrivateKey
-
from fastapi import Query
-from .crud import get_cashu
+from secp256k1 import PrivateKey, PublicKey
+
from lnbits.core.services import check_transaction_status, create_invoice
+from .crud import get_cashu
+from .models import BlindedMessage, BlindedSignature, Invoice, Proof
+
+
def _derive_keys(master_key: str, cashu_id: str = Query(None)):
"""Deterministic derivation of keys for 2^n values."""
return {
@@ -21,29 +23,34 @@ def _derive_keys(master_key: str, cashu_id: str = Query(None)):
for i in range(MAX_ORDER)
}
+
def _derive_pubkeys(keys: List[PrivateKey], cashu_id: str = Query(None)):
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
-async def _generate_promises(amounts: List[int], B_s: List[str], cashu_id: str = Query(None)):
+
+async def _generate_promises(
+ amounts: List[int], B_s: List[str], cashu_id: str = Query(None)
+):
"""Generates promises that sum to the given amount."""
return [
await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True))
for (amount, B_) in zip(amounts, B_s)
]
+
async def _generate_promise(amount: int, B_: PublicKey, cashu_id: str = Query(None)):
"""Generates a promise for given amount and returns a pair (amount, C')."""
secret_key = self.keys[amount] # Get the correct key
C_ = step2_bob(B_, secret_key)
- await store_promise(
- amount, B_=B_.serialize().hex(), C_=C_.serialize().hex()
- )
+ await store_promise(amount, B_=B_.serialize().hex(), C_=C_.serialize().hex())
return BlindedSignature(amount=amount, C_=C_.serialize().hex())
+
def _check_spendable(proof: Proof, cashu_id: str = Query(None)):
"""Checks whether the proof was already spent."""
return not proof.secret in self.proofs_used
+
def _verify_proof(proof: Proof, cashu_id: str = Query(None)):
"""Verifies that the proof of promise was issued by this ledger."""
if not self._check_spendable(proof):
@@ -52,7 +59,13 @@ def _verify_proof(proof: Proof, cashu_id: str = Query(None)):
C = PublicKey(bytes.fromhex(proof.C), raw=True)
return verify(secret_key, C, proof.secret)
-def _verify_outputs(total: int, amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)):
+
+def _verify_outputs(
+ total: int,
+ amount: int,
+ output_data: List[BlindedMessage],
+ cashu_id: str = Query(None),
+):
"""Verifies the expected split was correctly computed"""
fst_amt, snd_amt = total - amount, amount # we have two amounts to split to
fst_outputs = amount_split(fst_amt)
@@ -61,7 +74,10 @@ def _verify_outputs(total: int, amount: int, output_data: List[BlindedMessage],
given = [o.amount for o in output_data]
return given == expected
-def _verify_no_duplicates(proofs: List[Proof], output_data: List[BlindedMessage], cashu_id: str = Query(None)):
+
+def _verify_no_duplicates(
+ proofs: List[Proof], output_data: List[BlindedMessage], cashu_id: str = Query(None)
+):
secrets = [p.secret for p in proofs]
if len(secrets) != len(list(set(secrets))):
return False
@@ -70,6 +86,7 @@ def _verify_no_duplicates(proofs: List[Proof], output_data: List[BlindedMessage]
return False
return True
+
def _verify_split_amount(amount: int, cashu_id: str = Query(None)):
"""Split amount like output amount can't be negative or too big."""
try:
@@ -78,6 +95,7 @@ def _verify_split_amount(amount: int, cashu_id: str = Query(None)):
# For better error message
raise Exception("invalid split amount: " + str(amount))
+
def _verify_amount(amount: int, cashu_id: str = Query(None)):
"""Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
@@ -85,12 +103,16 @@ def _verify_amount(amount: int, cashu_id: str = Query(None)):
raise Exception("invalid amount: " + str(amount))
return amount
-def _verify_equation_balanced(proofs: List[Proof], outs: List[BlindedMessage], cashu_id: str = Query(None)):
+
+def _verify_equation_balanced(
+ proofs: List[Proof], outs: List[BlindedMessage], cashu_id: str = Query(None)
+):
"""Verify that Σoutputs - Σinputs = 0."""
sum_inputs = sum(self._verify_amount(p.amount) for p in proofs)
sum_outputs = sum(self._verify_amount(p.amount) for p in outs)
assert sum_outputs - sum_inputs == 0
+
def _get_output_split(amount: int, cashu_id: str):
"""Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
self._verify_amount(amount)
@@ -101,6 +123,7 @@ def _get_output_split(amount: int, cashu_id: str):
rv.append(2**pos)
return rv
+
async def _invalidate_proofs(proofs: List[Proof], cashu_id: str = Query(None)):
"""Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
# Mark proofs as used and prepare new promises
@@ -110,10 +133,12 @@ async def _invalidate_proofs(proofs: List[Proof], cashu_id: str = Query(None)):
for p in proofs:
await invalidate_proof(p)
+
def get_pubkeys(cashu_id: str = Query(None)):
"""Returns public keys for possible amounts."""
return {a: p.serialize().hex() for a, p in self.pub_keys.items()}
+
async def request_mint(amount, cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
@@ -125,9 +150,7 @@ async def request_mint(amount, cashu_id: str = Query(None)):
amount=amount,
memo=cashu.name,
unhashed_description=cashu.name.encode("utf-8"),
- extra={
- "tag": "Cashu"
- },
+ extra={"tag": "Cashu"},
)
invoice = Invoice(
@@ -137,15 +160,23 @@ async def request_mint(amount, cashu_id: str = Query(None)):
raise Exception(f"Could not create Lightning invoice.")
return payment_request, payment_hash
-async def mint(B_s: List[PublicKey], amounts: List[int], payment_hash: str = Query(None), cashu_id: str = Query(None)):
+
+async def mint(
+ B_s: List[PublicKey],
+ amounts: List[int],
+ payment_hash: str = Query(None),
+ cashu_id: str = Query(None),
+):
cashu = await get_cashu(cashu_id)
if not cashu:
raise Exception(f"Could not find Cashu")
"""Mints a promise for coins for B_."""
# check if lightning invoice was paid
- if payment_hash:
- if not await check_transaction_status(wallet_id=cashu.wallet, payment_hash=payment_hash):
+ if payment_hash:
+ if not await check_transaction_status(
+ wallet_id=cashu.wallet, payment_hash=payment_hash
+ ):
raise Exception("Lightning invoice not paid yet.")
for amount in amounts:
@@ -157,11 +188,14 @@ async def mint(B_s: List[PublicKey], amounts: List[int], payment_hash: str = Que
]
return promises
-async def melt(proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Query(None)):
+
+async def melt(
+ proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Query(None)
+):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
-
+ raise Exception(f"Could not find Cashu")
+
"""Invalidates proofs and pays a Lightning invoice."""
# if not LIGHTNING:
total = sum([p["amount"] for p in proofs])
@@ -181,6 +215,7 @@ async def melt(proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Q
await self._invalidate_proofs(proofs)
return status, payment_hash
+
async def check_spendable(proofs: List[Proof], cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
@@ -189,7 +224,13 @@ async def check_spendable(proofs: List[Proof], cashu_id: str = Query(None)):
"""Checks if all provided proofs are valid and still spendable (i.e. have not been spent)."""
return {i: self._check_spendable(p) for i, p in enumerate(proofs)}
-async def split(proofs: List[Proof], amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)):
+
+async def split(
+ proofs: List[Proof],
+ amount: int,
+ output_data: List[BlindedMessage],
+ cashu_id: str = Query(None),
+):
cashu = await get_cashu(cashu_id)
if not cashu:
raise Exception(f"Could not find Cashu")
@@ -226,18 +267,19 @@ async def split(proofs: List[Proof], amount: int, output_data: List[BlindedMessa
async def fee_reserve(amount_msat: int, cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
-
+ raise Exception(f"Could not find Cashu")
+
"""Function for calculating the Lightning fee reserve"""
return max(
int(LIGHTNING_RESERVE_FEE_MIN), int(amount_msat * LIGHTNING_FEE_PERCENT / 100.0)
)
+
async def amount_split(amount, cashu_id: str):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
-
+ raise Exception(f"Could not find Cashu")
+
"""Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
bits_amt = bin(amount)[::-1][:-2]
rv = []
@@ -246,11 +288,12 @@ async def amount_split(amount, cashu_id: str):
rv.append(2**pos)
return rv
+
async def hash_to_point(secret_msg, cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
-
+ raise Exception(f"Could not find Cashu")
+
"""Generates x coordinate from the message hash and checks if the point lies on the curve.
If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
point = None
@@ -280,10 +323,11 @@ async def step1_alice(secret_msg, cashu_id: str = Query(None)):
B_ = Y + r.pubkey
return B_, r
+
async def step2_bob(B_, a, cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
+ raise Exception(f"Could not find Cashu")
C_ = B_.mult(a)
return C_
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index 3f1df660..cb6b24f9 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -75,4 +75,4 @@ async def m001_initial(db):
);
"""
- )
\ No newline at end of file
+ )
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index fca096ed..c2568313 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -1,16 +1,16 @@
-
-from .models import Cashu
from .mint_helper import derive_keys, derive_pubkeys
+from .models import Cashu
def get_pubkeys(xpriv: str):
"""Returns public keys for possible amounts."""
-
+
keys = derive_keys(xpriv)
pub_keys = derive_pubkeys(keys)
return {a: p.serialize().hex() for a, p in pub_keys.items()}
+
# async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None):
# """Mints a promise for coins for B_."""
# # check if lightning invoice was paid
@@ -29,4 +29,4 @@ def get_pubkeys(xpriv: str):
# promises = [
# await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
# ]
-# return promises
\ No newline at end of file
+# return promises
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
index 30e66b03..1cf631b4 100644
--- a/lnbits/extensions/cashu/mint_helper.py
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -1,10 +1,12 @@
import hashlib
from typing import List, Set
+
from .core.secp import PrivateKey, PublicKey
# todo: extract const
MAX_ORDER = 64
+
def derive_keys(master_key: str):
"""Deterministic derivation of keys for 2^n values."""
return {
@@ -18,5 +20,6 @@ def derive_keys(master_key: str):
for i in range(MAX_ORDER)
}
+
def derive_pubkeys(keys: List[PrivateKey]):
- return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
\ No newline at end of file
+ return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index 094966ff..570387a2 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -1,5 +1,5 @@
from sqlite3 import Row
-from typing import Optional, List
+from typing import List, Optional
from fastapi import Query
from pydantic import BaseModel
@@ -20,20 +20,22 @@ class Cashu(BaseModel):
def from_row(cls, row: Row) -> "TPoS":
return cls(**dict(row))
+
class Pegs(BaseModel):
id: str
wallet: str
inout: str
amount: str
-
@classmethod
def from_row(cls, row: Row) -> "TPoS":
return cls(**dict(row))
+
class PayLnurlWData(BaseModel):
lnurl: str
+
class Promises(BaseModel):
id: str
amount: int
@@ -41,6 +43,7 @@ class Promises(BaseModel):
C_b: str
cashu_id: str
+
class Proof(BaseModel):
amount: int
secret: str
@@ -142,4 +145,4 @@ class CheckPayload(BaseModel):
class MeltPayload(BaseModel):
proofs: List[Proof]
amount: int
- invoice: str
\ No newline at end of file
+ invoice: str
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index 5fbdde8e..fe00a591 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -9,6 +9,7 @@ from lnbits.tasks import internal_invoice_queue, register_invoice_listener
from .crud import get_cashu
+
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue)
diff --git a/lnbits/extensions/cashu/templates/cashu/_api_docs.html b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
index 7378eb08..3476d41a 100644
--- a/lnbits/extensions/cashu/templates/cashu/_api_docs.html
+++ b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
@@ -71,7 +71,8 @@
Curl example
curl -X DELETE {{ request.base_url
- }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key: <admin_key>"
+ }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key:
+ <admin_key>"
diff --git a/lnbits/extensions/cashu/templates/cashu/_cashu.html b/lnbits/extensions/cashu/templates/cashu/_cashu.html
index 3c2a38f5..f5af738f 100644
--- a/lnbits/extensions/cashu/templates/cashu/_cashu.html
+++ b/lnbits/extensions/cashu/templates/cashu/_cashu.html
@@ -2,13 +2,12 @@
- Make Ecash mints with peg in/out to a wallet, that can create and manage ecash.
+ Make Ecash mints with peg in/out to a wallet, that can create and manage
+ ecash.
Created by
- Calle . Calle.
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index 3cd57d45..37dc360e 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -4,7 +4,9 @@
- New Mint
+ New Mint
@@ -18,8 +20,14 @@
Export to CSV
-
+
{% raw %}
@@ -34,23 +42,43 @@
- Shareable wallet page
+ target="_blank"
+ >Shareable wallet page
- Shareable mint page
-
+ target="_blank"
+ >Shareable mint page
{{ (col.name == 'tip_options' && col.value ?
JSON.parse(col.value).join(", ") : col.value) }}
-
+
@@ -79,34 +107,90 @@
-
-
-
+
+
+
-
- Use with hedging extension to create a stablecoin!
+
+ Use with hedging extension to create a stablecoin!
-
-
+
+
-
-
+
+
- Create Mint
+ Create Mint
- Cancel
+ Cancel
@@ -130,12 +214,12 @@
data: function () {
return {
cashus: [],
- hostname: location.protocol + "//" + location.host + "/cashu/mint/",
+ hostname: location.protocol + '//' + location.host + '/cashu/mint/',
toggleAdvanced: false,
cashusTable: {
columns: [
- { name: 'id', align: 'left', label: 'ID', field: 'id' },
- { name: 'name', align: 'left', label: 'Name', field: 'name' },
+ {name: 'id', align: 'left', label: 'ID', field: 'id'},
+ {name: 'name', align: 'left', label: 'Name', field: 'name'},
{
name: 'tickershort',
align: 'left',
@@ -165,7 +249,7 @@
align: 'left',
label: 'No. of coins',
field: 'coins'
- },
+ }
],
pagination: {
rowsPerPage: 10
@@ -173,7 +257,7 @@
},
formDialog: {
show: false,
- data: { fraction: false }
+ data: {fraction: false}
}
}
},
@@ -203,7 +287,7 @@
var data = {
name: this.formDialog.data.name,
tickershort: this.formDialog.data.tickershort,
- maxliquid: this.formDialog.data.maxliquid,
+ maxliquid: this.formDialog.data.maxliquid
}
var self = this
@@ -211,7 +295,7 @@
.request(
'POST',
'/cashu/api/v1/cashus',
- _.findWhere(this.g.user.wallets, { id: this.formDialog.data.wallet })
+ _.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet})
.inkey,
data
)
@@ -225,17 +309,19 @@
},
deleteMint: function (cashuId) {
var self = this
- var cashu = _.findWhere(this.cashus, { id: cashuId })
+ var cashu = _.findWhere(this.cashus, {id: cashuId})
console.log(cashu)
LNbits.utils
- .confirmDialog('Are you sure you want to delete this Mint? It will suck for users.')
+ .confirmDialog(
+ 'Are you sure you want to delete this Mint? It will suck for users.'
+ )
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/cashu/api/v1/cashus/' + cashuId,
- _.findWhere(self.g.user.wallets, { id: cashu.wallet }).adminkey
+ _.findWhere(self.g.user.wallets, {id: cashu.wallet}).adminkey
)
.then(function (response) {
self.cashus = _.reject(self.cashus, function (obj) {
@@ -258,4 +344,4 @@
}
})
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
index 0f3e0e09..3c0998b5 100644
--- a/lnbits/extensions/cashu/templates/cashu/mint.html
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -5,14 +5,17 @@
+ name="account_balance"
+ class="text-grey"
+ style="font-size: 10rem"
+ >
{{ mint_name }}
- Some data about mint here: * whether its online * Who to contact for support * etc...
+
+ Some data about mint here: * whether its online * Who to
+ contact for support * etc...
+
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index a5d5f371..75dff22e 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1,41 +1,69 @@
-{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Wallet {% endraw %}
-
-{% endblock %} {% block footer %}{% endblock %} {% block page_container %}
+{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Wallet
+{% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
+page_container %}
-
-
-
+
- {% raw %} {{balanceAmount}}
- {{tickershort}}{% endraw %}
+
+ {% raw %} {{balanceAmount}} {{tickershort}}{%
+ endraw %}
+
-
-
-
+
- Receive
+ Receive
- Send
+ Send
- Peg in/out
+ Peg in/out
- scan
+ scan
-
-
-
+
@@ -43,42 +71,99 @@
Transactions
- {% raw %}
-
{% endraw %}
+
+ {% raw %}
+ {% endraw %}
Mint details
- Export to CSV
+ Export to CSV
-
+
Show chart
-
+
-
+
{% raw %}
- {{ col.label }}
+ {{ col.label }}
-
-
+
+
Pending
-
-
-
+
+
+
#{{ props.row.tag }}
@@ -89,7 +174,12 @@
{{ props.row.dateFrom }}
{% endraw %}
- {% raw %} {{
+ {% raw %} {{
parseFloat(String(props.row.fsat).replaceAll(",", "")) / 100
}}
@@ -108,34 +198,68 @@
Invoice waiting to be paid
-
-
+
+
- Copy invoice
- Close
+ Copy invoice
+ Close
-
+
Payment Received
-
+
-
+
Payment Sent
-
+
Outgoing payment pending
-
+
@@ -149,49 +273,101 @@
{% raw %}
-
+
{{receive.lnurl.domain}} is requesting an invoice:
{% endraw %} {% if LNBITS_DENOMINATION != 'sats' %}
-
+
{% else %}
-
-
+
+
{% endif %}
-
+
{% raw %}
-
+
Withdraw from {{receive.lnurl.domain}}
Create invoice
- Cancel
+ Cancel
-
+
- Copy invoice
- Close
+ Copy invoice
+ Close
{% endraw %}
@@ -217,11 +393,17 @@
{% endraw %}
Pay
- Cancel
+ Cancel
- Not enough funds!
- Cancel
+ Not enough funds!
+ Cancel
@@ -243,23 +425,46 @@
Login
- Cancel
+ Cancel
{% endraw %}
-
-
+
+
- Read
- Cancel
+ Read
+ Cancel
-
+
@@ -274,10 +479,15 @@
-
+
- Cancel
+ Cancel
@@ -289,12 +499,19 @@
-
+
-
+
@@ -302,8 +519,10 @@
Warning
- BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots
- and "Save to homescreen"/"Install app" !
+ BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots
+ and "Save to homescreen"/"Install app" !
Ecash is a bearer asset, meaning you have the funds saved on this
@@ -311,8 +530,15 @@
lose the funds.
- Copy wallet URL
- I understand
+ Copy wallet URL
+ I understand
@@ -357,397 +583,391 @@
mixins: [windowMixin],
data: function () {
return {
- balanceAmount:"",
- tickershort:"",
- name:"",
- receive: {
- show: false,
- status: 'pending',
- paymentReq: null,
- paymentHash: null,
- minMax: [0, 2100000000000000],
- lnurl: null,
- units: ['sat'],
- unit: 'sat',
- data: {
- amount: null,
- memo: ''
- }
- },
- parse: {
- show: false,
- invoice: null,
- lnurlpay: null,
- lnurlauth: null,
- data: {
- request: '',
- amount: 0,
- comment: ''
- },
- paymentChecker: null,
- camera: {
+ balanceAmount: '',
+ tickershort: '',
+ name: '',
+ receive: {
show: false,
- camera: 'auto'
- }
- },
- payments: [],
- paymentsTable: {
- columns: [
- {
- name: 'note',
- align: 'left',
- label: 'Note',
- field: 'note'
- },
- {
- name: 'date',
- align: 'left',
- label: 'Date',
- field: 'date',
- sortable: true
- },
- {
- name: 'amount',
- align: 'right',
- label: 'Amount',
- field: 'amount',
- sortable: true
+ status: 'pending',
+ paymentReq: null,
+ paymentHash: null,
+ minMax: [0, 2100000000000000],
+ lnurl: null,
+ units: ['sat'],
+ unit: 'sat',
+ data: {
+ amount: null,
+ memo: ''
}
- ],
- pagination: {
- rowsPerPage: 10
},
- filter: null
- },
- paymentsChart: {
- show: false
- },
- disclaimerDialog: {
- show: false,
- location: window.location
- },
- balance: 0,
- credit: 0,
- newName: ''
- }
+ parse: {
+ show: false,
+ invoice: null,
+ lnurlpay: null,
+ lnurlauth: null,
+ data: {
+ request: '',
+ amount: 0,
+ comment: ''
+ },
+ paymentChecker: null,
+ camera: {
+ show: false,
+ camera: 'auto'
+ }
+ },
+ payments: [],
+ paymentsTable: {
+ columns: [
+ {
+ name: 'note',
+ align: 'left',
+ label: 'Note',
+ field: 'note'
+ },
+ {
+ name: 'date',
+ align: 'left',
+ label: 'Date',
+ field: 'date',
+ sortable: true
+ },
+ {
+ name: 'amount',
+ align: 'right',
+ label: 'Amount',
+ field: 'amount',
+ sortable: true
+ }
+ ],
+ pagination: {
+ rowsPerPage: 10
+ },
+ filter: null
+ },
+ paymentsChart: {
+ show: false
+ },
+ disclaimerDialog: {
+ show: false,
+ location: window.location
+ },
+ balance: 0,
+ credit: 0,
+ newName: ''
+ }
},
computed: {
- formattedBalance: function () {
- return this.balance / 100
- },
- filteredPayments: function () {
- var q = this.paymentsTable.filter
- if (!q || q === '') return this.payments
+ formattedBalance: function () {
+ return this.balance / 100
+ },
+ filteredPayments: function () {
+ var q = this.paymentsTable.filter
+ if (!q || q === '') return this.payments
- return LNbits.utils.search(this.payments, q)
+ return LNbits.utils.search(this.payments, q)
+ },
+ canPay: function () {
+ if (!this.parse.invoice) return false
+ return this.parse.invoice.sat <= this.balance
+ },
+ pendingPaymentsExist: function () {
+ return this.payments.findIndex(payment => payment.pending) !== -1
+ }
},
- canPay: function () {
- if (!this.parse.invoice) return false
- return this.parse.invoice.sat <= this.balance
+ filters: {
+ msatoshiFormat: function (value) {
+ return LNbits.utils.formatSat(value / 1000)
+ }
},
- pendingPaymentsExist: function () {
- return this.payments.findIndex(payment => payment.pending) !== -1
- }
- },
- filters: {
- msatoshiFormat: function (value) {
- return LNbits.utils.formatSat(value / 1000)
- }
- },
- methods: {
- paymentTableRowKey: function (row) {
- return row.payment_hash + row.amount
- },
- closeCamera: function () {
- this.parse.camera.show = false
- },
- showCamera: function () {
- this.parse.camera.show = true
- },
- showChart: function () {
- this.paymentsChart.show = true
- this.$nextTick(() => {
- generateChart(this.$refs.canvas, this.payments)
- })
- },
- focusInput(el) {
- this.$nextTick(() => this.$refs[el].focus())
- },
- showReceiveDialog: function () {
- this.receive.show = true
- this.receive.status = 'pending'
- this.receive.paymentReq = null
- this.receive.paymentHash = null
- this.receive.data.amount = null
- this.receive.data.memo = null
- this.receive.unit = 'sat'
- this.receive.paymentChecker = null
- this.receive.minMax = [0, 2100000000000000]
- this.receive.lnurl = null
- this.focusInput('setAmount')
- },
- showParseDialog: function () {
- this.parse.show = true
- this.parse.invoice = null
- this.parse.lnurlpay = null
- this.parse.lnurlauth = null
- this.parse.data.request = ''
- this.parse.data.comment = ''
- this.parse.data.paymentChecker = null
- this.parse.camera.show = false
- },
- updateBalance: function (credit) {
- this.balance = this.balance // update balance
- },
- closeReceiveDialog: function () {
- setTimeout(() => {
- clearInterval(this.receive.paymentChecker)
- }, 10000)
- },
- closeParseDialog: function () {
- setTimeout(() => {
- clearInterval(this.parse.paymentChecker)
- }, 10000)
- },
- onPaymentReceived: function (paymentHash) {
- this.fetchPayments()
- this.fetchBalance()
-
- if (this.receive.paymentHash === paymentHash) {
- this.receive.show = false
+ methods: {
+ paymentTableRowKey: function (row) {
+ return row.payment_hash + row.amount
+ },
+ closeCamera: function () {
+ this.parse.camera.show = false
+ },
+ showCamera: function () {
+ this.parse.camera.show = true
+ },
+ showChart: function () {
+ this.paymentsChart.show = true
+ this.$nextTick(() => {
+ generateChart(this.$refs.canvas, this.payments)
+ })
+ },
+ focusInput(el) {
+ this.$nextTick(() => this.$refs[el].focus())
+ },
+ showReceiveDialog: function () {
+ this.receive.show = true
+ this.receive.status = 'pending'
+ this.receive.paymentReq = null
this.receive.paymentHash = null
- clearInterval(this.receive.paymentChecker)
- }
- },
- createInvoice: function () {
- this.receive.status = 'loading'
- if (LNBITS_DENOMINATION != 'sats') {
- this.receive.data.amount = this.receive.data.amount * 100
- }
- LNbits.api
- .createInvoice(
- this.receive.data.amount,
- this.receive.data.memo,
- this.receive.unit,
- this.receive.lnurl && this.receive.lnurl.callback
- )
- .then(response => {
- this.receive.status = 'success'
- this.receive.paymentReq = response.data.payment_request
- this.receive.paymentHash = response.data.payment_hash
+ this.receive.data.amount = null
+ this.receive.data.memo = null
+ this.receive.unit = 'sat'
+ this.receive.paymentChecker = null
+ this.receive.minMax = [0, 2100000000000000]
+ this.receive.lnurl = null
+ this.focusInput('setAmount')
+ },
+ showParseDialog: function () {
+ this.parse.show = true
+ this.parse.invoice = null
+ this.parse.lnurlpay = null
+ this.parse.lnurlauth = null
+ this.parse.data.request = ''
+ this.parse.data.comment = ''
+ this.parse.data.paymentChecker = null
+ this.parse.camera.show = false
+ },
+ updateBalance: function (credit) {
+ this.balance = this.balance // update balance
+ },
+ closeReceiveDialog: function () {
+ setTimeout(() => {
+ clearInterval(this.receive.paymentChecker)
+ }, 10000)
+ },
+ closeParseDialog: function () {
+ setTimeout(() => {
+ clearInterval(this.parse.paymentChecker)
+ }, 10000)
+ },
+ onPaymentReceived: function (paymentHash) {
+ this.fetchPayments()
+ this.fetchBalance()
- if (response.data.lnurl_response !== null) {
- if (response.data.lnurl_response === false) {
- response.data.lnurl_response = `Unable to connect`
+ if (this.receive.paymentHash === paymentHash) {
+ this.receive.show = false
+ this.receive.paymentHash = null
+ clearInterval(this.receive.paymentChecker)
+ }
+ },
+ createInvoice: function () {
+ this.receive.status = 'loading'
+ if (LNBITS_DENOMINATION != 'sats') {
+ this.receive.data.amount = this.receive.data.amount * 100
+ }
+ LNbits.api
+ .createInvoice(
+ this.receive.data.amount,
+ this.receive.data.memo,
+ this.receive.unit,
+ this.receive.lnurl && this.receive.lnurl.callback
+ )
+ .then(response => {
+ this.receive.status = 'success'
+ this.receive.paymentReq = response.data.payment_request
+ this.receive.paymentHash = response.data.payment_hash
+
+ if (response.data.lnurl_response !== null) {
+ if (response.data.lnurl_response === false) {
+ response.data.lnurl_response = `Unable to connect`
+ }
+
+ if (typeof response.data.lnurl_response === 'string') {
+ // failure
+ this.$q.notify({
+ timeout: 5000,
+ type: 'warning',
+ message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
+ caption: response.data.lnurl_response
+ })
+ return
+ } else if (response.data.lnurl_response === true) {
+ // success
+ this.$q.notify({
+ timeout: 5000,
+ message: `Invoice sent to ${this.receive.lnurl.domain}!`,
+ spinner: true
+ })
+ }
}
- if (typeof response.data.lnurl_response === 'string') {
- // failure
- this.$q.notify({
- timeout: 5000,
- type: 'warning',
- message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
- caption: response.data.lnurl_response
- })
- return
- } else if (response.data.lnurl_response === true) {
- // success
- this.$q.notify({
- timeout: 5000,
- message: `Invoice sent to ${this.receive.lnurl.domain}!`,
- spinner: true
- })
+ clearInterval(this.receive.paymentChecker)
+ setTimeout(() => {
+ clearInterval(this.receive.paymentChecker)
+ }, 40000)
+ })
+ .catch(err => {
+ LNbits.utils.notifyApiError(err)
+ this.receive.status = 'pending'
+ })
+ },
+ decodeQR: function (res) {
+ this.parse.data.request = res
+ this.decodeRequest()
+ this.parse.camera.show = false
+ },
+ decodeRequest: function () {
+ this.parse.show = true
+ let req = this.parse.data.request.toLowerCase()
+ if (this.parse.data.request.toLowerCase().startsWith('lightning:')) {
+ this.parse.data.request = this.parse.data.request.slice(10)
+ } else if (this.parse.data.request.toLowerCase().startsWith('lnurl:')) {
+ this.parse.data.request = this.parse.data.request.slice(6)
+ } else if (req.indexOf('lightning=lnurl1') !== -1) {
+ this.parse.data.request = this.parse.data.request
+ .split('lightning=')[1]
+ .split('&')[0]
+ }
+
+ if (
+ this.parse.data.request.toLowerCase().startsWith('lnurl1') ||
+ this.parse.data.request.match(/[\w.+-~_]+@[\w.+-~_]/)
+ ) {
+ return
+ }
+
+ let invoice
+ try {
+ invoice = decode(this.parse.data.request)
+ } catch (error) {
+ this.$q.notify({
+ timeout: 3000,
+ type: 'warning',
+ message: error + '.',
+ caption: '400 BAD REQUEST'
+ })
+ this.parse.show = false
+ return
+ }
+
+ let cleanInvoice = {
+ msat: invoice.human_readable_part.amount,
+ sat: invoice.human_readable_part.amount / 1000,
+ fsat: LNbits.utils.formatSat(
+ invoice.human_readable_part.amount / 1000
+ )
+ }
+
+ _.each(invoice.data.tags, tag => {
+ if (_.isObject(tag) && _.has(tag, 'description')) {
+ if (tag.description === 'payment_hash') {
+ cleanInvoice.hash = tag.value
+ } else if (tag.description === 'description') {
+ cleanInvoice.description = tag.value
+ } else if (tag.description === 'expiry') {
+ var expireDate = new Date(
+ (invoice.data.time_stamp + tag.value) * 1000
+ )
+ cleanInvoice.expireDate = Quasar.utils.date.formatDate(
+ expireDate,
+ 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ )
+ cleanInvoice.expired = false // TODO
}
}
+ })
- clearInterval(this.receive.paymentChecker)
- setTimeout(() => {
- clearInterval(this.receive.paymentChecker)
- }, 40000)
-
+ this.parse.invoice = Object.freeze(cleanInvoice)
+ },
+ payInvoice: function () {
+ let dismissPaymentMsg = this.$q.notify({
+ timeout: 0,
+ message: 'Processing payment...'
+ })
+ },
+ payLnurl: function () {
+ let dismissPaymentMsg = this.$q.notify({
+ timeout: 0,
+ message: 'Processing payment...'
+ })
+ },
+ authLnurl: function () {
+ let dismissAuthMsg = this.$q.notify({
+ timeout: 10,
+ message: 'Performing authentication...'
+ })
+ },
+
+ deleteWallet: function (walletId, user) {
+ LNbits.utils
+ .confirmDialog('Are you sure you want to delete this wallet?')
+ .onOk(() => {
+ LNbits.href.deleteWallet(walletId, user)
+ })
+ },
+ fetchPayments: function () {
+ return
+ },
+ fetchBalance: function () {},
+ exportCSV: function () {
+ // status is important for export but it is not in paymentsTable
+ // because it is manually added with payment detail link and icons
+ // and would cause duplication in the list
+ let columns = this.paymentsTable.columns
+ columns.unshift({
+ name: 'pending',
+ align: 'left',
+ label: 'Pending',
+ field: 'pending'
+ })
+ LNbits.utils.exportCSV(columns, this.payments)
+ }
+ },
+ watch: {
+ payments: function () {
+ this.fetchBalance()
+ }
+ },
+ created: function () {
+ this.fetchBalance()
+ this.fetchPayments()
+
+ LNbits.api
+ .request('GET', '/api/v1/currencies')
+ .then(response => {
+ this.receive.units = ['sat', ...response.data]
})
.catch(err => {
LNbits.utils.notifyApiError(err)
- this.receive.status = 'pending'
})
},
- decodeQR: function (res) {
- this.parse.data.request = res
- this.decodeRequest()
- this.parse.camera.show = false
- },
- decodeRequest: function () {
- this.parse.show = true
- let req = this.parse.data.request.toLowerCase()
- if (this.parse.data.request.toLowerCase().startsWith('lightning:')) {
- this.parse.data.request = this.parse.data.request.slice(10)
- } else if (this.parse.data.request.toLowerCase().startsWith('lnurl:')) {
- this.parse.data.request = this.parse.data.request.slice(6)
- } else if (req.indexOf('lightning=lnurl1') !== -1) {
- this.parse.data.request = this.parse.data.request
- .split('lightning=')[1]
- .split('&')[0]
- }
+ created: function () {
+ let params = new URL(document.location).searchParams
+ // get ticker
if (
- this.parse.data.request.toLowerCase().startsWith('lnurl1') ||
- this.parse.data.request.match(/[\w.+-~_]+@[\w.+-~_]/)
+ !params.get('tsh') &&
+ !this.$q.localStorage.getItem('cashu.tickershort')
) {
- return
- }
-
- let invoice
- try {
- invoice = decode(this.parse.data.request)
- } catch (error) {
- this.$q.notify({
- timeout: 3000,
- type: 'warning',
- message: error + '.',
- caption: '400 BAD REQUEST'
- })
- this.parse.show = false
- return
- }
-
- let cleanInvoice = {
- msat: invoice.human_readable_part.amount,
- sat: invoice.human_readable_part.amount / 1000,
- fsat: LNbits.utils.formatSat(invoice.human_readable_part.amount / 1000)
- }
-
- _.each(invoice.data.tags, tag => {
- if (_.isObject(tag) && _.has(tag, 'description')) {
- if (tag.description === 'payment_hash') {
- cleanInvoice.hash = tag.value
- } else if (tag.description === 'description') {
- cleanInvoice.description = tag.value
- } else if (tag.description === 'expiry') {
- var expireDate = new Date(
- (invoice.data.time_stamp + tag.value) * 1000
- )
- cleanInvoice.expireDate = Quasar.utils.date.formatDate(
- expireDate,
- 'YYYY-MM-DDTHH:mm:ss.SSSZ'
- )
- cleanInvoice.expired = false // TODO
- }
- }
- })
-
- this.parse.invoice = Object.freeze(cleanInvoice)
- },
- payInvoice: function () {
- let dismissPaymentMsg = this.$q.notify({
- timeout: 0,
- message: 'Processing payment...'
- })
- },
- payLnurl: function () {
- let dismissPaymentMsg = this.$q.notify({
- timeout: 0,
- message: 'Processing payment...'
- })
- },
- authLnurl: function () {
- let dismissAuthMsg = this.$q.notify({
- timeout: 10,
- message: 'Performing authentication...'
- })
- },
-
- deleteWallet: function (walletId, user) {
- LNbits.utils
- .confirmDialog('Are you sure you want to delete this wallet?')
- .onOk(() => {
- LNbits.href.deleteWallet(walletId, user)
- })
- },
- fetchPayments: function () {
- return
- },
- fetchBalance: function () {
-
- },
- exportCSV: function () {
- // status is important for export but it is not in paymentsTable
- // because it is manually added with payment detail link and icons
- // and would cause duplication in the list
- let columns = this.paymentsTable.columns
- columns.unshift({
- name: 'pending',
- align: 'left',
- label: 'Pending',
- field: 'pending'
- })
- LNbits.utils.exportCSV(columns, this.payments)
- }
- },
- watch: {
- payments: function () {
- this.fetchBalance()
- }
- },
- created: function () {
- this.fetchBalance()
- this.fetchPayments()
-
- LNbits.api
- .request('GET', '/api/v1/currencies')
- .then(response => {
- this.receive.units = ['sat', ...response.data]
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- },
- created: function () {
-
- let params = (new URL(document.location)).searchParams
-
- // get ticker
- if(!params.get('tsh') && !this.$q.localStorage.getItem('cashu.tickershort')){
- this.$q.localStorage.set('cashu.tickershort', "CE")
- this.tickershort = "CE"
- }
- else if(params.get('tsh')){
+ this.$q.localStorage.set('cashu.tickershort', 'CE')
+ this.tickershort = 'CE'
+ } else if (params.get('tsh')) {
this.$q.localStorage.set('cashu.tickershort', params.get('tsh'))
this.tickershort = params.get('tsh')
- }
- else if(this.$q.localStorage.getItem('cashu.tickershort')){
+ } else if (this.$q.localStorage.getItem('cashu.tickershort')) {
this.tickershort = this.$q.localStorage.getItem('cashu.tickershort')
- }
+ }
-
- if (!this.$q.localStorage.getItem('cashu.amount')) {
- this.balanceAmount = 0
- }
+ if (!this.$q.localStorage.getItem('cashu.amount')) {
+ this.balanceAmount = 0
+ }
- // get mint
- if (params.get('mnt')) {
- this.mint = params.get('mnt')
- this.$q.localStorage.set('cashu.mint', params.get('mnt'))
- }
- else if(this.$q.localStorage.getItem('cashu.mint')){
- this.mint = this.$q.localStorage.getItem('cashu.mint')
- }
- else{
- this.$q.notify({
- color: 'red',
- message: 'No mint set!'
- })
- }
+ // get mint
+ if (params.get('mnt')) {
+ this.mint = params.get('mnt')
+ this.$q.localStorage.set('cashu.mint', params.get('mnt'))
+ } else if (this.$q.localStorage.getItem('cashu.mint')) {
+ this.mint = this.$q.localStorage.getItem('cashu.mint')
+ } else {
+ this.$q.notify({
+ color: 'red',
+ message: 'No mint set!'
+ })
+ }
- // get name
- if (params.get('nme')) {
- this.name = params.get('nme')
- this.$q.localStorage.set('cashu.name', params.get('nme'))
+ // get name
+ if (params.get('nme')) {
+ this.name = params.get('nme')
+ this.$q.localStorage.set('cashu.name', params.get('nme'))
+ } else if (this.$q.localStorage.getItem('cashu.name')) {
+ this.name = this.$q.localStorage.getItem('cashu.name')
+ }
}
- else if(this.$q.localStorage.getItem('cashu.name')){
- this.name = this.$q.localStorage.getItem('cashu.name')
- }
-
- }
-})
+ })
{% endblock %}
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 655ed028..097fbd00 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -22,14 +22,19 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
"cashu/index.html", {"request": request, "user": user.dict()}
)
+
@cashu_ext.get("/wallet")
async def cashu(request: Request):
- return cashu_renderer().TemplateResponse("cashu/wallet.html",{"request": request})
+ return cashu_renderer().TemplateResponse("cashu/wallet.html", {"request": request})
+
@cashu_ext.get("/mint/{mintID}")
async def cashu(request: Request, mintID):
cashu = await get_cashu(mintID)
- return cashu_renderer().TemplateResponse("cashu/mint.html",{"request": request, "mint_name": cashu.name})
+ return cashu_renderer().TemplateResponse(
+ "cashu/mint.html", {"request": request, "mint_name": cashu.name}
+ )
+
@cashu_ext.get("/manifest/{cashu_id}.webmanifest")
async def manifest(cashu_id: str):
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index c0bc59f3..ab96e101 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -1,5 +1,4 @@
from http import HTTPStatus
-from secp256k1 import PublicKey
from typing import Union
import httpx
@@ -7,35 +6,36 @@ from fastapi import Query
from fastapi.params import Depends
from lnurl import decode as decode_lnurl
from loguru import logger
+from secp256k1 import PublicKey
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
-from lnbits.core.services import create_invoice
+from lnbits.core.services import check_transaction_status, create_invoice
from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from .core.base import CashuError
+from lnbits.wallets.base import PaymentStatus
from . import cashu_ext
-from .ledger import request_mint, mint
-from .mint import get_pubkeys
-
+from .core.base import CashuError
from .crud import (
- create_cashu,
- delete_cashu,
- get_cashu,
+ create_cashu,
+ delete_cashu,
+ get_cashu,
get_cashus,
+ get_lightning_invoice,
store_lightning_invoice,
)
-
+from .ledger import mint, request_mint
+from .mint import get_pubkeys
from .models import (
Cashu,
- Invoice,
- Pegs,
- CheckPayload,
- MeltPayload,
- MintPayloads,
- SplitPayload,
- PayLnurlWData
+ CheckPayload,
+ Invoice,
+ MeltPayload,
+ MintPayloads,
+ PayLnurlWData,
+ Pegs,
+ SplitPayload,
)
########################################
@@ -55,13 +55,12 @@ async def api_cashus(
@cashu_ext.post("/api/v1/cashus", status_code=HTTPStatus.CREATED)
-async def api_cashu_create(
- data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
-):
+async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)):
cashu = await create_cashu(wallet_id=wallet.wallet.id, data=data)
logger.debug(cashu)
return cashu.dict()
+
@cashu_ext.post("/api/v1/cashus/upodatekeys", status_code=HTTPStatus.CREATED)
async def api_cashu_update_keys(
data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
@@ -122,7 +121,8 @@ async def api_cashu_create_invoice(
@cashu_ext.post(
- "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay", status_code=HTTPStatus.OK
+ "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay",
+ status_code=HTTPStatus.OK,
)
async def api_cashu_pay_invoice(
lnurl_data: PayLnurlWData, payment_request: str = None, cashu_id: str = None
@@ -203,26 +203,29 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
#################MINT###################
########################################
-@cashu_ext.get("/api/v1/mint/keys/{cashu_id}", status_code=HTTPStatus.OK)
-async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)):
+
+@cashu_ext.get("/api/v1/mint/keys/{cashu_id}", status_code=HTTPStatus.OK)
+async def keys(
+ cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
+):
"""Get the public keys of the mint"""
mint = await get_cashu(cashu_id)
if mint is None:
raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
- )
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
return get_pubkeys(mint.prvkey)
@cashu_ext.get("/api/v1/mint/{cashu_id}")
async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
"""Request minting of tokens. Server responds with a Lightning invoice."""
- print('############################ amount', amount)
+
cashu = await get_cashu(cashu_id)
if cashu is None:
raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
- )
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
try:
payment_hash, payment_request = await create_invoice(
@@ -237,20 +240,43 @@ async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
await store_lightning_invoice(cashu_id, invoice)
except Exception as e:
logger.error(e)
- raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(cashu_id))
+ raise HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(cashu_id)
+ )
return {"pr": payment_request, "hash": payment_hash}
@cashu_ext.post("/mint")
-async def mint_coins(payloads: MintPayloads, payment_hash: Union[str, None] = None, cashu_id: str = Query(None)):
+async def mint_coins(
+ payloads: MintPayloads,
+ payment_hash: Union[str, None] = None,
+ cashu_id: str = Query(None),
+):
"""
Requests the minting of tokens belonging to a paid payment request.
-
Call this endpoint after `GET /mint`.
"""
- amounts = []
- B_s = []
+ print("############################ amount")
+ cashu: Cashu = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
+ invoice: Invoice = get_lightning_invoice(cashu_id, payment_hash)
+ if invoice is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not have this invoice."
+ )
+
+ status: PaymentStatus = check_transaction_status(cashu.wallet, payment_hash)
+ if status.paid == False:
+ raise HTTPException(
+ status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
+ )
+
+ # amounts = []
+ # B_s = []
# for payload in payloads.blinded_messages:
# amounts.append(payload.amount)
# B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
@@ -290,4 +316,4 @@ async def spli_coinst(payload: SplitPayload, cashu_id: str = Query(None)):
"""There was a problem with the split"""
raise Exception("could not split tokens.")
fst_promises, snd_promises = split_return
- return {"fst": fst_promises, "snd": snd_promises}
\ No newline at end of file
+ return {"fst": fst_promises, "snd": snd_promises}
From 163f0b60140f997e1afa464b2a49c348b0ef8225 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 11:18:17 +0300
Subject: [PATCH 260/696] feat: add wallet check
---
lnbits/extensions/cashu/views_api.py | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index ab96e101..36a9d105 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -218,7 +218,11 @@ async def keys(
@cashu_ext.get("/api/v1/mint/{cashu_id}")
-async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
+async def mint_pay_request(
+ amount: int = 0,
+ cashu_id: str = Query(None),
+ wallet: WalletTypeInfo = Depends(get_key_type),
+):
"""Request minting of tokens. Server responds with a Lightning invoice."""
cashu = await get_cashu(cashu_id)
@@ -252,6 +256,7 @@ async def mint_coins(
payloads: MintPayloads,
payment_hash: Union[str, None] = None,
cashu_id: str = Query(None),
+ wallet: WalletTypeInfo = Depends(get_key_type),
):
"""
Requests the minting of tokens belonging to a paid payment request.
From 337ccf54596c93466f5fc9c27a5493b2c208c230 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 11:47:37 +0300
Subject: [PATCH 261/696] feat: check paid invoices
---
lnbits/extensions/cashu/core/base.py | 9 +++++----
lnbits/extensions/cashu/crud.py | 4 ++--
lnbits/extensions/cashu/models.py | 7 ++++++-
lnbits/extensions/cashu/views_api.py | 20 +++++++++++++-------
4 files changed, 26 insertions(+), 14 deletions(-)
diff --git a/lnbits/extensions/cashu/core/base.py b/lnbits/extensions/cashu/core/base.py
index 0aa02442..4943ee24 100644
--- a/lnbits/extensions/cashu/core/base.py
+++ b/lnbits/extensions/cashu/core/base.py
@@ -87,10 +87,11 @@ class Invoice(BaseModel):
@classmethod
def from_row(cls, row: Row):
return cls(
- amount=int(row[0]),
- pr=str(row[1]),
- hash=str(row[2]),
- issued=bool(row[3]),
+ cashu_id=str(row[0]),
+ amount=int(row[1]),
+ pr=str(row[2]),
+ hash=str(row[3]),
+ issued=bool(row[4]),
)
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 448614ac..733f4737 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -157,7 +157,7 @@ async def store_lightning_invoice(cashu_id: str, invoice: Invoice):
async def get_lightning_invoice(cashu_id: str, hash: str):
row = await db.fetchone(
"""
- SELECT * from invoices
+ SELECT * from cashu.invoices
WHERE cashu_id =? AND hash = ?
""",
(
@@ -170,7 +170,7 @@ async def get_lightning_invoice(cashu_id: str, hash: str):
async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
await db.execute(
- "UPDATE invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
+ "UPDATE cashu.invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
(
issued,
cashu_id,
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index 570387a2..8b5a3417 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -1,5 +1,5 @@
from sqlite3 import Row
-from typing import List, Optional
+from typing import List, Union
from fastapi import Query
from pydantic import BaseModel
@@ -146,3 +146,8 @@ class MeltPayload(BaseModel):
proofs: List[Proof]
amount: int
invoice: str
+
+class CreateTokens(BaseModel):
+ # cashu_id: str = Query(None)
+ payloads: MintPayloads
+ payment_hash: Union[str, None] = None
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 36a9d105..6f1e161a 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -30,6 +30,7 @@ from .mint import get_pubkeys
from .models import (
Cashu,
CheckPayload,
+ CreateTokens,
Invoice,
MeltPayload,
MintPayloads,
@@ -251,12 +252,11 @@ async def mint_pay_request(
return {"pr": payment_request, "hash": payment_hash}
-@cashu_ext.post("/mint")
+@cashu_ext.post("/api/v1/mint/{cashu_id}")
async def mint_coins(
- payloads: MintPayloads,
- payment_hash: Union[str, None] = None,
+ data: CreateTokens,
cashu_id: str = Query(None),
- wallet: WalletTypeInfo = Depends(get_key_type),
+ wallet: WalletTypeInfo = Depends(require_admin_key),
):
"""
Requests the minting of tokens belonging to a paid payment request.
@@ -268,14 +268,20 @@ async def mint_coins(
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
)
- invoice: Invoice = get_lightning_invoice(cashu_id, payment_hash)
+ invoice: Invoice = (
+ None
+ if data.payment_hash == None
+ else await get_lightning_invoice(cashu_id, data.payment_hash)
+ )
if invoice is None:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not have this invoice."
)
+ # if invoice.issued == True:
+ # todo: give old tokens?
- status: PaymentStatus = check_transaction_status(cashu.wallet, payment_hash)
- if status.paid == False:
+ status: PaymentStatus = await check_transaction_status(cashu.wallet, data.payment_hash)
+ if status.paid != True:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
)
From 2c8ebaf225c2c0504f2dcd829b6f7e57aa517649 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 13:44:26 +0300
Subject: [PATCH 262/696] feat: return promises
---
lnbits/extensions/cashu/core/b_dhke.py | 93 ++++++++++++++++++++++++++
lnbits/extensions/cashu/crud.py | 2 +-
lnbits/extensions/cashu/mint.py | 47 +++++++------
lnbits/extensions/cashu/mint_helper.py | 25 +++----
lnbits/extensions/cashu/models.py | 3 +-
lnbits/extensions/cashu/views_api.py | 34 ++++++----
6 files changed, 158 insertions(+), 46 deletions(-)
create mode 100644 lnbits/extensions/cashu/core/b_dhke.py
diff --git a/lnbits/extensions/cashu/core/b_dhke.py b/lnbits/extensions/cashu/core/b_dhke.py
new file mode 100644
index 00000000..be9a141b
--- /dev/null
+++ b/lnbits/extensions/cashu/core/b_dhke.py
@@ -0,0 +1,93 @@
+# Don't trust me with cryptography.
+
+"""
+Implementation of https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406
+Alice:
+A = a*G
+return A
+Bob:
+Y = hash_to_point(secret_message)
+r = random blinding factor
+B'= Y + r*G
+return B'
+Alice:
+C' = a*B'
+ (= a*Y + a*r*G)
+return C'
+Bob:
+C = C' - r*A
+ (= C' - a*r*G)
+ (= a*Y)
+return C, secret_message
+Alice:
+Y = hash_to_point(secret_message)
+C == a*Y
+If true, C must have originated from Alice
+"""
+
+import hashlib
+
+from secp256k1 import PrivateKey, PublicKey
+
+
+def hash_to_point(secret_msg):
+ """Generates x coordinate from the message hash and checks if the point lies on the curve.
+ If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
+ point = None
+ msg = secret_msg
+ while point is None:
+ _hash = hashlib.sha256(msg).hexdigest().encode("utf-8")
+ try:
+ # We construct compressed pub which has x coordinate encoded with even y
+ _hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes
+ _hash[0] = 0x02 # set first byte to represent even y coord
+ _hash = bytes(_hash)
+ point = PublicKey(_hash, raw=True)
+ except:
+ msg = _hash
+
+ return point
+
+
+def step1_alice(secret_msg):
+ secret_msg = secret_msg.encode("utf-8")
+ Y = hash_to_point(secret_msg)
+ r = PrivateKey()
+ B_ = Y + r.pubkey
+ return B_, r
+
+
+def step2_bob(B_, a):
+ C_ = B_.mult(a)
+ return C_
+
+
+def step3_alice(C_, r, A):
+ C = C_ - A.mult(r)
+ return C
+
+
+def verify(a, C, secret_msg):
+ Y = hash_to_point(secret_msg.encode("utf-8"))
+ return C == Y.mult(a)
+
+
+### Below is a test of a simple positive and negative case
+
+# # Alice's keys
+# a = PrivateKey()
+# A = a.pubkey
+# secret_msg = "test"
+# B_, r = step1_alice(secret_msg)
+# C_ = step2_bob(B_, a)
+# C = step3_alice(C_, r, A)
+# print("C:{}, secret_msg:{}".format(C, secret_msg))
+# assert verify(a, C, secret_msg)
+# assert verify(a, C + C, secret_msg) == False # adding C twice shouldn't pass
+# assert verify(a, A, secret_msg) == False # A shouldn't pass
+
+# # Test operations
+# b = PrivateKey()
+# B = b.pubkey
+# assert -A -A + A == -A # neg
+# assert B.mult(a) == A.mult(b) # a*B = A*b
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 733f4737..39af1f8c 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -93,7 +93,7 @@ async def delete_cashu(cashu_id) -> None:
##########################################
-async def store_promise(amount: int, B_: str, C_: str, cashu_id):
+async def store_promise(amount: int, B_: str, C_: str, cashu_id: str):
promise_id = urlsafe_short_hash()
await db.execute(
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index c2568313..b1bdd042 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -1,5 +1,12 @@
-from .mint_helper import derive_keys, derive_pubkeys
-from .models import Cashu
+from typing import List
+
+from .core.b_dhke import step2_bob
+from .core.base import BlindedSignature
+from .core.secp import PublicKey
+from .mint_helper import derive_key, derive_keys, derive_pubkeys
+
+# todo: extract const
+MAX_ORDER = 64
def get_pubkeys(xpriv: str):
@@ -11,22 +18,24 @@ def get_pubkeys(xpriv: str):
return {a: p.serialize().hex() for a, p in pub_keys.items()}
-# async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None):
-# """Mints a promise for coins for B_."""
-# # check if lightning invoice was paid
-# if LIGHTNING:
-# try:
-# paid = await self._check_lightning_invoice(payment_hash)
-# except:
-# raise Exception("could not check invoice.")
-# if not paid:
-# raise Exception("Lightning invoice not paid yet.")
+async def generate_promises(
+ master_prvkey: str, amounts: List[int], B_s: List[PublicKey]
+):
+ """Mints a promise for coins for B_."""
-# for amount in amounts:
-# if amount not in [2**i for i in range(MAX_ORDER)]:
-# raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
+ for amount in amounts:
+ if amount not in [2**i for i in range(MAX_ORDER)]:
+ raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
-# promises = [
-# await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
-# ]
-# return promises
+ promises = [
+ await generate_promise(master_prvkey, amount, B_)
+ for B_, amount in zip(B_s, amounts)
+ ]
+ return promises
+
+
+async def generate_promise(master_prvkey: str, amount: int, B_: PublicKey):
+ """Generates a promise for given amount and returns a pair (amount, C')."""
+ secret_key = derive_key(master_prvkey, amount) # Get the correct key
+ C_ = step2_bob(B_, secret_key)
+ return BlindedSignature(amount=amount, C_=C_.serialize().hex())
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
index 1cf631b4..50227733 100644
--- a/lnbits/extensions/cashu/mint_helper.py
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -1,7 +1,7 @@
import hashlib
-from typing import List, Set
+from typing import List
-from .core.secp import PrivateKey, PublicKey
+from .core.secp import PrivateKey
# todo: extract const
MAX_ORDER = 64
@@ -9,16 +9,17 @@ MAX_ORDER = 64
def derive_keys(master_key: str):
"""Deterministic derivation of keys for 2^n values."""
- return {
- 2
- ** i: PrivateKey(
- hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
- .hexdigest()
- .encode("utf-8")[:32],
- raw=True,
- )
- for i in range(MAX_ORDER)
- }
+ return {2**i: derive_key(master_key, i) for i in range(MAX_ORDER)}
+
+
+def derive_key(master_key: str, i: int):
+ """Deterministic derivation of keys for a particular value."""
+ return PrivateKey(
+ hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
+ .hexdigest()
+ .encode("utf-8")[:32],
+ raw=True,
+ )
def derive_pubkeys(keys: List[PrivateKey]):
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index 8b5a3417..8b7558d2 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -147,7 +147,8 @@ class MeltPayload(BaseModel):
amount: int
invoice: str
+
class CreateTokens(BaseModel):
# cashu_id: str = Query(None)
payloads: MintPayloads
- payment_hash: Union[str, None] = None
\ No newline at end of file
+ payment_hash: Union[str, None] = None
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 6f1e161a..93902356 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -24,9 +24,10 @@ from .crud import (
get_cashus,
get_lightning_invoice,
store_lightning_invoice,
+ store_promise,
)
from .ledger import mint, request_mint
-from .mint import get_pubkeys
+from .mint import generate_promises, get_pubkeys
from .models import (
Cashu,
CheckPayload,
@@ -280,22 +281,29 @@ async def mint_coins(
# if invoice.issued == True:
# todo: give old tokens?
- status: PaymentStatus = await check_transaction_status(cashu.wallet, data.payment_hash)
- if status.paid != True:
+ status: PaymentStatus = await check_transaction_status(
+ cashu.wallet, data.payment_hash
+ )
+ # todo: revert to: status.paid != True:
+ if status.paid == False:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
)
- # amounts = []
- # B_s = []
- # for payload in payloads.blinded_messages:
- # amounts.append(payload.amount)
- # B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
- # try:
- # promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
- # return promises
- # except Exception as exc:
- # return CashuError(error=str(exc))
+ amounts = []
+ B_s = []
+ for payload in data.payloads.blinded_messages:
+ amounts.append(payload.amount)
+ B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
+ print("### amounts", amounts)
+ print("### B_s", B_s)
+
+ try:
+ promises = await generate_promises(cashu.prvkey, amounts, B_s)
+ # await store_promise(amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), cashu_id)
+ return promises
+ except Exception as exc:
+ return CashuError(error=str(exc))
@cashu_ext.post("/melt")
From 7609f7ea5b0cc81bf6e36f2b015bd8a8545c21b5 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 14:51:06 +0300
Subject: [PATCH 263/696] fix: don't even try to refactor
---
lnbits/extensions/cashu/crud.py | 5 +++++
lnbits/extensions/cashu/mint.py | 4 ++--
lnbits/extensions/cashu/mint_helper.py | 20 ++++++++++----------
lnbits/extensions/cashu/views_api.py | 3 +++
4 files changed, 20 insertions(+), 12 deletions(-)
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 39af1f8c..698a8089 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -93,6 +93,11 @@ async def delete_cashu(cashu_id) -> None:
##########################################
+async def store_promises(amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str):
+ for amount, B_, C_ in zip(amounts, B_s, C_s):
+ await store_promise(amount, B_, C_, cashu_id)
+
+
async def store_promise(amount: int, B_: str, C_: str, cashu_id: str):
promise_id = urlsafe_short_hash()
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index b1bdd042..1be07b91 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -3,7 +3,7 @@ from typing import List
from .core.b_dhke import step2_bob
from .core.base import BlindedSignature
from .core.secp import PublicKey
-from .mint_helper import derive_key, derive_keys, derive_pubkeys
+from .mint_helper import derive_keys, derive_pubkeys
# todo: extract const
MAX_ORDER = 64
@@ -36,6 +36,6 @@ async def generate_promises(
async def generate_promise(master_prvkey: str, amount: int, B_: PublicKey):
"""Generates a promise for given amount and returns a pair (amount, C')."""
- secret_key = derive_key(master_prvkey, amount) # Get the correct key
+ secret_key = derive_keys(master_prvkey)[amount] # Get the correct key
C_ = step2_bob(B_, secret_key)
return BlindedSignature(amount=amount, C_=C_.serialize().hex())
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
index 50227733..cfb3b7d7 100644
--- a/lnbits/extensions/cashu/mint_helper.py
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -9,18 +9,18 @@ MAX_ORDER = 64
def derive_keys(master_key: str):
"""Deterministic derivation of keys for 2^n values."""
- return {2**i: derive_key(master_key, i) for i in range(MAX_ORDER)}
+ return {
+ 2
+ ** i: PrivateKey(
+ hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
+ .hexdigest()
+ .encode("utf-8")[:32],
+ raw=True,
+ )
+ for i in range(MAX_ORDER)
+ }
-def derive_key(master_key: str, i: int):
- """Deterministic derivation of keys for a particular value."""
- return PrivateKey(
- hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
- .hexdigest()
- .encode("utf-8")[:32],
- raw=True,
- )
-
def derive_pubkeys(keys: List[PrivateKey]):
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 93902356..fb4cefb0 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -300,6 +300,9 @@ async def mint_coins(
try:
promises = await generate_promises(cashu.prvkey, amounts, B_s)
+ for amount, B_, p in zip(amounts, B_s, promises):
+ await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
+ # store_promises(amounts, B_s, C_s, cashu_id)
# await store_promise(amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), cashu_id)
return promises
except Exception as exc:
From e7030dac19e0beaa4f980064f6b8766a881af0d4 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 14:53:13 +0300
Subject: [PATCH 264/696] fix: uniq promise
---
lnbits/extensions/cashu/migrations.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index cb6b24f9..d06baef1 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -43,6 +43,7 @@ async def m001_initial(db):
B_b TEXT NOT NULL,
C_b TEXT NOT NULL,
cashu_id TEXT NOT NULL
+ UNIQUE (B_b)
);
"""
)
From 01d7f415becd05ad698fd3395c5fb811d57c2997 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 14:57:12 +0300
Subject: [PATCH 265/696] chore: code clean-up
---
lnbits/extensions/cashu/views_api.py | 4 ----
1 file changed, 4 deletions(-)
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index fb4cefb0..2379aec6 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -295,15 +295,11 @@ async def mint_coins(
for payload in data.payloads.blinded_messages:
amounts.append(payload.amount)
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
- print("### amounts", amounts)
- print("### B_s", B_s)
try:
promises = await generate_promises(cashu.prvkey, amounts, B_s)
for amount, B_, p in zip(amounts, B_s, promises):
await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
- # store_promises(amounts, B_s, C_s, cashu_id)
- # await store_promise(amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), cashu_id)
return promises
except Exception as exc:
return CashuError(error=str(exc))
From cf31d2729dfa3620fa784d7d6df736d8d80105b2 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 15:09:26 +0300
Subject: [PATCH 266/696] fix: mint format
---
lnbits/extensions/cashu/views_api.py | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 2379aec6..5bbf084e 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -255,8 +255,9 @@ async def mint_pay_request(
@cashu_ext.post("/api/v1/mint/{cashu_id}")
async def mint_coins(
- data: CreateTokens,
+ data: MintPayloads,
cashu_id: str = Query(None),
+ payment_hash: Union[str, None] = None,
wallet: WalletTypeInfo = Depends(require_admin_key),
):
"""
@@ -271,8 +272,8 @@ async def mint_coins(
)
invoice: Invoice = (
None
- if data.payment_hash == None
- else await get_lightning_invoice(cashu_id, data.payment_hash)
+ if payment_hash == None
+ else await get_lightning_invoice(cashu_id, payment_hash)
)
if invoice is None:
raise HTTPException(
@@ -282,7 +283,7 @@ async def mint_coins(
# todo: give old tokens?
status: PaymentStatus = await check_transaction_status(
- cashu.wallet, data.payment_hash
+ cashu.wallet, payment_hash
)
# todo: revert to: status.paid != True:
if status.paid == False:
@@ -292,7 +293,7 @@ async def mint_coins(
amounts = []
B_s = []
- for payload in data.payloads.blinded_messages:
+ for payload in data.blinded_messages:
amounts.append(payload.amount)
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
From 2757ad2ea932b0eb8889240190ed3dcdaeadbf5f Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 15:18:09 +0300
Subject: [PATCH 267/696] feat: update invoice status
---
lnbits/extensions/cashu/views_api.py | 32 +++++++++++++++++-----------
1 file changed, 19 insertions(+), 13 deletions(-)
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 5bbf084e..d6b34538 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -25,6 +25,7 @@ from .crud import (
get_lightning_invoice,
store_lightning_invoice,
store_promise,
+ update_lightning_invoice,
)
from .ledger import mint, request_mint
from .mint import generate_promises, get_pubkeys
@@ -279,8 +280,10 @@ async def mint_coins(
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not have this invoice."
)
- # if invoice.issued == True:
- # todo: give old tokens?
+ if invoice.issued == True:
+ raise HTTPException(
+ status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Tokens already issued for this invoice."
+ )
status: PaymentStatus = await check_transaction_status(
cashu.wallet, payment_hash
@@ -290,18 +293,21 @@ async def mint_coins(
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
)
-
- amounts = []
- B_s = []
- for payload in data.blinded_messages:
- amounts.append(payload.amount)
- B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
-
try:
- promises = await generate_promises(cashu.prvkey, amounts, B_s)
- for amount, B_, p in zip(amounts, B_s, promises):
- await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
- return promises
+ await update_lightning_invoice(cashu_id, payment_hash, True)
+
+ amounts = []
+ B_s = []
+ for payload in data.blinded_messages:
+ amounts.append(payload.amount)
+ B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
+
+
+ promises = await generate_promises(cashu.prvkey, amounts, B_s)
+ for amount, B_, p in zip(amounts, B_s, promises):
+ await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
+
+ return promises
except Exception as exc:
return CashuError(error=str(exc))
From 0ee024f27643673b07d5e0e2a7fd0b1ce9c0dd31 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 17:44:02 +0300
Subject: [PATCH 268/696] feat: melt tokens
---
lnbits/extensions/cashu/crud.py | 12 ++---
lnbits/extensions/cashu/mint.py | 62 ++++++++++++++++++++++++--
lnbits/extensions/cashu/mint_helper.py | 21 +++++++--
lnbits/extensions/cashu/models.py | 6 ---
lnbits/extensions/cashu/views_api.py | 41 ++++++++++-------
5 files changed, 109 insertions(+), 33 deletions(-)
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 698a8089..c8f3c72b 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -93,7 +93,9 @@ async def delete_cashu(cashu_id) -> None:
##########################################
-async def store_promises(amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str):
+async def store_promises(
+ amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str
+):
for amount, B_, C_ in zip(amounts, B_s, C_s):
await store_promise(amount, B_, C_, cashu_id)
@@ -113,21 +115,21 @@ async def store_promise(amount: int, B_: str, C_: str, cashu_id: str):
async def get_promises(cashu_id) -> Optional[Cashu]:
row = await db.fetchall(
- "SELECT * FROM cashu.promises WHERE cashu_id = ?", (promises_id,)
+ "SELECT * FROM cashu.promises WHERE cashu_id = ?", (cashu_id,)
)
return Promises(**row) if row else None
async def get_proofs_used(cashu_id):
rows = await db.fetchall(
- "SELECT secret from cashu.proofs_used WHERE id = ?", (cashu_id,)
+ "SELECT secret from cashu.proofs_used WHERE cashu_id = ?", (cashu_id,)
)
return [row[0] for row in rows]
-async def invalidate_proof(proof: Proof, cashu_id):
+async def invalidate_proof(cashu_id: str, proof: Proof):
invalidate_proof_id = urlsafe_short_hash()
- await (conn or db).execute(
+ await db.execute(
"""
INSERT INTO cashu.proofs_used
(id, amount, C, secret, cashu_id)
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index 1be07b91..6be60703 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -1,9 +1,16 @@
-from typing import List
+import math
+from typing import List, Set
+
+from lnbits import bolt11
+from lnbits.core.services import check_transaction_status, fee_reserve, pay_invoice
+from lnbits.extensions.cashu.models import Cashu
+from lnbits.wallets.base import PaymentStatus
from .core.b_dhke import step2_bob
-from .core.base import BlindedSignature
+from .core.base import BlindedSignature, Proof
from .core.secp import PublicKey
-from .mint_helper import derive_keys, derive_pubkeys
+from .crud import get_proofs_used, invalidate_proof
+from .mint_helper import derive_keys, derive_pubkeys, verify_proof
# todo: extract const
MAX_ORDER = 64
@@ -39,3 +46,52 @@ async def generate_promise(master_prvkey: str, amount: int, B_: PublicKey):
secret_key = derive_keys(master_prvkey)[amount] # Get the correct key
C_ = step2_bob(B_, secret_key)
return BlindedSignature(amount=amount, C_=C_.serialize().hex())
+
+
+async def melt(cashu: Cashu, proofs: List[Proof], invoice: str):
+ """Invalidates proofs and pays a Lightning invoice."""
+ # Verify proofs
+ proofs_used: Set[str] = set(await get_proofs_used(cashu.id))
+ # if not all([verify_proof(cashu.prvkey, proofs_used, p) for p in proofs]):
+ # raise Exception("could not verify proofs.")
+ for p in proofs:
+ await verify_proof(cashu.prvkey, proofs_used, p)
+
+ total_provided = sum([p["amount"] for p in proofs])
+ invoice_obj = bolt11.decode(invoice)
+ amount = math.ceil(invoice_obj.amount_msat / 1000)
+
+ fees_msat = await check_fees(cashu.wallet, invoice_obj)
+ assert total_provided >= amount + fees_msat / 1000, Exception(
+ "provided proofs not enough for Lightning payment."
+ )
+
+ await pay_invoice(
+ wallet_id=cashu.wallet,
+ payment_request=invoice,
+ description=f"pay cashu invoice",
+ extra={"tag": "cashu", "cahsu_name": cashu.name},
+ )
+
+ status: PaymentStatus = await check_transaction_status(
+ cashu.wallet, invoice_obj.payment_hash
+ )
+ if status.paid == True:
+ await invalidate_proofs(cashu.id, proofs)
+ return status.paid, status.preimage
+ return False, ""
+
+
+async def check_fees(wallet_id: str, decoded_invoice):
+ """Returns the fees (in msat) required to pay this pr."""
+ amount = math.ceil(decoded_invoice.amount_msat / 1000)
+ status: PaymentStatus = await check_transaction_status(
+ wallet_id, decoded_invoice.payment_hash
+ )
+ fees_msat = fee_reserve(amount * 1000) if status.paid != True else 0
+ return fees_msat
+
+async def invalidate_proofs(cashu_id: str, proofs: List[Proof]):
+ """Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
+ for p in proofs:
+ await invalidate_proof(cashu_id, p)
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
index cfb3b7d7..5fc43e49 100644
--- a/lnbits/extensions/cashu/mint_helper.py
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -1,7 +1,9 @@
import hashlib
-from typing import List
+from typing import List, Set
-from .core.secp import PrivateKey
+from .core.b_dhke import verify
+from .core.secp import PrivateKey, PublicKey
+from .models import Proof
# todo: extract const
MAX_ORDER = 64
@@ -21,6 +23,19 @@ def derive_keys(master_key: str):
}
-
def derive_pubkeys(keys: List[PrivateKey]):
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
+
+
+async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
+ """Verifies that the proof of promise was issued by this ledger."""
+ if proof.secret in proofs_used:
+ raise Exception(f"tokens already spent. Secret: {proof.secret}")
+
+ secret_key = derive_keys(master_prvkey)[
+ proof.amount
+ ] # Get the correct key to check against
+ C = PublicKey(bytes.fromhex(proof.C), raw=True)
+ validMintSig = verify(secret_key, C, proof.secret)
+ if validMintSig != True:
+ raise Exception(f"tokens not valid. Secret: {proof.secret}")
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index 8b7558d2..596db047 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -146,9 +146,3 @@ class MeltPayload(BaseModel):
proofs: List[Proof]
amount: int
invoice: str
-
-
-class CreateTokens(BaseModel):
- # cashu_id: str = Query(None)
- payloads: MintPayloads
- payment_hash: Union[str, None] = None
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index d6b34538..bd1d27f0 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -28,11 +28,10 @@ from .crud import (
update_lightning_invoice,
)
from .ledger import mint, request_mint
-from .mint import generate_promises, get_pubkeys
+from .mint import generate_promises, get_pubkeys, melt
from .models import (
Cashu,
CheckPayload,
- CreateTokens,
Invoice,
MeltPayload,
MintPayloads,
@@ -248,7 +247,7 @@ async def mint_pay_request(
except Exception as e:
logger.error(e)
raise HTTPException(
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(cashu_id)
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)
)
return {"pr": payment_request, "hash": payment_hash}
@@ -265,7 +264,6 @@ async def mint_coins(
Requests the minting of tokens belonging to a paid payment request.
Call this endpoint after `GET /mint`.
"""
- print("############################ amount")
cashu: Cashu = await get_cashu(cashu_id)
if cashu is None:
raise HTTPException(
@@ -282,12 +280,11 @@ async def mint_coins(
)
if invoice.issued == True:
raise HTTPException(
- status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Tokens already issued for this invoice."
+ status_code=HTTPStatus.PAYMENT_REQUIRED,
+ detail="Tokens already issued for this invoice.",
)
- status: PaymentStatus = await check_transaction_status(
- cashu.wallet, payment_hash
- )
+ status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
# todo: revert to: status.paid != True:
if status.paid == False:
raise HTTPException(
@@ -302,21 +299,33 @@ async def mint_coins(
amounts.append(payload.amount)
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
-
promises = await generate_promises(cashu.prvkey, amounts, B_s)
for amount, B_, p in zip(amounts, B_s, promises):
await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
-
+
return promises
- except Exception as exc:
- return CashuError(error=str(exc))
+ except Exception as e:
+ logger.error(e)
+ raise HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)
+ )
-@cashu_ext.post("/melt")
+@cashu_ext.post("/api/v1/melt/{cashu_id}")
async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
-
- ok, preimage = await melt(payload.proofs, payload.amount, payload.invoice, cashu_id)
- return {"paid": ok, "preimage": preimage}
+ cashu: Cashu = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
+ try:
+ ok, preimage = await melt(cashu, payload.proofs, payload.invoice)
+ return {"paid": ok, "preimage": preimage}
+ except Exception as e:
+ logger.error(e)
+ raise HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)
+ )
@cashu_ext.post("/check")
From d1c751870afeeb6bc3a26f200fb68a0248bb7d02 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 18:18:46 +0300
Subject: [PATCH 269/696] feat: make endpoints compatible with `cashu` client
---
lnbits/extensions/cashu/views_api.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index bd1d27f0..5ba80eaa 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -206,7 +206,7 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
########################################
-@cashu_ext.get("/api/v1/mint/keys/{cashu_id}", status_code=HTTPStatus.OK)
+@cashu_ext.get("/api/v1/mint/{cashu_id}/keys", status_code=HTTPStatus.OK)
async def keys(
cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
@@ -219,7 +219,7 @@ async def keys(
return get_pubkeys(mint.prvkey)
-@cashu_ext.get("/api/v1/mint/{cashu_id}")
+@cashu_ext.get("/api/v1/{cashu_id}/mint")
async def mint_pay_request(
amount: int = 0,
cashu_id: str = Query(None),
@@ -253,7 +253,7 @@ async def mint_pay_request(
return {"pr": payment_request, "hash": payment_hash}
-@cashu_ext.post("/api/v1/mint/{cashu_id}")
+@cashu_ext.post("/api/v1/{cashu_id}/mint")
async def mint_coins(
data: MintPayloads,
cashu_id: str = Query(None),
@@ -311,7 +311,7 @@ async def mint_coins(
)
-@cashu_ext.post("/api/v1/melt/{cashu_id}")
+@cashu_ext.post("/api/v1/{cashu_id}/melt")
async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
cashu: Cashu = await get_cashu(cashu_id)
if cashu is None:
From e842c3df52b0d5c38eb837819aef549e76a14ca7 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 19:44:06 +0300
Subject: [PATCH 270/696] fix: enpoints
---
lnbits/extensions/cashu/views_api.py | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 5ba80eaa..25298972 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -82,11 +82,11 @@ async def api_cashu_delete(
if not cashu:
raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+ status_code=HTTPStatus.NOT_FOUND, detail="Cashu does not exist."
)
if cashu.wallet != wallet.wallet.id:
- raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your TPoS.")
+ raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your Cashu.")
await delete_cashu(cashu_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
@@ -206,7 +206,7 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
########################################
-@cashu_ext.get("/api/v1/mint/{cashu_id}/keys", status_code=HTTPStatus.OK)
+@cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
async def keys(
cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
@@ -219,7 +219,7 @@ async def keys(
return get_pubkeys(mint.prvkey)
-@cashu_ext.get("/api/v1/{cashu_id}/mint")
+@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
async def mint_pay_request(
amount: int = 0,
cashu_id: str = Query(None),
@@ -253,7 +253,7 @@ async def mint_pay_request(
return {"pr": payment_request, "hash": payment_hash}
-@cashu_ext.post("/api/v1/{cashu_id}/mint")
+@cashu_ext.post("/api/v1/cashu/{cashu_id}/mint")
async def mint_coins(
data: MintPayloads,
cashu_id: str = Query(None),
@@ -286,7 +286,7 @@ async def mint_coins(
status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
# todo: revert to: status.paid != True:
- if status.paid == False:
+ if status.paid != True:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
)
@@ -311,7 +311,7 @@ async def mint_coins(
)
-@cashu_ext.post("/api/v1/{cashu_id}/melt")
+@cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
cashu: Cashu = await get_cashu(cashu_id)
if cashu is None:
From f27632eb8e59490b3814fda1906263b28e782aa1 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 7 Oct 2022 20:27:40 +0300
Subject: [PATCH 271/696] fix: check invoice amount against blinded message
amount
---
lnbits/extensions/cashu/core/base.py | 2 +-
lnbits/extensions/cashu/views_api.py | 15 +++++++++++----
2 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/cashu/core/base.py b/lnbits/extensions/cashu/core/base.py
index 4943ee24..947da987 100644
--- a/lnbits/extensions/cashu/core/base.py
+++ b/lnbits/extensions/cashu/core/base.py
@@ -4,7 +4,7 @@ from typing import List, Union
from pydantic import BaseModel
-class CashuError(BaseModel):
+class CashuError(BaseException):
code = "000"
error = "CashuError"
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 25298972..a53caaa0 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -284,6 +284,13 @@ async def mint_coins(
detail="Tokens already issued for this invoice.",
)
+ total_requested = sum([bm.amount for bm in data.blinded_messages])
+ if total_requested > invoice.amount:
+ # raise CashuError(error = f"Requested amount to high: {total_requested}. Invoice amount: {invoice.amount}")
+ raise HTTPException(
+ status_code=HTTPStatus.PAYMENT_REQUIRED, detail=f"Requested amount to high: {total_requested}. Invoice amount: {invoice.amount}"
+ )
+
status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
# todo: revert to: status.paid != True:
if status.paid != True:
@@ -299,11 +306,11 @@ async def mint_coins(
amounts.append(payload.amount)
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
- promises = await generate_promises(cashu.prvkey, amounts, B_s)
- for amount, B_, p in zip(amounts, B_s, promises):
- await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
+ promises = await generate_promises(cashu.prvkey, amounts, B_s)
+ for amount, B_, p in zip(amounts, B_s, promises):
+ await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
- return promises
+ return promises
except Exception as e:
logger.error(e)
raise HTTPException(
From ea5903a4304278340d992933e7d1abaeef461002 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Sat, 8 Oct 2022 11:38:14 +0300
Subject: [PATCH 272/696] feat: add split logic
---
lnbits/extensions/cashu/core/split.py | 8 +++
lnbits/extensions/cashu/mint.py | 72 +++++++++++++++++++++++---
lnbits/extensions/cashu/mint_helper.py | 57 +++++++++++++++++++-
lnbits/extensions/cashu/views_api.py | 67 ++++++++++++------------
4 files changed, 160 insertions(+), 44 deletions(-)
create mode 100644 lnbits/extensions/cashu/core/split.py
diff --git a/lnbits/extensions/cashu/core/split.py b/lnbits/extensions/cashu/core/split.py
new file mode 100644
index 00000000..44b9cf51
--- /dev/null
+++ b/lnbits/extensions/cashu/core/split.py
@@ -0,0 +1,8 @@
+def amount_split(amount):
+ """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
+ bits_amt = bin(amount)[::-1][:-2]
+ rv = []
+ for (pos, bit) in enumerate(bits_amt):
+ if bit == "1":
+ rv.append(2**pos)
+ return rv
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index 6be60703..3388b45a 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -3,14 +3,24 @@ from typing import List, Set
from lnbits import bolt11
from lnbits.core.services import check_transaction_status, fee_reserve, pay_invoice
-from lnbits.extensions.cashu.models import Cashu
from lnbits.wallets.base import PaymentStatus
from .core.b_dhke import step2_bob
-from .core.base import BlindedSignature, Proof
+from .core.base import BlindedMessage, BlindedSignature, Proof
from .core.secp import PublicKey
+from .core.split import amount_split
from .crud import get_proofs_used, invalidate_proof
-from .mint_helper import derive_keys, derive_pubkeys, verify_proof
+from .mint_helper import (
+ derive_keys,
+ derive_pubkeys,
+ verify_equation_balanced,
+ verify_no_duplicates,
+ verify_outputs,
+ verify_proof,
+ verify_secret_criteria,
+ verify_split_amount,
+)
+from .models import Cashu
# todo: extract const
MAX_ORDER = 64
@@ -52,10 +62,8 @@ async def melt(cashu: Cashu, proofs: List[Proof], invoice: str):
"""Invalidates proofs and pays a Lightning invoice."""
# Verify proofs
proofs_used: Set[str] = set(await get_proofs_used(cashu.id))
- # if not all([verify_proof(cashu.prvkey, proofs_used, p) for p in proofs]):
- # raise Exception("could not verify proofs.")
for p in proofs:
- await verify_proof(cashu.prvkey, proofs_used, p)
+ await verify_proof(cashu.prvkey, proofs_used, p)
total_provided = sum([p["amount"] for p in proofs])
invoice_obj = bolt11.decode(invoice)
@@ -91,7 +99,57 @@ async def check_fees(wallet_id: str, decoded_invoice):
fees_msat = fee_reserve(amount * 1000) if status.paid != True else 0
return fees_msat
+
+async def split(
+ cashu: Cashu, proofs: List[Proof], amount: int, outputs: List[BlindedMessage]
+):
+ """Consumes proofs and prepares new promises based on the amount split."""
+ total = sum([p.amount for p in proofs])
+
+ # verify that amount is kosher
+ verify_split_amount(amount)
+ # verify overspending attempt
+ if amount > total:
+ raise Exception(
+ f"split amount ({amount}) is higher than the total sum ({total})."
+ )
+
+ # Verify secret criteria
+ if not all([verify_secret_criteria(p) for p in proofs]):
+ raise Exception("secrets do not match criteria.")
+ # verify that only unique proofs and outputs were used
+ if not verify_no_duplicates(proofs, outputs):
+ raise Exception("duplicate proofs or promises.")
+ # verify that outputs have the correct amount
+ if not verify_outputs(total, amount, outputs): # ?
+ raise Exception("split of promises is not as expected.")
+ # Verify proofs
+ # Verify proofs
+ proofs_used: Set[str] = set(await get_proofs_used(cashu.id))
+ for p in proofs:
+ await verify_proof(cashu.prvkey, proofs_used, p)
+
+ # Mark proofs as used and prepare new promises
+ await invalidate_proofs(cashu.id, proofs)
+
+ outs_fst = amount_split(total - amount)
+ outs_snd = amount_split(amount)
+ B_fst = [
+ PublicKey(bytes.fromhex(od.B_), raw=True) for od in outputs[: len(outs_fst)]
+ ]
+ B_snd = [
+ PublicKey(bytes.fromhex(od.B_), raw=True) for od in outputs[len(outs_fst) :]
+ ]
+ # PublicKey(bytes.fromhex(payload.B_), raw=True)
+ prom_fst, prom_snd = await generate_promises(
+ cashu.prvkey, outs_fst, B_fst
+ ), await generate_promises(cashu.prvkey, outs_snd, B_snd)
+ # verify amounts in produced proofs
+ verify_equation_balanced(proofs, prom_fst + prom_snd)
+ return prom_fst, prom_snd
+
+
async def invalidate_proofs(cashu_id: str, proofs: List[Proof]):
"""Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
for p in proofs:
- await invalidate_proof(cashu_id, p)
\ No newline at end of file
+ await invalidate_proof(cashu_id, p)
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
index 5fc43e49..5c96d831 100644
--- a/lnbits/extensions/cashu/mint_helper.py
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -2,8 +2,10 @@ import hashlib
from typing import List, Set
from .core.b_dhke import verify
+from .core.base import BlindedSignature
from .core.secp import PrivateKey, PublicKey
-from .models import Proof
+from .core.split import amount_split
+from .models import BlindedMessage, Proof
# todo: extract const
MAX_ORDER = 64
@@ -27,6 +29,7 @@ def derive_pubkeys(keys: List[PrivateKey]):
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
+# async required?
async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
"""Verifies that the proof of promise was issued by this ledger."""
if proof.secret in proofs_used:
@@ -38,4 +41,54 @@ async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
C = PublicKey(bytes.fromhex(proof.C), raw=True)
validMintSig = verify(secret_key, C, proof.secret)
if validMintSig != True:
- raise Exception(f"tokens not valid. Secret: {proof.secret}")
+ raise Exception(f"tokens not valid. Secret: {proof.secret}")
+
+
+def verify_split_amount(amount: int):
+ """Split amount like output amount can't be negative or too big."""
+ try:
+ verify_amount(amount)
+ except:
+ # For better error message
+ raise Exception("invalid split amount: " + str(amount))
+
+
+def verify_secret_criteria(proof: Proof):
+ if proof.secret is None or proof.secret == "":
+ raise Exception("no secret in proof.")
+ return True
+
+
+def verify_no_duplicates(proofs: List[Proof], outputs: List[BlindedMessage]):
+ secrets = [p.secret for p in proofs]
+ if len(secrets) != len(list(set(secrets))):
+ return False
+ B_s = [od.B_ for od in outputs]
+ if len(B_s) != len(list(set(B_s))):
+ return False
+ return True
+
+
+def verify_outputs(total: int, amount: int, outputs: List[BlindedMessage]):
+ """Verifies the expected split was correctly computed"""
+ frst_amt, scnd_amt = total - amount, amount # we have two amounts to split to
+ frst_outputs = amount_split(frst_amt)
+ scnd_outputs = amount_split(scnd_amt)
+ expected = frst_outputs + scnd_outputs
+ given = [o.amount for o in outputs]
+ return given == expected
+
+
+def verify_amount(amount: int):
+ """Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
+ valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
+ if not valid:
+ raise Exception("invalid amount: " + str(amount))
+ return amount
+
+
+def verify_equation_balanced(proofs: List[Proof], outs: List[BlindedSignature]):
+ """Verify that Σoutputs - Σinputs = 0."""
+ sum_inputs = sum(verify_amount(p.amount) for p in proofs)
+ sum_outputs = sum(verify_amount(p.amount) for p in outs)
+ assert sum_outputs - sum_inputs == 0
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index a53caaa0..3d221a17 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -1,3 +1,4 @@
+import json
from http import HTTPStatus
from typing import Union
@@ -16,7 +17,7 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.wallets.base import PaymentStatus
from . import cashu_ext
-from .core.base import CashuError
+from .core.base import CashuError, PostSplitResponse, SplitRequest
from .crud import (
create_cashu,
delete_cashu,
@@ -28,7 +29,7 @@ from .crud import (
update_lightning_invoice,
)
from .ledger import mint, request_mint
-from .mint import generate_promises, get_pubkeys, melt
+from .mint import generate_promises, get_pubkeys, melt, split
from .models import (
Cashu,
CheckPayload,
@@ -207,9 +208,7 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
@cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
-async def keys(
- cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
-):
+async def keys(cashu_id: str = Query(False)):
"""Get the public keys of the mint"""
mint = await get_cashu(cashu_id)
if mint is None:
@@ -220,11 +219,7 @@ async def keys(
@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
-async def mint_pay_request(
- amount: int = 0,
- cashu_id: str = Query(None),
- wallet: WalletTypeInfo = Depends(get_key_type),
-):
+async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
"""Request minting of tokens. Server responds with a Lightning invoice."""
cashu = await get_cashu(cashu_id)
@@ -246,9 +241,7 @@ async def mint_pay_request(
await store_lightning_invoice(cashu_id, invoice)
except Exception as e:
logger.error(e)
- raise HTTPException(
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)
- )
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
return {"pr": payment_request, "hash": payment_hash}
@@ -258,7 +251,6 @@ async def mint_coins(
data: MintPayloads,
cashu_id: str = Query(None),
payment_hash: Union[str, None] = None,
- wallet: WalletTypeInfo = Depends(require_admin_key),
):
"""
Requests the minting of tokens belonging to a paid payment request.
@@ -286,17 +278,17 @@ async def mint_coins(
total_requested = sum([bm.amount for bm in data.blinded_messages])
if total_requested > invoice.amount:
- # raise CashuError(error = f"Requested amount to high: {total_requested}. Invoice amount: {invoice.amount}")
raise HTTPException(
- status_code=HTTPStatus.PAYMENT_REQUIRED, detail=f"Requested amount to high: {total_requested}. Invoice amount: {invoice.amount}"
+ status_code=HTTPStatus.PAYMENT_REQUIRED,
+ detail=f"Requested amount too high: {total_requested}. Invoice amount: {invoice.amount}",
)
status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
# todo: revert to: status.paid != True:
- if status.paid != True:
- raise HTTPException(
- status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
- )
+ # if status.paid != True:
+ # raise HTTPException(
+ # status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
+ # )
try:
await update_lightning_invoice(cashu_id, payment_hash, True)
@@ -313,13 +305,12 @@ async def mint_coins(
return promises
except Exception as e:
logger.error(e)
- raise HTTPException(
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)
- )
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
@cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
+ """Invalidates proofs and pays a Lightning invoice."""
cashu: Cashu = await get_cashu(cashu_id)
if cashu is None:
raise HTTPException(
@@ -330,9 +321,7 @@ async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
return {"paid": ok, "preimage": preimage}
except Exception as e:
logger.error(e)
- raise HTTPException(
- status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e)
- )
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
@cashu_ext.post("/check")
@@ -340,21 +329,29 @@ async def check_spendable_coins(payload: CheckPayload, cashu_id: str = Query(Non
return await check_spendable(payload.proofs, cashu_id)
-@cashu_ext.post("/split")
-async def spli_coinst(payload: SplitPayload, cashu_id: str = Query(None)):
+@cashu_ext.post("/api/v1/cashu/{cashu_id}/split")
+async def split_proofs(payload: SplitRequest, cashu_id: str = Query(None)):
"""
Requetst a set of tokens with amount "total" to be split into two
newly minted sets with amount "split" and "total-split".
"""
+ print("### RECEIVE")
+ print("payload", json.dumps(payload, default=vars))
+ cashu: Cashu = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
proofs = payload.proofs
amount = payload.amount
- output_data = payload.output_data.blinded_messages
+ outputs = payload.outputs.blinded_messages if payload.outputs else None
try:
- split_return = await split(proofs, amount, output_data)
+ split_return = await split(cashu, proofs, amount, outputs)
except Exception as exc:
- return {"error": str(exc)}
+ raise CashuError(error=str(exc))
if not split_return:
- """There was a problem with the split"""
- raise Exception("could not split tokens.")
- fst_promises, snd_promises = split_return
- return {"fst": fst_promises, "snd": snd_promises}
+ return {"error": "there was a problem with the split."}
+ frst_promises, scnd_promises = split_return
+ resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
+ print("### resp", json.dumps(resp, default=vars))
+ return resp
From 2c7d8fe25838f20d768c9fae240209acfd2fba0c Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Sat, 8 Oct 2022 11:51:07 +0300
Subject: [PATCH 273/696] chore: fix unused endpoint path
---
lnbits/extensions/cashu/views_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 3d221a17..e2410909 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -324,7 +324,7 @@ async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-@cashu_ext.post("/check")
+@cashu_ext.post("/api/v1/cashu/{cashu_id}/check")
async def check_spendable_coins(payload: CheckPayload, cashu_id: str = Query(None)):
return await check_spendable(payload.proofs, cashu_id)
From e0dade55650c68dd4ee56cde524b1760893da2d6 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Sat, 8 Oct 2022 12:30:29 +0300
Subject: [PATCH 274/696] feat: random ui update
---
.../cashu/templates/cashu/wallet.html | 41 +++++++++++++++----
1 file changed, 34 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 75dff22e..3fa8028c 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -7,12 +7,38 @@ page_container %}
-
-
- {% raw %} {{balanceAmount}} {{tickershort}}{%
- endraw %}
-
-
+
+
+ Buy tokens
+ (with sats)
+
+
+
+
+
+ {% raw %} {{balanceAmount}}
+ {{tickershort}}{% endraw %}
+
+
+
+
+ Sell tokens
+ (for sats)
+
+
+
@@ -38,7 +64,8 @@ page_container %}
color="primary"
class="full-width"
@click="showSendDialog"
- >Send
+ Send
From b33aad5ee3fa37b7a2e4bdff28be95e3b256b9ff Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Sat, 8 Oct 2022 14:21:49 +0300
Subject: [PATCH 275/696] feat: show mint invoice dialog
---
.../cashu/templates/cashu/index.html | 2 +-
.../cashu/templates/cashu/wallet.html | 176 ++++++++++++------
2 files changed, 115 insertions(+), 63 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index 37dc360e..4e5ba911 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -49,7 +49,7 @@
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
- :href="'wallet/?tsh=' + props.row.tickershort + '&mnt=' + hostname + props.row.id + '&nme=' + props.row.name"
+ :href="'wallet/?tsh=' + (props.row.tickershort || '') + '&mint_id=' + props.row.id + '&mint_name=' + props.row.name"
target="_blank"
>Shareable wallet page
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 3fa8028c..de5cb7b6 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -14,8 +14,8 @@ page_container %}
rounded
color="secondary"
class="full-width"
- @click="showCamera"
- >Buy tokens
+ @click="showBuyTokensDialog"
+ >Buy tokens
(with sats)
@@ -28,12 +28,7 @@ page_container %}
- Sell tokens
(for sats)
@@ -52,7 +47,6 @@ page_container %}
rounded
color="primary"
class="full-width"
- @click="showParseDialog"
>Receive
@@ -63,7 +57,6 @@ page_container %}
rounded
color="primary"
class="full-width"
- @click="showSendDialog"
>
Send
@@ -75,7 +68,6 @@ page_container %}
color="secondary"
icon="sync_alt"
class="full-width"
- @click="showCamera"
>Peg in/out
@@ -86,7 +78,6 @@ page_container %}
color="secondary"
icon="photo_camera"
class="full-width"
- @click="showCamera"
>scan
@@ -107,23 +98,14 @@ page_container %}
icon="approval"
color="grey"
type="a"
- :href="mint"
+ :href="mintName"
target="_blank"
>{% endraw %}
Mint details
-
Export to CSV
+
Export to CSV
-
+
Show chart
@@ -244,12 +226,7 @@ page_container %}
-
Copy invoice
+
Copy invoice
-
-
+
+
@@ -557,18 +530,72 @@ page_container %}
lose the funds.
- Copy wallet URL
+ Copy wallet URL
I understand
+
+
+ {% raw %}
+
+
+
+
+
+ How much would you like to buy?
+
+
+
+
+
+
+
+
+ Copy invoice
+ Request Invoice
+ Close
+
+
+ {% endraw %}
+
@@ -605,6 +632,8 @@ page_container %}
return obj
}
+ Vue.component(VueQrcode.name, VueQrcode)
+
new Vue({
el: '#vue',
mixins: [windowMixin],
@@ -613,6 +642,17 @@ page_container %}
balanceAmount: '',
tickershort: '',
name: '',
+
+ mintId: '',
+ mintName: '',
+ buyTokens: {
+ showDialog: false,
+ amount: 0,
+ memo: '',
+ bolt11: '',
+ hash: ''
+ },
+
receive: {
show: false,
status: 'pending',
@@ -933,6 +973,28 @@ page_container %}
field: 'pending'
})
LNbits.utils.exportCSV(columns, this.payments)
+ },
+
+ /////////////////////////////////// WALLET ///////////////////////////////////
+ showBuyTokensDialog: async function () {
+ this.buyTokens.amount = 0
+ this.buyTokens.bolt11 = ''
+ this.buyTokens.hash = ''
+ this.buyTokens.memo = ''
+ this.buyTokens.showDialog = true
+ },
+ requestInvoice: async function () {
+ try {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ `/cashu/api/v1/cashu/${this.mintId}/mint?amount=${this.buyTokens.amount}`
+ )
+ console.log('### data', data)
+ this.buyTokens.bolt11 = data.pr
+ this.buyTokens.hash = data.hash
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
}
},
watch: {
@@ -940,19 +1002,7 @@ page_container %}
this.fetchBalance()
}
},
- created: function () {
- this.fetchBalance()
- this.fetchPayments()
- LNbits.api
- .request('GET', '/api/v1/currencies')
- .then(response => {
- this.receive.units = ['sat', ...response.data]
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- },
created: function () {
let params = new URL(document.location).searchParams
@@ -975,11 +1025,11 @@ page_container %}
}
// get mint
- if (params.get('mnt')) {
- this.mint = params.get('mnt')
- this.$q.localStorage.set('cashu.mint', params.get('mnt'))
+ if (params.get('mint_id')) {
+ this.mintId = params.get('mint_id')
+ this.$q.localStorage.set('cashu.mint', params.get('mint_id'))
} else if (this.$q.localStorage.getItem('cashu.mint')) {
- this.mint = this.$q.localStorage.getItem('cashu.mint')
+ this.mintId = this.$q.localStorage.getItem('cashu.mint')
} else {
this.$q.notify({
color: 'red',
@@ -988,12 +1038,14 @@ page_container %}
}
// get name
- if (params.get('nme')) {
- this.name = params.get('nme')
- this.$q.localStorage.set('cashu.name', params.get('nme'))
+ if (params.get('mint_name')) {
+ this.mintName = params.get('mint_name')
+ this.$q.localStorage.set('cashu.mintName', params.get('mint_name'))
} else if (this.$q.localStorage.getItem('cashu.name')) {
- this.name = this.$q.localStorage.getItem('cashu.name')
+ this.mintName = this.$q.localStorage.getItem('cashu.name')
}
+ console.log('#### this.mintId', this.mintId)
+ console.log('#### this.mintName', this.mintName)
}
})
From 8c7ca3f0166cb4bafb387be2723d007618a37628 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Sat, 8 Oct 2022 14:28:27 +0300
Subject: [PATCH 276/696] feat: ui stuff
---
.../cashu/templates/cashu/wallet.html | 71 +++++++------------
1 file changed, 25 insertions(+), 46 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index de5cb7b6..8d0ec1b7 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -37,54 +37,33 @@ page_container %}
-
-
- Receive
-
-
-
- Send
-
-
- Peg in/out
-
-
-
- scan
-
-
-
-
+
+
+ Receive
+
+
+
+
+ Send
+
+
+
Transactions
From c60b3808c0effb1af6a37ac8810e5794061ca4bb Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Sat, 8 Oct 2022 14:41:45 +0300
Subject: [PATCH 277/696] feat: check mobile UI
---
.../extensions/cashu/templates/cashu/wallet.html | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 8d0ec1b7..74837719 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -40,7 +40,7 @@ page_container %}
-
+
Receive
-
-
+
+
-
+
+
-
-
+
@@ -1000,7 +1000,7 @@ page_container %}
}
if (!this.$q.localStorage.getItem('cashu.amount')) {
- this.balanceAmount = 0
+ this.balanceAmount = 112340
}
// get mint
From 864b157cdc1d54f00f34e294304f3cc918684619 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Sat, 8 Oct 2022 14:54:48 +0300
Subject: [PATCH 278/696] feat: basic buys table
---
.../cashu/templates/cashu/wallet.html | 46 ++++++++++---------
1 file changed, 25 insertions(+), 21 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 74837719..3eb67861 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -64,7 +64,7 @@ page_container %}
-
+
{% raw %}
@@ -663,13 +662,20 @@ page_container %}
}
},
payments: [],
- paymentsTable: {
+ buysTable: {
columns: [
{
- name: 'note',
+ name: 'amount',
align: 'left',
- label: 'Note',
- field: 'note'
+ label: 'Amount',
+ field: 'amount'
+ },
+ {
+ name: 'memo',
+ align: 'left',
+ label: 'Memo',
+ field: 'memo',
+ sortable: true
},
{
name: 'date',
@@ -679,10 +685,10 @@ page_container %}
sortable: true
},
{
- name: 'amount',
+ name: 'hash',
align: 'right',
- label: 'Amount',
- field: 'amount',
+ label: 'Hash',
+ field: 'hash',
sortable: true
}
],
@@ -707,12 +713,10 @@ page_container %}
formattedBalance: function () {
return this.balance / 100
},
- filteredPayments: function () {
- var q = this.paymentsTable.filter
- if (!q || q === '') return this.payments
-
- return LNbits.utils.search(this.payments, q)
+ tokenBuys: function() {
+ return []
},
+
canPay: function () {
if (!this.parse.invoice) return false
return this.parse.invoice.sat <= this.balance
@@ -1000,7 +1004,7 @@ page_container %}
}
if (!this.$q.localStorage.getItem('cashu.amount')) {
- this.balanceAmount = 112340
+ this.balanceAmount = 0
}
// get mint
From 4f98926ae8c45934e49bccec1d0eec493b31b5bd Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 09:07:15 +0300
Subject: [PATCH 279/696] feat: store `tokenBuys`
---
.../cashu/templates/cashu/wallet.html | 240 +++++++++---------
1 file changed, 117 insertions(+), 123 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 3eb67861..6cf03b7d 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -110,142 +110,129 @@ page_container %}
:filter="buysTable.filter"
>
{% raw %}
-
-
-
- {{ col.label }}
-
-
-
-
-
- Pending
-
-
-
-
- #{{ props.row.tag }}
-
-
- {{ props.row.memo }}
+ {{props.row.amount}}
-
- {{ props.row.date }}
- {{ props.row.dateFrom }}
+
+
+ {{props.row.memo}}
- {% endraw %}
- {% raw %} {{
- parseFloat(String(props.row.fsat).replaceAll(",", "")) / 100
- }}
+
+ {{props.row.date}}
-
-
- {{ props.row.fsat }}
-
-
- {{ props.row.fee }}
+
+ {{props.row.hash}}
+
-
-
-
-
-
-
- Invoice waiting to be paid
-
-
+
+
+
+
-
- Copy invoice
- Close
-
-
-
- Payment Received
-
+
+ Copy
-
-
- Payment Sent
-
+
+
+ Rescan
-
-
- Outgoing payment pending
-
+
+ History
+
+
+ View Coins
-
-
+
+
+
Note:
+
+
+
+
+ Update
+
+
+
+
+
+
+
+ {{props.row.error}}
+
+
+
+
+
+ Gap limit of 20 addresses exceeded. Other wallets might not
+ detect funds at this address.
+
+
+
+ -->
{% endraw %}
@@ -623,6 +610,7 @@ page_container %}
mintId: '',
mintName: '',
+ tokenBuys: [],
buyTokens: {
showDialog: false,
amount: 0,
@@ -713,9 +701,9 @@ page_container %}
formattedBalance: function () {
return this.balance / 100
},
- tokenBuys: function() {
- return []
- },
+ // tokenBuys: function() {
+ // return []
+ // },
canPay: function () {
if (!this.parse.invoice) return false
@@ -973,8 +961,11 @@ page_container %}
`/cashu/api/v1/cashu/${this.mintId}/mint?amount=${this.buyTokens.amount}`
)
console.log('### data', data)
+
this.buyTokens.bolt11 = data.pr
this.buyTokens.hash = data.hash
+ this.tokenBuys.push({...this.buyTokens, date: new Date().toISOString()})
+ localStorage.setItem('cashu.tokenBuys', JSON.stringify(this.tokenBuys))
} catch (error) {
LNbits.utils.notifyApiError(error)
}
@@ -1027,6 +1018,9 @@ page_container %}
} else if (this.$q.localStorage.getItem('cashu.name')) {
this.mintName = this.$q.localStorage.getItem('cashu.name')
}
+
+ this.tokenBuys = JSON.parse(localStorage.getItem('cashu.tokenBuys') || '[]')
+ console.log('#### this.tokenBuys', this.tokenBuys)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
}
From c593be82e2cc0e8e6dc8af96aa7e4cac9045c9f4 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 09:19:53 +0300
Subject: [PATCH 280/696] refactor: renamings
---
.../cashu/templates/cashu/wallet.html | 63 ++++++++++++-------
1 file changed, 39 insertions(+), 24 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 6cf03b7d..8e4f9e0a 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -119,7 +119,7 @@ page_container %}
>
{{props.row.amount}}
-
+
{{props.row.memo}}
@@ -129,7 +129,6 @@ page_container %}
{{props.row.hash}}
-
{{props.row.hash}}
-
{% endraw %}
@@ -621,7 +483,6 @@ page_container %}
bolt11: '',
hash: ''
}
-
},
receive: {
From 1f592604b47f2294a113b1dae205d84ef6cfb748 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 09:35:47 +0300
Subject: [PATCH 282/696] feat: show status
---
.../extensions/cashu/templates/cashu/wallet.html | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index cdb7996a..8a25a8f7 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -76,6 +76,13 @@ page_container %}
{% raw %}
+
+
+
+ Pending
+
+
+
Date: Mon, 10 Oct 2022 12:42:34 +0300
Subject: [PATCH 283/696] feat: stupid random tests
---
lnbits/extensions/cashu/__init__.py | 9 +
lnbits/extensions/cashu/static/js/dhke.js | 31 +
.../cashu/static/js/noble-secp256k1.js | 1179 +++++++++++++++++
lnbits/extensions/cashu/static/js/utils.js | 23 +
.../cashu/templates/cashu/wallet.html | 128 ++
lnbits/extensions/cashu/views_api.py | 8 +-
6 files changed, 1374 insertions(+), 4 deletions(-)
create mode 100644 lnbits/extensions/cashu/static/js/dhke.js
create mode 100644 lnbits/extensions/cashu/static/js/noble-secp256k1.js
create mode 100644 lnbits/extensions/cashu/static/js/utils.js
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index bd7d5513..cb62ffca 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -1,6 +1,7 @@
import asyncio
from fastapi import APIRouter
+from fastapi.staticfiles import StaticFiles
from lnbits.db import Database
from lnbits.helpers import template_renderer
@@ -10,6 +11,14 @@ db = Database("ext_cashu")
cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
+cashu_static_files = [
+ {
+ "path": "/cashu/static",
+ "app": StaticFiles(directory="lnbits/extensions/cashu/static"),
+ "name": "cashu_static",
+ }
+]
+
def cashu_renderer():
return template_renderer(["lnbits/extensions/cashu/templates"])
diff --git a/lnbits/extensions/cashu/static/js/dhke.js b/lnbits/extensions/cashu/static/js/dhke.js
new file mode 100644
index 00000000..73b1bcb8
--- /dev/null
+++ b/lnbits/extensions/cashu/static/js/dhke.js
@@ -0,0 +1,31 @@
+async function hashToCurve(secretMessage) {
+ let point
+ while (!point) {
+ const hash = await nobleSecp256k1.utils.sha256(secretMessage)
+ try {
+ point = nobleSecp256k1.Point.fromHex(hash)
+ } catch (error) {
+ // console.error(error)
+ // const x = bytesToNumber(hash) + ''
+ // const msg = await nobleSecp256k1.utils.sha256(x)
+ secretMessage = await nobleSecp256k1.utils.sha256(hash)
+ // secretMessage = nobleSecp256k1.utils.bytesToHex(msg)
+ }
+ }
+ return point
+}
+
+async function step1Bob(secretMessage) {
+ const Y = await hashToCurve(secretMessage)
+ const randomBlindingFactor = bytesToNumber(
+ nobleSecp256k1.utils.randomPrivateKey()
+ )
+ const P = nobleSecp256k1.Point.fromPrivateKey(randomBlindingFactor)
+ const B_ = Y.add(P)
+ return {B_, randomBlindingFactor}
+}
+
+function step3Bob(C_, r, A) {
+ const C = C_.subtract(A.multiply(r))
+ return C
+}
diff --git a/lnbits/extensions/cashu/static/js/noble-secp256k1.js b/lnbits/extensions/cashu/static/js/noble-secp256k1.js
new file mode 100644
index 00000000..3b24b884
--- /dev/null
+++ b/lnbits/extensions/cashu/static/js/noble-secp256k1.js
@@ -0,0 +1,1179 @@
+;(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined'
+ ? factory(exports)
+ : typeof define === 'function' && define.amd
+ ? define(['exports'], factory)
+ : ((global =
+ typeof globalThis !== 'undefined' ? globalThis : global || self),
+ factory((global.nobleSecp256k1 = {})))
+})(this, function (exports) {
+ 'use strict'
+
+ const _nodeResolve_empty = {}
+
+ const nodeCrypto = /*#__PURE__*/ Object.freeze({
+ __proto__: null,
+ default: _nodeResolve_empty
+ })
+
+ /*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */
+ const _0n = BigInt(0)
+ const _1n = BigInt(1)
+ const _2n = BigInt(2)
+ const _3n = BigInt(3)
+ const _8n = BigInt(8)
+ const POW_2_256 = _2n ** BigInt(256)
+ const CURVE = {
+ a: _0n,
+ b: BigInt(7),
+ P: POW_2_256 - _2n ** BigInt(32) - BigInt(977),
+ n: POW_2_256 - BigInt('432420386565659656852420866394968145599'),
+ h: _1n,
+ Gx: BigInt(
+ '55066263022277343669578718895168534326250603453777594175500187360389116729240'
+ ),
+ Gy: BigInt(
+ '32670510020758816978083085130507043184471273380659243275938904335757337482424'
+ ),
+ beta: BigInt(
+ '0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee'
+ )
+ }
+ function weistrass(x) {
+ const {a, b} = CURVE
+ const x2 = mod(x * x)
+ const x3 = mod(x2 * x)
+ return mod(x3 + a * x + b)
+ }
+ const USE_ENDOMORPHISM = CURVE.a === _0n
+ class JacobianPoint {
+ constructor(x, y, z) {
+ this.x = x
+ this.y = y
+ this.z = z
+ }
+ static fromAffine(p) {
+ if (!(p instanceof Point)) {
+ throw new TypeError('JacobianPoint#fromAffine: expected Point')
+ }
+ return new JacobianPoint(p.x, p.y, _1n)
+ }
+ static toAffineBatch(points) {
+ const toInv = invertBatch(points.map(p => p.z))
+ return points.map((p, i) => p.toAffine(toInv[i]))
+ }
+ static normalizeZ(points) {
+ return JacobianPoint.toAffineBatch(points).map(JacobianPoint.fromAffine)
+ }
+ equals(other) {
+ if (!(other instanceof JacobianPoint))
+ throw new TypeError('JacobianPoint expected')
+ const {x: X1, y: Y1, z: Z1} = this
+ const {x: X2, y: Y2, z: Z2} = other
+ const Z1Z1 = mod(Z1 ** _2n)
+ const Z2Z2 = mod(Z2 ** _2n)
+ const U1 = mod(X1 * Z2Z2)
+ const U2 = mod(X2 * Z1Z1)
+ const S1 = mod(mod(Y1 * Z2) * Z2Z2)
+ const S2 = mod(mod(Y2 * Z1) * Z1Z1)
+ return U1 === U2 && S1 === S2
+ }
+ negate() {
+ return new JacobianPoint(this.x, mod(-this.y), this.z)
+ }
+ double() {
+ const {x: X1, y: Y1, z: Z1} = this
+ const A = mod(X1 ** _2n)
+ const B = mod(Y1 ** _2n)
+ const C = mod(B ** _2n)
+ const D = mod(_2n * (mod((X1 + B) ** _2n) - A - C))
+ const E = mod(_3n * A)
+ const F = mod(E ** _2n)
+ const X3 = mod(F - _2n * D)
+ const Y3 = mod(E * (D - X3) - _8n * C)
+ const Z3 = mod(_2n * Y1 * Z1)
+ return new JacobianPoint(X3, Y3, Z3)
+ }
+ add(other) {
+ if (!(other instanceof JacobianPoint))
+ throw new TypeError('JacobianPoint expected')
+ const {x: X1, y: Y1, z: Z1} = this
+ const {x: X2, y: Y2, z: Z2} = other
+ if (X2 === _0n || Y2 === _0n) return this
+ if (X1 === _0n || Y1 === _0n) return other
+ const Z1Z1 = mod(Z1 ** _2n)
+ const Z2Z2 = mod(Z2 ** _2n)
+ const U1 = mod(X1 * Z2Z2)
+ const U2 = mod(X2 * Z1Z1)
+ const S1 = mod(mod(Y1 * Z2) * Z2Z2)
+ const S2 = mod(mod(Y2 * Z1) * Z1Z1)
+ const H = mod(U2 - U1)
+ const r = mod(S2 - S1)
+ if (H === _0n) {
+ if (r === _0n) {
+ return this.double()
+ } else {
+ return JacobianPoint.ZERO
+ }
+ }
+ const HH = mod(H ** _2n)
+ const HHH = mod(H * HH)
+ const V = mod(U1 * HH)
+ const X3 = mod(r ** _2n - HHH - _2n * V)
+ const Y3 = mod(r * (V - X3) - S1 * HHH)
+ const Z3 = mod(Z1 * Z2 * H)
+ return new JacobianPoint(X3, Y3, Z3)
+ }
+ subtract(other) {
+ return this.add(other.negate())
+ }
+ multiplyUnsafe(scalar) {
+ const P0 = JacobianPoint.ZERO
+ if (typeof scalar === 'bigint' && scalar === _0n) return P0
+ let n = normalizeScalar(scalar)
+ if (n === _1n) return this
+ if (!USE_ENDOMORPHISM) {
+ let p = P0
+ let d = this
+ while (n > _0n) {
+ if (n & _1n) p = p.add(d)
+ d = d.double()
+ n >>= _1n
+ }
+ return p
+ }
+ let {k1neg, k1, k2neg, k2} = splitScalarEndo(n)
+ let k1p = P0
+ let k2p = P0
+ let d = this
+ while (k1 > _0n || k2 > _0n) {
+ if (k1 & _1n) k1p = k1p.add(d)
+ if (k2 & _1n) k2p = k2p.add(d)
+ d = d.double()
+ k1 >>= _1n
+ k2 >>= _1n
+ }
+ if (k1neg) k1p = k1p.negate()
+ if (k2neg) k2p = k2p.negate()
+ k2p = new JacobianPoint(mod(k2p.x * CURVE.beta), k2p.y, k2p.z)
+ return k1p.add(k2p)
+ }
+ precomputeWindow(W) {
+ const windows = USE_ENDOMORPHISM ? 128 / W + 1 : 256 / W + 1
+ const points = []
+ let p = this
+ let base = p
+ for (let window = 0; window < windows; window++) {
+ base = p
+ points.push(base)
+ for (let i = 1; i < 2 ** (W - 1); i++) {
+ base = base.add(p)
+ points.push(base)
+ }
+ p = base.double()
+ }
+ return points
+ }
+ wNAF(n, affinePoint) {
+ if (!affinePoint && this.equals(JacobianPoint.BASE))
+ affinePoint = Point.BASE
+ const W = (affinePoint && affinePoint._WINDOW_SIZE) || 1
+ if (256 % W) {
+ throw new Error(
+ 'Point#wNAF: Invalid precomputation window, must be power of 2'
+ )
+ }
+ let precomputes = affinePoint && pointPrecomputes.get(affinePoint)
+ if (!precomputes) {
+ precomputes = this.precomputeWindow(W)
+ if (affinePoint && W !== 1) {
+ precomputes = JacobianPoint.normalizeZ(precomputes)
+ pointPrecomputes.set(affinePoint, precomputes)
+ }
+ }
+ let p = JacobianPoint.ZERO
+ let f = JacobianPoint.ZERO
+ const windows = 1 + (USE_ENDOMORPHISM ? 128 / W : 256 / W)
+ const windowSize = 2 ** (W - 1)
+ const mask = BigInt(2 ** W - 1)
+ const maxNumber = 2 ** W
+ const shiftBy = BigInt(W)
+ for (let window = 0; window < windows; window++) {
+ const offset = window * windowSize
+ let wbits = Number(n & mask)
+ n >>= shiftBy
+ if (wbits > windowSize) {
+ wbits -= maxNumber
+ n += _1n
+ }
+ if (wbits === 0) {
+ let pr = precomputes[offset]
+ if (window % 2) pr = pr.negate()
+ f = f.add(pr)
+ } else {
+ let cached = precomputes[offset + Math.abs(wbits) - 1]
+ if (wbits < 0) cached = cached.negate()
+ p = p.add(cached)
+ }
+ }
+ return {p, f}
+ }
+ multiply(scalar, affinePoint) {
+ let n = normalizeScalar(scalar)
+ let point
+ let fake
+ if (USE_ENDOMORPHISM) {
+ const {k1neg, k1, k2neg, k2} = splitScalarEndo(n)
+ let {p: k1p, f: f1p} = this.wNAF(k1, affinePoint)
+ let {p: k2p, f: f2p} = this.wNAF(k2, affinePoint)
+ if (k1neg) k1p = k1p.negate()
+ if (k2neg) k2p = k2p.negate()
+ k2p = new JacobianPoint(mod(k2p.x * CURVE.beta), k2p.y, k2p.z)
+ point = k1p.add(k2p)
+ fake = f1p.add(f2p)
+ } else {
+ const {p, f} = this.wNAF(n, affinePoint)
+ point = p
+ fake = f
+ }
+ return JacobianPoint.normalizeZ([point, fake])[0]
+ }
+ toAffine(invZ = invert(this.z)) {
+ const {x, y, z} = this
+ const iz1 = invZ
+ const iz2 = mod(iz1 * iz1)
+ const iz3 = mod(iz2 * iz1)
+ const ax = mod(x * iz2)
+ const ay = mod(y * iz3)
+ const zz = mod(z * iz1)
+ if (zz !== _1n) throw new Error('invZ was invalid')
+ return new Point(ax, ay)
+ }
+ }
+ JacobianPoint.BASE = new JacobianPoint(CURVE.Gx, CURVE.Gy, _1n)
+ JacobianPoint.ZERO = new JacobianPoint(_0n, _1n, _0n)
+ const pointPrecomputes = new WeakMap()
+ class Point {
+ constructor(x, y) {
+ this.x = x
+ this.y = y
+ }
+ _setWindowSize(windowSize) {
+ this._WINDOW_SIZE = windowSize
+ pointPrecomputes.delete(this)
+ }
+ static fromCompressedHex(bytes) {
+ const isShort = bytes.length === 32
+ const x = bytesToNumber(isShort ? bytes : bytes.subarray(1))
+ if (!isValidFieldElement(x)) throw new Error('Point is not on curve')
+ const y2 = weistrass(x)
+ let y = sqrtMod(y2)
+ const isYOdd = (y & _1n) === _1n
+ if (isShort) {
+ if (isYOdd) y = mod(-y)
+ } else {
+ const isFirstByteOdd = (bytes[0] & 1) === 1
+ if (isFirstByteOdd !== isYOdd) y = mod(-y)
+ }
+ const point = new Point(x, y)
+ point.assertValidity()
+ return point
+ }
+ static fromUncompressedHex(bytes) {
+ const x = bytesToNumber(bytes.subarray(1, 33))
+ const y = bytesToNumber(bytes.subarray(33, 65))
+ const point = new Point(x, y)
+ point.assertValidity()
+ return point
+ }
+ static fromHex(hex) {
+ const bytes = ensureBytes(hex)
+ const len = bytes.length
+ const header = bytes[0]
+ if (len === 32 || (len === 33 && (header === 0x02 || header === 0x03))) {
+ return this.fromCompressedHex(bytes)
+ }
+ if (len === 65 && header === 0x04) return this.fromUncompressedHex(bytes)
+ throw new Error(
+ `Point.fromHex: received invalid point. Expected 32-33 compressed bytes or 65 uncompressed bytes, not ${len}`
+ )
+ }
+ static fromPrivateKey(privateKey) {
+ return Point.BASE.multiply(normalizePrivateKey(privateKey))
+ }
+ static fromSignature(msgHash, signature, recovery) {
+ msgHash = ensureBytes(msgHash)
+ const h = truncateHash(msgHash)
+ const {r, s} = normalizeSignature(signature)
+ if (recovery !== 0 && recovery !== 1) {
+ throw new Error('Cannot recover signature: invalid recovery bit')
+ }
+ const prefix = recovery & 1 ? '03' : '02'
+ const R = Point.fromHex(prefix + numTo32bStr(r))
+ const {n} = CURVE
+ const rinv = invert(r, n)
+ const u1 = mod(-h * rinv, n)
+ const u2 = mod(s * rinv, n)
+ const Q = Point.BASE.multiplyAndAddUnsafe(R, u1, u2)
+ if (!Q) throw new Error('Cannot recover signature: point at infinify')
+ Q.assertValidity()
+ return Q
+ }
+ toRawBytes(isCompressed = false) {
+ return hexToBytes(this.toHex(isCompressed))
+ }
+ toHex(isCompressed = false) {
+ const x = numTo32bStr(this.x)
+ if (isCompressed) {
+ const prefix = this.y & _1n ? '03' : '02'
+ return `${prefix}${x}`
+ } else {
+ return `04${x}${numTo32bStr(this.y)}`
+ }
+ }
+ toHexX() {
+ return this.toHex(true).slice(2)
+ }
+ toRawX() {
+ return this.toRawBytes(true).slice(1)
+ }
+ assertValidity() {
+ const msg = 'Point is not on elliptic curve'
+ const {x, y} = this
+ if (!isValidFieldElement(x) || !isValidFieldElement(y))
+ throw new Error(msg)
+ const left = mod(y * y)
+ const right = weistrass(x)
+ if (mod(left - right) !== _0n) throw new Error(msg)
+ }
+ equals(other) {
+ return this.x === other.x && this.y === other.y
+ }
+ negate() {
+ return new Point(this.x, mod(-this.y))
+ }
+ double() {
+ return JacobianPoint.fromAffine(this).double().toAffine()
+ }
+ add(other) {
+ return JacobianPoint.fromAffine(this)
+ .add(JacobianPoint.fromAffine(other))
+ .toAffine()
+ }
+ subtract(other) {
+ return this.add(other.negate())
+ }
+ multiply(scalar) {
+ return JacobianPoint.fromAffine(this).multiply(scalar, this).toAffine()
+ }
+ multiplyAndAddUnsafe(Q, a, b) {
+ const P = JacobianPoint.fromAffine(this)
+ const aP =
+ a === _0n || a === _1n || this !== Point.BASE
+ ? P.multiplyUnsafe(a)
+ : P.multiply(a)
+ const bQ = JacobianPoint.fromAffine(Q).multiplyUnsafe(b)
+ const sum = aP.add(bQ)
+ return sum.equals(JacobianPoint.ZERO) ? undefined : sum.toAffine()
+ }
+ }
+ Point.BASE = new Point(CURVE.Gx, CURVE.Gy)
+ Point.ZERO = new Point(_0n, _0n)
+ function sliceDER(s) {
+ return Number.parseInt(s[0], 16) >= 8 ? '00' + s : s
+ }
+ function parseDERInt(data) {
+ if (data.length < 2 || data[0] !== 0x02) {
+ throw new Error(`Invalid signature integer tag: ${bytesToHex(data)}`)
+ }
+ const len = data[1]
+ const res = data.subarray(2, len + 2)
+ if (!len || res.length !== len) {
+ throw new Error(`Invalid signature integer: wrong length`)
+ }
+ if (res[0] === 0x00 && res[1] <= 0x7f) {
+ throw new Error('Invalid signature integer: trailing length')
+ }
+ return {data: bytesToNumber(res), left: data.subarray(len + 2)}
+ }
+ function parseDERSignature(data) {
+ if (data.length < 2 || data[0] != 0x30) {
+ throw new Error(`Invalid signature tag: ${bytesToHex(data)}`)
+ }
+ if (data[1] !== data.length - 2) {
+ throw new Error('Invalid signature: incorrect length')
+ }
+ const {data: r, left: sBytes} = parseDERInt(data.subarray(2))
+ const {data: s, left: rBytesLeft} = parseDERInt(sBytes)
+ if (rBytesLeft.length) {
+ throw new Error(
+ `Invalid signature: left bytes after parsing: ${bytesToHex(rBytesLeft)}`
+ )
+ }
+ return {r, s}
+ }
+ class Signature {
+ constructor(r, s) {
+ this.r = r
+ this.s = s
+ this.assertValidity()
+ }
+ static fromCompact(hex) {
+ const arr = isUint8a(hex)
+ const name = 'Signature.fromCompact'
+ if (typeof hex !== 'string' && !arr)
+ throw new TypeError(`${name}: Expected string or Uint8Array`)
+ const str = arr ? bytesToHex(hex) : hex
+ if (str.length !== 128) throw new Error(`${name}: Expected 64-byte hex`)
+ return new Signature(
+ hexToNumber(str.slice(0, 64)),
+ hexToNumber(str.slice(64, 128))
+ )
+ }
+ static fromDER(hex) {
+ const arr = isUint8a(hex)
+ if (typeof hex !== 'string' && !arr)
+ throw new TypeError(`Signature.fromDER: Expected string or Uint8Array`)
+ const {r, s} = parseDERSignature(arr ? hex : hexToBytes(hex))
+ return new Signature(r, s)
+ }
+ static fromHex(hex) {
+ return this.fromDER(hex)
+ }
+ assertValidity() {
+ const {r, s} = this
+ if (!isWithinCurveOrder(r))
+ throw new Error('Invalid Signature: r must be 0 < r < n')
+ if (!isWithinCurveOrder(s))
+ throw new Error('Invalid Signature: s must be 0 < s < n')
+ }
+ hasHighS() {
+ const HALF = CURVE.n >> _1n
+ return this.s > HALF
+ }
+ normalizeS() {
+ return this.hasHighS() ? new Signature(this.r, CURVE.n - this.s) : this
+ }
+ toDERRawBytes(isCompressed = false) {
+ return hexToBytes(this.toDERHex(isCompressed))
+ }
+ toDERHex(isCompressed = false) {
+ const sHex = sliceDER(numberToHexUnpadded(this.s))
+ if (isCompressed) return sHex
+ const rHex = sliceDER(numberToHexUnpadded(this.r))
+ const rLen = numberToHexUnpadded(rHex.length / 2)
+ const sLen = numberToHexUnpadded(sHex.length / 2)
+ const length = numberToHexUnpadded(rHex.length / 2 + sHex.length / 2 + 4)
+ return `30${length}02${rLen}${rHex}02${sLen}${sHex}`
+ }
+ toRawBytes() {
+ return this.toDERRawBytes()
+ }
+ toHex() {
+ return this.toDERHex()
+ }
+ toCompactRawBytes() {
+ return hexToBytes(this.toCompactHex())
+ }
+ toCompactHex() {
+ return numTo32bStr(this.r) + numTo32bStr(this.s)
+ }
+ }
+ function concatBytes(...arrays) {
+ if (!arrays.every(isUint8a)) throw new Error('Uint8Array list expected')
+ if (arrays.length === 1) return arrays[0]
+ const length = arrays.reduce((a, arr) => a + arr.length, 0)
+ const result = new Uint8Array(length)
+ for (let i = 0, pad = 0; i < arrays.length; i++) {
+ const arr = arrays[i]
+ result.set(arr, pad)
+ pad += arr.length
+ }
+ return result
+ }
+ function isUint8a(bytes) {
+ return bytes instanceof Uint8Array
+ }
+ const hexes = Array.from({length: 256}, (v, i) =>
+ i.toString(16).padStart(2, '0')
+ )
+ function bytesToHex(uint8a) {
+ if (!(uint8a instanceof Uint8Array)) throw new Error('Expected Uint8Array')
+ let hex = ''
+ for (let i = 0; i < uint8a.length; i++) {
+ hex += hexes[uint8a[i]]
+ }
+ return hex
+ }
+ function numTo32bStr(num) {
+ if (num > POW_2_256) throw new Error('Expected number < 2^256')
+ return num.toString(16).padStart(64, '0')
+ }
+ function numTo32b(num) {
+ return hexToBytes(numTo32bStr(num))
+ }
+ function numberToHexUnpadded(num) {
+ const hex = num.toString(16)
+ return hex.length & 1 ? `0${hex}` : hex
+ }
+ function hexToNumber(hex) {
+ if (typeof hex !== 'string') {
+ throw new TypeError('hexToNumber: expected string, got ' + typeof hex)
+ }
+ return BigInt(`0x${hex}`)
+ }
+ function hexToBytes(hex) {
+ if (typeof hex !== 'string') {
+ throw new TypeError('hexToBytes: expected string, got ' + typeof hex)
+ }
+ if (hex.length % 2)
+ throw new Error('hexToBytes: received invalid unpadded hex' + hex.length)
+ const array = new Uint8Array(hex.length / 2)
+ for (let i = 0; i < array.length; i++) {
+ const j = i * 2
+ const hexByte = hex.slice(j, j + 2)
+ const byte = Number.parseInt(hexByte, 16)
+ if (Number.isNaN(byte) || byte < 0)
+ throw new Error('Invalid byte sequence')
+ array[i] = byte
+ }
+ return array
+ }
+ function bytesToNumber(bytes) {
+ return hexToNumber(bytesToHex(bytes))
+ }
+ function ensureBytes(hex) {
+ return hex instanceof Uint8Array ? Uint8Array.from(hex) : hexToBytes(hex)
+ }
+ function normalizeScalar(num) {
+ if (typeof num === 'number' && Number.isSafeInteger(num) && num > 0)
+ return BigInt(num)
+ if (typeof num === 'bigint' && isWithinCurveOrder(num)) return num
+ throw new TypeError('Expected valid private scalar: 0 < scalar < curve.n')
+ }
+ function mod(a, b = CURVE.P) {
+ const result = a % b
+ return result >= _0n ? result : b + result
+ }
+ function pow2(x, power) {
+ const {P} = CURVE
+ let res = x
+ while (power-- > _0n) {
+ res *= res
+ res %= P
+ }
+ return res
+ }
+ function sqrtMod(x) {
+ const {P} = CURVE
+ const _6n = BigInt(6)
+ const _11n = BigInt(11)
+ const _22n = BigInt(22)
+ const _23n = BigInt(23)
+ const _44n = BigInt(44)
+ const _88n = BigInt(88)
+ const b2 = (x * x * x) % P
+ const b3 = (b2 * b2 * x) % P
+ const b6 = (pow2(b3, _3n) * b3) % P
+ const b9 = (pow2(b6, _3n) * b3) % P
+ const b11 = (pow2(b9, _2n) * b2) % P
+ const b22 = (pow2(b11, _11n) * b11) % P
+ const b44 = (pow2(b22, _22n) * b22) % P
+ const b88 = (pow2(b44, _44n) * b44) % P
+ const b176 = (pow2(b88, _88n) * b88) % P
+ const b220 = (pow2(b176, _44n) * b44) % P
+ const b223 = (pow2(b220, _3n) * b3) % P
+ const t1 = (pow2(b223, _23n) * b22) % P
+ const t2 = (pow2(t1, _6n) * b2) % P
+ return pow2(t2, _2n)
+ }
+ function invert(number, modulo = CURVE.P) {
+ if (number === _0n || modulo <= _0n) {
+ throw new Error(
+ `invert: expected positive integers, got n=${number} mod=${modulo}`
+ )
+ }
+ let a = mod(number, modulo)
+ let b = modulo
+ let x = _0n,
+ u = _1n
+ while (a !== _0n) {
+ const q = b / a
+ const r = b % a
+ const m = x - u * q
+ ;(b = a), (a = r), (x = u), (u = m)
+ }
+ const gcd = b
+ if (gcd !== _1n) throw new Error('invert: does not exist')
+ return mod(x, modulo)
+ }
+ function invertBatch(nums, p = CURVE.P) {
+ const scratch = new Array(nums.length)
+ const lastMultiplied = nums.reduce((acc, num, i) => {
+ if (num === _0n) return acc
+ scratch[i] = acc
+ return mod(acc * num, p)
+ }, _1n)
+ const inverted = invert(lastMultiplied, p)
+ nums.reduceRight((acc, num, i) => {
+ if (num === _0n) return acc
+ scratch[i] = mod(acc * scratch[i], p)
+ return mod(acc * num, p)
+ }, inverted)
+ return scratch
+ }
+ const divNearest = (a, b) => (a + b / _2n) / b
+ const POW_2_128 = _2n ** BigInt(128)
+ function splitScalarEndo(k) {
+ const {n} = CURVE
+ const a1 = BigInt('0x3086d221a7d46bcde86c90e49284eb15')
+ const b1 = -_1n * BigInt('0xe4437ed6010e88286f547fa90abfe4c3')
+ const a2 = BigInt('0x114ca50f7a8e2f3f657c1108d9d44cfd8')
+ const b2 = a1
+ const c1 = divNearest(b2 * k, n)
+ const c2 = divNearest(-b1 * k, n)
+ let k1 = mod(k - c1 * a1 - c2 * a2, n)
+ let k2 = mod(-c1 * b1 - c2 * b2, n)
+ const k1neg = k1 > POW_2_128
+ const k2neg = k2 > POW_2_128
+ if (k1neg) k1 = n - k1
+ if (k2neg) k2 = n - k2
+ if (k1 > POW_2_128 || k2 > POW_2_128) {
+ throw new Error('splitScalarEndo: Endomorphism failed, k=' + k)
+ }
+ return {k1neg, k1, k2neg, k2}
+ }
+ function truncateHash(hash) {
+ const {n} = CURVE
+ const byteLength = hash.length
+ const delta = byteLength * 8 - 256
+ let h = bytesToNumber(hash)
+ if (delta > 0) h = h >> BigInt(delta)
+ if (h >= n) h -= n
+ return h
+ }
+ class HmacDrbg {
+ constructor() {
+ this.v = new Uint8Array(32).fill(1)
+ this.k = new Uint8Array(32).fill(0)
+ this.counter = 0
+ }
+ hmac(...values) {
+ return utils.hmacSha256(this.k, ...values)
+ }
+ hmacSync(...values) {
+ if (typeof utils.hmacSha256Sync !== 'function')
+ throw new Error('utils.hmacSha256Sync is undefined, you need to set it')
+ const res = utils.hmacSha256Sync(this.k, ...values)
+ if (res instanceof Promise)
+ throw new Error('To use sync sign(), ensure utils.hmacSha256 is sync')
+ return res
+ }
+ incr() {
+ if (this.counter >= 1000) {
+ throw new Error('Tried 1,000 k values for sign(), all were invalid')
+ }
+ this.counter += 1
+ }
+ async reseed(seed = new Uint8Array()) {
+ this.k = await this.hmac(this.v, Uint8Array.from([0x00]), seed)
+ this.v = await this.hmac(this.v)
+ if (seed.length === 0) return
+ this.k = await this.hmac(this.v, Uint8Array.from([0x01]), seed)
+ this.v = await this.hmac(this.v)
+ }
+ reseedSync(seed = new Uint8Array()) {
+ this.k = this.hmacSync(this.v, Uint8Array.from([0x00]), seed)
+ this.v = this.hmacSync(this.v)
+ if (seed.length === 0) return
+ this.k = this.hmacSync(this.v, Uint8Array.from([0x01]), seed)
+ this.v = this.hmacSync(this.v)
+ }
+ async generate() {
+ this.incr()
+ this.v = await this.hmac(this.v)
+ return this.v
+ }
+ generateSync() {
+ this.incr()
+ this.v = this.hmacSync(this.v)
+ return this.v
+ }
+ }
+ function isWithinCurveOrder(num) {
+ return _0n < num && num < CURVE.n
+ }
+ function isValidFieldElement(num) {
+ return _0n < num && num < CURVE.P
+ }
+ function kmdToSig(kBytes, m, d) {
+ const k = bytesToNumber(kBytes)
+ if (!isWithinCurveOrder(k)) return
+ const {n} = CURVE
+ const q = Point.BASE.multiply(k)
+ const r = mod(q.x, n)
+ if (r === _0n) return
+ const s = mod(invert(k, n) * mod(m + d * r, n), n)
+ if (s === _0n) return
+ const sig = new Signature(r, s)
+ const recovery = (q.x === sig.r ? 0 : 2) | Number(q.y & _1n)
+ return {sig, recovery}
+ }
+ function normalizePrivateKey(key) {
+ let num
+ if (typeof key === 'bigint') {
+ num = key
+ } else if (
+ typeof key === 'number' &&
+ Number.isSafeInteger(key) &&
+ key > 0
+ ) {
+ num = BigInt(key)
+ } else if (typeof key === 'string') {
+ if (key.length !== 64) throw new Error('Expected 32 bytes of private key')
+ num = hexToNumber(key)
+ } else if (isUint8a(key)) {
+ if (key.length !== 32) throw new Error('Expected 32 bytes of private key')
+ num = bytesToNumber(key)
+ } else {
+ throw new TypeError('Expected valid private key')
+ }
+ if (!isWithinCurveOrder(num))
+ throw new Error('Expected private key: 0 < key < n')
+ return num
+ }
+ function normalizePublicKey(publicKey) {
+ if (publicKey instanceof Point) {
+ publicKey.assertValidity()
+ return publicKey
+ } else {
+ return Point.fromHex(publicKey)
+ }
+ }
+ function normalizeSignature(signature) {
+ if (signature instanceof Signature) {
+ signature.assertValidity()
+ return signature
+ }
+ try {
+ return Signature.fromDER(signature)
+ } catch (error) {
+ return Signature.fromCompact(signature)
+ }
+ }
+ function getPublicKey(privateKey, isCompressed = false) {
+ return Point.fromPrivateKey(privateKey).toRawBytes(isCompressed)
+ }
+ function recoverPublicKey(
+ msgHash,
+ signature,
+ recovery,
+ isCompressed = false
+ ) {
+ return Point.fromSignature(msgHash, signature, recovery).toRawBytes(
+ isCompressed
+ )
+ }
+ function isPub(item) {
+ const arr = isUint8a(item)
+ const str = typeof item === 'string'
+ const len = (arr || str) && item.length
+ if (arr) return len === 33 || len === 65
+ if (str) return len === 66 || len === 130
+ if (item instanceof Point) return true
+ return false
+ }
+ function getSharedSecret(privateA, publicB, isCompressed = false) {
+ if (isPub(privateA))
+ throw new TypeError('getSharedSecret: first arg must be private key')
+ if (!isPub(publicB))
+ throw new TypeError('getSharedSecret: second arg must be public key')
+ const b = normalizePublicKey(publicB)
+ b.assertValidity()
+ return b.multiply(normalizePrivateKey(privateA)).toRawBytes(isCompressed)
+ }
+ function bits2int(bytes) {
+ const slice = bytes.length > 32 ? bytes.slice(0, 32) : bytes
+ return bytesToNumber(slice)
+ }
+ function bits2octets(bytes) {
+ const z1 = bits2int(bytes)
+ const z2 = mod(z1, CURVE.n)
+ return int2octets(z2 < _0n ? z1 : z2)
+ }
+ function int2octets(num) {
+ if (typeof num !== 'bigint') throw new Error('Expected bigint')
+ const hex = numTo32bStr(num)
+ return hexToBytes(hex)
+ }
+ function initSigArgs(msgHash, privateKey, extraEntropy) {
+ if (msgHash == null)
+ throw new Error(`sign: expected valid message hash, not "${msgHash}"`)
+ const h1 = ensureBytes(msgHash)
+ const d = normalizePrivateKey(privateKey)
+ const seedArgs = [int2octets(d), bits2octets(h1)]
+ if (extraEntropy != null) {
+ if (extraEntropy === true) extraEntropy = utils.randomBytes(32)
+ const e = ensureBytes(extraEntropy)
+ if (e.length !== 32)
+ throw new Error('sign: Expected 32 bytes of extra data')
+ seedArgs.push(e)
+ }
+ const seed = concatBytes(...seedArgs)
+ const m = bits2int(h1)
+ return {seed, m, d}
+ }
+ function finalizeSig(recSig, opts) {
+ let {sig, recovery} = recSig
+ const {canonical, der, recovered} = Object.assign(
+ {canonical: true, der: true},
+ opts
+ )
+ if (canonical && sig.hasHighS()) {
+ sig = sig.normalizeS()
+ recovery ^= 1
+ }
+ const hashed = der ? sig.toDERRawBytes() : sig.toCompactRawBytes()
+ return recovered ? [hashed, recovery] : hashed
+ }
+ async function sign(msgHash, privKey, opts = {}) {
+ const {seed, m, d} = initSigArgs(msgHash, privKey, opts.extraEntropy)
+ let sig
+ const drbg = new HmacDrbg()
+ await drbg.reseed(seed)
+ while (!(sig = kmdToSig(await drbg.generate(), m, d))) await drbg.reseed()
+ return finalizeSig(sig, opts)
+ }
+ function signSync(msgHash, privKey, opts = {}) {
+ const {seed, m, d} = initSigArgs(msgHash, privKey, opts.extraEntropy)
+ let sig
+ const drbg = new HmacDrbg()
+ drbg.reseedSync(seed)
+ while (!(sig = kmdToSig(drbg.generateSync(), m, d))) drbg.reseedSync()
+ return finalizeSig(sig, opts)
+ }
+ const vopts = {strict: true}
+ function verify(signature, msgHash, publicKey, opts = vopts) {
+ let sig
+ try {
+ sig = normalizeSignature(signature)
+ msgHash = ensureBytes(msgHash)
+ } catch (error) {
+ return false
+ }
+ const {r, s} = sig
+ if (opts.strict && sig.hasHighS()) return false
+ const h = truncateHash(msgHash)
+ let P
+ try {
+ P = normalizePublicKey(publicKey)
+ } catch (error) {
+ return false
+ }
+ const {n} = CURVE
+ const sinv = invert(s, n)
+ const u1 = mod(h * sinv, n)
+ const u2 = mod(r * sinv, n)
+ const R = Point.BASE.multiplyAndAddUnsafe(P, u1, u2)
+ if (!R) return false
+ const v = mod(R.x, n)
+ return v === r
+ }
+ function finalizeSchnorrChallenge(ch) {
+ return mod(bytesToNumber(ch), CURVE.n)
+ }
+ function hasEvenY(point) {
+ return (point.y & _1n) === _0n
+ }
+ class SchnorrSignature {
+ constructor(r, s) {
+ this.r = r
+ this.s = s
+ this.assertValidity()
+ }
+ static fromHex(hex) {
+ const bytes = ensureBytes(hex)
+ if (bytes.length !== 64)
+ throw new TypeError(
+ `SchnorrSignature.fromHex: expected 64 bytes, not ${bytes.length}`
+ )
+ const r = bytesToNumber(bytes.subarray(0, 32))
+ const s = bytesToNumber(bytes.subarray(32, 64))
+ return new SchnorrSignature(r, s)
+ }
+ assertValidity() {
+ const {r, s} = this
+ if (!isValidFieldElement(r) || !isWithinCurveOrder(s))
+ throw new Error('Invalid signature')
+ }
+ toHex() {
+ return numTo32bStr(this.r) + numTo32bStr(this.s)
+ }
+ toRawBytes() {
+ return hexToBytes(this.toHex())
+ }
+ }
+ function schnorrGetPublicKey(privateKey) {
+ return Point.fromPrivateKey(privateKey).toRawX()
+ }
+ function initSchnorrSigArgs(message, privateKey, auxRand) {
+ if (message == null)
+ throw new TypeError(`sign: Expected valid message, not "${message}"`)
+ const m = ensureBytes(message)
+ const d0 = normalizePrivateKey(privateKey)
+ const rand = ensureBytes(auxRand)
+ if (rand.length !== 32)
+ throw new TypeError('sign: Expected 32 bytes of aux randomness')
+ const P = Point.fromPrivateKey(d0)
+ const px = P.toRawX()
+ const d = hasEvenY(P) ? d0 : CURVE.n - d0
+ return {m, P, px, d, rand}
+ }
+ function initSchnorrNonce(d, t0h) {
+ return numTo32b(d ^ bytesToNumber(t0h))
+ }
+ function finalizeSchnorrNonce(k0h) {
+ const k0 = mod(bytesToNumber(k0h), CURVE.n)
+ if (k0 === _0n)
+ throw new Error('sign: Creation of signature failed. k is zero')
+ const R = Point.fromPrivateKey(k0)
+ const rx = R.toRawX()
+ const k = hasEvenY(R) ? k0 : CURVE.n - k0
+ return {R, rx, k}
+ }
+ function finalizeSchnorrSig(R, k, e, d) {
+ return new SchnorrSignature(R.x, mod(k + e * d, CURVE.n)).toRawBytes()
+ }
+ async function schnorrSign(
+ message,
+ privateKey,
+ auxRand = utils.randomBytes()
+ ) {
+ const {m, px, d, rand} = initSchnorrSigArgs(message, privateKey, auxRand)
+ const t = initSchnorrNonce(d, await utils.taggedHash(TAGS.aux, rand))
+ const {R, rx, k} = finalizeSchnorrNonce(
+ await utils.taggedHash(TAGS.nonce, t, px, m)
+ )
+ const e = finalizeSchnorrChallenge(
+ await utils.taggedHash(TAGS.challenge, rx, px, m)
+ )
+ const sig = finalizeSchnorrSig(R, k, e, d)
+ const isValid = await schnorrVerify(sig, m, px)
+ if (!isValid) throw new Error('sign: Invalid signature produced')
+ return sig
+ }
+ function schnorrSignSync(message, privateKey, auxRand = utils.randomBytes()) {
+ const {m, px, d, rand} = initSchnorrSigArgs(message, privateKey, auxRand)
+ const t = initSchnorrNonce(d, utils.taggedHashSync(TAGS.aux, rand))
+ const {R, rx, k} = finalizeSchnorrNonce(
+ utils.taggedHashSync(TAGS.nonce, t, px, m)
+ )
+ const e = finalizeSchnorrChallenge(
+ utils.taggedHashSync(TAGS.challenge, rx, px, m)
+ )
+ const sig = finalizeSchnorrSig(R, k, e, d)
+ const isValid = schnorrVerifySync(sig, m, px)
+ if (!isValid) throw new Error('sign: Invalid signature produced')
+ return sig
+ }
+ function initSchnorrVerify(signature, message, publicKey) {
+ const raw = signature instanceof SchnorrSignature
+ const sig = raw ? signature : SchnorrSignature.fromHex(signature)
+ if (raw) sig.assertValidity()
+ return {
+ ...sig,
+ m: ensureBytes(message),
+ P: normalizePublicKey(publicKey)
+ }
+ }
+ function finalizeSchnorrVerify(r, P, s, e) {
+ const R = Point.BASE.multiplyAndAddUnsafe(
+ P,
+ normalizePrivateKey(s),
+ mod(-e, CURVE.n)
+ )
+ if (!R || !hasEvenY(R) || R.x !== r) return false
+ return true
+ }
+ async function schnorrVerify(signature, message, publicKey) {
+ try {
+ const {r, s, m, P} = initSchnorrVerify(signature, message, publicKey)
+ const e = finalizeSchnorrChallenge(
+ await utils.taggedHash(TAGS.challenge, numTo32b(r), P.toRawX(), m)
+ )
+ return finalizeSchnorrVerify(r, P, s, e)
+ } catch (error) {
+ return false
+ }
+ }
+ function schnorrVerifySync(signature, message, publicKey) {
+ try {
+ const {r, s, m, P} = initSchnorrVerify(signature, message, publicKey)
+ const e = finalizeSchnorrChallenge(
+ utils.taggedHashSync(TAGS.challenge, numTo32b(r), P.toRawX(), m)
+ )
+ return finalizeSchnorrVerify(r, P, s, e)
+ } catch (error) {
+ return false
+ }
+ }
+ const schnorr = {
+ Signature: SchnorrSignature,
+ getPublicKey: schnorrGetPublicKey,
+ sign: schnorrSign,
+ verify: schnorrVerify,
+ signSync: schnorrSignSync,
+ verifySync: schnorrVerifySync
+ }
+ Point.BASE._setWindowSize(8)
+ const crypto = {
+ node: nodeCrypto,
+ web: typeof self === 'object' && 'crypto' in self ? self.crypto : undefined
+ }
+ const TAGS = {
+ challenge: 'BIP0340/challenge',
+ aux: 'BIP0340/aux',
+ nonce: 'BIP0340/nonce'
+ }
+ const TAGGED_HASH_PREFIXES = {}
+ const utils = {
+ isValidPrivateKey(privateKey) {
+ try {
+ normalizePrivateKey(privateKey)
+ return true
+ } catch (error) {
+ return false
+ }
+ },
+ privateAdd: (privateKey, tweak) => {
+ const p = normalizePrivateKey(privateKey)
+ const t = normalizePrivateKey(tweak)
+ return numTo32b(mod(p + t, CURVE.n))
+ },
+ privateNegate: privateKey => {
+ const p = normalizePrivateKey(privateKey)
+ return numTo32b(CURVE.n - p)
+ },
+ pointAddScalar: (p, tweak, isCompressed) => {
+ const P = Point.fromHex(p)
+ const t = normalizePrivateKey(tweak)
+ const Q = Point.BASE.multiplyAndAddUnsafe(P, t, _1n)
+ if (!Q) throw new Error('Tweaked point at infinity')
+ return Q.toRawBytes(isCompressed)
+ },
+ pointMultiply: (p, tweak, isCompressed) => {
+ const P = Point.fromHex(p)
+ const t = bytesToNumber(ensureBytes(tweak))
+ return P.multiply(t).toRawBytes(isCompressed)
+ },
+ hashToPrivateKey: hash => {
+ hash = ensureBytes(hash)
+ if (hash.length < 40 || hash.length > 1024)
+ throw new Error('Expected 40-1024 bytes of private key as per FIPS 186')
+ const num = mod(bytesToNumber(hash), CURVE.n - _1n) + _1n
+ return numTo32b(num)
+ },
+ randomBytes: (bytesLength = 32) => {
+ if (crypto.web) {
+ return crypto.web.getRandomValues(new Uint8Array(bytesLength))
+ } else if (crypto.node) {
+ const {randomBytes} = crypto.node
+ return Uint8Array.from(randomBytes(bytesLength))
+ } else {
+ throw new Error("The environment doesn't have randomBytes function")
+ }
+ },
+ randomPrivateKey: () => {
+ return utils.hashToPrivateKey(utils.randomBytes(40))
+ },
+ bytesToHex,
+ hexToBytes,
+ concatBytes,
+ mod,
+ invert,
+ sha256: async (...messages) => {
+ console.log('### sha256 messages', messages)
+ if (crypto.web) {
+ const buffer = await crypto.web.subtle.digest(
+ 'SHA-256',
+ concatBytes(...messages)
+ )
+ return new Uint8Array(buffer)
+ } else if (crypto.node) {
+ const {createHash} = crypto.node
+ const hash = createHash('sha256')
+ messages.forEach(m => hash.update(m))
+ return Uint8Array.from(hash.digest())
+ } else {
+ throw new Error("The environment doesn't have sha256 function")
+ }
+ },
+ hmacSha256: async (key, ...messages) => {
+ if (crypto.web) {
+ const ckey = await crypto.web.subtle.importKey(
+ 'raw',
+ key,
+ {name: 'HMAC', hash: {name: 'SHA-256'}},
+ false,
+ ['sign']
+ )
+ const message = concatBytes(...messages)
+ const buffer = await crypto.web.subtle.sign('HMAC', ckey, message)
+ return new Uint8Array(buffer)
+ } else if (crypto.node) {
+ const {createHmac} = crypto.node
+ const hash = createHmac('sha256', key)
+ messages.forEach(m => hash.update(m))
+ return Uint8Array.from(hash.digest())
+ } else {
+ throw new Error("The environment doesn't have hmac-sha256 function")
+ }
+ },
+ sha256Sync: undefined,
+ hmacSha256Sync: undefined,
+ taggedHash: async (tag, ...messages) => {
+ let tagP = TAGGED_HASH_PREFIXES[tag]
+ if (tagP === undefined) {
+ const tagH = await utils.sha256(
+ Uint8Array.from(tag, c => c.charCodeAt(0))
+ )
+ tagP = concatBytes(tagH, tagH)
+ TAGGED_HASH_PREFIXES[tag] = tagP
+ }
+ return utils.sha256(tagP, ...messages)
+ },
+ taggedHashSync: (tag, ...messages) => {
+ if (typeof utils.sha256Sync !== 'function')
+ throw new Error('utils.sha256Sync is undefined, you need to set it')
+ let tagP = TAGGED_HASH_PREFIXES[tag]
+ if (tagP === undefined) {
+ const tagH = utils.sha256Sync(
+ Uint8Array.from(tag, c => c.charCodeAt(0))
+ )
+ tagP = concatBytes(tagH, tagH)
+ TAGGED_HASH_PREFIXES[tag] = tagP
+ }
+ return utils.sha256Sync(tagP, ...messages)
+ },
+ precompute(windowSize = 8, point = Point.BASE) {
+ const cached = point === Point.BASE ? point : new Point(point.x, point.y)
+ cached._setWindowSize(windowSize)
+ cached.multiply(_3n)
+ return cached
+ }
+ }
+
+ exports.CURVE = CURVE
+ exports.Point = Point
+ exports.Signature = Signature
+ exports.getPublicKey = getPublicKey
+ exports.getSharedSecret = getSharedSecret
+ exports.recoverPublicKey = recoverPublicKey
+ exports.schnorr = schnorr
+ exports.sign = sign
+ exports.signSync = signSync
+ exports.utils = utils
+ exports.verify = verify
+
+ Object.defineProperty(exports, '__esModule', {value: true})
+})
diff --git a/lnbits/extensions/cashu/static/js/utils.js b/lnbits/extensions/cashu/static/js/utils.js
new file mode 100644
index 00000000..cf852b58
--- /dev/null
+++ b/lnbits/extensions/cashu/static/js/utils.js
@@ -0,0 +1,23 @@
+function splitAmount(value) {
+ const chunks = []
+ for (let i = 0; i < 32; i++) {
+ const mask = 1 << i
+ if ((value & mask) !== 0) chunks.push(Math.pow(2, i))
+ }
+ return chunks
+}
+
+function bytesToNumber(bytes) {
+ return hexToNumber(nobleSecp256k1.utils.bytesToHex(bytes))
+}
+
+function bigIntStringify(key, value) {
+ return typeof value === 'bigint' ? value.toString() : value
+}
+
+function hexToNumber(hex) {
+ if (typeof hex !== 'string') {
+ throw new TypeError('hexToNumber: expected string, got ' + typeof hex)
+ }
+ return BigInt(`0x${hex}`)
+}
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 8a25a8f7..eed6cb1d 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -81,6 +81,14 @@ page_container %}
Pending
+
+ Recheck
+
bt.hash = hash)
+ if (!tokens) {
+ console.error('####### no token for hash', hash)
+ return
+ }
+ const promises = await this.fetchPromisesFromMint(hash, tokens.blindedMessages)
+ if (promises && promises.length){
+ tokens.promises = promises
+ }
+ },
+
+ fetchPromisesFromMint: async function (hash, blindedMessages) {
+ console.log('### fetchPromisesFromMint', hash, blindedMessages)
+ try {
+ const {data} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/cashu/${this.mintId}/mint?payment_hash=${hash}`,
+ '',
+ {
+ blinded_messages: blindedMessages
+ }
+ )
+ console.log('### fetchPromisesFromMint data', data)
+ return data
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ }
+
+ },
+
+ requestTokens: async function (amounts, paymentHash) {
+ const newTokens = await this.buildTokens(amounts, paymentHash)
+ this.tokens.push(newTokens)
+ localStorage.setItem(
+ 'cashu.tokens',
+ JSON.stringify(this.tokens, bigIntStringify)
+ )
+ console.log('### this.tokens', this.tokens)
+ await this.fetchPromisesFromMint(paymentHash, newTokens.newTokens)
+ },
+
+ buildTokens: async function (amounts, paymentHash) {
+ const blindedMessages = []
+ const secrets = []
+ const randomBlindingFactors = []
+ for (let i = 0; i < amounts.length; i++) {
+ // const secret = bytesToNumber(nobleSecp256k1.utils.randomBytes(32)) + ''
+ const secret = nobleSecp256k1.utils.randomBytes(32)
+ secrets.push(secret)
+ const {B_, randomBlindingFactor} = await step1Bob(secret)
+ randomBlindingFactors.push(randomBlindingFactor)
+ blindedMessages.push({amount: amounts[i], B_: B_})
+ }
+
+ const newTokens = {
+ hash: paymentHash,
+ blindedMessages,
+ randomBlindingFactors,
+ secrets,
+ status: 'pending'
+ }
+ return newTokens
+ // console.log('### payloadsJson.payloads', payloadsJson.payloads)
+ // const promises = await mintApi.mint(payloadsJson.payloads, paymentHash)
+ // if (promises.error) {
+ // throw new Error(promises.error)
+ // }
+ // return this._constructProofs(promises, randomBlindingFactors, secrets)
+ },
+
+ _constructProofs: function (promises, randomBlindingFactors, secrets) {
+ return promises.map((p, i) => {
+ const C_ = nobleSecp256k1.Point.fromHex(p['C_'])
+ const A = this.keys[p.amount]
+ const C = step3Bob(
+ C_,
+ randomBlindingFactors[i],
+ nobleSecp256k1.Point.fromHex(A)
+ ).toHex()
+ return {
+ amount: p.amount,
+ C: {C, secret: secrets[i]}
+ }
+ })
}
},
watch: {
@@ -910,10 +1032,16 @@ page_container %}
this.tokenBuys = JSON.parse(
localStorage.getItem('cashu.tokenBuys') || '[]'
)
+ this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
console.log('#### this.tokenBuys', this.tokenBuys)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
+
+ this.checkXXXXXX()
}
})
+
+
+
{% endblock %}
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index e2410909..2f4b5ae4 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -285,10 +285,10 @@ async def mint_coins(
status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
# todo: revert to: status.paid != True:
- # if status.paid != True:
- # raise HTTPException(
- # status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
- # )
+ if status.paid != True:
+ raise HTTPException(
+ status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
+ )
try:
await update_lightning_invoice(cashu_id, payment_hash, True)
From 50bc631f1685688f101d8a9a8e5781f41871bd66 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 13:06:11 +0300
Subject: [PATCH 284/696] chore: do not loose stuff
---
lnbits/extensions/cashu/static/js/dhke.js | 2 +-
lnbits/extensions/cashu/templates/cashu/wallet.html | 10 ++++++++--
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/static/js/dhke.js b/lnbits/extensions/cashu/static/js/dhke.js
index 73b1bcb8..959ba6f1 100644
--- a/lnbits/extensions/cashu/static/js/dhke.js
+++ b/lnbits/extensions/cashu/static/js/dhke.js
@@ -22,7 +22,7 @@ async function step1Bob(secretMessage) {
)
const P = nobleSecp256k1.Point.fromPrivateKey(randomBlindingFactor)
const B_ = Y.add(P)
- return {B_, randomBlindingFactor}
+ return {B_: B_.toHex(true), randomBlindingFactor}
}
function step3Bob(C_, r, A) {
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index eed6cb1d..e59be049 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -78,7 +78,7 @@ page_container %}
-
+
Pending
bt.hash = hash)
+ const tokens = this.tokens.find(bt => bt.hash = hash)
if (!tokens) {
console.error('####### no token for hash', hash)
return
From bdcae96114975e5bd7ac309a96a587b1eb0b6699 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 13:45:53 +0300
Subject: [PATCH 285/696] refactor: renamings
---
.../cashu/static/js/noble-secp256k1.js | 1 -
.../cashu/templates/cashu/wallet.html | 73 +++++++++----------
2 files changed, 34 insertions(+), 40 deletions(-)
diff --git a/lnbits/extensions/cashu/static/js/noble-secp256k1.js b/lnbits/extensions/cashu/static/js/noble-secp256k1.js
index 3b24b884..6a6bd441 100644
--- a/lnbits/extensions/cashu/static/js/noble-secp256k1.js
+++ b/lnbits/extensions/cashu/static/js/noble-secp256k1.js
@@ -1092,7 +1092,6 @@
mod,
invert,
sha256: async (...messages) => {
- console.log('### sha256 messages', messages)
if (crypto.web) {
const buffer = await crypto.web.subtle.digest(
'SHA-256',
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index e59be049..ee052fc9 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -67,7 +67,7 @@ page_container %}
-
+
{% raw %}
-
+
Copy invoice
Date: Mon, 10 Oct 2022 13:54:52 +0300
Subject: [PATCH 286/696] refactor: rename
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index ee052fc9..6933acfd 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -85,7 +85,7 @@ page_container %}
size="lg"
color="secondary"
class="q-mr-md cursor-pointer"
- @click="recheckToken(props.row.hash)"
+ @click="recheckBuyOrder(props.row.hash)"
>
Recheck
@@ -893,8 +893,8 @@ page_container %}
}
},
- recheckToken: async function(hash) {
- console.log('### recheckToken', hash)
+ recheckBuyOrder: async function(hash) {
+ console.log('### recheckBuyOrder', hash)
const tokens = this.tokens.find(bt => bt.hash = hash)
if (!tokens) {
console.error('####### no token for hash', hash)
From 3e1aade1e831987cd562f3665475bc4f4db1a979 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 13:58:20 +0300
Subject: [PATCH 287/696] refactor: extract store functions
---
.../cashu/templates/cashu/wallet.html | 47 +++++++++++--------
1 file changed, 28 insertions(+), 19 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 6933acfd..9a29ed79 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -78,7 +78,11 @@ page_container %}
-
+
Pending
- Recheck
+ Recheck
@@ -492,9 +496,9 @@ page_container %}
buyOrders: [],
buyData: {
amount: 0,
- memo: '',
- bolt11: '',
- hash: '',
+ memo: '',
+ bolt11: '',
+ hash: ''
},
showInvoiceDetails: false,
tokens: [],
@@ -843,7 +847,6 @@ page_container %}
this.buyData = data
this.showInvoiceDetails = true
},
-
requestInvoice: async function () {
try {
@@ -860,10 +863,7 @@ page_container %}
date: currentDateStr(),
status: 'pending'
})
- localStorage.setItem(
- 'cashu.buyOrders',
- JSON.stringify(this.buyOrders)
- )
+ this.storeBuyOrders()
const amounts = splitAmount(this.buyData.amount)
await this.requestTokens(amounts, this.buyData.hash)
} catch (error) {
@@ -893,15 +893,18 @@ page_container %}
}
},
- recheckBuyOrder: async function(hash) {
+ recheckBuyOrder: async function (hash) {
console.log('### recheckBuyOrder', hash)
- const tokens = this.tokens.find(bt => bt.hash = hash)
+ const tokens = this.tokens.find(bt => (bt.hash = hash))
if (!tokens) {
console.error('####### no token for hash', hash)
return
}
- const promises = await this.fetchPromisesFromMint(hash, tokens.blindedMessages)
- if (promises && promises.length){
+ const promises = await this.fetchPromisesFromMint(
+ hash,
+ tokens.blindedMessages
+ )
+ if (promises && promises.length) {
tokens.promises = promises
}
},
@@ -923,16 +926,12 @@ page_container %}
console.error(error)
LNbits.utils.notifyApiError(error)
}
-
},
requestTokens: async function (amounts, paymentHash) {
const newTokens = await this.buildTokens(amounts, paymentHash)
this.tokens.push(newTokens)
- localStorage.setItem(
- 'cashu.tokens',
- JSON.stringify(this.tokens, bigIntStringify)
- )
+ this.storeTokens()
console.log('### this.tokens', this.tokens)
await this.fetchPromisesFromMint(paymentHash, newTokens.newTokens)
},
@@ -980,6 +979,16 @@ page_container %}
C: {C, secret: secrets[i]}
}
})
+ },
+
+ storeBuyOrders: function () {
+ localStorage.setItem('cashu.buyOrders', JSON.stringify(this.buyOrders))
+ },
+ storeTokens: function () {
+ localStorage.setItem(
+ 'cashu.tokens',
+ JSON.stringify(this.tokens, bigIntStringify)
+ )
}
},
watch: {
From 3efb3041d5d162f696e3abdef00398b04b9b54f0 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 14:22:00 +0300
Subject: [PATCH 288/696] chore: here and there
---
.../cashu/templates/cashu/wallet.html | 24 +++++++++++++++----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 9a29ed79..c9f1b555 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -94,6 +94,12 @@ page_container %}
Recheck
+
+
+
(bt.hash = hash))
+ console.log('### recheckBuyOrder.hash', hash)
+ const tokens = this.tokens.find(bt => (bt.hash === hash))
+ console.log('### recheckBuyOrder.tokens', tokens)
if (!tokens) {
console.error('####### no token for hash', hash)
return
@@ -906,6 +913,12 @@ page_container %}
)
if (promises && promises.length) {
tokens.promises = promises
+ tokens.status = 'paid'
+ this.storeTokens()
+
+ const buyOrder = this.buyOrders.find(bo => bo.hash === hash)
+ buyOrder.status = 'paid'
+ this.storeBuyOrders()
}
},
@@ -1043,7 +1056,8 @@ page_container %}
localStorage.getItem('cashu.buyOrders') || '[]'
)
this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
- console.log('#### this.buyOrders', this.buyOrders)
+ console.table(this.buyOrders)
+ console.table(this.tokens)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
From 53172ff60839aa03c99fb7f2906263f618357d37 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 14:25:25 +0300
Subject: [PATCH 289/696] chore: code clean-up
---
.../cashu/templates/cashu/wallet.html | 234 +-----------------
1 file changed, 2 insertions(+), 232 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index c9f1b555..01a5bc1a 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -126,234 +126,6 @@ page_container %}
-
- {% raw %}
-
-
-
- {{receive.lnurl.domain}} is requesting an invoice:
-
- {% endraw %} {% if LNBITS_DENOMINATION != 'sats' %}
-
- {% else %}
-
-
- {% endif %}
-
-
- {% raw %}
-
-
-
- Withdraw from {{receive.lnurl.domain}}
-
- Create invoice
-
- Cancel
-
-
-
-
-
-
-
- Copy invoice
- Close
-
-
- {% endraw %}
-
-
-
-
-
-
- {% raw %} {{ parseFloat(String(parse.invoice.fsat).replaceAll(",",
- "")) / 100 }} {% endraw %} {{LNBITS_DENOMINATION}} {% raw %}
-
-
- {{ parse.invoice.fsat }}{% endraw %} {{LNBITS_DENOMINATION}} {%
- raw %}
-
-
-
- Description: {{ parse.invoice.description }}
- Expire date: {{ parse.invoice.expireDate }}
- Hash: {{ parse.invoice.hash }}
-
- {% endraw %}
-
- Pay
- Cancel
-
-
- Not enough funds!
- Cancel
-
-
-
- {% raw %}
-
-
- Authenticate with {{ parse.lnurlauth.domain }} ?
-
-
-
- For every website and for every LNbits wallet, a new keypair
- will be deterministically generated so your identity can't be
- tied to your LNbits wallet or linked across websites. No other
- data will be shared with {{ parse.lnurlauth.domain }}.
-
- Your public key for {{ parse.lnurlauth.domain }} is:
-
- {{ parse.lnurlauth.pubkey }}
-
-
- Login
- Cancel
-
-
- {% endraw %}
-
-
-
-
-
-
- Read
- Cancel
-
-
-
-
-
-
-
-
-
-
-
-
-
- Cancel
-
-
-
-
-
-
-
-
-
-
-
- {% raw %}
@@ -445,7 +216,6 @@ page_container %}
>
- {% endraw %}
@@ -1056,8 +826,8 @@ page_container %}
localStorage.getItem('cashu.buyOrders') || '[]'
)
this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
- console.table(this.buyOrders)
- console.table(this.tokens)
+ // console.table(this.buyOrders)
+ // console.table(this.tokens)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
From fa484cb8086865eb5484287bdbd2ccd1f2a32d76 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 15:05:32 +0300
Subject: [PATCH 290/696] chore: parse invoice
---
.../cashu/templates/cashu/wallet.html | 127 ++++++++++++++++--
1 file changed, 117 insertions(+), 10 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 01a5bc1a..b3ccbc8b 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -28,7 +28,12 @@ page_container %}
- Sell tokens
(for sats)
@@ -95,10 +100,7 @@ page_container %}
-
+
-
@@ -217,6 +218,51 @@ page_container %}
+
+
+
+
+
+
+ Please paste a Lightning invoice
+
+
+
+
+
+ {% raw %}
+ Amount: {{ sellData.invoice.sat }} sats
+ Description: {{ sellData.invoice.description }}
+ Expire date: {{ sellData.invoice.expireDate }}
+ Expired: {{ sellData.invoice.expired }}
+ Hash: {{ sellData.invoice.hash }} {% endraw %}
+
+
+ Check Invoice
+ Sell Token
+ Close
+
+
+
@@ -276,7 +322,12 @@ page_container %}
bolt11: '',
hash: ''
},
+ sellData: {
+ invoice: '',
+ bolt11: ''
+ },
showInvoiceDetails: false,
+ showPayInvoice: false,
tokens: [],
receive: {
@@ -619,11 +670,18 @@ page_container %}
this.showInvoiceDetails = true
},
- showInvoiceDialog: async function (data) {
+ showInvoiceDialog: function (data) {
this.buyData = _.clone(data)
this.showInvoiceDetails = true
},
+ showPayInvoiceDialog: function () {
+ console.log('### showPayInvoiceDialog')
+ this.sellData.invoice = ''
+ this.sellData.bolt11 = ''
+ this.showPayInvoice = true
+ },
+
requestInvoice: async function () {
try {
const {data} = await LNbits.api.request(
@@ -671,7 +729,7 @@ page_container %}
recheckBuyOrder: async function (hash) {
console.log('### recheckBuyOrder.hash', hash)
- const tokens = this.tokens.find(bt => (bt.hash === hash))
+ const tokens = this.tokens.find(bt => bt.hash === hash)
console.log('### recheckBuyOrder.tokens', tokens)
if (!tokens) {
console.error('####### no token for hash', hash)
@@ -764,6 +822,55 @@ page_container %}
})
},
+ checkInvoice: function () {
+ console.log('#### checkInvoice')
+ try {
+ const invoice = decode(this.sellData.bolt11)
+
+ const cleanInvoice = {
+ msat: invoice.human_readable_part.amount,
+ sat: invoice.human_readable_part.amount / 1000,
+ fsat: LNbits.utils.formatSat(
+ invoice.human_readable_part.amount / 1000
+ )
+ }
+
+ _.each(invoice.data.tags, tag => {
+ if (_.isObject(tag) && _.has(tag, 'description')) {
+ if (tag.description === 'payment_hash') {
+ cleanInvoice.hash = tag.value
+ } else if (tag.description === 'description') {
+ cleanInvoice.description = tag.value
+ } else if (tag.description === 'expiry') {
+ var expireDate = new Date(
+ (invoice.data.time_stamp + tag.value) * 1000
+ )
+ cleanInvoice.expireDate = Quasar.utils.date.formatDate(
+ expireDate,
+ 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ )
+ cleanInvoice.expired = false // TODO
+ }
+ }
+
+ this.sellData.invoice = cleanInvoice
+ })
+
+ console.log('#### this.sellData.invoice', this.sellData.invoice)
+ } catch (error) {
+ this.$q.notify({
+ timeout: 5000,
+ type: 'warning',
+ message: 'Cannot decode invoice',
+ caption: error + ''
+ })
+ }
+ },
+
+ sellTokens: async function () {
+ console.log('#### sell tokens')
+ },
+
storeBuyOrders: function () {
localStorage.setItem('cashu.buyOrders', JSON.stringify(this.buyOrders))
},
@@ -826,8 +933,8 @@ page_container %}
localStorage.getItem('cashu.buyOrders') || '[]'
)
this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
- // console.table(this.buyOrders)
- // console.table(this.tokens)
+ console.log('### buyOrders',this.buyOrders)
+ console.table('### tokens',this.tokens)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
From 791c5eb41df1fb63392dcf77093b249343bf4923 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 22:56:03 +0300
Subject: [PATCH 291/696] feat: secret stuff
---
lnbits/extensions/cashu/core/b_dhke.py | 37 +++----
lnbits/extensions/cashu/mint_helper.py | 9 +-
lnbits/extensions/cashu/static/js/base64.js | 40 +++++++
lnbits/extensions/cashu/static/js/dhke.js | 19 ++--
.../cashu/templates/cashu/wallet.html | 100 +++++++++++++-----
5 files changed, 146 insertions(+), 59 deletions(-)
create mode 100644 lnbits/extensions/cashu/static/js/base64.js
diff --git a/lnbits/extensions/cashu/core/b_dhke.py b/lnbits/extensions/cashu/core/b_dhke.py
index be9a141b..8855481c 100644
--- a/lnbits/extensions/cashu/core/b_dhke.py
+++ b/lnbits/extensions/cashu/core/b_dhke.py
@@ -6,7 +6,7 @@ Alice:
A = a*G
return A
Bob:
-Y = hash_to_point(secret_message)
+Y = hash_to_curve(secret_message)
r = random blinding factor
B'= Y + r*G
return B'
@@ -20,7 +20,7 @@ C = C' - r*A
(= a*Y)
return C, secret_message
Alice:
-Y = hash_to_point(secret_message)
+Y = hash_to_curve(secret_message)
C == a*Y
If true, C must have originated from Alice
"""
@@ -30,28 +30,23 @@ import hashlib
from secp256k1 import PrivateKey, PublicKey
-def hash_to_point(secret_msg):
- """Generates x coordinate from the message hash and checks if the point lies on the curve.
- If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
- point = None
- msg = secret_msg
- while point is None:
- _hash = hashlib.sha256(msg).hexdigest().encode("utf-8")
- try:
- # We construct compressed pub which has x coordinate encoded with even y
- _hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes
- _hash[0] = 0x02 # set first byte to represent even y coord
- _hash = bytes(_hash)
- point = PublicKey(_hash, raw=True)
- except:
- msg = _hash
-
+def hash_to_curve(message: bytes):
+ """Generates a point from the message hash and checks if the point lies on the curve.
+ If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
+ point = None
+ msg_to_hash = message
+ while point is None:
+ try:
+ _hash = hashlib.sha256(msg_to_hash).digest()
+ point = PublicKey(b"\x02" + _hash, raw=True)
+ except:
+ msg_to_hash = _hash
return point
def step1_alice(secret_msg):
- secret_msg = secret_msg.encode("utf-8")
- Y = hash_to_point(secret_msg)
+ secret_msg = secret_msg
+ Y = hash_to_curve(secret_msg)
r = PrivateKey()
B_ = Y + r.pubkey
return B_, r
@@ -68,7 +63,7 @@ def step3_alice(C_, r, A):
def verify(a, C, secret_msg):
- Y = hash_to_point(secret_msg.encode("utf-8"))
+ Y = hash_to_curve(secret_msg)
return C == Y.mult(a)
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
index 5c96d831..3892c67f 100644
--- a/lnbits/extensions/cashu/mint_helper.py
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -1,4 +1,5 @@
import hashlib
+import base64
from typing import List, Set
from .core.b_dhke import verify
@@ -32,14 +33,16 @@ def derive_pubkeys(keys: List[PrivateKey]):
# async required?
async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
"""Verifies that the proof of promise was issued by this ledger."""
- if proof.secret in proofs_used:
- raise Exception(f"tokens already spent. Secret: {proof.secret}")
+ # if proof.secret in proofs_used:
+ # raise Exception(f"tokens already spent. Secret: {proof.secret}")
secret_key = derive_keys(master_prvkey)[
proof.amount
] # Get the correct key to check against
C = PublicKey(bytes.fromhex(proof.C), raw=True)
- validMintSig = verify(secret_key, C, proof.secret)
+ secret = base64.urlsafe_b64decode(proof.secret)
+ print('### secret', secret)
+ validMintSig = verify(secret_key, C, secret)
if validMintSig != True:
raise Exception(f"tokens not valid. Secret: {proof.secret}")
diff --git a/lnbits/extensions/cashu/static/js/base64.js b/lnbits/extensions/cashu/static/js/base64.js
new file mode 100644
index 00000000..2cd19a83
--- /dev/null
+++ b/lnbits/extensions/cashu/static/js/base64.js
@@ -0,0 +1,40 @@
+function unescapeBase64Url (str) {
+ return (str + '==='.slice((str.length + 3) % 4))
+ .replace(/-/g, '+')
+ .replace(/_/g, '/')
+ }
+
+ function escapeBase64Url (str) {
+ return str.replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/=/g, '')
+ }
+
+ const uint8ToBase64 = (function (exports) {
+ 'use strict';
+
+ var fromCharCode = String.fromCharCode;
+ var encode = function encode(uint8array) {
+ var output = [];
+
+ for (var i = 0, length = uint8array.length; i < length; i++) {
+ output.push(fromCharCode(uint8array[i]));
+ }
+
+ return btoa(output.join(''));
+ };
+
+ var asCharCode = function asCharCode(c) {
+ return c.charCodeAt(0);
+ };
+
+ var decode = function decode(chars) {
+ return Uint8Array.from(atob(chars), asCharCode);
+ };
+
+ exports.decode = decode;
+ exports.encode = encode;
+
+ return exports;
+
+ }({}));
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/static/js/dhke.js b/lnbits/extensions/cashu/static/js/dhke.js
index 959ba6f1..c9e2d146 100644
--- a/lnbits/extensions/cashu/static/js/dhke.js
+++ b/lnbits/extensions/cashu/static/js/dhke.js
@@ -1,15 +1,19 @@
async function hashToCurve(secretMessage) {
+ console.log(
+ '### secretMessage',
+ nobleSecp256k1.utils.bytesToHex(secretMessage)
+ )
let point
while (!point) {
const hash = await nobleSecp256k1.utils.sha256(secretMessage)
+ const hashHex = nobleSecp256k1.utils.bytesToHex(hash)
+ const pointX = '02' + hashHex
+ console.log('### pointX', pointX)
try {
- point = nobleSecp256k1.Point.fromHex(hash)
+ point = nobleSecp256k1.Point.fromHex(pointX)
+ console.log('### point', point.toHex())
} catch (error) {
- // console.error(error)
- // const x = bytesToNumber(hash) + ''
- // const msg = await nobleSecp256k1.utils.sha256(x)
- secretMessage = await nobleSecp256k1.utils.sha256(hash)
- // secretMessage = nobleSecp256k1.utils.bytesToHex(msg)
+ secretMessage = await nobleSecp256k1.utils.sha256(secretMessage)
}
}
return point
@@ -26,6 +30,7 @@ async function step1Bob(secretMessage) {
}
function step3Bob(C_, r, A) {
- const C = C_.subtract(A.multiply(r))
+ const rInt = BigInt(r)
+ const C = C_.subtract(A.multiply(rInt))
return C
}
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index b3ccbc8b..cef80d42 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -240,7 +240,8 @@ page_container %}
{% raw %}
- Amount: {{ sellData.invoice.sat }} sats
+ Amount: {{ sellData.invoice.sat }}
+ sats
Description: {{ sellData.invoice.description }}
Expire date: {{ sellData.invoice.expireDate }}
Expired: {{ sellData.invoice.expired }}
@@ -315,6 +316,7 @@ page_container %}
mintId: '',
mintName: '',
+ keys: '',
buyOrders: [],
buyData: {
amount: 0,
@@ -782,9 +784,14 @@ page_container %}
const secrets = []
const randomBlindingFactors = []
for (let i = 0; i < amounts.length; i++) {
- // const secret = bytesToNumber(nobleSecp256k1.utils.randomBytes(32)) + ''
- const secret = nobleSecp256k1.utils.randomBytes(32)
- secrets.push(secret)
+ // const secret = nobleSecp256k1.utils.randomBytes(32)
+ const secret = nobleSecp256k1.utils.hexToBytes('0000000000000000000000000000000000000000000000000000000000000003')
+ const encodedSecret = uint8ToBase64.encode(secret)
+ console.log('### encodedSecret', encodedSecret)
+ const decodedSecret = uint8ToBase64.decode(encodedSecret)
+ const hexSecret = nobleSecp256k1.utils.bytesToHex(decodedSecret)
+ console.log('### decodedSecret', hexSecret)
+ secrets.push(encodedSecret)
const {B_, randomBlindingFactor} = await step1Bob(secret)
randomBlindingFactors.push(randomBlindingFactor)
blindedMessages.push({amount: amounts[i], B_: B_})
@@ -798,28 +805,6 @@ page_container %}
status: 'pending'
}
return newTokens
- // console.log('### payloadsJson.payloads', payloadsJson.payloads)
- // const promises = await mintApi.mint(payloadsJson.payloads, paymentHash)
- // if (promises.error) {
- // throw new Error(promises.error)
- // }
- // return this._constructProofs(promises, randomBlindingFactors, secrets)
- },
-
- _constructProofs: function (promises, randomBlindingFactors, secrets) {
- return promises.map((p, i) => {
- const C_ = nobleSecp256k1.Point.fromHex(p['C_'])
- const A = this.keys[p.amount]
- const C = step3Bob(
- C_,
- randomBlindingFactors[i],
- nobleSecp256k1.Point.fromHex(A)
- ).toHex()
- return {
- amount: p.amount,
- C: {C, secret: secrets[i]}
- }
- })
},
checkInvoice: function () {
@@ -869,6 +854,57 @@ page_container %}
sellTokens: async function () {
console.log('#### sell tokens')
+ const amount = this.sellData.invoice.sat
+ const token = this.tokens
+ .filter(t => t.promises?.length)
+ .find(t => t.promises.find(b => b.amount === amount))
+ console.log('### token', token)
+ if (token) {
+ const promiseIndex = token.promises
+ .map(p => `${p.amount}`)
+ .indexOf(`${amount}`)
+ const promise = token.promises[promiseIndex]
+ console.log('### promise', promise)
+
+ const secret = token.secrets[promiseIndex]
+ const randomBlindingFactor = token.randomBlindingFactors[promiseIndex]
+
+ const C_ = nobleSecp256k1.Point.fromHex(promise['C_'])
+ const A = this.keys[promise.amount] // todo
+
+ console.log('#### C_', C_)
+ console.log('#### A', A)
+
+ const C = step3Bob(
+ C_,
+ randomBlindingFactor,
+ nobleSecp256k1.Point.fromHex(A)
+ )
+
+ const proofs = [
+ {
+ amount,
+ secret,
+ C: C.toHex(true)
+ }
+ ]
+
+ const payload = {
+ proofs,
+ amount,
+ invoice: this.sellData.bolt11
+ }
+ console.log('#### payload', JSON.stringify(payload))
+ }
+ },
+
+ fetchMintKeys: async function () {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ `/cashu/api/v1/cashu/${this.mintId}/keys`
+ )
+ this.keys = data
+ localStorage.setItem('cashu.keys', JSON.stringify(data))
},
storeBuyOrders: function () {
@@ -929,12 +965,19 @@ page_container %}
this.mintName = this.$q.localStorage.getItem('cashu.name')
}
+ const keysJson = localStorage.getItem('cashu.keys')
+ if (!keysJson) {
+ this.fetchMintKeys()
+ } else {
+ this.keys = JSON.parse(keysJson)
+ }
+
this.buyOrders = JSON.parse(
localStorage.getItem('cashu.buyOrders') || '[]'
)
this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
- console.log('### buyOrders',this.buyOrders)
- console.table('### tokens',this.tokens)
+ console.log('### buyOrders', this.buyOrders)
+ console.table('### tokens', this.tokens)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
@@ -945,4 +988,5 @@ page_container %}
+
{% endblock %}
From 0a9503b5571bb06943bc7b3ed53f79499a53faf3 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Mon, 10 Oct 2022 23:06:16 +0300
Subject: [PATCH 292/696] chore: remove mock data
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index cef80d42..0d250a62 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -784,13 +784,10 @@ page_container %}
const secrets = []
const randomBlindingFactors = []
for (let i = 0; i < amounts.length; i++) {
- // const secret = nobleSecp256k1.utils.randomBytes(32)
- const secret = nobleSecp256k1.utils.hexToBytes('0000000000000000000000000000000000000000000000000000000000000003')
+ const secret = nobleSecp256k1.utils.randomBytes(32)
+ // const secret = nobleSecp256k1.utils.hexToBytes('0000000000000000000000000000000000000000000000000000000000000003')
+ // todo: base64Url
const encodedSecret = uint8ToBase64.encode(secret)
- console.log('### encodedSecret', encodedSecret)
- const decodedSecret = uint8ToBase64.decode(encodedSecret)
- const hexSecret = nobleSecp256k1.utils.bytesToHex(decodedSecret)
- console.log('### decodedSecret', hexSecret)
secrets.push(encodedSecret)
const {B_, randomBlindingFactor} = await step1Bob(secret)
randomBlindingFactors.push(randomBlindingFactor)
From d8be5d7b7cf4f96b5890faf436ea0a124bb2a208 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 13:51:35 +0300
Subject: [PATCH 293/696] feat: sell fixed amount token
---
lnbits/extensions/cashu/mint.py | 2 +-
lnbits/extensions/cashu/mint_helper.py | 8 +-
lnbits/extensions/cashu/static/js/base64.js | 73 +++++++++----------
.../cashu/templates/cashu/wallet.html | 64 ++++++++--------
4 files changed, 72 insertions(+), 75 deletions(-)
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index 3388b45a..883d0fa9 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -71,7 +71,7 @@ async def melt(cashu: Cashu, proofs: List[Proof], invoice: str):
fees_msat = await check_fees(cashu.wallet, invoice_obj)
assert total_provided >= amount + fees_msat / 1000, Exception(
- "provided proofs not enough for Lightning payment."
+ f"Provided proofs (${total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)."
)
await pay_invoice(
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
index 3892c67f..4db92946 100644
--- a/lnbits/extensions/cashu/mint_helper.py
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -33,19 +33,21 @@ def derive_pubkeys(keys: List[PrivateKey]):
# async required?
async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
"""Verifies that the proof of promise was issued by this ledger."""
- # if proof.secret in proofs_used:
- # raise Exception(f"tokens already spent. Secret: {proof.secret}")
+ if proof.secret in proofs_used:
+ raise Exception(f"tokens already spent. Secret: {proof.secret}")
secret_key = derive_keys(master_prvkey)[
proof.amount
] # Get the correct key to check against
C = PublicKey(bytes.fromhex(proof.C), raw=True)
- secret = base64.urlsafe_b64decode(proof.secret)
+ secret = base64.standard_b64decode(proof.secret)
print('### secret', secret)
validMintSig = verify(secret_key, C, secret)
if validMintSig != True:
raise Exception(f"tokens not valid. Secret: {proof.secret}")
+
+
def verify_split_amount(amount: int):
"""Split amount like output amount can't be negative or too big."""
diff --git a/lnbits/extensions/cashu/static/js/base64.js b/lnbits/extensions/cashu/static/js/base64.js
index 2cd19a83..b150882f 100644
--- a/lnbits/extensions/cashu/static/js/base64.js
+++ b/lnbits/extensions/cashu/static/js/base64.js
@@ -1,40 +1,37 @@
-function unescapeBase64Url (str) {
- return (str + '==='.slice((str.length + 3) % 4))
- .replace(/-/g, '+')
- .replace(/_/g, '/')
- }
-
- function escapeBase64Url (str) {
- return str.replace(/\+/g, '-')
- .replace(/\//g, '_')
- .replace(/=/g, '')
+function unescapeBase64Url(str) {
+ return (str + '==='.slice((str.length + 3) % 4))
+ .replace(/-/g, '+')
+ .replace(/_/g, '/')
+}
+
+function escapeBase64Url(str) {
+ return str.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '')
+}
+
+const uint8ToBase64 = (function (exports) {
+ 'use strict'
+
+ var fromCharCode = String.fromCharCode
+ var encode = function encode(uint8array) {
+ var output = []
+
+ for (var i = 0, length = uint8array.length; i < length; i++) {
+ output.push(fromCharCode(uint8array[i]))
+ }
+
+ return btoa(output.join(''))
}
- const uint8ToBase64 = (function (exports) {
- 'use strict';
-
- var fromCharCode = String.fromCharCode;
- var encode = function encode(uint8array) {
- var output = [];
-
- for (var i = 0, length = uint8array.length; i < length; i++) {
- output.push(fromCharCode(uint8array[i]));
- }
-
- return btoa(output.join(''));
- };
-
- var asCharCode = function asCharCode(c) {
- return c.charCodeAt(0);
- };
-
- var decode = function decode(chars) {
- return Uint8Array.from(atob(chars), asCharCode);
- };
-
- exports.decode = decode;
- exports.encode = encode;
-
- return exports;
-
- }({}));
\ No newline at end of file
+ var asCharCode = function asCharCode(c) {
+ return c.charCodeAt(0)
+ }
+
+ var decode = function decode(chars) {
+ return Uint8Array.from(atob(chars), asCharCode)
+ }
+
+ exports.decode = decode
+ exports.encode = encode
+
+ return exports
+})({})
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 0d250a62..5da41d0a 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -787,7 +787,7 @@ page_container %}
const secret = nobleSecp256k1.utils.randomBytes(32)
// const secret = nobleSecp256k1.utils.hexToBytes('0000000000000000000000000000000000000000000000000000000000000003')
// todo: base64Url
- const encodedSecret = uint8ToBase64.encode(secret)
+ const encodedSecret = uint8ToBase64.encode(secret)
secrets.push(encodedSecret)
const {B_, randomBlindingFactor} = await step1Bob(secret)
randomBlindingFactors.push(randomBlindingFactor)
@@ -852,47 +852,45 @@ page_container %}
sellTokens: async function () {
console.log('#### sell tokens')
const amount = this.sellData.invoice.sat
- const token = this.tokens
- .filter(t => t.promises?.length)
- .find(t => t.promises.find(b => b.amount === amount))
- console.log('### token', token)
- if (token) {
- const promiseIndex = token.promises
- .map(p => `${p.amount}`)
- .indexOf(`${amount}`)
- const promise = token.promises[promiseIndex]
- console.log('### promise', promise)
+ const paidTokens = this.tokens.filter(t => t.promises?.length)
+ console.log('### paidTokens', paidTokens)
+ const proofs = paidTokens.map(token => {
+ // const promiseIndex = token.promises
+ // .map(p => `${p.amount}`)
+ // .indexOf(`${amount}`)
+ return token.promises.map((promise, promiseIndex) => {
+ // const promise = token.promises[promiseIndex]
+ console.log('### promise', promise)
- const secret = token.secrets[promiseIndex]
- const randomBlindingFactor = token.randomBlindingFactors[promiseIndex]
+ const secret = token.secrets[promiseIndex]
+ const randomBlindingFactor =
+ token.randomBlindingFactors[promiseIndex]
- const C_ = nobleSecp256k1.Point.fromHex(promise['C_'])
- const A = this.keys[promise.amount] // todo
+ const C_ = nobleSecp256k1.Point.fromHex(promise['C_'])
+ const A = this.keys[promise.amount] // todo
- console.log('#### C_', C_)
- console.log('#### A', A)
+ console.log('#### C_', C_)
+ console.log('#### A', A)
- const C = step3Bob(
- C_,
- randomBlindingFactor,
- nobleSecp256k1.Point.fromHex(A)
- )
+ const C = step3Bob(
+ C_,
+ randomBlindingFactor,
+ nobleSecp256k1.Point.fromHex(A)
+ )
- const proofs = [
- {
- amount,
+ return {
+ amount: promise.amount,
secret,
C: C.toHex(true)
}
- ]
-
- const payload = {
- proofs,
- amount,
- invoice: this.sellData.bolt11
- }
- console.log('#### payload', JSON.stringify(payload))
+ })
+ })
+ const payload = {
+ proofs: proofs.flat(),
+ amount,
+ invoice: this.sellData.bolt11
}
+ console.log('#### payload', JSON.stringify(payload))
},
fetchMintKeys: async function () {
From cbd19e96422837884bbc15ea9ffb791de009c8ec Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 14:05:08 +0300
Subject: [PATCH 294/696] feat: add tabs
---
.../cashu/templates/cashu/wallet.html | 177 ++++++++++++------
1 file changed, 124 insertions(+), 53 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 5da41d0a..cad45765 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -69,61 +69,131 @@ page_container %}
-
- {% raw %}
-
-
-
-
-
+
+
+
+
+
+
+
+ {% raw %}
+
+
+
+
+
+ Pending
+
+
+ Recheck
+
+
+
+
+
+
+
- Pending
-
-
- Recheck
-
-
-
-
-
-
-
- {{props.row.amount}}
-
+ {{props.row.amount}}
+
-
- {{props.row.memo}}
-
-
- {{props.row.date}}
-
-
- {{props.row.hash}}
-
-
-
- {% endraw %}
-
+
+ {{props.row.memo}}
+
+
+ {{props.row.date}}
+
+
+ {{props.row.hash}}
+
+
+
+ {% endraw %}
+
+
+
+
+ {% raw %}
+
+
+
+
+
+ Pending
+
+
+ Recheck
+
+
+
+
+
+
+
+ {{props.row.amount}}
+
+
+
+ {{props.row.memo}}
+
+
+ {{props.row.date}}
+
+
+ {{props.row.hash}}
+
+
+
+ {% endraw %}
+
+
+
+
+ History
+
+
@@ -331,6 +401,7 @@ page_container %}
showInvoiceDetails: false,
showPayInvoice: false,
tokens: [],
+ tab: 'tokens',
receive: {
show: false,
From c74f9cc6ca5ae0f94fabc23fde574d2c8b47cb1b Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 14:55:34 +0300
Subject: [PATCH 295/696] feat: simple tokenList
---
.../cashu/templates/cashu/wallet.html | 114 ++++++++++++------
1 file changed, 77 insertions(+), 37 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index cad45765..7b5e1c3f 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -79,54 +79,31 @@ page_container %}
{% raw %}
-
-
-
- Pending
-
-
- Recheck
-
-
-
-
-
-
- {{props.row.amount}}
+ {{props.row.denomination}}
+
+
+ {{props.row.count}}
+
+
+ {{props.row.value}}
-
{{props.row.memo}}
-
- {{props.row.date}}
-
-
- {{props.row.hash}}
-
{% endraw %}
@@ -475,6 +452,44 @@ page_container %}
},
filter: null
},
+
+ tokensTable: {
+ columns: [
+ {
+ name: 'denomination',
+ align: 'left',
+ label: 'Denomination',
+ field: 'denomination',
+ sortable: true
+ },
+ {
+ name: 'count',
+ align: 'left',
+ label: 'Count',
+ field: 'count',
+ sortable: true
+ },
+ {
+ name: 'value',
+ align: 'left',
+ label: 'Value',
+ field: 'value',
+ sortable: true
+ },
+ {
+ name: 'memo',
+ align: 'left',
+ label: 'Memo',
+ field: 'memo',
+ sortable: true
+ }
+ ],
+ pagination: {
+ rowsPerPage: 10
+ },
+ filter: null
+ },
+
paymentsChart: {
show: false
},
@@ -498,6 +513,31 @@ page_container %}
},
pendingPaymentsExist: function () {
return this.payments.findIndex(payment => payment.pending) !== -1
+ },
+
+ tokenList: function () {
+ const y = {}
+ let x = this.tokens
+ .map(t => t.blindedMessages)
+ .flat()
+ .map(t => ({
+ blindingFactor: t.B_,
+ denomination: t.amount
+ }))
+ console.log('### x1', x)
+ x.forEach(t => {
+ y[`_${t.denomination}`] = y[`_${t.denomination}`] || []
+ y[`_${t.denomination}`].push(t)
+ })
+ x = Object.keys(y).map(k => ({
+ denomination: y[k][0].denomination,
+ count: y[k].length,
+ value: y[k][0].denomination * y[k].length
+ }))
+ console.log('### x2', x)
+ console.log('### y', y)
+
+ return x
}
},
filters: {
From ac773cffda1123add5ec304838a34d0fc35ec256 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 14:59:20 +0300
Subject: [PATCH 296/696] refactor: simplify code
---
.../cashu/templates/cashu/wallet.html | 23 ++++++++-----------
1 file changed, 10 insertions(+), 13 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 7b5e1c3f..16f8e163 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -524,20 +524,17 @@ page_container %}
blindingFactor: t.B_,
denomination: t.amount
}))
- console.log('### x1', x)
- x.forEach(t => {
- y[`_${t.denomination}`] = y[`_${t.denomination}`] || []
- y[`_${t.denomination}`].push(t)
- })
- x = Object.keys(y).map(k => ({
- denomination: y[k][0].denomination,
- count: y[k].length,
- value: y[k][0].denomination * y[k].length
- }))
- console.log('### x2', x)
- console.log('### y', y)
+ .reduce((y, t) => {
+ y[`_${t.denomination}`] = y[`_${t.denomination}`] || []
+ y[`_${t.denomination}`].push(t)
+ return y
+ }, {})
- return x
+ return Object.keys(x).map(k => ({
+ denomination: x[k][0].denomination,
+ count: x[k].length,
+ value: x[k][0].denomination * x[k].length
+ }))
}
},
filters: {
From 429f14e728426c82b7ead07f21e9bfcf53f4cc1d Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 15:06:10 +0300
Subject: [PATCH 297/696] feat: show balance
---
.../cashu/templates/cashu/wallet.html | 25 ++++++++++---------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 16f8e163..d0e90493 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -22,7 +22,7 @@ page_container %}
- {% raw %} {{balanceAmount}}
+ {% raw %} {{balance}}
{{tickershort}}{% endraw %}
@@ -357,7 +357,6 @@ page_container %}
mixins: [windowMixin],
data: function () {
return {
- balanceAmount: '',
tickershort: '',
name: '',
@@ -497,7 +496,7 @@ page_container %}
show: false,
location: window.location
},
- balance: 0,
+
credit: 0,
newName: ''
}
@@ -516,8 +515,8 @@ page_container %}
},
tokenList: function () {
- const y = {}
- let x = this.tokens
+ const x = this.tokens
+ .filter(t => t.promises?.length)
.map(t => t.blindedMessages)
.flat()
.map(t => ({
@@ -535,6 +534,14 @@ page_container %}
count: x[k].length,
value: x[k][0].denomination * x[k].length
}))
+ },
+
+ balance: function () {
+ return this.tokens
+ .filter(t => t.promises?.length)
+ .map(t => t.blindedMessages)
+ .flat()
+ .reduce((sum, el) => (sum += el.amount), 0)
}
},
filters: {
@@ -584,9 +591,7 @@ page_container %}
this.parse.data.paymentChecker = null
this.parse.camera.show = false
},
- updateBalance: function (credit) {
- this.balance = this.balance // update balance
- },
+
closeReceiveDialog: function () {
setTimeout(() => {
clearInterval(this.receive.paymentChecker)
@@ -1043,10 +1048,6 @@ page_container %}
this.tickershort = this.$q.localStorage.getItem('cashu.tickershort')
}
- if (!this.$q.localStorage.getItem('cashu.amount')) {
- this.balanceAmount = 0
- }
-
// get mint
if (params.get('mint_id')) {
this.mintId = params.get('mint_id')
From 1bfec1843303fe5f50524aa2236eb28ab04e26bc Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 15:36:24 +0300
Subject: [PATCH 298/696] feat: send tokens UI
---
.../cashu/templates/cashu/wallet.html | 70 +++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index d0e90493..385143e1 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -63,6 +63,7 @@ page_container %}
rounded
color="primary"
class="full-width"
+ @click="showSendTokensDialog"
>
Send
@@ -266,6 +267,58 @@ page_container %}
+
+
+
+
+
+ How much would you like to send?
+
+
+
+
+
+
+
+
+
+
+ Show Tokens
+
+ Close
+
+
+
+
@@ -374,8 +427,14 @@ page_container %}
invoice: '',
bolt11: ''
},
+ sendData: {
+ amount: 0,
+ memo: '',
+ tokens: ''
+ },
showInvoiceDetails: false,
showPayInvoice: false,
+ showSendTokens: false,
tokens: [],
tab: 'tokens',
@@ -797,6 +856,13 @@ page_container %}
this.showPayInvoice = true
},
+ showSendTokensDialog: function () {
+ this.sendData.tokens = ''
+ this.sendData.amount = 0
+ this.sendData.memo = ''
+ this.showSendTokens = true
+ },
+
requestInvoice: async function () {
try {
const {data} = await LNbits.api.request(
@@ -892,6 +958,10 @@ page_container %}
await this.fetchPromisesFromMint(paymentHash, newTokens.newTokens)
},
+ buildAnsShowTokens: async function () {
+ this.sendData.tokens = 'toookeeens:' + this.sendData.amount
+ },
+
buildTokens: async function (amounts, paymentHash) {
const blindedMessages = []
const secrets = []
From 96046d7fc59e2abdbbe88e3f949c8539345d5dcb Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 15:55:18 +0300
Subject: [PATCH 299/696] refactor: re-order methods
---
.../extensions/cashu/templates/cashu/wallet.html | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 385143e1..641b704c 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -931,6 +931,14 @@ page_container %}
}
},
+ requestTokens: async function (amounts, paymentHash) {
+ const newTokens = await this.buildTokens(amounts, paymentHash)
+ this.tokens.push(newTokens)
+ this.storeTokens()
+ console.log('### this.tokens', this.tokens)
+ await this.fetchPromisesFromMint(paymentHash, newTokens.newTokens)
+ },
+
fetchPromisesFromMint: async function (hash, blindedMessages) {
console.log('### fetchPromisesFromMint', hash, blindedMessages)
try {
@@ -950,14 +958,6 @@ page_container %}
}
},
- requestTokens: async function (amounts, paymentHash) {
- const newTokens = await this.buildTokens(amounts, paymentHash)
- this.tokens.push(newTokens)
- this.storeTokens()
- console.log('### this.tokens', this.tokens)
- await this.fetchPromisesFromMint(paymentHash, newTokens.newTokens)
- },
-
buildAnsShowTokens: async function () {
this.sendData.tokens = 'toookeeens:' + this.sendData.amount
},
From e06ba3a52040da31445cc9534c74e3e596b866f1 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 17:42:47 +0300
Subject: [PATCH 300/696] feat: select coin limitted
---
lnbits/extensions/cashu/mint.py | 2 +-
.../cashu/templates/cashu/wallet.html | 99 ++++++++++++++-----
2 files changed, 77 insertions(+), 24 deletions(-)
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index 883d0fa9..4153fb30 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -71,7 +71,7 @@ async def melt(cashu: Cashu, proofs: List[Proof], invoice: str):
fees_msat = await check_fees(cashu.wallet, invoice_obj)
assert total_provided >= amount + fees_msat / 1000, Exception(
- f"Provided proofs (${total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)."
+ f"Provided proofs ({total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)."
)
await pay_invoice(
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 641b704c..02358d3d 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -306,7 +306,7 @@ page_container %}
Show Tokens p.amount === amount)
+ if (index >= 0) {
+ return {
+ promise: token.promises[index],
+ secret: token.secrets[index],
+ randomBlindingFactor: token.randomBlindingFactors[index]
+ }
+ }
+ }
+ },
+
+ constructProof: function (token) {},
+
buildTokens: async function (amounts, paymentHash) {
const blindedMessages = []
const secrets = []
@@ -1038,34 +1072,19 @@ page_container %}
const paidTokens = this.tokens.filter(t => t.promises?.length)
console.log('### paidTokens', paidTokens)
const proofs = paidTokens.map(token => {
- // const promiseIndex = token.promises
- // .map(p => `${p.amount}`)
- // .indexOf(`${amount}`)
return token.promises.map((promise, promiseIndex) => {
- // const promise = token.promises[promiseIndex]
console.log('### promise', promise)
const secret = token.secrets[promiseIndex]
const randomBlindingFactor =
token.randomBlindingFactors[promiseIndex]
- const C_ = nobleSecp256k1.Point.fromHex(promise['C_'])
- const A = this.keys[promise.amount] // todo
-
- console.log('#### C_', C_)
- console.log('#### A', A)
-
- const C = step3Bob(
- C_,
- randomBlindingFactor,
- nobleSecp256k1.Point.fromHex(A)
- )
-
- return {
- amount: promise.amount,
+ return this.promiseToProof(
+ promise.amount,
+ promise['C_'],
secret,
- C: C.toHex(true)
- }
+ randomBlindingFactor
+ )
})
})
const payload = {
@@ -1074,6 +1093,40 @@ page_container %}
invoice: this.sellData.bolt11
}
console.log('#### payload', JSON.stringify(payload))
+ try {
+ const {data} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/cashu/${this.mintId}/melt`,
+ '',
+ payload
+ )
+ this.$q.notify({
+ timeout: 5000,
+ message: 'Invoice paid'
+ })
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+
+ // C_hex = promise['C_']
+ // amount = promise.amount
+ promiseToProof: function (amount, C_hex, secret, randomBlindingFactor) {
+ const C_ = nobleSecp256k1.Point.fromHex(C_hex)
+ const A = this.keys[amount]
+
+ const C = step3Bob(
+ C_,
+ randomBlindingFactor,
+ nobleSecp256k1.Point.fromHex(A)
+ )
+
+ return {
+ amount,
+ secret,
+ C: C.toHex(true)
+ }
},
fetchMintKeys: async function () {
From b6d408ea713685025a6556f315b88ec3df019248 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 18:09:07 +0300
Subject: [PATCH 301/696] chore: aahaaa\
---
.../cashu/templates/cashu/wallet.html | 30 ++++++++++++++++---
1 file changed, 26 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 02358d3d..0da29d78 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -281,7 +281,7 @@ page_container %}
filled
dense
v-model.number="sendData.amount"
- label="Amount (sats) *"
+ label="Amount (tokens) *"
type="number"
class="q-mb-lg"
>
@@ -297,7 +297,7 @@ page_container %}
Show Tokens
+ Burn Tokens
Close t.promises?.length && t.secrets.find(s => s == token.secret)
+ )
+ if (secretIndex >= 0) {
+ token.blindedMessages?.splice(secretIndex, 1)
+ token.promises?.splice(secretIndex, 1)
+ token.randomBlindingFactors?.splice(secretIndex, 1)
+ token.secrets?.splice(secretIndex, 1)
+ }
+ }
+ console.log('### this.tokens', this.tokens)
+ },
+
findTokenForAmount: function (amount) {
for (const token of this.tokens) {
const index = token.promises?.findIndex(p => p.amount === amount)
From 429a898ff904b87d0e4fe7aed79b78e2e55767cd Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 18:24:29 +0300
Subject: [PATCH 302/696] feat: burn in hell token
---
.../cashu/templates/cashu/wallet.html | 26 ++++++++++++-------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 0da29d78..301f6e6f 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -989,17 +989,25 @@ page_container %}
},
burnTokens: function () {
- for (const token of this.sendData.tokens) {
- const secretIndex = this.tokens.findIndex(
- t => t.promises?.length && t.secrets.find(s => s == token.secret)
- )
- if (secretIndex >= 0) {
- token.blindedMessages?.splice(secretIndex, 1)
- token.promises?.splice(secretIndex, 1)
- token.randomBlindingFactors?.splice(secretIndex, 1)
- token.secrets?.splice(secretIndex, 1)
+ for (const sentToken of this.sendData.tokens) {
+ for (const token of this.tokens) {
+ if (token.status === 'paid') {
+ const secretIndex = token.secrets.findIndex(
+ s => s === sentToken.secret
+ )
+ console.log('### secretIndex', secretIndex)
+ if (secretIndex >= 0) {
+ token.blindedMessages?.splice(secretIndex, 1)
+ token.promises?.splice(secretIndex, 1)
+ token.randomBlindingFactors?.splice(secretIndex, 1)
+ token.secrets?.splice(secretIndex, 1)
+ // todo: persist
+ return
+ }
+ }
}
}
+
console.log('### this.tokens', this.tokens)
},
From 2179c7a3279d355c29ba8d31a52e7b4544276bc1 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 18:28:07 +0300
Subject: [PATCH 303/696] fix: do not stop secret search
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 301f6e6f..14290570 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1001,13 +1001,17 @@ page_container %}
token.promises?.splice(secretIndex, 1)
token.randomBlindingFactors?.splice(secretIndex, 1)
token.secrets?.splice(secretIndex, 1)
- // todo: persist
- return
}
}
}
}
+ this.$q.notify({
+ timeout: 5000,
+ message: 'Tokens burned'
+ })
+ this.storeTokens()
+ this.showSendTokens = false
console.log('### this.tokens', this.tokens)
},
From dc03692cfd2961d8f1c1505d5993c2d0350b5521 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 18:44:28 +0300
Subject: [PATCH 304/696] feat: show receive dialog
---
.../cashu/templates/cashu/wallet.html | 60 ++++++++++++++++++-
1 file changed, 58 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 14290570..6cf394a0 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -52,6 +52,7 @@ page_container %}
rounded
color="primary"
class="full-width"
+ @click="showReceiveTokensDialog"
>Receive
@@ -322,6 +323,43 @@ page_container %}
+
+
+
+
+
+
+
+ Paste tokens please
+
+
+
+
+
+
+ Accept Tokens
+ Close
+
+
+
+
+
@@ -436,9 +474,13 @@ page_container %}
tokens: '',
tokensBase64: ''
},
+ receiveData: {
+ tokensBase64: ''
+ },
showInvoiceDetails: false,
showPayInvoice: false,
showSendTokens: false,
+ showReceiveTokens: false,
tokens: [],
tab: 'tokens',
@@ -868,6 +910,11 @@ page_container %}
this.showSendTokens = true
},
+ showReceiveTokensDialog : function() {
+ this.receiveData.tokensBase64 = ''
+ this.showReceiveTokens = true
+ },
+
requestInvoice: async function () {
try {
const {data} = await LNbits.api.request(
@@ -1015,14 +1062,23 @@ page_container %}
console.log('### this.tokens', this.tokens)
},
+ acceptTokens: async function() {
+ this.showReceiveTokens = false
+ console.log('### receive tokens', this.receiveData.tokensBase64)
+ if (this.receiveData.tokensBase64) {
+ const tokensJson = atob(this.receiveData.tokensBase64)
+ console.log('tokensJson', tokensJson)
+ }
+
+ },
+
findTokenForAmount: function (amount) {
for (const token of this.tokens) {
const index = token.promises?.findIndex(p => p.amount === amount)
if (index >= 0) {
return {
promise: token.promises[index],
- secret: token.secrets[index],
- randomBlindingFactor: token.randomBlindingFactors[index]
+ secret: token.secrets[index]
}
}
}
From 38b84b32c07a4d78802637223740ade5b4a5ebda Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Tue, 11 Oct 2022 21:37:08 +0300
Subject: [PATCH 305/696] feat: try to accept tokens
---
.../cashu/templates/cashu/wallet.html | 75 +++++++++++++------
1 file changed, 54 insertions(+), 21 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 6cf394a0..af12182b 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -323,16 +323,12 @@ page_container %}
-
-
- Paste tokens please
+ Paste tokens please
-
Accept Tokens
-
@@ -910,7 +902,7 @@ page_container %}
this.showSendTokens = true
},
- showReceiveTokensDialog : function() {
+ showReceiveTokensDialog: function () {
this.receiveData.tokensBase64 = ''
this.showReceiveTokens = true
},
@@ -989,11 +981,11 @@ page_container %}
this.tokens.push(newTokens)
this.storeTokens()
console.log('### this.tokens', this.tokens)
- await this.fetchPromisesFromMint(paymentHash, newTokens.newTokens)
+ // await this.fetchPromisesFromMint(paymentHash, newTokens.newTokens)
},
fetchPromisesFromMint: async function (hash, blindedMessages) {
- console.log('### fetchPromisesFromMint', hash, blindedMessages)
+ console.log('### promises', hash, blindedMessages)
try {
const {data} = await LNbits.api.request(
'POST',
@@ -1003,7 +995,7 @@ page_container %}
blinded_messages: blindedMessages
}
)
- console.log('### fetchPromisesFromMint data', data)
+ console.log('### promises data', data)
return data
} catch (error) {
console.error(error)
@@ -1030,9 +1022,16 @@ page_container %}
}
}
console.log('### sendTokens', sendTokens)
- this.sendData.tokens = sendTokens
- this.sendData.tokensBase64 = btoa(JSON.stringify(sendTokens))
- return sendTokens
+ this.sendData.tokens = sendTokens.map(t => {
+ return this.promiseToProof(
+ t.promise.amount,
+ t.promise['C_'],
+ t.secret,
+ t.randomBlindingFactor
+ )
+ })
+ console.log('### this.sendData.tokens', this.sendData.tokens)
+ this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
},
burnTokens: function () {
@@ -1062,14 +1061,47 @@ page_container %}
console.log('### this.tokens', this.tokens)
},
- acceptTokens: async function() {
+ acceptTokens: async function () {
this.showReceiveTokens = false
console.log('### receive tokens', this.receiveData.tokensBase64)
if (this.receiveData.tokensBase64) {
const tokensJson = atob(this.receiveData.tokensBase64)
- console.log('tokensJson', tokensJson)
+ const proofs = JSON.parse(tokensJson)
+ const amount = proofs.reduce((s, t) => (s += t.amount), 0)
+ const amounts = splitAmount(amount)
+ const newTokens = await this.buildTokens(amounts)
+ console.log('newTokens', newTokens)
+
+ const payload = {
+ amount,
+ proofs,
+ outputs: {
+ blinded_messages: newTokens.blindedMessages
+ }
+ }
+
+ console.log('payload', JSON.stringify(payload))
+ try {
+ const {data} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/cashu/${this.mintId}/split`,
+ '',
+ payload
+ )
+
+ newTokens.promises = data.snd;
+ // console.log('split data', JSON.stringify(data.snd))
+ // for (let i =0 ;i < newTokens.length; i++) {
+ // Object.assign(newTokens[i], promises)
+ // }
+ console.log('newTokens 2', newTokens)
+ this.tokens.push(newTokens)
+ this.storeTokens()
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ }
}
-
},
findTokenForAmount: function (amount) {
@@ -1078,7 +1110,8 @@ page_container %}
if (index >= 0) {
return {
promise: token.promises[index],
- secret: token.secrets[index]
+ secret: token.secrets[index],
+ randomBlindingFactor: token.randomBlindingFactors[index]
}
}
}
From 8a4ef4f6dd9c13b4f800c19a52a7134a8f0f4492 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 12 Oct 2022 08:03:23 +0300
Subject: [PATCH 306/696] chore: code format
---
lnbits/extensions/cashu/core/b_dhke.py | 22 +++++++++----------
lnbits/extensions/cashu/mint_helper.py | 6 ++---
.../cashu/templates/cashu/wallet.html | 2 +-
3 files changed, 14 insertions(+), 16 deletions(-)
diff --git a/lnbits/extensions/cashu/core/b_dhke.py b/lnbits/extensions/cashu/core/b_dhke.py
index 8855481c..ff0bc515 100644
--- a/lnbits/extensions/cashu/core/b_dhke.py
+++ b/lnbits/extensions/cashu/core/b_dhke.py
@@ -30,17 +30,17 @@ import hashlib
from secp256k1 import PrivateKey, PublicKey
-def hash_to_curve(message: bytes):
- """Generates a point from the message hash and checks if the point lies on the curve.
- If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
- point = None
- msg_to_hash = message
- while point is None:
- try:
- _hash = hashlib.sha256(msg_to_hash).digest()
- point = PublicKey(b"\x02" + _hash, raw=True)
- except:
- msg_to_hash = _hash
+def hash_to_curve(message: bytes):
+ """Generates a point from the message hash and checks if the point lies on the curve.
+ If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
+ point = None
+ msg_to_hash = message
+ while point is None:
+ try:
+ _hash = hashlib.sha256(msg_to_hash).digest()
+ point = PublicKey(b"\x02" + _hash, raw=True)
+ except:
+ msg_to_hash = _hash
return point
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
index 4db92946..8e7e2275 100644
--- a/lnbits/extensions/cashu/mint_helper.py
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -1,5 +1,5 @@
-import hashlib
import base64
+import hashlib
from typing import List, Set
from .core.b_dhke import verify
@@ -41,13 +41,11 @@ async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
] # Get the correct key to check against
C = PublicKey(bytes.fromhex(proof.C), raw=True)
secret = base64.standard_b64decode(proof.secret)
- print('### secret', secret)
+ print("### secret", secret)
validMintSig = verify(secret_key, C, secret)
if validMintSig != True:
raise Exception(f"tokens not valid. Secret: {proof.secret}")
-
-
def verify_split_amount(amount: int):
"""Split amount like output amount can't be negative or too big."""
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index af12182b..f64a19b7 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1089,7 +1089,7 @@ page_container %}
payload
)
- newTokens.promises = data.snd;
+ newTokens.promises = data.snd
// console.log('split data', JSON.stringify(data.snd))
// for (let i =0 ;i < newTokens.length; i++) {
// Object.assign(newTokens[i], promises)
From e145bcf2238b9fc484d80ad5fc9f11aa92cb9e3f Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 12 Oct 2022 08:27:14 +0300
Subject: [PATCH 307/696] chore: add extension by default
---
lnbits/extensions/cashu/config.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
index 4f097e8b..e798e2ef 100644
--- a/lnbits/extensions/cashu/config.json
+++ b/lnbits/extensions/cashu/config.json
@@ -2,5 +2,6 @@
"name": "Cashu Ecash",
"short_description": "Ecash mints with LN peg in/out",
"icon": "approval",
- "contributors": ["arcbtc", "calle"]
+ "contributors": ["arcbtc", "calle"],
+ "hidden": true
}
From be3bef6da8ca3c9756e731ee327f6227459dd222 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 12 Oct 2022 08:32:41 +0300
Subject: [PATCH 308/696] chore: disable extension by default
---
lnbits/extensions/cashu/config.json | 2 +-
lnbits/extensions/cashu/config.json.example | 7 +++++++
2 files changed, 8 insertions(+), 1 deletion(-)
create mode 100644 lnbits/extensions/cashu/config.json.example
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
index e798e2ef..fe802034 100644
--- a/lnbits/extensions/cashu/config.json
+++ b/lnbits/extensions/cashu/config.json
@@ -3,5 +3,5 @@
"short_description": "Ecash mints with LN peg in/out",
"icon": "approval",
"contributors": ["arcbtc", "calle"],
- "hidden": true
+ "hidden": false
}
diff --git a/lnbits/extensions/cashu/config.json.example b/lnbits/extensions/cashu/config.json.example
new file mode 100644
index 00000000..e798e2ef
--- /dev/null
+++ b/lnbits/extensions/cashu/config.json.example
@@ -0,0 +1,7 @@
+{
+ "name": "Cashu Ecash",
+ "short_description": "Ecash mints with LN peg in/out",
+ "icon": "approval",
+ "contributors": ["arcbtc", "calle"],
+ "hidden": true
+}
From a85d27ed5c4519a46c9292ae359ea4828dd6119b Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 12 Oct 2022 06:46:50 +0100
Subject: [PATCH 309/696] missing comma
---
lnbits/extensions/cashu/migrations.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index d06baef1..0cc6a7d4 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -42,7 +42,7 @@ async def m001_initial(db):
amount INT,
B_b TEXT NOT NULL,
C_b TEXT NOT NULL,
- cashu_id TEXT NOT NULL
+ cashu_id TEXT NOT NULL,
UNIQUE (B_b)
);
"""
From 6873cf1be0c8b749dc369661a89cff70cccd6834 Mon Sep 17 00:00:00 2001
From: ben
Date: Wed, 12 Oct 2022 06:50:36 +0100
Subject: [PATCH 310/696] added props
---
lnbits/extensions/cashu/config.json | 2 +-
lnbits/extensions/cashu/templates/cashu/_cashu.html | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
index fe802034..5262ea62 100644
--- a/lnbits/extensions/cashu/config.json
+++ b/lnbits/extensions/cashu/config.json
@@ -2,6 +2,6 @@
"name": "Cashu Ecash",
"short_description": "Ecash mints with LN peg in/out",
"icon": "approval",
- "contributors": ["arcbtc", "calle"],
+ "contributors": ["arcbtc", "calle", "vlad"],
"hidden": false
}
diff --git a/lnbits/extensions/cashu/templates/cashu/_cashu.html b/lnbits/extensions/cashu/templates/cashu/_cashu.html
index f5af738f..03457ac7 100644
--- a/lnbits/extensions/cashu/templates/cashu/_cashu.html
+++ b/lnbits/extensions/cashu/templates/cashu/_cashu.html
@@ -1,4 +1,4 @@
-
+
@@ -7,7 +7,7 @@
Created by
- Calle . arcbtc, vlad , calle .
From e6e5dc68a5137872d04c7568a0ec9e39281f9e59 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Wed, 12 Oct 2022 23:16:39 +0200
Subject: [PATCH 311/696] lnbits migrations work
---
lnbits/extensions/cashu/__init__.py | 24 ++-
lnbits/extensions/cashu/config.json | 6 +-
lnbits/extensions/cashu/crud.py | 117 +++++++++++++-
lnbits/extensions/cashu/migrations.py | 80 +---------
lnbits/extensions/cashu/tasks.py | 15 ++
.../cashu/templates/cashu/index.html | 6 +-
.../cashu/templates/cashu/wallet.html | 121 +++++++-------
lnbits/extensions/cashu/views_api.py | 147 ++++++++++++++++--
8 files changed, 358 insertions(+), 158 deletions(-)
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index cb62ffca..b36e94ef 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -9,7 +9,26 @@ from lnbits.tasks import catch_everything_and_restart
db = Database("ext_cashu")
-cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
+import sys
+
+sys.path.append("/Users/cc/git/cashu")
+from cashu.mint.ledger import Ledger
+from .crud import LedgerCrud
+
+# db = Database("ext_cashu", LNBITS_DATA_FOLDER)
+
+ledger = Ledger(
+ db=db,
+ # seed=MINT_PRIVATE_KEY,
+ seed="asd",
+ derivation_path="0/0/0/1",
+ crud=LedgerCrud,
+)
+
+cashu_ext: APIRouter = APIRouter(prefix="/api/v1/cashu", tags=["cashu"])
+# from cashu.mint.router import router as cashu_router
+
+# cashu_ext.include_router(router=cashu_router)
cashu_static_files = [
{
@@ -24,11 +43,12 @@ def cashu_renderer():
return template_renderer(["lnbits/extensions/cashu/templates"])
-from .tasks import wait_for_paid_invoices
+from .tasks import wait_for_paid_invoices, startup_cashu_mint
from .views import * # noqa
from .views_api import * # noqa
def cashu_start():
loop = asyncio.get_event_loop()
+ loop.create_task(catch_everything_and_restart(startup_cashu_mint))
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
index 5262ea62..e71e6889 100644
--- a/lnbits/extensions/cashu/config.json
+++ b/lnbits/extensions/cashu/config.json
@@ -1,7 +1,9 @@
{
"name": "Cashu Ecash",
- "short_description": "Ecash mints with LN peg in/out",
+ "short_description": "Ecash mint and wallet",
"icon": "approval",
"contributors": ["arcbtc", "calle", "vlad"],
- "hidden": false
+ "hidden": false,
+ "migration_module": "cashu.mint.migrations",
+ "db_name": "cashu"
}
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index c8f3c72b..48b90d70 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -1,7 +1,8 @@
import os
import random
+import time
from binascii import hexlify, unhexlify
-from typing import List, Optional, Union
+from typing import List, Optional, Union, Any
from embit import bip32, bip39, ec, script
from embit.networks import NETWORKS
@@ -13,6 +14,49 @@ from . import db
from .core.base import Invoice
from .models import Cashu, Pegs, Promises, Proof
+from cashu.core.base import MintKeyset
+from lnbits.db import Database, Connection
+
+
+class LedgerCrud:
+ """
+ Database interface for Cashu mint.
+
+ This class needs to be overloaded by any app that imports the Cashu mint.
+ """
+
+ async def get_keyset(*args, **kwags):
+
+ return await get_keyset(*args, **kwags)
+
+ async def get_lightning_invoice(*args, **kwags):
+
+ return await get_lightning_invoice(*args, **kwags)
+
+ async def get_proofs_used(*args, **kwags):
+
+ return await get_proofs_used(*args, **kwags)
+
+ async def invalidate_proof(*args, **kwags):
+
+ return await invalidate_proof(*args, **kwags)
+
+ async def store_keyset(*args, **kwags):
+
+ return await store_keyset(*args, **kwags)
+
+ async def store_lightning_invoice(*args, **kwags):
+
+ return await store_lightning_invoice(*args, **kwags)
+
+ async def store_promise(*args, **kwags):
+
+ return await store_promise(*args, **kwags)
+
+ async def update_lightning_invoice(*args, **kwags):
+
+ return await update_lightning_invoice(*args, **kwags)
+
async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
cashu_id = urlsafe_short_hash()
@@ -120,9 +164,15 @@ async def get_promises(cashu_id) -> Optional[Cashu]:
return Promises(**row) if row else None
-async def get_proofs_used(cashu_id):
- rows = await db.fetchall(
- "SELECT secret from cashu.proofs_used WHERE cashu_id = ?", (cashu_id,)
+async def get_proofs_used(
+ db: Database,
+ conn: Optional[Connection] = None,
+):
+
+ rows = await (conn or db).fetchall(
+ """
+ SELECT secret from cashu.proofs_used
+ """
)
return [row[0] for row in rows]
@@ -184,3 +234,62 @@ async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
hash,
),
)
+
+
+##############################
+######### KEYSETS ############
+##############################
+
+
+async def store_keyset(
+ keyset: MintKeyset,
+ db: Database = None,
+ conn: Optional[Connection] = None,
+):
+
+ await (conn or db).execute( # type: ignore
+ """
+ INSERT INTO cashu.keysets
+ (id, derivation_path, valid_from, valid_to, first_seen, active, version)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ keyset.id,
+ keyset.derivation_path,
+ keyset.valid_from or db.timestamp_now,
+ keyset.valid_to or db.timestamp_now,
+ keyset.first_seen or db.timestamp_now,
+ True,
+ keyset.version,
+ ),
+ )
+
+
+async def get_keyset(
+ id: str = None,
+ derivation_path: str = "",
+ db: Database = None,
+ conn: Optional[Connection] = None,
+):
+ clauses = []
+ values: List[Any] = []
+ clauses.append("active = ?")
+ values.append(True)
+ if id:
+ clauses.append("id = ?")
+ values.append(id)
+ if derivation_path:
+ clauses.append("derivation_path = ?")
+ values.append(derivation_path)
+ where = ""
+ if clauses:
+ where = f"WHERE {' AND '.join(clauses)}"
+
+ rows = await (conn or db).fetchall( # type: ignore
+ f"""
+ SELECT * from cashu.keysets
+ {where}
+ """,
+ tuple(values),
+ )
+ return [MintKeyset.from_row(row) for row in rows]
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index 0cc6a7d4..019ee84d 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -1,79 +1 @@
-async def m001_initial(db):
- """
- Initial cashu table.
- """
- await db.execute(
- """
- CREATE TABLE cashu.cashu (
- id TEXT PRIMARY KEY,
- wallet TEXT NOT NULL,
- name TEXT NOT NULL,
- tickershort TEXT DEFAULT 'sats',
- fraction BOOL,
- maxsats INT,
- coins INT,
- prvkey TEXT NOT NULL,
- pubkey TEXT NOT NULL
- );
- """
- )
-
- """
- Initial cashus table.
- """
- await db.execute(
- """
- CREATE TABLE cashu.pegs (
- id TEXT PRIMARY KEY,
- wallet TEXT NOT NULL,
- inout BOOL NOT NULL,
- amount INT
- );
- """
- )
-
- """
- Initial cashus table.
- """
- await db.execute(
- """
- CREATE TABLE cashu.promises (
- id TEXT PRIMARY KEY,
- amount INT,
- B_b TEXT NOT NULL,
- C_b TEXT NOT NULL,
- cashu_id TEXT NOT NULL,
- UNIQUE (B_b)
- );
- """
- )
-
- """
- Initial cashus table.
- """
- await db.execute(
- """
- CREATE TABLE cashu.proofs_used (
- id TEXT PRIMARY KEY,
- amount INT,
- C TEXT NOT NULL,
- secret TEXT NOT NULL,
- cashu_id TEXT NOT NULL
- );
- """
- )
-
- await db.execute(
- """
- CREATE TABLE IF NOT EXISTS cashu.invoices (
- cashu_id TEXT NOT NULL,
- amount INTEGER NOT NULL,
- pr TEXT NOT NULL,
- hash TEXT NOT NULL,
- issued BOOL NOT NULL,
-
- UNIQUE (hash)
-
- );
- """
- )
+# this extension will use the migration_module module cashu.mint.migrations (see config.json)
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index fe00a591..9d2d7f15 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -9,6 +9,21 @@ from lnbits.tasks import internal_invoice_queue, register_invoice_listener
from .crud import get_cashu
+import sys
+
+sys.path.append("/Users/cc/git/cashu")
+# from cashu.mint import migrations
+# from cashu.core.migrations import migrate_databases
+from . import db, ledger
+
+
+async def startup_cashu_mint():
+ # await migrate_databases(db, migrations)
+ await ledger.load_used_proofs()
+ await ledger.init_keysets()
+ print(ledger.get_keyset())
+ pass
+
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index 4e5ba911..dcb2b4a7 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -120,7 +120,7 @@
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
- label="Wallet *"
+ label="Cashu wallet *"
>
@@ -229,7 +229,7 @@
{
name: 'wallet',
align: 'left',
- label: 'Wallet',
+ label: 'Cashu wallet',
field: 'wallet'
},
{
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index f64a19b7..4f12cd1c 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1,5 +1,5 @@
-{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Wallet
-{% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
+{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Cashu
+wallet {% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
page_container %}
@@ -14,9 +14,8 @@ page_container %}
rounded
color="secondary"
class="full-width"
- @click="showBuyTokensDialog"
- >Buy tokens
- (with sats)
+ @click="showInvoicesDialog"
+ >Create invoice
@@ -34,8 +33,7 @@ page_container %}
rounded
color="secondary"
class="full-width"
- >Sell tokens
-
(for sats)
+ >Pay invoice
@@ -115,11 +113,11 @@ page_container %}
{% raw %}
@@ -137,7 +135,7 @@ page_container %}
size="lg"
color="secondary"
class="q-mr-md cursor-pointer"
- @click="recheckBuyOrder(props.row.hash)"
+ @click="recheckInvoice(props.row.hash)"
>
Recheck
@@ -182,11 +180,15 @@ page_container %}
active-class="px-0"
indicator-color="transparent"
>
-
+
-
-
-
+
+
+
@@ -214,7 +216,7 @@ page_container %}
-
+
Copy invoice bt.hash === hash)
- console.log('### recheckBuyOrder.tokens', tokens)
+ console.log('### recheckInvoice.tokens', tokens)
if (!tokens) {
console.error('####### no token for hash', hash)
return
@@ -970,9 +972,9 @@ page_container %}
tokens.status = 'paid'
this.storeTokens()
- const buyOrder = this.buyOrders.find(bo => bo.hash === hash)
- buyOrder.status = 'paid'
- this.storeBuyOrders()
+ const invoice = this.invoicesCashu.find(bo => bo.hash === hash)
+ invoice.status = 'paid'
+ this.storeinvoicesCashu()
}
},
@@ -1261,8 +1263,11 @@ page_container %}
localStorage.setItem('cashu.keys', JSON.stringify(data))
},
- storeBuyOrders: function () {
- localStorage.setItem('cashu.buyOrders', JSON.stringify(this.buyOrders))
+ storeinvoicesCashu: function () {
+ localStorage.setItem(
+ 'cashu.invoicesCashu',
+ JSON.stringify(this.invoicesCashu)
+ )
},
storeTokens: function () {
localStorage.setItem(
@@ -1285,8 +1290,8 @@ page_container %}
!params.get('tsh') &&
!this.$q.localStorage.getItem('cashu.tickershort')
) {
- this.$q.localStorage.set('cashu.tickershort', 'CE')
- this.tickershort = 'CE'
+ this.$q.localStorage.set('cashu.tickershort', 'sats')
+ this.tickershort = 'sats'
} else if (params.get('tsh')) {
this.$q.localStorage.set('cashu.tickershort', params.get('tsh'))
this.tickershort = params.get('tsh')
@@ -1322,11 +1327,11 @@ page_container %}
this.keys = JSON.parse(keysJson)
}
- this.buyOrders = JSON.parse(
- localStorage.getItem('cashu.buyOrders') || '[]'
+ this.invoicesCashu = JSON.parse(
+ localStorage.getItem('cashu.invoicesCashu') || '[]'
)
this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
- console.log('### buyOrders', this.buyOrders)
+ console.log('### invoicesCashu', this.invoicesCashu)
console.table('### tokens', this.tokens)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 2f4b5ae4..e786cd2f 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -28,7 +28,8 @@ from .crud import (
store_promise,
update_lightning_invoice,
)
-from .ledger import mint, request_mint
+
+# from .ledger import mint, request_mint
from .mint import generate_promises, get_pubkeys, melt, split
from .models import (
Cashu,
@@ -207,15 +208,15 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
########################################
-@cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
-async def keys(cashu_id: str = Query(False)):
- """Get the public keys of the mint"""
- mint = await get_cashu(cashu_id)
- if mint is None:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
- )
- return get_pubkeys(mint.prvkey)
+# @cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
+# async def keys(cashu_id: str = Query(False)):
+# """Get the public keys of the mint"""
+# mint = await get_cashu(cashu_id)
+# if mint is None:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+# )
+# return get_pubkeys(mint.prvkey)
@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
@@ -355,3 +356,129 @@ async def split_proofs(payload: SplitRequest, cashu_id: str = Query(None)):
resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
print("### resp", json.dumps(resp, default=vars))
return resp
+
+
+##################################################################
+##################################################################
+# CASHU LIB
+##################################################################
+
+from typing import Dict, List, Union
+
+from fastapi import APIRouter
+from secp256k1 import PublicKey
+
+from cashu.core.base import (
+ BlindedSignature,
+ CheckFeesRequest,
+ CheckFeesResponse,
+ CheckRequest,
+ GetMeltResponse,
+ GetMintResponse,
+ MeltRequest,
+ MintRequest,
+ PostSplitResponse,
+ SplitRequest,
+)
+from cashu.core.errors import CashuError
+
+from . import db, ledger
+
+
+@cashu_ext.get("/keys")
+async def keys() -> dict[int, str]:
+ """Get the public keys of the mint"""
+ return ledger.get_keyset()
+
+
+@cashu_ext.get("/keysets")
+async def keysets() -> dict[str, list[str]]:
+ """Get all active keysets of the mint"""
+ return {"keysets": await ledger.keysets.get_ids()}
+
+
+@cashu_ext.get("/mint")
+async def request_mint(amount: int = 0) -> GetMintResponse:
+ """
+ Request minting of new tokens. The mint responds with a Lightning invoice.
+ This endpoint can be used for a Lightning invoice UX flow.
+
+ Call `POST /mint` after paying the invoice.
+ """
+ payment_request, payment_hash = await ledger.request_mint(amount)
+ print(f"Lightning invoice: {payment_request}")
+ resp = GetMintResponse(pr=payment_request, hash=payment_hash)
+ return resp
+
+
+@cashu_ext.post("/mint")
+async def mint(
+ payloads: MintRequest,
+ payment_hash: Union[str, None] = None,
+) -> Union[List[BlindedSignature], CashuError]:
+ """
+ Requests the minting of tokens belonging to a paid payment request.
+
+ Call this endpoint after `GET /mint`.
+ """
+ amounts = []
+ B_s = []
+ for payload in payloads.blinded_messages:
+ amounts.append(payload.amount)
+ B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
+ try:
+ promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
+ return promises
+ except Exception as exc:
+ return CashuError(error=str(exc))
+
+
+@cashu_ext.post("/melt")
+async def melt(payload: MeltRequest) -> GetMeltResponse:
+ """
+ Requests tokens to be destroyed and sent out via Lightning.
+ """
+ ok, preimage = await ledger.melt(payload.proofs, payload.invoice)
+ resp = GetMeltResponse(paid=ok, preimage=preimage)
+ return resp
+
+
+@cashu_ext.post("/check")
+async def check_spendable(payload: CheckRequest) -> Dict[int, bool]:
+ """Check whether a secret has been spent already or not."""
+ return await ledger.check_spendable(payload.proofs)
+
+
+@cashu_ext.post("/checkfees")
+async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
+ """
+ Responds with the fees necessary to pay a Lightning invoice.
+ Used by wallets for figuring out the fees they need to supply.
+ This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu).
+ """
+ fees_msat = await ledger.check_fees(payload.pr)
+ return CheckFeesResponse(fee=fees_msat / 1000)
+
+
+@cashu_ext.post("/split")
+async def split(
+ payload: SplitRequest,
+) -> Union[CashuError, PostSplitResponse]:
+ """
+ Requetst a set of tokens with amount "total" to be split into two
+ newly minted sets with amount "split" and "total-split".
+ """
+ proofs = payload.proofs
+ amount = payload.amount
+ outputs = payload.outputs.blinded_messages if payload.outputs else None
+ # backwards compatibility with clients < v0.2.2
+ assert outputs, Exception("no outputs provided.")
+ try:
+ split_return = await ledger.split(proofs, amount, outputs)
+ except Exception as exc:
+ return CashuError(error=str(exc))
+ if not split_return:
+ return CashuError(error="there was an error with the split")
+ frst_promises, scnd_promises = split_return
+ resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
+ return resp
From 4d1c96adc051b4725b755dd0802b1c1ec1aa229e Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Wed, 12 Oct 2022 23:17:21 +0200
Subject: [PATCH 312/696] allow external migration modules
---
lnbits/commands.py | 16 +-
lnbits/extensions/cashu/ledger.py | 351 ------------------------------
lnbits/helpers.py | 4 +
3 files changed, 14 insertions(+), 357 deletions(-)
delete mode 100644 lnbits/extensions/cashu/ledger.py
diff --git a/lnbits/commands.py b/lnbits/commands.py
index 83003314..1e0a98a1 100644
--- a/lnbits/commands.py
+++ b/lnbits/commands.py
@@ -69,14 +69,14 @@ async def migrate_databases():
(db_name, version, version),
)
- async def run_migration(db, migrations_module):
- db_name = migrations_module.__name__.split(".")[-2]
+ async def run_migration(db, migrations_module, db_name):
for key, migrate in migrations_module.__dict__.items():
match = match = matcher.match(key)
if match:
version = int(match.group(1))
if version > current_versions.get(db_name, 0):
logger.debug(f"running migration {db_name}.{version}")
+ logger.debug(f"db = {db}")
await migrate(db)
if db.schema == None:
@@ -101,20 +101,24 @@ async def migrate_databases():
rows = await (await conn.execute("SELECT * FROM dbversions")).fetchall()
current_versions = {row["db"]: row["version"] for row in rows}
matcher = re.compile(r"^m(\d\d\d)_")
- await run_migration(conn, core_migrations)
+ db_name = core_migrations.__name__.split(".")[-2]
+ await run_migration(conn, core_migrations, db_name)
for ext in get_valid_extensions():
try:
- ext_migrations = importlib.import_module(
- f"lnbits.extensions.{ext.code}.migrations"
+
+ module_str = (
+ ext.migration_module or f"lnbits.extensions.{ext.code}.migrations"
)
+ ext_migrations = importlib.import_module(module_str)
ext_db = importlib.import_module(f"lnbits.extensions.{ext.code}").db
+ db_name = ext.db_name or module_str.split(".")[-2]
except ImportError:
raise ImportError(
f"Please make sure that the extension `{ext.code}` has a migrations file."
)
async with ext_db.connect() as ext_conn:
- await run_migration(ext_conn, ext_migrations)
+ await run_migration(ext_conn, ext_migrations, db_name)
logger.info("✔️ All migrations done.")
diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py
deleted file mode 100644
index a28dc97a..00000000
--- a/lnbits/extensions/cashu/ledger.py
+++ /dev/null
@@ -1,351 +0,0 @@
-import hashlib
-from typing import List, Set
-
-from fastapi import Query
-from secp256k1 import PrivateKey, PublicKey
-
-from lnbits.core.services import check_transaction_status, create_invoice
-
-from .crud import get_cashu
-from .models import BlindedMessage, BlindedSignature, Invoice, Proof
-
-
-def _derive_keys(master_key: str, cashu_id: str = Query(None)):
- """Deterministic derivation of keys for 2^n values."""
- return {
- 2
- ** i: PrivateKey(
- hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
- .hexdigest()
- .encode("utf-8")[:32],
- raw=True,
- )
- for i in range(MAX_ORDER)
- }
-
-
-def _derive_pubkeys(keys: List[PrivateKey], cashu_id: str = Query(None)):
- return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
-
-
-async def _generate_promises(
- amounts: List[int], B_s: List[str], cashu_id: str = Query(None)
-):
- """Generates promises that sum to the given amount."""
- return [
- await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True))
- for (amount, B_) in zip(amounts, B_s)
- ]
-
-
-async def _generate_promise(amount: int, B_: PublicKey, cashu_id: str = Query(None)):
- """Generates a promise for given amount and returns a pair (amount, C')."""
- secret_key = self.keys[amount] # Get the correct key
- C_ = step2_bob(B_, secret_key)
- await store_promise(amount, B_=B_.serialize().hex(), C_=C_.serialize().hex())
- return BlindedSignature(amount=amount, C_=C_.serialize().hex())
-
-
-def _check_spendable(proof: Proof, cashu_id: str = Query(None)):
- """Checks whether the proof was already spent."""
- return not proof.secret in self.proofs_used
-
-
-def _verify_proof(proof: Proof, cashu_id: str = Query(None)):
- """Verifies that the proof of promise was issued by this ledger."""
- if not self._check_spendable(proof):
- raise Exception(f"tokens already spent. Secret: {proof.secret}")
- secret_key = self.keys[proof.amount] # Get the correct key to check against
- C = PublicKey(bytes.fromhex(proof.C), raw=True)
- return verify(secret_key, C, proof.secret)
-
-
-def _verify_outputs(
- total: int,
- amount: int,
- output_data: List[BlindedMessage],
- cashu_id: str = Query(None),
-):
- """Verifies the expected split was correctly computed"""
- fst_amt, snd_amt = total - amount, amount # we have two amounts to split to
- fst_outputs = amount_split(fst_amt)
- snd_outputs = amount_split(snd_amt)
- expected = fst_outputs + snd_outputs
- given = [o.amount for o in output_data]
- return given == expected
-
-
-def _verify_no_duplicates(
- proofs: List[Proof], output_data: List[BlindedMessage], cashu_id: str = Query(None)
-):
- secrets = [p.secret for p in proofs]
- if len(secrets) != len(list(set(secrets))):
- return False
- B_s = [od.B_ for od in output_data]
- if len(B_s) != len(list(set(B_s))):
- return False
- return True
-
-
-def _verify_split_amount(amount: int, cashu_id: str = Query(None)):
- """Split amount like output amount can't be negative or too big."""
- try:
- self._verify_amount(amount)
- except:
- # For better error message
- raise Exception("invalid split amount: " + str(amount))
-
-
-def _verify_amount(amount: int, cashu_id: str = Query(None)):
- """Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
- valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
- if not valid:
- raise Exception("invalid amount: " + str(amount))
- return amount
-
-
-def _verify_equation_balanced(
- proofs: List[Proof], outs: List[BlindedMessage], cashu_id: str = Query(None)
-):
- """Verify that Σoutputs - Σinputs = 0."""
- sum_inputs = sum(self._verify_amount(p.amount) for p in proofs)
- sum_outputs = sum(self._verify_amount(p.amount) for p in outs)
- assert sum_outputs - sum_inputs == 0
-
-
-def _get_output_split(amount: int, cashu_id: str):
- """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
- self._verify_amount(amount)
- bits_amt = bin(amount)[::-1][:-2]
- rv = []
- for (pos, bit) in enumerate(bits_amt):
- if bit == "1":
- rv.append(2**pos)
- return rv
-
-
-async def _invalidate_proofs(proofs: List[Proof], cashu_id: str = Query(None)):
- """Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
- # Mark proofs as used and prepare new promises
- proof_msgs = set([p.secret for p in proofs])
- self.proofs_used |= proof_msgs
- # store in db
- for p in proofs:
- await invalidate_proof(p)
-
-
-def get_pubkeys(cashu_id: str = Query(None)):
- """Returns public keys for possible amounts."""
- return {a: p.serialize().hex() for a, p in self.pub_keys.items()}
-
-
-async def request_mint(amount, cashu_id: str = Query(None)):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- """Returns Lightning invoice and stores it in the db."""
- payment_hash, payment_request = await create_invoice(
- wallet_id=cashu.wallet,
- amount=amount,
- memo=cashu.name,
- unhashed_description=cashu.name.encode("utf-8"),
- extra={"tag": "Cashu"},
- )
-
- invoice = Invoice(
- amount=amount, pr=payment_request, hash=payment_hash, issued=False
- )
- if not payment_request or not payment_hash:
- raise Exception(f"Could not create Lightning invoice.")
- return payment_request, payment_hash
-
-
-async def mint(
- B_s: List[PublicKey],
- amounts: List[int],
- payment_hash: str = Query(None),
- cashu_id: str = Query(None),
-):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- """Mints a promise for coins for B_."""
- # check if lightning invoice was paid
- if payment_hash:
- if not await check_transaction_status(
- wallet_id=cashu.wallet, payment_hash=payment_hash
- ):
- raise Exception("Lightning invoice not paid yet.")
-
- for amount in amounts:
- if amount not in [2**i for i in range(MAX_ORDER)]:
- raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
-
- promises = [
- await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
- ]
- return promises
-
-
-async def melt(
- proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Query(None)
-):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- """Invalidates proofs and pays a Lightning invoice."""
- # if not LIGHTNING:
- total = sum([p["amount"] for p in proofs])
- # check that lightning fees are included
- assert total + fee_reserve(amount * 1000) >= amount, Exception(
- "provided proofs not enough for Lightning payment."
- )
-
- status, payment_hash = await pay_invoice(
- wallet_id=link.wallet,
- payment_request=invoice,
- max_sat=amount,
- extra={"tag": "Ecash melt"},
- )
-
- if status == True:
- await self._invalidate_proofs(proofs)
- return status, payment_hash
-
-
-async def check_spendable(proofs: List[Proof], cashu_id: str = Query(None)):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- """Checks if all provided proofs are valid and still spendable (i.e. have not been spent)."""
- return {i: self._check_spendable(p) for i, p in enumerate(proofs)}
-
-
-async def split(
- proofs: List[Proof],
- amount: int,
- output_data: List[BlindedMessage],
- cashu_id: str = Query(None),
-):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- """Consumes proofs and prepares new promises based on the amount split."""
- self._verify_split_amount(amount)
- # Verify proofs are valid
- if not all([self._verify_proof(p) for p in proofs]):
- return False
-
- total = sum([p.amount for p in proofs])
-
- if not self._verify_no_duplicates(proofs, output_data):
- raise Exception("duplicate proofs or promises")
- if amount > total:
- raise Exception("split amount is higher than the total sum")
- if not self._verify_outputs(total, amount, output_data):
- raise Exception("split of promises is not as expected")
-
- # Mark proofs as used and prepare new promises
- await self._invalidate_proofs(proofs)
-
- outs_fst = amount_split(total - amount)
- outs_snd = amount_split(amount)
- B_fst = [od.B_ for od in output_data[: len(outs_fst)]]
- B_snd = [od.B_ for od in output_data[len(outs_fst) :]]
- prom_fst, prom_snd = await self._generate_promises(
- outs_fst, B_fst
- ), await self._generate_promises(outs_snd, B_snd)
- self._verify_equation_balanced(proofs, prom_fst + prom_snd)
- return prom_fst, prom_snd
-
-
-async def fee_reserve(amount_msat: int, cashu_id: str = Query(None)):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- """Function for calculating the Lightning fee reserve"""
- return max(
- int(LIGHTNING_RESERVE_FEE_MIN), int(amount_msat * LIGHTNING_FEE_PERCENT / 100.0)
- )
-
-
-async def amount_split(amount, cashu_id: str):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
- bits_amt = bin(amount)[::-1][:-2]
- rv = []
- for (pos, bit) in enumerate(bits_amt):
- if bit == "1":
- rv.append(2**pos)
- return rv
-
-
-async def hash_to_point(secret_msg, cashu_id: str = Query(None)):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- """Generates x coordinate from the message hash and checks if the point lies on the curve.
- If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
- point = None
- msg = secret_msg
- while point is None:
- _hash = hashlib.sha256(msg).hexdigest().encode("utf-8")
- try:
- # We construct compressed pub which has x coordinate encoded with even y
- _hash = list(_hash[:33]) # take the 33 bytes and get a list of bytes
- _hash[0] = 0x02 # set first byte to represent even y coord
- _hash = bytes(_hash)
- point = PublicKey(_hash, raw=True)
- except:
- msg = _hash
-
- return point
-
-
-async def step1_alice(secret_msg, cashu_id: str = Query(None)):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- secret_msg = secret_msg.encode("utf-8")
- Y = hash_to_point(secret_msg)
- r = PrivateKey()
- B_ = Y + r.pubkey
- return B_, r
-
-
-async def step2_bob(B_, a, cashu_id: str = Query(None)):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- C_ = B_.mult(a)
- return C_
-
-
-async def step3_alice(C_, r, A, cashu_id: str = Query(None)):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- C = C_ - A.mult(r)
- return C
-
-
-async def verify(a, C, secret_msg, cashu_id: str = Query(None)):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise Exception(f"Could not find Cashu")
-
- Y = hash_to_point(secret_msg.encode("utf-8"))
- return C == Y.mult(a)
diff --git a/lnbits/helpers.py b/lnbits/helpers.py
index 06d407ab..8d3099e4 100644
--- a/lnbits/helpers.py
+++ b/lnbits/helpers.py
@@ -20,6 +20,8 @@ class Extension(NamedTuple):
icon: Optional[str] = None
contributors: Optional[List[str]] = None
hidden: bool = False
+ migration_module: Optional[str] = None
+ db_name: Optional[str] = None
class ExtensionManager:
@@ -64,6 +66,8 @@ class ExtensionManager:
config.get("icon"),
config.get("contributors"),
config.get("hidden") or False,
+ config.get("migration_module"),
+ config.get("db_name"),
)
)
From 686076fa5c12d888cf9ea786035db84bccb9ce57 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 13 Oct 2022 00:24:15 +0200
Subject: [PATCH 313/696] allow for internal and external migrations
---
lnbits/extensions/cashu/__init__.py | 4 +-
lnbits/extensions/cashu/config.json | 4 +-
lnbits/extensions/cashu/crud.py | 145 ++++++++++++-------------
lnbits/extensions/cashu/migrations.py | 149 +++++++++++++++++++++++++-
lnbits/extensions/cashu/tasks.py | 6 +-
lnbits/extensions/cashu/views_api.py | 7 ++
6 files changed, 234 insertions(+), 81 deletions(-)
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index b36e94ef..81b18bc7 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -13,7 +13,8 @@ import sys
sys.path.append("/Users/cc/git/cashu")
from cashu.mint.ledger import Ledger
-from .crud import LedgerCrud
+
+# from .crud import LedgerCrud
# db = Database("ext_cashu", LNBITS_DATA_FOLDER)
@@ -22,7 +23,6 @@ ledger = Ledger(
# seed=MINT_PRIVATE_KEY,
seed="asd",
derivation_path="0/0/0/1",
- crud=LedgerCrud,
)
cashu_ext: APIRouter = APIRouter(prefix="/api/v1/cashu", tags=["cashu"])
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
index e71e6889..99838eb4 100644
--- a/lnbits/extensions/cashu/config.json
+++ b/lnbits/extensions/cashu/config.json
@@ -3,7 +3,5 @@
"short_description": "Ecash mint and wallet",
"icon": "approval",
"contributors": ["arcbtc", "calle", "vlad"],
- "hidden": false,
- "migration_module": "cashu.mint.migrations",
- "db_name": "cashu"
+ "hidden": false
}
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 48b90d70..31c185dc 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -11,6 +11,7 @@ from loguru import logger
from lnbits.helpers import urlsafe_short_hash
from . import db
+
from .core.base import Invoice
from .models import Cashu, Pegs, Promises, Proof
@@ -18,44 +19,44 @@ from cashu.core.base import MintKeyset
from lnbits.db import Database, Connection
-class LedgerCrud:
- """
- Database interface for Cashu mint.
+# class LedgerCrud:
+# """
+# Database interface for Cashu mint.
- This class needs to be overloaded by any app that imports the Cashu mint.
- """
+# This class needs to be overloaded by any app that imports the Cashu mint.
+# """
- async def get_keyset(*args, **kwags):
+# async def get_keyset(*args, **kwags):
- return await get_keyset(*args, **kwags)
+# return await get_keyset(*args, **kwags)
- async def get_lightning_invoice(*args, **kwags):
+# async def get_lightning_invoice(*args, **kwags):
- return await get_lightning_invoice(*args, **kwags)
+# return await get_lightning_invoice(*args, **kwags)
- async def get_proofs_used(*args, **kwags):
+# async def get_proofs_used(*args, **kwags):
- return await get_proofs_used(*args, **kwags)
+# return await get_proofs_used(*args, **kwags)
- async def invalidate_proof(*args, **kwags):
+# async def invalidate_proof(*args, **kwags):
- return await invalidate_proof(*args, **kwags)
+# return await invalidate_proof(*args, **kwags)
- async def store_keyset(*args, **kwags):
+# async def store_keyset(*args, **kwags):
- return await store_keyset(*args, **kwags)
+# return await store_keyset(*args, **kwags)
- async def store_lightning_invoice(*args, **kwags):
+# async def store_lightning_invoice(*args, **kwags):
- return await store_lightning_invoice(*args, **kwags)
+# return await store_lightning_invoice(*args, **kwags)
- async def store_promise(*args, **kwags):
+# async def store_promise(*args, **kwags):
- return await store_promise(*args, **kwags)
+# return await store_promise(*args, **kwags)
- async def update_lightning_invoice(*args, **kwags):
+# async def update_lightning_invoice(*args, **kwags):
- return await update_lightning_invoice(*args, **kwags)
+# return await update_lightning_invoice(*args, **kwags)
async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
@@ -132,9 +133,9 @@ async def delete_cashu(cashu_id) -> None:
await db.execute("DELETE FROM cashu.cashu WHERE id = ?", (cashu_id,))
-##########################################
-###############MINT STUFF#################
-##########################################
+# ##########################################
+# ###############MINT STUFF#################
+# ##########################################
async def store_promises(
@@ -241,55 +242,55 @@ async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
##############################
-async def store_keyset(
- keyset: MintKeyset,
- db: Database = None,
- conn: Optional[Connection] = None,
-):
+# async def store_keyset(
+# keyset: MintKeyset,
+# db: Database = None,
+# conn: Optional[Connection] = None,
+# ):
- await (conn or db).execute( # type: ignore
- """
- INSERT INTO cashu.keysets
- (id, derivation_path, valid_from, valid_to, first_seen, active, version)
- VALUES (?, ?, ?, ?, ?, ?, ?)
- """,
- (
- keyset.id,
- keyset.derivation_path,
- keyset.valid_from or db.timestamp_now,
- keyset.valid_to or db.timestamp_now,
- keyset.first_seen or db.timestamp_now,
- True,
- keyset.version,
- ),
- )
+# await (conn or db).execute( # type: ignore
+# """
+# INSERT INTO cashu.keysets
+# (id, derivation_path, valid_from, valid_to, first_seen, active, version)
+# VALUES (?, ?, ?, ?, ?, ?, ?)
+# """,
+# (
+# keyset.id,
+# keyset.derivation_path,
+# keyset.valid_from or db.timestamp_now,
+# keyset.valid_to or db.timestamp_now,
+# keyset.first_seen or db.timestamp_now,
+# True,
+# keyset.version,
+# ),
+# )
-async def get_keyset(
- id: str = None,
- derivation_path: str = "",
- db: Database = None,
- conn: Optional[Connection] = None,
-):
- clauses = []
- values: List[Any] = []
- clauses.append("active = ?")
- values.append(True)
- if id:
- clauses.append("id = ?")
- values.append(id)
- if derivation_path:
- clauses.append("derivation_path = ?")
- values.append(derivation_path)
- where = ""
- if clauses:
- where = f"WHERE {' AND '.join(clauses)}"
+# async def get_keyset(
+# id: str = None,
+# derivation_path: str = "",
+# db: Database = None,
+# conn: Optional[Connection] = None,
+# ):
+# clauses = []
+# values: List[Any] = []
+# clauses.append("active = ?")
+# values.append(True)
+# if id:
+# clauses.append("id = ?")
+# values.append(id)
+# if derivation_path:
+# clauses.append("derivation_path = ?")
+# values.append(derivation_path)
+# where = ""
+# if clauses:
+# where = f"WHERE {' AND '.join(clauses)}"
- rows = await (conn or db).fetchall( # type: ignore
- f"""
- SELECT * from cashu.keysets
- {where}
- """,
- tuple(values),
- )
- return [MintKeyset.from_row(row) for row in rows]
+# rows = await (conn or db).fetchall( # type: ignore
+# f"""
+# SELECT * from cashu.keysets
+# {where}
+# """,
+# tuple(values),
+# )
+# return [MintKeyset.from_row(row) for row in rows]
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index 019ee84d..6af53d5d 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -1 +1,148 @@
-# this extension will use the migration_module module cashu.mint.migrations (see config.json)
+async def m001_initial(db):
+ """
+ Initial cashu table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE cashu.cashu (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ name TEXT NOT NULL,
+ tickershort TEXT DEFAULT 'sats',
+ fraction BOOL,
+ maxsats INT,
+ coins INT,
+ prvkey TEXT NOT NULL,
+ pubkey TEXT NOT NULL
+ );
+ """
+ )
+
+ """
+ Initial cashus table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE cashu.pegs (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ inout BOOL NOT NULL,
+ amount INT
+ );
+ """
+ )
+
+
+# async def m001_initial(db):
+# await db.execute(
+# """
+# CREATE TABLE IF NOT EXISTS cashu.promises (
+# amount INTEGER NOT NULL,
+# B_b TEXT NOT NULL,
+# C_b TEXT NOT NULL,
+
+# UNIQUE (B_b)
+
+# );
+# """
+# )
+
+# await db.execute(
+# """
+# CREATE TABLE IF NOT EXISTS cashu.proofs_used (
+# amount INTEGER NOT NULL,
+# C TEXT NOT NULL,
+# secret TEXT NOT NULL,
+
+# UNIQUE (secret)
+
+# );
+# """
+# )
+
+# await db.execute(
+# """
+# CREATE TABLE IF NOT EXISTS cashu.invoices (
+# amount INTEGER NOT NULL,
+# pr TEXT NOT NULL,
+# hash TEXT NOT NULL,
+# issued BOOL NOT NULL,
+
+# UNIQUE (hash)
+
+# );
+# """
+# )
+
+# await db.execute(
+# """
+# CREATE VIEW IF NOT EXISTS cashu.balance_issued AS
+# SELECT COALESCE(SUM(s), 0) AS balance FROM (
+# SELECT SUM(amount) AS s
+# FROM cashu.promises
+# WHERE amount > 0
+# );
+# """
+# )
+
+# await db.execute(
+# """
+# CREATE VIEW IF NOT EXISTS cashu.balance_used AS
+# SELECT COALESCE(SUM(s), 0) AS balance FROM (
+# SELECT SUM(amount) AS s
+# FROM cashu.proofs_used
+# WHERE amount > 0
+# );
+# """
+# )
+
+# await db.execute(
+# """
+# CREATE VIEW IF NOT EXISTS cashu.balance AS
+# SELECT s_issued - s_used AS balance FROM (
+# SELECT bi.balance AS s_issued, bu.balance AS s_used
+# FROM cashu.balance_issued bi
+# CROSS JOIN balance_used bu
+# );
+# """
+# )
+
+
+# async def m003_mint_keysets(db):
+# """
+# Stores mint keysets from different mints and epochs.
+# """
+# await db.execute(
+# f"""
+# CREATE TABLE IF NOT EXISTS cashu.keysets (
+# id TEXT NOT NULL,
+# derivation_path TEXT,
+# valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
+# valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
+# first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
+# active BOOL DEFAULT TRUE,
+
+# UNIQUE (derivation_path)
+
+# );
+# """
+# )
+# await db.execute(
+# f"""
+# CREATE TABLE IF NOT EXISTS cashu.mint_pubkeys (
+# id TEXT NOT NULL,
+# amount INTEGER NOT NULL,
+# pubkey TEXT NOT NULL,
+
+# UNIQUE (id, pubkey)
+
+# );
+# """
+# )
+
+
+# async def m004_keysets_add_version(db):
+# """
+# Column that remembers with which version
+# """
+# await db.execute("ALTER TABLE cashu.keysets ADD COLUMN version TEXT")
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index 9d2d7f15..03bba895 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -12,13 +12,13 @@ from .crud import get_cashu
import sys
sys.path.append("/Users/cc/git/cashu")
-# from cashu.mint import migrations
-# from cashu.core.migrations import migrate_databases
+from cashu.mint import migrations
+from cashu.core.migrations import migrate_databases
from . import db, ledger
async def startup_cashu_mint():
- # await migrate_databases(db, migrations)
+ await migrate_databases(db, migrations)
await ledger.load_used_proofs()
await ledger.init_keysets()
print(ledger.get_keyset())
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index e786cd2f..e643c2ac 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -29,6 +29,13 @@ from .crud import (
update_lightning_invoice,
)
+# from cashu.mint.crud import (
+# get_lightning_invoice,
+# store_lightning_invoice,
+# store_promise,
+# update_lightning_invoice,
+# )
+
# from .ledger import mint, request_mint
from .mint import generate_promises, get_pubkeys, melt, split
from .models import (
From b41bd694a1d983dd00f52c0206573370b1f5f90f Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 13 Oct 2022 22:10:33 +0200
Subject: [PATCH 314/696] endpoints work
---
lnbits/extensions/cashu/crud.py | 20 +-
lnbits/extensions/cashu/migrations.py | 3 +-
lnbits/extensions/cashu/models.py | 4 +-
lnbits/extensions/cashu/views_api.py | 675 +++++++++++++-------------
poetry.lock | 15 +
pyproject.toml | 1 +
6 files changed, 371 insertions(+), 347 deletions(-)
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 31c185dc..a56eadf0 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -59,21 +59,14 @@ from lnbits.db import Database, Connection
# return await update_lightning_invoice(*args, **kwags)
-async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
- cashu_id = urlsafe_short_hash()
-
- entropy = bytes([random.getrandbits(8) for i in range(16)])
- mnemonic = bip39.mnemonic_from_bytes(entropy)
- seed = bip39.mnemonic_to_seed(mnemonic)
- root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
-
- bip44_xprv = root.derive("m/44h/1h/0h")
- bip44_xpub = bip44_xprv.to_public()
+async def create_cashu(
+ cashu_id: str, keyset_id: str, wallet_id: str, data: Cashu
+) -> Cashu:
await db.execute(
"""
- INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins, prvkey, pubkey)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
+ INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins, keyset_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
cashu_id,
@@ -83,8 +76,7 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
data.fraction,
data.maxsats,
data.coins,
- bip44_xprv.to_base58(),
- bip44_xpub.to_base58(),
+ keyset_id,
),
)
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index 6af53d5d..3f799534 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -12,8 +12,7 @@ async def m001_initial(db):
fraction BOOL,
maxsats INT,
coins INT,
- prvkey TEXT NOT NULL,
- pubkey TEXT NOT NULL
+ keyset_id TEXT NOT NULL
);
"""
)
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index 596db047..49fa9059 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -17,7 +17,7 @@ class Cashu(BaseModel):
pubkey: str = Query(None)
@classmethod
- def from_row(cls, row: Row) -> "TPoS":
+ def from_row(cls, row: Row):
return cls(**dict(row))
@@ -28,7 +28,7 @@ class Pegs(BaseModel):
amount: str
@classmethod
- def from_row(cls, row: Row) -> "TPoS":
+ def from_row(cls, row: Row):
return cls(**dict(row))
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index e643c2ac..f47f8744 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -15,6 +15,7 @@ from lnbits.core.services import check_transaction_status, create_invoice
from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.wallets.base import PaymentStatus
+from lnbits.helpers import urlsafe_short_hash
from . import cashu_ext
from .core.base import CashuError, PostSplitResponse, SplitRequest
@@ -29,13 +30,6 @@ from .crud import (
update_lightning_invoice,
)
-# from cashu.mint.crud import (
-# get_lightning_invoice,
-# store_lightning_invoice,
-# store_promise,
-# update_lightning_invoice,
-# )
-
# from .ledger import mint, request_mint
from .mint import generate_promises, get_pubkeys, melt, split
from .models import (
@@ -49,327 +43,8 @@ from .models import (
SplitPayload,
)
-########################################
-#################MINT CRUD##############
-########################################
-
-# todo: use /mints
-@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
-async def api_cashus(
- all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
-):
- wallet_ids = [wallet.wallet.id]
- if all_wallets:
- wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
-
- return [cashu.dict() for cashu in await get_cashus(wallet_ids)]
-
-
-@cashu_ext.post("/api/v1/cashus", status_code=HTTPStatus.CREATED)
-async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)):
- cashu = await create_cashu(wallet_id=wallet.wallet.id, data=data)
- logger.debug(cashu)
- return cashu.dict()
-
-
-@cashu_ext.post("/api/v1/cashus/upodatekeys", status_code=HTTPStatus.CREATED)
-async def api_cashu_update_keys(
- data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
-):
- cashu = await get_cashu(data.id)
-
- cashu = await create_cashu(wallet_id=wallet.wallet.id, data=data)
- logger.debug(cashu)
- return cashu.dict()
-
-
-@cashu_ext.delete("/api/v1/cashus/{cashu_id}")
-async def api_cashu_delete(
- cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
-):
- cashu = await get_cashu(cashu_id)
-
- if not cashu:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Cashu does not exist."
- )
-
- if cashu.wallet != wallet.wallet.id:
- raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your Cashu.")
-
- await delete_cashu(cashu_id)
- raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
-
-
-########################################
-#################????###################
-########################################
-@cashu_ext.post("/api/v1/cashus/{cashu_id}/invoices", status_code=HTTPStatus.CREATED)
-async def api_cashu_create_invoice(
- amount: int = Query(..., ge=1), tipAmount: int = None, cashu_id: str = None
-):
- cashu = await get_cashu(cashu_id)
-
- if not cashu:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
- )
-
- if tipAmount:
- amount += tipAmount
-
- try:
- payment_hash, payment_request = await create_invoice(
- wallet_id=cashu.wallet,
- amount=amount,
- memo=f"{cashu.name}",
- extra={"tag": "cashu", "tipAmount": tipAmount, "cashuId": cashu_id},
- )
- except Exception as e:
- raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-
- return {"payment_hash": payment_hash, "payment_request": payment_request}
-
-
-@cashu_ext.post(
- "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay",
- status_code=HTTPStatus.OK,
-)
-async def api_cashu_pay_invoice(
- lnurl_data: PayLnurlWData, payment_request: str = None, cashu_id: str = None
-):
- cashu = await get_cashu(cashu_id)
-
- if not cashu:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
- )
-
- lnurl = (
- lnurl_data.lnurl.replace("lnurlw://", "")
- .replace("lightning://", "")
- .replace("LIGHTNING://", "")
- .replace("lightning:", "")
- .replace("LIGHTNING:", "")
- )
-
- if lnurl.lower().startswith("lnurl"):
- lnurl = decode_lnurl(lnurl)
- else:
- lnurl = "https://" + lnurl
-
- async with httpx.AsyncClient() as client:
- try:
- r = await client.get(lnurl, follow_redirects=True)
- if r.is_error:
- lnurl_response = {"success": False, "detail": "Error loading"}
- else:
- resp = r.json()
- if resp["tag"] != "withdrawRequest":
- lnurl_response = {"success": False, "detail": "Wrong tag type"}
- else:
- r2 = await client.get(
- resp["callback"],
- follow_redirects=True,
- params={
- "k1": resp["k1"],
- "pr": payment_request,
- },
- )
- resp2 = r2.json()
- if r2.is_error:
- lnurl_response = {
- "success": False,
- "detail": "Error loading callback",
- }
- elif resp2["status"] == "ERROR":
- lnurl_response = {"success": False, "detail": resp2["reason"]}
- else:
- lnurl_response = {"success": True, "detail": resp2}
- except (httpx.ConnectError, httpx.RequestError):
- lnurl_response = {"success": False, "detail": "Unexpected error occurred"}
-
- return lnurl_response
-
-
-@cashu_ext.get(
- "/api/v1/cashus/{cashu_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK
-)
-async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
- cashu = await get_cashu(cashu_id)
- if not cashu:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
- )
- try:
- status = await api_payment(payment_hash)
-
- except Exception as exc:
- logger.error(exc)
- return {"paid": False}
- return status
-
-
-########################################
-#################MINT###################
-########################################
-
-
-# @cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
-# async def keys(cashu_id: str = Query(False)):
-# """Get the public keys of the mint"""
-# mint = await get_cashu(cashu_id)
-# if mint is None:
-# raise HTTPException(
-# status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
-# )
-# return get_pubkeys(mint.prvkey)
-
-
-@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
-async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
- """Request minting of tokens. Server responds with a Lightning invoice."""
-
- cashu = await get_cashu(cashu_id)
- if cashu is None:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
- )
-
- try:
- payment_hash, payment_request = await create_invoice(
- wallet_id=cashu.wallet,
- amount=amount,
- memo=f"{cashu.name}",
- extra={"tag": "cashu"},
- )
- invoice = Invoice(
- amount=amount, pr=payment_request, hash=payment_hash, issued=False
- )
- await store_lightning_invoice(cashu_id, invoice)
- except Exception as e:
- logger.error(e)
- raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-
- return {"pr": payment_request, "hash": payment_hash}
-
-
-@cashu_ext.post("/api/v1/cashu/{cashu_id}/mint")
-async def mint_coins(
- data: MintPayloads,
- cashu_id: str = Query(None),
- payment_hash: Union[str, None] = None,
-):
- """
- Requests the minting of tokens belonging to a paid payment request.
- Call this endpoint after `GET /mint`.
- """
- cashu: Cashu = await get_cashu(cashu_id)
- if cashu is None:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
- )
- invoice: Invoice = (
- None
- if payment_hash == None
- else await get_lightning_invoice(cashu_id, payment_hash)
- )
- if invoice is None:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not have this invoice."
- )
- if invoice.issued == True:
- raise HTTPException(
- status_code=HTTPStatus.PAYMENT_REQUIRED,
- detail="Tokens already issued for this invoice.",
- )
-
- total_requested = sum([bm.amount for bm in data.blinded_messages])
- if total_requested > invoice.amount:
- raise HTTPException(
- status_code=HTTPStatus.PAYMENT_REQUIRED,
- detail=f"Requested amount too high: {total_requested}. Invoice amount: {invoice.amount}",
- )
-
- status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
- # todo: revert to: status.paid != True:
- if status.paid != True:
- raise HTTPException(
- status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
- )
- try:
- await update_lightning_invoice(cashu_id, payment_hash, True)
-
- amounts = []
- B_s = []
- for payload in data.blinded_messages:
- amounts.append(payload.amount)
- B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
-
- promises = await generate_promises(cashu.prvkey, amounts, B_s)
- for amount, B_, p in zip(amounts, B_s, promises):
- await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
-
- return promises
- except Exception as e:
- logger.error(e)
- raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-
-
-@cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
-async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
- """Invalidates proofs and pays a Lightning invoice."""
- cashu: Cashu = await get_cashu(cashu_id)
- if cashu is None:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
- )
- try:
- ok, preimage = await melt(cashu, payload.proofs, payload.invoice)
- return {"paid": ok, "preimage": preimage}
- except Exception as e:
- logger.error(e)
- raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-
-
-@cashu_ext.post("/api/v1/cashu/{cashu_id}/check")
-async def check_spendable_coins(payload: CheckPayload, cashu_id: str = Query(None)):
- return await check_spendable(payload.proofs, cashu_id)
-
-
-@cashu_ext.post("/api/v1/cashu/{cashu_id}/split")
-async def split_proofs(payload: SplitRequest, cashu_id: str = Query(None)):
- """
- Requetst a set of tokens with amount "total" to be split into two
- newly minted sets with amount "split" and "total-split".
- """
- print("### RECEIVE")
- print("payload", json.dumps(payload, default=vars))
- cashu: Cashu = await get_cashu(cashu_id)
- if cashu is None:
- raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
- )
- proofs = payload.proofs
- amount = payload.amount
- outputs = payload.outputs.blinded_messages if payload.outputs else None
- try:
- split_return = await split(cashu, proofs, amount, outputs)
- except Exception as exc:
- raise CashuError(error=str(exc))
- if not split_return:
- return {"error": "there was a problem with the split."}
- frst_promises, scnd_promises = split_return
- resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
- print("### resp", json.dumps(resp, default=vars))
- return resp
-
-
-##################################################################
-##################################################################
-# CASHU LIB
-##################################################################
+############### IMPORT CALLE
from typing import Dict, List, Union
from fastapi import APIRouter
@@ -389,11 +64,54 @@ from cashu.core.base import (
)
from cashu.core.errors import CashuError
+
+########################################
+############### LNBITS MINTS ###########
+########################################
+
+# todo: use /mints
+@cashu_ext.get("/cashus", status_code=HTTPStatus.OK)
+async def api_cashus(
+ all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
+):
+ wallet_ids = [wallet.wallet.id]
+ if all_wallets:
+ wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
+
+ return [cashu.dict() for cashu in await get_cashus(wallet_ids)]
+
+
+@cashu_ext.post("/cashus", status_code=HTTPStatus.CREATED)
+async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)):
+ cashu_id = urlsafe_short_hash()
+ # generate a new keyset in cashu
+ keyset = await ledger.load_keyset(cashu_id)
+
+ cashu = await create_cashu(
+ cashu_id=cashu_id, keyset_id=keyset.id, wallet_id=wallet.wallet.id, data=data
+ )
+ logger.debug(cashu)
+ return cashu.dict()
+
+
+#######################################
+########### CASHU ENDPOINTS ###########
+#######################################
+
+
from . import db, ledger
-@cashu_ext.get("/keys")
-async def keys() -> dict[int, str]:
+@cashu_ext.get("{cashu_id}/keys", status_code=HTTPStatus.OK)
+async def keys(cashu_id: str = None) -> dict[int, str]:
+
+ cashu = await get_cashu(cashu_id)
+
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+ )
+
"""Get the public keys of the mint"""
return ledger.get_keyset()
@@ -489,3 +207,302 @@ async def split(
frst_promises, scnd_promises = split_return
resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
return resp
+
+
+# @cashu_ext.post("/api/v1/cashus/upodatekeys", status_code=HTTPStatus.CREATED)
+# async def api_cashu_update_keys(
+# data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
+# ):
+# cashu = await get_cashu(data.id)
+
+# cashu = await create_cashu(wallet_id=wallet.wallet.id, data=data)
+# logger.debug(cashu)
+# return cashu.dict()
+
+
+# @cashu_ext.delete("/api/v1/cashus/{cashu_id}")
+# async def api_cashu_delete(
+# cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
+# ):
+# cashu = await get_cashu(cashu_id)
+
+# if not cashu:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="Cashu does not exist."
+# )
+
+# if cashu.wallet != wallet.wallet.id:
+# raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your Cashu.")
+
+# await delete_cashu(cashu_id)
+# raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+
+
+# ########################################
+# #################????###################
+# ########################################
+# @cashu_ext.post("/api/v1/cashus/{cashu_id}/invoices", status_code=HTTPStatus.CREATED)
+# async def api_cashu_create_invoice(
+# amount: int = Query(..., ge=1), tipAmount: int = None, cashu_id: str = None
+# ):
+# cashu = await get_cashu(cashu_id)
+
+# if not cashu:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+# )
+
+# if tipAmount:
+# amount += tipAmount
+
+# try:
+# payment_hash, payment_request = await create_invoice(
+# wallet_id=cashu.wallet,
+# amount=amount,
+# memo=f"{cashu.name}",
+# extra={"tag": "cashu", "tipAmount": tipAmount, "cashuId": cashu_id},
+# )
+# except Exception as e:
+# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+
+# return {"payment_hash": payment_hash, "payment_request": payment_request}
+
+
+# @cashu_ext.post(
+# "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay",
+# status_code=HTTPStatus.OK,
+# )
+# async def api_cashu_pay_invoice(
+# lnurl_data: PayLnurlWData, payment_request: str = None, cashu_id: str = None
+# ):
+# cashu = await get_cashu(cashu_id)
+
+# if not cashu:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+# )
+
+# lnurl = (
+# lnurl_data.lnurl.replace("lnurlw://", "")
+# .replace("lightning://", "")
+# .replace("LIGHTNING://", "")
+# .replace("lightning:", "")
+# .replace("LIGHTNING:", "")
+# )
+
+# if lnurl.lower().startswith("lnurl"):
+# lnurl = decode_lnurl(lnurl)
+# else:
+# lnurl = "https://" + lnurl
+
+# async with httpx.AsyncClient() as client:
+# try:
+# r = await client.get(lnurl, follow_redirects=True)
+# if r.is_error:
+# lnurl_response = {"success": False, "detail": "Error loading"}
+# else:
+# resp = r.json()
+# if resp["tag"] != "withdrawRequest":
+# lnurl_response = {"success": False, "detail": "Wrong tag type"}
+# else:
+# r2 = await client.get(
+# resp["callback"],
+# follow_redirects=True,
+# params={
+# "k1": resp["k1"],
+# "pr": payment_request,
+# },
+# )
+# resp2 = r2.json()
+# if r2.is_error:
+# lnurl_response = {
+# "success": False,
+# "detail": "Error loading callback",
+# }
+# elif resp2["status"] == "ERROR":
+# lnurl_response = {"success": False, "detail": resp2["reason"]}
+# else:
+# lnurl_response = {"success": True, "detail": resp2}
+# except (httpx.ConnectError, httpx.RequestError):
+# lnurl_response = {"success": False, "detail": "Unexpected error occurred"}
+
+# return lnurl_response
+
+
+# @cashu_ext.get(
+# "/api/v1/cashus/{cashu_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK
+# )
+# async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
+# cashu = await get_cashu(cashu_id)
+# if not cashu:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+# )
+# try:
+# status = await api_payment(payment_hash)
+
+# except Exception as exc:
+# logger.error(exc)
+# return {"paid": False}
+# return status
+
+
+# ########################################
+# #################MINT###################
+# ########################################
+
+
+# # @cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
+# # async def keys(cashu_id: str = Query(False)):
+# # """Get the public keys of the mint"""
+# # mint = await get_cashu(cashu_id)
+# # if mint is None:
+# # raise HTTPException(
+# # status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+# # )
+# # return get_pubkeys(mint.prvkey)
+
+
+# @cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
+# async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
+# """Request minting of tokens. Server responds with a Lightning invoice."""
+
+# cashu = await get_cashu(cashu_id)
+# if cashu is None:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+# )
+
+# try:
+# payment_hash, payment_request = await create_invoice(
+# wallet_id=cashu.wallet,
+# amount=amount,
+# memo=f"{cashu.name}",
+# extra={"tag": "cashu"},
+# )
+# invoice = Invoice(
+# amount=amount, pr=payment_request, hash=payment_hash, issued=False
+# )
+# await store_lightning_invoice(cashu_id, invoice)
+# except Exception as e:
+# logger.error(e)
+# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+
+# return {"pr": payment_request, "hash": payment_hash}
+
+
+# @cashu_ext.post("/api/v1/cashu/{cashu_id}/mint")
+# async def mint_coins(
+# data: MintPayloads,
+# cashu_id: str = Query(None),
+# payment_hash: Union[str, None] = None,
+# ):
+# """
+# Requests the minting of tokens belonging to a paid payment request.
+# Call this endpoint after `GET /mint`.
+# """
+# cashu: Cashu = await get_cashu(cashu_id)
+# if cashu is None:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+# )
+# invoice: Invoice = (
+# None
+# if payment_hash == None
+# else await get_lightning_invoice(cashu_id, payment_hash)
+# )
+# if invoice is None:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="Mint does not have this invoice."
+# )
+# if invoice.issued == True:
+# raise HTTPException(
+# status_code=HTTPStatus.PAYMENT_REQUIRED,
+# detail="Tokens already issued for this invoice.",
+# )
+
+# total_requested = sum([bm.amount for bm in data.blinded_messages])
+# if total_requested > invoice.amount:
+# raise HTTPException(
+# status_code=HTTPStatus.PAYMENT_REQUIRED,
+# detail=f"Requested amount too high: {total_requested}. Invoice amount: {invoice.amount}",
+# )
+
+# status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
+# # todo: revert to: status.paid != True:
+# if status.paid != True:
+# raise HTTPException(
+# status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
+# )
+# try:
+# await update_lightning_invoice(cashu_id, payment_hash, True)
+
+# amounts = []
+# B_s = []
+# for payload in data.blinded_messages:
+# amounts.append(payload.amount)
+# B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
+
+# promises = await generate_promises(cashu.prvkey, amounts, B_s)
+# for amount, B_, p in zip(amounts, B_s, promises):
+# await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
+
+# return promises
+# except Exception as e:
+# logger.error(e)
+# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+
+
+# @cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
+# async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
+# """Invalidates proofs and pays a Lightning invoice."""
+# cashu: Cashu = await get_cashu(cashu_id)
+# if cashu is None:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+# )
+# try:
+# ok, preimage = await melt(cashu, payload.proofs, payload.invoice)
+# return {"paid": ok, "preimage": preimage}
+# except Exception as e:
+# logger.error(e)
+# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+
+
+# @cashu_ext.post("/api/v1/cashu/{cashu_id}/check")
+# async def check_spendable_coins(payload: CheckPayload, cashu_id: str = Query(None)):
+# return await check_spendable(payload.proofs, cashu_id)
+
+
+# @cashu_ext.post("/api/v1/cashu/{cashu_id}/split")
+# async def split_proofs(payload: SplitRequest, cashu_id: str = Query(None)):
+# """
+# Requetst a set of tokens with amount "total" to be split into two
+# newly minted sets with amount "split" and "total-split".
+# """
+# print("### RECEIVE")
+# print("payload", json.dumps(payload, default=vars))
+# cashu: Cashu = await get_cashu(cashu_id)
+# if cashu is None:
+# raise HTTPException(
+# status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+# )
+# proofs = payload.proofs
+# amount = payload.amount
+# outputs = payload.outputs.blinded_messages if payload.outputs else None
+# try:
+# split_return = await split(cashu, proofs, amount, outputs)
+# except Exception as exc:
+# raise CashuError(error=str(exc))
+# if not split_return:
+# return {"error": "there was a problem with the split."}
+# frst_promises, scnd_promises = split_return
+# resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
+# print("### resp", json.dumps(resp, default=vars))
+# return resp
+
+
+##################################################################
+##################################################################
+# CASHU LIB
+##################################################################
diff --git a/poetry.lock b/poetry.lock
index 079b2b99..57d3f6d6 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -871,6 +871,17 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""
[package.extras]
full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
+[[package]]
+name = "starlette-context"
+version = "0.3.4"
+description = "Access context in Starlette"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+starlette = "*"
+
[[package]]
name = "tomli"
version = "2.0.1"
@@ -1790,6 +1801,10 @@ starlette = [
{file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
{file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
]
+starlette-context = [
+ {file = "starlette_context-0.3.4-py37-none-any.whl", hash = "sha256:b16bf17bd3ead7ded2f458aebf7f913744b9cf28305e16c69b435a6c6ddf1135"},
+ {file = "starlette_context-0.3.4.tar.gz", hash = "sha256:2d28e1838302fb5d5adacadc10fb73fb2d5cca1f0aa1e279698701cc96f1567c"},
+]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
diff --git a/pyproject.toml b/pyproject.toml
index b99bb353..d15a4fc5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,6 +63,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
+starlette-context = "^0.3.4"
[tool.poetry.dev-dependencies]
isort = "^5.10.1"
From 5b1ee554df9feb1795dcfd243226db9958645729 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 14 Oct 2022 00:06:02 +0200
Subject: [PATCH 315/696] more endpoints working
---
lnbits/extensions/cashu/models.py | 3 +-
lnbits/extensions/cashu/views_api.py | 189 ++++++++++++++++++++++-----
2 files changed, 154 insertions(+), 38 deletions(-)
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index 49fa9059..c820d12e 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -13,8 +13,7 @@ class Cashu(BaseModel):
fraction: bool = Query(None)
maxsats: int = Query(0)
coins: int = Query(0)
- prvkey: str = Query(None)
- pubkey: str = Query(None)
+ keyset_id: str = Query(None)
@classmethod
def from_row(cls, row: Row):
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index f47f8744..2d28c78b 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -1,6 +1,7 @@
import json
from http import HTTPStatus
from typing import Union
+import math
import httpx
from fastapi import Query
@@ -9,14 +10,21 @@ from lnurl import decode as decode_lnurl
from loguru import logger
from secp256k1 import PublicKey
from starlette.exceptions import HTTPException
+from lnbits import bolt11
from lnbits.core.crud import get_user
-from lnbits.core.services import check_transaction_status, create_invoice
+from lnbits.core.services import (
+ check_transaction_status,
+ create_invoice,
+ fee_reserve,
+ pay_invoice,
+)
+
from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.wallets.base import PaymentStatus
from lnbits.helpers import urlsafe_short_hash
-
+from lnbits.core.crud import check_internal
from . import cashu_ext
from .core.base import CashuError, PostSplitResponse, SplitRequest
from .crud import (
@@ -47,10 +55,10 @@ from .models import (
############### IMPORT CALLE
from typing import Dict, List, Union
-from fastapi import APIRouter
from secp256k1 import PublicKey
from cashu.core.base import (
+ Proof,
BlindedSignature,
CheckFeesRequest,
CheckFeesResponse,
@@ -63,7 +71,9 @@ from cashu.core.base import (
SplitRequest,
)
from cashu.core.errors import CashuError
+from . import db, ledger
+LIGHTNING = False
########################################
############### LNBITS MINTS ###########
@@ -99,63 +109,170 @@ async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key
#######################################
-from . import db, ledger
-
-
-@cashu_ext.get("{cashu_id}/keys", status_code=HTTPStatus.OK)
-async def keys(cashu_id: str = None) -> dict[int, str]:
-
- cashu = await get_cashu(cashu_id)
+@cashu_ext.get("/{cashu_id}/keys", status_code=HTTPStatus.OK)
+async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
+ """Get the public keys of the mint"""
+ cashu: Union[Cashu, None] = await get_cashu(cashu_id)
if not cashu:
raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
)
- """Get the public keys of the mint"""
- return ledger.get_keyset()
+ return ledger.get_keyset(keyset_id=cashu.keyset_id)
-@cashu_ext.get("/keysets")
-async def keysets() -> dict[str, list[str]]:
- """Get all active keysets of the mint"""
- return {"keysets": await ledger.keysets.get_ids()}
-
-
-@cashu_ext.get("/mint")
-async def request_mint(amount: int = 0) -> GetMintResponse:
+@cashu_ext.get("/{cashu_id}/mint")
+async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintResponse:
"""
Request minting of new tokens. The mint responds with a Lightning invoice.
This endpoint can be used for a Lightning invoice UX flow.
Call `POST /mint` after paying the invoice.
"""
- payment_request, payment_hash = await ledger.request_mint(amount)
+ cashu: Union[Cashu, None] = await get_cashu(cashu_id)
+
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
+
+ # create an invoice that the wallet needs to pay
+ try:
+ payment_hash, payment_request = await create_invoice(
+ wallet_id=cashu.wallet,
+ amount=amount,
+ memo=f"{cashu.name}",
+ extra={"tag": "cashu"},
+ )
+ invoice = Invoice(
+ amount=amount, pr=payment_request, hash=payment_hash, issued=False
+ )
+ # await store_lightning_invoice(cashu_id, invoice)
+ await ledger.crud.store_lightning_invoice(invoice=invoice, db=ledger.db)
+ except Exception as e:
+ logger.error(e)
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+
print(f"Lightning invoice: {payment_request}")
resp = GetMintResponse(pr=payment_request, hash=payment_hash)
+ # return {"pr": payment_request, "hash": payment_hash}
return resp
-@cashu_ext.post("/mint")
-async def mint(
- payloads: MintRequest,
- payment_hash: Union[str, None] = None,
-) -> Union[List[BlindedSignature], CashuError]:
+@cashu_ext.post("/{cashu_id}/mint")
+async def mint_coins(
+ data: MintRequest,
+ cashu_id: str = Query(None),
+ payment_hash: str = Query(None),
+):
"""
Requests the minting of tokens belonging to a paid payment request.
-
Call this endpoint after `GET /mint`.
"""
- amounts = []
- B_s = []
- for payload in payloads.blinded_messages:
- amounts.append(payload.amount)
- B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
+ cashu: Union[Cashu, None] = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
+
+ if LIGHTNING:
+ invoice: Invoice = await ledger.crud.get_lightning_invoice(
+ db=ledger.db, hash=payment_hash
+ )
+ if invoice is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND,
+ detail="Mint does not have this invoice.",
+ )
+ if invoice.issued == True:
+ raise HTTPException(
+ status_code=HTTPStatus.PAYMENT_REQUIRED,
+ detail="Tokens already issued for this invoice.",
+ )
+
+ total_requested = sum([bm.amount for bm in data.blinded_messages])
+ if total_requested > invoice.amount:
+ raise HTTPException(
+ status_code=HTTPStatus.PAYMENT_REQUIRED,
+ detail=f"Requested amount too high: {total_requested}. Invoice amount: {invoice.amount}",
+ )
+
+ status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
+ # todo: revert to: status.paid != True:
+ if status.paid != True:
+ raise HTTPException(
+ status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
+ )
try:
- promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
+ await ledger.crud.update_lightning_invoice(
+ db=ledger.db, hash=payment_hash, issued=True
+ )
+ keyset = ledger.keysets.keysets[cashu.keyset_id]
+
+ promises = await ledger._generate_promises(
+ B_s=data.blinded_messages, keyset=keyset
+ )
return promises
- except Exception as exc:
- return CashuError(error=str(exc))
+ except Exception as e:
+ logger.error(e)
+ raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
+
+
+@cashu_ext.post("/{cashu_id}/melt")
+async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)):
+ """Invalidates proofs and pays a Lightning invoice."""
+ cashu: Union[None, Cashu] = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
+ proofs = payload.proofs
+ invoice = payload.invoice
+ # async def melt(cashu: Cashu, proofs: List[Proof], invoice: str):
+ # """Invalidates proofs and pays a Lightning invoice."""
+
+ # !!!!!!! MAKE SURE THAT PROOFS ARE ONLY FROM THIS CASHU KEYSET ID
+ # THIS IS NECESSARY BECAUSE THE CASHU BACKEND WILL ACCEPT ANY VALID
+ # TOKENS
+ assert all([p.id == cashu.keyset_id for p in proofs]), HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST,
+ detail="Proofs include tokens from other mint.",
+ )
+
+ assert all([ledger._verify_proof(p) for p in proofs]), HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST,
+ detail="Could not verify proofs.",
+ )
+
+ total_provided = sum([p["amount"] for p in proofs])
+ invoice_obj = bolt11.decode(invoice)
+ amount = math.ceil(invoice_obj.amount_msat / 1000)
+
+ internal_checking_id = await check_internal(invoice_obj.payment_hash)
+
+ if not internal_checking_id:
+ fees_msat = fee_reserve(invoice_obj.amount_msat)
+ else:
+ fees_msat = 0
+ assert total_provided >= amount + fees_msat / 1000, Exception(
+ f"Provided proofs ({total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)."
+ )
+
+ await pay_invoice(
+ wallet_id=cashu.wallet,
+ payment_request=invoice,
+ description=f"pay cashu invoice",
+ extra={"tag": "cashu", "cahsu_name": cashu.name},
+ )
+
+ status: PaymentStatus = await check_transaction_status(
+ cashu.wallet, invoice_obj.payment_hash
+ )
+ if status.paid == True:
+ await ledger._invalidate_proofs(proofs)
+ return status.paid, status.preimage
+ return False, ""
@cashu_ext.post("/melt")
From 53db275fe034c02a433913ad03739ac8ffa0176b Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 14 Oct 2022 01:01:43 +0200
Subject: [PATCH 316/696] cleanup
---
lnbits/extensions/cashu/__init__.py | 22 +--
lnbits/extensions/cashu/crud.py | 228 ++++++++++---------------
lnbits/extensions/cashu/migrations.py | 115 -------------
lnbits/extensions/cashu/mint.py | 155 -----------------
lnbits/extensions/cashu/mint_helper.py | 97 -----------
lnbits/extensions/cashu/tasks.py | 55 +-----
lnbits/extensions/cashu/views_api.py | 86 +++++-----
7 files changed, 141 insertions(+), 617 deletions(-)
delete mode 100644 lnbits/extensions/cashu/mint.py
delete mode 100644 lnbits/extensions/cashu/mint_helper.py
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index 81b18bc7..6e19eb6e 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -11,13 +11,16 @@ db = Database("ext_cashu")
import sys
+cashu_static_files = [
+ {
+ "path": "/cashu/static",
+ "app": StaticFiles(directory="lnbits/extensions/cashu/static"),
+ "name": "cashu_static",
+ }
+]
sys.path.append("/Users/cc/git/cashu")
from cashu.mint.ledger import Ledger
-# from .crud import LedgerCrud
-
-# db = Database("ext_cashu", LNBITS_DATA_FOLDER)
-
ledger = Ledger(
db=db,
# seed=MINT_PRIVATE_KEY,
@@ -26,17 +29,6 @@ ledger = Ledger(
)
cashu_ext: APIRouter = APIRouter(prefix="/api/v1/cashu", tags=["cashu"])
-# from cashu.mint.router import router as cashu_router
-
-# cashu_ext.include_router(router=cashu_router)
-
-cashu_static_files = [
- {
- "path": "/cashu/static",
- "app": StaticFiles(directory="lnbits/extensions/cashu/static"),
- "name": "cashu_static",
- }
-]
def cashu_renderer():
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index a56eadf0..2057f6ff 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -19,46 +19,6 @@ from cashu.core.base import MintKeyset
from lnbits.db import Database, Connection
-# class LedgerCrud:
-# """
-# Database interface for Cashu mint.
-
-# This class needs to be overloaded by any app that imports the Cashu mint.
-# """
-
-# async def get_keyset(*args, **kwags):
-
-# return await get_keyset(*args, **kwags)
-
-# async def get_lightning_invoice(*args, **kwags):
-
-# return await get_lightning_invoice(*args, **kwags)
-
-# async def get_proofs_used(*args, **kwags):
-
-# return await get_proofs_used(*args, **kwags)
-
-# async def invalidate_proof(*args, **kwags):
-
-# return await invalidate_proof(*args, **kwags)
-
-# async def store_keyset(*args, **kwags):
-
-# return await store_keyset(*args, **kwags)
-
-# async def store_lightning_invoice(*args, **kwags):
-
-# return await store_lightning_invoice(*args, **kwags)
-
-# async def store_promise(*args, **kwags):
-
-# return await store_promise(*args, **kwags)
-
-# async def update_lightning_invoice(*args, **kwags):
-
-# return await update_lightning_invoice(*args, **kwags)
-
-
async def create_cashu(
cashu_id: str, keyset_id: str, wallet_id: str, data: Cashu
) -> Cashu:
@@ -85,23 +45,23 @@ async def create_cashu(
return cashu
-async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
- entropy = bytes([random.getrandbits(8) for i in range(16)])
- mnemonic = bip39.mnemonic_from_bytes(entropy)
- seed = bip39.mnemonic_to_seed(mnemonic)
- root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
+# async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
+# entropy = bytes([random.getrandbits(8) for i in range(16)])
+# mnemonic = bip39.mnemonic_from_bytes(entropy)
+# seed = bip39.mnemonic_to_seed(mnemonic)
+# root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
- bip44_xprv = root.derive("m/44h/1h/0h")
- bip44_xpub = bip44_xprv.to_public()
+# bip44_xprv = root.derive("m/44h/1h/0h")
+# bip44_xpub = bip44_xprv.to_public()
- await db.execute(
- "UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?",
- bip44_xprv.to_base58(),
- bip44_xpub.to_base58(),
- cashu_id,
- )
- row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
- return Cashu(**row) if row else None
+# await db.execute(
+# "UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?",
+# bip44_xprv.to_base58(),
+# bip44_xpub.to_base58(),
+# cashu_id,
+# )
+# row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
+# return Cashu(**row) if row else None
async def get_cashu(cashu_id) -> Optional[Cashu]:
@@ -130,103 +90,103 @@ async def delete_cashu(cashu_id) -> None:
# ##########################################
-async def store_promises(
- amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str
-):
- for amount, B_, C_ in zip(amounts, B_s, C_s):
- await store_promise(amount, B_, C_, cashu_id)
+# async def store_promises(
+# amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str
+# ):
+# for amount, B_, C_ in zip(amounts, B_s, C_s):
+# await store_promise(amount, B_, C_, cashu_id)
-async def store_promise(amount: int, B_: str, C_: str, cashu_id: str):
- promise_id = urlsafe_short_hash()
+# async def store_promise(amount: int, B_: str, C_: str, cashu_id: str):
+# promise_id = urlsafe_short_hash()
- await db.execute(
- """
- INSERT INTO cashu.promises
- (id, amount, B_b, C_b, cashu_id)
- VALUES (?, ?, ?, ?, ?)
- """,
- (promise_id, amount, str(B_), str(C_), cashu_id),
- )
+# await db.execute(
+# """
+# INSERT INTO cashu.promises
+# (id, amount, B_b, C_b, cashu_id)
+# VALUES (?, ?, ?, ?, ?)
+# """,
+# (promise_id, amount, str(B_), str(C_), cashu_id),
+# )
-async def get_promises(cashu_id) -> Optional[Cashu]:
- row = await db.fetchall(
- "SELECT * FROM cashu.promises WHERE cashu_id = ?", (cashu_id,)
- )
- return Promises(**row) if row else None
+# async def get_promises(cashu_id) -> Optional[Cashu]:
+# row = await db.fetchall(
+# "SELECT * FROM cashu.promises WHERE cashu_id = ?", (cashu_id,)
+# )
+# return Promises(**row) if row else None
-async def get_proofs_used(
- db: Database,
- conn: Optional[Connection] = None,
-):
+# async def get_proofs_used(
+# db: Database,
+# conn: Optional[Connection] = None,
+# ):
- rows = await (conn or db).fetchall(
- """
- SELECT secret from cashu.proofs_used
- """
- )
- return [row[0] for row in rows]
+# rows = await (conn or db).fetchall(
+# """
+# SELECT secret from cashu.proofs_used
+# """
+# )
+# return [row[0] for row in rows]
-async def invalidate_proof(cashu_id: str, proof: Proof):
- invalidate_proof_id = urlsafe_short_hash()
- await db.execute(
- """
- INSERT INTO cashu.proofs_used
- (id, amount, C, secret, cashu_id)
- VALUES (?, ?, ?, ?, ?)
- """,
- (invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id),
- )
+# async def invalidate_proof(cashu_id: str, proof: Proof):
+# invalidate_proof_id = urlsafe_short_hash()
+# await db.execute(
+# """
+# INSERT INTO cashu.proofs_used
+# (id, amount, C, secret, cashu_id)
+# VALUES (?, ?, ?, ?, ?)
+# """,
+# (invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id),
+# )
-########################################
-############ MINT INVOICES #############
-########################################
+# ########################################
+# ############ MINT INVOICES #############
+# ########################################
-async def store_lightning_invoice(cashu_id: str, invoice: Invoice):
- await db.execute(
- """
- INSERT INTO cashu.invoices
- (cashu_id, amount, pr, hash, issued)
- VALUES (?, ?, ?, ?, ?)
- """,
- (
- cashu_id,
- invoice.amount,
- invoice.pr,
- invoice.hash,
- invoice.issued,
- ),
- )
+# async def store_lightning_invoice(cashu_id: str, invoice: Invoice):
+# await db.execute(
+# """
+# INSERT INTO cashu.invoices
+# (cashu_id, amount, pr, hash, issued)
+# VALUES (?, ?, ?, ?, ?)
+# """,
+# (
+# cashu_id,
+# invoice.amount,
+# invoice.pr,
+# invoice.hash,
+# invoice.issued,
+# ),
+# )
-async def get_lightning_invoice(cashu_id: str, hash: str):
- row = await db.fetchone(
- """
- SELECT * from cashu.invoices
- WHERE cashu_id =? AND hash = ?
- """,
- (
- cashu_id,
- hash,
- ),
- )
- return Invoice.from_row(row)
+# async def get_lightning_invoice(cashu_id: str, hash: str):
+# row = await db.fetchone(
+# """
+# SELECT * from cashu.invoices
+# WHERE cashu_id =? AND hash = ?
+# """,
+# (
+# cashu_id,
+# hash,
+# ),
+# )
+# return Invoice.from_row(row)
-async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
- await db.execute(
- "UPDATE cashu.invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
- (
- issued,
- cashu_id,
- hash,
- ),
- )
+# async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
+# await db.execute(
+# "UPDATE cashu.invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
+# (
+# issued,
+# cashu_id,
+# hash,
+# ),
+# )
##############################
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index 3f799534..ec520765 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -30,118 +30,3 @@ async def m001_initial(db):
);
"""
)
-
-
-# async def m001_initial(db):
-# await db.execute(
-# """
-# CREATE TABLE IF NOT EXISTS cashu.promises (
-# amount INTEGER NOT NULL,
-# B_b TEXT NOT NULL,
-# C_b TEXT NOT NULL,
-
-# UNIQUE (B_b)
-
-# );
-# """
-# )
-
-# await db.execute(
-# """
-# CREATE TABLE IF NOT EXISTS cashu.proofs_used (
-# amount INTEGER NOT NULL,
-# C TEXT NOT NULL,
-# secret TEXT NOT NULL,
-
-# UNIQUE (secret)
-
-# );
-# """
-# )
-
-# await db.execute(
-# """
-# CREATE TABLE IF NOT EXISTS cashu.invoices (
-# amount INTEGER NOT NULL,
-# pr TEXT NOT NULL,
-# hash TEXT NOT NULL,
-# issued BOOL NOT NULL,
-
-# UNIQUE (hash)
-
-# );
-# """
-# )
-
-# await db.execute(
-# """
-# CREATE VIEW IF NOT EXISTS cashu.balance_issued AS
-# SELECT COALESCE(SUM(s), 0) AS balance FROM (
-# SELECT SUM(amount) AS s
-# FROM cashu.promises
-# WHERE amount > 0
-# );
-# """
-# )
-
-# await db.execute(
-# """
-# CREATE VIEW IF NOT EXISTS cashu.balance_used AS
-# SELECT COALESCE(SUM(s), 0) AS balance FROM (
-# SELECT SUM(amount) AS s
-# FROM cashu.proofs_used
-# WHERE amount > 0
-# );
-# """
-# )
-
-# await db.execute(
-# """
-# CREATE VIEW IF NOT EXISTS cashu.balance AS
-# SELECT s_issued - s_used AS balance FROM (
-# SELECT bi.balance AS s_issued, bu.balance AS s_used
-# FROM cashu.balance_issued bi
-# CROSS JOIN balance_used bu
-# );
-# """
-# )
-
-
-# async def m003_mint_keysets(db):
-# """
-# Stores mint keysets from different mints and epochs.
-# """
-# await db.execute(
-# f"""
-# CREATE TABLE IF NOT EXISTS cashu.keysets (
-# id TEXT NOT NULL,
-# derivation_path TEXT,
-# valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
-# valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
-# first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
-# active BOOL DEFAULT TRUE,
-
-# UNIQUE (derivation_path)
-
-# );
-# """
-# )
-# await db.execute(
-# f"""
-# CREATE TABLE IF NOT EXISTS cashu.mint_pubkeys (
-# id TEXT NOT NULL,
-# amount INTEGER NOT NULL,
-# pubkey TEXT NOT NULL,
-
-# UNIQUE (id, pubkey)
-
-# );
-# """
-# )
-
-
-# async def m004_keysets_add_version(db):
-# """
-# Column that remembers with which version
-# """
-# await db.execute("ALTER TABLE cashu.keysets ADD COLUMN version TEXT")
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
deleted file mode 100644
index 4153fb30..00000000
--- a/lnbits/extensions/cashu/mint.py
+++ /dev/null
@@ -1,155 +0,0 @@
-import math
-from typing import List, Set
-
-from lnbits import bolt11
-from lnbits.core.services import check_transaction_status, fee_reserve, pay_invoice
-from lnbits.wallets.base import PaymentStatus
-
-from .core.b_dhke import step2_bob
-from .core.base import BlindedMessage, BlindedSignature, Proof
-from .core.secp import PublicKey
-from .core.split import amount_split
-from .crud import get_proofs_used, invalidate_proof
-from .mint_helper import (
- derive_keys,
- derive_pubkeys,
- verify_equation_balanced,
- verify_no_duplicates,
- verify_outputs,
- verify_proof,
- verify_secret_criteria,
- verify_split_amount,
-)
-from .models import Cashu
-
-# todo: extract const
-MAX_ORDER = 64
-
-
-def get_pubkeys(xpriv: str):
- """Returns public keys for possible amounts."""
-
- keys = derive_keys(xpriv)
- pub_keys = derive_pubkeys(keys)
-
- return {a: p.serialize().hex() for a, p in pub_keys.items()}
-
-
-async def generate_promises(
- master_prvkey: str, amounts: List[int], B_s: List[PublicKey]
-):
- """Mints a promise for coins for B_."""
-
- for amount in amounts:
- if amount not in [2**i for i in range(MAX_ORDER)]:
- raise Exception(f"Can only mint amounts up to {2**MAX_ORDER}.")
-
- promises = [
- await generate_promise(master_prvkey, amount, B_)
- for B_, amount in zip(B_s, amounts)
- ]
- return promises
-
-
-async def generate_promise(master_prvkey: str, amount: int, B_: PublicKey):
- """Generates a promise for given amount and returns a pair (amount, C')."""
- secret_key = derive_keys(master_prvkey)[amount] # Get the correct key
- C_ = step2_bob(B_, secret_key)
- return BlindedSignature(amount=amount, C_=C_.serialize().hex())
-
-
-async def melt(cashu: Cashu, proofs: List[Proof], invoice: str):
- """Invalidates proofs and pays a Lightning invoice."""
- # Verify proofs
- proofs_used: Set[str] = set(await get_proofs_used(cashu.id))
- for p in proofs:
- await verify_proof(cashu.prvkey, proofs_used, p)
-
- total_provided = sum([p["amount"] for p in proofs])
- invoice_obj = bolt11.decode(invoice)
- amount = math.ceil(invoice_obj.amount_msat / 1000)
-
- fees_msat = await check_fees(cashu.wallet, invoice_obj)
- assert total_provided >= amount + fees_msat / 1000, Exception(
- f"Provided proofs ({total_provided} sats) not enough for Lightning payment ({amount + fees_msat} sats)."
- )
-
- await pay_invoice(
- wallet_id=cashu.wallet,
- payment_request=invoice,
- description=f"pay cashu invoice",
- extra={"tag": "cashu", "cahsu_name": cashu.name},
- )
-
- status: PaymentStatus = await check_transaction_status(
- cashu.wallet, invoice_obj.payment_hash
- )
- if status.paid == True:
- await invalidate_proofs(cashu.id, proofs)
- return status.paid, status.preimage
- return False, ""
-
-
-async def check_fees(wallet_id: str, decoded_invoice):
- """Returns the fees (in msat) required to pay this pr."""
- amount = math.ceil(decoded_invoice.amount_msat / 1000)
- status: PaymentStatus = await check_transaction_status(
- wallet_id, decoded_invoice.payment_hash
- )
- fees_msat = fee_reserve(amount * 1000) if status.paid != True else 0
- return fees_msat
-
-
-async def split(
- cashu: Cashu, proofs: List[Proof], amount: int, outputs: List[BlindedMessage]
-):
- """Consumes proofs and prepares new promises based on the amount split."""
- total = sum([p.amount for p in proofs])
-
- # verify that amount is kosher
- verify_split_amount(amount)
- # verify overspending attempt
- if amount > total:
- raise Exception(
- f"split amount ({amount}) is higher than the total sum ({total})."
- )
-
- # Verify secret criteria
- if not all([verify_secret_criteria(p) for p in proofs]):
- raise Exception("secrets do not match criteria.")
- # verify that only unique proofs and outputs were used
- if not verify_no_duplicates(proofs, outputs):
- raise Exception("duplicate proofs or promises.")
- # verify that outputs have the correct amount
- if not verify_outputs(total, amount, outputs): # ?
- raise Exception("split of promises is not as expected.")
- # Verify proofs
- # Verify proofs
- proofs_used: Set[str] = set(await get_proofs_used(cashu.id))
- for p in proofs:
- await verify_proof(cashu.prvkey, proofs_used, p)
-
- # Mark proofs as used and prepare new promises
- await invalidate_proofs(cashu.id, proofs)
-
- outs_fst = amount_split(total - amount)
- outs_snd = amount_split(amount)
- B_fst = [
- PublicKey(bytes.fromhex(od.B_), raw=True) for od in outputs[: len(outs_fst)]
- ]
- B_snd = [
- PublicKey(bytes.fromhex(od.B_), raw=True) for od in outputs[len(outs_fst) :]
- ]
- # PublicKey(bytes.fromhex(payload.B_), raw=True)
- prom_fst, prom_snd = await generate_promises(
- cashu.prvkey, outs_fst, B_fst
- ), await generate_promises(cashu.prvkey, outs_snd, B_snd)
- # verify amounts in produced proofs
- verify_equation_balanced(proofs, prom_fst + prom_snd)
- return prom_fst, prom_snd
-
-
-async def invalidate_proofs(cashu_id: str, proofs: List[Proof]):
- """Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
- for p in proofs:
- await invalidate_proof(cashu_id, p)
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
deleted file mode 100644
index 8e7e2275..00000000
--- a/lnbits/extensions/cashu/mint_helper.py
+++ /dev/null
@@ -1,97 +0,0 @@
-import base64
-import hashlib
-from typing import List, Set
-
-from .core.b_dhke import verify
-from .core.base import BlindedSignature
-from .core.secp import PrivateKey, PublicKey
-from .core.split import amount_split
-from .models import BlindedMessage, Proof
-
-# todo: extract const
-MAX_ORDER = 64
-
-
-def derive_keys(master_key: str):
- """Deterministic derivation of keys for 2^n values."""
- return {
- 2
- ** i: PrivateKey(
- hashlib.sha256((str(master_key) + str(i)).encode("utf-8"))
- .hexdigest()
- .encode("utf-8")[:32],
- raw=True,
- )
- for i in range(MAX_ORDER)
- }
-
-
-def derive_pubkeys(keys: List[PrivateKey]):
- return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
-
-
-# async required?
-async def verify_proof(master_prvkey: str, proofs_used: Set[str], proof: Proof):
- """Verifies that the proof of promise was issued by this ledger."""
- if proof.secret in proofs_used:
- raise Exception(f"tokens already spent. Secret: {proof.secret}")
-
- secret_key = derive_keys(master_prvkey)[
- proof.amount
- ] # Get the correct key to check against
- C = PublicKey(bytes.fromhex(proof.C), raw=True)
- secret = base64.standard_b64decode(proof.secret)
- print("### secret", secret)
- validMintSig = verify(secret_key, C, secret)
- if validMintSig != True:
- raise Exception(f"tokens not valid. Secret: {proof.secret}")
-
-
-def verify_split_amount(amount: int):
- """Split amount like output amount can't be negative or too big."""
- try:
- verify_amount(amount)
- except:
- # For better error message
- raise Exception("invalid split amount: " + str(amount))
-
-
-def verify_secret_criteria(proof: Proof):
- if proof.secret is None or proof.secret == "":
- raise Exception("no secret in proof.")
- return True
-
-
-def verify_no_duplicates(proofs: List[Proof], outputs: List[BlindedMessage]):
- secrets = [p.secret for p in proofs]
- if len(secrets) != len(list(set(secrets))):
- return False
- B_s = [od.B_ for od in outputs]
- if len(B_s) != len(list(set(B_s))):
- return False
- return True
-
-
-def verify_outputs(total: int, amount: int, outputs: List[BlindedMessage]):
- """Verifies the expected split was correctly computed"""
- frst_amt, scnd_amt = total - amount, amount # we have two amounts to split to
- frst_outputs = amount_split(frst_amt)
- scnd_outputs = amount_split(scnd_amt)
- expected = frst_outputs + scnd_outputs
- given = [o.amount for o in outputs]
- return given == expected
-
-
-def verify_amount(amount: int):
- """Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
- valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
- if not valid:
- raise Exception("invalid amount: " + str(amount))
- return amount
-
-
-def verify_equation_balanced(proofs: List[Proof], outs: List[BlindedSignature]):
- """Verify that Σoutputs - Σinputs = 0."""
- sum_inputs = sum(verify_amount(p.amount) for p in proofs)
- sum_outputs = sum(verify_amount(p.amount) for p in outs)
- assert sum_outputs - sum_inputs == 0
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index 03bba895..dddd1ef1 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -1,17 +1,11 @@
import asyncio
import json
-from lnbits.core import db as core_db
-from lnbits.core.crud import create_payment
from lnbits.core.models import Payment
-from lnbits.helpers import urlsafe_short_hash
-from lnbits.tasks import internal_invoice_queue, register_invoice_listener
+from lnbits.tasks import register_invoice_listener
from .crud import get_cashu
-import sys
-
-sys.path.append("/Users/cc/git/cashu")
from cashu.mint import migrations
from cashu.core.migrations import migrate_databases
from . import db, ledger
@@ -35,51 +29,6 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
- if payment.extra.get("tag") == "cashu" and payment.extra.get("tipSplitted"):
- # already splitted, ignore
+ if not payment.extra.get("tag") == "cashu":
return
-
- # now we make some special internal transfers (from no one to the receiver)
- cashu = await get_cashu(payment.extra.get("cashuId"))
- tipAmount = payment.extra.get("tipAmount")
-
- if tipAmount is None:
- # no tip amount
- return
-
- tipAmount = tipAmount * 1000
- amount = payment.amount - tipAmount
-
- # mark the original payment with one extra key, "splitted"
- # (this prevents us from doing this process again and it's informative)
- # and reduce it by the amount we're going to send to the producer
- await core_db.execute(
- """
- UPDATE apipayments
- SET extra = ?, amount = ?
- WHERE hash = ?
- AND checking_id NOT LIKE 'internal_%'
- """,
- (
- json.dumps(dict(**payment.extra, tipSplitted=True)),
- amount,
- payment.payment_hash,
- ),
- )
-
- # perform the internal transfer using the same payment_hash
- internal_checking_id = f"internal_{urlsafe_short_hash()}"
- await create_payment(
- wallet_id=cashu.tip_wallet,
- checking_id=internal_checking_id,
- payment_request="",
- payment_hash=payment.payment_hash,
- amount=tipAmount,
- memo=f"Tip for {payment.memo}",
- pending=False,
- extra={"tipSplitted": True},
- )
-
- # manually send this for now
- await internal_invoice_queue.put(internal_checking_id)
return
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 2d28c78b..6315a765 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -2,6 +2,7 @@ import json
from http import HTTPStatus
from typing import Union
import math
+from typing import Dict, List, Union
import httpx
from fastapi import Query
@@ -25,38 +26,22 @@ from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.wallets.base import PaymentStatus
from lnbits.helpers import urlsafe_short_hash
from lnbits.core.crud import check_internal
+
+# --------- extension imports
+
from . import cashu_ext
-from .core.base import CashuError, PostSplitResponse, SplitRequest
from .crud import (
create_cashu,
delete_cashu,
get_cashu,
get_cashus,
- get_lightning_invoice,
- store_lightning_invoice,
- store_promise,
- update_lightning_invoice,
)
-# from .ledger import mint, request_mint
-from .mint import generate_promises, get_pubkeys, melt, split
-from .models import (
- Cashu,
- CheckPayload,
- Invoice,
- MeltPayload,
- MintPayloads,
- PayLnurlWData,
- Pegs,
- SplitPayload,
-)
+from .models import Cashu
+from . import ledger
-############### IMPORT CALLE
-from typing import Dict, List, Union
-
-from secp256k1 import PublicKey
-
+# -------- cashu imports
from cashu.core.base import (
Proof,
BlindedSignature,
@@ -69,9 +54,8 @@ from cashu.core.base import (
MintRequest,
PostSplitResponse,
SplitRequest,
+ Invoice,
)
-from cashu.core.errors import CashuError
-from . import db, ledger
LIGHTNING = False
@@ -165,7 +149,7 @@ async def mint_coins(
data: MintRequest,
cashu_id: str = Query(None),
payment_hash: str = Query(None),
-):
+) -> List[BlindedSignature]:
"""
Requests the minting of tokens belonging to a paid payment request.
Call this endpoint after `GET /mint`.
@@ -220,7 +204,9 @@ async def mint_coins(
@cashu_ext.post("/{cashu_id}/melt")
-async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)):
+async def melt_coins(
+ payload: MeltRequest, cashu_id: str = Query(None)
+) -> GetMeltResponse:
"""Invalidates proofs and pays a Lightning invoice."""
cashu: Union[None, Cashu] = await get_cashu(cashu_id)
if cashu is None:
@@ -229,8 +215,6 @@ async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)):
)
proofs = payload.proofs
invoice = payload.invoice
- # async def melt(cashu: Cashu, proofs: List[Proof], invoice: str):
- # """Invalidates proofs and pays a Lightning invoice."""
# !!!!!!! MAKE SURE THAT PROOFS ARE ONLY FROM THIS CASHU KEYSET ID
# THIS IS NECESSARY BECAUSE THE CASHU BACKEND WILL ACCEPT ANY VALID
@@ -271,18 +255,7 @@ async def melt_coins(payload: MeltRequest, cashu_id: str = Query(None)):
)
if status.paid == True:
await ledger._invalidate_proofs(proofs)
- return status.paid, status.preimage
- return False, ""
-
-
-@cashu_ext.post("/melt")
-async def melt(payload: MeltRequest) -> GetMeltResponse:
- """
- Requests tokens to be destroyed and sent out via Lightning.
- """
- ok, preimage = await ledger.melt(payload.proofs, payload.invoice)
- resp = GetMeltResponse(paid=ok, preimage=preimage)
- return resp
+ return GetMeltResponse(paid=status.paid, preimage=status.preimage)
@cashu_ext.post("/check")
@@ -298,29 +271,46 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
Used by wallets for figuring out the fees they need to supply.
This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu).
"""
- fees_msat = await ledger.check_fees(payload.pr)
+ invoice_obj = bolt11.decode(payload.pr)
+ internal_checking_id = await check_internal(invoice_obj.payment_hash)
+
+ if not internal_checking_id:
+ fees_msat = fee_reserve(invoice_obj.amount_msat)
+ else:
+ fees_msat = 0
return CheckFeesResponse(fee=fees_msat / 1000)
-@cashu_ext.post("/split")
+@cashu_ext.post("/{cashu_id}/split")
async def split(
- payload: SplitRequest,
-) -> Union[CashuError, PostSplitResponse]:
+ payload: SplitRequest, cashu_id: str = Query(None)
+) -> PostSplitResponse:
"""
Requetst a set of tokens with amount "total" to be split into two
newly minted sets with amount "split" and "total-split".
"""
+ cashu: Union[None, Cashu] = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
proofs = payload.proofs
amount = payload.amount
- outputs = payload.outputs.blinded_messages if payload.outputs else None
+ outputs = payload.outputs.blinded_messages
# backwards compatibility with clients < v0.2.2
assert outputs, Exception("no outputs provided.")
try:
- split_return = await ledger.split(proofs, amount, outputs)
+ split_return = await ledger.split(proofs, amount, outputs, cashu.keyset_id)
except Exception as exc:
- return CashuError(error=str(exc))
+ HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST,
+ detail=str(exc),
+ )
if not split_return:
- return CashuError(error="there was an error with the split")
+ raise HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST,
+ detail="there was an error with the split",
+ )
frst_promises, scnd_promises = split_return
resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
return resp
From b682d1210605137d442c2a8886d0cb83c09139d8 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 17 Oct 2022 11:03:39 +0200
Subject: [PATCH 317/696] clean up
---
lnbits/extensions/cashu/__init__.py | 3 +-
lnbits/extensions/cashu/core/b_dhke.py | 88 -------------
lnbits/extensions/cashu/core/base.py | 168 -------------------------
lnbits/extensions/cashu/core/secp.py | 52 --------
lnbits/extensions/cashu/core/split.py | 8 --
lnbits/extensions/cashu/views_api.py | 41 +++---
6 files changed, 22 insertions(+), 338 deletions(-)
delete mode 100644 lnbits/extensions/cashu/core/b_dhke.py
delete mode 100644 lnbits/extensions/cashu/core/base.py
delete mode 100644 lnbits/extensions/cashu/core/secp.py
delete mode 100644 lnbits/extensions/cashu/core/split.py
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index 6e19eb6e..440be092 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -18,7 +18,6 @@ cashu_static_files = [
"name": "cashu_static",
}
]
-sys.path.append("/Users/cc/git/cashu")
from cashu.mint.ledger import Ledger
ledger = Ledger(
@@ -28,7 +27,7 @@ ledger = Ledger(
derivation_path="0/0/0/1",
)
-cashu_ext: APIRouter = APIRouter(prefix="/api/v1/cashu", tags=["cashu"])
+cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
def cashu_renderer():
diff --git a/lnbits/extensions/cashu/core/b_dhke.py b/lnbits/extensions/cashu/core/b_dhke.py
deleted file mode 100644
index ff0bc515..00000000
--- a/lnbits/extensions/cashu/core/b_dhke.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# Don't trust me with cryptography.
-
-"""
-Implementation of https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406
-Alice:
-A = a*G
-return A
-Bob:
-Y = hash_to_curve(secret_message)
-r = random blinding factor
-B'= Y + r*G
-return B'
-Alice:
-C' = a*B'
- (= a*Y + a*r*G)
-return C'
-Bob:
-C = C' - r*A
- (= C' - a*r*G)
- (= a*Y)
-return C, secret_message
-Alice:
-Y = hash_to_curve(secret_message)
-C == a*Y
-If true, C must have originated from Alice
-"""
-
-import hashlib
-
-from secp256k1 import PrivateKey, PublicKey
-
-
-def hash_to_curve(message: bytes):
- """Generates a point from the message hash and checks if the point lies on the curve.
- If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
- point = None
- msg_to_hash = message
- while point is None:
- try:
- _hash = hashlib.sha256(msg_to_hash).digest()
- point = PublicKey(b"\x02" + _hash, raw=True)
- except:
- msg_to_hash = _hash
- return point
-
-
-def step1_alice(secret_msg):
- secret_msg = secret_msg
- Y = hash_to_curve(secret_msg)
- r = PrivateKey()
- B_ = Y + r.pubkey
- return B_, r
-
-
-def step2_bob(B_, a):
- C_ = B_.mult(a)
- return C_
-
-
-def step3_alice(C_, r, A):
- C = C_ - A.mult(r)
- return C
-
-
-def verify(a, C, secret_msg):
- Y = hash_to_curve(secret_msg)
- return C == Y.mult(a)
-
-
-### Below is a test of a simple positive and negative case
-
-# # Alice's keys
-# a = PrivateKey()
-# A = a.pubkey
-# secret_msg = "test"
-# B_, r = step1_alice(secret_msg)
-# C_ = step2_bob(B_, a)
-# C = step3_alice(C_, r, A)
-# print("C:{}, secret_msg:{}".format(C, secret_msg))
-# assert verify(a, C, secret_msg)
-# assert verify(a, C + C, secret_msg) == False # adding C twice shouldn't pass
-# assert verify(a, A, secret_msg) == False # A shouldn't pass
-
-# # Test operations
-# b = PrivateKey()
-# B = b.pubkey
-# assert -A -A + A == -A # neg
-# assert B.mult(a) == A.mult(b) # a*B = A*b
diff --git a/lnbits/extensions/cashu/core/base.py b/lnbits/extensions/cashu/core/base.py
deleted file mode 100644
index 947da987..00000000
--- a/lnbits/extensions/cashu/core/base.py
+++ /dev/null
@@ -1,168 +0,0 @@
-from sqlite3 import Row
-from typing import List, Union
-
-from pydantic import BaseModel
-
-
-class CashuError(BaseException):
- code = "000"
- error = "CashuError"
-
-
-class P2SHScript(BaseModel):
- script: str
- signature: str
- address: Union[str, None] = None
-
- @classmethod
- def from_row(cls, row: Row):
- return cls(
- address=row[0],
- script=row[1],
- signature=row[2],
- used=row[3],
- )
-
-
-class Proof(BaseModel):
- amount: int
- secret: str = ""
- C: str
- script: Union[P2SHScript, None] = None
- reserved: bool = False # whether this proof is reserved for sending
- send_id: str = "" # unique ID of send attempt
- time_created: str = ""
- time_reserved: str = ""
-
- @classmethod
- def from_row(cls, row: Row):
- return cls(
- amount=row[0],
- C=row[1],
- secret=row[2],
- reserved=row[3] or False,
- send_id=row[4] or "",
- time_created=row[5] or "",
- time_reserved=row[6] or "",
- )
-
- @classmethod
- def from_dict(cls, d: dict):
- assert "amount" in d, "no amount in proof"
- return cls(
- amount=d.get("amount"),
- C=d.get("C"),
- secret=d.get("secret") or "",
- reserved=d.get("reserved") or False,
- send_id=d.get("send_id") or "",
- time_created=d.get("time_created") or "",
- time_reserved=d.get("time_reserved") or "",
- )
-
- def to_dict(self):
- return dict(amount=self.amount, secret=self.secret, C=self.C)
-
- def to_dict_no_secret(self):
- return dict(amount=self.amount, C=self.C)
-
- def __getitem__(self, key):
- return self.__getattribute__(key)
-
- def __setitem__(self, key, val):
- self.__setattr__(key, val)
-
-
-class Proofs(BaseModel):
- """TODO: Use this model"""
-
- proofs: List[Proof]
-
-
-class Invoice(BaseModel):
- amount: int
- pr: str
- hash: str
- issued: bool = False
-
- @classmethod
- def from_row(cls, row: Row):
- return cls(
- cashu_id=str(row[0]),
- amount=int(row[1]),
- pr=str(row[2]),
- hash=str(row[3]),
- issued=bool(row[4]),
- )
-
-
-class BlindedMessage(BaseModel):
- amount: int
- B_: str
-
-
-class BlindedSignature(BaseModel):
- amount: int
- C_: str
-
- @classmethod
- def from_dict(cls, d: dict):
- return cls(
- amount=d["amount"],
- C_=d["C_"],
- )
-
-
-class MintRequest(BaseModel):
- blinded_messages: List[BlindedMessage] = []
-
-
-class GetMintResponse(BaseModel):
- pr: str
- hash: str
-
-
-class GetMeltResponse(BaseModel):
- paid: Union[bool, None]
- preimage: Union[str, None]
-
-
-class SplitRequest(BaseModel):
- proofs: List[Proof]
- amount: int
- output_data: Union[
- MintRequest, None
- ] = None # backwards compatibility with clients < v0.2.2
- outputs: Union[MintRequest, None] = None
-
- def __init__(self, **data):
- super().__init__(**data)
- self.backwards_compatibility_v021()
-
- def backwards_compatibility_v021(self):
- # before v0.2.2: output_data, after: outputs
- if self.output_data:
- self.outputs = self.output_data
- self.output_data = None
-
-
-class PostSplitResponse(BaseModel):
- fst: List[BlindedSignature]
- snd: List[BlindedSignature]
-
-
-class CheckRequest(BaseModel):
- proofs: List[Proof]
-
-
-class CheckFeesRequest(BaseModel):
- pr: str
-
-
-class CheckFeesResponse(BaseModel):
- fee: Union[int, None]
-
-
-class MeltRequest(BaseModel):
- proofs: List[Proof]
- amount: int = None # deprecated
- invoice: str
diff --git a/lnbits/extensions/cashu/core/secp.py b/lnbits/extensions/cashu/core/secp.py
deleted file mode 100644
index 33416434..00000000
--- a/lnbits/extensions/cashu/core/secp.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from secp256k1 import PrivateKey, PublicKey
-
-
-# We extend the public key to define some operations on points
-# Picked from https://github.com/WTRMQDev/secp256k1-zkp-py/blob/master/secp256k1_zkp/__init__.py
-class PublicKeyExt(PublicKey):
- def __add__(self, pubkey2):
- if isinstance(pubkey2, PublicKey):
- new_pub = PublicKey()
- new_pub.combine([self.public_key, pubkey2.public_key])
- return new_pub
- else:
- raise TypeError("Cant add pubkey and %s" % pubkey2.__class__)
-
- def __neg__(self):
- serialized = self.serialize()
- first_byte, remainder = serialized[:1], serialized[1:]
- # flip odd/even byte
- first_byte = {b"\x03": b"\x02", b"\x02": b"\x03"}[first_byte]
- return PublicKey(first_byte + remainder, raw=True)
-
- def __sub__(self, pubkey2):
- if isinstance(pubkey2, PublicKey):
- return self + (-pubkey2)
- else:
- raise TypeError("Can't add pubkey and %s" % pubkey2.__class__)
-
- def mult(self, privkey):
- if isinstance(privkey, PrivateKey):
- return self.tweak_mul(privkey.private_key)
- else:
- raise TypeError("Can't multiply with non privatekey")
-
- def __eq__(self, pubkey2):
- if isinstance(pubkey2, PublicKey):
- seq1 = self.to_data()
- seq2 = pubkey2.to_data()
- return seq1 == seq2
- else:
- raise TypeError("Can't compare pubkey and %s" % pubkey2.__class__)
-
- def to_data(self):
- return [self.public_key.data[i] for i in range(64)]
-
-
-# Horrible monkeypatching
-PublicKey.__add__ = PublicKeyExt.__add__
-PublicKey.__neg__ = PublicKeyExt.__neg__
-PublicKey.__sub__ = PublicKeyExt.__sub__
-PublicKey.mult = PublicKeyExt.mult
-PublicKey.__eq__ = PublicKeyExt.__eq__
-PublicKey.to_data = PublicKeyExt.to_data
diff --git a/lnbits/extensions/cashu/core/split.py b/lnbits/extensions/cashu/core/split.py
deleted file mode 100644
index 44b9cf51..00000000
--- a/lnbits/extensions/cashu/core/split.py
+++ /dev/null
@@ -1,8 +0,0 @@
-def amount_split(amount):
- """Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
- bits_amt = bin(amount)[::-1][:-2]
- rv = []
- for (pos, bit) in enumerate(bits_amt):
- if bit == "1":
- rv.append(2**pos)
- return rv
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 6315a765..b271bccc 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -64,7 +64,7 @@ LIGHTNING = False
########################################
# todo: use /mints
-@cashu_ext.get("/cashus", status_code=HTTPStatus.OK)
+@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
async def api_cashus(
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
@@ -75,7 +75,7 @@ async def api_cashus(
return [cashu.dict() for cashu in await get_cashus(wallet_ids)]
-@cashu_ext.post("/cashus", status_code=HTTPStatus.CREATED)
+@cashu_ext.post("/api/v1/cashus", status_code=HTTPStatus.CREATED)
async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)):
cashu_id = urlsafe_short_hash()
# generate a new keyset in cashu
@@ -93,7 +93,7 @@ async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key
#######################################
-@cashu_ext.get("/{cashu_id}/keys", status_code=HTTPStatus.OK)
+@cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
"""Get the public keys of the mint"""
cashu: Union[Cashu, None] = await get_cashu(cashu_id)
@@ -106,7 +106,7 @@ async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
return ledger.get_keyset(keyset_id=cashu.keyset_id)
-@cashu_ext.get("/{cashu_id}/mint")
+@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintResponse:
"""
Request minting of new tokens. The mint responds with a Lightning invoice.
@@ -144,7 +144,7 @@ async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintR
return resp
-@cashu_ext.post("/{cashu_id}/mint")
+@cashu_ext.post("/api/v1/cashu/{cashu_id}/mint")
async def mint_coins(
data: MintRequest,
cashu_id: str = Query(None),
@@ -203,7 +203,7 @@ async def mint_coins(
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-@cashu_ext.post("/{cashu_id}/melt")
+@cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
async def melt_coins(
payload: MeltRequest, cashu_id: str = Query(None)
) -> GetMeltResponse:
@@ -258,13 +258,13 @@ async def melt_coins(
return GetMeltResponse(paid=status.paid, preimage=status.preimage)
-@cashu_ext.post("/check")
+@cashu_ext.post("/api/v1/check")
async def check_spendable(payload: CheckRequest) -> Dict[int, bool]:
"""Check whether a secret has been spent already or not."""
return await ledger.check_spendable(payload.proofs)
-@cashu_ext.post("/checkfees")
+@cashu_ext.post("/api/v1/checkfees")
async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
"""
Responds with the fees necessary to pay a Lightning invoice.
@@ -281,7 +281,7 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
return CheckFeesResponse(fee=fees_msat / 1000)
-@cashu_ext.post("/{cashu_id}/split")
+@cashu_ext.post("/api/v1/cashu/{cashu_id}/split")
async def split(
payload: SplitRequest, cashu_id: str = Query(None)
) -> PostSplitResponse:
@@ -299,6 +299,7 @@ async def split(
outputs = payload.outputs.blinded_messages
# backwards compatibility with clients < v0.2.2
assert outputs, Exception("no outputs provided.")
+ split_return = None
try:
split_return = await ledger.split(proofs, amount, outputs, cashu.keyset_id)
except Exception as exc:
@@ -316,7 +317,7 @@ async def split(
return resp
-# @cashu_ext.post("/api/v1/cashus/upodatekeys", status_code=HTTPStatus.CREATED)
+# @cashu_ext.post("/api/v1s/upodatekeys", status_code=HTTPStatus.CREATED)
# async def api_cashu_update_keys(
# data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
# ):
@@ -327,7 +328,7 @@ async def split(
# return cashu.dict()
-# @cashu_ext.delete("/api/v1/cashus/{cashu_id}")
+# @cashu_ext.delete("/api/v1s/{cashu_id}")
# async def api_cashu_delete(
# cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
# ):
@@ -348,7 +349,7 @@ async def split(
# ########################################
# #################????###################
# ########################################
-# @cashu_ext.post("/api/v1/cashus/{cashu_id}/invoices", status_code=HTTPStatus.CREATED)
+# @cashu_ext.post("/api/v1s/{cashu_id}/invoices", status_code=HTTPStatus.CREATED)
# async def api_cashu_create_invoice(
# amount: int = Query(..., ge=1), tipAmount: int = None, cashu_id: str = None
# ):
@@ -376,7 +377,7 @@ async def split(
# @cashu_ext.post(
-# "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay",
+# "/api/v1s/{cashu_id}/invoices/{payment_request}/pay",
# status_code=HTTPStatus.OK,
# )
# async def api_cashu_pay_invoice(
@@ -437,7 +438,7 @@ async def split(
# @cashu_ext.get(
-# "/api/v1/cashus/{cashu_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK
+# "/api/v1s/{cashu_id}/invoices/{payment_hash}", status_code=HTTPStatus.OK
# )
# async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
# cashu = await get_cashu(cashu_id)
@@ -459,7 +460,7 @@ async def split(
# ########################################
-# # @cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
+# # @cashu_ext.get("/api/v1/{cashu_id}/keys", status_code=HTTPStatus.OK)
# # async def keys(cashu_id: str = Query(False)):
# # """Get the public keys of the mint"""
# # mint = await get_cashu(cashu_id)
@@ -470,7 +471,7 @@ async def split(
# # return get_pubkeys(mint.prvkey)
-# @cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
+# @cashu_ext.get("/api/v1/{cashu_id}/mint")
# async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
# """Request minting of tokens. Server responds with a Lightning invoice."""
@@ -498,7 +499,7 @@ async def split(
# return {"pr": payment_request, "hash": payment_hash}
-# @cashu_ext.post("/api/v1/cashu/{cashu_id}/mint")
+# @cashu_ext.post("/api/v1/{cashu_id}/mint")
# async def mint_coins(
# data: MintPayloads,
# cashu_id: str = Query(None),
@@ -560,7 +561,7 @@ async def split(
# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-# @cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
+# @cashu_ext.post("/api/v1/{cashu_id}/melt")
# async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
# """Invalidates proofs and pays a Lightning invoice."""
# cashu: Cashu = await get_cashu(cashu_id)
@@ -576,12 +577,12 @@ async def split(
# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-# @cashu_ext.post("/api/v1/cashu/{cashu_id}/check")
+# @cashu_ext.post("/api/v1/{cashu_id}/check")
# async def check_spendable_coins(payload: CheckPayload, cashu_id: str = Query(None)):
# return await check_spendable(payload.proofs, cashu_id)
-# @cashu_ext.post("/api/v1/cashu/{cashu_id}/split")
+# @cashu_ext.post("/api/v1/{cashu_id}/split")
# async def split_proofs(payload: SplitRequest, cashu_id: str = Query(None)):
# """
# Requetst a set of tokens with amount "total" to be split into two
From ff07aa5a66dccb5bda49c6599551f6554d48235b Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Tue, 18 Oct 2022 11:20:40 +0200
Subject: [PATCH 318/696] remove unused import
---
lnbits/extensions/cashu/crud.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 2057f6ff..7ecf9b6a 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -12,7 +12,6 @@ from lnbits.helpers import urlsafe_short_hash
from . import db
-from .core.base import Invoice
from .models import Cashu, Pegs, Promises, Proof
from cashu.core.base import MintKeyset
From df59e5475e5348d7612c71db082fb32f2fd32cba Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Tue, 18 Oct 2022 11:20:53 +0200
Subject: [PATCH 319/696] restore pyproject
---
poetry.lock | 15 ---------------
pyproject.toml | 2 +-
2 files changed, 1 insertion(+), 16 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 57d3f6d6..079b2b99 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -871,17 +871,6 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""
[package.extras]
full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
-[[package]]
-name = "starlette-context"
-version = "0.3.4"
-description = "Access context in Starlette"
-category = "main"
-optional = false
-python-versions = ">=3.7"
-
-[package.dependencies]
-starlette = "*"
-
[[package]]
name = "tomli"
version = "2.0.1"
@@ -1801,10 +1790,6 @@ starlette = [
{file = "starlette-0.19.1-py3-none-any.whl", hash = "sha256:5a60c5c2d051f3a8eb546136aa0c9399773a689595e099e0877704d5888279bf"},
{file = "starlette-0.19.1.tar.gz", hash = "sha256:c6d21096774ecb9639acad41b86b7706e52ba3bf1dc13ea4ed9ad593d47e24c7"},
]
-starlette-context = [
- {file = "starlette_context-0.3.4-py37-none-any.whl", hash = "sha256:b16bf17bd3ead7ded2f458aebf7f913744b9cf28305e16c69b435a6c6ddf1135"},
- {file = "starlette_context-0.3.4.tar.gz", hash = "sha256:2d28e1838302fb5d5adacadc10fb73fb2d5cca1f0aa1e279698701cc96f1567c"},
-]
tomli = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
diff --git a/pyproject.toml b/pyproject.toml
index d15a4fc5..9273dcc7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,7 +63,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
-starlette-context = "^0.3.4"
+
[tool.poetry.dev-dependencies]
isort = "^5.10.1"
From 544765cf6ba9eb02a189d293baec220569222097 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Tue, 18 Oct 2022 11:24:45 +0200
Subject: [PATCH 320/696] can install cashu 0.4.1
---
poetry.lock | 178 +++++++++++++++++++++++++++++++++----------------
pyproject.toml | 15 +++--
2 files changed, 129 insertions(+), 64 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 079b2b99..2b68fb23 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -161,7 +161,7 @@ unicode-backport = ["unicodedata2"]
[[package]]
name = "click"
-version = "8.0.1"
+version = "8.0.4"
description = "Composable command line interface toolkit"
category = "main"
optional = false
@@ -195,7 +195,7 @@ toml = ["tomli"]
[[package]]
name = "ecdsa"
-version = "0.17.0"
+version = "0.18.0"
description = "ECDSA cryptographic signature library (pure python)"
category = "main"
optional = false
@@ -226,7 +226,7 @@ python-versions = "*"
[[package]]
name = "fastapi"
-version = "0.78.0"
+version = "0.83.0"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
category = "main"
optional = false
@@ -238,9 +238,9 @@ starlette = "0.19.1"
[package.extras]
all = ["email_validator (>=1.1.1,<2.0.0)", "itsdangerous (>=1.1.0,<3.0.0)", "jinja2 (>=2.11.2,<4.0.0)", "orjson (>=3.2.1,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "pyyaml (>=5.3.1,<7.0.0)", "requests (>=2.24.0,<3.0.0)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
-dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
+dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "pre-commit (>=2.17.0,<3.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "uvicorn[standard] (>=0.12.0,<0.18.0)"]
doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"]
-test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
+test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
[[package]]
name = "grpcio"
@@ -342,7 +342,7 @@ testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517",
name = "iniconfig"
version = "1.1.1"
description = "iniconfig: brain-dead simple config-ini parsing"
-category = "dev"
+category = "main"
optional = false
python-versions = "*"
@@ -389,7 +389,7 @@ typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "loguru"
-version = "0.5.3"
+version = "0.6.0"
description = "Python logging made (stupidly) simple"
category = "main"
optional = false
@@ -400,7 +400,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""}
win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""}
[package.extras]
-dev = ["Sphinx (>=2.2.1)", "black (>=19.10b0)", "codecov (>=2.0.15)", "colorama (>=0.3.4)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)", "tox-travis (>=0.12)"]
+dev = ["Sphinx (>=4.1.1)", "black (>=19.10b0)", "colorama (>=0.3.4)", "docutils (==0.16)", "flake8 (>=3.7.7)", "isort (>=5.1.1)", "pytest (>=4.6.2)", "pytest-cov (>=2.7.1)", "sphinx-autobuild (>=0.7.1)", "sphinx-rtd-theme (>=0.4.3)", "tox (>=3.9.0)"]
[[package]]
name = "markupsafe"
@@ -513,7 +513,7 @@ test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytes
name = "pluggy"
version = "1.0.0"
description = "plugin and hook calling mechanisms for python"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.6"
@@ -544,7 +544,7 @@ python-versions = ">=3.6"
name = "py"
version = "1.11.0"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
@@ -566,14 +566,14 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]]
name = "pydantic"
-version = "1.8.2"
-description = "Data validation and settings management using python 3.6 type hinting"
+version = "1.10.2"
+description = "Data validation and settings management using python type hints"
category = "main"
optional = false
-python-versions = ">=3.6.1"
+python-versions = ">=3.7"
[package.dependencies]
-typing-extensions = ">=3.7.4.3"
+typing-extensions = ">=4.1.0"
[package.extras]
dotenv = ["python-dotenv (>=0.10.4)"]
@@ -667,7 +667,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
name = "pytest"
version = "7.1.2"
description = "pytest: simple powerful testing with Python"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.7"
@@ -689,7 +689,7 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.
name = "pytest-asyncio"
version = "0.19.0"
description = "Pytest support for asyncio"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.7"
@@ -715,6 +715,14 @@ pytest = ">=4.6"
[package.extras]
testing = ["virtualenv", "pytest-xdist", "six", "process-tests", "hunter", "fields"]
+[[package]]
+name = "python-bitcoinlib"
+version = "0.11.2"
+description = "The Swiss Army Knife of the Bitcoin protocol."
+category = "main"
+optional = false
+python-versions = "*"
+
[[package]]
name = "python-dotenv"
version = "0.19.0"
@@ -748,6 +756,24 @@ six = ">=1.8.0"
[package.extras]
test = ["ipython", "mock", "pytest (>=3.0.5)"]
+[[package]]
+name = "requests"
+version = "2.27.1"
+description = "Python HTTP for Humans."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""}
+idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""}
+urllib3 = ">=1.21.1,<1.27"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
+use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
+
[[package]]
name = "rfc3986"
version = "1.5.0"
@@ -875,7 +901,7 @@ full = ["itsdangerous", "jinja2", "python-multipart", "pyyaml", "requests"]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
-category = "dev"
+category = "main"
optional = false
python-versions = ">=3.7"
@@ -897,15 +923,28 @@ python-versions = "*"
[[package]]
name = "typing-extensions"
-version = "3.10.0.2"
-description = "Backported and Experimental Type Hints for Python 3.5+"
+version = "4.4.0"
+description = "Backported and Experimental Type Hints for Python 3.7+"
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=3.7"
+
+[[package]]
+name = "urllib3"
+version = "1.26.12"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4"
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
+secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
+socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]]
name = "uvicorn"
-version = "0.18.1"
+version = "0.18.3"
description = "The lightning-fast ASGI server."
category = "main"
optional = false
@@ -917,7 +956,7 @@ h11 = ">=0.8"
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
[package.extras]
-standard = ["PyYAML (>=5.1)", "colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"]
+standard = ["colorama (>=0.4)", "httptools (>=0.4.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.0)"]
[[package]]
name = "uvloop"
@@ -1113,8 +1152,8 @@ charset-normalizer = [
{file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"},
]
click = [
- {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
- {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
+ {file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
+ {file = "click-8.0.4.tar.gz", hash = "sha256:8458d7b1287c5fb128c90e23381cf99dcde74beaf6c7ff6384ce84d6fe090adb"},
]
coincurve = [
{file = "coincurve-17.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac8c87d6fd080faa74e7ecf64a6ed20c11a254863238759eb02c3f13ad12b0c4"},
@@ -1231,8 +1270,8 @@ cryptography = [
{file = "cryptography-36.0.2.tar.gz", hash = "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9"},
]
ecdsa = [
- {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"},
- {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"},
+ {file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
+ {file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
]
embit = [
{file = "embit-0.4.9.tar.gz", hash = "sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9"},
@@ -1243,8 +1282,8 @@ enum34 = [
{file = "enum34-1.1.10.tar.gz", hash = "sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248"},
]
fastapi = [
- {file = "fastapi-0.78.0-py3-none-any.whl", hash = "sha256:15fcabd5c78c266fa7ae7d8de9b384bfc2375ee0503463a6febbe3bab69d6f65"},
- {file = "fastapi-0.78.0.tar.gz", hash = "sha256:3233d4a789ba018578658e2af1a4bb5e38bdd122ff722b313666a9b2c6786a83"},
+ {file = "fastapi-0.83.0-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"},
+ {file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"},
]
grpcio = [
{file = "grpcio-1.50.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:906f4d1beb83b3496be91684c47a5d870ee628715227d5d7c54b04a8de802974"},
@@ -1366,8 +1405,8 @@ lnurl = [
{file = "lnurl-0.3.6.tar.gz", hash = "sha256:8af07460115a48f3122a5a9c9a6062bee3897d5f6ab4c9a60f6561a83a8234f6"},
]
loguru = [
- {file = "loguru-0.5.3-py3-none-any.whl", hash = "sha256:f8087ac396b5ee5f67c963b495d615ebbceac2796379599820e324419d53667c"},
- {file = "loguru-0.5.3.tar.gz", hash = "sha256:b28e72ac7a98be3d28ad28570299a393dfcd32e5e3f6a353dec94675767b6319"},
+ {file = "loguru-0.6.0-py3-none-any.whl", hash = "sha256:4e2414d534a2ab57573365b3e6d0234dfb1d84b68b7f3b948e6fb743860a77c3"},
+ {file = "loguru-0.6.0.tar.gz", hash = "sha256:066bd06758d0a513e9836fd9c6b5a75bfb3fd36841f4b996bc60b547a309d41c"},
]
markupsafe = [
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
@@ -1589,28 +1628,42 @@ pycryptodomex = [
{file = "pycryptodomex-3.14.1.tar.gz", hash = "sha256:2ce76ed0081fd6ac8c74edc75b9d14eca2064173af79843c24fa62573263c1f2"},
]
pydantic = [
- {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
- {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
- {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:589eb6cd6361e8ac341db97602eb7f354551482368a37f4fd086c0733548308e"},
- {file = "pydantic-1.8.2-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:10e5622224245941efc193ad1d159887872776df7a8fd592ed746aa25d071840"},
- {file = "pydantic-1.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:99a9fc39470010c45c161a1dc584997f1feb13f689ecf645f59bb4ba623e586b"},
- {file = "pydantic-1.8.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a83db7205f60c6a86f2c44a61791d993dff4b73135df1973ecd9eed5ea0bda20"},
- {file = "pydantic-1.8.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:41b542c0b3c42dc17da70554bc6f38cbc30d7066d2c2815a94499b5684582ecb"},
- {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:ea5cb40a3b23b3265f6325727ddfc45141b08ed665458be8c6285e7b85bd73a1"},
- {file = "pydantic-1.8.2-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:18b5ea242dd3e62dbf89b2b0ec9ba6c7b5abaf6af85b95a97b00279f65845a23"},
- {file = "pydantic-1.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:234a6c19f1c14e25e362cb05c68afb7f183eb931dd3cd4605eafff055ebbf287"},
- {file = "pydantic-1.8.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd"},
- {file = "pydantic-1.8.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e710876437bc07bd414ff453ac8ec63d219e7690128d925c6e82889d674bb505"},
- {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:ac8eed4ca3bd3aadc58a13c2aa93cd8a884bcf21cb019f8cfecaae3b6ce3746e"},
- {file = "pydantic-1.8.2-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:4a03cbbe743e9c7247ceae6f0d8898f7a64bb65800a45cbdc52d65e370570820"},
- {file = "pydantic-1.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:8621559dcf5afacf0069ed194278f35c255dc1a1385c28b32dd6c110fd6531b3"},
- {file = "pydantic-1.8.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8b223557f9510cf0bfd8b01316bf6dd281cf41826607eada99662f5e4963f316"},
- {file = "pydantic-1.8.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:244ad78eeb388a43b0c927e74d3af78008e944074b7d0f4f696ddd5b2af43c62"},
- {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:05ef5246a7ffd2ce12a619cbb29f3307b7c4509307b1b49f456657b43529dc6f"},
- {file = "pydantic-1.8.2-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:54cd5121383f4a461ff7644c7ca20c0419d58052db70d8791eacbbe31528916b"},
- {file = "pydantic-1.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:4be75bebf676a5f0f87937c6ddb061fa39cbea067240d98e298508c1bda6f3f3"},
- {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
- {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
+ {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"},
+ {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"},
+ {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"},
+ {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"},
+ {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"},
+ {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"},
+ {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"},
+ {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"},
+ {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"},
+ {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"},
+ {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"},
+ {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"},
+ {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"},
+ {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"},
+ {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"},
+ {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"},
+ {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"},
+ {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"},
+ {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"},
+ {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"},
+ {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"},
+ {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"},
+ {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"},
+ {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"},
+ {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"},
+ {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"},
+ {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"},
+ {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"},
+ {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"},
+ {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"},
+ {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"},
+ {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"},
+ {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"},
+ {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"},
+ {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
+ {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
]
pyln-bolt7 = [
{file = "pyln-bolt7-1.0.246.tar.gz", hash = "sha256:2b53744fa21c1b12d2c9c9df153651b122e38fa65d4a5c3f2957317ee148e089"},
@@ -1655,6 +1708,10 @@ pytest-cov = [
{file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
{file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
]
+python-bitcoinlib = [
+ {file = "python-bitcoinlib-0.11.2.tar.gz", hash = "sha256:61ba514e0d232cc84741e49862dcedaf37199b40bba252a17edc654f63d13f39"},
+ {file = "python_bitcoinlib-0.11.2-py3-none-any.whl", hash = "sha256:78bd4ee717fe805cd760dfdd08765e77b7c7dbef4627f8596285e84953756508"},
+]
python-dotenv = [
{file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
{file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
@@ -1694,6 +1751,10 @@ represent = [
{file = "Represent-1.6.0.post0-py2.py3-none-any.whl", hash = "sha256:99142650756ef1998ce0661568f54a47dac8c638fb27e3816c02536575dbba8c"},
{file = "Represent-1.6.0.post0.tar.gz", hash = "sha256:026c0de2ee8385d1255b9c2426cd4f03fe9177ac94c09979bc601946c8493aa0"},
]
+requests = [
+ {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"},
+ {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"},
+]
rfc3986 = [
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
@@ -1825,13 +1886,16 @@ types-protobuf = [
{file = "types_protobuf-3.20.4.1-py3-none-any.whl", hash = "sha256:c227975ffd0f6a1eb1754e9a3aa9ca3b12265e63b462e9761e824c41fd25331c"},
]
typing-extensions = [
- {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"},
- {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"},
- {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"},
+ {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
+ {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
+]
+urllib3 = [
+ {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
+ {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
]
uvicorn = [
- {file = "uvicorn-0.18.1-py3-none-any.whl", hash = "sha256:013c4ea0787cc2dc456ef4368e18c01982e6be57903e4d3183218e543eb889b7"},
- {file = "uvicorn-0.18.1.tar.gz", hash = "sha256:35703e6518105cfe53f16a5a9435db3e2e227d0784f1fd8fbc1214b1fdc108df"},
+ {file = "uvicorn-0.18.3-py3-none-any.whl", hash = "sha256:0abd429ebb41e604ed8d2be6c60530de3408f250e8d2d84967d85ba9e86fe3af"},
+ {file = "uvicorn-0.18.3.tar.gz", hash = "sha256:9a66e7c42a2a95222f76ec24a4b754c158261c4696e683b9dadc72b590e0311b"},
]
uvloop = [
{file = "uvloop-0.16.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6224f1401025b748ffecb7a6e2652b17768f30b1a6a3f7b44660e5b5b690b12d"},
diff --git a/pyproject.toml b/pyproject.toml
index 9273dcc7..3e9a8cb0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -17,8 +17,8 @@ bech32 = "1.2.0"
bitstring = "3.1.9"
certifi = "2021.5.30"
charset-normalizer = "2.0.6"
-click = "8.0.1"
-ecdsa = "0.17.0"
+click = "8.0.4"
+ecdsa = "0.18.0"
embit = "0.4.9"
fastapi = "0.78.0"
h11 = "0.12.0"
@@ -34,7 +34,7 @@ marshmallow = "3.17.0"
outcome = "1.1.0"
psycopg2-binary = "2.9.1"
pycryptodomex = "3.14.1"
-pydantic = "1.8.2"
+pydantic = "1.10.2"
pypng = "0.0.21"
pyqrcode = "1.2.1"
pyScss = "1.4.0"
@@ -46,16 +46,16 @@ secp256k1 = "0.14.0"
shortuuid = "1.0.1"
six = "1.16.0"
sniffio = "1.2.0"
-sqlalchemy = "1.3.23"
+sqlalchemy = "1.3.24"
sqlalchemy-aio = "0.17.0"
sse-starlette = "0.6.2"
-typing-extensions = "3.10.0.2"
-uvicorn = "0.18.1"
+typing-extensions = "^4.4.0"
+uvicorn = "0.18.3"
uvloop = "0.16.0"
watchgod = "0.7"
websockets = "10.0"
zipp = "3.5.0"
-loguru = "0.5.3"
+loguru = "0.6.0"
cffi = "1.15.0"
websocket-client = "1.3.3"
grpcio = "^1.49.1"
@@ -63,6 +63,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
+cashu = {path = "../cashu"}
[tool.poetry.dev-dependencies]
From 5294e589e35ba94bd09b5d5ba2938c4c50414d0e Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Tue, 18 Oct 2022 11:49:20 +0200
Subject: [PATCH 321/696] disable codecov check
---
.github/codecov.yml | 9 +++++++++
1 file changed, 9 insertions(+)
create mode 100644 .github/codecov.yml
diff --git a/.github/codecov.yml b/.github/codecov.yml
new file mode 100644
index 00000000..e190e6aa
--- /dev/null
+++ b/.github/codecov.yml
@@ -0,0 +1,9 @@
+coverage:
+ status:
+ patch: off
+ project:
+ default:
+ target: auto
+ # adjust accordingly based on how flaky your tests are
+ # this allows a 10% drop from the previous base commit coverage
+ threshold: 10%
\ No newline at end of file
From c2d2366390ad7918e3da2aec9aabb892c54c16c5 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 21 Oct 2022 15:15:43 +0200
Subject: [PATCH 322/696] adjust versions to import cashu
---
poetry.lock | 206 ++++++++++++++++++++++++++-----------------------
pyproject.toml | 24 +++---
2 files changed, 122 insertions(+), 108 deletions(-)
diff --git a/poetry.lock b/poetry.lock
index 2b68fb23..6ce5a364 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -59,14 +59,14 @@ typing-extensions = {version = ">=3.6.5", markers = "python_version < \"3.8\""}
[[package]]
name = "attrs"
-version = "21.2.0"
+version = "22.1.0"
description = "Classes Without Boilerplate"
category = "main"
optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+python-versions = ">=3.5"
[package.extras]
-dev = ["coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "sphinx", "sphinx-notfound-page", "zope.interface"]
+dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"]
docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"]
tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six", "zope.interface"]
tests-no-zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "mypy", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "six"]
@@ -131,15 +131,15 @@ python-versions = ">=2.7"
[[package]]
name = "certifi"
-version = "2021.5.30"
+version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
-python-versions = "*"
+python-versions = ">=3.6"
[[package]]
name = "cffi"
-version = "1.15.0"
+version = "1.15.1"
description = "Foreign Function Interface for Python calling C code."
category = "main"
optional = false
@@ -150,7 +150,7 @@ pycparser = "*"
[[package]]
name = "charset-normalizer"
-version = "2.0.6"
+version = "2.0.12"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false
@@ -315,7 +315,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]]
name = "idna"
-version = "3.2"
+version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
@@ -323,20 +323,20 @@ python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
-version = "4.8.1"
+version = "5.0.0"
description = "Read metadata from Python packages"
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""}
zipp = ">=0.5"
[package.extras]
-docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
perf = ["ipython"]
-testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pep517", "pyfakefs", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy", "pytest-perf (>=0.9.2)"]
+testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
[[package]]
name = "iniconfig"
@@ -412,7 +412,7 @@ python-versions = ">=3.6"
[[package]]
name = "marshmallow"
-version = "3.17.0"
+version = "3.18.0"
description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
category = "main"
optional = false
@@ -422,9 +422,9 @@ python-versions = ">=3.7"
packaging = ">=17.0"
[package.extras]
-dev = ["flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "mypy (==0.961)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"]
-docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.8)", "sphinx (==4.5.0)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"]
-lint = ["flake8 (==4.0.1)", "flake8-bugbear (==22.6.22)", "mypy (==0.961)", "pre-commit (>=2.4,<3.0)"]
+dev = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)", "pytest", "pytz", "simplejson", "tox"]
+docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.1.1)", "sphinx-issues (==3.0.1)", "sphinx-version-warning (==1.1.2)"]
+lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)"]
tests = ["pytest", "pytz", "simplejson"]
[[package]]
@@ -469,11 +469,11 @@ python-versions = "*"
[[package]]
name = "outcome"
-version = "1.1.0"
+version = "1.2.0"
description = "Capture the outcome of Python function calls."
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
attrs = ">=19.2.0"
@@ -725,11 +725,11 @@ python-versions = "*"
[[package]]
name = "python-dotenv"
-version = "0.19.0"
+version = "0.21.0"
description = "Read key-value pairs from a .env file and set them as environment variables"
category = "main"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
[package.extras]
cli = ["click (>=5.0)"]
@@ -830,11 +830,11 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
name = "sniffio"
-version = "1.2.0"
+version = "1.3.0"
description = "Sniff out which async library your code is running under"
category = "main"
optional = false
-python-versions = ">=3.5"
+python-versions = ">=3.7"
[[package]]
name = "sqlalchemy"
@@ -1013,15 +1013,15 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
name = "zipp"
-version = "3.5.0"
+version = "3.9.0"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.extras]
-docs = ["jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "sphinx"]
-testing = ["func-timeout", "jaraco.itertools", "pytest (>=4.6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-flake8", "pytest-mypy"]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"]
+testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
[metadata]
lock-version = "1.1"
@@ -1049,8 +1049,8 @@ async-timeout = [
{file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"},
]
attrs = [
- {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"},
- {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"},
+ {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"},
+ {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"},
]
base58 = [
{file = "base58-2.1.1-py3-none-any.whl", hash = "sha256:11a36f4d3ce51dfc1043f3218591ac4eb1ceb172919cebe05b52a5bcc8d245c2"},
@@ -1092,64 +1092,78 @@ cerberus = [
{file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"},
]
certifi = [
- {file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
- {file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
+ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+ {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
]
cffi = [
- {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"},
- {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"},
- {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"},
- {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"},
- {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"},
- {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"},
- {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"},
- {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"},
- {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"},
- {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"},
- {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"},
- {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"},
- {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"},
- {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"},
- {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"},
- {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"},
- {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"},
- {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"},
- {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"},
- {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"},
- {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"},
- {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"},
- {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"},
- {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"},
- {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"},
- {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"},
- {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"},
- {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"},
- {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"},
- {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"},
+ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
+ {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
+ {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
+ {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
+ {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
+ {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
+ {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
+ {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
+ {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
+ {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
+ {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
+ {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
+ {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
+ {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
+ {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
+ {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
+ {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
+ {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
+ {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
+ {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
+ {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
+ {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
+ {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
+ {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
+ {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
+ {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
+ {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
+ {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
+ {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
+ {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
+ {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
+ {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
+ {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
]
charset-normalizer = [
- {file = "charset-normalizer-2.0.6.tar.gz", hash = "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"},
- {file = "charset_normalizer-2.0.6-py3-none-any.whl", hash = "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6"},
+ {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"},
+ {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"},
]
click = [
{file = "click-8.0.4-py3-none-any.whl", hash = "sha256:6a7a62563bbfabfda3a38f3023a1db4a35978c0abd76f6c9605ecd6554d6d9b1"},
@@ -1381,12 +1395,12 @@ httpx = [
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
]
idna = [
- {file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"},
- {file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"},
+ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
importlib-metadata = [
- {file = "importlib_metadata-4.8.1-py3-none-any.whl", hash = "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15"},
- {file = "importlib_metadata-4.8.1.tar.gz", hash = "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"},
+ {file = "importlib_metadata-5.0.0-py3-none-any.whl", hash = "sha256:ddb0e35065e8938f867ed4928d0ae5bf2a53b7773871bfe6bcc7e4fcdc7dea43"},
+ {file = "importlib_metadata-5.0.0.tar.gz", hash = "sha256:da31db32b304314d044d3c12c79bd59e307889b287ad12ff387b3500835fc2ab"},
]
iniconfig = [
{file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"},
@@ -1480,8 +1494,8 @@ markupsafe = [
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
]
marshmallow = [
- {file = "marshmallow-3.17.0-py3-none-any.whl", hash = "sha256:00040ab5ea0c608e8787137627a8efae97fabd60552a05dc889c888f814e75eb"},
- {file = "marshmallow-3.17.0.tar.gz", hash = "sha256:635fb65a3285a31a30f276f30e958070f5214c7196202caa5c7ecf28f5274bc7"},
+ {file = "marshmallow-3.18.0-py3-none-any.whl", hash = "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104"},
+ {file = "marshmallow-3.18.0.tar.gz", hash = "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7"},
]
mock = [
{file = "mock-4.0.3-py3-none-any.whl", hash = "sha256:122fcb64ee37cfad5b3f48d7a7d51875d7031aaf3d8be7c42e2bee25044eee62"},
@@ -1517,8 +1531,8 @@ mypy-extensions = [
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
outcome = [
- {file = "outcome-1.1.0-py2.py3-none-any.whl", hash = "sha256:c7dd9375cfd3c12db9801d080a3b63d4b0a261aa996c4c13152380587288d958"},
- {file = "outcome-1.1.0.tar.gz", hash = "sha256:e862f01d4e626e63e8f92c38d1f8d5546d3f9cce989263c521b2e7990d186967"},
+ {file = "outcome-1.2.0-py2.py3-none-any.whl", hash = "sha256:c4ab89a56575d6d38a05aa16daeaa333109c1f96167aba8901ab18b6b5e0f7f5"},
+ {file = "outcome-1.2.0.tar.gz", hash = "sha256:6f82bd3de45da303cf1f771ecafa1633750a358436a8bb60e06a1ceb745d2672"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
@@ -1713,8 +1727,8 @@ python-bitcoinlib = [
{file = "python_bitcoinlib-0.11.2-py3-none-any.whl", hash = "sha256:78bd4ee717fe805cd760dfdd08765e77b7c7dbef4627f8596285e84953756508"},
]
python-dotenv = [
- {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"},
- {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"},
+ {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
+ {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
]
pyyaml = [
{file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"},
@@ -1797,8 +1811,8 @@ six = [
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
sniffio = [
- {file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
- {file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
+ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
]
sqlalchemy = [
{file = "SQLAlchemy-1.3.23-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:fd3b96f8c705af8e938eaa99cbd8fd1450f632d38cad55e7367c33b263bf98ec"},
@@ -1955,6 +1969,6 @@ win32-setctime = [
{file = "win32_setctime-1.1.0.tar.gz", hash = "sha256:15cf5750465118d6929ae4de4eb46e8edae9a5634350c01ba582df868e932cb2"},
]
zipp = [
- {file = "zipp-3.5.0-py3-none-any.whl", hash = "sha256:957cfda87797e389580cb8b9e3870841ca991e2125350677b2ca83a0e99390a3"},
- {file = "zipp-3.5.0.tar.gz", hash = "sha256:f5812b1e007e48cff63449a5e9f4e7ebea716b4111f9c4f9a645f91d579bf0c4"},
+ {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"},
+ {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"},
]
diff --git a/pyproject.toml b/pyproject.toml
index 3e9a8cb0..beb0eae9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,11 +12,11 @@ script = "build.py"
python = "^3.10 | ^3.9 | ^3.8 | ^3.7"
aiofiles = "0.8.0"
asgiref = "3.4.1"
-attrs = "21.2.0"
+attrs = "22.1.0"
bech32 = "1.2.0"
bitstring = "3.1.9"
-certifi = "2021.5.30"
-charset-normalizer = "2.0.6"
+certifi = "2022.9.24"
+charset-normalizer = "2.0.12"
click = "8.0.4"
ecdsa = "0.18.0"
embit = "0.4.9"
@@ -25,27 +25,27 @@ h11 = "0.12.0"
httpcore = "0.15.0"
httptools = "0.4.0"
httpx = "0.23.0"
-idna = "3.2"
-importlib-metadata = "4.8.1"
+idna = "3.4"
+importlib-metadata = "5.0.0"
jinja2 = "3.0.1"
lnurl = "0.3.6"
markupsafe = "2.0.1"
-marshmallow = "3.17.0"
-outcome = "1.1.0"
+marshmallow = "3.18.0"
+outcome = "1.2.0"
psycopg2-binary = "2.9.1"
pycryptodomex = "3.14.1"
pydantic = "1.10.2"
pypng = "0.0.21"
pyqrcode = "1.2.1"
pyScss = "1.4.0"
-python-dotenv = "0.19.0"
+python-dotenv = "0.21.0"
pyyaml = "5.4.1"
represent = "1.6.0.post0"
rfc3986 = "1.5.0"
secp256k1 = "0.14.0"
shortuuid = "1.0.1"
six = "1.16.0"
-sniffio = "1.2.0"
+sniffio = "1.3.0"
sqlalchemy = "1.3.24"
sqlalchemy-aio = "0.17.0"
sse-starlette = "0.6.2"
@@ -54,16 +54,16 @@ uvicorn = "0.18.3"
uvloop = "0.16.0"
watchgod = "0.7"
websockets = "10.0"
-zipp = "3.5.0"
+zipp = "3.9.0"
loguru = "0.6.0"
-cffi = "1.15.0"
+cffi = "1.15.1"
websocket-client = "1.3.3"
grpcio = "^1.49.1"
protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
-cashu = {path = "../cashu"}
+cashu = "0.4.2"
[tool.poetry.dev-dependencies]
From f5971008c8b470552661cd5c33897005113bd225 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 21 Oct 2022 15:17:44 +0200
Subject: [PATCH 323/696] add cashu to requirements.txt
---
requirements.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/requirements.txt b/requirements.txt
index eb9a6e5e..6a48c861 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,6 +4,7 @@ asyncio==3.4.3
attrs==21.4.0
bech32==1.2.0
bitstring==3.1.9
+cashu==0.4.2
cerberus==1.3.4
certifi==2022.6.15
cffi==1.15.0
From 4af106cce6163252557e69429bb0f4fa1edb234c Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 21 Oct 2022 15:23:44 +0200
Subject: [PATCH 324/696] generate requirements from poetry
---
requirements.txt | 138 ++++++++++++++++++++++++++++-------------------
1 file changed, 82 insertions(+), 56 deletions(-)
diff --git a/requirements.txt b/requirements.txt
index 6a48c861..e8001e70 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,56 +1,82 @@
-aiofiles==0.8.0
-anyio==3.6.1
-asyncio==3.4.3
-attrs==21.4.0
-bech32==1.2.0
-bitstring==3.1.9
-cashu==0.4.2
-cerberus==1.3.4
-certifi==2022.6.15
-cffi==1.15.0
-click==8.1.3
-ecdsa==0.18.0
-embit==0.5.0
-environs==9.5.0
-fastapi==0.79.0
-h11==0.12.0
-httpcore==0.15.0
-httptools==0.4.0
-httpx==0.23.0
-idna==3.3
-jinja2==3.0.1
-lnurl==0.3.6
-loguru==0.6.0
-markupsafe==2.1.1
-marshmallow==3.17.0
-outcome==1.2.0
-packaging==21.3
-psycopg2-binary==2.9.3
-pycparser==2.21
-pycryptodomex==3.15.0
-pydantic==1.9.1
-pyngrok==5.1.0
-pyparsing==3.0.9
-pypng==0.20220715.0
-pyqrcode==1.2.1
-pyscss==1.4.0
-python-dotenv==0.20.0
-pyyaml==6.0
-represent==1.6.0.post0
-rfc3986==1.5.0
-secp256k1==0.14.0
-shortuuid==1.0.9
-six==1.16.0
-sniffio==1.2.0
-sqlalchemy-aio==0.17.0
-sqlalchemy==1.3.23
-sse-starlette==0.10.3
-starlette==0.19.1
-typing-extensions==4.3.0
-uvicorn==0.18.2
-uvloop==0.16.0
-watchfiles==0.16.0
-websockets==10.3
-websocket-client==1.3.3
-async-timeout==4.0.2
-setuptools==65.4.0
\ No newline at end of file
+aiofiles==0.8.0 ; python_version >= "3.7" and python_version < "4.0"
+anyio==3.6.2 ; python_version >= "3.7" and python_version < "4.0"
+asgiref==3.4.1 ; python_version >= "3.7" and python_version < "4.0"
+asn1crypto==1.5.1 ; python_version >= "3.7" and python_version < "4.0"
+async-timeout==4.0.2 ; python_version >= "3.7" and python_version < "4.0"
+attrs==22.1.0 ; python_version >= "3.7" and python_version < "4.0"
+base58==2.1.1 ; python_version >= "3.7" and python_version < "4.0"
+bech32==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
+bitstring==3.1.9 ; python_version >= "3.7" and python_version < "4.0"
+cashu==0.4.2 ; python_version >= "3.7" and python_version < "4.0"
+cerberus==1.3.4 ; python_version >= "3.7" and python_version < "4.0"
+certifi==2022.9.24 ; python_version >= "3.7" and python_version < "4.0"
+cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0"
+charset-normalizer==2.0.12 ; python_version >= "3.7" and python_version < "4.0"
+click==8.0.4 ; python_version >= "3.7" and python_version < "4.0"
+coincurve==17.0.0 ; python_version >= "3.7" and python_version < "4.0"
+colorama==0.4.5 ; python_version >= "3.7" and python_version < "4.0" and platform_system == "Windows" or python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32"
+cryptography==36.0.2 ; python_version >= "3.7" and python_version < "4.0"
+ecdsa==0.18.0 ; python_version >= "3.7" and python_version < "4.0"
+embit==0.4.9 ; python_version >= "3.7" and python_version < "4.0"
+enum34==1.1.10 ; python_version >= "3.7" and python_version < "4.0"
+environs==9.5.0 ; python_version >= "3.7" and python_version < "4.0"
+fastapi==0.83.0 ; python_version >= "3.7" and python_version < "4.0"
+grpcio==1.50.0 ; python_version >= "3.7" and python_version < "4.0"
+h11==0.12.0 ; python_version >= "3.7" and python_version < "4.0"
+httpcore==0.15.0 ; python_version >= "3.7" and python_version < "4.0"
+httptools==0.4.0 ; python_version >= "3.7" and python_version < "4.0"
+httpx==0.23.0 ; python_version >= "3.7" and python_version < "4.0"
+idna==3.4 ; python_version >= "3.7" and python_version < "4.0"
+importlib-metadata==5.0.0 ; python_version >= "3.7" and python_version < "4.0"
+iniconfig==1.1.1 ; python_version >= "3.7" and python_version < "4.0"
+jinja2==3.0.1 ; python_version >= "3.7" and python_version < "4.0"
+lnurl==0.3.6 ; python_version >= "3.7" and python_version < "4.0"
+loguru==0.6.0 ; python_version >= "3.7" and python_version < "4.0"
+markupsafe==2.0.1 ; python_version >= "3.7" and python_version < "4.0"
+marshmallow==3.18.0 ; python_version >= "3.7" and python_version < "4.0"
+outcome==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
+packaging==21.3 ; python_version >= "3.7" and python_version < "4.0"
+pathlib2==2.3.7.post1 ; python_version >= "3.7" and python_version < "4.0"
+pluggy==1.0.0 ; python_version >= "3.7" and python_version < "4.0"
+protobuf==4.21.8 ; python_version >= "3.7" and python_version < "4.0"
+psycopg2-binary==2.9.1 ; python_version >= "3.7" and python_version < "4.0"
+py==1.11.0 ; python_version >= "3.7" and python_version < "4.0"
+pycparser==2.21 ; python_version >= "3.7" and python_version < "4.0"
+pycryptodomex==3.14.1 ; python_version >= "3.7" and python_version < "4.0"
+pydantic==1.10.2 ; python_version >= "3.7" and python_version < "4.0"
+pyln-bolt7==1.0.246 ; python_version >= "3.7" and python_version < "4.0"
+pyln-client==0.11.1 ; python_version >= "3.7" and python_version < "4.0"
+pyln-proto==0.11.1 ; python_version >= "3.7" and python_version < "4.0"
+pyparsing==3.0.9 ; python_version >= "3.7" and python_version < "4.0"
+pypng==0.0.21 ; python_version >= "3.7" and python_version < "4.0"
+pyqrcode==1.2.1 ; python_version >= "3.7" and python_version < "4.0"
+pyscss==1.4.0 ; python_version >= "3.7" and python_version < "4.0"
+pysocks==1.7.1 ; python_version >= "3.7" and python_version < "4.0"
+pytest-asyncio==0.19.0 ; python_version >= "3.7" and python_version < "4.0"
+pytest==7.1.3 ; python_version >= "3.7" and python_version < "4.0"
+python-bitcoinlib==0.11.2 ; python_version >= "3.7" and python_version < "4.0"
+python-dotenv==0.21.0 ; python_version >= "3.7" and python_version < "4.0"
+pyyaml==5.4.1 ; python_version >= "3.7" and python_version < "4.0"
+represent==1.6.0.post0 ; python_version >= "3.7" and python_version < "4.0"
+requests==2.27.1 ; python_version >= "3.7" and python_version < "4.0"
+rfc3986==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
+rfc3986[idna2008]==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
+secp256k1==0.14.0 ; python_version >= "3.7" and python_version < "4.0"
+setuptools==65.5.0 ; python_version >= "3.7" and python_version < "4.0"
+shortuuid==1.0.1 ; python_version >= "3.7" and python_version < "4.0"
+six==1.16.0 ; python_version >= "3.7" and python_version < "4.0"
+sniffio==1.3.0 ; python_version >= "3.7" and python_version < "4.0"
+sqlalchemy-aio==0.17.0 ; python_version >= "3.7" and python_version < "4.0"
+sqlalchemy==1.3.24 ; python_version >= "3.7" and python_version < "4.0"
+sse-starlette==0.6.2 ; python_version >= "3.7" and python_version < "4.0"
+starlette==0.19.1 ; python_version >= "3.7" and python_version < "4.0"
+tomli==2.0.1 ; python_version >= "3.7" and python_version < "4.0"
+typing-extensions==4.4.0 ; python_version >= "3.7" and python_version < "4.0"
+urllib3==1.26.12 ; python_version >= "3.7" and python_version < "4"
+uvicorn==0.18.3 ; python_version >= "3.7" and python_version < "4.0"
+uvloop==0.16.0 ; python_version >= "3.7" and python_version < "4.0"
+watchgod==0.7 ; python_version >= "3.7" and python_version < "4.0"
+websocket-client==1.3.3 ; python_version >= "3.7" and python_version < "4.0"
+websockets==10.0 ; python_version >= "3.7" and python_version < "4.0"
+win32-setctime==1.1.0 ; python_version >= "3.7" and python_version < "4.0" and sys_platform == "win32"
+zipp==3.9.0 ; python_version >= "3.7" and python_version < "4.0"
From 59ffe8b979cdcb0f1e5457d396aa1e3b0b0494a6 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 21 Oct 2022 15:31:24 +0200
Subject: [PATCH 325/696] make format
---
lnbits/extensions/cashu/__init__.py | 2 +-
lnbits/extensions/cashu/crud.py | 8 +--
lnbits/extensions/cashu/tasks.py | 8 +--
.../cashu/templates/cashu/_cashu.html | 4 +-
lnbits/extensions/cashu/views_api.py | 68 ++++++++-----------
5 files changed, 40 insertions(+), 50 deletions(-)
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index 440be092..7944e658 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -34,7 +34,7 @@ def cashu_renderer():
return template_renderer(["lnbits/extensions/cashu/templates"])
-from .tasks import wait_for_paid_invoices, startup_cashu_mint
+from .tasks import startup_cashu_mint, wait_for_paid_invoices
from .views import * # noqa
from .views_api import * # noqa
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index 7ecf9b6a..cbeedc12 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -2,21 +2,19 @@ import os
import random
import time
from binascii import hexlify, unhexlify
-from typing import List, Optional, Union, Any
+from typing import Any, List, Optional, Union
+from cashu.core.base import MintKeyset
from embit import bip32, bip39, ec, script
from embit.networks import NETWORKS
from loguru import logger
+from lnbits.db import Connection, Database
from lnbits.helpers import urlsafe_short_hash
from . import db
-
from .models import Cashu, Pegs, Promises, Proof
-from cashu.core.base import MintKeyset
-from lnbits.db import Database, Connection
-
async def create_cashu(
cashu_id: str, keyset_id: str, wallet_id: str, data: Cashu
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index dddd1ef1..40b521f0 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -1,14 +1,14 @@
import asyncio
import json
+from cashu.core.migrations import migrate_databases
+from cashu.mint import migrations
+
from lnbits.core.models import Payment
from lnbits.tasks import register_invoice_listener
-from .crud import get_cashu
-
-from cashu.mint import migrations
-from cashu.core.migrations import migrate_databases
from . import db, ledger
+from .crud import get_cashu
async def startup_cashu_mint():
diff --git a/lnbits/extensions/cashu/templates/cashu/_cashu.html b/lnbits/extensions/cashu/templates/cashu/_cashu.html
index 03457ac7..0c7c4338 100644
--- a/lnbits/extensions/cashu/templates/cashu/_cashu.html
+++ b/lnbits/extensions/cashu/templates/cashu/_cashu.html
@@ -7,7 +7,9 @@
Created by
- arcbtc , vlad , calle . arcbtc,
+
vlad ,
+
calle .
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index b271bccc..28857663 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -1,61 +1,51 @@
import json
-from http import HTTPStatus
-from typing import Union
import math
+from http import HTTPStatus
from typing import Dict, List, Union
import httpx
-from fastapi import Query
-from fastapi.params import Depends
-from lnurl import decode as decode_lnurl
-from loguru import logger
-from secp256k1 import PublicKey
-from starlette.exceptions import HTTPException
-from lnbits import bolt11
-
-from lnbits.core.crud import get_user
-from lnbits.core.services import (
- check_transaction_status,
- create_invoice,
- fee_reserve,
- pay_invoice,
-)
-
-from lnbits.core.views.api import api_payment
-from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from lnbits.wallets.base import PaymentStatus
-from lnbits.helpers import urlsafe_short_hash
-from lnbits.core.crud import check_internal
-
-# --------- extension imports
-
-from . import cashu_ext
-from .crud import (
- create_cashu,
- delete_cashu,
- get_cashu,
- get_cashus,
-)
-
-from .models import Cashu
-
-from . import ledger
# -------- cashu imports
from cashu.core.base import (
- Proof,
BlindedSignature,
CheckFeesRequest,
CheckFeesResponse,
CheckRequest,
GetMeltResponse,
GetMintResponse,
+ Invoice,
MeltRequest,
MintRequest,
PostSplitResponse,
+ Proof,
SplitRequest,
- Invoice,
)
+from fastapi import Query
+from fastapi.params import Depends
+from lnurl import decode as decode_lnurl
+from loguru import logger
+from secp256k1 import PublicKey
+from starlette.exceptions import HTTPException
+
+from lnbits import bolt11
+from lnbits.core.crud import check_internal, get_user
+from lnbits.core.services import (
+ check_transaction_status,
+ create_invoice,
+ fee_reserve,
+ pay_invoice,
+)
+from lnbits.core.views.api import api_payment
+from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
+from lnbits.helpers import urlsafe_short_hash
+from lnbits.wallets.base import PaymentStatus
+
+from . import cashu_ext, ledger
+from .crud import create_cashu, delete_cashu, get_cashu, get_cashus
+from .models import Cashu
+
+# --------- extension imports
+
LIGHTNING = False
From da61ade4d06521f946b1fe0a6ddf81233cbfab0c Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 24 Oct 2022 09:26:32 +0200
Subject: [PATCH 326/696] works
---
lnbits/extensions/cashu/migrations.py | 3 +-
.../cashu/templates/cashu/_api_docs.html | 12 +-
.../cashu/templates/cashu/index.html | 8 +-
.../cashu/templates/cashu/wallet.html | 12 +-
lnbits/extensions/cashu/views_api.py | 119 ++++++++++++------
pyproject.toml | 2 +-
6 files changed, 100 insertions(+), 56 deletions(-)
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index ec520765..b54c4108 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -12,7 +12,8 @@ async def m001_initial(db):
fraction BOOL,
maxsats INT,
coins INT,
- keyset_id TEXT NOT NULL
+ keyset_id TEXT NOT NULL,
+ issued_sat INT
);
"""
)
diff --git a/lnbits/extensions/cashu/templates/cashu/_api_docs.html b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
index 3476d41a..27ce55f9 100644
--- a/lnbits/extensions/cashu/templates/cashu/_api_docs.html
+++ b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
@@ -8,7 +8,7 @@
- GET /cashu/api/v1/cashus
+ GET /cashu/api/v1/mints
Headers
{"X-Api-Key": <invoice_key>}
Body (application/json)
@@ -18,7 +18,7 @@
[<cashu_object>, ...]
Curl example
curl -X GET {{ request.base_url }}cashu/api/v1/cashus -H "X-Api-Key:
+ >curl -X GET {{ request.base_url }}cashu/api/v1/mints -H "X-Api-Key:
<invoice_key>"
@@ -27,7 +27,7 @@
- POST /cashu/api/v1/cashus
+ POST /cashu/api/v1/mints
Headers
{"X-Api-Key": <invoice_key>}
Body (application/json)
@@ -43,7 +43,7 @@
>
Curl example
curl -X POST {{ request.base_url }}cashu/api/v1/cashus -d '{"name":
+ >curl -X POST {{ request.base_url }}cashu/api/v1/mints -d '{"name":
<string>, "currency": <string>}' -H "Content-type:
application/json" -H "X-Api-Key: <admin_key>"
@@ -62,7 +62,7 @@
DELETE
- /cashu/api/v1/cashus/<cashu_id>
Headers
{"X-Api-Key": <admin_key>}
@@ -71,7 +71,7 @@
Curl example
curl -X DELETE {{ request.base_url
- }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key:
+ }}cashu/api/v1/mints/<cashu_id> -H "X-Api-Key:
<admin_key>"
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index dcb2b4a7..20ed567c 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -271,7 +271,7 @@
LNbits.api
.request(
'GET',
- '/cashu/api/v1/cashus?all_wallets=true',
+ '/cashu/api/v1/mints?all_wallets=true',
this.g.user.wallets[0].inkey
)
.then(function (response) {
@@ -294,7 +294,7 @@
LNbits.api
.request(
'POST',
- '/cashu/api/v1/cashus',
+ '/cashu/api/v1/mints',
_.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet})
.inkey,
data
@@ -314,13 +314,13 @@
LNbits.utils
.confirmDialog(
- 'Are you sure you want to delete this Mint? It will suck for users.'
+ "Are you sure you want to delete this Mint? This mint's users will not be able to redeem their tokens!"
)
.onOk(function () {
LNbits.api
.request(
'DELETE',
- '/cashu/api/v1/cashus/' + cashuId,
+ '/cashu/api/v1/mints/' + cashuId,
_.findWhere(self.g.user.wallets, {id: cashu.wallet}).adminkey
)
.then(function (response) {
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 4f12cd1c..11ac044b 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -913,7 +913,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'GET',
- `/cashu/api/v1/cashu/${this.mintId}/mint?amount=${this.invoiceData.amount}`
+ `/cashu/api/v1/${this.mintId}/mint?amount=${this.invoiceData.amount}`
)
console.log('### data', data)
@@ -940,7 +940,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'POST',
- `/cashu/api/v1/cashu/${this.mintId}/mint?payment_hash=${invoice.hash}`,
+ `/cashu/api/v1/${this.mintId}/mint?payment_hash=${invoice.hash}`,
'',
{
blinded_messages: []
@@ -991,7 +991,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'POST',
- `/cashu/api/v1/cashu/${this.mintId}/mint?payment_hash=${hash}`,
+ `/cashu/api/v1/${this.mintId}/mint?payment_hash=${hash}`,
'',
{
blinded_messages: blindedMessages
@@ -1086,7 +1086,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'POST',
- `/cashu/api/v1/cashu/${this.mintId}/split`,
+ `/cashu/api/v1/${this.mintId}/split`,
'',
payload
)
@@ -1221,7 +1221,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'POST',
- `/cashu/api/v1/cashu/${this.mintId}/melt`,
+ `/cashu/api/v1/${this.mintId}/melt`,
'',
payload
)
@@ -1257,7 +1257,7 @@ page_container %}
fetchMintKeys: async function () {
const {data} = await LNbits.api.request(
'GET',
- `/cashu/api/v1/cashu/${this.mintId}/keys`
+ `/cashu/api/v1/${this.mintId}/keys`
)
this.keys = data
localStorage.setItem('cashu.keys', JSON.stringify(data))
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 28857663..d4654c55 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -47,17 +47,20 @@ from .models import Cashu
# --------- extension imports
-LIGHTNING = False
+LIGHTNING = True
########################################
############### LNBITS MINTS ###########
########################################
-# todo: use /mints
-@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
+
+@cashu_ext.get("/api/v1/mints", status_code=HTTPStatus.OK)
async def api_cashus(
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
+ """
+ Get all mints of this wallet.
+ """
wallet_ids = [wallet.wallet.id]
if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
@@ -65,8 +68,11 @@ async def api_cashus(
return [cashu.dict() for cashu in await get_cashus(wallet_ids)]
-@cashu_ext.post("/api/v1/cashus", status_code=HTTPStatus.CREATED)
+@cashu_ext.post("/api/v1/mints", status_code=HTTPStatus.CREATED)
async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)):
+ """
+ Create a new mint for this wallet.
+ """
cashu_id = urlsafe_short_hash()
# generate a new keyset in cashu
keyset = await ledger.load_keyset(cashu_id)
@@ -78,12 +84,35 @@ async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key
return cashu.dict()
+@cashu_ext.delete("/api/v1/mints/{cashu_id}")
+async def api_cashu_delete(
+ cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
+):
+ """
+ Delete an existing cashu mint.
+ """
+ cashu = await get_cashu(cashu_id)
+
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Cashu mint does not exist."
+ )
+
+ if cashu.wallet != wallet.wallet.id:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN, detail="Not your Cashu mint."
+ )
+
+ await delete_cashu(cashu_id)
+ raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+
+
#######################################
########### CASHU ENDPOINTS ###########
#######################################
-@cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
+@cashu_ext.get("/api/v1/{cashu_id}/keys", status_code=HTTPStatus.OK)
async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
"""Get the public keys of the mint"""
cashu: Union[Cashu, None] = await get_cashu(cashu_id)
@@ -96,7 +125,20 @@ async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
return ledger.get_keyset(keyset_id=cashu.keyset_id)
-@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
+@cashu_ext.get("/api/v1/{cashu_id}/keysets", status_code=HTTPStatus.OK)
+async def keysets(cashu_id: str = Query(None)) -> dict[str, list[str]]:
+ """Get the public keys of the mint"""
+ cashu: Union[Cashu, None] = await get_cashu(cashu_id)
+
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
+
+ return {"keysets": [cashu.keyset_id]}
+
+
+@cashu_ext.get("/api/v1/{cashu_id}/mint")
async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintResponse:
"""
Request minting of new tokens. The mint responds with a Lightning invoice.
@@ -134,7 +176,7 @@ async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintR
return resp
-@cashu_ext.post("/api/v1/cashu/{cashu_id}/mint")
+@cashu_ext.post("/api/v1/{cashu_id}/mint")
async def mint_coins(
data: MintRequest,
cashu_id: str = Query(None),
@@ -157,7 +199,7 @@ async def mint_coins(
if invoice is None:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND,
- detail="Mint does not have this invoice.",
+ detail="Mint does not know this invoice.",
)
if invoice.issued == True:
raise HTTPException(
@@ -173,27 +215,31 @@ async def mint_coins(
)
status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
- # todo: revert to: status.paid != True:
+
if status.paid != True:
raise HTTPException(
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
)
try:
- await ledger.crud.update_lightning_invoice(
- db=ledger.db, hash=payment_hash, issued=True
- )
keyset = ledger.keysets.keysets[cashu.keyset_id]
promises = await ledger._generate_promises(
B_s=data.blinded_messages, keyset=keyset
)
+ assert len(promises), HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="No promises returned."
+ )
+ await ledger.crud.update_lightning_invoice(
+ db=ledger.db, hash=payment_hash, issued=True
+ )
+
return promises
except Exception as e:
logger.error(e)
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-@cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
+@cashu_ext.post("/api/v1/{cashu_id}/melt")
async def melt_coins(
payload: MeltRequest, cashu_id: str = Query(None)
) -> GetMeltResponse:
@@ -211,7 +257,7 @@ async def melt_coins(
# TOKENS
assert all([p.id == cashu.keyset_id for p in proofs]), HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
- detail="Proofs include tokens from other mint.",
+ detail="Proofs include tokens from another mint.",
)
assert all([ledger._verify_proof(p) for p in proofs]), HTTPException(
@@ -248,19 +294,33 @@ async def melt_coins(
return GetMeltResponse(paid=status.paid, preimage=status.preimage)
-@cashu_ext.post("/api/v1/check")
-async def check_spendable(payload: CheckRequest) -> Dict[int, bool]:
+@cashu_ext.post("/api/v1/{cashu_id}/check")
+async def check_spendable(
+ payload: CheckRequest, cashu_id: str = Query(None)
+) -> Dict[int, bool]:
"""Check whether a secret has been spent already or not."""
+ cashu: Union[None, Cashu] = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
return await ledger.check_spendable(payload.proofs)
-@cashu_ext.post("/api/v1/checkfees")
-async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
+@cashu_ext.post("/api/v1/{cashu_id}/checkfees")
+async def check_fees(
+ payload: CheckFeesRequest, cashu_id: str = Query(None)
+) -> CheckFeesResponse:
"""
Responds with the fees necessary to pay a Lightning invoice.
Used by wallets for figuring out the fees they need to supply.
This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu).
"""
+ cashu: Union[None, Cashu] = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
invoice_obj = bolt11.decode(payload.pr)
internal_checking_id = await check_internal(invoice_obj.payment_hash)
@@ -271,7 +331,7 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
return CheckFeesResponse(fee=fees_msat / 1000)
-@cashu_ext.post("/api/v1/cashu/{cashu_id}/split")
+@cashu_ext.post("/api/v1/{cashu_id}/split")
async def split(
payload: SplitRequest, cashu_id: str = Query(None)
) -> PostSplitResponse:
@@ -291,7 +351,8 @@ async def split(
assert outputs, Exception("no outputs provided.")
split_return = None
try:
- split_return = await ledger.split(proofs, amount, outputs, cashu.keyset_id)
+ keyset = ledger.keysets.keysets[cashu.keyset_id]
+ split_return = await ledger.split(proofs, amount, outputs, keyset)
except Exception as exc:
HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
@@ -318,24 +379,6 @@ async def split(
# return cashu.dict()
-# @cashu_ext.delete("/api/v1s/{cashu_id}")
-# async def api_cashu_delete(
-# cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
-# ):
-# cashu = await get_cashu(cashu_id)
-
-# if not cashu:
-# raise HTTPException(
-# status_code=HTTPStatus.NOT_FOUND, detail="Cashu does not exist."
-# )
-
-# if cashu.wallet != wallet.wallet.id:
-# raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your Cashu.")
-
-# await delete_cashu(cashu_id)
-# raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
-
-
# ########################################
# #################????###################
# ########################################
diff --git a/pyproject.toml b/pyproject.toml
index beb0eae9..74a76acc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,7 +63,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
-cashu = "0.4.2"
+cashu = {path = "../cashu"}
[tool.poetry.dev-dependencies]
From 5ecf198f2cf41b5a8ff7cf6b485973048d27dbd6 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Wed, 26 Oct 2022 02:30:28 +0200
Subject: [PATCH 327/696] doesnt work yet
---
.../cashu/templates/cashu/wallet.html | 390 ++++++++++--------
1 file changed, 218 insertions(+), 172 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 11ac044b..ee243c1f 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -71,7 +71,7 @@ page_container %}
-
+
@@ -109,7 +109,7 @@ page_container %}
{% endraw %}
-
+
- How much would you like to buy?
+ Create a Lightning invoice
@@ -260,7 +261,7 @@ page_container %}
color="grey"
>Copy invoice
- Request Invoice
Show Tokens Send Tokens
Burn Tokens
-
Accept Tokens Receive Tokens
Close
-
+
{% raw %}
- Amount: {{ sellData.invoice.sat }}
+ Amount: {{ payInvoiceData.invoice.sat }}
sats
- Description: {{ sellData.invoice.description }}
- Expire date: {{ sellData.invoice.expireDate }}
- Expired: {{ sellData.invoice.expired }}
- Hash: {{ sellData.invoice.hash }} {% endraw %}
+ Description: {{ payInvoiceData.invoice.description
+ }}
+ Expire date: {{ payInvoiceData.invoice.expireDate
+ }}
+ Expired: {{ payInvoiceData.invoice.expired }}
+ Hash: {{ payInvoiceData.invoice.hash }} {% endraw
+ %}
Check Invoice
-
Sell Token
+
Pay invoice
Close
@@ -458,7 +460,7 @@ page_container %}
bolt11: '',
hash: ''
},
- sellData: {
+ payInvoiceData: {
invoice: '',
bolt11: ''
},
@@ -475,6 +477,7 @@ page_container %}
showPayInvoice: false,
showSendTokens: false,
showReceiveTokens: false,
+ promises: [],
tokens: [],
tab: 'tokens',
@@ -614,7 +617,7 @@ page_container %}
},
tokenList: function () {
- const x = this.tokens
+ const x = this.proofs
.filter(t => t.promises?.length)
.map(t => t.blindedMessages)
.flat()
@@ -636,7 +639,7 @@ page_container %}
},
balance: function () {
- return this.tokens
+ return this.proofs
.filter(t => t.promises?.length)
.map(t => t.blindedMessages)
.flat()
@@ -891,8 +894,8 @@ page_container %}
showPayInvoiceDialog: function () {
console.log('### showPayInvoiceDialog')
- this.sellData.invoice = ''
- this.sellData.bolt11 = ''
+ this.payInvoiceData.invoice = ''
+ this.payInvoiceData.bolt11 = ''
this.showPayInvoice = true
},
@@ -909,31 +912,6 @@ page_container %}
this.showReceiveTokens = true
},
- requestInvoice: async function () {
- try {
- const {data} = await LNbits.api.request(
- 'GET',
- `/cashu/api/v1/${this.mintId}/mint?amount=${this.invoiceData.amount}`
- )
- console.log('### data', data)
-
- this.invoiceData.bolt11 = data.pr
- this.invoiceData.hash = data.hash
- this.invoicesCashu.push({
- ..._.clone(this.invoiceData),
- date: currentDateStr(),
- status: 'pending'
- })
- this.storeinvoicesCashu()
- const amounts = splitAmount(this.invoiceData.amount)
- await this.requestTokens(amounts, this.invoiceData.hash)
- this.tab = 'orders'
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- }
- },
-
checkXXXXXX: async function () {
for (const invoice of this.invoicesCashu) {
if (invoice.status === 'pending') {
@@ -955,24 +933,72 @@ page_container %}
}
},
- recheckInvoice: async function (hash) {
- console.log('### recheckInvoice.hash', hash)
- const tokens = this.tokens.find(bt => bt.hash === hash)
- console.log('### recheckInvoice.tokens', tokens)
- if (!tokens) {
- console.error('####### no token for hash', hash)
- return
- }
- const promises = await this.fetchPromisesFromMint(
- hash,
- tokens.blindedMessages
- )
- if (promises && promises.length) {
- tokens.promises = promises
- tokens.status = 'paid'
- this.storeTokens()
+ //////////////////////// MINT //////////////////////////////////////////
- const invoice = this.invoicesCashu.find(bo => bo.hash === hash)
+ requestMint: async function () {
+ // gets an invoice from the mint to get new tokens
+ try {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ `/cashu/api/v1/${this.mintId}/mint?amount=${this.invoiceData.amount}`
+ )
+ console.log('### data', data)
+
+ this.invoiceData.bolt11 = data.pr
+ this.invoiceData.hash = data.hash
+ this.invoicesCashu.push({
+ ..._.clone(this.invoiceData),
+ date: currentDateStr(),
+ status: 'pending'
+ })
+ this.storeinvoicesCashu()
+ this.tab = 'invoices'
+ return data
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ mintApi: async function (amounts, payment_hash) {
+ console.log('### promises', payment_hash)
+ try {
+ let secrets = generateSecrets(amounts)
+ let {blinded_messages, rs} = constructOutputs(amounts, secrets)
+ const {promises} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/${this.mintId}/mint?payment_hash=${payment_hash}`,
+ '',
+ {
+ blinded_messages: blinded_messages
+ }
+ )
+ console.log('### promises data', promises)
+ return promises
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ mint: async function (amount, payment_hash) {
+ try {
+ const split = splitAmount(amount)
+ const proofs = await mintApi(split, payment_hash)
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ recheckInvoice: async function (payment_hash) {
+ console.log('### recheckInvoice.hash', payment_hash)
+ const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
+ const amounts = splitAmount(invoice.amount)
+ const newTokens = await this.buildTokens(amounts, payment_hash)
+ const promises = await this.mint(invoice.amount, payment_hash)
+ if (promises && promises.length) {
+ newTokens.promises = promises
+ newTokens.status = 'paid'
+ this.proofs.push(newTokens)
+ this.storeProofs()
invoice.status = 'paid'
this.storeinvoicesCashu()
}
@@ -980,56 +1006,116 @@ page_container %}
requestTokens: async function (amounts, paymentHash) {
const newTokens = await this.buildTokens(amounts, paymentHash)
- this.tokens.push(newTokens)
- this.storeTokens()
- console.log('### this.tokens', this.tokens)
- // await this.fetchPromisesFromMint(paymentHash, newTokens.newTokens)
+ // this.proofs.push(newTokens)
+ // this.storeProofs()
+ // console.log('### this.proofs', this.proofs)
+ // await this.mint(paymentHash, newTokens.newTokens)
},
- fetchPromisesFromMint: async function (hash, blindedMessages) {
- console.log('### promises', hash, blindedMessages)
- try {
- const {data} = await LNbits.api.request(
- 'POST',
- `/cashu/api/v1/${this.mintId}/mint?payment_hash=${hash}`,
- '',
- {
- blinded_messages: blindedMessages
- }
- )
- console.log('### promises data', data)
- return data
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
+ generateSecrets: async function (amounts) {
+ const secrets = []
+ for (let i = 0; i < amounts.length; i++) {
+ const secret = nobleSecp256k1.utils.randomBytes(32)
+ secrets.push(encodedSecret)
+ }
+ return secrets
+ },
+ constructOutputs: async function (amounts, secrets) {
+ const blindedMessages = []
+ const randomBlindingFactors = []
+ for (let i = 0; i < amounts.length; i++) {
+ const {B_, r} = await step1Bob(secret)
+ blindedMessages.push(B_)
+ randomBlindingFactors.push(r)
+ }
+ return {
+ blindedMessages,
+ randomBlindingFactors
}
},
- buildAndShowTokens: async function () {
+ constructProofs: function (promises, secrets, rs) {
+ const proofs = []
+ for (let i = 0; i < promises.length; i++) {
+ let {amount, C, secret} = promiseToProof(
+ promises[i].amount,
+ promises[i]['C_'],
+ promises[i].secret,
+ promises[i].randomBlindingFactor
+ )
+ }
+ return proofs
+ },
+
+ promiseToProof: function (amount, C_hex, secret, randomBlindingFactor) {
+ const C_ = nobleSecp256k1.Point.fromHex(C_hex)
+ const A = this.keys[amount]
+ const C = step3Bob(
+ C_,
+ randomBlindingFactor,
+ nobleSecp256k1.Point.fromHex(A)
+ )
+ return {
+ amount,
+ C: C.toHex(true),
+ secret
+ }
+ },
+ buildTokens: async function (amounts, paymentHash) {
+ const blindedMessages = []
+ const secrets = []
+ const randomBlindingFactors = []
+ for (let i = 0; i < amounts.length; i++) {
+ const secret = nobleSecp256k1.utils.randomBytes(32)
+ // const secret = nobleSecp256k1.utils.hexToBytes('0000000000000000000000000000000000000000000000000000000000000003')
+ // todo: base64Url
+ const encodedSecret = uint8ToBase64.encode(secret)
+ secrets.push(encodedSecret)
+ const {B_, randomBlindingFactor} = await step1Bob(secret)
+ randomBlindingFactors.push(randomBlindingFactor)
+ blindedMessages.push({amount: amounts[i], B_: B_})
+ }
+
+ const newTokens = {
+ hash: paymentHash,
+ blindedMessages,
+ randomBlindingFactors,
+ secrets,
+ status: 'pending'
+ }
+ return newTokens
+ },
+
+ ////////////////////////////////////////////////////////////////////////////////////
+
+ sendTokens: async function () {
const amounts = splitAmount(this.sendData.amount)
const sendTokens = []
- for (const amount of amounts) {
- const token = this.findTokenForAmount(amount)
- if (token) {
- sendTokens.push(token)
- } else {
- this.$q.notify({
- timeout: 5000,
- type: 'warning',
- message: `Cannot select amount for denomination ${amount}`
- })
- this.sendData.tokens = ''
- this.sendData.tokensBase64 = ''
- return
- }
- }
+ sendTokens.push(this.proofs)
+ // for (const amount of amounts) {
+ // const token = this.findTokenForAmount(amount)
+ // if (token) {
+ // sendTokens.push(token)
+ // } else {
+ // this.$q.notify({
+ // timeout: 5000,
+ // type: 'warning',
+ // message: `Cannot select amount for denomination ${amount}`
+ // })
+ // this.sendData.tokens = ''
+ // this.sendData.tokensBase64 = ''
+ // return
+ // }
+ // }
+ this.sendData.tokens = ''
+ this.sendData.tokensBase64 = ''
console.log('### sendTokens', sendTokens)
- this.sendData.tokens = sendTokens.map(t => {
+ this.sendData.tokens = sendTokens.map((token, tokenIndex) => {
return this.promiseToProof(
- t.promise.amount,
- t.promise['C_'],
- t.secret,
- t.randomBlindingFactor
+ token.promises[tokenIndex].amount,
+ token.promises[tokenIndex]['C_'],
+ token.promises[tokenIndex].secret,
+ token.promises[tokenIndex].randomBlindingFactor
)
})
console.log('### this.sendData.tokens', this.sendData.tokens)
@@ -1038,7 +1124,7 @@ page_container %}
burnTokens: function () {
for (const sentToken of this.sendData.tokens) {
- for (const token of this.tokens) {
+ for (const token of this.proofs) {
if (token.status === 'paid') {
const secretIndex = token.secrets.findIndex(
s => s === sentToken.secret
@@ -1058,12 +1144,12 @@ page_container %}
timeout: 5000,
message: 'Tokens burned'
})
- this.storeTokens()
+ this.storeProofs()
this.showSendTokens = false
- console.log('### this.tokens', this.tokens)
+ console.log('### this.proofs', this.proofs)
},
- acceptTokens: async function () {
+ receiveTokens: async function () {
this.showReceiveTokens = false
console.log('### receive tokens', this.receiveData.tokensBase64)
if (this.receiveData.tokensBase64) {
@@ -1097,8 +1183,8 @@ page_container %}
// Object.assign(newTokens[i], promises)
// }
console.log('newTokens 2', newTokens)
- this.tokens.push(newTokens)
- this.storeTokens()
+ this.proofs.push(newTokens)
+ this.storeProofs()
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
@@ -1107,7 +1193,7 @@ page_container %}
},
findTokenForAmount: function (amount) {
- for (const token of this.tokens) {
+ for (const token of this.proofs) {
const index = token.promises?.findIndex(p => p.amount === amount)
if (index >= 0) {
return {
@@ -1119,37 +1205,10 @@ page_container %}
}
},
- constructProof: function (token) {},
-
- buildTokens: async function (amounts, paymentHash) {
- const blindedMessages = []
- const secrets = []
- const randomBlindingFactors = []
- for (let i = 0; i < amounts.length; i++) {
- const secret = nobleSecp256k1.utils.randomBytes(32)
- // const secret = nobleSecp256k1.utils.hexToBytes('0000000000000000000000000000000000000000000000000000000000000003')
- // todo: base64Url
- const encodedSecret = uint8ToBase64.encode(secret)
- secrets.push(encodedSecret)
- const {B_, randomBlindingFactor} = await step1Bob(secret)
- randomBlindingFactors.push(randomBlindingFactor)
- blindedMessages.push({amount: amounts[i], B_: B_})
- }
-
- const newTokens = {
- hash: paymentHash,
- blindedMessages,
- randomBlindingFactors,
- secrets,
- status: 'pending'
- }
- return newTokens
- },
-
checkInvoice: function () {
console.log('#### checkInvoice')
try {
- const invoice = decode(this.sellData.bolt11)
+ const invoice = decode(this.payInvoiceData.bolt11)
const cleanInvoice = {
msat: invoice.human_readable_part.amount,
@@ -1177,10 +1236,13 @@ page_container %}
}
}
- this.sellData.invoice = cleanInvoice
+ this.payInvoiceData.invoice = cleanInvoice
})
- console.log('#### this.sellData.invoice', this.sellData.invoice)
+ console.log(
+ '#### this.payInvoiceData.invoice',
+ this.payInvoiceData.invoice
+ )
} catch (error) {
this.$q.notify({
timeout: 5000,
@@ -1191,10 +1253,10 @@ page_container %}
}
},
- sellTokens: async function () {
+ melt: async function () {
console.log('#### sell tokens')
- const amount = this.sellData.invoice.sat
- const paidTokens = this.tokens.filter(t => t.promises?.length)
+ const amount = this.payInvoiceData.invoice.sat
+ const paidTokens = this.proofs.filter(t => t.promises?.length)
console.log('### paidTokens', paidTokens)
const proofs = paidTokens.map(token => {
return token.promises.map((promise, promiseIndex) => {
@@ -1215,7 +1277,7 @@ page_container %}
const payload = {
proofs: proofs.flat(),
amount,
- invoice: this.sellData.bolt11
+ invoice: this.payInvoiceData.bolt11
}
console.log('#### payload', JSON.stringify(payload))
try {
@@ -1237,22 +1299,6 @@ page_container %}
// C_hex = promise['C_']
// amount = promise.amount
- promiseToProof: function (amount, C_hex, secret, randomBlindingFactor) {
- const C_ = nobleSecp256k1.Point.fromHex(C_hex)
- const A = this.keys[amount]
-
- const C = step3Bob(
- C_,
- randomBlindingFactor,
- nobleSecp256k1.Point.fromHex(A)
- )
-
- return {
- amount,
- secret,
- C: C.toHex(true)
- }
- },
fetchMintKeys: async function () {
const {data} = await LNbits.api.request(
@@ -1269,10 +1315,10 @@ page_container %}
JSON.stringify(this.invoicesCashu)
)
},
- storeTokens: function () {
+ storeProofs: function () {
localStorage.setItem(
- 'cashu.tokens',
- JSON.stringify(this.tokens, bigIntStringify)
+ 'cashu.proofs',
+ JSON.stringify(this.proofs, bigIntStringify)
)
}
},
@@ -1330,9 +1376,9 @@ page_container %}
this.invoicesCashu = JSON.parse(
localStorage.getItem('cashu.invoicesCashu') || '[]'
)
- this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
+ this.proofs = JSON.parse(localStorage.getItem('cashu.proofs') || '[]')
console.log('### invoicesCashu', this.invoicesCashu)
- console.table('### tokens', this.tokens)
+ console.table('### tokens', this.proofs)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
From 67d0249d193f90a7ccdadcae4b90d327df4de930 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 28 Oct 2022 19:34:04 +0200
Subject: [PATCH 328/696] split doesnt work yet
---
lnbits/extensions/cashu/static/js/dhke.js | 12 +-
.../cashu/templates/cashu/wallet.html | 417 +++++++++++-------
lnbits/extensions/cashu/views_api.py | 1 -
3 files changed, 269 insertions(+), 161 deletions(-)
diff --git a/lnbits/extensions/cashu/static/js/dhke.js b/lnbits/extensions/cashu/static/js/dhke.js
index c9e2d146..c35a34e9 100644
--- a/lnbits/extensions/cashu/static/js/dhke.js
+++ b/lnbits/extensions/cashu/static/js/dhke.js
@@ -19,17 +19,15 @@ async function hashToCurve(secretMessage) {
return point
}
-async function step1Bob(secretMessage) {
+async function step1Alice(secretMessage) {
const Y = await hashToCurve(secretMessage)
- const randomBlindingFactor = bytesToNumber(
- nobleSecp256k1.utils.randomPrivateKey()
- )
- const P = nobleSecp256k1.Point.fromPrivateKey(randomBlindingFactor)
+ const r = bytesToNumber(nobleSecp256k1.utils.randomPrivateKey())
+ const P = nobleSecp256k1.Point.fromPrivateKey(r)
const B_ = Y.add(P)
- return {B_: B_.toHex(true), randomBlindingFactor}
+ return {B_: B_.toHex(true), r}
}
-function step3Bob(C_, r, A) {
+function step3Alice(C_, r, A) {
const rInt = BigInt(r)
const C = C_.subtract(A.multiply(rInt))
return C
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index ee243c1f..4af9c148 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -426,6 +426,10 @@ page_container %}
}
{% endblock %} {% block scripts %}
+
+
+
+
-
-
-
-
{% endblock %}
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index d4654c55..4d3e854c 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -347,7 +347,6 @@ async def split(
proofs = payload.proofs
amount = payload.amount
outputs = payload.outputs.blinded_messages
- # backwards compatibility with clients < v0.2.2
assert outputs, Exception("no outputs provided.")
split_return = None
try:
From c0f0421f8ab3de8511c7073c37020e6c298ef78d Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Wed, 2 Nov 2022 23:43:37 +0100
Subject: [PATCH 329/696] for r, dont use bigint but hex string
---
lnbits/extensions/cashu/static/js/dhke.js | 8 +++++---
lnbits/extensions/cashu/templates/cashu/wallet.html | 10 ++++++++--
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/cashu/static/js/dhke.js b/lnbits/extensions/cashu/static/js/dhke.js
index c35a34e9..935bf6d4 100644
--- a/lnbits/extensions/cashu/static/js/dhke.js
+++ b/lnbits/extensions/cashu/static/js/dhke.js
@@ -21,14 +21,16 @@ async function hashToCurve(secretMessage) {
async function step1Alice(secretMessage) {
const Y = await hashToCurve(secretMessage)
- const r = bytesToNumber(nobleSecp256k1.utils.randomPrivateKey())
+ const rpk = nobleSecp256k1.utils.randomPrivateKey()
+ const r = bytesToNumber(rpk)
const P = nobleSecp256k1.Point.fromPrivateKey(r)
const B_ = Y.add(P)
- return {B_: B_.toHex(true), r}
+ return {B_: B_.toHex(true), r: nobleSecp256k1.utils.bytesToHex(rpk)}
}
function step3Alice(C_, r, A) {
- const rInt = BigInt(r)
+ // const rInt = BigInt(r)
+ const rInt = bytesToNumber(r)
const C = C_.subtract(A.multiply(rInt))
return C
}
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 4af9c148..105c1e08 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1060,7 +1060,11 @@ page_container %}
promiseToProof: function (id, amount, C_hex, secret, r) {
const C_ = nobleSecp256k1.Point.fromHex(C_hex)
const A = this.keys[amount]
- const C = step3Alice(C_, r, nobleSecp256k1.Point.fromHex(A))
+ const C = step3Alice(
+ C_,
+ nobleSecp256k1.utils.hexToBytes(r),
+ nobleSecp256k1.Point.fromHex(A)
+ )
return {
id,
amount,
@@ -1226,7 +1230,9 @@ page_container %}
const rs = []
for (let i = 0; i < amounts.length; i++) {
const secret = nobleSecp256k1.utils.randomBytes(32)
- // const secret = nobleSecp256k1.utils.hexToBytes('0000000000000000000000000000000000000000000000000000000000000003')
+ // const secret = nobleSecp256k1.utils.hexToBytes(
+ // '0000000000000000000000000000000000000000000000000000000000000000'
+ // )
// todo: base64Url
const encodedSecret = uint8ToBase64.encode(secret)
secrets.push(encodedSecret)
From 3f7da8a69b9ff7dbbdde2dcec2a8b284a67c4102 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 3 Nov 2022 15:15:09 +0200
Subject: [PATCH 330/696] fix: token validation
---
lnbits/extensions/cashu/static/js/dhke.js | 3 +++
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
lnbits/extensions/cashu/views_api.py | 2 +-
3 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/static/js/dhke.js b/lnbits/extensions/cashu/static/js/dhke.js
index 935bf6d4..9c7aee1b 100644
--- a/lnbits/extensions/cashu/static/js/dhke.js
+++ b/lnbits/extensions/cashu/static/js/dhke.js
@@ -20,6 +20,9 @@ async function hashToCurve(secretMessage) {
}
async function step1Alice(secretMessage) {
+ // todo: document & validate `secretMessage` format
+ secretMessage = uint8ToBase64.encode(secretMessage)
+ secretMessage = new TextEncoder().encode(secretMessage);
const Y = await hashToCurve(secretMessage)
const rpk = nobleSecp256k1.utils.randomPrivateKey()
const r = bytesToNumber(rpk)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 105c1e08..26bc3d26 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1278,7 +1278,7 @@ page_container %}
// }
this.sendData.tokens = ''
this.sendData.tokensBase64 = ''
- console.log('### sendTokens', sendTokens)
+ // console.log('### sendTokens', sendTokens)
// this.sendData.tokens = sendTokens.map((token, tokenIndex) => {
// return this.promiseToProof(
// token.promises[tokenIndex].amount,
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 4d3e854c..806347cb 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -353,7 +353,7 @@ async def split(
keyset = ledger.keysets.keysets[cashu.keyset_id]
split_return = await ledger.split(proofs, amount, outputs, keyset)
except Exception as exc:
- HTTPException(
+ raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST,
detail=str(exc),
)
From 57989b0d1e45c0911ad47621fa21ed24ab61655c Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 15:25:51 +0100
Subject: [PATCH 331/696] works
---
.../cashu/templates/cashu/wallet.html | 579 ++++++++++--------
1 file changed, 335 insertions(+), 244 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 26bc3d26..3a9c48db 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -10,8 +10,9 @@ page_container %}
Pay invoice
@@ -43,28 +45,28 @@ page_container %}
-
+
Receive Receive Tokens
-
-
@@ -82,24 +84,24 @@ page_container %}
:data="tokenList"
:columns="tokensTable.columns"
:pagination.sync="tokensTable.pagination"
- no-data-label="No tokens made yet"
+ no-data-label="No tokens yet"
:filter="tokensTable.filter"
>
{% raw %}
- {{props.row.denomination}}
+ {{props.row.value}}
{{props.row.count}}
-
- {{props.row.value}}
+
+ {{props.row.sum}}
{{props.row.memo}}
@@ -181,14 +183,14 @@ page_container %}
indicator-color="transparent"
>
-
-
-
+
+
@@ -227,13 +229,12 @@ page_container %}
dense
v-model.number="invoiceData.amount"
label="Amount ({{LNBITS_DENOMINATION}}) *"
- mask="#.##"
+ mask="#"
fill-mask="0"
reverse-fill-mask
- type="number"
+ autofocus
class="q-mb-lg"
>
-
Copy invoice
- Request Invoice
+ Recheck
+ -->
+ Create Invoice
Close
-
@@ -310,13 +322,22 @@ page_container %}
Send Tokens
- Burn Tokens -->
+ Copy token
-
Receive Tokens
@@ -394,7 +417,9 @@ page_container %}
color="grey"
>Check Invoice
-
Pay invoice
+
Pay invoice
Close
@@ -464,6 +489,7 @@ page_container %}
bolt11: '',
hash: ''
},
+ invoiceCheckListener: () => {},
payInvoiceData: {
invoice: '',
bolt11: ''
@@ -553,6 +579,8 @@ page_container %}
}
],
pagination: {
+ sortBy: 'date',
+ descending: true,
rowsPerPage: 10
},
filter: null
@@ -561,10 +589,10 @@ page_container %}
tokensTable: {
columns: [
{
- name: 'denomination',
+ name: 'value',
align: 'left',
- label: 'Denomination',
- field: 'denomination',
+ label: 'Value ({{LNBITS_DENOMINATION}})',
+ field: 'value',
sortable: true
},
{
@@ -575,19 +603,19 @@ page_container %}
sortable: true
},
{
- name: 'value',
+ name: 'sum',
align: 'left',
- label: 'Value',
- field: 'value',
- sortable: true
- },
- {
- name: 'memo',
- align: 'left',
- label: 'Memo',
- field: 'memo',
+ label: 'Sum ({{LNBITS_DENOMINATION}})',
+ field: 'sum',
sortable: true
}
+ // {
+ // name: 'memo',
+ // align: 'left',
+ // label: 'Memo',
+ // field: 'memo',
+ // sortable: true
+ // }
],
pagination: {
rowsPerPage: 10
@@ -622,23 +650,15 @@ page_container %}
tokenList: function () {
const x = this.proofs
- .filter(t => t.promises?.length)
- .map(t => t.blindedMessages)
- .flat()
- .map(t => ({
- blindingFactor: t.B_,
- denomination: t.amount
- }))
- .reduce((y, t) => {
- y[`_${t.denomination}`] = y[`_${t.denomination}`] || []
- y[`_${t.denomination}`].push(t)
- return y
+ .map(t => t.amount)
+ .reduce((acc, amount) => {
+ acc[amount] = acc[amount] + amount || 1
+ return acc
}, {})
-
return Object.keys(x).map(k => ({
- denomination: x[k][0].denomination,
- count: x[k].length,
- value: x[k][0].denomination * x[k].length
+ value: k,
+ count: x[k],
+ sum: k * x[k]
}))
},
@@ -804,6 +824,7 @@ page_container %}
caption: '400 BAD REQUEST'
})
this.parse.show = false
+ throw error
return
}
@@ -891,6 +912,7 @@ page_container %}
},
showInvoiceDialog: function (data) {
+ console.log('##### showInvoiceDialog')
this.invoiceData = _.clone(data)
this.showInvoiceDetails = true
},
@@ -915,29 +937,25 @@ page_container %}
this.showReceiveTokens = true
},
- recheckPendingInvoices: async function () {
- for (const invoice of this.invoicesCashu) {
- if (invoice.status === 'pending') {
- this.recheckInvoice(invoice.hash)
- // try {
- // const {data} = await LNbits.api.request(
- // 'POST',
- // `/cashu/api/v1/${this.mintId}/mint?payment_hash=${invoice.hash}`,
- // '',
- // {
- // blinded_messages: []
- // }
- // )
- // console.log('### data', data)
- // } catch (error) {
- // console.error(error)
- // LNbits.utils.notifyApiError(error)
- // }
- }
- }
- },
-
//////////////////////// MINT //////////////////////////////////////////
+ requestMintButton: async function () {
+ await this.requestMint()
+ console.log('this is your invoice BEFORE')
+ console.log(this.invoiceData)
+ this.invoiceCheckListener = setInterval(async () => {
+ try {
+ console.log('this is your invoice AFTER')
+ console.log(this.invoiceData)
+ await this.recheckInvoice(this.invoiceData.hash, false)
+ clearInterval(this.invoiceCheckListener)
+ this.invoiceData.bolt11 = ''
+ this.showInvoiceDetails = false
+ this.fetchBalance()
+ } catch (error) {
+ console.log('not paid yet')
+ }
+ }, 3000)
+ },
requestMint: async function () {
// gets an invoice from the mint to get new tokens
@@ -961,9 +979,10 @@ page_container %}
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
+ throw error
}
},
- mintApi: async function (amounts, payment_hash) {
+ mintApi: async function (amounts, payment_hash, verbose = true) {
console.log('### promises', payment_hash)
try {
let secrets = await this.generateSecrets(amounts)
@@ -984,19 +1003,29 @@ page_container %}
return proofs
} catch (error) {
console.error(error)
- LNbits.utils.notifyApiError(error)
+ if (verbose) {
+ LNbits.utils.notifyApiError(error)
+ }
+ throw error
}
},
- mint: async function (amount, payment_hash) {
+ mint: async function (amount, payment_hash, verbose = true) {
try {
const split = splitAmount(amount)
- const proofs = await this.mintApi(split, payment_hash)
+ const proofs = await this.mintApi(split, payment_hash, verbose)
+ if (!proofs.length) {
+ throw 'could not mint'
+ }
this.proofs.push(...proofs)
this.storeProofs()
await this.setInvoicePaid(payment_hash)
+ return proofs
} catch (error) {
console.error(error)
- LNbits.utils.notifyApiError(error)
+ if (verbose) {
+ LNbits.utils.notifyApiError(error)
+ }
+ throw error
}
},
setInvoicePaid: async function (payment_hash) {
@@ -1004,10 +1033,16 @@ page_container %}
invoice.status = 'paid'
this.storeinvoicesCashu()
},
- recheckInvoice: async function (payment_hash) {
+ recheckInvoice: async function (payment_hash, verbose = true) {
console.log('### recheckInvoice.hash', payment_hash)
const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
- this.mint(invoice.amount, invoice.hash)
+ try {
+ proofs = await this.mint(invoice.amount, invoice.hash, verbose)
+ return proofs
+ } catch (error) {
+ console.log('Invoice still pending')
+ throw error
+ }
},
// requestTokens: async function (amounts, paymentHash) {
@@ -1100,6 +1135,7 @@ page_container %}
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
+ throw error
}
},
@@ -1120,6 +1156,7 @@ page_container %}
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
+ throw error
}
},
splitApi: async function (proofs, amount) {
@@ -1176,79 +1213,27 @@ page_container %}
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
+ throw error
}
},
- ////////////////////////////////////////////////////////////////////////////////////
-
- receiveTokens: async function () {
+ redeem: async function () {
this.showReceiveTokens = false
console.log('### receive tokens', this.receiveData.tokensBase64)
- if (this.receiveData.tokensBase64) {
+ try {
+ if (this.receiveData.tokensBase64.length == 0) {
+ throw new Error('no tokens provided.')
+ }
const tokensJson = atob(this.receiveData.tokensBase64)
const proofs = JSON.parse(tokensJson)
const amount = proofs.reduce((s, t) => (s += t.amount), 0)
- const amounts = splitAmount(amount)
- const newTokens = await this.buildTokens(amounts)
- console.log('newTokens', newTokens)
-
- const payload = {
- amount,
- proofs,
- outputs: {
- blinded_messages: newTokens.blindedMessages
- }
- }
-
- console.log('payload', JSON.stringify(payload))
- try {
- const {data} = await LNbits.api.request(
- 'POST',
- `/cashu/api/v1/${this.mintId}/split`,
- '',
- payload
- )
-
- newTokens.promises = data.snd
- // console.log('split data', JSON.stringify(data.snd))
- // for (let i =0 ;i < newTokens.length; i++) {
- // Object.assign(newTokens[i], promises)
- // }
- console.log('newTokens 2', newTokens)
- this.proofs.push(newTokens)
- this.storeProofs()
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- }
+ let {fristProofs, scndProofs} = await this.split(proofs, amount)
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
}
- },
-
- buildTokens: async function (amounts, paymentHash) {
- const blindedMessages = []
- const secrets = []
- const rs = []
- for (let i = 0; i < amounts.length; i++) {
- const secret = nobleSecp256k1.utils.randomBytes(32)
- // const secret = nobleSecp256k1.utils.hexToBytes(
- // '0000000000000000000000000000000000000000000000000000000000000000'
- // )
- // todo: base64Url
- const encodedSecret = uint8ToBase64.encode(secret)
- secrets.push(encodedSecret)
- const {B_, r} = await step1Alice(secret)
- rs.push(r)
- blindedMessages.push({amount: amounts[i], B_: B_})
- }
-
- const newTokens = {
- hash: paymentHash,
- blindedMessages,
- rs,
- secrets,
- status: 'pending'
- }
- return newTokens
+ // }
},
sendTokens: async function () {
@@ -1257,68 +1242,173 @@ page_container %}
this.proofs,
this.sendData.amount
)
-
- // const amounts = splitAmount(this.sendData.amount)
- // const sendTokens = []
- // sendTokens.push(this.proofs)
- // for (const amount of amounts) {
- // const token = this.findTokenForAmount(amount)
- // if (token) {
- // sendTokens.push(token)
- // } else {
- // this.$q.notify({
- // timeout: 5000,
- // type: 'warning',
- // message: `Cannot select amount for denomination ${amount}`
- // })
- // this.sendData.tokens = ''
- // this.sendData.tokensBase64 = ''
- // return
- // }
- // }
this.sendData.tokens = ''
this.sendData.tokensBase64 = ''
- // console.log('### sendTokens', sendTokens)
- // this.sendData.tokens = sendTokens.map((token, tokenIndex) => {
- // return this.promiseToProof(
- // token.promises[tokenIndex].amount,
- // token.promises[tokenIndex]['C_'],
- // token.promises[tokenIndex].secret,
- // token.promises[tokenIndex].r
- // )
- // })
this.sendData.tokens = scndProofs
console.log('### this.sendData.tokens', this.sendData.tokens)
this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
+
+ // delete tokens from db
+ this.proofs = fristProofs
+ // add new fristProofs, scndProofs to this.proofs
+ this.storeProofs()
},
- burnTokens: function () {
- for (const sentToken of this.sendData.tokens) {
- for (const token of this.proofs) {
- if (token.status === 'paid') {
- const secretIndex = token.secrets.findIndex(
- s => s === sentToken.secret
- )
- console.log('### secretIndex', secretIndex)
- if (secretIndex >= 0) {
- token.blindedMessages?.splice(secretIndex, 1)
- token.promises?.splice(secretIndex, 1)
- token.rs?.splice(secretIndex, 1)
- token.secrets?.splice(secretIndex, 1)
- }
- }
+ melt: async function () {
+ console.log('#### sell tokens')
+ const amount = this.payInvoiceData.invoice.sat
+ const paidTokens = this.proofs.filter(t => t.promises?.length)
+ console.log('### paidTokens', paidTokens)
+ const proofs = paidTokens.map(token => {
+ return token.promises.map((promise, promiseIndex) => {
+ console.log('### promise', promise)
+
+ const secret = token.secrets[promiseIndex]
+ const r = token.rs[promiseIndex]
+
+ return this.promiseToProof(promise.amount, promise['C_'], secret, r)
+ })
+ })
+ const payload = {
+ proofs: proofs.flat(),
+ amount,
+ invoice: this.payInvoiceData.bolt11
+ }
+ console.log('#### payload', JSON.stringify(payload))
+ try {
+ const {data} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/${this.mintId}/melt`,
+ '',
+ payload
+ )
+ this.$q.notify({
+ timeout: 5000,
+ message: 'Invoice paid'
+ })
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ },
+
+ recheckPendingInvoices: async function () {
+ for (const invoice of this.invoicesCashu) {
+ if (invoice.status === 'pending') {
+ this.recheckInvoice(invoice.hash, false)
}
}
-
- this.$q.notify({
- timeout: 5000,
- message: 'Tokens burned'
- })
- this.storeProofs()
- this.showSendTokens = false
- console.log('### this.proofs', this.proofs)
},
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ // receiveTokens: async function () {
+ // this.showReceiveTokens = false
+ // console.log('### receive tokens', this.receiveData.tokensBase64)
+ // // if (this.receiveData.tokensBase64) {
+ // // const tokensJson = atob(this.receiveData.tokensBase64)
+ // // const proofs = JSON.parse(tokensJson)
+ // // const amount = proofs.reduce((s, t) => (s += t.amount), 0)
+ // // const amounts = splitAmount(amount)
+ // // const newTokens = await this.buildTokens(amounts)
+ // // console.log('newTokens', newTokens)
+
+ // // const payload = {
+ // // amount,
+ // // proofs,
+ // // outputs: {
+ // // blinded_messages: newTokens.blindedMessages
+ // // }
+ // // }
+
+ // // console.log('payload', JSON.stringify(payload))
+ // try {
+ // if (this.receiveData.tokensBase64.length == 0) {
+ // throw new Error('no tokens provided.')
+ // }
+ // const tokensJson = atob(this.receiveData.tokensBase64)
+ // const proofs = JSON.parse(tokensJson)
+ // const amount = proofs.reduce((s, t) => (s += t.amount), 0)
+ // let {fristProofs, scndProofs} = await this.split(proofs, amount)
+ // // const {data} = await LNbits.api.request(
+ // // 'POST',
+ // // `/cashu/api/v1/${this.mintId}/split`,
+ // // '',
+ // // payload
+ // // )
+ // // newTokens.promises = data.snd
+ // // // console.log('split data', JSON.stringify(data.snd))
+ // // // for (let i =0 ;i < newTokens.length; i++) {
+ // // // Object.assign(newTokens[i], promises)
+ // // // }
+ // // console.log('newTokens 2', newTokens)
+ // // this.proofs.push(newTokens)
+ // // this.storeProofs()
+ // } catch (error) {
+ // console.error(error)
+ // LNbits.utils.notifyApiError(error)
+ // }
+ // // }
+ // },
+
+ // buildTokens: async function (amounts, paymentHash) {
+ // const blindedMessages = []
+ // const secrets = []
+ // const rs = []
+ // for (let i = 0; i < amounts.length; i++) {
+ // const secret = nobleSecp256k1.utils.randomBytes(32)
+ // // const secret = nobleSecp256k1.utils.hexToBytes(
+ // // '0000000000000000000000000000000000000000000000000000000000000000'
+ // // )
+ // // todo: base64Url
+ // const encodedSecret = uint8ToBase64.encode(secret)
+ // secrets.push(encodedSecret)
+ // const {B_, r} = await step1Alice(secret)
+ // rs.push(r)
+ // blindedMessages.push({amount: amounts[i], B_: B_})
+ // }
+
+ // const newTokens = {
+ // hash: paymentHash,
+ // blindedMessages,
+ // rs,
+ // secrets,
+ // status: 'pending'
+ // }
+ // return newTokens
+ // },
+
+ // burnTokens: function () {
+ // for (const sentToken of this.sendData.tokens) {
+ // for (const token of this.proofs) {
+ // if (token.status === 'paid') {
+ // const secretIndex = token.secrets.findIndex(
+ // s => s === sentToken.secret
+ // )
+ // console.log('### secretIndex', secretIndex)
+ // if (secretIndex >= 0) {
+ // token.blindedMessages?.splice(secretIndex, 1)
+ // token.promises?.splice(secretIndex, 1)
+ // token.rs?.splice(secretIndex, 1)
+ // token.secrets?.splice(secretIndex, 1)
+ // }
+ // }
+ // }
+ // }
+
+ // this.$q.notify({
+ // timeout: 5000,
+ // message: 'Tokens burned'
+ // })
+ // this.storeProofs()
+ // this.showSendTokens = false
+ // console.log('### this.proofs', this.proofs)
+ // },
+
findTokenForAmount: function (amount) {
for (const token of this.proofs) {
const index = token.promises?.findIndex(p => p.amount === amount)
@@ -1377,46 +1467,47 @@ page_container %}
message: 'Cannot decode invoice',
caption: error + ''
})
+ throw error
}
},
- melt: async function () {
- console.log('#### sell tokens')
- const amount = this.payInvoiceData.invoice.sat
- const paidTokens = this.proofs.filter(t => t.promises?.length)
- console.log('### paidTokens', paidTokens)
- const proofs = paidTokens.map(token => {
- return token.promises.map((promise, promiseIndex) => {
- console.log('### promise', promise)
+ // melt: async function () {
+ // console.log('#### sell tokens')
+ // const amount = this.payInvoiceData.invoice.sat
+ // const paidTokens = this.proofs.filter(t => t.promises?.length)
+ // console.log('### paidTokens', paidTokens)
+ // const proofs = paidTokens.map(token => {
+ // return token.promises.map((promise, promiseIndex) => {
+ // console.log('### promise', promise)
- const secret = token.secrets[promiseIndex]
- const r = token.rs[promiseIndex]
+ // const secret = token.secrets[promiseIndex]
+ // const r = token.rs[promiseIndex]
- return this.promiseToProof(promise.amount, promise['C_'], secret, r)
- })
- })
- const payload = {
- proofs: proofs.flat(),
- amount,
- invoice: this.payInvoiceData.bolt11
- }
- console.log('#### payload', JSON.stringify(payload))
- try {
- const {data} = await LNbits.api.request(
- 'POST',
- `/cashu/api/v1/${this.mintId}/melt`,
- '',
- payload
- )
- this.$q.notify({
- timeout: 5000,
- message: 'Invoice paid'
- })
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- }
- },
+ // return this.promiseToProof(promise.amount, promise['C_'], secret, r)
+ // })
+ // })
+ // const payload = {
+ // proofs: proofs.flat(),
+ // amount,
+ // invoice: this.payInvoiceData.bolt11
+ // }
+ // console.log('#### payload', JSON.stringify(payload))
+ // try {
+ // const {data} = await LNbits.api.request(
+ // 'POST',
+ // `/cashu/api/v1/${this.mintId}/melt`,
+ // '',
+ // payload
+ // )
+ // this.$q.notify({
+ // timeout: 5000,
+ // message: 'Invoice paid'
+ // })
+ // } catch (error) {
+ // console.error(error)
+ // LNbits.utils.notifyApiError(error)
+ // }
+ // },
// C_hex = promise['C_']
// amount = promise.amount
@@ -1445,7 +1536,7 @@ page_container %}
},
watch: {
payments: function () {
- this.fetchBalance()
+ this.balance()
}
},
From 56b405126fb26a0e6364ec542d87c11447b09f12 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 15:40:29 +0100
Subject: [PATCH 332/696] fix manifest
---
.../cashu/templates/cashu/_cashu.html | 5 +--
.../cashu/templates/cashu/wallet.html | 1 +
lnbits/extensions/cashu/views.py | 35 ++++++++++++-------
3 files changed, 25 insertions(+), 16 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/_cashu.html b/lnbits/extensions/cashu/templates/cashu/_cashu.html
index 0c7c4338..952fe7e1 100644
--- a/lnbits/extensions/cashu/templates/cashu/_cashu.html
+++ b/lnbits/extensions/cashu/templates/cashu/_cashu.html
@@ -1,10 +1,7 @@
-
- Make Ecash mints with peg in/out to a wallet, that can create and manage
- ecash.
-
+ Create Cashu ecash mints and wallets.
Created by
arcbtc ,
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 3a9c48db..e4621e11 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -180,6 +180,7 @@ page_container %}
Date: Fri, 4 Nov 2022 16:16:15 +0100
Subject: [PATCH 333/696] trying to get camera to run
---
.../cashu/templates/cashu/wallet.html | 257 ++++++++++++++++--
1 file changed, 238 insertions(+), 19 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index e4621e11..110ecfa2 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -45,7 +45,7 @@ page_container %}
-
+
Receive Tokens
-
-
+
+
-
+
+
+
+
+
+
+
+
+
+ {% raw %} {{ parseFloat(String(parse.invoice.fsat).replaceAll(",",
+ "")) / 100 }} {% endraw %} {{LNBITS_DENOMINATION}} {% raw %}
+
+
+ {{ parse.invoice.fsat }}{% endraw %} {{LNBITS_DENOMINATION}} {%
+ raw %}
+
+
+
+ Description: {{ parse.invoice.description }}
+ Expire date: {{ parse.invoice.expireDate }}
+ Hash: {{ parse.invoice.hash }}
+
+ {% endraw %}
+
+ Pay
+ Cancel
+
+
+ Not enough funds!
+ Cancel
+
+
+
+ {% raw %}
+
+
+ Authenticate with {{ parse.lnurlauth.domain }} ?
+
+
+
+ For every website and for every LNbits wallet, a new keypair
+ will be deterministically generated so your identity can't be
+ tied to your LNbits wallet or linked across websites. No other
+ data will be shared with {{ parse.lnurlauth.domain }}.
+
+ Your public key for {{ parse.lnurlauth.domain }} is:
+
+ {{ parse.lnurlauth.pubkey }}
+
+
+ Login
+ Cancel
+
+
+ {% endraw %}
+
+
+ {% raw %}
+
+
+ {{ parse.lnurlpay.domain }} is requesting {{
+ parse.lnurlpay.maxSendable | msatoshiFormat }}
+ {{LNBITS_DENOMINATION}}
+
+
+ and a {{parse.lnurlpay.commentAllowed}}-char comment
+
+
+
+ {{ parse.lnurlpay.targetUser || parse.lnurlpay.domain }}
+ is requesting
+ between
+ {{ parse.lnurlpay.minSendable | msatoshiFormat }} and
+ {{ parse.lnurlpay.maxSendable | msatoshiFormat }}
+ {% endraw %} {{LNBITS_DENOMINATION}} {% raw %}
+
+
+ and a {{parse.lnurlpay.commentAllowed}}-char comment
+
+
+
+
+
+ {{ parse.lnurlpay.description }}
+
+
+
+
+
+
+
+ {% endraw %}
+
+ {% raw %}
+
+
+
+
+
+
+ Send {{LNBITS_DENOMINATION}}
+ Cancel
+
+
+ {% endraw %}
+
+
+
+
+
+
+ Read
+ Cancel
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Cancel
+
+
+
+
Warning
@@ -493,7 +693,11 @@ page_container %}
invoiceCheckListener: () => {},
payInvoiceData: {
invoice: '',
- bolt11: ''
+ bolt11: '',
+ camera: {
+ show: false,
+ camera: 'auto'
+ }
},
sendData: {
amount: 0,
@@ -923,6 +1127,7 @@ page_container %}
this.payInvoiceData.invoice = ''
this.payInvoiceData.bolt11 = ''
this.showPayInvoice = true
+ this.payInvoiceData.camera.show = false
},
showSendTokensDialog: function () {
@@ -1131,6 +1336,10 @@ page_container %}
this.proofs[i].reserved = true
}
}
+
+ // delete tokens from db
+ this.proofs = fristProofs
+ // add new fristProofs, scndProofs to this.proofs
this.storeProofs()
return {fristProofs, scndProofs}
} catch (error) {
@@ -1250,28 +1459,37 @@ page_container %}
this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
// delete tokens from db
- this.proofs = fristProofs
+ // this.proofs = fristProofs
// add new fristProofs, scndProofs to this.proofs
- this.storeProofs()
+ // this.storeProofs()
},
melt: async function () {
- console.log('#### sell tokens')
+ console.log('#### pay lightning')
const amount = this.payInvoiceData.invoice.sat
- const paidTokens = this.proofs.filter(t => t.promises?.length)
- console.log('### paidTokens', paidTokens)
- const proofs = paidTokens.map(token => {
- return token.promises.map((promise, promiseIndex) => {
- console.log('### promise', promise)
+ // if (amount > balance()) {
+ // LNbits.utils.notifyApiError('Balance too low')
+ // return
+ // }
+ let {fristProofs, scndProofs} = await this.splitToSend(
+ this.proofs,
+ amount
+ )
- const secret = token.secrets[promiseIndex]
- const r = token.rs[promiseIndex]
+ // const paidTokens = this.proofs.filter(t => t.promises?.length)
+ // console.log('### paidTokens', paidTokens)
+ // const proofs = paidTokens.map(token => {
+ // return token.promises.map((promise, promiseIndex) => {
+ // console.log('### promise', promise)
- return this.promiseToProof(promise.amount, promise['C_'], secret, r)
- })
- })
+ // const secret = token.secrets[promiseIndex]
+ // const r = token.rs[promiseIndex]
+
+ // return this.promiseToProof(promise.amount, promise['C_'], secret, r)
+ // })
+ // })
const payload = {
- proofs: proofs.flat(),
+ proofs: scndProofs.flat(),
amount,
invoice: this.payInvoiceData.bolt11
}
@@ -1285,6 +1503,7 @@ page_container %}
)
this.$q.notify({
timeout: 5000,
+ type: 'positive',
message: 'Invoice paid'
})
} catch (error) {
From c79cafb38ef7779a7f9bd4016569eada02460024 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 16:44:17 +0100
Subject: [PATCH 334/696] works
---
.../cashu/templates/cashu/wallet.html | 189 +++++++++++-------
1 file changed, 117 insertions(+), 72 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 110ecfa2..56104d50 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -202,22 +202,25 @@ page_container %}
-
+
-
+
- {% raw %} {{ parseFloat(String(parse.invoice.fsat).replaceAll(",",
+ {% raw %} {{
+ parseFloat(String(payInvoiceData.invoice.fsat).replaceAll(",",
"")) / 100 }} {% endraw %} {{LNBITS_DENOMINATION}} {% raw %}
- {{ parse.invoice.fsat }}{% endraw %} {{LNBITS_DENOMINATION}} {%
- raw %}
+ {{ payInvoiceData.invoice.fsat }}{% endraw %}
+ {{LNBITS_DENOMINATION}} {% raw %}
- Description: {{ parse.invoice.description }}
- Expire date: {{ parse.invoice.expireDate }}
- Hash: {{ parse.invoice.hash }}
+ Description: {{
+ payInvoiceData.invoice.description }}
+ Expire date: {{ payInvoiceData.invoice.expireDate
+ }}
+ Hash: {{ payInvoiceData.invoice.hash }}
{% endraw %}
@@ -235,22 +238,27 @@ page_container %}
>
-
+
{% raw %}
- Authenticate with {{ parse.lnurlauth.domain }} ?
+ Authenticate with {{ payInvoiceData.lnurlauth.domain }} ?
For every website and for every LNbits wallet, a new keypair
will be deterministically generated so your identity can't be
tied to your LNbits wallet or linked across websites. No other
- data will be shared with {{ parse.lnurlauth.domain }}.
+ data will be shared with {{ payInvoiceData.lnurlauth.domain }}.
+
+
+ Your public key for
+ {{ payInvoiceData.lnurlauth.domain }} is:
- Your public key for {{ parse.lnurlauth.domain }} is:
- {{ parse.lnurlauth.pubkey }}
+
+ {{ payInvoiceData.lnurlauth.pubkey }}
+
Login
@@ -261,37 +269,45 @@ page_container %}
{% endraw %}
-
+
{% raw %}
-
- {{ parse.lnurlpay.domain }} is requesting {{
- parse.lnurlpay.maxSendable | msatoshiFormat }}
+
+ {{ payInvoiceData.lnurlpay.domain }} is requesting {{
+ payInvoiceData.lnurlpay.maxSendable | msatoshiFormat }}
{{LNBITS_DENOMINATION}}
-
+
- and a {{parse.lnurlpay.commentAllowed}}-char comment
+ and a {{payInvoiceData.lnurlpay.commentAllowed}}-char comment
- {{ parse.lnurlpay.targetUser || parse.lnurlpay.domain }}
+ {{ payInvoiceData.lnurlpay.targetUser ||
+ payInvoiceData.lnurlpay.domain }}
is requesting
between
- {{ parse.lnurlpay.minSendable | msatoshiFormat }} and
- {{ parse.lnurlpay.maxSendable | msatoshiFormat }}
+ {{ payInvoiceData.lnurlpay.minSendable | msatoshiFormat }}
+ and
+ {{ payInvoiceData.lnurlpay.maxSendable | msatoshiFormat }}
{% endraw %} {{LNBITS_DENOMINATION}} {% raw %}
-
+
- and a {{parse.lnurlpay.commentAllowed}}-char comment
+ and a {{payInvoiceData.lnurlpay.commentAllowed}}-char comment
- {{ parse.lnurlpay.description }}
+ {{ payInvoiceData.lnurlpay.description }}
-
-
+
+
@@ -300,26 +316,26 @@ page_container %}
{% raw %}
@@ -336,14 +352,15 @@ page_container %}
@@ -352,7 +369,7 @@ page_container %}
Read
@@ -378,7 +395,7 @@ page_container %}
-
+
{},
payInvoiceData: {
- invoice: '',
+ // invoice: '',
bolt11: '',
+ // camera: {
+ // show: false,
+ // camera: 'auto'
+ // }
+ show: false,
+ invoice: null,
+ lnurlpay: null,
+ lnurlauth: null,
+ data: {
+ request: '',
+ amount: 0,
+ comment: ''
+ },
+ paymentChecker: null,
camera: {
show: false,
camera: 'auto'
@@ -846,8 +878,8 @@ page_container %}
},
canPay: function () {
- if (!this.parse.invoice) return false
- return this.parse.invoice.sat <= this.balance
+ if (!this.payInvoiceData.invoice) return false
+ return this.payInvoiceData.invoice.sat <= this.balance
},
pendingPaymentsExist: function () {
return this.payments.findIndex(payment => payment.pending) !== -1
@@ -884,10 +916,10 @@ page_container %}
return row.payment_hash + row.amount
},
closeCamera: function () {
- this.parse.camera.show = false
+ this.payInvoiceData.camera.show = false
},
showCamera: function () {
- this.parse.camera.show = true
+ this.payInvoiceData.camera.show = true
},
showChart: function () {
this.paymentsChart.show = true
@@ -912,14 +944,15 @@ page_container %}
this.focusInput('setAmount')
},
showParseDialog: function () {
- this.parse.show = true
- this.parse.invoice = null
- this.parse.lnurlpay = null
- this.parse.lnurlauth = null
- this.parse.data.request = ''
- this.parse.data.comment = ''
- this.parse.data.paymentChecker = null
- this.parse.camera.show = false
+ this.payInvoiceData.show = true
+ this.payInvoiceData.invoice = null
+ this.payInvoiceData.lnurlpay = null
+ this.payInvoiceData.lnurlauth = null
+ this.payInvoiceData.data.request = ''
+ this.payInvoiceData.data.comment = ''
+ this.payInvoiceData.data.paymentChecker = null
+ this.payInvoiceData.camera.show = false
+ this.focusInput('pasteInput')
},
closeReceiveDialog: function () {
@@ -929,7 +962,7 @@ page_container %}
},
closeParseDialog: function () {
setTimeout(() => {
- clearInterval(this.parse.paymentChecker)
+ clearInterval(this.payInvoiceData.paymentChecker)
}, 10000)
},
onPaymentReceived: function (paymentHash) {
@@ -994,33 +1027,43 @@ page_container %}
})
},
decodeQR: function (res) {
- this.parse.data.request = res
+ this.payInvoiceData.data.request = res
this.decodeRequest()
- this.parse.camera.show = false
+ this.payInvoiceData.camera.show = false
},
decodeRequest: function () {
- this.parse.show = true
- let req = this.parse.data.request.toLowerCase()
- if (this.parse.data.request.toLowerCase().startsWith('lightning:')) {
- this.parse.data.request = this.parse.data.request.slice(10)
- } else if (this.parse.data.request.toLowerCase().startsWith('lnurl:')) {
- this.parse.data.request = this.parse.data.request.slice(6)
+ this.payInvoiceData.show = true
+ let req = this.payInvoiceData.data.request.toLowerCase()
+ if (
+ this.payInvoiceData.data.request
+ .toLowerCase()
+ .startsWith('lightning:')
+ ) {
+ this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice(
+ 10
+ )
+ } else if (
+ this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl:')
+ ) {
+ this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice(
+ 6
+ )
} else if (req.indexOf('lightning=lnurl1') !== -1) {
- this.parse.data.request = this.parse.data.request
+ this.payInvoiceData.data.request = this.payInvoiceData.data.request
.split('lightning=')[1]
.split('&')[0]
}
if (
- this.parse.data.request.toLowerCase().startsWith('lnurl1') ||
- this.parse.data.request.match(/[\w.+-~_]+@[\w.+-~_]/)
+ this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl1') ||
+ this.payInvoiceData.data.request.match(/[\w.+-~_]+@[\w.+-~_]/)
) {
return
}
let invoice
try {
- invoice = decode(this.parse.data.request)
+ invoice = decode(this.payInvoiceData.data.request)
} catch (error) {
this.$q.notify({
timeout: 3000,
@@ -1028,7 +1071,7 @@ page_container %}
message: error + '.',
caption: '400 BAD REQUEST'
})
- this.parse.show = false
+ this.payInvoiceData.show = false
throw error
return
}
@@ -1060,7 +1103,7 @@ page_container %}
}
})
- this.parse.invoice = Object.freeze(cleanInvoice)
+ this.payInvoiceData.invoice = Object.freeze(cleanInvoice)
},
payInvoice: function () {
let dismissPaymentMsg = this.$q.notify({
@@ -1125,7 +1168,7 @@ page_container %}
showPayInvoiceDialog: function () {
console.log('### showPayInvoiceDialog')
this.payInvoiceData.invoice = ''
- this.payInvoiceData.bolt11 = ''
+ this.payInvoiceData.data.request = ''
this.showPayInvoice = true
this.payInvoiceData.camera.show = false
},
@@ -1491,7 +1534,7 @@ page_container %}
const payload = {
proofs: scndProofs.flat(),
amount,
- invoice: this.payInvoiceData.bolt11
+ invoice: this.payInvoiceData.data.request
}
console.log('#### payload', JSON.stringify(payload))
try {
@@ -1506,6 +1549,8 @@ page_container %}
type: 'positive',
message: 'Invoice paid'
})
+ this.payInvoiceData.invoice = null
+ this.payInvoiceData.show = false
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
@@ -1645,7 +1690,7 @@ page_container %}
checkInvoice: function () {
console.log('#### checkInvoice')
try {
- const invoice = decode(this.payInvoiceData.bolt11)
+ const invoice = decode(this.payInvoiceData.data.request)
const cleanInvoice = {
msat: invoice.human_readable_part.amount,
@@ -1709,7 +1754,7 @@ page_container %}
// const payload = {
// proofs: proofs.flat(),
// amount,
- // invoice: this.payInvoiceData.bolt11
+ // invoice: this.payInvoiceData.data.request
// }
// console.log('#### payload', JSON.stringify(payload))
// try {
From 75fd846d1645a3a9679c327e5af60cd62964a82d Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 17:17:11 +0100
Subject: [PATCH 335/696] works
---
.../cashu/templates/cashu/wallet.html | 177 +-----------------
lnbits/extensions/cashu/views.py | 6 +-
2 files changed, 9 insertions(+), 174 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 56104d50..efd3ded3 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1199,7 +1199,11 @@ page_container %}
clearInterval(this.invoiceCheckListener)
this.invoiceData.bolt11 = ''
this.showInvoiceDetails = false
- this.fetchBalance()
+ this.$q.notify({
+ timeout: 5000,
+ type: 'positive',
+ message: 'Payment received'
+ })
} catch (error) {
console.log('not paid yet')
}
@@ -1294,14 +1298,6 @@ page_container %}
}
},
- // requestTokens: async function (amounts, paymentHash) {
- // const newTokens = await this.buildTokens(amounts, paymentHash)
- // // this.proofs.push(newTokens)
- // // this.storeProofs()
- // // console.log('### this.proofs', this.proofs)
- // // await this.mint(paymentHash, newTokens.newTokens)
- // },
-
generateSecrets: async function (amounts) {
const secrets = []
for (let i = 0; i < amounts.length; i++) {
@@ -1500,14 +1496,10 @@ page_container %}
this.sendData.tokens = scndProofs
console.log('### this.sendData.tokens', this.sendData.tokens)
this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
-
- // delete tokens from db
- // this.proofs = fristProofs
- // add new fristProofs, scndProofs to this.proofs
- // this.storeProofs()
},
melt: async function () {
+ // todo: get fees from server and add to inputs
console.log('#### pay lightning')
const amount = this.payInvoiceData.invoice.sat
// if (amount > balance()) {
@@ -1518,19 +1510,6 @@ page_container %}
this.proofs,
amount
)
-
- // const paidTokens = this.proofs.filter(t => t.promises?.length)
- // console.log('### paidTokens', paidTokens)
- // const proofs = paidTokens.map(token => {
- // return token.promises.map((promise, promiseIndex) => {
- // console.log('### promise', promise)
-
- // const secret = token.secrets[promiseIndex]
- // const r = token.rs[promiseIndex]
-
- // return this.promiseToProof(promise.amount, promise['C_'], secret, r)
- // })
- // })
const payload = {
proofs: scndProofs.flat(),
amount,
@@ -1571,109 +1550,6 @@ page_container %}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- // receiveTokens: async function () {
- // this.showReceiveTokens = false
- // console.log('### receive tokens', this.receiveData.tokensBase64)
- // // if (this.receiveData.tokensBase64) {
- // // const tokensJson = atob(this.receiveData.tokensBase64)
- // // const proofs = JSON.parse(tokensJson)
- // // const amount = proofs.reduce((s, t) => (s += t.amount), 0)
- // // const amounts = splitAmount(amount)
- // // const newTokens = await this.buildTokens(amounts)
- // // console.log('newTokens', newTokens)
-
- // // const payload = {
- // // amount,
- // // proofs,
- // // outputs: {
- // // blinded_messages: newTokens.blindedMessages
- // // }
- // // }
-
- // // console.log('payload', JSON.stringify(payload))
- // try {
- // if (this.receiveData.tokensBase64.length == 0) {
- // throw new Error('no tokens provided.')
- // }
- // const tokensJson = atob(this.receiveData.tokensBase64)
- // const proofs = JSON.parse(tokensJson)
- // const amount = proofs.reduce((s, t) => (s += t.amount), 0)
- // let {fristProofs, scndProofs} = await this.split(proofs, amount)
- // // const {data} = await LNbits.api.request(
- // // 'POST',
- // // `/cashu/api/v1/${this.mintId}/split`,
- // // '',
- // // payload
- // // )
- // // newTokens.promises = data.snd
- // // // console.log('split data', JSON.stringify(data.snd))
- // // // for (let i =0 ;i < newTokens.length; i++) {
- // // // Object.assign(newTokens[i], promises)
- // // // }
- // // console.log('newTokens 2', newTokens)
- // // this.proofs.push(newTokens)
- // // this.storeProofs()
- // } catch (error) {
- // console.error(error)
- // LNbits.utils.notifyApiError(error)
- // }
- // // }
- // },
-
- // buildTokens: async function (amounts, paymentHash) {
- // const blindedMessages = []
- // const secrets = []
- // const rs = []
- // for (let i = 0; i < amounts.length; i++) {
- // const secret = nobleSecp256k1.utils.randomBytes(32)
- // // const secret = nobleSecp256k1.utils.hexToBytes(
- // // '0000000000000000000000000000000000000000000000000000000000000000'
- // // )
- // // todo: base64Url
- // const encodedSecret = uint8ToBase64.encode(secret)
- // secrets.push(encodedSecret)
- // const {B_, r} = await step1Alice(secret)
- // rs.push(r)
- // blindedMessages.push({amount: amounts[i], B_: B_})
- // }
-
- // const newTokens = {
- // hash: paymentHash,
- // blindedMessages,
- // rs,
- // secrets,
- // status: 'pending'
- // }
- // return newTokens
- // },
-
- // burnTokens: function () {
- // for (const sentToken of this.sendData.tokens) {
- // for (const token of this.proofs) {
- // if (token.status === 'paid') {
- // const secretIndex = token.secrets.findIndex(
- // s => s === sentToken.secret
- // )
- // console.log('### secretIndex', secretIndex)
- // if (secretIndex >= 0) {
- // token.blindedMessages?.splice(secretIndex, 1)
- // token.promises?.splice(secretIndex, 1)
- // token.rs?.splice(secretIndex, 1)
- // token.secrets?.splice(secretIndex, 1)
- // }
- // }
- // }
- // }
-
- // this.$q.notify({
- // timeout: 5000,
- // message: 'Tokens burned'
- // })
- // this.storeProofs()
- // this.showSendTokens = false
- // console.log('### this.proofs', this.proofs)
- // },
-
findTokenForAmount: function (amount) {
for (const token of this.proofs) {
const index = token.promises?.findIndex(p => p.amount === amount)
@@ -1736,47 +1612,6 @@ page_container %}
}
},
- // melt: async function () {
- // console.log('#### sell tokens')
- // const amount = this.payInvoiceData.invoice.sat
- // const paidTokens = this.proofs.filter(t => t.promises?.length)
- // console.log('### paidTokens', paidTokens)
- // const proofs = paidTokens.map(token => {
- // return token.promises.map((promise, promiseIndex) => {
- // console.log('### promise', promise)
-
- // const secret = token.secrets[promiseIndex]
- // const r = token.rs[promiseIndex]
-
- // return this.promiseToProof(promise.amount, promise['C_'], secret, r)
- // })
- // })
- // const payload = {
- // proofs: proofs.flat(),
- // amount,
- // invoice: this.payInvoiceData.data.request
- // }
- // console.log('#### payload', JSON.stringify(payload))
- // try {
- // const {data} = await LNbits.api.request(
- // 'POST',
- // `/cashu/api/v1/${this.mintId}/melt`,
- // '',
- // payload
- // )
- // this.$q.notify({
- // timeout: 5000,
- // message: 'Invoice paid'
- // })
- // } catch (error) {
- // console.error(error)
- // LNbits.utils.notifyApiError(error)
- // }
- // },
-
- // C_hex = promise['C_']
- // amount = promise.amount
-
fetchMintKeys: async function () {
const {data} = await LNbits.api.request(
'GET',
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 17e39cbe..b254d615 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -56,8 +56,8 @@ async def manifest(cashu_id: str):
)
return {
- "short_name": "Cashu wallet",
- "name": cashu.name + " - " + "Cashu wallet",
+ "short_name": "Cashu",
+ "name": cashu.name + " - " + "Cashu",
"icons": [
{
"src": LNBITS_CUSTOM_LOGO
@@ -71,7 +71,7 @@ async def manifest(cashu_id: str):
"background_color": "#1F2234",
"description": "Cashu ecash wallet",
"display": "standalone",
- "scope": "/cashu/wallet?mint_id=" + cashu_id,
+ "scope": "/cashu/",
"theme_color": "#1F2234",
"shortcuts": [
{
From ce27136757e22bfd2b40ca7f7b30641158c2fb72 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 17:17:57 +0100
Subject: [PATCH 336/696] make format
---
lnbits/extensions/cashu/static/js/dhke.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/static/js/dhke.js b/lnbits/extensions/cashu/static/js/dhke.js
index 9c7aee1b..41c2fb46 100644
--- a/lnbits/extensions/cashu/static/js/dhke.js
+++ b/lnbits/extensions/cashu/static/js/dhke.js
@@ -22,7 +22,7 @@ async function hashToCurve(secretMessage) {
async function step1Alice(secretMessage) {
// todo: document & validate `secretMessage` format
secretMessage = uint8ToBase64.encode(secretMessage)
- secretMessage = new TextEncoder().encode(secretMessage);
+ secretMessage = new TextEncoder().encode(secretMessage)
const Y = await hashToCurve(secretMessage)
const rpk = nobleSecp256k1.utils.randomPrivateKey()
const r = bytesToNumber(rpk)
From f140b19e56fff05fa1ab4e73691cc03e7328b818 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 4 Nov 2022 18:46:26 +0200
Subject: [PATCH 337/696] fix: ugly method to refresh UI
---
.../cashu/templates/cashu/wallet.html | 29 +++++++++++++++----
1 file changed, 24 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index efd3ded3..e771cf9c 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -22,7 +22,7 @@ page_container %}
- {% raw %} {{balance}}
+ {% raw %} {{getBalance()}}
{{tickershort}}{% endraw %}
@@ -81,7 +81,7 @@ page_container %}
t)
+ .flat()
+ .reduce((sum, el) => (sum += el.amount), 0)
+ },
+ getTokenList: function () {
+ const x = this.proofs
+ .map(t => t.amount)
+ .reduce((acc, amount) => {
+ acc[amount] = acc[amount] + amount || 1
+ return acc
+ }, {})
+ return Object.keys(x).map(k => ({
+ value: k,
+ count: x[k],
+ sum: k * x[k]
+ }))
+ },
+
paymentTableRowKey: function (row) {
return row.payment_hash + row.amount
},
@@ -1269,7 +1289,7 @@ page_container %}
if (!proofs.length) {
throw 'could not mint'
}
- this.proofs.push(...proofs)
+ this.proofs = this.proofs.concat(proofs)
this.storeProofs()
await this.setInvoicePaid(payment_hash)
return proofs
@@ -1398,8 +1418,7 @@ page_container %}
const usedSecrets = proofs.map(p => p.secret)
this.proofs = this.proofs.filter(p => !usedSecrets.includes(p.secret))
// add new fristProofs, scndProofs to this.proofs
- this.proofs.push(...fristProofs)
- this.proofs.push(...scndProofs)
+ this.proofs = this.proofs.concat(fristProofs).concat(scndProofs)
this.storeProofs()
return {fristProofs, scndProofs}
} catch (error) {
From 01f83dce3fa34a1ca00c630e5a20dc6c20070edd Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 19:43:23 +0100
Subject: [PATCH 338/696] cashu update
---
pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index 74a76acc..dc7215d5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,7 +63,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
-cashu = {path = "../cashu"}
+cashu = "^0.5.1"
[tool.poetry.dev-dependencies]
From 0ebd56c18b23c47ac5825fd9d61bfdd2bf2c493e Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 20:44:35 +0100
Subject: [PATCH 339/696] new icons
---
lnbits/extensions/cashu/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index b254d615..a2dd9347 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -62,7 +62,7 @@ async def manifest(cashu_id: str):
{
"src": LNBITS_CUSTOM_LOGO
if LNBITS_CUSTOM_LOGO
- else "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
+ else "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
"type": "image/png",
"sizes": "512x512",
}
From d9003f6d7c6b0f36172b15423b359a6a29fcf2ad Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 20:45:12 +0100
Subject: [PATCH 340/696] new icons
---
lnbits/extensions/cashu/views.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index a2dd9347..88c09f47 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -64,7 +64,7 @@ async def manifest(cashu_id: str):
if LNBITS_CUSTOM_LOGO
else "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
"type": "image/png",
- "sizes": "512x512",
+ "sizes": "96x96",
}
],
"start_url": "/cashu/wallet?mint_id=" + cashu_id,
From 6526c6d6f27a0cf09e48100d588fa728ed179e75 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 20:45:56 +0100
Subject: [PATCH 341/696] manifest
---
lnbits/extensions/cashu/views.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 88c09f47..6aec5cda 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -60,9 +60,7 @@ async def manifest(cashu_id: str):
"name": cashu.name + " - " + "Cashu",
"icons": [
{
- "src": LNBITS_CUSTOM_LOGO
- if LNBITS_CUSTOM_LOGO
- else "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
"type": "image/png",
"sizes": "96x96",
}
From db61ad8b32337267271a2c4b5752caa77a319603 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 4 Nov 2022 20:48:32 +0100
Subject: [PATCH 342/696] manifest
---
lnbits/extensions/cashu/views.py | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 6aec5cda..ca05d83e 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -59,11 +59,16 @@ async def manifest(cashu_id: str):
"short_name": "Cashu",
"name": cashu.name + " - " + "Cashu",
"icons": [
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
+ "type": "image/png",
+ "sizes": "512x512",
+ },
{
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
"type": "image/png",
"sizes": "96x96",
- }
+ },
],
"start_url": "/cashu/wallet?mint_id=" + cashu_id,
"background_color": "#1F2234",
@@ -77,6 +82,16 @@ async def manifest(cashu_id: str):
"short_name": cashu.name,
"description": cashu.name + " - " + "Cashu wallet",
"url": "/cashu/wallet?mint_id=" + cashu_id,
+ "icons": [
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
+ "sizes": "96x96",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
+ "sizes": "512x512",
+ },
+ ],
}
],
}
From 3ec143ee2e9819d3c72df18bf6f051ec1fd62727 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 05:38:29 +0100
Subject: [PATCH 343/696] better buttons
---
lnbits/extensions/cashu/config.json | 6 +-
lnbits/extensions/cashu/config.json.example | 6 +-
.../cashu/templates/cashu/index.html | 6 +-
.../cashu/templates/cashu/wallet.html | 123 ++++++++++++------
4 files changed, 93 insertions(+), 48 deletions(-)
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
index 99838eb4..af202d43 100644
--- a/lnbits/extensions/cashu/config.json
+++ b/lnbits/extensions/cashu/config.json
@@ -1,7 +1,7 @@
{
- "name": "Cashu Ecash",
+ "name": "Cashu",
"short_description": "Ecash mint and wallet",
- "icon": "approval",
- "contributors": ["arcbtc", "calle", "vlad"],
+ "icon": "account_balance",
+ "contributors": ["calle", "vlad", "arcbtc"],
"hidden": false
}
diff --git a/lnbits/extensions/cashu/config.json.example b/lnbits/extensions/cashu/config.json.example
index e798e2ef..0cb4043d 100644
--- a/lnbits/extensions/cashu/config.json.example
+++ b/lnbits/extensions/cashu/config.json.example
@@ -1,7 +1,7 @@
{
- "name": "Cashu Ecash",
+ "name": "Cashu",
"short_description": "Ecash mints with LN peg in/out",
- "icon": "approval",
- "contributors": ["arcbtc", "calle"],
+ "icon": "account_balance",
+ "contributors": ["calle", "vlad", "arcbtc"],
"hidden": true
}
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index 20ed567c..f54b39b1 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -46,12 +46,12 @@
unelevated
dense
size="xs"
- icon="launch"
+ icon="account_balance_wallet"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
- :href="'wallet/?tsh=' + (props.row.tickershort || '') + '&mint_id=' + props.row.id + '&mint_name=' + props.row.name"
+ :href="'wallet/?' + 'mint_id=' + props.row.id"
target="_blank"
- >Shareable wallet page Shareable wallet
Create invoice
+ >Receive invoice
@@ -30,8 +31,9 @@ page_container %}
-
+
+
+
+
-
+ > -->
-
+
-
+
@@ -371,8 +376,9 @@ page_container %}
color="primary"
:disable="payInvoiceData.data.request == ''"
type="submit"
- >ReadContinue
+
Cancel
@@ -595,7 +601,7 @@ page_container %}
-
+
@@ -885,20 +891,6 @@ page_container %}
return this.payments.findIndex(payment => payment.pending) !== -1
},
- tokenList: function () {
- const x = this.proofs
- .map(t => t.amount)
- .reduce((acc, amount) => {
- acc[amount] = acc[amount] + amount || 1
- return acc
- }, {})
- return Object.keys(x).map(k => ({
- value: k,
- count: x[k],
- sum: k * x[k]
- }))
- },
-
balance: function () {
return this.proofs
.map(t => t)
@@ -931,7 +923,7 @@ page_container %}
sum: k * x[k]
}))
},
-
+
paymentTableRowKey: function (row) {
return row.payment_hash + row.amount
},
@@ -1376,7 +1368,7 @@ page_container %}
sumProofs: function (proofs) {
return proofs.reduce((s, t) => (s += t.amount), 0)
},
- splitToSend: async function (proofs, amount) {
+ splitToSend: async function (proofs, amount, invlalidate = false) {
try {
const spendableProofs = proofs.filter(p => !p.reserved)
if (this.sumProofs(spendableProofs) < amount) {
@@ -1395,11 +1387,13 @@ page_container %}
this.proofs[i].reserved = true
}
}
+ if (invlalidate) {
+ // delete tokens from db
+ this.proofs = fristProofs
+ // add new fristProofs, scndProofs to this.proofs
+ this.storeProofs()
+ }
- // delete tokens from db
- this.proofs = fristProofs
- // add new fristProofs, scndProofs to this.proofs
- this.storeProofs()
return {fristProofs, scndProofs}
} catch (error) {
console.error(error)
@@ -1508,7 +1502,8 @@ page_container %}
// keep firstProofs, send scndProofs
let {fristProofs, scndProofs} = await this.splitToSend(
this.proofs,
- this.sendData.amount
+ this.sendData.amount,
+ true
)
this.sendData.tokens = ''
this.sendData.tokensBase64 = ''
@@ -1516,11 +1511,39 @@ page_container %}
console.log('### this.sendData.tokens', this.sendData.tokens)
this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
},
-
+ checkFees: async function (payment_request) {
+ const payload = {
+ pr: payment_request
+ }
+ console.log('#### payload', JSON.stringify(payload))
+ try {
+ const {data} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/${this.mintId}/checkfees`,
+ '',
+ payload
+ )
+ console.log('#### checkFees', payment_request, data.fee)
+ return data.fee
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ },
melt: async function () {
// todo: get fees from server and add to inputs
console.log('#### pay lightning')
- const amount = this.payInvoiceData.invoice.sat
+ const amount_invoice = this.payInvoiceData.invoice.sat
+ const amount =
+ amount_invoice +
+ (await this.checkFees(this.payInvoiceData.data.request))
+ console.log(
+ '#### amount invoice',
+ amount_invoice,
+ 'amount with fees',
+ amount
+ )
// if (amount > balance()) {
// LNbits.utils.notifyApiError('Balance too low')
// return
@@ -1547,8 +1570,30 @@ page_container %}
type: 'positive',
message: 'Invoice paid'
})
- this.payInvoiceData.invoice = null
+ // delete tokens from db
+ this.proofs = fristProofs
+ // add new fristProofs, scndProofs to this.proofs
+ this.storeProofs()
+ console.log({
+ amount: -amount,
+ bolt11: this.payInvoiceData.data.request,
+ hash: this.payInvoiceData.data.hash,
+ memo: this.payInvoiceData.data.memo,
+ })
+ this.invoicesCashu.push({
+ amount: -amount,
+ bolt11: this.payInvoiceData.data.request,
+ hash: this.payInvoiceData.data.hash,
+ memo: this.payInvoiceData.data.memo,
+ date: currentDateStr(),
+ status: 'paid'
+ })
+ this.storeinvoicesCashu()
+ this.tab = 'invoices'
+
+ this.payInvoiceData.invoice = false
this.payInvoiceData.show = false
+
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
@@ -1558,7 +1603,7 @@ page_container %}
recheckPendingInvoices: async function () {
for (const invoice of this.invoicesCashu) {
- if (invoice.status === 'pending') {
+ if (invoice.status === 'pending' && invoice.sat > 0) {
this.recheckInvoice(invoice.hash, false)
}
}
From cc027d4815a00113d6648f66e10b90e8f3479e47 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 05:41:31 +0100
Subject: [PATCH 344/696] title
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 0debaeb9..b973d4b0 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1,5 +1,4 @@
-{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Cashu
-wallet {% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
+{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Cashu {% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
page_container %}
From a9fdaf27d68976dd8631b15f222d8533e5067b8f Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 05:56:40 +0100
Subject: [PATCH 345/696] buttons outline
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index b973d4b0..3e69260d 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -54,6 +54,7 @@ page_container %}
color="primary"
class="full-width"
@click="showReceiveTokensDialog"
+ outline
>Receive Tokens
@@ -66,6 +67,7 @@ page_container %}
color="primary"
class="full-width"
@click="showSendTokensDialog"
+ outline
>
Send Tokens
From 4a5c2bb711bb3e6dfcc26ed5a0b147c82fa4b3a5 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 05:59:29 +0100
Subject: [PATCH 346/696] outline
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 3e69260d..43040342 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -377,6 +377,7 @@ page_container %}
color="primary"
:disable="payInvoiceData.data.request == ''"
type="submit"
+ outline
>Continue
@@ -550,7 +551,6 @@ page_container %}
v-if="!sendData.tokens"
:disable="sendData.amount == null || sendData.amount <= 0"
@click="sendTokens"
- outline
color="primary"
type="submit"
>Send Tokens
Date: Sat, 5 Nov 2022 06:36:32 +0100
Subject: [PATCH 347/696] disclaimer
---
.../cashu/templates/cashu/wallet.html | 95 +++++--------------
1 file changed, 26 insertions(+), 69 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 43040342..eb42a06b 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -181,8 +181,18 @@ page_container %}
+
+
+
-
+
Warning
- BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots
- and "Save to homescreen"/"Install app" !
+ Bookmark this page!
- Ecash is a bearer asset, meaning you have the funds saved on this
- page, losing the page without exporting the page will mean you will
- lose the funds.
+ Ecash is a bearer asset, meaning losing access to this wallet will mean you will
+ lose the funds. This wallet stores tokens in its database. If you lose the link or request
+ your data, you will lose your tokens. Bookmark this page or press the
+ hamburger icon and add this page to your home screen.
+
+
+ This service is in BETA, and we hold no responsibility for people losing
+ access to funds. Use at your own risk!
- Copy wallet URL
- Copy wallet URL
+ I understand
@@ -489,14 +501,6 @@ page_container %}
color="primary"
>Copy invoice
-
Create Invoice
@@ -578,7 +582,7 @@ page_container %}
- Paste tokens please
+ Paste Cashu tokens
-
@@ -967,6 +921,9 @@ page_container %}
this.payInvoiceData.camera.show = false
this.focusInput('pasteInput')
},
+ showDisclaimerDialog: function() {
+ this.disclaimerDialog.show = true
+ },
closeReceiveDialog: function () {
setTimeout(() => {
From 39d263c19dbb2b849b8dc36ab627773b59e9010c Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 06:39:13 +0100
Subject: [PATCH 348/696] warning
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index eb42a06b..c38d0165 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -182,14 +182,14 @@ page_container %}
-
+ @click="showDisclaimerDialog"> Warning
From 4c72d2fe19ca6c82a896da7c0cc9fdb9b3ddeb9b Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 06:40:12 +0100
Subject: [PATCH 349/696] warning
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 1 -
1 file changed, 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index c38d0165..963ac7c4 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -183,7 +183,6 @@ page_container %}
Date: Sat, 5 Nov 2022 06:42:44 +0100
Subject: [PATCH 350/696] rows
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 963ac7c4..8cffd248 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -187,7 +187,6 @@ page_container %}
rectangle
color="warning"
outline
- class="full-width"
@click="showDisclaimerDialog"> Warning
@@ -778,7 +777,7 @@ page_container %}
pagination: {
sortBy: 'date',
descending: true,
- rowsPerPage: 10
+ rowsPerPage: 5
},
filter: null
},
@@ -815,7 +814,7 @@ page_container %}
// }
],
pagination: {
- rowsPerPage: 10
+ rowsPerPage: 5
},
filter: null
},
From 4fa1165ecd1f56fc5c4b323db50286685b81d983 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 06:52:31 +0100
Subject: [PATCH 351/696] color
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 8cffd248..0832473b 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -54,7 +54,6 @@ page_container %}
color="primary"
class="full-width"
@click="showReceiveTokensDialog"
- outline
>Receive Tokens
@@ -67,7 +66,6 @@ page_container %}
color="primary"
class="full-width"
@click="showSendTokensDialog"
- outline
>
Send Tokens
@@ -499,7 +497,7 @@ page_container %}
color="primary"
>Copy invoice
-
Create Invoice
- Paste Cashu tokens
+ Receive Cashu tokens
- Receive Tokens
Date: Sat, 5 Nov 2022 07:02:59 +0100
Subject: [PATCH 352/696] check token id
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 8 ++++----
lnbits/extensions/cashu/views_api.py | 9 +++++++++
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 0832473b..d8763cbc 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -119,7 +119,7 @@ page_container %}
:data="invoicesCashu"
:columns="invoicesTable.columns"
:pagination.sync="invoicesTable.pagination"
- no-data-label="No invoices made yet"
+ no-data-label="There are no invoices here yet"
:filter="invoicesTable.filter"
>
{% raw %}
@@ -140,12 +140,12 @@ page_container %}
class="q-mr-md cursor-pointer"
@click="recheckInvoice(props.row.hash)"
>
- Recheck
+ Check
-
-
+ Received
+ Paid
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 806347cb..5f7e9310 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -345,6 +345,15 @@ async def split(
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
)
proofs = payload.proofs
+
+ # !!!!!!! MAKE SURE THAT PROOFS ARE ONLY FROM THIS CASHU KEYSET ID
+ # THIS IS NECESSARY BECAUSE THE CASHU BACKEND WILL ACCEPT ANY VALID
+ # TOKENS
+ assert all([p.id == cashu.keyset_id for p in proofs]), HTTPException(
+ status_code=HTTPStatus.BAD_REQUEST,
+ detail="Proofs include tokens from another mint.",
+ )
+
amount = payload.amount
outputs = payload.outputs.blinded_messages
assert outputs, Exception("no outputs provided.")
From 87ca450e98a7125cc82d5103d2d03bcdad9d0500 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 07:30:09 +0100
Subject: [PATCH 353/696] qtab
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 9 +--------
1 file changed, 1 insertion(+), 8 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index d8763cbc..722c5e73 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -190,7 +190,7 @@ page_container %}
-
-
From 3a987739abf61a7d507a3af7573345b5969e9336 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 07:31:55 +0100
Subject: [PATCH 354/696] qtab
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 722c5e73..8adf8fce 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -190,7 +190,7 @@ page_container %}
Date: Sat, 5 Nov 2022 07:42:39 +0100
Subject: [PATCH 355/696] vibrate
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 8adf8fce..fa185687 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1158,6 +1158,7 @@ page_container %}
clearInterval(this.invoiceCheckListener)
this.invoiceData.bolt11 = ''
this.showInvoiceDetails = false
+ navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
@@ -1437,6 +1438,12 @@ page_container %}
const proofs = JSON.parse(tokensJson)
const amount = proofs.reduce((s, t) => (s += t.amount), 0)
let {fristProofs, scndProofs} = await this.split(proofs, amount)
+ navigator.vibrate(200)
+ this.$q.notify({
+ timeout: 5000,
+ type: 'positive',
+ message: 'Tokens received'
+ })
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
@@ -1457,6 +1464,7 @@ page_container %}
this.sendData.tokens = scndProofs
console.log('### this.sendData.tokens', this.sendData.tokens)
this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
+ navigator.vibrate(200)
},
checkFees: async function (payment_request) {
const payload = {
@@ -1512,6 +1520,7 @@ page_container %}
'',
payload
)
+ navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
From 6dc23f9246709cf578e29f270dd146a929a9afa6 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 16:54:40 +0100
Subject: [PATCH 356/696] multiple mints
---
.../cashu/templates/cashu/wallet.html | 69 ++++++++++---------
1 file changed, 38 insertions(+), 31 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index fa185687..e66e54f5 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1565,6 +1565,15 @@ page_container %}
}
},
+ fetchMintKeys: async function () {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ `/cashu/api/v1/${this.mintId}/keys`
+ )
+ this.keys = data
+ localStorage.setItem(this.mintKey(this.mintId, 'keys'), JSON.stringify(data))
+ },
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1625,34 +1634,32 @@ page_container %}
this.$q.notify({
timeout: 5000,
type: 'warning',
- message: 'Cannot decode invoice',
+ message: 'Could not decode invoice',
caption: error + ''
})
throw error
}
},
- fetchMintKeys: async function () {
- const {data} = await LNbits.api.request(
- 'GET',
- `/cashu/api/v1/${this.mintId}/keys`
- )
- this.keys = data
- localStorage.setItem('cashu.keys', JSON.stringify(data))
- },
-
storeinvoicesCashu: function () {
localStorage.setItem(
- 'cashu.invoicesCashu',
+ this.mintKey(this.mintId, 'invoicesCashu'),
JSON.stringify(this.invoicesCashu)
)
},
storeProofs: function () {
localStorage.setItem(
- 'cashu.proofs',
+ this.mintKey(this.mintId, 'proofs'),
JSON.stringify(this.proofs, bigIntStringify)
)
- }
+ },
+
+ mintKey: function(mintId, key) {
+ // returns a key for the local storage
+ // depending on the current mint
+ return "cashu." + mintId + "." + key
+ },
+
},
watch: {
payments: function () {
@@ -1663,20 +1670,6 @@ page_container %}
created: function () {
let params = new URL(document.location).searchParams
- // get ticker
- if (
- !params.get('tsh') &&
- !this.$q.localStorage.getItem('cashu.tickershort')
- ) {
- this.$q.localStorage.set('cashu.tickershort', 'sats')
- this.tickershort = 'sats'
- } else if (params.get('tsh')) {
- this.$q.localStorage.set('cashu.tickershort', params.get('tsh'))
- this.tickershort = params.get('tsh')
- } else if (this.$q.localStorage.getItem('cashu.tickershort')) {
- this.tickershort = this.$q.localStorage.getItem('cashu.tickershort')
- }
-
// get mint
if (params.get('mint_id')) {
this.mintId = params.get('mint_id')
@@ -1693,12 +1686,26 @@ page_container %}
// get name
if (params.get('mint_name')) {
this.mintName = params.get('mint_name')
- this.$q.localStorage.set('cashu.mintName', params.get('mint_name'))
+ this.$q.localStorage.set(this.mintKey(this.mintId, 'mintName'), this.mintName)
} else if (this.$q.localStorage.getItem('cashu.name')) {
this.mintName = this.$q.localStorage.getItem('cashu.name')
}
- const keysJson = localStorage.getItem('cashu.keys')
+ // get ticker
+ if (
+ !params.get('tsh') &&
+ !this.$q.localStorage.getItem(this.mintKey(this.mintId, 'tickershort'))
+ ) {
+ this.$q.localStorage.set(this.mintKey(this.mintId, 'tickershort'), 'sats')
+ this.tickershort = 'sats'
+ } else if (params.get('tsh')) {
+ this.$q.localStorage.set(this.mintKey(this.mintId, 'tickershort'), params.get('tsh'))
+ this.tickershort = params.get('tsh')
+ } else if (this.$q.localStorage.getItem(this.mintKey(this.mintId, 'tickershort'))) {
+ this.tickershort = this.$q.localStorage.getItem(this.mintKey(this.mintId, 'tickershort'))
+ }
+
+ const keysJson = localStorage.getItem(this.mintKey(this.mintId, 'keys'))
if (!keysJson) {
this.fetchMintKeys()
} else {
@@ -1706,9 +1713,9 @@ page_container %}
}
this.invoicesCashu = JSON.parse(
- localStorage.getItem('cashu.invoicesCashu') || '[]'
+ localStorage.getItem(this.mintKey(this.mintId, 'invoicesCashu')) || '[]'
)
- this.proofs = JSON.parse(localStorage.getItem('cashu.proofs') || '[]')
+ this.proofs = JSON.parse(localStorage.getItem(this.mintKey(this.mintId, 'proofs')) || '[]')
console.log('### invoicesCashu', this.invoicesCashu)
console.table('### tokens', this.proofs)
console.log('#### this.mintId', this.mintId)
From 5a45ca5cb4a89301af000103db95f1cdf23b83ea Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 17:18:39 +0100
Subject: [PATCH 357/696] apple icon
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index e66e54f5..68bf1a16 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1,3 +1,7 @@
+
+
+
+
{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Cashu {% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
page_container %}
From ee6801de805173e0b99f83558a355a58b3794c07 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 17:50:35 +0100
Subject: [PATCH 358/696] fix chrome
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 11 ++++-------
lnbits/extensions/cashu/views.py | 7 +++++++
2 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 68bf1a16..0dc5bcf1 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1,9 +1,6 @@
-
-
-
-
-{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Cashu {% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
-page_container %}
+{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Cashu {% endraw %} {% endblock %}
+{% block footer %}{% endblock %}
+{% block page_container %}
@@ -1321,6 +1318,7 @@ page_container %}
return proofs.reduce((s, t) => (s += t.amount), 0)
},
splitToSend: async function (proofs, amount, invlalidate = false) {
+ // splits proofs so the user can keep firstProofs, send scndProofs
try {
const spendableProofs = proofs.filter(p => !p.reserved)
if (this.sumProofs(spendableProofs) < amount) {
@@ -1330,7 +1328,6 @@ page_container %}
spendableProofs,
amount
)
- // keep firstProofs, send scndProofs
// set scndProofs in this.proofs as reserved
const usedSecrets = proofs.map(p => p.secret)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index ca05d83e..f0a02438 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -86,10 +86,17 @@ async def manifest(cashu_id: str):
{
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
"sizes": "96x96",
+ "type": "image/png",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/180x180.png",
+ "sizes": "180x180",
+ "type": "image/png",
},
{
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
"sizes": "512x512",
+ "type": "image/png",
},
],
}
From aaa325a5a2ec208bf800debda7bcdd3cf1a27ac2 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 17:58:07 +0100
Subject: [PATCH 359/696] refactor wallet js
---
lnbits/extensions/cashu/static/js/wallet.js | 1104 +++++++++++++++++
.../cashu/templates/cashu/wallet.html | 1102 +---------------
2 files changed, 1105 insertions(+), 1101 deletions(-)
create mode 100644 lnbits/extensions/cashu/static/js/wallet.js
diff --git a/lnbits/extensions/cashu/static/js/wallet.js b/lnbits/extensions/cashu/static/js/wallet.js
new file mode 100644
index 00000000..832b075d
--- /dev/null
+++ b/lnbits/extensions/cashu/static/js/wallet.js
@@ -0,0 +1,1104 @@
+var currentDateStr = function () {
+ return Quasar.utils.date.formatDate(new Date(), 'YYYY-MM-DD HH:mm')
+}
+var mapMint = function (obj) {
+ obj.date = Quasar.utils.date.formatDate(
+ new Date(obj.time * 1000),
+ 'YYYY-MM-DD HH:mm'
+ )
+ obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
+ obj.cashu = ['/cashu/', obj.id].join('')
+ return obj
+}
+
+Vue.component(VueQrcode.name, VueQrcode)
+
+new Vue({
+ el: '#vue',
+ mixins: [windowMixin],
+ data: function () {
+ return {
+ tickershort: '',
+ name: '',
+
+ mintId: '',
+ mintName: '',
+ keys: '',
+ invoicesCashu: [],
+ invoiceData: {
+ amount: 0,
+ memo: '',
+ bolt11: '',
+ hash: ''
+ },
+ invoiceCheckListener: () => {},
+ payInvoiceData: {
+ // invoice: '',
+ bolt11: '',
+ // camera: {
+ // show: false,
+ // camera: 'auto'
+ // }
+ show: false,
+ invoice: null,
+ lnurlpay: null,
+ lnurlauth: null,
+ data: {
+ request: '',
+ amount: 0,
+ comment: ''
+ },
+ paymentChecker: null,
+ camera: {
+ show: false,
+ camera: 'auto'
+ }
+ },
+ sendData: {
+ amount: 0,
+ memo: '',
+ tokens: '',
+ tokensBase64: ''
+ },
+ receiveData: {
+ tokensBase64: ''
+ },
+ showInvoiceDetails: false,
+ showPayInvoice: false,
+ showSendTokens: false,
+ showReceiveTokens: false,
+ promises: [],
+ tokens: [],
+ tab: 'tokens',
+
+ receive: {
+ show: false,
+ status: 'pending',
+ paymentReq: null,
+ paymentHash: null,
+ minMax: [0, 2100000000000000],
+ lnurl: null,
+ units: ['sat'],
+ unit: 'sat',
+ data: {
+ amount: null,
+ memo: ''
+ }
+ },
+ parse: {
+ show: false,
+ invoice: null,
+ lnurlpay: null,
+ lnurlauth: null,
+ data: {
+ request: '',
+ amount: 0,
+ comment: ''
+ },
+ paymentChecker: null,
+ camera: {
+ show: false,
+ camera: 'auto'
+ }
+ },
+ payments: [],
+ invoicesTable: {
+ columns: [
+ {
+ name: 'status',
+ align: 'left',
+ label: '',
+ field: 'status'
+ },
+ {
+ name: 'amount',
+ align: 'left',
+ label: 'Amount',
+ field: 'amount'
+ },
+ {
+ name: 'memo',
+ align: 'left',
+ label: 'Memo',
+ field: 'memo',
+ sortable: true
+ },
+ {
+ name: 'date',
+ align: 'left',
+ label: 'Date',
+ field: 'date',
+ sortable: true
+ },
+ {
+ name: 'hash',
+ align: 'right',
+ label: 'Hash',
+ field: 'hash',
+ sortable: true
+ }
+ ],
+ pagination: {
+ sortBy: 'date',
+ descending: true,
+ rowsPerPage: 5
+ },
+ filter: null
+ },
+
+ tokensTable: {
+ columns: [
+ {
+ name: 'value',
+ align: 'left',
+ label: 'Value ({{LNBITS_DENOMINATION}})',
+ field: 'value',
+ sortable: true
+ },
+ {
+ name: 'count',
+ align: 'left',
+ label: 'Count',
+ field: 'count',
+ sortable: true
+ },
+ {
+ name: 'sum',
+ align: 'left',
+ label: 'Sum ({{LNBITS_DENOMINATION}})',
+ field: 'sum',
+ sortable: true
+ }
+ // {
+ // name: 'memo',
+ // align: 'left',
+ // label: 'Memo',
+ // field: 'memo',
+ // sortable: true
+ // }
+ ],
+ pagination: {
+ rowsPerPage: 5
+ },
+ filter: null
+ },
+
+ paymentsChart: {
+ show: false
+ },
+ disclaimerDialog: {
+ show: false,
+ location: window.location
+ },
+
+ credit: 0,
+ newName: ''
+ }
+ },
+ computed: {
+ formattedBalance: function () {
+ return this.balance / 100
+ },
+
+ canPay: function () {
+ if (!this.payInvoiceData.invoice) return false
+ return this.payInvoiceData.invoice.sat <= this.balance
+ },
+ pendingPaymentsExist: function () {
+ return this.payments.findIndex(payment => payment.pending) !== -1
+ },
+
+ balance: function () {
+ return this.proofs
+ .map(t => t)
+ .flat()
+ .reduce((sum, el) => (sum += el.amount), 0)
+ }
+ },
+ filters: {
+ msatoshiFormat: function (value) {
+ return LNbits.utils.formatSat(value / 1000)
+ }
+ },
+ methods: {
+ getBalance: function () {
+ return this.proofs
+ .map(t => t)
+ .flat()
+ .reduce((sum, el) => (sum += el.amount), 0)
+ },
+ getTokenList: function () {
+ const x = this.proofs
+ .map(t => t.amount)
+ .reduce((acc, amount) => {
+ acc[amount] = acc[amount] + amount || 1
+ return acc
+ }, {})
+ return Object.keys(x).map(k => ({
+ value: k,
+ count: x[k],
+ sum: k * x[k]
+ }))
+ },
+
+ paymentTableRowKey: function (row) {
+ return row.payment_hash + row.amount
+ },
+ closeCamera: function () {
+ this.payInvoiceData.camera.show = false
+ },
+ showCamera: function () {
+ this.payInvoiceData.camera.show = true
+ },
+ showChart: function () {
+ this.paymentsChart.show = true
+ this.$nextTick(() => {
+ generateChart(this.$refs.canvas, this.payments)
+ })
+ },
+ focusInput(el) {
+ this.$nextTick(() => this.$refs[el].focus())
+ },
+ showReceiveDialog: function () {
+ this.receive.show = true
+ this.receive.status = 'pending'
+ this.receive.paymentReq = null
+ this.receive.paymentHash = null
+ this.receive.data.amount = null
+ this.receive.data.memo = null
+ this.receive.unit = 'sat'
+ this.receive.paymentChecker = null
+ this.receive.minMax = [0, 2100000000000000]
+ this.receive.lnurl = null
+ this.focusInput('setAmount')
+ },
+ showParseDialog: function () {
+ this.payInvoiceData.show = true
+ this.payInvoiceData.invoice = null
+ this.payInvoiceData.lnurlpay = null
+ this.payInvoiceData.lnurlauth = null
+ this.payInvoiceData.data.request = ''
+ this.payInvoiceData.data.comment = ''
+ this.payInvoiceData.data.paymentChecker = null
+ this.payInvoiceData.camera.show = false
+ this.focusInput('pasteInput')
+ },
+ showDisclaimerDialog: function () {
+ this.disclaimerDialog.show = true
+ },
+
+ closeReceiveDialog: function () {
+ setTimeout(() => {
+ clearInterval(this.receive.paymentChecker)
+ }, 10000)
+ },
+ closeParseDialog: function () {
+ setTimeout(() => {
+ clearInterval(this.payInvoiceData.paymentChecker)
+ }, 10000)
+ },
+ onPaymentReceived: function (paymentHash) {
+ this.fetchPayments()
+ this.fetchBalance()
+
+ if (this.receive.paymentHash === paymentHash) {
+ this.receive.show = false
+ this.receive.paymentHash = null
+ clearInterval(this.receive.paymentChecker)
+ }
+ },
+ createInvoice: function () {
+ this.receive.status = 'loading'
+ if (LNBITS_DENOMINATION != 'sats') {
+ this.receive.data.amount = this.receive.data.amount * 100
+ }
+ LNbits.api
+ .createInvoice(
+ this.receive.data.amount,
+ this.receive.data.memo,
+ this.receive.unit,
+ this.receive.lnurl && this.receive.lnurl.callback
+ )
+ .then(response => {
+ this.receive.status = 'success'
+ this.receive.paymentReq = response.data.payment_request
+ this.receive.paymentHash = response.data.payment_hash
+
+ if (response.data.lnurl_response !== null) {
+ if (response.data.lnurl_response === false) {
+ response.data.lnurl_response = `Unable to connect`
+ }
+
+ if (typeof response.data.lnurl_response === 'string') {
+ // failure
+ this.$q.notify({
+ timeout: 5000,
+ type: 'warning',
+ message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
+ caption: response.data.lnurl_response
+ })
+ return
+ } else if (response.data.lnurl_response === true) {
+ // success
+ this.$q.notify({
+ timeout: 5000,
+ message: `Invoice sent to ${this.receive.lnurl.domain}!`,
+ spinner: true
+ })
+ }
+ }
+
+ clearInterval(this.receive.paymentChecker)
+ setTimeout(() => {
+ clearInterval(this.receive.paymentChecker)
+ }, 40000)
+ })
+ .catch(err => {
+ LNbits.utils.notifyApiError(err)
+ this.receive.status = 'pending'
+ })
+ },
+ decodeQR: function (res) {
+ this.payInvoiceData.data.request = res
+ this.decodeRequest()
+ this.payInvoiceData.camera.show = false
+ },
+ decodeRequest: function () {
+ this.payInvoiceData.show = true
+ let req = this.payInvoiceData.data.request.toLowerCase()
+ if (
+ this.payInvoiceData.data.request.toLowerCase().startsWith('lightning:')
+ ) {
+ this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice(
+ 10
+ )
+ } else if (
+ this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl:')
+ ) {
+ this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice(
+ 6
+ )
+ } else if (req.indexOf('lightning=lnurl1') !== -1) {
+ this.payInvoiceData.data.request = this.payInvoiceData.data.request
+ .split('lightning=')[1]
+ .split('&')[0]
+ }
+
+ if (
+ this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl1') ||
+ this.payInvoiceData.data.request.match(/[\w.+-~_]+@[\w.+-~_]/)
+ ) {
+ return
+ }
+
+ let invoice
+ try {
+ invoice = decode(this.payInvoiceData.data.request)
+ } catch (error) {
+ this.$q.notify({
+ timeout: 3000,
+ type: 'warning',
+ message: error + '.',
+ caption: '400 BAD REQUEST'
+ })
+ this.payInvoiceData.show = false
+ throw error
+ return
+ }
+
+ let cleanInvoice = {
+ msat: invoice.human_readable_part.amount,
+ sat: invoice.human_readable_part.amount / 1000,
+ fsat: LNbits.utils.formatSat(invoice.human_readable_part.amount / 1000)
+ }
+
+ _.each(invoice.data.tags, tag => {
+ if (_.isObject(tag) && _.has(tag, 'description')) {
+ if (tag.description === 'payment_hash') {
+ cleanInvoice.hash = tag.value
+ } else if (tag.description === 'description') {
+ cleanInvoice.description = tag.value
+ } else if (tag.description === 'expiry') {
+ var expireDate = new Date(
+ (invoice.data.time_stamp + tag.value) * 1000
+ )
+ cleanInvoice.expireDate = Quasar.utils.date.formatDate(
+ expireDate,
+ 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ )
+ cleanInvoice.expired = false // TODO
+ }
+ }
+ })
+
+ this.payInvoiceData.invoice = Object.freeze(cleanInvoice)
+ },
+ payInvoice: function () {
+ let dismissPaymentMsg = this.$q.notify({
+ timeout: 0,
+ message: 'Processing payment...'
+ })
+ },
+ payLnurl: function () {
+ let dismissPaymentMsg = this.$q.notify({
+ timeout: 0,
+ message: 'Processing payment...'
+ })
+ },
+ authLnurl: function () {
+ let dismissAuthMsg = this.$q.notify({
+ timeout: 10,
+ message: 'Performing authentication...'
+ })
+ },
+
+ deleteWallet: function (walletId, user) {
+ LNbits.utils
+ .confirmDialog('Are you sure you want to delete this wallet?')
+ .onOk(() => {
+ LNbits.href.deleteWallet(walletId, user)
+ })
+ },
+ fetchPayments: function () {
+ return
+ },
+ fetchBalance: function () {},
+ exportCSV: function () {
+ // status is important for export but it is not in paymentsTable
+ // because it is manually added with payment detail link and icons
+ // and would cause duplication in the list
+ let columns = this.paymentsTable.columns
+ columns.unshift({
+ name: 'pending',
+ align: 'left',
+ label: 'Pending',
+ field: 'pending'
+ })
+ LNbits.utils.exportCSV(columns, this.payments)
+ },
+
+ /////////////////////////////////// WALLET ///////////////////////////////////
+ showInvoicesDialog: async function () {
+ console.log('##### showInvoicesDialog')
+ this.invoiceData.amount = 0
+ this.invoiceData.bolt11 = ''
+ this.invoiceData.hash = ''
+ this.invoiceData.memo = ''
+ this.showInvoiceDetails = true
+ },
+
+ showInvoiceDialog: function (data) {
+ console.log('##### showInvoiceDialog')
+ this.invoiceData = _.clone(data)
+ this.showInvoiceDetails = true
+ },
+
+ showPayInvoiceDialog: function () {
+ console.log('### showPayInvoiceDialog')
+ this.payInvoiceData.invoice = ''
+ this.payInvoiceData.data.request = ''
+ this.showPayInvoice = true
+ this.payInvoiceData.camera.show = false
+ },
+
+ showSendTokensDialog: function () {
+ this.sendData.tokens = ''
+ this.sendData.tokensBase64 = ''
+ this.sendData.amount = 0
+ this.sendData.memo = ''
+ this.showSendTokens = true
+ },
+
+ showReceiveTokensDialog: function () {
+ this.receiveData.tokensBase64 = ''
+ this.showReceiveTokens = true
+ },
+
+ //////////////////////// MINT //////////////////////////////////////////
+ requestMintButton: async function () {
+ await this.requestMint()
+ console.log('this is your invoice BEFORE')
+ console.log(this.invoiceData)
+ this.invoiceCheckListener = setInterval(async () => {
+ try {
+ console.log('this is your invoice AFTER')
+ console.log(this.invoiceData)
+ await this.recheckInvoice(this.invoiceData.hash, false)
+ clearInterval(this.invoiceCheckListener)
+ this.invoiceData.bolt11 = ''
+ this.showInvoiceDetails = false
+ navigator.vibrate(200)
+ this.$q.notify({
+ timeout: 5000,
+ type: 'positive',
+ message: 'Payment received'
+ })
+ } catch (error) {
+ console.log('not paid yet')
+ }
+ }, 3000)
+ },
+
+ requestMint: async function () {
+ // gets an invoice from the mint to get new tokens
+ try {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ `/cashu/api/v1/${this.mintId}/mint?amount=${this.invoiceData.amount}`
+ )
+ console.log('### data', data)
+
+ this.invoiceData.bolt11 = data.pr
+ this.invoiceData.hash = data.hash
+ this.invoicesCashu.push({
+ ..._.clone(this.invoiceData),
+ date: currentDateStr(),
+ status: 'pending'
+ })
+ this.storeinvoicesCashu()
+ this.tab = 'invoices'
+ return data
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ },
+ mintApi: async function (amounts, payment_hash, verbose = true) {
+ console.log('### promises', payment_hash)
+ try {
+ let secrets = await this.generateSecrets(amounts)
+ let {blindedMessages, rs} = await this.constructOutputs(
+ amounts,
+ secrets
+ )
+ const promises = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/${this.mintId}/mint?payment_hash=${payment_hash}`,
+ '',
+ {
+ blinded_messages: blindedMessages
+ }
+ )
+ console.log('### promises data', promises.data)
+ let proofs = await this.constructProofs(promises.data, secrets, rs)
+ return proofs
+ } catch (error) {
+ console.error(error)
+ if (verbose) {
+ LNbits.utils.notifyApiError(error)
+ }
+ throw error
+ }
+ },
+ mint: async function (amount, payment_hash, verbose = true) {
+ try {
+ const split = splitAmount(amount)
+ const proofs = await this.mintApi(split, payment_hash, verbose)
+ if (!proofs.length) {
+ throw 'could not mint'
+ }
+ this.proofs = this.proofs.concat(proofs)
+ this.storeProofs()
+ await this.setInvoicePaid(payment_hash)
+ return proofs
+ } catch (error) {
+ console.error(error)
+ if (verbose) {
+ LNbits.utils.notifyApiError(error)
+ }
+ throw error
+ }
+ },
+ setInvoicePaid: async function (payment_hash) {
+ const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
+ invoice.status = 'paid'
+ this.storeinvoicesCashu()
+ },
+ recheckInvoice: async function (payment_hash, verbose = true) {
+ console.log('### recheckInvoice.hash', payment_hash)
+ const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
+ try {
+ proofs = await this.mint(invoice.amount, invoice.hash, verbose)
+ return proofs
+ } catch (error) {
+ console.log('Invoice still pending')
+ throw error
+ }
+ },
+
+ generateSecrets: async function (amounts) {
+ const secrets = []
+ for (let i = 0; i < amounts.length; i++) {
+ const secret = nobleSecp256k1.utils.randomBytes(32)
+ secrets.push(secret)
+ }
+ return secrets
+ },
+
+ constructOutputs: async function (amounts, secrets) {
+ const blindedMessages = []
+ const rs = []
+ for (let i = 0; i < amounts.length; i++) {
+ const {B_, r} = await step1Alice(secrets[i])
+ blindedMessages.push({amount: amounts[i], B_: B_})
+ rs.push(r)
+ }
+ return {
+ blindedMessages,
+ rs
+ }
+ },
+
+ constructProofs: function (promises, secrets, rs) {
+ const proofs = []
+ for (let i = 0; i < promises.length; i++) {
+ const encodedSecret = uint8ToBase64.encode(secrets[i])
+ let {id, amount, C, secret} = this.promiseToProof(
+ promises[i].id,
+ promises[i].amount,
+ promises[i]['C_'],
+ encodedSecret,
+ rs[i]
+ )
+ proofs.push({id, amount, C, secret})
+ }
+ return proofs
+ },
+
+ promiseToProof: function (id, amount, C_hex, secret, r) {
+ const C_ = nobleSecp256k1.Point.fromHex(C_hex)
+ const A = this.keys[amount]
+ const C = step3Alice(
+ C_,
+ nobleSecp256k1.utils.hexToBytes(r),
+ nobleSecp256k1.Point.fromHex(A)
+ )
+ return {
+ id,
+ amount,
+ C: C.toHex(true),
+ secret
+ }
+ },
+
+ sumProofs: function (proofs) {
+ return proofs.reduce((s, t) => (s += t.amount), 0)
+ },
+ splitToSend: async function (proofs, amount, invlalidate = false) {
+ // splits proofs so the user can keep firstProofs, send scndProofs
+ try {
+ const spendableProofs = proofs.filter(p => !p.reserved)
+ if (this.sumProofs(spendableProofs) < amount) {
+ throw new Error('balance too low.')
+ }
+ let {fristProofs, scndProofs} = await this.split(
+ spendableProofs,
+ amount
+ )
+
+ // set scndProofs in this.proofs as reserved
+ const usedSecrets = proofs.map(p => p.secret)
+ for (let i = 0; i < this.proofs.length; i++) {
+ if (usedSecrets.includes(this.proofs[i].secret)) {
+ this.proofs[i].reserved = true
+ }
+ }
+ if (invlalidate) {
+ // delete tokens from db
+ this.proofs = fristProofs
+ // add new fristProofs, scndProofs to this.proofs
+ this.storeProofs()
+ }
+
+ return {fristProofs, scndProofs}
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ },
+
+ split: async function (proofs, amount) {
+ try {
+ if (proofs.length == 0) {
+ throw new Error('no proofs provided.')
+ }
+ let {fristProofs, scndProofs} = await this.splitApi(proofs, amount)
+ // delete proofs from this.proofs
+ const usedSecrets = proofs.map(p => p.secret)
+ this.proofs = this.proofs.filter(p => !usedSecrets.includes(p.secret))
+ // add new fristProofs, scndProofs to this.proofs
+ this.proofs = this.proofs.concat(fristProofs).concat(scndProofs)
+ this.storeProofs()
+ return {fristProofs, scndProofs}
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ },
+ splitApi: async function (proofs, amount) {
+ try {
+ const total = this.sumProofs(proofs)
+ const frst_amount = total - amount
+ const scnd_amount = amount
+ const frst_amounts = splitAmount(frst_amount)
+ const scnd_amounts = splitAmount(scnd_amount)
+ const amounts = _.clone(frst_amounts)
+ amounts.push(...scnd_amounts)
+ let secrets = await this.generateSecrets(amounts)
+ if (secrets.length != amounts.length) {
+ throw new Error('number of secrets does not match number of outputs.')
+ }
+ let {blindedMessages, rs} = await this.constructOutputs(
+ amounts,
+ secrets
+ )
+ const payload = {
+ amount,
+ proofs,
+ outputs: {
+ blinded_messages: blindedMessages
+ }
+ }
+
+ console.log('payload', JSON.stringify(payload))
+
+ const {data} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/${this.mintId}/split`,
+ '',
+ payload
+ )
+ const frst_rs = rs.slice(0, frst_amounts.length)
+ const frst_secrets = secrets.slice(0, frst_amounts.length)
+ const scnd_rs = rs.slice(frst_amounts.length)
+ const scnd_secrets = secrets.slice(frst_amounts.length)
+ const fristProofs = this.constructProofs(
+ data.fst,
+ frst_secrets,
+ frst_rs
+ )
+ const scndProofs = this.constructProofs(data.snd, scnd_secrets, scnd_rs)
+
+ return {fristProofs, scndProofs}
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ },
+
+ redeem: async function () {
+ this.showReceiveTokens = false
+ console.log('### receive tokens', this.receiveData.tokensBase64)
+ try {
+ if (this.receiveData.tokensBase64.length == 0) {
+ throw new Error('no tokens provided.')
+ }
+ const tokensJson = atob(this.receiveData.tokensBase64)
+ const proofs = JSON.parse(tokensJson)
+ const amount = proofs.reduce((s, t) => (s += t.amount), 0)
+ let {fristProofs, scndProofs} = await this.split(proofs, amount)
+ // HACK: we need to do this so the balance updates
+ this.proofs = this.proofs.concat([])
+ navigator.vibrate(200)
+ this.$q.notify({
+ timeout: 5000,
+ type: 'positive',
+ message: 'Tokens received'
+ })
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ // }
+ },
+
+ sendTokens: async function () {
+ // keep firstProofs, send scndProofs
+ let {fristProofs, scndProofs} = await this.splitToSend(
+ this.proofs,
+ this.sendData.amount,
+ true
+ )
+ this.sendData.tokens = ''
+ this.sendData.tokensBase64 = ''
+ this.sendData.tokens = scndProofs
+ console.log('### this.sendData.tokens', this.sendData.tokens)
+ this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
+ navigator.vibrate(200)
+ },
+ checkFees: async function (payment_request) {
+ const payload = {
+ pr: payment_request
+ }
+ console.log('#### payload', JSON.stringify(payload))
+ try {
+ const {data} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/${this.mintId}/checkfees`,
+ '',
+ payload
+ )
+ console.log('#### checkFees', payment_request, data.fee)
+ return data.fee
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ },
+ melt: async function () {
+ // todo: get fees from server and add to inputs
+ console.log('#### pay lightning')
+ const amount_invoice = this.payInvoiceData.invoice.sat
+ const amount =
+ amount_invoice +
+ (await this.checkFees(this.payInvoiceData.data.request))
+ console.log(
+ '#### amount invoice',
+ amount_invoice,
+ 'amount with fees',
+ amount
+ )
+ // if (amount > balance()) {
+ // LNbits.utils.notifyApiError('Balance too low')
+ // return
+ // }
+ let {fristProofs, scndProofs} = await this.splitToSend(
+ this.proofs,
+ amount
+ )
+ const payload = {
+ proofs: scndProofs.flat(),
+ amount,
+ invoice: this.payInvoiceData.data.request
+ }
+ console.log('#### payload', JSON.stringify(payload))
+ try {
+ const {data} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/${this.mintId}/melt`,
+ '',
+ payload
+ )
+ navigator.vibrate(200)
+ this.$q.notify({
+ timeout: 5000,
+ type: 'positive',
+ message: 'Invoice paid'
+ })
+ // delete tokens from db
+ this.proofs = fristProofs
+ // add new fristProofs, scndProofs to this.proofs
+ this.storeProofs()
+ console.log({
+ amount: -amount,
+ bolt11: this.payInvoiceData.data.request,
+ hash: this.payInvoiceData.data.hash,
+ memo: this.payInvoiceData.data.memo
+ })
+ this.invoicesCashu.push({
+ amount: -amount,
+ bolt11: this.payInvoiceData.data.request,
+ hash: this.payInvoiceData.data.hash,
+ memo: this.payInvoiceData.data.memo,
+ date: currentDateStr(),
+ status: 'paid'
+ })
+ this.storeinvoicesCashu()
+ this.tab = 'invoices'
+
+ this.payInvoiceData.invoice = false
+ this.payInvoiceData.show = false
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ },
+
+ recheckPendingInvoices: async function () {
+ for (const invoice of this.invoicesCashu) {
+ if (invoice.status === 'pending' && invoice.sat > 0) {
+ this.recheckInvoice(invoice.hash, false)
+ }
+ }
+ },
+
+ fetchMintKeys: async function () {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ `/cashu/api/v1/${this.mintId}/keys`
+ )
+ this.keys = data
+ localStorage.setItem(
+ this.mintKey(this.mintId, 'keys'),
+ JSON.stringify(data)
+ )
+ },
+
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+ ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ findTokenForAmount: function (amount) {
+ for (const token of this.proofs) {
+ const index = token.promises?.findIndex(p => p.amount === amount)
+ if (index >= 0) {
+ return {
+ promise: token.promises[index],
+ secret: token.secrets[index],
+ r: token.rs[index]
+ }
+ }
+ }
+ },
+
+ checkInvoice: function () {
+ console.log('#### checkInvoice')
+ try {
+ const invoice = decode(this.payInvoiceData.data.request)
+
+ const cleanInvoice = {
+ msat: invoice.human_readable_part.amount,
+ sat: invoice.human_readable_part.amount / 1000,
+ fsat: LNbits.utils.formatSat(
+ invoice.human_readable_part.amount / 1000
+ )
+ }
+
+ _.each(invoice.data.tags, tag => {
+ if (_.isObject(tag) && _.has(tag, 'description')) {
+ if (tag.description === 'payment_hash') {
+ cleanInvoice.hash = tag.value
+ } else if (tag.description === 'description') {
+ cleanInvoice.description = tag.value
+ } else if (tag.description === 'expiry') {
+ var expireDate = new Date(
+ (invoice.data.time_stamp + tag.value) * 1000
+ )
+ cleanInvoice.expireDate = Quasar.utils.date.formatDate(
+ expireDate,
+ 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ )
+ cleanInvoice.expired = false // TODO
+ }
+ }
+
+ this.payInvoiceData.invoice = cleanInvoice
+ })
+
+ console.log(
+ '#### this.payInvoiceData.invoice',
+ this.payInvoiceData.invoice
+ )
+ } catch (error) {
+ this.$q.notify({
+ timeout: 5000,
+ type: 'warning',
+ message: 'Could not decode invoice',
+ caption: error + ''
+ })
+ throw error
+ }
+ },
+
+ storeinvoicesCashu: function () {
+ localStorage.setItem(
+ this.mintKey(this.mintId, 'invoicesCashu'),
+ JSON.stringify(this.invoicesCashu)
+ )
+ },
+ storeProofs: function () {
+ localStorage.setItem(
+ this.mintKey(this.mintId, 'proofs'),
+ JSON.stringify(this.proofs, bigIntStringify)
+ )
+ },
+
+ mintKey: function (mintId, key) {
+ // returns a key for the local storage
+ // depending on the current mint
+ return 'cashu.' + mintId + '.' + key
+ }
+ },
+ watch: {
+ payments: function () {
+ this.balance()
+ }
+ },
+
+ created: function () {
+ let params = new URL(document.location).searchParams
+
+ // get mint
+ if (params.get('mint_id')) {
+ this.mintId = params.get('mint_id')
+ this.$q.localStorage.set('cashu.mint', params.get('mint_id'))
+ } else if (this.$q.localStorage.getItem('cashu.mint')) {
+ this.mintId = this.$q.localStorage.getItem('cashu.mint')
+ } else {
+ this.$q.notify({
+ color: 'red',
+ message: 'No mint set!'
+ })
+ }
+
+ // get name
+ if (params.get('mint_name')) {
+ this.mintName = params.get('mint_name')
+ this.$q.localStorage.set(
+ this.mintKey(this.mintId, 'mintName'),
+ this.mintName
+ )
+ } else if (this.$q.localStorage.getItem('cashu.name')) {
+ this.mintName = this.$q.localStorage.getItem('cashu.name')
+ }
+
+ // get ticker
+ if (
+ !params.get('tsh') &&
+ !this.$q.localStorage.getItem(this.mintKey(this.mintId, 'tickershort'))
+ ) {
+ this.$q.localStorage.set(this.mintKey(this.mintId, 'tickershort'), 'sats')
+ this.tickershort = 'sats'
+ } else if (params.get('tsh')) {
+ this.$q.localStorage.set(
+ this.mintKey(this.mintId, 'tickershort'),
+ params.get('tsh')
+ )
+ this.tickershort = params.get('tsh')
+ } else if (
+ this.$q.localStorage.getItem(this.mintKey(this.mintId, 'tickershort'))
+ ) {
+ this.tickershort = this.$q.localStorage.getItem(
+ this.mintKey(this.mintId, 'tickershort')
+ )
+ }
+
+ const keysJson = localStorage.getItem(this.mintKey(this.mintId, 'keys'))
+ if (!keysJson) {
+ this.fetchMintKeys()
+ } else {
+ this.keys = JSON.parse(keysJson)
+ }
+
+ this.invoicesCashu = JSON.parse(
+ localStorage.getItem(this.mintKey(this.mintId, 'invoicesCashu')) || '[]'
+ )
+ this.proofs = JSON.parse(
+ localStorage.getItem(this.mintKey(this.mintId, 'proofs')) || '[]'
+ )
+ console.log('### invoicesCashu', this.invoicesCashu)
+ console.table('### tokens', this.proofs)
+ console.log('#### this.mintId', this.mintId)
+ console.log('#### this.mintName', this.mintName)
+
+ this.recheckPendingInvoices()
+ }
+})
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 0dc5bcf1..7ec99437 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -625,1105 +625,5 @@
-
+
{% endblock %}
From 6c53feae49c76fff7ab472c554db692b233f4cef Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 18:01:35 +0100
Subject: [PATCH 360/696] revert
---
lnbits/extensions/cashu/static/js/wallet.js | 1104 -----------------
.../cashu/templates/cashu/wallet.html | 1104 ++++++++++++++++-
2 files changed, 1103 insertions(+), 1105 deletions(-)
delete mode 100644 lnbits/extensions/cashu/static/js/wallet.js
diff --git a/lnbits/extensions/cashu/static/js/wallet.js b/lnbits/extensions/cashu/static/js/wallet.js
deleted file mode 100644
index 832b075d..00000000
--- a/lnbits/extensions/cashu/static/js/wallet.js
+++ /dev/null
@@ -1,1104 +0,0 @@
-var currentDateStr = function () {
- return Quasar.utils.date.formatDate(new Date(), 'YYYY-MM-DD HH:mm')
-}
-var mapMint = function (obj) {
- obj.date = Quasar.utils.date.formatDate(
- new Date(obj.time * 1000),
- 'YYYY-MM-DD HH:mm'
- )
- obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
- obj.cashu = ['/cashu/', obj.id].join('')
- return obj
-}
-
-Vue.component(VueQrcode.name, VueQrcode)
-
-new Vue({
- el: '#vue',
- mixins: [windowMixin],
- data: function () {
- return {
- tickershort: '',
- name: '',
-
- mintId: '',
- mintName: '',
- keys: '',
- invoicesCashu: [],
- invoiceData: {
- amount: 0,
- memo: '',
- bolt11: '',
- hash: ''
- },
- invoiceCheckListener: () => {},
- payInvoiceData: {
- // invoice: '',
- bolt11: '',
- // camera: {
- // show: false,
- // camera: 'auto'
- // }
- show: false,
- invoice: null,
- lnurlpay: null,
- lnurlauth: null,
- data: {
- request: '',
- amount: 0,
- comment: ''
- },
- paymentChecker: null,
- camera: {
- show: false,
- camera: 'auto'
- }
- },
- sendData: {
- amount: 0,
- memo: '',
- tokens: '',
- tokensBase64: ''
- },
- receiveData: {
- tokensBase64: ''
- },
- showInvoiceDetails: false,
- showPayInvoice: false,
- showSendTokens: false,
- showReceiveTokens: false,
- promises: [],
- tokens: [],
- tab: 'tokens',
-
- receive: {
- show: false,
- status: 'pending',
- paymentReq: null,
- paymentHash: null,
- minMax: [0, 2100000000000000],
- lnurl: null,
- units: ['sat'],
- unit: 'sat',
- data: {
- amount: null,
- memo: ''
- }
- },
- parse: {
- show: false,
- invoice: null,
- lnurlpay: null,
- lnurlauth: null,
- data: {
- request: '',
- amount: 0,
- comment: ''
- },
- paymentChecker: null,
- camera: {
- show: false,
- camera: 'auto'
- }
- },
- payments: [],
- invoicesTable: {
- columns: [
- {
- name: 'status',
- align: 'left',
- label: '',
- field: 'status'
- },
- {
- name: 'amount',
- align: 'left',
- label: 'Amount',
- field: 'amount'
- },
- {
- name: 'memo',
- align: 'left',
- label: 'Memo',
- field: 'memo',
- sortable: true
- },
- {
- name: 'date',
- align: 'left',
- label: 'Date',
- field: 'date',
- sortable: true
- },
- {
- name: 'hash',
- align: 'right',
- label: 'Hash',
- field: 'hash',
- sortable: true
- }
- ],
- pagination: {
- sortBy: 'date',
- descending: true,
- rowsPerPage: 5
- },
- filter: null
- },
-
- tokensTable: {
- columns: [
- {
- name: 'value',
- align: 'left',
- label: 'Value ({{LNBITS_DENOMINATION}})',
- field: 'value',
- sortable: true
- },
- {
- name: 'count',
- align: 'left',
- label: 'Count',
- field: 'count',
- sortable: true
- },
- {
- name: 'sum',
- align: 'left',
- label: 'Sum ({{LNBITS_DENOMINATION}})',
- field: 'sum',
- sortable: true
- }
- // {
- // name: 'memo',
- // align: 'left',
- // label: 'Memo',
- // field: 'memo',
- // sortable: true
- // }
- ],
- pagination: {
- rowsPerPage: 5
- },
- filter: null
- },
-
- paymentsChart: {
- show: false
- },
- disclaimerDialog: {
- show: false,
- location: window.location
- },
-
- credit: 0,
- newName: ''
- }
- },
- computed: {
- formattedBalance: function () {
- return this.balance / 100
- },
-
- canPay: function () {
- if (!this.payInvoiceData.invoice) return false
- return this.payInvoiceData.invoice.sat <= this.balance
- },
- pendingPaymentsExist: function () {
- return this.payments.findIndex(payment => payment.pending) !== -1
- },
-
- balance: function () {
- return this.proofs
- .map(t => t)
- .flat()
- .reduce((sum, el) => (sum += el.amount), 0)
- }
- },
- filters: {
- msatoshiFormat: function (value) {
- return LNbits.utils.formatSat(value / 1000)
- }
- },
- methods: {
- getBalance: function () {
- return this.proofs
- .map(t => t)
- .flat()
- .reduce((sum, el) => (sum += el.amount), 0)
- },
- getTokenList: function () {
- const x = this.proofs
- .map(t => t.amount)
- .reduce((acc, amount) => {
- acc[amount] = acc[amount] + amount || 1
- return acc
- }, {})
- return Object.keys(x).map(k => ({
- value: k,
- count: x[k],
- sum: k * x[k]
- }))
- },
-
- paymentTableRowKey: function (row) {
- return row.payment_hash + row.amount
- },
- closeCamera: function () {
- this.payInvoiceData.camera.show = false
- },
- showCamera: function () {
- this.payInvoiceData.camera.show = true
- },
- showChart: function () {
- this.paymentsChart.show = true
- this.$nextTick(() => {
- generateChart(this.$refs.canvas, this.payments)
- })
- },
- focusInput(el) {
- this.$nextTick(() => this.$refs[el].focus())
- },
- showReceiveDialog: function () {
- this.receive.show = true
- this.receive.status = 'pending'
- this.receive.paymentReq = null
- this.receive.paymentHash = null
- this.receive.data.amount = null
- this.receive.data.memo = null
- this.receive.unit = 'sat'
- this.receive.paymentChecker = null
- this.receive.minMax = [0, 2100000000000000]
- this.receive.lnurl = null
- this.focusInput('setAmount')
- },
- showParseDialog: function () {
- this.payInvoiceData.show = true
- this.payInvoiceData.invoice = null
- this.payInvoiceData.lnurlpay = null
- this.payInvoiceData.lnurlauth = null
- this.payInvoiceData.data.request = ''
- this.payInvoiceData.data.comment = ''
- this.payInvoiceData.data.paymentChecker = null
- this.payInvoiceData.camera.show = false
- this.focusInput('pasteInput')
- },
- showDisclaimerDialog: function () {
- this.disclaimerDialog.show = true
- },
-
- closeReceiveDialog: function () {
- setTimeout(() => {
- clearInterval(this.receive.paymentChecker)
- }, 10000)
- },
- closeParseDialog: function () {
- setTimeout(() => {
- clearInterval(this.payInvoiceData.paymentChecker)
- }, 10000)
- },
- onPaymentReceived: function (paymentHash) {
- this.fetchPayments()
- this.fetchBalance()
-
- if (this.receive.paymentHash === paymentHash) {
- this.receive.show = false
- this.receive.paymentHash = null
- clearInterval(this.receive.paymentChecker)
- }
- },
- createInvoice: function () {
- this.receive.status = 'loading'
- if (LNBITS_DENOMINATION != 'sats') {
- this.receive.data.amount = this.receive.data.amount * 100
- }
- LNbits.api
- .createInvoice(
- this.receive.data.amount,
- this.receive.data.memo,
- this.receive.unit,
- this.receive.lnurl && this.receive.lnurl.callback
- )
- .then(response => {
- this.receive.status = 'success'
- this.receive.paymentReq = response.data.payment_request
- this.receive.paymentHash = response.data.payment_hash
-
- if (response.data.lnurl_response !== null) {
- if (response.data.lnurl_response === false) {
- response.data.lnurl_response = `Unable to connect`
- }
-
- if (typeof response.data.lnurl_response === 'string') {
- // failure
- this.$q.notify({
- timeout: 5000,
- type: 'warning',
- message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
- caption: response.data.lnurl_response
- })
- return
- } else if (response.data.lnurl_response === true) {
- // success
- this.$q.notify({
- timeout: 5000,
- message: `Invoice sent to ${this.receive.lnurl.domain}!`,
- spinner: true
- })
- }
- }
-
- clearInterval(this.receive.paymentChecker)
- setTimeout(() => {
- clearInterval(this.receive.paymentChecker)
- }, 40000)
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- this.receive.status = 'pending'
- })
- },
- decodeQR: function (res) {
- this.payInvoiceData.data.request = res
- this.decodeRequest()
- this.payInvoiceData.camera.show = false
- },
- decodeRequest: function () {
- this.payInvoiceData.show = true
- let req = this.payInvoiceData.data.request.toLowerCase()
- if (
- this.payInvoiceData.data.request.toLowerCase().startsWith('lightning:')
- ) {
- this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice(
- 10
- )
- } else if (
- this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl:')
- ) {
- this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice(
- 6
- )
- } else if (req.indexOf('lightning=lnurl1') !== -1) {
- this.payInvoiceData.data.request = this.payInvoiceData.data.request
- .split('lightning=')[1]
- .split('&')[0]
- }
-
- if (
- this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl1') ||
- this.payInvoiceData.data.request.match(/[\w.+-~_]+@[\w.+-~_]/)
- ) {
- return
- }
-
- let invoice
- try {
- invoice = decode(this.payInvoiceData.data.request)
- } catch (error) {
- this.$q.notify({
- timeout: 3000,
- type: 'warning',
- message: error + '.',
- caption: '400 BAD REQUEST'
- })
- this.payInvoiceData.show = false
- throw error
- return
- }
-
- let cleanInvoice = {
- msat: invoice.human_readable_part.amount,
- sat: invoice.human_readable_part.amount / 1000,
- fsat: LNbits.utils.formatSat(invoice.human_readable_part.amount / 1000)
- }
-
- _.each(invoice.data.tags, tag => {
- if (_.isObject(tag) && _.has(tag, 'description')) {
- if (tag.description === 'payment_hash') {
- cleanInvoice.hash = tag.value
- } else if (tag.description === 'description') {
- cleanInvoice.description = tag.value
- } else if (tag.description === 'expiry') {
- var expireDate = new Date(
- (invoice.data.time_stamp + tag.value) * 1000
- )
- cleanInvoice.expireDate = Quasar.utils.date.formatDate(
- expireDate,
- 'YYYY-MM-DDTHH:mm:ss.SSSZ'
- )
- cleanInvoice.expired = false // TODO
- }
- }
- })
-
- this.payInvoiceData.invoice = Object.freeze(cleanInvoice)
- },
- payInvoice: function () {
- let dismissPaymentMsg = this.$q.notify({
- timeout: 0,
- message: 'Processing payment...'
- })
- },
- payLnurl: function () {
- let dismissPaymentMsg = this.$q.notify({
- timeout: 0,
- message: 'Processing payment...'
- })
- },
- authLnurl: function () {
- let dismissAuthMsg = this.$q.notify({
- timeout: 10,
- message: 'Performing authentication...'
- })
- },
-
- deleteWallet: function (walletId, user) {
- LNbits.utils
- .confirmDialog('Are you sure you want to delete this wallet?')
- .onOk(() => {
- LNbits.href.deleteWallet(walletId, user)
- })
- },
- fetchPayments: function () {
- return
- },
- fetchBalance: function () {},
- exportCSV: function () {
- // status is important for export but it is not in paymentsTable
- // because it is manually added with payment detail link and icons
- // and would cause duplication in the list
- let columns = this.paymentsTable.columns
- columns.unshift({
- name: 'pending',
- align: 'left',
- label: 'Pending',
- field: 'pending'
- })
- LNbits.utils.exportCSV(columns, this.payments)
- },
-
- /////////////////////////////////// WALLET ///////////////////////////////////
- showInvoicesDialog: async function () {
- console.log('##### showInvoicesDialog')
- this.invoiceData.amount = 0
- this.invoiceData.bolt11 = ''
- this.invoiceData.hash = ''
- this.invoiceData.memo = ''
- this.showInvoiceDetails = true
- },
-
- showInvoiceDialog: function (data) {
- console.log('##### showInvoiceDialog')
- this.invoiceData = _.clone(data)
- this.showInvoiceDetails = true
- },
-
- showPayInvoiceDialog: function () {
- console.log('### showPayInvoiceDialog')
- this.payInvoiceData.invoice = ''
- this.payInvoiceData.data.request = ''
- this.showPayInvoice = true
- this.payInvoiceData.camera.show = false
- },
-
- showSendTokensDialog: function () {
- this.sendData.tokens = ''
- this.sendData.tokensBase64 = ''
- this.sendData.amount = 0
- this.sendData.memo = ''
- this.showSendTokens = true
- },
-
- showReceiveTokensDialog: function () {
- this.receiveData.tokensBase64 = ''
- this.showReceiveTokens = true
- },
-
- //////////////////////// MINT //////////////////////////////////////////
- requestMintButton: async function () {
- await this.requestMint()
- console.log('this is your invoice BEFORE')
- console.log(this.invoiceData)
- this.invoiceCheckListener = setInterval(async () => {
- try {
- console.log('this is your invoice AFTER')
- console.log(this.invoiceData)
- await this.recheckInvoice(this.invoiceData.hash, false)
- clearInterval(this.invoiceCheckListener)
- this.invoiceData.bolt11 = ''
- this.showInvoiceDetails = false
- navigator.vibrate(200)
- this.$q.notify({
- timeout: 5000,
- type: 'positive',
- message: 'Payment received'
- })
- } catch (error) {
- console.log('not paid yet')
- }
- }, 3000)
- },
-
- requestMint: async function () {
- // gets an invoice from the mint to get new tokens
- try {
- const {data} = await LNbits.api.request(
- 'GET',
- `/cashu/api/v1/${this.mintId}/mint?amount=${this.invoiceData.amount}`
- )
- console.log('### data', data)
-
- this.invoiceData.bolt11 = data.pr
- this.invoiceData.hash = data.hash
- this.invoicesCashu.push({
- ..._.clone(this.invoiceData),
- date: currentDateStr(),
- status: 'pending'
- })
- this.storeinvoicesCashu()
- this.tab = 'invoices'
- return data
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- throw error
- }
- },
- mintApi: async function (amounts, payment_hash, verbose = true) {
- console.log('### promises', payment_hash)
- try {
- let secrets = await this.generateSecrets(amounts)
- let {blindedMessages, rs} = await this.constructOutputs(
- amounts,
- secrets
- )
- const promises = await LNbits.api.request(
- 'POST',
- `/cashu/api/v1/${this.mintId}/mint?payment_hash=${payment_hash}`,
- '',
- {
- blinded_messages: blindedMessages
- }
- )
- console.log('### promises data', promises.data)
- let proofs = await this.constructProofs(promises.data, secrets, rs)
- return proofs
- } catch (error) {
- console.error(error)
- if (verbose) {
- LNbits.utils.notifyApiError(error)
- }
- throw error
- }
- },
- mint: async function (amount, payment_hash, verbose = true) {
- try {
- const split = splitAmount(amount)
- const proofs = await this.mintApi(split, payment_hash, verbose)
- if (!proofs.length) {
- throw 'could not mint'
- }
- this.proofs = this.proofs.concat(proofs)
- this.storeProofs()
- await this.setInvoicePaid(payment_hash)
- return proofs
- } catch (error) {
- console.error(error)
- if (verbose) {
- LNbits.utils.notifyApiError(error)
- }
- throw error
- }
- },
- setInvoicePaid: async function (payment_hash) {
- const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
- invoice.status = 'paid'
- this.storeinvoicesCashu()
- },
- recheckInvoice: async function (payment_hash, verbose = true) {
- console.log('### recheckInvoice.hash', payment_hash)
- const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
- try {
- proofs = await this.mint(invoice.amount, invoice.hash, verbose)
- return proofs
- } catch (error) {
- console.log('Invoice still pending')
- throw error
- }
- },
-
- generateSecrets: async function (amounts) {
- const secrets = []
- for (let i = 0; i < amounts.length; i++) {
- const secret = nobleSecp256k1.utils.randomBytes(32)
- secrets.push(secret)
- }
- return secrets
- },
-
- constructOutputs: async function (amounts, secrets) {
- const blindedMessages = []
- const rs = []
- for (let i = 0; i < amounts.length; i++) {
- const {B_, r} = await step1Alice(secrets[i])
- blindedMessages.push({amount: amounts[i], B_: B_})
- rs.push(r)
- }
- return {
- blindedMessages,
- rs
- }
- },
-
- constructProofs: function (promises, secrets, rs) {
- const proofs = []
- for (let i = 0; i < promises.length; i++) {
- const encodedSecret = uint8ToBase64.encode(secrets[i])
- let {id, amount, C, secret} = this.promiseToProof(
- promises[i].id,
- promises[i].amount,
- promises[i]['C_'],
- encodedSecret,
- rs[i]
- )
- proofs.push({id, amount, C, secret})
- }
- return proofs
- },
-
- promiseToProof: function (id, amount, C_hex, secret, r) {
- const C_ = nobleSecp256k1.Point.fromHex(C_hex)
- const A = this.keys[amount]
- const C = step3Alice(
- C_,
- nobleSecp256k1.utils.hexToBytes(r),
- nobleSecp256k1.Point.fromHex(A)
- )
- return {
- id,
- amount,
- C: C.toHex(true),
- secret
- }
- },
-
- sumProofs: function (proofs) {
- return proofs.reduce((s, t) => (s += t.amount), 0)
- },
- splitToSend: async function (proofs, amount, invlalidate = false) {
- // splits proofs so the user can keep firstProofs, send scndProofs
- try {
- const spendableProofs = proofs.filter(p => !p.reserved)
- if (this.sumProofs(spendableProofs) < amount) {
- throw new Error('balance too low.')
- }
- let {fristProofs, scndProofs} = await this.split(
- spendableProofs,
- amount
- )
-
- // set scndProofs in this.proofs as reserved
- const usedSecrets = proofs.map(p => p.secret)
- for (let i = 0; i < this.proofs.length; i++) {
- if (usedSecrets.includes(this.proofs[i].secret)) {
- this.proofs[i].reserved = true
- }
- }
- if (invlalidate) {
- // delete tokens from db
- this.proofs = fristProofs
- // add new fristProofs, scndProofs to this.proofs
- this.storeProofs()
- }
-
- return {fristProofs, scndProofs}
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- throw error
- }
- },
-
- split: async function (proofs, amount) {
- try {
- if (proofs.length == 0) {
- throw new Error('no proofs provided.')
- }
- let {fristProofs, scndProofs} = await this.splitApi(proofs, amount)
- // delete proofs from this.proofs
- const usedSecrets = proofs.map(p => p.secret)
- this.proofs = this.proofs.filter(p => !usedSecrets.includes(p.secret))
- // add new fristProofs, scndProofs to this.proofs
- this.proofs = this.proofs.concat(fristProofs).concat(scndProofs)
- this.storeProofs()
- return {fristProofs, scndProofs}
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- throw error
- }
- },
- splitApi: async function (proofs, amount) {
- try {
- const total = this.sumProofs(proofs)
- const frst_amount = total - amount
- const scnd_amount = amount
- const frst_amounts = splitAmount(frst_amount)
- const scnd_amounts = splitAmount(scnd_amount)
- const amounts = _.clone(frst_amounts)
- amounts.push(...scnd_amounts)
- let secrets = await this.generateSecrets(amounts)
- if (secrets.length != amounts.length) {
- throw new Error('number of secrets does not match number of outputs.')
- }
- let {blindedMessages, rs} = await this.constructOutputs(
- amounts,
- secrets
- )
- const payload = {
- amount,
- proofs,
- outputs: {
- blinded_messages: blindedMessages
- }
- }
-
- console.log('payload', JSON.stringify(payload))
-
- const {data} = await LNbits.api.request(
- 'POST',
- `/cashu/api/v1/${this.mintId}/split`,
- '',
- payload
- )
- const frst_rs = rs.slice(0, frst_amounts.length)
- const frst_secrets = secrets.slice(0, frst_amounts.length)
- const scnd_rs = rs.slice(frst_amounts.length)
- const scnd_secrets = secrets.slice(frst_amounts.length)
- const fristProofs = this.constructProofs(
- data.fst,
- frst_secrets,
- frst_rs
- )
- const scndProofs = this.constructProofs(data.snd, scnd_secrets, scnd_rs)
-
- return {fristProofs, scndProofs}
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- throw error
- }
- },
-
- redeem: async function () {
- this.showReceiveTokens = false
- console.log('### receive tokens', this.receiveData.tokensBase64)
- try {
- if (this.receiveData.tokensBase64.length == 0) {
- throw new Error('no tokens provided.')
- }
- const tokensJson = atob(this.receiveData.tokensBase64)
- const proofs = JSON.parse(tokensJson)
- const amount = proofs.reduce((s, t) => (s += t.amount), 0)
- let {fristProofs, scndProofs} = await this.split(proofs, amount)
- // HACK: we need to do this so the balance updates
- this.proofs = this.proofs.concat([])
- navigator.vibrate(200)
- this.$q.notify({
- timeout: 5000,
- type: 'positive',
- message: 'Tokens received'
- })
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- throw error
- }
- // }
- },
-
- sendTokens: async function () {
- // keep firstProofs, send scndProofs
- let {fristProofs, scndProofs} = await this.splitToSend(
- this.proofs,
- this.sendData.amount,
- true
- )
- this.sendData.tokens = ''
- this.sendData.tokensBase64 = ''
- this.sendData.tokens = scndProofs
- console.log('### this.sendData.tokens', this.sendData.tokens)
- this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
- navigator.vibrate(200)
- },
- checkFees: async function (payment_request) {
- const payload = {
- pr: payment_request
- }
- console.log('#### payload', JSON.stringify(payload))
- try {
- const {data} = await LNbits.api.request(
- 'POST',
- `/cashu/api/v1/${this.mintId}/checkfees`,
- '',
- payload
- )
- console.log('#### checkFees', payment_request, data.fee)
- return data.fee
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- throw error
- }
- },
- melt: async function () {
- // todo: get fees from server and add to inputs
- console.log('#### pay lightning')
- const amount_invoice = this.payInvoiceData.invoice.sat
- const amount =
- amount_invoice +
- (await this.checkFees(this.payInvoiceData.data.request))
- console.log(
- '#### amount invoice',
- amount_invoice,
- 'amount with fees',
- amount
- )
- // if (amount > balance()) {
- // LNbits.utils.notifyApiError('Balance too low')
- // return
- // }
- let {fristProofs, scndProofs} = await this.splitToSend(
- this.proofs,
- amount
- )
- const payload = {
- proofs: scndProofs.flat(),
- amount,
- invoice: this.payInvoiceData.data.request
- }
- console.log('#### payload', JSON.stringify(payload))
- try {
- const {data} = await LNbits.api.request(
- 'POST',
- `/cashu/api/v1/${this.mintId}/melt`,
- '',
- payload
- )
- navigator.vibrate(200)
- this.$q.notify({
- timeout: 5000,
- type: 'positive',
- message: 'Invoice paid'
- })
- // delete tokens from db
- this.proofs = fristProofs
- // add new fristProofs, scndProofs to this.proofs
- this.storeProofs()
- console.log({
- amount: -amount,
- bolt11: this.payInvoiceData.data.request,
- hash: this.payInvoiceData.data.hash,
- memo: this.payInvoiceData.data.memo
- })
- this.invoicesCashu.push({
- amount: -amount,
- bolt11: this.payInvoiceData.data.request,
- hash: this.payInvoiceData.data.hash,
- memo: this.payInvoiceData.data.memo,
- date: currentDateStr(),
- status: 'paid'
- })
- this.storeinvoicesCashu()
- this.tab = 'invoices'
-
- this.payInvoiceData.invoice = false
- this.payInvoiceData.show = false
- } catch (error) {
- console.error(error)
- LNbits.utils.notifyApiError(error)
- throw error
- }
- },
-
- recheckPendingInvoices: async function () {
- for (const invoice of this.invoicesCashu) {
- if (invoice.status === 'pending' && invoice.sat > 0) {
- this.recheckInvoice(invoice.hash, false)
- }
- }
- },
-
- fetchMintKeys: async function () {
- const {data} = await LNbits.api.request(
- 'GET',
- `/cashu/api/v1/${this.mintId}/keys`
- )
- this.keys = data
- localStorage.setItem(
- this.mintKey(this.mintId, 'keys'),
- JSON.stringify(data)
- )
- },
-
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
- ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-
- findTokenForAmount: function (amount) {
- for (const token of this.proofs) {
- const index = token.promises?.findIndex(p => p.amount === amount)
- if (index >= 0) {
- return {
- promise: token.promises[index],
- secret: token.secrets[index],
- r: token.rs[index]
- }
- }
- }
- },
-
- checkInvoice: function () {
- console.log('#### checkInvoice')
- try {
- const invoice = decode(this.payInvoiceData.data.request)
-
- const cleanInvoice = {
- msat: invoice.human_readable_part.amount,
- sat: invoice.human_readable_part.amount / 1000,
- fsat: LNbits.utils.formatSat(
- invoice.human_readable_part.amount / 1000
- )
- }
-
- _.each(invoice.data.tags, tag => {
- if (_.isObject(tag) && _.has(tag, 'description')) {
- if (tag.description === 'payment_hash') {
- cleanInvoice.hash = tag.value
- } else if (tag.description === 'description') {
- cleanInvoice.description = tag.value
- } else if (tag.description === 'expiry') {
- var expireDate = new Date(
- (invoice.data.time_stamp + tag.value) * 1000
- )
- cleanInvoice.expireDate = Quasar.utils.date.formatDate(
- expireDate,
- 'YYYY-MM-DDTHH:mm:ss.SSSZ'
- )
- cleanInvoice.expired = false // TODO
- }
- }
-
- this.payInvoiceData.invoice = cleanInvoice
- })
-
- console.log(
- '#### this.payInvoiceData.invoice',
- this.payInvoiceData.invoice
- )
- } catch (error) {
- this.$q.notify({
- timeout: 5000,
- type: 'warning',
- message: 'Could not decode invoice',
- caption: error + ''
- })
- throw error
- }
- },
-
- storeinvoicesCashu: function () {
- localStorage.setItem(
- this.mintKey(this.mintId, 'invoicesCashu'),
- JSON.stringify(this.invoicesCashu)
- )
- },
- storeProofs: function () {
- localStorage.setItem(
- this.mintKey(this.mintId, 'proofs'),
- JSON.stringify(this.proofs, bigIntStringify)
- )
- },
-
- mintKey: function (mintId, key) {
- // returns a key for the local storage
- // depending on the current mint
- return 'cashu.' + mintId + '.' + key
- }
- },
- watch: {
- payments: function () {
- this.balance()
- }
- },
-
- created: function () {
- let params = new URL(document.location).searchParams
-
- // get mint
- if (params.get('mint_id')) {
- this.mintId = params.get('mint_id')
- this.$q.localStorage.set('cashu.mint', params.get('mint_id'))
- } else if (this.$q.localStorage.getItem('cashu.mint')) {
- this.mintId = this.$q.localStorage.getItem('cashu.mint')
- } else {
- this.$q.notify({
- color: 'red',
- message: 'No mint set!'
- })
- }
-
- // get name
- if (params.get('mint_name')) {
- this.mintName = params.get('mint_name')
- this.$q.localStorage.set(
- this.mintKey(this.mintId, 'mintName'),
- this.mintName
- )
- } else if (this.$q.localStorage.getItem('cashu.name')) {
- this.mintName = this.$q.localStorage.getItem('cashu.name')
- }
-
- // get ticker
- if (
- !params.get('tsh') &&
- !this.$q.localStorage.getItem(this.mintKey(this.mintId, 'tickershort'))
- ) {
- this.$q.localStorage.set(this.mintKey(this.mintId, 'tickershort'), 'sats')
- this.tickershort = 'sats'
- } else if (params.get('tsh')) {
- this.$q.localStorage.set(
- this.mintKey(this.mintId, 'tickershort'),
- params.get('tsh')
- )
- this.tickershort = params.get('tsh')
- } else if (
- this.$q.localStorage.getItem(this.mintKey(this.mintId, 'tickershort'))
- ) {
- this.tickershort = this.$q.localStorage.getItem(
- this.mintKey(this.mintId, 'tickershort')
- )
- }
-
- const keysJson = localStorage.getItem(this.mintKey(this.mintId, 'keys'))
- if (!keysJson) {
- this.fetchMintKeys()
- } else {
- this.keys = JSON.parse(keysJson)
- }
-
- this.invoicesCashu = JSON.parse(
- localStorage.getItem(this.mintKey(this.mintId, 'invoicesCashu')) || '[]'
- )
- this.proofs = JSON.parse(
- localStorage.getItem(this.mintKey(this.mintId, 'proofs')) || '[]'
- )
- console.log('### invoicesCashu', this.invoicesCashu)
- console.table('### tokens', this.proofs)
- console.log('#### this.mintId', this.mintId)
- console.log('#### this.mintName', this.mintName)
-
- this.recheckPendingInvoices()
- }
-})
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 7ec99437..88af72ca 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -625,5 +625,1107 @@
-
+
{% endblock %}
From 2e10e8985711d1715687c5a5f149bb4ceb8a49a7 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 19:12:13 +0100
Subject: [PATCH 361/696] check token history
---
.../cashu/templates/cashu/wallet.html | 388 +++++++++++++-----
1 file changed, 293 insertions(+), 95 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 88af72ca..6d86ff35 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -72,13 +72,19 @@
>
-
+
+
+
+
+
+
+
+
{{props.row.amount}}
-
+
{{props.row.date}}
@@ -173,10 +182,68 @@
{% endraw %}
+
+
- History
+
+ {% raw %}
+
+
+
+
+
+ Pending
+
+
+ Check
+
+
+
+ Received
+ Paid
+
+
+
+
+
+ {{props.row.amount}}
+
+
+
+ {{props.row.date}}
+
+
+ {{props.row.token}}
+
+
+
+ {% endraw %}
+
+
+
@@ -653,6 +720,7 @@
mintName: '',
keys: '',
invoicesCashu: [],
+ historyTokens: [],
invoiceData: {
amount: 0,
memo: '',
@@ -736,21 +804,23 @@
name: 'status',
align: 'left',
label: '',
- field: 'status'
+ field: 'status',
+ sortable: true
},
{
name: 'amount',
align: 'left',
label: 'Amount',
- field: 'amount'
- },
- {
- name: 'memo',
- align: 'left',
- label: 'Memo',
- field: 'memo',
+ field: 'amount',
sortable: true
},
+ // {
+ // name: 'memo',
+ // align: 'left',
+ // label: 'Memo',
+ // field: 'memo',
+ // sortable: true
+ // },
{
name: 'date',
align: 'left',
@@ -763,7 +833,7 @@
align: 'right',
label: 'Hash',
field: 'hash',
- sortable: true
+ sortable: false
}
],
pagination: {
@@ -811,6 +881,43 @@
filter: null
},
+ historyTable: {
+ columns: [
+ {
+ name: 'status',
+ align: 'left',
+ label: '',
+ field: 'status',
+ sortable: true
+ },
+ {
+ name: 'amount',
+ align: 'left',
+ label: 'Value ({{LNBITS_DENOMINATION}})',
+ field: 'amount',
+ sortable: true
+ },
+ {
+ name: 'date',
+ align: 'left',
+ label: 'Date',
+ field: 'date',
+ sortable: true
+ },
+ {
+ name: 'token',
+ align: 'left',
+ label: 'Token',
+ field: 'token',
+ sortable: false
+ }
+ ],
+ pagination: {
+ rowsPerPage: 5
+ },
+ filter: null
+ },
+
paymentsChart: {
show: false
},
@@ -1147,15 +1254,90 @@
},
//////////////////////// MINT //////////////////////////////////////////
+
+ generateSecrets: async function (amounts) {
+ const secrets = []
+ for (let i = 0; i < amounts.length; i++) {
+ const secret = nobleSecp256k1.utils.randomBytes(32)
+ secrets.push(secret)
+ }
+ return secrets
+ },
+
+ constructOutputs: async function (amounts, secrets) {
+ const blindedMessages = []
+ const rs = []
+ for (let i = 0; i < amounts.length; i++) {
+ const {B_, r} = await step1Alice(secrets[i])
+ blindedMessages.push({amount: amounts[i], B_: B_})
+ rs.push(r)
+ }
+ return {
+ blindedMessages,
+ rs
+ }
+ },
+
+ constructProofs: function (promises, secrets, rs) {
+ const proofs = []
+ for (let i = 0; i < promises.length; i++) {
+ const encodedSecret = uint8ToBase64.encode(secrets[i])
+ let {id, amount, C, secret} = this.promiseToProof(
+ promises[i].id,
+ promises[i].amount,
+ promises[i]['C_'],
+ encodedSecret,
+ rs[i]
+ )
+ proofs.push({id, amount, C, secret})
+ }
+ return proofs
+ },
+
+ promiseToProof: function (id, amount, C_hex, secret, r) {
+ const C_ = nobleSecp256k1.Point.fromHex(C_hex)
+ const A = this.keys[amount]
+ const C = step3Alice(
+ C_,
+ nobleSecp256k1.utils.hexToBytes(r),
+ nobleSecp256k1.Point.fromHex(A)
+ )
+ return {
+ id,
+ amount,
+ C: C.toHex(true),
+ secret
+ }
+ },
+
+ sumProofs: function (proofs) {
+ return proofs.reduce((s, t) => (s += t.amount), 0)
+ },
+
+
+ //////////// API ///////////
+
requestMintButton: async function () {
await this.requestMint()
- console.log('this is your invoice BEFORE')
- console.log(this.invoiceData)
+ console.log("#### request mint", this.invoiceData)
+ let nInterval = 0
this.invoiceCheckListener = setInterval(async () => {
try {
- console.log('this is your invoice AFTER')
+ nInterval += 1
+
+ // exit loop after 5m
+ if (nInterval > 100) {
+ console.log("### stopping invoice check worker")
+ clearInterval(this.invoiceCheckListener)
+ }
+ console.log('### setInterval', nInterval)
console.log(this.invoiceData)
+
+ // this will throw an error if the invoice is pending
await this.recheckInvoice(this.invoiceData.hash, false)
+
+ // only without error (invoice paid) will we reach here
+ console.log("### stopping invoice check worker")
clearInterval(this.invoiceCheckListener)
this.invoiceData.bolt11 = ''
this.showInvoiceDetails = false
@@ -1242,81 +1424,6 @@
throw error
}
},
- setInvoicePaid: async function (payment_hash) {
- const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
- invoice.status = 'paid'
- this.storeinvoicesCashu()
- },
- recheckInvoice: async function (payment_hash, verbose = true) {
- console.log('### recheckInvoice.hash', payment_hash)
- const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
- try {
- proofs = await this.mint(invoice.amount, invoice.hash, verbose)
- return proofs
- } catch (error) {
- console.log('Invoice still pending')
- throw error
- }
- },
-
- generateSecrets: async function (amounts) {
- const secrets = []
- for (let i = 0; i < amounts.length; i++) {
- const secret = nobleSecp256k1.utils.randomBytes(32)
- secrets.push(secret)
- }
- return secrets
- },
-
- constructOutputs: async function (amounts, secrets) {
- const blindedMessages = []
- const rs = []
- for (let i = 0; i < amounts.length; i++) {
- const {B_, r} = await step1Alice(secrets[i])
- blindedMessages.push({amount: amounts[i], B_: B_})
- rs.push(r)
- }
- return {
- blindedMessages,
- rs
- }
- },
-
- constructProofs: function (promises, secrets, rs) {
- const proofs = []
- for (let i = 0; i < promises.length; i++) {
- const encodedSecret = uint8ToBase64.encode(secrets[i])
- let {id, amount, C, secret} = this.promiseToProof(
- promises[i].id,
- promises[i].amount,
- promises[i]['C_'],
- encodedSecret,
- rs[i]
- )
- proofs.push({id, amount, C, secret})
- }
- return proofs
- },
-
- promiseToProof: function (id, amount, C_hex, secret, r) {
- const C_ = nobleSecp256k1.Point.fromHex(C_hex)
- const A = this.keys[amount]
- const C = step3Alice(
- C_,
- nobleSecp256k1.utils.hexToBytes(r),
- nobleSecp256k1.Point.fromHex(A)
- )
- return {
- id,
- amount,
- C: C.toHex(true),
- secret
- }
- },
-
- sumProofs: function (proofs) {
- return proofs.reduce((s, t) => (s += t.amount), 0)
- },
splitToSend: async function (proofs, amount, invlalidate = false) {
// splits proofs so the user can keep firstProofs, send scndProofs
try {
@@ -1435,12 +1542,21 @@
if (this.receiveData.tokensBase64.length == 0) {
throw new Error('no tokens provided.')
}
- const tokensJson = atob(this.receiveData.tokensBase64)
- const proofs = JSON.parse(tokensJson)
+ const tokenJson = atob(this.receiveData.tokensBase64)
+ const proofs = JSON.parse(tokenJson)
const amount = proofs.reduce((s, t) => (s += t.amount), 0)
let {fristProofs, scndProofs} = await this.split(proofs, amount)
// HACK: we need to do this so the balance updates
this.proofs = this.proofs.concat([])
+
+ this.historyTokens.push({
+ status: 'paid',
+ amount: amount,
+ date: currentDateStr(),
+ token: this.receiveData.tokensBase64
+ })
+ this.storehistoryTokens()
+
navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
@@ -1467,6 +1583,15 @@
this.sendData.tokens = scndProofs
console.log('### this.sendData.tokens', this.sendData.tokens)
this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
+
+ this.historyTokens.push({
+ status: 'pending',
+ amount: -this.sendData.amount,
+ date: currentDateStr(),
+ token: this.sendData.tokensBase64
+ })
+ this.storehistoryTokens()
+
navigator.vibrate(200)
},
checkFees: async function (payment_request) {
@@ -1559,7 +1684,22 @@
throw error
}
},
-
+ setInvoicePaid: async function (payment_hash) {
+ const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
+ invoice.status = 'paid'
+ this.storeinvoicesCashu()
+ },
+ recheckInvoice: async function (payment_hash, verbose = true) {
+ console.log('### recheckInvoice.hash', payment_hash)
+ const invoice = this.invoicesCashu.find(i => i.hash === payment_hash)
+ try {
+ proofs = await this.mint(invoice.amount, invoice.hash, verbose)
+ return proofs
+ } catch (error) {
+ console.log('Invoice still pending')
+ throw error
+ }
+ },
recheckPendingInvoices: async function () {
for (const invoice of this.invoicesCashu) {
if (invoice.status === 'pending' && invoice.sat > 0) {
@@ -1567,7 +1707,53 @@
}
}
},
-
+ setTokenPaid: async function (token) {
+ const invoice = this.historyTokens.find(i => i.token === token)
+ invoice.status = 'paid'
+ this.storehistoryTokens()
+ },
+ checkTokenSpendable: async function(token) {
+ const tokenJson = atob(token)
+ const proofs = JSON.parse(tokenJson)
+ const payload = {
+ proofs: proofs.flat(),
+ }
+ console.log('#### payload', JSON.stringify(payload))
+ try {
+ const {data} = await LNbits.api.request(
+ 'POST',
+ `/cashu/api/v1/${this.mintId}/check`,
+ '',
+ payload
+ )
+ // iterate through response of form {0: true, 1: false, ...}
+ let paid = false
+ for (const [key, spendable] of Object.entries(data)) {
+ if (!spendable){
+ this.setTokenPaid(token)
+ paid = true
+ }
+ }
+ if (paid){
+ navigator.vibrate(200)
+ this.$q.notify({
+ timeout: 5000,
+ type: 'positive',
+ message: 'Token sent'
+ })
+ } else {
+ this.$q.notify({
+ timeout: 5000,
+ color: 'gray',
+ message: 'Token still pending'
+ })
+ }
+ } catch (error) {
+ console.error(error)
+ LNbits.utils.notifyApiError(error)
+ throw error
+ }
+ },
fetchMintKeys: async function () {
const {data} = await LNbits.api.request(
'GET',
@@ -1577,6 +1763,7 @@
localStorage.setItem(this.mintKey(this.mintId, 'keys'), JSON.stringify(data))
},
+
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -1650,6 +1837,12 @@
JSON.stringify(this.invoicesCashu)
)
},
+ storehistoryTokens: function () {
+ localStorage.setItem(
+ this.mintKey(this.mintId, 'historyTokens'),
+ JSON.stringify(this.historyTokens)
+ )
+ },
storeProofs: function () {
localStorage.setItem(
this.mintKey(this.mintId, 'proofs'),
@@ -1718,6 +1911,11 @@
this.invoicesCashu = JSON.parse(
localStorage.getItem(this.mintKey(this.mintId, 'invoicesCashu')) || '[]'
)
+
+ this.historyTokens = JSON.parse(
+ localStorage.getItem(this.mintKey(this.mintId, 'historyTokens')) || '[]'
+ )
+
this.proofs = JSON.parse(localStorage.getItem(this.mintKey(this.mintId, 'proofs')) || '[]')
console.log('### invoicesCashu', this.invoicesCashu)
console.table('### tokens', this.proofs)
From 28b8087458380fdc9babefcddd16121e88735efa Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 20:00:24 +0100
Subject: [PATCH 362/696] invoice and token check worker
---
.../cashu/templates/cashu/wallet.html | 76 ++++++++++++++++---
1 file changed, 65 insertions(+), 11 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 6d86ff35..c67f25e1 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -201,7 +201,7 @@
@@ -913,6 +913,8 @@
}
],
pagination: {
+ sortBy: 'date',
+ descending: true,
rowsPerPage: 5
},
filter: null
@@ -1230,6 +1232,8 @@
console.log('##### showInvoiceDialog')
this.invoiceData = _.clone(data)
this.showInvoiceDetails = true
+ // kick off invoice check worker
+ this.invoiceCheckWorker()
},
showPayInvoiceDialog: function () {
@@ -1240,7 +1244,18 @@
this.payInvoiceData.camera.show = false
},
+ showTokenDialog: function (token) {
+ console.log('##### showTokenDialog')
+ // TODO: this must be decoded and desiarlized!
+ this.sendData.tokens = _.clone(token)
+ this.sendData.tokensBase64 = _.clone(token)
+ this.showSendTokens = true
+ // kick off token check worker
+ this.checkTokenSpendableWorker()
+ },
+
showSendTokensDialog: function () {
+ console.log('##### showSendTokensDialog')
this.sendData.tokens = ''
this.sendData.tokensBase64 = ''
this.sendData.amount = 0
@@ -1317,9 +1332,7 @@
//////////// API ///////////
- requestMintButton: async function () {
- await this.requestMint()
- console.log("#### request mint", this.invoiceData)
+ invoiceCheckWorker: async function () {
let nInterval = 0
this.invoiceCheckListener = setInterval(async () => {
try {
@@ -1353,6 +1366,12 @@
}, 3000)
},
+ requestMintButton: async function () {
+ await this.requestMint()
+ console.log("#### request mint", this.invoiceData)
+ await this.invoiceCheckWorker()
+ },
+
requestMint: async function () {
// gets an invoice from the mint to get new tokens
try {
@@ -1578,8 +1597,6 @@
this.sendData.amount,
true
)
- this.sendData.tokens = ''
- this.sendData.tokensBase64 = ''
this.sendData.tokens = scndProofs
console.log('### this.sendData.tokens', this.sendData.tokens)
this.sendData.tokensBase64 = btoa(JSON.stringify(this.sendData.tokens))
@@ -1591,8 +1608,7 @@
token: this.sendData.tokensBase64
})
this.storehistoryTokens()
-
- navigator.vibrate(200)
+ this.checkTokenSpendableWorker()
},
checkFees: async function (payment_request) {
const payload = {
@@ -1712,7 +1728,38 @@
invoice.status = 'paid'
this.storehistoryTokens()
},
- checkTokenSpendable: async function(token) {
+
+
+ checkTokenSpendableWorker: async function () {
+ let nInterval = 0
+ this.tokensCheckSpendableListener = setInterval(async () => {
+ try {
+ nInterval += 1
+
+ // exit loop after 5m
+ if (nInterval > 100) {
+ console.log("### stopping token check worker")
+ clearInterval(this.tokensCheckSpendableListener)
+ }
+ console.log('### setInterval', nInterval)
+ console.log(this.sendData)
+
+ // this will throw an error if the invoice is pending
+ paid = await this.checkTokenSpendable(this.sendData.tokensBase64, false)
+ if (paid) {
+ console.log("### stopping token check worker")
+ clearInterval(this.tokensCheckSpendableListener)
+ this.sendData.tokens = ''
+ this.showSendTokens = false
+ }
+
+ } catch (error) {
+ console.log('not paid yet')
+ }
+ }, 3000)
+ },
+
+ checkTokenSpendable: async function(token, verbose=true) {
const tokenJson = atob(token)
const proofs = JSON.parse(tokenJson)
const payload = {
@@ -1735,25 +1782,32 @@
}
}
if (paid){
+ console.log("### token paid")
navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
- message: 'Token sent'
+ message: 'Token paid'
})
} else {
- this.$q.notify({
+ console.log("### token not paid yet")
+ if (verbose) {
+ this.$q.notify({
timeout: 5000,
color: 'gray',
message: 'Token still pending'
})
+ }
+ this.sendData.tokens = token
}
+ return paid
} catch (error) {
console.error(error)
LNbits.utils.notifyApiError(error)
throw error
}
},
+
fetchMintKeys: async function () {
const {data} = await LNbits.api.request(
'GET',
From d9a8da10f0ea8f1afe585a8749bf3887ae508af5 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 20:05:57 +0100
Subject: [PATCH 363/696] cols
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index c67f25e1..00db14f5 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -8,7 +8,7 @@
-
+
Receive invoice
-
+
{% raw %} {{getBalance()}}
@@ -28,7 +28,7 @@
-
+
Date: Sat, 5 Nov 2022 20:16:17 +0100
Subject: [PATCH 364/696] error handling
---
.../extensions/cashu/templates/cashu/wallet.html | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 00db14f5..f040d0fa 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1448,7 +1448,12 @@
try {
const spendableProofs = proofs.filter(p => !p.reserved)
if (this.sumProofs(spendableProofs) < amount) {
- throw new Error('balance too low.')
+ this.$q.notify({
+ timeout: 5000,
+ type: 'warning',
+ message: 'Balance too low'
+ })
+ throw Error('balance too low.')
}
let {fristProofs, scndProofs} = await this.split(
spendableProofs,
@@ -1591,6 +1596,7 @@
},
sendTokens: async function () {
+ try {
// keep firstProofs, send scndProofs
let {fristProofs, scndProofs} = await this.splitToSend(
this.proofs,
@@ -1609,6 +1615,11 @@
})
this.storehistoryTokens()
this.checkTokenSpendableWorker()
+ } catch (error) {
+ console.error(error)
+ throw error
+ }
+
},
checkFees: async function (payment_request) {
const payload = {
@@ -1794,7 +1805,7 @@
if (verbose) {
this.$q.notify({
timeout: 5000,
- color: 'gray',
+ color: 'grey',
message: 'Token still pending'
})
}
From 4eff5564031f34c0021f260608f25bbef9cbd39f Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 20:49:11 +0100
Subject: [PATCH 365/696] show bolt11
---
.../cashu/templates/cashu/wallet.html | 100 ++++++++++--------
1 file changed, 55 insertions(+), 45 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index f040d0fa..3fa7fe56 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -174,6 +174,9 @@
{{props.row.date}}
+
+ {{props.row.bolt11}}
+
{{props.row.hash}}
@@ -798,51 +801,6 @@
}
},
payments: [],
- invoicesTable: {
- columns: [
- {
- name: 'status',
- align: 'left',
- label: '',
- field: 'status',
- sortable: true
- },
- {
- name: 'amount',
- align: 'left',
- label: 'Amount',
- field: 'amount',
- sortable: true
- },
- // {
- // name: 'memo',
- // align: 'left',
- // label: 'Memo',
- // field: 'memo',
- // sortable: true
- // },
- {
- name: 'date',
- align: 'left',
- label: 'Date',
- field: 'date',
- sortable: true
- },
- {
- name: 'hash',
- align: 'right',
- label: 'Hash',
- field: 'hash',
- sortable: false
- }
- ],
- pagination: {
- sortBy: 'date',
- descending: true,
- rowsPerPage: 5
- },
- filter: null
- },
tokensTable: {
columns: [
@@ -880,6 +838,58 @@
},
filter: null
},
+ invoicesTable: {
+ columns: [
+ {
+ name: 'status',
+ align: 'left',
+ label: '',
+ field: 'status',
+ sortable: true
+ },
+ {
+ name: 'amount',
+ align: 'left',
+ label: 'Amount',
+ field: 'amount',
+ sortable: true
+ },
+ // {
+ // name: 'memo',
+ // align: 'left',
+ // label: 'Memo',
+ // field: 'memo',
+ // sortable: true
+ // },
+ {
+ name: 'date',
+ align: 'left',
+ label: 'Date',
+ field: 'date',
+ sortable: true
+ },
+ {
+ name: 'bolt11',
+ align: 'left',
+ label: 'Payment request',
+ field: 'bolt11',
+ sortable: false
+ },
+ {
+ name: 'hash',
+ align: 'left',
+ label: 'Hash',
+ field: 'hash',
+ sortable: false
+ }
+ ],
+ pagination: {
+ sortBy: 'date',
+ descending: true,
+ rowsPerPage: 5
+ },
+ filter: null
+ },
historyTable: {
columns: [
From aa38b79b8175fcb4512d123c521b7df9eae3fa4c Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 20:52:52 +0100
Subject: [PATCH 366/696] sort fix
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 3fa7fe56..bbab1c1c 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -903,7 +903,7 @@
{
name: 'amount',
align: 'left',
- label: 'Value ({{LNBITS_DENOMINATION}})',
+ label: 'Amount',
field: 'amount',
sortable: true
},
@@ -982,7 +982,7 @@
return acc
}, {})
return Object.keys(x).map(k => ({
- value: k,
+ value: parseInt(k),
count: x[k],
sum: k * x[k]
}))
From 509cf45f67aa5b100567f8206e43809e37a05b88 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 21:01:46 +0100
Subject: [PATCH 367/696] remove arrows
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 --
1 file changed, 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index bbab1c1c..20f51c5c 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -50,7 +50,6 @@
Date: Sat, 5 Nov 2022 21:09:00 +0100
Subject: [PATCH 368/696] warning page
---
.../cashu/templates/cashu/wallet.html | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 20f51c5c..e247c17e 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -492,15 +492,18 @@
Warning
Bookmark this page!
-
-
Ecash is a bearer asset, meaning losing access to this wallet will mean you will
- lose the funds. This wallet stores tokens in its database. If you lose the link or request
- your data, you will lose your tokens. Bookmark this page or press the
- hamburger icon and add this page to your home screen.
+ lose the funds. This wallet stores tokens in its database. If you lose the link or delete your
+ your browser data, you will lose your tokens. Bookmark this page.
-
- This service is in BETA, and we hold no responsibility for people losing
+
+ Add to home screen.
+ You can add Cashu to your home screen as a progressive web app (PWA).
+ On Android Chrome, click the hamburger menu at the upper right.
+ On iOS Safari, click the share button.
+
+
+ This service is in BETA! We hold no responsibility for people losing
access to funds. Use at your own risk!
From aa259103677306385f30a94d98711ec669de6086 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 22:08:15 +0100
Subject: [PATCH 369/696] input fields
---
.../cashu/templates/cashu/wallet.html | 23 +++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index e247c17e..381d6351 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -526,6 +526,7 @@
+
+
+
+
+
+
+
+
@@ -1232,7 +1247,7 @@
/////////////////////////////////// WALLET ///////////////////////////////////
showInvoicesDialog: async function () {
console.log('##### showInvoicesDialog')
- this.invoiceData.amount = 0
+ this.invoiceData.amount = ''
this.invoiceData.bolt11 = ''
this.invoiceData.hash = ''
this.invoiceData.memo = ''
@@ -1269,7 +1284,7 @@
console.log('##### showSendTokensDialog')
this.sendData.tokens = ''
this.sendData.tokensBase64 = ''
- this.sendData.amount = 0
+ this.sendData.amount = ''
this.sendData.memo = ''
this.showSendTokens = true
},
From 00a3e12232b7cd724b844950ea99d3c2dde62050 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 22:12:23 +0100
Subject: [PATCH 370/696] buttons flat
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 381d6351..3c781ac3 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -445,7 +445,7 @@
color="primary"
:disable="payInvoiceData.data.request == ''"
type="submit"
- outline
+ flat
>Continue
@@ -563,7 +563,7 @@
color="primary"
>Copy invoice
-
Create Invoice
Send Tokens
@@ -673,7 +674,7 @@
-
Receive Tokens
Date: Sat, 5 Nov 2022 22:32:36 +0100
Subject: [PATCH 371/696] backup button
---
.../cashu/templates/cashu/wallet.html | 43 ++++++++++++++++++-
1 file changed, 41 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 3c781ac3..be4fd126 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -248,13 +248,22 @@
-
@@ -511,6 +520,7 @@
I understand
+
@@ -1923,6 +1933,36 @@
}
},
+ ////////////// STORAGE /////////////
+
+ getLocalstorageToFile: async function() {
+ // https://stackoverflow.com/questions/24263682/save-restore-local-storage-to-a-local-file
+ const fileName = `cashu_backup_${currentDateStr()}.json`
+ var a = {};
+ for (var i = 0; i < localStorage.length; i++) {
+ var k = localStorage.key(i);
+ var v = localStorage.getItem(k);
+ a[k] = v;
+ }
+ var textToSave = JSON.stringify(a)
+ var textToSaveAsBlob = new Blob([textToSave], {
+ type: "text/plain"
+ });
+ var textToSaveAsURL = window.URL.createObjectURL(textToSaveAsBlob);
+
+ var downloadLink = document.createElement("a");
+ downloadLink.download = fileName;
+ downloadLink.innerHTML = "Download File";
+ downloadLink.href = textToSaveAsURL;
+ downloadLink.onclick = function () {
+ document.body.removeChild(event.target);
+ };
+ downloadLink.style.display = "none";
+ document.body.appendChild(downloadLink);
+ downloadLink.click();
+
+ },
+
storeinvoicesCashu: function () {
localStorage.setItem(
this.mintKey(this.mintId, 'invoicesCashu'),
@@ -2013,7 +2053,6 @@
console.table('### tokens', this.proofs)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
-
this.recheckPendingInvoices()
}
})
From b7aeab57ae90f92906051c8a13e59380b882a2e6 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 22:35:07 +0100
Subject: [PATCH 372/696] tooltip
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index be4fd126..b6919e85 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -256,14 +256,14 @@
color="warning"
outline
@click="showDisclaimerDialog"> Warning
-
Backup
+ tooltip="asd"
+ @click="getLocalstorageToFile">BackupDownload wallet backup
From 45519f8339583bcbbb57897e7de8f7b2c39c9ef5 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 23:12:25 +0100
Subject: [PATCH 373/696] block button
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index b6919e85..7eb81c9d 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -307,7 +307,7 @@
{% endraw %}
-
Pay
+
Pay
Cancel
@@ -759,6 +759,7 @@
},
invoiceCheckListener: () => {},
payInvoiceData: {
+ blocking: false,
// invoice: '',
bolt11: '',
// camera: {
@@ -1590,6 +1591,7 @@
return {fristProofs, scndProofs}
} catch (error) {
+ this.payInvoiceData.blocking = false
console.error(error)
LNbits.utils.notifyApiError(error)
throw error
@@ -1680,6 +1682,8 @@
},
melt: async function () {
// todo: get fees from server and add to inputs
+ this.payInvoiceData.blocking = true
+
console.log('#### pay lightning')
const amount_invoice = this.payInvoiceData.invoice.sat
const amount =
@@ -1718,9 +1722,8 @@
type: 'positive',
message: 'Invoice paid'
})
- // delete tokens from db
+ // delete spent tokens from db
this.proofs = fristProofs
- // add new fristProofs, scndProofs to this.proofs
this.storeProofs()
console.log({
amount: -amount,
@@ -1741,8 +1744,10 @@
this.payInvoiceData.invoice = false
this.payInvoiceData.show = false
+ this.payInvoiceData.blocking = false
} catch (error) {
+ this.payInvoiceData.blocking = false
console.error(error)
LNbits.utils.notifyApiError(error)
throw error
From a89a915e7a1db42c5b70a79e38bd00a98be86e91 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 5 Nov 2022 23:50:31 +0100
Subject: [PATCH 374/696] vibrate check
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 7eb81c9d..b7b5334f 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1392,7 +1392,7 @@
clearInterval(this.invoiceCheckListener)
this.invoiceData.bolt11 = ''
this.showInvoiceDetails = false
- navigator.vibrate(200)
+ if (window.navigator.vibrate) navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
@@ -1620,7 +1620,7 @@
})
this.storehistoryTokens()
- navigator.vibrate(200)
+ if (window.navigator.vibrate) navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
@@ -1716,7 +1716,7 @@
'',
payload
)
- navigator.vibrate(200)
+ if (window.navigator.vibrate) navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
@@ -1836,7 +1836,7 @@
}
if (paid){
console.log("### token paid")
- navigator.vibrate(200)
+ if (window.navigator.vibrate) navigator.vibrate(200)
this.$q.notify({
timeout: 5000,
type: 'positive',
From a030b164712055962c4c5a4e787d0ddcdcfec154 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 00:04:24 +0100
Subject: [PATCH 375/696] add seconds
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index b7b5334f..25964cf0 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -724,12 +724,12 @@
From f698074f8983c1e85d55c6c44e3d0b01bb8c9e3f Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 12:48:33 +0100
Subject: [PATCH 393/696] auto check
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 --
1 file changed, 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index cc558e93..2b2462e4 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1847,9 +1847,7 @@ page_container %}
recheckPendingTokens: async function () {
for (const token of this.historyTokens) {
- console.log(token.status, token.amount)
if (token.status === 'pending' && token.amount < 0) {
- print('CHEKABLE')
this.checkTokenSpendable(token.token, false)
}
}
From a6669fa78bef53e27d856bee8b2a5c82118f8ed7 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 13:03:12 +0100
Subject: [PATCH 394/696] check if token already received
---
.../extensions/cashu/templates/cashu/wallet.html | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 2b2462e4..7a056644 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -2149,8 +2149,18 @@ page_container %}
// get recv_token
if (params.get('recv_token')) {
- this.receiveData.tokensBase64 = params.get('recv_token')
- this.showReceiveTokens = true
+ tokenBase64 = params.get('recv_token')
+ let seen = false
+ for (var i = 0; i < this.historyTokens.length; i++) {
+ var thisToken = this.historyTokens[i].token
+ if (thisToken == tokenBase64) {
+ seen = true
+ }
+ }
+ if (!seen) {
+ this.receiveData.tokensBase64 = params.get('recv_token')
+ this.showReceiveTokens = true
+ }
}
console.log('### invoicesCashu', this.invoicesCashu)
From 2ee2de6694c6eec4a398acf3fa5b3c9c1ae3af15 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 13:03:57 +0100
Subject: [PATCH 395/696] check if token already received
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 7a056644..86889bcb 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -2153,7 +2153,7 @@ page_container %}
let seen = false
for (var i = 0; i < this.historyTokens.length; i++) {
var thisToken = this.historyTokens[i].token
- if (thisToken == tokenBase64) {
+ if (thisToken == tokenBase64 && this.historyTokens[i].amount > 0) {
seen = true
}
}
From 72455e4b926bba94bf0dd207086dfdd38f316b00 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 13:12:59 +0100
Subject: [PATCH 396/696] logging
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 --
1 file changed, 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 86889bcb..23762558 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1070,14 +1070,12 @@ page_container %}
.reduce((sum, el) => (sum += el.amount), 0)
},
getTokenList: function () {
- console.log(this.proofs)
const amounts = this.proofs.map(t => t.amount)
const counts = {}
for (const num of amounts) {
counts[num] = counts[num] ? counts[num] + 1 : 1
}
- console.log('counts', counts)
return Object.keys(counts).map(k => ({
value: parseInt(k),
count: parseInt(counts[k]),
From e878b66d7ac6b70a282fb3ecbc3c2b91ed29c1af Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 13:31:02 +0100
Subject: [PATCH 397/696] link in QR
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 23762558..5cf3e2da 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -684,7 +684,7 @@ page_container %}
From 59de402af9fd7143bacc6862bc248555462fe05e Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 17:45:17 +0100
Subject: [PATCH 398/696] fix scroll
---
.../cashu/templates/cashu/wallet.html | 22 +++++++++----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 5cf3e2da..07700d05 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -5,7 +5,7 @@ page_container %}
-
+
@@ -67,9 +67,10 @@ page_container %}
rectangle
unelevated
color="primary"
+ icon="file_download"
class="full-width"
@click="showReceiveTokensDialog"
- >Receive TokensGet Tokens
@@ -79,10 +80,11 @@ page_container %}
rectangle
unelevated
color="primary"
+ icon="file_upload"
class="full-width"
@click="showSendTokensDialog"
>
- Send Tokens
@@ -280,7 +282,7 @@ page_container %}
-
+
BackupDownload wallet backup
@@ -308,22 +308,22 @@ page_container %}
From 3f55866c0c1be0dadd1914341d16541254ff0e1e Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 17:46:35 +0100
Subject: [PATCH 399/696] faster
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 07700d05..06d9588f 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1884,7 +1884,7 @@ page_container %}
} catch (error) {
console.log('not paid yet')
}
- }, 5000)
+ }, 3000)
},
checkTokenSpendable: async function (token, verbose = true) {
From 527eef428192054300594476e4978c6749a31fb3 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 17:48:42 +0100
Subject: [PATCH 400/696] ecash
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 06d9588f..0ddb2f0a 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -70,7 +70,7 @@ page_container %}
icon="file_download"
class="full-width"
@click="showReceiveTokensDialog"
- >Get TokensGet Ecash
@@ -84,7 +84,7 @@ page_container %}
class="full-width"
@click="showSendTokensDialog"
>
- Pay Tokens
From cd3fe35b051cee47953ef8b2f799e129574d467c Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 18:01:54 +0100
Subject: [PATCH 401/696] update height
---
.../cashu/templates/cashu/wallet.html | 18 ++++++++++++++++--
1 file changed, 16 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 0ddb2f0a..9928b26a 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -5,7 +5,7 @@ page_container %}
-
+
@@ -315,7 +315,7 @@ page_container %}
@@ -2161,6 +2161,20 @@ page_container %}
}
}
+ var body = document.body,
+ html = document.documentElement
+
+ var height = Math.max(
+ body.scrollHeight,
+ body.offsetHeight,
+ html.clientHeight,
+ html.scrollHeight,
+ html.offsetHeight
+ )
+
+ console.log('height', height)
+ this.height = height
+
console.log('### invoicesCashu', this.invoicesCashu)
console.table('### tokens', this.proofs)
console.log('#### this.mintId', this.mintId)
From 7b12bf27bafbd627f56df28613779a679158cf46 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 18:37:40 +0100
Subject: [PATCH 402/696] label
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 9928b26a..9b49a063 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -18,7 +18,7 @@ page_container %}
rectangle
color="primary"
@click="showInvoicesDialog"
- >Receive invoice
+ >Get invoice
From 2444c1d1cc3dedd30e68637fdba5b36f65610115 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 19:09:08 +0100
Subject: [PATCH 403/696] new icons
---
lnbits/extensions/cashu/views.py | 141 ++++++++++++++++++++++++++++---
1 file changed, 127 insertions(+), 14 deletions(-)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 954936b9..a1469d0a 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -61,12 +61,12 @@ async def manifest(cashu_id: str):
"name": "Cashu" + " - " + cashu.name,
"icons": [
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-512-512.png",
"type": "image/png",
"sizes": "512x512",
},
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-96-96.png",
"type": "image/png",
"sizes": "96x96",
},
@@ -85,19 +85,132 @@ async def manifest(cashu_id: str):
"url": "/cashu/wallet?mint_id=" + cashu_id,
"icons": [
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
- "sizes": "96x96",
- "type": "image/png",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/180x180.png",
- "sizes": "180x180",
- "type": "image/png",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-512-512.png",
"sizes": "512x512",
- "type": "image/png",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-192-192.png",
+ "sizes": "192x192",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-144-144.png",
+ "sizes": "144x144",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-96-96.png",
+ "sizes": "96x96",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-72-72.png",
+ "sizes": "72x72",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-48-48.png",
+ "sizes": "48x48",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/16.png",
+ "sizes": "16x16",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/20.png",
+ "sizes": "20x20",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/29.png",
+ "sizes": "29x29",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/32.png",
+ "sizes": "32x32",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/40.png",
+ "sizes": "40x40",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/50.png",
+ "sizes": "50x50",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/57.png",
+ "sizes": "57x57",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/58.png",
+ "sizes": "58x58",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/60.png",
+ "sizes": "60x60",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/64.png",
+ "sizes": "64x64",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/72.png",
+ "sizes": "72x72",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/76.png",
+ "sizes": "76x76",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/80.png",
+ "sizes": "80x80",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/87.png",
+ "sizes": "87x87",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/100.png",
+ "sizes": "100x100",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/114.png",
+ "sizes": "114x114",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/120.png",
+ "sizes": "120x120",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/128.png",
+ "sizes": "128x128",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/144.png",
+ "sizes": "144x144",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/152.png",
+ "sizes": "152x152",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/167.png",
+ "sizes": "167x167",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/180.png",
+ "sizes": "180x180",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/192.png",
+ "sizes": "192x192",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/256.png",
+ "sizes": "256x256",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/512.png",
+ "sizes": "512x512",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/1024.png",
+ "sizes": "1024x1024",
},
],
}
From 113c510f4764ea98ef46b9ec435edefab695a7a4 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 19:11:54 +0100
Subject: [PATCH 404/696] fix nanifest
---
lnbits/extensions/cashu/views.py | 129 ++-----------------------------
1 file changed, 8 insertions(+), 121 deletions(-)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index a1469d0a..954936b9 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -61,12 +61,12 @@ async def manifest(cashu_id: str):
"name": "Cashu" + " - " + cashu.name,
"icons": [
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-512-512.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
"type": "image/png",
"sizes": "512x512",
},
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-96-96.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
"type": "image/png",
"sizes": "96x96",
},
@@ -85,132 +85,19 @@ async def manifest(cashu_id: str):
"url": "/cashu/wallet?mint_id=" + cashu_id,
"icons": [
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-512-512.png",
- "sizes": "512x512",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-192-192.png",
- "sizes": "192x192",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-144-144.png",
- "sizes": "144x144",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-96-96.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
"sizes": "96x96",
+ "type": "image/png",
},
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-72-72.png",
- "sizes": "72x72",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-48-48.png",
- "sizes": "48x48",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/16.png",
- "sizes": "16x16",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/20.png",
- "sizes": "20x20",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/29.png",
- "sizes": "29x29",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/32.png",
- "sizes": "32x32",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/40.png",
- "sizes": "40x40",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/50.png",
- "sizes": "50x50",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/57.png",
- "sizes": "57x57",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/58.png",
- "sizes": "58x58",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/60.png",
- "sizes": "60x60",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/64.png",
- "sizes": "64x64",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/72.png",
- "sizes": "72x72",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/76.png",
- "sizes": "76x76",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/80.png",
- "sizes": "80x80",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/87.png",
- "sizes": "87x87",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/100.png",
- "sizes": "100x100",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/114.png",
- "sizes": "114x114",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/120.png",
- "sizes": "120x120",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/128.png",
- "sizes": "128x128",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/144.png",
- "sizes": "144x144",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/152.png",
- "sizes": "152x152",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/167.png",
- "sizes": "167x167",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/180.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/180x180.png",
"sizes": "180x180",
+ "type": "image/png",
},
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/192.png",
- "sizes": "192x192",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/256.png",
- "sizes": "256x256",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/512.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
"sizes": "512x512",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/1024.png",
- "sizes": "1024x1024",
+ "type": "image/png",
},
],
}
From 28dd4e68f3790eed46b7fd5bb07e3d305edd48ff Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 19:12:18 +0100
Subject: [PATCH 405/696] fix nanifest
---
lnbits/extensions/cashu/views.py | 141 ++++++++++++++++++++++++++++---
1 file changed, 127 insertions(+), 14 deletions(-)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 954936b9..a1469d0a 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -61,12 +61,12 @@ async def manifest(cashu_id: str):
"name": "Cashu" + " - " + cashu.name,
"icons": [
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-512-512.png",
"type": "image/png",
"sizes": "512x512",
},
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-96-96.png",
"type": "image/png",
"sizes": "96x96",
},
@@ -85,19 +85,132 @@ async def manifest(cashu_id: str):
"url": "/cashu/wallet?mint_id=" + cashu_id,
"icons": [
{
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/96x96.png",
- "sizes": "96x96",
- "type": "image/png",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/180x180.png",
- "sizes": "180x180",
- "type": "image/png",
- },
- {
- "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/512x512.png",
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-512-512.png",
"sizes": "512x512",
- "type": "image/png",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-192-192.png",
+ "sizes": "192x192",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-144-144.png",
+ "sizes": "144x144",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-96-96.png",
+ "sizes": "96x96",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-72-72.png",
+ "sizes": "72x72",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-48-48.png",
+ "sizes": "48x48",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/16.png",
+ "sizes": "16x16",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/20.png",
+ "sizes": "20x20",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/29.png",
+ "sizes": "29x29",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/32.png",
+ "sizes": "32x32",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/40.png",
+ "sizes": "40x40",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/50.png",
+ "sizes": "50x50",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/57.png",
+ "sizes": "57x57",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/58.png",
+ "sizes": "58x58",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/60.png",
+ "sizes": "60x60",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/64.png",
+ "sizes": "64x64",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/72.png",
+ "sizes": "72x72",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/76.png",
+ "sizes": "76x76",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/80.png",
+ "sizes": "80x80",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/87.png",
+ "sizes": "87x87",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/100.png",
+ "sizes": "100x100",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/114.png",
+ "sizes": "114x114",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/120.png",
+ "sizes": "120x120",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/128.png",
+ "sizes": "128x128",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/144.png",
+ "sizes": "144x144",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/152.png",
+ "sizes": "152x152",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/167.png",
+ "sizes": "167x167",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/180.png",
+ "sizes": "180x180",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/192.png",
+ "sizes": "192x192",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/256.png",
+ "sizes": "256x256",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/512.png",
+ "sizes": "512x512",
+ },
+ {
+ "src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/1024.png",
+ "sizes": "1024x1024",
},
],
}
From 66c44bb990276c7be5fe9c72d9065c2e7dc52c12 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 6 Nov 2022 19:19:23 +0100
Subject: [PATCH 406/696] id
---
lnbits/extensions/cashu/views.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index a1469d0a..8ea628df 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -71,6 +71,7 @@ async def manifest(cashu_id: str):
"sizes": "96x96",
},
],
+ "id": "/cashu/wallet?mint_id=" + cashu_id,
"start_url": "/cashu/wallet?mint_id=" + cashu_id,
"background_color": "#1F2234",
"description": "Cashu ecash wallet",
From 4c38bcbcf31c857b5655766138c33dc6ead9325d Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 14:00:05 +0100
Subject: [PATCH 407/696] set private key
---
.env.example | 4 ++++
lnbits/extensions/cashu/__init__.py | 9 ++++++---
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/.env.example b/.env.example
index 32a67f75..b6a53205 100644
--- a/.env.example
+++ b/.env.example
@@ -108,3 +108,7 @@ ECLAIR_PASS=eclairpw
# Enter /api in LightningTipBot to get your key
LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
LNTIPS_API_ENDPOINT=https://ln.tips
+
+# Cashu Mint
+# Use a long-enough random (!) private key
+CASHU_PRIVATE_KEY="SuperSecretPrivateKey"
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index 7944e658..5d5fe131 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -1,5 +1,6 @@
import asyncio
+from environs import Env # type: ignore
from fastapi import APIRouter
from fastapi.staticfiles import StaticFiles
@@ -20,11 +21,13 @@ cashu_static_files = [
]
from cashu.mint.ledger import Ledger
+env = Env()
+env.read_env()
+
ledger = Ledger(
db=db,
- # seed=MINT_PRIVATE_KEY,
- seed="asd",
- derivation_path="0/0/0/1",
+ seed=env.str("CASHU_PRIVATE_KEY", default="SuperSecretPrivateKey"),
+ derivation_path="0/0/0/0",
)
cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
From b936d122a3f5479f219fa1eded01fadf2bbeb374 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 15:36:52 +0100
Subject: [PATCH 408/696] scrolling works
---
.env.example | 3 +-
.../cashu/templates/cashu/wallet.html | 560 +++++++++---------
2 files changed, 283 insertions(+), 280 deletions(-)
diff --git a/.env.example b/.env.example
index b6a53205..28e52698 100644
--- a/.env.example
+++ b/.env.example
@@ -110,5 +110,6 @@ LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
LNTIPS_API_ENDPOINT=https://ln.tips
# Cashu Mint
-# Use a long-enough random (!) private key
+# Use a long-enough random (!) private key.
+# Once set, you cannot change this key as for now.
CASHU_PRIVATE_KEY="SuperSecretPrivateKey"
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 9b49a063..a0861ae7 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -5,305 +5,307 @@ page_container %}
-
-
-
-
-
-
- Get invoice
-
-
-
-
-
- {% raw %} {{getBalance()}}
- {{tickershort}}{% endraw %}
-
-
-
-
- Pay invoice
-
-
-
-
-
-
-
-
-
- {% raw %} {{getBalance()}}
- {{tickershort}}{% endraw %}
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
Get Ecash
+ size="14px"
+ icon="bolt"
+ rectangle
+ color="primary"
+ @click="showInvoicesDialog"
+ >Get invoice
+
-
-
+
+
+
+ {% raw %} {{getBalance()}}
+ {{tickershort}}{% endraw %}
+
+
+
+
- Pay Ecash
+ @click="showParseDialog"
+ size="14px"
+ icon="bolt"
+ rectangle
+ color="primary"
+ class="full-width"
+ >Pay invoice
+
+
+
+
+
+
+
+ {% raw %} {{getBalance()}}
+ {{tickershort}}{% endraw %}
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
- {% raw %}
-
-
-
- {{props.row.value}}
-
-
- {{props.row.count}}
-
-
- {{props.row.sum}}
-
-
- {{props.row.memo}}
-
-
-
- {% endraw %}
-
-
+
+
+ {% raw %}
+
+
+
+ {{props.row.value}}
+
+
+ {{props.row.count}}
+
+
+ {{props.row.sum}}
+
+
+ {{props.row.memo}}
+
+
+
+ {% endraw %}
+
+
-
+
-
-
- {% raw %}
-
-
-
-
-
- Pending
-
-
- Check
-
-
-
- Received
- Paid
-
-
-
-
- {{props.row.amount}}
-
+
+
+ {% raw %}
+
+
+
+
+
+ Pending
+
+
+ Check
+
+
+
+ Received
+ Paid
+
+
+
+
+ {{props.row.amount}}
+
-
-
- {{props.row.date}}
-
-
- {{props.row.bolt11}}
-
-
- {{props.row.hash}}
-
-
-
- {% endraw %}
-
-
+
+ {{props.row.date}}
+
+
+ {{props.row.bolt11}}
+
+
+ {{props.row.hash}}
+
+
+
+ {% endraw %}
+
+
-
+
-
-
- {% raw %}
-
-
-
-
-
- Pending
-
-
- Check
-
-
-
- Received
- Paid
-
-
-
-
- {{props.row.amount}}
-
+
+
+ {% raw %}
+
+
+
+
+
+ Pending
+
+
+ Check
+
+
+
+ Received
+ Paid
+
+
+
+
+ {{props.row.amount}}
+
-
- {{props.row.date}}
-
-
- {{props.row.token}}
-
-
-
- {% endraw %}
-
-
-
-
-
+
+ {{props.row.date}}
+
+
+ {{props.row.token}}
+
+
+
+ {% endraw %}
+
+
+
+
+
-
-
- Warning
- BackupDownload wallet backup
-
-
+
+
+ Warning
+ BackupDownload wallet backup
+
Date: Mon, 7 Nov 2022 16:16:15 +0100
Subject: [PATCH 409/696] camera for tokens
---
.../cashu/templates/cashu/wallet.html | 178 +++++++++++-------
1 file changed, 105 insertions(+), 73 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index a0861ae7..65e3690b 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -321,7 +321,8 @@ page_container %}
@click="showInvoicesDialog"
>
-
+
+
@@ -512,11 +513,12 @@ page_container %}
unelevated
icon="photo_camera"
class="q-mx-0"
+ v-if="hasCamera"
@click="showCamera"
>
Cancel Close
@@ -537,7 +539,7 @@ page_container %}
-
+
Receive Tokens
+
Close
@@ -825,6 +834,11 @@ page_container %}
bolt11: '',
hash: ''
},
+ camera: {
+ data: null,
+ show: false,
+ camera: 'auto'
+ },
invoiceCheckListener: () => {},
payInvoiceData: {
blocking: false,
@@ -1089,10 +1103,15 @@ page_container %}
return row.payment_hash + row.amount
},
closeCamera: function () {
- this.payInvoiceData.camera.show = false
+ this.camera.show = false
},
showCamera: function () {
- this.payInvoiceData.camera.show = true
+ this.camera.show = true
+ },
+ hasCamera: function () {
+ navigator.permissions.query({name: 'camera'}).then(res => {
+ return res.state == 'granted'
+ })
},
showChart: function () {
this.paymentsChart.show = true
@@ -1124,7 +1143,7 @@ page_container %}
this.payInvoiceData.data.request = ''
this.payInvoiceData.data.comment = ''
this.payInvoiceData.data.paymentChecker = null
- this.payInvoiceData.camera.show = false
+ this.camera.show = false
this.focusInput('pasteInput')
},
showDisclaimerDialog: function () {
@@ -1203,83 +1222,96 @@ page_container %}
})
},
decodeQR: function (res) {
- this.payInvoiceData.data.request = res
+ this.camera.data = res
+ // this.payInvoiceData.data.request = res
this.decodeRequest()
- this.payInvoiceData.camera.show = false
+ this.camera.show = false
},
decodeRequest: function () {
- this.payInvoiceData.show = true
- let req = this.payInvoiceData.data.request.toLowerCase()
- if (
- this.payInvoiceData.data.request
- .toLowerCase()
- .startsWith('lightning:')
- ) {
- this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice(
- 10
- )
- } else if (
- this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl:')
- ) {
- this.payInvoiceData.data.request = this.payInvoiceData.data.request.slice(
- 6
- )
+ // let req = this.payInvoiceData.data.request.toLowerCase()
+ reqtype = null
+ let req = this.camera.data
+ if (req.toLowerCase().startsWith('lnbc')) {
+ this.payInvoiceData.data.request = req
+ reqtype = 'bolt11'
+ } else if (req.toLowerCase().startsWith('lightning:')) {
+ this.payInvoiceData.data.request = req.slice(10)
+ reqtype = 'bolt11'
+ } else if (req.toLowerCase().startsWith('lnurl:')) {
+ this.payInvoiceData.data.request = req.slice(6)
+ reqtype = 'lnurl'
} else if (req.indexOf('lightning=lnurl1') !== -1) {
- this.payInvoiceData.data.request = this.payInvoiceData.data.request
+ this.payInvoiceData.data.request = req
.split('lightning=')[1]
.split('&')[0]
- }
-
- if (
- this.payInvoiceData.data.request.toLowerCase().startsWith('lnurl1') ||
- this.payInvoiceData.data.request.match(/[\w.+-~_]+@[\w.+-~_]/)
+ reqtype = 'lnurl'
+ } else if (
+ req.toLowerCase().startsWith('lnurl1') ||
+ req.match(/[\w.+-~_]+@[\w.+-~_]/)
) {
+ this.payInvoiceData.data.request = req
+ reqtype = 'lnurl'
return
+ } else if (req.indexOf('W3siaWQ') !== 1) {
+ // very dirty way of parsing cashu tokens
+
+ this.receiveData.tokensBase64 = req.slice(req.indexOf('W3siaWQ'))
+ reqtype = 'cashu'
}
- let invoice
- try {
- invoice = decode(this.payInvoiceData.data.request)
- } catch (error) {
- this.$q.notify({
- timeout: 3000,
- type: 'warning',
- message: error + '.',
- caption: '400 BAD REQUEST'
- })
- this.payInvoiceData.show = false
- throw error
- return
- }
-
- let cleanInvoice = {
- msat: invoice.human_readable_part.amount,
- sat: invoice.human_readable_part.amount / 1000,
- fsat: LNbits.utils.formatSat(
- invoice.human_readable_part.amount / 1000
- )
- }
-
- _.each(invoice.data.tags, tag => {
- if (_.isObject(tag) && _.has(tag, 'description')) {
- if (tag.description === 'payment_hash') {
- cleanInvoice.hash = tag.value
- } else if (tag.description === 'description') {
- cleanInvoice.description = tag.value
- } else if (tag.description === 'expiry') {
- var expireDate = new Date(
- (invoice.data.time_stamp + tag.value) * 1000
- )
- cleanInvoice.expireDate = Quasar.utils.date.formatDate(
- expireDate,
- 'YYYY-MM-DDTHH:mm:ss.SSSZ'
- )
- cleanInvoice.expired = false // TODO
- }
+ if (reqtype == 'bolt11') {
+ console.log('#### QR CODE: BOLT11')
+ this.payInvoiceData.show = true
+ let invoice
+ try {
+ invoice = decode(this.payInvoiceData.data.request)
+ } catch (error) {
+ this.$q.notify({
+ timeout: 3000,
+ type: 'warning',
+ message: error + '.',
+ caption: '400 BAD REQUEST'
+ })
+ this.payInvoiceData.show = false
+ throw error
+ return
}
- })
- this.payInvoiceData.invoice = Object.freeze(cleanInvoice)
+ let cleanInvoice = {
+ msat: invoice.human_readable_part.amount,
+ sat: invoice.human_readable_part.amount / 1000,
+ fsat: LNbits.utils.formatSat(
+ invoice.human_readable_part.amount / 1000
+ )
+ }
+
+ _.each(invoice.data.tags, tag => {
+ if (_.isObject(tag) && _.has(tag, 'description')) {
+ if (tag.description === 'payment_hash') {
+ cleanInvoice.hash = tag.value
+ } else if (tag.description === 'description') {
+ cleanInvoice.description = tag.value
+ } else if (tag.description === 'expiry') {
+ var expireDate = new Date(
+ (invoice.data.time_stamp + tag.value) * 1000
+ )
+ cleanInvoice.expireDate = Quasar.utils.date.formatDate(
+ expireDate,
+ 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ )
+ cleanInvoice.expired = false // TODO
+ }
+ }
+ })
+
+ this.payInvoiceData.invoice = Object.freeze(cleanInvoice)
+ } else if (reqtype == 'lnurl') {
+ console.log('#### QR CODE: LNURL')
+ // not supported yet
+ } else if (reqtype == 'cashu') {
+ console.log('#### QR CODE: CASHU TOKEN')
+ this.showReceiveTokens = true
+ }
},
payInvoice: function () {
let dismissPaymentMsg = this.$q.notify({
@@ -1348,7 +1380,7 @@ page_container %}
this.payInvoiceData.invoice = ''
this.payInvoiceData.data.request = ''
this.showPayInvoice = true
- this.payInvoiceData.camera.show = false
+ this.camera.show = false
},
showTokenDialog: function (token) {
From 8e07b220b1b6c9b2df86e5b3f6f373f761f2bf17 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 16:26:39 +0100
Subject: [PATCH 410/696] take private key from config
---
lnbits/extensions/cashu/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index 5d5fe131..e6507bba 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -27,7 +27,7 @@ env.read_env()
ledger = Ledger(
db=db,
seed=env.str("CASHU_PRIVATE_KEY", default="SuperSecretPrivateKey"),
- derivation_path="0/0/0/0",
+ derivation_path="0/0/0/1",
)
cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
From 44df0d5cabe8d92394119321a2efe540a20f3013 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 16:54:25 +0100
Subject: [PATCH 411/696] clean
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 65e3690b..e095a419 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1228,8 +1228,7 @@ page_container %}
this.camera.show = false
},
decodeRequest: function () {
- // let req = this.payInvoiceData.data.request.toLowerCase()
- reqtype = null
+ let reqtype = null
let req = this.camera.data
if (req.toLowerCase().startsWith('lnbc')) {
this.payInvoiceData.data.request = req
@@ -1252,9 +1251,11 @@ page_container %}
this.payInvoiceData.data.request = req
reqtype = 'lnurl'
return
+ } else if (req.indexOf('cashu:') !== 1) {
+ this.receiveData.tokensBase64 = req.slice(req.indexOf('cashu:'))
+ reqtype = 'cashu'
} else if (req.indexOf('W3siaWQ') !== 1) {
// very dirty way of parsing cashu tokens
-
this.receiveData.tokensBase64 = req.slice(req.indexOf('W3siaWQ'))
reqtype = 'cashu'
}
From bf0355262354dde576df539073bba09d93c9cc64 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 17:08:30 +0100
Subject: [PATCH 412/696] fix html
---
.../cashu/templates/cashu/wallet.html | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index e095a419..32b03478 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -758,12 +758,12 @@ page_container %}
Receive Tokens
+ unelevated
+ icon="photo_camera"
+ class="q-mx-0"
+ v-if="hasCamera"
+ @click="showCamera"
+ >
Close
@@ -2110,6 +2110,8 @@ page_container %}
}
},
+ mounted: function () {},
+
created: function () {
let params = new URL(document.location).searchParams
@@ -2180,7 +2182,7 @@ page_container %}
localStorage.getItem(this.mintKey(this.mintId, 'proofs')) || '[]'
)
- // get recv_token
+ // get recv_token to receive tokens from a link
if (params.get('recv_token')) {
tokenBase64 = params.get('recv_token')
let seen = false
@@ -2214,6 +2216,7 @@ page_container %}
console.table('### tokens', this.proofs)
console.log('#### this.mintId', this.mintId)
console.log('#### this.mintName', this.mintName)
+
this.recheckPendingInvoices()
this.recheckPendingTokens()
}
From c9f3f26ae03ba6d13b5fb9768d8054f891aef179 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 17:23:04 +0100
Subject: [PATCH 413/696] denser
---
.../extensions/cashu/templates/cashu/wallet.html | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 32b03478..0d0fd4f1 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -3,10 +3,10 @@
page_container %}
-
+
-
-
+
+
@@ -57,7 +57,7 @@ page_container %}
-
+
@@ -281,11 +281,7 @@ page_container %}
-
+
Date: Mon, 7 Nov 2022 17:31:30 +0100
Subject: [PATCH 414/696] invoices first
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 0d0fd4f1..79ba5c44 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -92,8 +92,8 @@ page_container %}
////////////////// TABLES /////////////////
/////////////////////////////////////////// -->
-
+
@@ -874,7 +874,7 @@ page_container %}
showReceiveTokens: false,
promises: [],
tokens: [],
- tab: 'tokens',
+ tab: 'invoices',
receive: {
show: false,
From b18307cc30de74e2b616d0968ff3d22e281e4a59 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 17:44:36 +0100
Subject: [PATCH 415/696] wider
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 79ba5c44..08cae6bd 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -3,9 +3,9 @@
page_container %}
-
+
-
+
From 40f385dbe48fcf5e4639c26c208bef84fdc754f0 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 17:45:16 +0100
Subject: [PATCH 416/696] wider
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 08cae6bd..9fda994b 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -5,7 +5,7 @@ page_container %}
-
+
From 5e900508fc6330ef4877098e4b819849ff202fc7 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 17:50:12 +0100
Subject: [PATCH 417/696] read qr tokens
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 9fda994b..aff8f6a4 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1247,9 +1247,9 @@ page_container %}
this.payInvoiceData.data.request = req
reqtype = 'lnurl'
return
- } else if (req.indexOf('cashu:') !== 1) {
- this.receiveData.tokensBase64 = req.slice(req.indexOf('cashu:'))
- reqtype = 'cashu'
+ // } else if (req.indexOf('cashu:') !== 1) {
+ // this.receiveData.tokensBase64 = req.slice(req.indexOf('cashu:'))
+ // reqtype = 'cashu'
} else if (req.indexOf('W3siaWQ') !== 1) {
// very dirty way of parsing cashu tokens
this.receiveData.tokensBase64 = req.slice(req.indexOf('W3siaWQ'))
From 71601542140cc8c92fb1a482b680a9e84b0e8013 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 17:53:52 +0100
Subject: [PATCH 418/696] string fix
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index aff8f6a4..c8e298e6 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -106,7 +106,7 @@ page_container %}
:data="getTokenList()"
:columns="tokensTable.columns"
:pagination.sync="tokensTable.pagination"
- no-data-label="No tokens yet"
+ no-data-label="There are no tokens here yet"
:filter="tokensTable.filter"
>
{% raw %}
From f44faac9b6f44e8bbe7b1ddeb07be46e605c011b Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 18:29:54 +0100
Subject: [PATCH 419/696] fix invoice input
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index c8e298e6..5d704c83 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1225,7 +1225,16 @@ page_container %}
},
decodeRequest: function () {
let reqtype = null
- let req = this.camera.data
+ let req = null
+ // get request
+ if (this.camera.data) {
+ // get request from camera
+ req = this.camera.data
+ } else if (this.payInvoiceData.data.request) {
+ // get request from pay invoice dialog
+ req = this.payInvoiceData.data.request
+ }
+
if (req.toLowerCase().startsWith('lnbc')) {
this.payInvoiceData.data.request = req
reqtype = 'bolt11'
From 0d0d56a9c73cd24e264af8e29a695dd600880439 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 7 Nov 2022 21:59:58 +0100
Subject: [PATCH 420/696] notification
---
.../cashu/templates/cashu/wallet.html | 201 ++++++++++++++----
lnbits/extensions/cashu/views.py | 4 +
2 files changed, 163 insertions(+), 42 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 5d704c83..2eceb5f5 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -16,7 +16,7 @@ page_container %}
icon="bolt"
rectangle
color="primary"
- @click="showInvoicesDialog"
+ @click="showInvoiceCreateDialog"
>Get invoice
@@ -152,7 +152,7 @@ page_container %}
@@ -314,7 +314,7 @@ page_container %}
class="q-pa-none"
icon="bolt"
label="Get invoice"
- @click="showInvoicesDialog"
+ @click="showInvoiceCreateDialog"
>
@@ -494,7 +494,7 @@ page_container %}
dense
v-model.trim="payInvoiceData.data.request"
type="textarea"
- label="Enter an invoice *"
+ label="Enter a Lightning invoice *"
>
@@ -1194,7 +1194,15 @@ page_container %}
timeout: 5000,
type: 'warning',
message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
- caption: response.data.lnurl_response
+ caption: response.data.lnurl_response,
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
return
} else if (response.data.lnurl_response === true) {
@@ -1202,7 +1210,15 @@ page_container %}
this.$q.notify({
timeout: 5000,
message: `Invoice sent to ${this.receive.lnurl.domain}!`,
- spinner: true
+ spinner: true,
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
}
}
@@ -1276,7 +1292,15 @@ page_container %}
timeout: 3000,
type: 'warning',
message: error + '.',
- caption: '400 BAD REQUEST'
+ caption: 'Failed to decode invoice',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
this.payInvoiceData.show = false
throw error
@@ -1322,19 +1346,43 @@ page_container %}
payInvoice: function () {
let dismissPaymentMsg = this.$q.notify({
timeout: 0,
- message: 'Processing payment...'
+ message: 'Processing payment...',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
},
payLnurl: function () {
let dismissPaymentMsg = this.$q.notify({
timeout: 0,
- message: 'Processing payment...'
+ message: 'Processing payment...',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
},
authLnurl: function () {
let dismissAuthMsg = this.$q.notify({
timeout: 10,
- message: 'Performing authentication...'
+ message: 'Performing authentication...',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
},
@@ -1364,8 +1412,8 @@ page_container %}
},
/////////////////////////////////// WALLET ///////////////////////////////////
- showInvoicesDialog: async function () {
- console.log('##### showInvoicesDialog')
+ showInvoiceCreateDialog: async function () {
+ console.log('##### showInvoiceCreateDialog')
this.invoiceData.amount = ''
this.invoiceData.bolt11 = ''
this.invoiceData.hash = ''
@@ -1373,21 +1421,21 @@ page_container %}
this.showInvoiceDetails = true
},
- showInvoiceDialog: function (data) {
- console.log('##### showInvoiceDialog')
+ showInvoicInfoDialog: function (data) {
+ console.log('##### showInvoicInfoDialog')
this.invoiceData = _.clone(data)
this.showInvoiceDetails = true
// kick off invoice check worker
this.invoiceCheckWorker()
},
- showPayInvoiceDialog: function () {
- console.log('### showPayInvoiceDialog')
- this.payInvoiceData.invoice = ''
- this.payInvoiceData.data.request = ''
- this.showPayInvoice = true
- this.camera.show = false
- },
+ // showPayInvoiceDialog: function () {
+ // console.log('### showPayInvoiceDialog')
+ // this.payInvoiceData.invoice = ''
+ // this.payInvoiceData.data.request = ''
+ // this.showPayInvoice = true
+ // this.camera.show = false
+ // },
showTokenDialog: function (token) {
console.log('##### showTokenDialog')
@@ -1502,7 +1550,15 @@ page_container %}
this.$q.notify({
timeout: 5000,
type: 'positive',
- message: 'Payment received'
+ message: 'Payment received',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
} catch (error) {
console.log('not paid yet')
@@ -1595,7 +1651,15 @@ page_container %}
this.$q.notify({
timeout: 5000,
type: 'warning',
- message: 'Balance too low'
+ message: 'Balance too low',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
throw Error('balance too low.')
}
@@ -1730,7 +1794,15 @@ page_container %}
this.$q.notify({
timeout: 5000,
type: 'positive',
- message: 'Tokens received'
+ message: 'Tokens received',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
} catch (error) {
console.error(error)
@@ -1827,7 +1899,15 @@ page_container %}
this.$q.notify({
timeout: 5000,
type: 'positive',
- message: 'Invoice paid'
+ message: 'Invoice paid',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
// delete spent tokens from db
this.proofs = fristProofs
@@ -1955,7 +2035,15 @@ page_container %}
this.$q.notify({
timeout: 5000,
type: 'positive',
- message: 'Token paid'
+ message: 'Token paid',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
} else {
console.log('### token not paid yet')
@@ -1963,7 +2051,15 @@ page_container %}
this.$q.notify({
timeout: 5000,
color: 'grey',
- message: 'Token still pending'
+ message: 'Token still pending',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
}
this.sendData.tokens = token
@@ -2049,7 +2145,15 @@ page_container %}
timeout: 5000,
type: 'warning',
message: 'Could not decode invoice',
- caption: error + ''
+ caption: error + '',
+ position: 'top',
+ actions: [
+ {
+ icon: 'close',
+ color: 'white',
+ handler: () => {}
+ }
+ ]
})
throw error
}
@@ -2129,7 +2233,8 @@ page_container %}
} else {
this.$q.notify({
color: 'red',
- message: 'No mint set!'
+ message: 'No mint set!',
+ position: 'center'
})
}
@@ -2190,6 +2295,7 @@ page_container %}
// get recv_token to receive tokens from a link
if (params.get('recv_token')) {
tokenBase64 = params.get('recv_token')
+ // make sure to react only to tokens not in the users history
let seen = false
for (var i = 0; i < this.historyTokens.length; i++) {
var thisToken = this.historyTokens[i].token
@@ -2198,24 +2304,17 @@ page_container %}
}
}
if (!seen) {
+ // show receive token dialog
this.receiveData.tokensBase64 = params.get('recv_token')
this.showReceiveTokens = true
}
}
- var body = document.body,
- html = document.documentElement
-
- var height = Math.max(
- body.scrollHeight,
- body.offsetHeight,
- html.clientHeight,
- html.scrollHeight,
- html.offsetHeight
- )
-
- console.log('height', height)
- this.height = height
+ // get lightning invoice from a link
+ if (params.get('lightning')) {
+ this.showParseDialog()
+ this.payInvoiceData.data.request = params.get('lightning')
+ }
console.log('### invoicesCashu', this.invoicesCashu)
console.table('### tokens', this.proofs)
@@ -2224,6 +2323,24 @@ page_container %}
this.recheckPendingInvoices()
this.recheckPendingTokens()
+
+ // register lightning: link
+ // Intercept any `lightning:` requests
+ window.addEventListener('click', ev => {
+ // Use composedPath() for detecting links inside a Shadow DOM
+ // https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath
+ const target = ev.composedPath()[0]
+
+ if (!target || !target.closest) {
+ return
+ }
+ const lightningLink = target.closest('[href^="lightning:" i]')
+ if (lightningLink) {
+ href = lightningLink.getAttribute('href').toLowerCase()
+ paymentRequest = href.replace('lightning:', '')
+ link = lightningLink
+ }
+ })
}
})
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 8ea628df..afbef23c 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -78,6 +78,10 @@ async def manifest(cashu_id: str):
"display": "standalone",
"scope": "/cashu/",
"theme_color": "#1F2234",
+ "protocol_handlers": [
+ {"protocol": "cashu", "url": "&recv_token=%s"},
+ {"protocol": "lightning", "url": "&lightning=%s"},
+ ],
"shortcuts": [
{
"name": "Cashu" + " - " + cashu.name,
From fedd40d2e76022b613502190e02245cabe67cbb3 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Tue, 8 Nov 2022 23:34:26 +0100
Subject: [PATCH 421/696] only one worker at a time
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 2eceb5f5..e0d7671e 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1526,6 +1526,9 @@ page_container %}
invoiceCheckWorker: async function () {
let nInterval = 0
+ if (this.invoiceCheckListener) {
+ clearInterval(this.invoiceCheckListener)
+ }
this.invoiceCheckListener = setInterval(async () => {
try {
nInterval += 1
@@ -1978,6 +1981,9 @@ page_container %}
checkTokenSpendableWorker: async function () {
let nInterval = 0
+ if (this.tokensCheckSpendableListener) {
+ clearInterval(this.tokensCheckSpendableListener)
+ }
this.tokensCheckSpendableListener = setInterval(async () => {
try {
nInterval += 1
From 4b82bd2cda9f0aae5a1962c7f91a10731c3ab26c Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Tue, 8 Nov 2022 23:38:36 +0100
Subject: [PATCH 422/696] one worker at a time
---
.../cashu/templates/cashu/wallet.html | 28 ++++++++++---------
1 file changed, 15 insertions(+), 13 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index e0d7671e..af8a03cf 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1523,12 +1523,17 @@ page_container %}
},
//////////// API ///////////
-
- invoiceCheckWorker: async function () {
- let nInterval = 0
+ clearAllWorkers: function () {
if (this.invoiceCheckListener) {
clearInterval(this.invoiceCheckListener)
}
+ if (this.tokensCheckSpendableListener) {
+ clearInterval(this.tokensCheckSpendableListener)
+ }
+ },
+ invoiceCheckWorker: async function () {
+ let nInterval = 0
+ this.clearAllWorkers()
this.invoiceCheckListener = setInterval(async () => {
try {
nInterval += 1
@@ -1536,9 +1541,9 @@ page_container %}
// exit loop after 2m
if (nInterval > 40) {
console.log('### stopping invoice check worker')
- clearInterval(this.invoiceCheckListener)
+ this.clearAllWorkers()
}
- console.log('### setInterval', nInterval)
+ console.log('### invoiceCheckWorker setInterval', nInterval)
console.log(this.invoiceData)
// this will throw an error if the invoice is pending
@@ -1546,7 +1551,7 @@ page_container %}
// only without error (invoice paid) will we reach here
console.log('### stopping invoice check worker')
- clearInterval(this.invoiceCheckListener)
+ this.clearAllWorkers()
this.invoiceData.bolt11 = ''
this.showInvoiceDetails = false
if (window.navigator.vibrate) navigator.vibrate(200)
@@ -1981,19 +1986,16 @@ page_container %}
checkTokenSpendableWorker: async function () {
let nInterval = 0
- if (this.tokensCheckSpendableListener) {
- clearInterval(this.tokensCheckSpendableListener)
- }
+ this.clearAllWorkers()
this.tokensCheckSpendableListener = setInterval(async () => {
try {
nInterval += 1
-
// exit loop after 2m
if (nInterval > 24) {
console.log('### stopping token check worker')
- clearInterval(this.tokensCheckSpendableListener)
+ this.clearAllWorkers()
}
- console.log('### setInterval', nInterval)
+ console.log('### checkTokenSpendableWorker setInterval', nInterval)
console.log(this.sendData)
// this will throw an error if the invoice is pending
@@ -2003,7 +2005,7 @@ page_container %}
)
if (paid) {
console.log('### stopping token check worker')
- clearInterval(this.tokensCheckSpendableListener)
+ this.clearAllWorkers()
this.sendData.tokens = ''
this.showSendTokens = false
}
From 13923fb72ea8b1586ff58e44ddc266784de35011 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Wed, 9 Nov 2022 15:36:07 +0100
Subject: [PATCH 423/696] clean
---
.../cashu/templates/cashu/wallet.html | 18 ------------------
1 file changed, 18 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index af8a03cf..27ef5e5c 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -2331,24 +2331,6 @@ page_container %}
this.recheckPendingInvoices()
this.recheckPendingTokens()
-
- // register lightning: link
- // Intercept any `lightning:` requests
- window.addEventListener('click', ev => {
- // Use composedPath() for detecting links inside a Shadow DOM
- // https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath
- const target = ev.composedPath()[0]
-
- if (!target || !target.closest) {
- return
- }
- const lightningLink = target.closest('[href^="lightning:" i]')
- if (lightningLink) {
- href = lightningLink.getAttribute('href').toLowerCase()
- paymentRequest = href.replace('lightning:', '')
- link = lightningLink
- }
- })
}
})
From 29c449dbeb24584b68e6ba50b6ba58529a11070b Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Wed, 9 Nov 2022 15:37:08 +0100
Subject: [PATCH 424/696] clean cli
---
lnbits/extensions/cashu/tasks.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index 40b521f0..11eb2fd2 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -15,7 +15,6 @@ async def startup_cashu_mint():
await migrate_databases(db, migrations)
await ledger.load_used_proofs()
await ledger.init_keysets()
- print(ledger.get_keyset())
pass
From 21a26c89c5e0e81ab6e3840c248cac3b831e1513 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 13 Nov 2022 19:15:40 -0600
Subject: [PATCH 425/696] change pages
---
.../cashu/templates/cashu/index.html | 6 +--
.../cashu/templates/cashu/mint.html | 24 +++++++----
.../cashu/templates/cashu/wallet.html | 42 +++++++++----------
3 files changed, 39 insertions(+), 33 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index f54b39b1..6c69da93 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -218,18 +218,18 @@
toggleAdvanced: false,
cashusTable: {
columns: [
- {name: 'id', align: 'left', label: 'ID', field: 'id'},
+ {name: 'id', align: 'left', label: 'Mint ID', field: 'id'},
{name: 'name', align: 'left', label: 'Name', field: 'name'},
{
name: 'tickershort',
align: 'left',
- label: 'tickershort',
+ label: 'Ticker',
field: 'tickershort'
},
{
name: 'wallet',
align: 'left',
- label: 'Cashu wallet',
+ label: 'Mint wallet',
field: 'wallet'
},
{
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
index 894d7087..b9300c3d 100644
--- a/lnbits/extensions/cashu/templates/cashu/mint.html
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -1,7 +1,7 @@
{% extends "public.html" %} {% block page %}
-
+
- {{ mint_name }}
- Cashu mint
+ {{ mint_name }}
Open wallet
- Read the following text carefully!
+ Read the following carefully!
This is a
Cashu
- mint. Cashu is a Ecash system on Bitcoin.
+ mint which is an Ecash system on Bitcoin.
+
+
+ Open this page in your browser
+ Before you continue, make sure to open this page in your device's
+ native browser application (Safari for iOS, Chrome for Android) if
+ you want to add Cashu wallet to your home screen.
Add to home screen
You can add Cashu to your home screen as a progressive web app
- (PWA). When you open the wallet in your browser, on Android Chrome,
- click the menu at the upper right. On iOS Safari, click the share
- button. Now press the Add to Home screen button.
+ (PWA). When you open the wallet in your browser, on Android
+ (Chrome), click the menu at the upper right. On iOS (Safari), click
+ the share button. Now press the Add to Home screen button.
Backup your wallet
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index 27ef5e5c..a8016ba7 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -125,9 +125,6 @@ page_container %}
{{props.row.sum}}
-
- {{props.row.memo}}
-
{% endraw %}
@@ -186,17 +183,17 @@ page_container %}
{{props.row.amount}}
-
{{props.row.date}}
+
{{props.row.bolt11}}
@@ -269,6 +266,9 @@ page_container %}
{{props.row.date}}
+
{{props.row.token}}
@@ -931,13 +931,6 @@ page_container %}
field: 'sum',
sortable: true
}
- // {
- // name: 'memo',
- // align: 'left',
- // label: 'Memo',
- // field: 'memo',
- // sortable: true
- // }
],
pagination: {
rowsPerPage: 5
@@ -960,13 +953,6 @@ page_container %}
field: 'amount',
sortable: true
},
- // {
- // name: 'memo',
- // align: 'left',
- // label: 'Memo',
- // field: 'memo',
- // sortable: true
- // },
{
name: 'date',
align: 'left',
@@ -974,6 +960,13 @@ page_container %}
field: 'date',
sortable: true
},
+ // {
+ // name: 'memo',
+ // align: 'left',
+ // label: 'Memo',
+ // field: 'memo',
+ // sortable: true
+ // },
{
name: 'bolt11',
align: 'left',
@@ -1020,6 +1013,13 @@ page_container %}
field: 'date',
sortable: true
},
+ // {
+ // name: 'memo',
+ // align: 'left',
+ // label: 'Memo',
+ // field: 'memo',
+ // sortable: true
+ // },
{
name: 'token',
align: 'left',
From 7fa1fb5f01986e4bbfd0cbdce3069d92783f1400 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 14 Nov 2022 08:01:53 -0600
Subject: [PATCH 426/696] open in same window
---
lnbits/extensions/cashu/templates/cashu/mint.html | 1 -
1 file changed, 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
index b9300c3d..5a899bdb 100644
--- a/lnbits/extensions/cashu/templates/cashu/mint.html
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -14,7 +14,6 @@
class="q-my-xl text-white"
style="font-size: 1.5rem"
href="../wallet?mint_id={{ mint_id }}"
- target="”_blank”"
>Open wallet
From b6d6ba3037eae9e56f4abf2958ed18c1457368ff Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 14 Nov 2022 08:08:35 -0600
Subject: [PATCH 427/696] update mint cover
---
.../cashu/templates/cashu/mint.html | 82 +++++++++----------
1 file changed, 41 insertions(+), 41 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
index 5a899bdb..2077a509 100644
--- a/lnbits/extensions/cashu/templates/cashu/mint.html
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -17,47 +17,47 @@
>Open wallet
-
-
-
- Read the following carefully!
-
- This is a
- Cashu
- mint which is an Ecash system on Bitcoin.
-
-
- Open this page in your browser
- Before you continue, make sure to open this page in your device's
- native browser application (Safari for iOS, Chrome for Android) if
- you want to add Cashu wallet to your home screen.
-
-
- Add to home screen
- You can add Cashu to your home screen as a progressive web app
- (PWA). When you open the wallet in your browser, on Android
- (Chrome), click the menu at the upper right. On iOS (Safari), click
- the share button. Now press the Add to Home screen button.
-
-
- Backup your wallet
- Ecash is a bearer asset, meaning losing access to your wallet will
- mean you will lose the funds. The wallet stores ecash tokens on your
- device's database. If you lose the link or delete your your data
- without backing up, you will lose your tokens. Press the Backup
- button in the wallet to download a copy of your tokens.
-
-
- This service is in BETA
- We hold no responsibility for people losing access to funds. Use at
- your own risk!
-
-
+
+
+
+
+ Read the following carefully!
+
+ This is a
+ Cashu
+ mint which is an Ecash system on Bitcoin.
+
+
+ Open this page in your browser
+ Before you continue, make sure to open this page in your device's
+ native browser application (Safari for iOS, Chrome for Android) if you
+ want to add Cashu wallet to your home screen.
+
+
+ Add to home screen
+ You can add Cashu to your home screen as a progressive web app (PWA).
+ When you open the wallet in your browser, on Android (Chrome), click
+ the menu at the upper right. On iOS (Safari), click the share button.
+ Now press the Add to Home screen button.
+
+
+ Backup your wallet
+ Ecash is a bearer asset, meaning losing access to your wallet will
+ mean you will lose the funds. The wallet stores ecash tokens on your
+ device's database. If you lose the link or delete your your data
+ without backing up, you will lose your tokens. Press the Backup button
+ in the wallet to download a copy of your tokens.
+
+
+ This service is in BETA
+ We hold no responsibility for people losing access to funds. Use at
+ your own risk!
+
From b57e7729b0e89c0ffc84221d3637041a9e59defb Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 14 Nov 2022 08:09:15 -0600
Subject: [PATCH 428/696] update mint cover
---
lnbits/extensions/cashu/templates/cashu/mint.html | 6 +-----
1 file changed, 1 insertion(+), 5 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
index 2077a509..5afb0343 100644
--- a/lnbits/extensions/cashu/templates/cashu/mint.html
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -4,11 +4,7 @@
-
+
{{ mint_name }}
Date: Mon, 14 Nov 2022 08:09:56 -0600
Subject: [PATCH 429/696] update mint cover
---
lnbits/extensions/cashu/templates/cashu/mint.html | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
index 5afb0343..2077a509 100644
--- a/lnbits/extensions/cashu/templates/cashu/mint.html
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -4,7 +4,11 @@
-
+
{{ mint_name }}
Date: Mon, 14 Nov 2022 08:14:59 -0600
Subject: [PATCH 430/696] update mint cover
---
.../cashu/templates/cashu/mint.html | 27 +++++++++----------
1 file changed, 12 insertions(+), 15 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
index 2077a509..50d201b3 100644
--- a/lnbits/extensions/cashu/templates/cashu/mint.html
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -24,31 +24,28 @@
Read the following carefully!
This is a
- Cashu
- mint which is an Ecash system on Bitcoin.
+ mint. Cashu is an ecash system for Bitcoin.
- Open this page in your browser
- Before you continue, make sure to open this page in your device's
- native browser application (Safari for iOS, Chrome for Android) if you
- want to add Cashu wallet to your home screen.
+ Open this page in your native browser
+ Before you continue to the wallet, make sure to open this page in your
+ device's native browser application (Safari for iOS, Chrome for
+ Android) if you want to add Cashu wallet to your home screen.
- Add to home screen
+ Add wallet to home screen
You can add Cashu to your home screen as a progressive web app (PWA).
- When you open the wallet in your browser, on Android (Chrome), click
- the menu at the upper right. On iOS (Safari), click the share button.
- Now press the Add to Home screen button.
+ After opening the wallet in your browser (click the link above), on
+ Android (Chrome), click the menu at the upper right. On iOS (Safari),
+ click the share button. Now press the Add to Home screen button.
Backup your wallet
- Ecash is a bearer asset, meaning losing access to your wallet will
- mean you will lose the funds. The wallet stores ecash tokens on your
+ Ecash is a bearer asset. That means losing access to your wallet will
+ make you lose your funds. The wallet stores ecash tokens on your
device's database. If you lose the link or delete your your data
without backing up, you will lose your tokens. Press the Backup button
in the wallet to download a copy of your tokens.
From e6837bad2ed81bc50a256bbf5e7bcfacc0506c21 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Mon, 14 Nov 2022 08:16:46 -0600
Subject: [PATCH 431/696] update mint cover
---
lnbits/extensions/cashu/templates/cashu/mint.html | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
index 50d201b3..ee6ab606 100644
--- a/lnbits/extensions/cashu/templates/cashu/mint.html
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -33,7 +33,8 @@
Open this page in your native browser
Before you continue to the wallet, make sure to open this page in your
device's native browser application (Safari for iOS, Chrome for
- Android) if you want to add Cashu wallet to your home screen.
+ Android). Do not use Cashu in an embedded browser that opens when you
+ click a link in a messenger.
Add wallet to home screen
From 2c58dfda7ef828855772bace947204711afc5837 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 24 Nov 2022 15:17:20 +0100
Subject: [PATCH 432/696] clean up for merge
---
.../cashu/templates/cashu/index.html | 4 +-
lnbits/extensions/cashu/views.py | 6 -
lnbits/extensions/cashu/views_api.py | 281 ------------------
3 files changed, 2 insertions(+), 289 deletions(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index 6c69da93..eb90b2c1 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -122,7 +122,7 @@
:options="g.user.walletOptions"
label="Cashu wallet *"
>
-
@@ -179,7 +179,7 @@
label="Coins that 'exist' in mint (optional)"
placeholder="∞"
>
-
+
-->
invoice.amount:
-# raise HTTPException(
-# status_code=HTTPStatus.PAYMENT_REQUIRED,
-# detail=f"Requested amount too high: {total_requested}. Invoice amount: {invoice.amount}",
-# )
-
-# status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
-# # todo: revert to: status.paid != True:
-# if status.paid != True:
-# raise HTTPException(
-# status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
-# )
-# try:
-# await update_lightning_invoice(cashu_id, payment_hash, True)
-
-# amounts = []
-# B_s = []
-# for payload in data.blinded_messages:
-# amounts.append(payload.amount)
-# B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
-
-# promises = await generate_promises(cashu.prvkey, amounts, B_s)
-# for amount, B_, p in zip(amounts, B_s, promises):
-# await store_promise(amount, B_.serialize().hex(), p.C_, cashu_id)
-
-# return promises
-# except Exception as e:
-# logger.error(e)
-# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-
-
-# @cashu_ext.post("/api/v1/{cashu_id}/melt")
-# async def melt_coins(payload: MeltPayload, cashu_id: str = Query(None)):
-# """Invalidates proofs and pays a Lightning invoice."""
-# cashu: Cashu = await get_cashu(cashu_id)
-# if cashu is None:
-# raise HTTPException(
-# status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
-# )
-# try:
-# ok, preimage = await melt(cashu, payload.proofs, payload.invoice)
-# return {"paid": ok, "preimage": preimage}
-# except Exception as e:
-# logger.error(e)
-# raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
-
-
-# @cashu_ext.post("/api/v1/{cashu_id}/check")
-# async def check_spendable_coins(payload: CheckPayload, cashu_id: str = Query(None)):
-# return await check_spendable(payload.proofs, cashu_id)
-
-
-# @cashu_ext.post("/api/v1/{cashu_id}/split")
-# async def split_proofs(payload: SplitRequest, cashu_id: str = Query(None)):
-# """
-# Requetst a set of tokens with amount "total" to be split into two
-# newly minted sets with amount "split" and "total-split".
-# """
-# print("### RECEIVE")
-# print("payload", json.dumps(payload, default=vars))
-# cashu: Cashu = await get_cashu(cashu_id)
-# if cashu is None:
-# raise HTTPException(
-# status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
-# )
-# proofs = payload.proofs
-# amount = payload.amount
-# outputs = payload.outputs.blinded_messages if payload.outputs else None
-# try:
-# split_return = await split(cashu, proofs, amount, outputs)
-# except Exception as exc:
-# raise CashuError(error=str(exc))
-# if not split_return:
-# return {"error": "there was a problem with the split."}
-# frst_promises, scnd_promises = split_return
-# resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
-# print("### resp", json.dumps(resp, default=vars))
-# return resp
-
-
-##################################################################
-##################################################################
-# CASHU LIB
-##################################################################
From 93ffdb98655da29cfe0964a37e959c9050dcf884 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 24 Nov 2022 15:22:13 +0100
Subject: [PATCH 433/696] mypy
---
lnbits/extensions/cashu/views.py | 9 ++++++++-
lnbits/extensions/cashu/views_api.py | 13 +++++++++----
2 files changed, 17 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 0b851c70..0de791c4 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -16,7 +16,10 @@ templates = Jinja2Templates(directory="templates")
@cashu_ext.get("/", response_class=HTMLResponse)
-async def index(request: Request, user: User = Depends(check_user_exists)):
+async def index(
+ request: Request,
+ user: User = Depends(check_user_exists), # type: ignore
+):
return cashu_renderer().TemplateResponse(
"cashu/index.html", {"request": request, "user": user.dict()}
)
@@ -36,6 +39,10 @@ async def wallet(request: Request, mint_id: str):
@cashu_ext.get("/mint/{mintID}")
async def cashu(request: Request, mintID):
cashu = await get_cashu(mintID)
+ if not cashu:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="TPoS does not exist."
+ )
return cashu_renderer().TemplateResponse(
"cashu/mint.html",
{"request": request, "mint_name": cashu.name, "mint_id": mintID},
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 3cbd84e6..4edd9229 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -56,20 +56,25 @@ LIGHTNING = True
@cashu_ext.get("/api/v1/mints", status_code=HTTPStatus.OK)
async def api_cashus(
- all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
+ all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type) # type: ignore
):
"""
Get all mints of this wallet.
"""
wallet_ids = [wallet.wallet.id]
if all_wallets:
- wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
+ user = await get_user(wallet.wallet.user)
+ if user:
+ wallet_ids = user.wallet_ids
return [cashu.dict() for cashu in await get_cashus(wallet_ids)]
@cashu_ext.post("/api/v1/mints", status_code=HTTPStatus.CREATED)
-async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)):
+async def api_cashu_create(
+ data: Cashu,
+ wallet: WalletTypeInfo = Depends(get_key_type), # type: ignore
+):
"""
Create a new mint for this wallet.
"""
@@ -86,7 +91,7 @@ async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key
@cashu_ext.delete("/api/v1/mints/{cashu_id}")
async def api_cashu_delete(
- cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
+ cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) # type: ignore
):
"""
Delete an existing cashu mint.
From 12848938e04ab9c9581ed2ca33137cfbf259dcd7 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 24 Nov 2022 15:24:16 +0100
Subject: [PATCH 434/696] better error
---
lnbits/extensions/cashu/views_api.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 4edd9229..3b51bb6a 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -261,8 +261,8 @@ async def melt_coins(
# THIS IS NECESSARY BECAUSE THE CASHU BACKEND WILL ACCEPT ANY VALID
# TOKENS
assert all([p.id == cashu.keyset_id for p in proofs]), HTTPException(
- status_code=HTTPStatus.BAD_REQUEST,
- detail="Proofs include tokens from another mint.",
+ status_code=HTTPStatus.METHOD_NOT_ALLOWED,
+ detail="Tokens are from another mint.",
)
assert all([ledger._verify_proof(p) for p in proofs]), HTTPException(
@@ -355,8 +355,8 @@ async def split(
# THIS IS NECESSARY BECAUSE THE CASHU BACKEND WILL ACCEPT ANY VALID
# TOKENS
assert all([p.id == cashu.keyset_id for p in proofs]), HTTPException(
- status_code=HTTPStatus.BAD_REQUEST,
- detail="Proofs include tokens from another mint.",
+ status_code=HTTPStatus.METHOD_NOT_ALLOWED,
+ detail="Tokens are from another mint.",
)
amount = payload.amount
From f390bc8798549821f8be3fa340c38fdbf5b255bc Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Thu, 24 Nov 2022 15:25:37 +0100
Subject: [PATCH 435/696] mypy
---
lnbits/extensions/cashu/tasks.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index 11eb2fd2..bb2d324b 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -28,6 +28,6 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None:
- if not payment.extra.get("tag") == "cashu":
+ if payment.extra and not payment.extra.get("tag") == "cashu":
return
return
From e3510e5133261943add2562f3bf2133e3cc72c32 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Fri, 25 Nov 2022 14:19:53 +0100
Subject: [PATCH 436/696] check skip
---
tools/conv.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/conv.py b/tools/conv.py
index 2b18c1aa..d4b5a43c 100644
--- a/tools/conv.py
+++ b/tools/conv.py
@@ -212,7 +212,7 @@ if os.path.isdir(args.sqlite_path):
else:
files = [args.sqlite_path]
-excluded_exts = ["ext_lnurlpos.sqlite3"]
+excluded_exts = ["ext_lnurlpos.sqlite3", "cashu.sqlite3"]
for file in files:
filename = os.path.basename(file)
if filename.startswith("ext_") and filename not in excluded_exts:
From 074706c4225b70f6f632bdb5b2199424a703341f Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 26 Nov 2022 01:11:17 +0100
Subject: [PATCH 437/696] pass migrations
---
tools/conv.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/tools/conv.py b/tools/conv.py
index d4b5a43c..1192db93 100644
--- a/tools/conv.py
+++ b/tools/conv.py
@@ -203,7 +203,8 @@ if os.path.isdir(args.sqlite_path):
file = os.path.join(args.sqlite_path, "database.sqlite3")
check_db_versions(file)
if not args.extensions_only:
- migrate_core(file, exclude_tables)
+ # migrate_core(file, exclude_tables)
+ pass
if os.path.isdir(args.sqlite_path):
files = [
@@ -216,4 +217,5 @@ excluded_exts = ["ext_lnurlpos.sqlite3", "cashu.sqlite3"]
for file in files:
filename = os.path.basename(file)
if filename.startswith("ext_") and filename not in excluded_exts:
- migrate_ext(file)
+ # migrate_ext(file)
+ pass
From c79b121fd23d0623a631397c73ab6bf03ab1fa42 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 26 Nov 2022 01:14:06 +0100
Subject: [PATCH 438/696] pass migrations
---
tools/conv.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/tools/conv.py b/tools/conv.py
index 1192db93..ec179b75 100644
--- a/tools/conv.py
+++ b/tools/conv.py
@@ -203,8 +203,7 @@ if os.path.isdir(args.sqlite_path):
file = os.path.join(args.sqlite_path, "database.sqlite3")
check_db_versions(file)
if not args.extensions_only:
- # migrate_core(file, exclude_tables)
- pass
+ migrate_core(file, exclude_tables)
if os.path.isdir(args.sqlite_path):
files = [
From e12d038e22e34f2488bdc9100738c65ad155dd02 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 26 Nov 2022 01:14:31 +0100
Subject: [PATCH 439/696] pass migrations
---
tools/conv.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/tools/conv.py b/tools/conv.py
index ec179b75..2f2285cc 100644
--- a/tools/conv.py
+++ b/tools/conv.py
@@ -212,9 +212,8 @@ if os.path.isdir(args.sqlite_path):
else:
files = [args.sqlite_path]
-excluded_exts = ["ext_lnurlpos.sqlite3", "cashu.sqlite3"]
+excluded_exts = ["ext_lnurlpos.sqlite3", "ext_cashu.sqlite3"]
for file in files:
filename = os.path.basename(file)
if filename.startswith("ext_") and filename not in excluded_exts:
- # migrate_ext(file)
- pass
+ migrate_ext(file)
From 23308fe6cf4b54c6e5ca63ef1943a9bd1664936b Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 26 Nov 2022 01:31:32 +0100
Subject: [PATCH 440/696] skip dbversions
---
tools/conv.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/tools/conv.py b/tools/conv.py
index 2f2285cc..dbe75063 100644
--- a/tools/conv.py
+++ b/tools/conv.py
@@ -128,6 +128,10 @@ def migrate_db(file: str, schema: str, exclude_tables: List[str] = []):
for table in tables:
tableName = table[0]
+ print(f"Migrating table {tableName}")
+ # hard coded skip for dbversions (already produced during startup)
+ if tableName == "dbversions":
+ continue
if tableName in exclude_tables:
continue
@@ -212,7 +216,7 @@ if os.path.isdir(args.sqlite_path):
else:
files = [args.sqlite_path]
-excluded_exts = ["ext_lnurlpos.sqlite3", "ext_cashu.sqlite3"]
+excluded_exts = ["ext_lnurlpos.sqlite3"]
for file in files:
filename = os.path.basename(file)
if filename.startswith("ext_") and filename not in excluded_exts:
From 175516f8e4bc8ea6263237bce055263379aee4d2 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 26 Nov 2022 02:14:39 +0100
Subject: [PATCH 441/696] update to cashu 0.5.3 with db fix
---
lnbits/extensions/cashu/crud.py | 182 ---------------------------
lnbits/extensions/cashu/views_api.py | 11 +-
pyproject.toml | 2 +-
3 files changed, 7 insertions(+), 188 deletions(-)
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index cbeedc12..773a11fd 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -42,25 +42,6 @@ async def create_cashu(
return cashu
-# async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
-# entropy = bytes([random.getrandbits(8) for i in range(16)])
-# mnemonic = bip39.mnemonic_from_bytes(entropy)
-# seed = bip39.mnemonic_to_seed(mnemonic)
-# root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
-
-# bip44_xprv = root.derive("m/44h/1h/0h")
-# bip44_xpub = bip44_xprv.to_public()
-
-# await db.execute(
-# "UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?",
-# bip44_xprv.to_base58(),
-# bip44_xpub.to_base58(),
-# cashu_id,
-# )
-# row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
-# return Cashu(**row) if row else None
-
-
async def get_cashu(cashu_id) -> Optional[Cashu]:
row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
return Cashu(**row) if row else None
@@ -80,166 +61,3 @@ async def get_cashus(wallet_ids: Union[str, List[str]]) -> List[Cashu]:
async def delete_cashu(cashu_id) -> None:
await db.execute("DELETE FROM cashu.cashu WHERE id = ?", (cashu_id,))
-
-
-# ##########################################
-# ###############MINT STUFF#################
-# ##########################################
-
-
-# async def store_promises(
-# amounts: List[int], B_s: List[str], C_s: List[str], cashu_id: str
-# ):
-# for amount, B_, C_ in zip(amounts, B_s, C_s):
-# await store_promise(amount, B_, C_, cashu_id)
-
-
-# async def store_promise(amount: int, B_: str, C_: str, cashu_id: str):
-# promise_id = urlsafe_short_hash()
-
-# await db.execute(
-# """
-# INSERT INTO cashu.promises
-# (id, amount, B_b, C_b, cashu_id)
-# VALUES (?, ?, ?, ?, ?)
-# """,
-# (promise_id, amount, str(B_), str(C_), cashu_id),
-# )
-
-
-# async def get_promises(cashu_id) -> Optional[Cashu]:
-# row = await db.fetchall(
-# "SELECT * FROM cashu.promises WHERE cashu_id = ?", (cashu_id,)
-# )
-# return Promises(**row) if row else None
-
-
-# async def get_proofs_used(
-# db: Database,
-# conn: Optional[Connection] = None,
-# ):
-
-# rows = await (conn or db).fetchall(
-# """
-# SELECT secret from cashu.proofs_used
-# """
-# )
-# return [row[0] for row in rows]
-
-
-# async def invalidate_proof(cashu_id: str, proof: Proof):
-# invalidate_proof_id = urlsafe_short_hash()
-# await db.execute(
-# """
-# INSERT INTO cashu.proofs_used
-# (id, amount, C, secret, cashu_id)
-# VALUES (?, ?, ?, ?, ?)
-# """,
-# (invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id),
-# )
-
-
-# ########################################
-# ############ MINT INVOICES #############
-# ########################################
-
-
-# async def store_lightning_invoice(cashu_id: str, invoice: Invoice):
-# await db.execute(
-# """
-# INSERT INTO cashu.invoices
-# (cashu_id, amount, pr, hash, issued)
-# VALUES (?, ?, ?, ?, ?)
-# """,
-# (
-# cashu_id,
-# invoice.amount,
-# invoice.pr,
-# invoice.hash,
-# invoice.issued,
-# ),
-# )
-
-
-# async def get_lightning_invoice(cashu_id: str, hash: str):
-# row = await db.fetchone(
-# """
-# SELECT * from cashu.invoices
-# WHERE cashu_id =? AND hash = ?
-# """,
-# (
-# cashu_id,
-# hash,
-# ),
-# )
-# return Invoice.from_row(row)
-
-
-# async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
-# await db.execute(
-# "UPDATE cashu.invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
-# (
-# issued,
-# cashu_id,
-# hash,
-# ),
-# )
-
-
-##############################
-######### KEYSETS ############
-##############################
-
-
-# async def store_keyset(
-# keyset: MintKeyset,
-# db: Database = None,
-# conn: Optional[Connection] = None,
-# ):
-
-# await (conn or db).execute( # type: ignore
-# """
-# INSERT INTO cashu.keysets
-# (id, derivation_path, valid_from, valid_to, first_seen, active, version)
-# VALUES (?, ?, ?, ?, ?, ?, ?)
-# """,
-# (
-# keyset.id,
-# keyset.derivation_path,
-# keyset.valid_from or db.timestamp_now,
-# keyset.valid_to or db.timestamp_now,
-# keyset.first_seen or db.timestamp_now,
-# True,
-# keyset.version,
-# ),
-# )
-
-
-# async def get_keyset(
-# id: str = None,
-# derivation_path: str = "",
-# db: Database = None,
-# conn: Optional[Connection] = None,
-# ):
-# clauses = []
-# values: List[Any] = []
-# clauses.append("active = ?")
-# values.append(True)
-# if id:
-# clauses.append("id = ?")
-# values.append(id)
-# if derivation_path:
-# clauses.append("derivation_path = ?")
-# values.append(derivation_path)
-# where = ""
-# if clauses:
-# where = f"WHERE {' AND '.join(clauses)}"
-
-# rows = await (conn or db).fetchall( # type: ignore
-# f"""
-# SELECT * from cashu.keysets
-# {where}
-# """,
-# tuple(values),
-# )
-# return [MintKeyset.from_row(row) for row in rows]
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index 3b51bb6a..ad253abf 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -262,7 +262,7 @@ async def melt_coins(
# TOKENS
assert all([p.id == cashu.keyset_id for p in proofs]), HTTPException(
status_code=HTTPStatus.METHOD_NOT_ALLOWED,
- detail="Tokens are from another mint.",
+ detail="Error: Tokens are from another mint.",
)
assert all([ledger._verify_proof(p) for p in proofs]), HTTPException(
@@ -354,10 +354,11 @@ async def split(
# !!!!!!! MAKE SURE THAT PROOFS ARE ONLY FROM THIS CASHU KEYSET ID
# THIS IS NECESSARY BECAUSE THE CASHU BACKEND WILL ACCEPT ANY VALID
# TOKENS
- assert all([p.id == cashu.keyset_id for p in proofs]), HTTPException(
- status_code=HTTPStatus.METHOD_NOT_ALLOWED,
- detail="Tokens are from another mint.",
- )
+ if not all([p.id == cashu.keyset_id for p in proofs]):
+ raise HTTPException(
+ status_code=HTTPStatus.METHOD_NOT_ALLOWED,
+ detail="Error: Tokens are from another mint.",
+ )
amount = payload.amount
outputs = payload.outputs.blinded_messages
diff --git a/pyproject.toml b/pyproject.toml
index dc7215d5..fd9ecd50 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,7 +63,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
-cashu = "^0.5.1"
+cashu = "^0.5.3"
[tool.poetry.dev-dependencies]
From ecec9e959622e9719e614ce7d11e1222dc860fd1 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 26 Nov 2022 02:21:07 +0100
Subject: [PATCH 442/696] consider bool
---
tools/conv.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tools/conv.py b/tools/conv.py
index dbe75063..4a9ad0d5 100644
--- a/tools/conv.py
+++ b/tools/conv.py
@@ -155,7 +155,7 @@ def build_insert_query(schema, tableName, columns):
def to_column_type(columnType):
if columnType == "TIMESTAMP":
return "to_timestamp(%s)"
- if columnType == "BOOLEAN":
+ if columnType in ["BOOLEAN", "BOOL"]:
return "%s::boolean"
return "%s"
From e790a5567bb24b75805b6894fc423540e3fba629 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 26 Nov 2022 03:01:06 +0100
Subject: [PATCH 443/696] update cashu
---
lnbits/extensions/cashu/tasks.py | 2 +-
.../cashu/templates/cashu/index.html | 74 ++++++++++++-------
poetry.lock | 52 ++++++-------
pyproject.toml | 2 +-
4 files changed, 75 insertions(+), 55 deletions(-)
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index bb2d324b..9de17a1c 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -14,7 +14,7 @@ from .crud import get_cashu
async def startup_cashu_mint():
await migrate_databases(db, migrations)
await ledger.load_used_proofs()
- await ledger.init_keysets()
+ await ledger.init_keysets(autosave=False)
pass
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index eb90b2c1..2599669c 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -4,9 +4,22 @@
- New Mint
+ Cashu mint and wallet
+
+
+ Here you can create multiple cashu mints that you can share. Each mint
+ can service many users but all ecash tokens of a mint are only valid
+ inside that mint and not across different mints. To exchange funds
+ between mints, use Lightning payments.
+
+ Important
+
+
+ If you are the operator of this LNbits instance, make sure to set
+ CASHU_PRIVATE_KEY="randomkey" in your configuration file. Do not
+ create mints before setting the key and do not change the key once
+ set.
+
@@ -84,6 +97,13 @@
{% endraw %}
+
New Mint
@@ -220,36 +240,36 @@
columns: [
{name: 'id', align: 'left', label: 'Mint ID', field: 'id'},
{name: 'name', align: 'left', label: 'Name', field: 'name'},
- {
- name: 'tickershort',
- align: 'left',
- label: 'Ticker',
- field: 'tickershort'
- },
+ // {
+ // name: 'tickershort',
+ // align: 'left',
+ // label: 'Ticker',
+ // field: 'tickershort'
+ // },
{
name: 'wallet',
align: 'left',
label: 'Mint wallet',
field: 'wallet'
- },
- {
- name: 'fraction',
- align: 'left',
- label: 'Using fraction',
- field: 'fraction'
- },
- {
- name: 'maxsats',
- align: 'left',
- label: 'Max Sats',
- field: 'maxsats'
- },
- {
- name: 'coins',
- align: 'left',
- label: 'No. of coins',
- field: 'coins'
}
+ // {
+ // name: 'fraction',
+ // align: 'left',
+ // label: 'Using fraction',
+ // field: 'fraction'
+ // },
+ // {
+ // name: 'maxsats',
+ // align: 'left',
+ // label: 'Max Sats',
+ // field: 'maxsats'
+ // },
+ // {
+ // name: 'coins',
+ // align: 'left',
+ // label: 'No. of coins',
+ // field: 'coins'
+ // }
],
pagination: {
rowsPerPage: 10
diff --git a/poetry.lock b/poetry.lock
index 6ce5a364..5e07f04a 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -499,8 +499,8 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
[[package]]
name = "platformdirs"
-version = "2.5.2"
-description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
+version = "2.5.4"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
@@ -526,7 +526,7 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "protobuf"
-version = "4.21.8"
+version = "4.21.9"
description = ""
category = "main"
optional = false
@@ -801,7 +801,7 @@ cffi = ">=1.3.0"
[[package]]
name = "setuptools"
-version = "65.5.0"
+version = "65.6.3"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main"
optional = false
@@ -809,7 +809,7 @@ python-versions = ">=3.7"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
-testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
@@ -915,7 +915,7 @@ python-versions = ">=3.6"
[[package]]
name = "types-protobuf"
-version = "3.20.4.1"
+version = "3.20.4.6"
description = "Typing stubs for protobuf"
category = "dev"
optional = false
@@ -1543,28 +1543,28 @@ pathspec = [
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
]
platformdirs = [
- {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
- {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
+ {file = "platformdirs-2.5.4-py3-none-any.whl", hash = "sha256:af0276409f9a02373d540bf8480021a048711d572745aef4b7842dad245eba10"},
+ {file = "platformdirs-2.5.4.tar.gz", hash = "sha256:1006647646d80f16130f052404c6b901e80ee4ed6bef6792e1f238a8969106f7"},
]
pluggy = [
{file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"},
{file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"},
]
protobuf = [
- {file = "protobuf-4.21.8-cp310-abi3-win32.whl", hash = "sha256:c252c55ee15175aa1b21b7b9896e6add5162d066d5202e75c39f96136f08cce3"},
- {file = "protobuf-4.21.8-cp310-abi3-win_amd64.whl", hash = "sha256:809ca0b225d3df42655a12f311dd0f4148a943c51f1ad63c38343e457492b689"},
- {file = "protobuf-4.21.8-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bbececaf3cfea9ea65ebb7974e6242d310d2a7772a6f015477e0d79993af4511"},
- {file = "protobuf-4.21.8-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:b02eabb9ebb1a089ed20626a90ad7a69cee6bcd62c227692466054b19c38dd1f"},
- {file = "protobuf-4.21.8-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:4761201b93e024bb70ee3a6a6425d61f3152ca851f403ba946fb0cde88872661"},
- {file = "protobuf-4.21.8-cp37-cp37m-win32.whl", hash = "sha256:f2d55ff22ec300c4d954d3b0d1eeb185681ec8ad4fbecff8a5aee6a1cdd345ba"},
- {file = "protobuf-4.21.8-cp37-cp37m-win_amd64.whl", hash = "sha256:c5f94911dd8feb3cd3786fc90f7565c9aba7ce45d0f254afd625b9628f578c3f"},
- {file = "protobuf-4.21.8-cp38-cp38-win32.whl", hash = "sha256:b37b76efe84d539f16cba55ee0036a11ad91300333abd213849cbbbb284b878e"},
- {file = "protobuf-4.21.8-cp38-cp38-win_amd64.whl", hash = "sha256:2c92a7bfcf4ae76a8ac72e545e99a7407e96ffe52934d690eb29a8809ee44d7b"},
- {file = "protobuf-4.21.8-cp39-cp39-win32.whl", hash = "sha256:89d641be4b5061823fa0e463c50a2607a97833e9f8cfb36c2f91ef5ccfcc3861"},
- {file = "protobuf-4.21.8-cp39-cp39-win_amd64.whl", hash = "sha256:bc471cf70a0f53892fdd62f8cd4215f0af8b3f132eeee002c34302dff9edd9b6"},
- {file = "protobuf-4.21.8-py2.py3-none-any.whl", hash = "sha256:a55545ce9eec4030cf100fcb93e861c622d927ef94070c1a3c01922902464278"},
- {file = "protobuf-4.21.8-py3-none-any.whl", hash = "sha256:0f236ce5016becd989bf39bd20761593e6d8298eccd2d878eda33012645dc369"},
- {file = "protobuf-4.21.8.tar.gz", hash = "sha256:427426593b55ff106c84e4a88cac855175330cb6eb7e889e85aaa7b5652b686d"},
+ {file = "protobuf-4.21.9-cp310-abi3-win32.whl", hash = "sha256:6e0be9f09bf9b6cf497b27425487706fa48c6d1632ddd94dab1a5fe11a422392"},
+ {file = "protobuf-4.21.9-cp310-abi3-win_amd64.whl", hash = "sha256:a7d0ea43949d45b836234f4ebb5ba0b22e7432d065394b532cdca8f98415e3cf"},
+ {file = "protobuf-4.21.9-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:b5ab0b8918c136345ff045d4b3d5f719b505b7c8af45092d7f45e304f55e50a1"},
+ {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:2c9c2ed7466ad565f18668aa4731c535511c5d9a40c6da39524bccf43e441719"},
+ {file = "protobuf-4.21.9-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:e575c57dc8b5b2b2caa436c16d44ef6981f2235eb7179bfc847557886376d740"},
+ {file = "protobuf-4.21.9-cp37-cp37m-win32.whl", hash = "sha256:9227c14010acd9ae7702d6467b4625b6fe853175a6b150e539b21d2b2f2b409c"},
+ {file = "protobuf-4.21.9-cp37-cp37m-win_amd64.whl", hash = "sha256:a419cc95fca8694804709b8c4f2326266d29659b126a93befe210f5bbc772536"},
+ {file = "protobuf-4.21.9-cp38-cp38-win32.whl", hash = "sha256:5b0834e61fb38f34ba8840d7dcb2e5a2f03de0c714e0293b3963b79db26de8ce"},
+ {file = "protobuf-4.21.9-cp38-cp38-win_amd64.whl", hash = "sha256:84ea107016244dfc1eecae7684f7ce13c788b9a644cd3fca5b77871366556444"},
+ {file = "protobuf-4.21.9-cp39-cp39-win32.whl", hash = "sha256:f9eae277dd240ae19bb06ff4e2346e771252b0e619421965504bd1b1bba7c5fa"},
+ {file = "protobuf-4.21.9-cp39-cp39-win_amd64.whl", hash = "sha256:6e312e280fbe3c74ea9e080d9e6080b636798b5e3939242298b591064470b06b"},
+ {file = "protobuf-4.21.9-py2.py3-none-any.whl", hash = "sha256:7eb8f2cc41a34e9c956c256e3ac766cf4e1a4c9c925dc757a41a01be3e852965"},
+ {file = "protobuf-4.21.9-py3-none-any.whl", hash = "sha256:48e2cd6b88c6ed3d5877a3ea40df79d08374088e89bedc32557348848dff250b"},
+ {file = "protobuf-4.21.9.tar.gz", hash = "sha256:61f21493d96d2a77f9ca84fefa105872550ab5ef71d21c458eb80edcf4885a99"},
]
psycopg2-binary = [
{file = "psycopg2-binary-2.9.1.tar.gz", hash = "sha256:b0221ca5a9837e040ebf61f48899926b5783668b7807419e4adae8175a31f773"},
@@ -1799,8 +1799,8 @@ secp256k1 = [
{file = "secp256k1-0.14.0.tar.gz", hash = "sha256:82c06712d69ef945220c8b53c1a0d424c2ff6a1f64aee609030df79ad8383397"},
]
setuptools = [
- {file = "setuptools-65.5.0-py3-none-any.whl", hash = "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"},
- {file = "setuptools-65.5.0.tar.gz", hash = "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17"},
+ {file = "setuptools-65.6.3-py3-none-any.whl", hash = "sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54"},
+ {file = "setuptools-65.6.3.tar.gz", hash = "sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75"},
]
shortuuid = [
{file = "shortuuid-1.0.1-py3-none-any.whl", hash = "sha256:492c7402ff91beb1342a5898bd61ea953985bf24a41cd9f247409aa2e03c8f77"},
@@ -1896,8 +1896,8 @@ typed-ast = [
{file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"},
]
types-protobuf = [
- {file = "types-protobuf-3.20.4.1.tar.gz", hash = "sha256:67df7cc7ec85d114db2664a8ae8905543e75fb5edbed437dabc9e9fb8f8fcf9e"},
- {file = "types_protobuf-3.20.4.1-py3-none-any.whl", hash = "sha256:c227975ffd0f6a1eb1754e9a3aa9ca3b12265e63b462e9761e824c41fd25331c"},
+ {file = "types-protobuf-3.20.4.6.tar.gz", hash = "sha256:ba27443c592bbec1629dd69494a24c84461c63f0d3b7d648ce258aaae9680965"},
+ {file = "types_protobuf-3.20.4.6-py3-none-any.whl", hash = "sha256:ab2d315ba82246b83d28f8797c98dc0fe1dd5cfd187909e56faf87239aedaae3"},
]
typing-extensions = [
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
diff --git a/pyproject.toml b/pyproject.toml
index fd9ecd50..bcb9253c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,7 +63,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
-cashu = "^0.5.3"
+cashu = "^0.5.4"
[tool.poetry.dev-dependencies]
From 2a9bec55b91d944fec153d9b1053d3d672d9892a Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sat, 26 Nov 2022 15:26:32 +0100
Subject: [PATCH 444/696] lock cashu version
---
pyproject.toml | 2 +-
requirements.txt | 6 +++---
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index bcb9253c..610878ff 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,7 +63,7 @@ protobuf = "^4.21.6"
Cerberus = "^1.3.4"
async-timeout = "^4.0.2"
pyln-client = "0.11.1"
-cashu = "^0.5.4"
+cashu = "0.5.4"
[tool.poetry.dev-dependencies]
diff --git a/requirements.txt b/requirements.txt
index e8001e70..06e8642f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,7 +7,7 @@ attrs==22.1.0 ; python_version >= "3.7" and python_version < "4.0"
base58==2.1.1 ; python_version >= "3.7" and python_version < "4.0"
bech32==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
bitstring==3.1.9 ; python_version >= "3.7" and python_version < "4.0"
-cashu==0.4.2 ; python_version >= "3.7" and python_version < "4.0"
+cashu==0.5.4 ; python_version >= "3.7" and python_version < "4.0"
cerberus==1.3.4 ; python_version >= "3.7" and python_version < "4.0"
certifi==2022.9.24 ; python_version >= "3.7" and python_version < "4.0"
cffi==1.15.1 ; python_version >= "3.7" and python_version < "4.0"
@@ -38,7 +38,7 @@ outcome==1.2.0 ; python_version >= "3.7" and python_version < "4.0"
packaging==21.3 ; python_version >= "3.7" and python_version < "4.0"
pathlib2==2.3.7.post1 ; python_version >= "3.7" and python_version < "4.0"
pluggy==1.0.0 ; python_version >= "3.7" and python_version < "4.0"
-protobuf==4.21.8 ; python_version >= "3.7" and python_version < "4.0"
+protobuf==4.21.9 ; python_version >= "3.7" and python_version < "4.0"
psycopg2-binary==2.9.1 ; python_version >= "3.7" and python_version < "4.0"
py==1.11.0 ; python_version >= "3.7" and python_version < "4.0"
pycparser==2.21 ; python_version >= "3.7" and python_version < "4.0"
@@ -62,7 +62,7 @@ requests==2.27.1 ; python_version >= "3.7" and python_version < "4.0"
rfc3986==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
rfc3986[idna2008]==1.5.0 ; python_version >= "3.7" and python_version < "4.0"
secp256k1==0.14.0 ; python_version >= "3.7" and python_version < "4.0"
-setuptools==65.5.0 ; python_version >= "3.7" and python_version < "4.0"
+setuptools==65.6.3 ; python_version >= "3.7" and python_version < "4.0"
shortuuid==1.0.1 ; python_version >= "3.7" and python_version < "4.0"
six==1.16.0 ; python_version >= "3.7" and python_version < "4.0"
sniffio==1.3.0 ; python_version >= "3.7" and python_version < "4.0"
From 9c8830d4afffe6bb33946586571f04e113a22f87 Mon Sep 17 00:00:00 2001
From: callebtc <93376500+callebtc@users.noreply.github.com>
Date: Sun, 27 Nov 2022 17:33:24 +0100
Subject: [PATCH 445/696] fix padding of bottom bar
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index a8016ba7..e96e8188 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -305,7 +305,7 @@ page_container %}
Date: Sun, 27 Nov 2022 17:38:52 +0100
Subject: [PATCH 446/696] fix padding of bottom bar
---
lnbits/extensions/cashu/templates/cashu/wallet.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index e96e8188..a133f592 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -3,7 +3,7 @@
page_container %}
-
+
From 5774180ce38141de80e725319aaa4253ebf99e1f Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 23 Nov 2022 17:04:08 +0200
Subject: [PATCH 447/696] fix: oldObj values lost
---
lnbits/extensions/satspay/static/js/utils.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/satspay/static/js/utils.js b/lnbits/extensions/satspay/static/js/utils.js
index 9b4abbfc..5ce21a5d 100644
--- a/lnbits/extensions/satspay/static/js/utils.js
+++ b/lnbits/extensions/satspay/static/js/utils.js
@@ -14,7 +14,7 @@ const retryWithDelay = async function (fn, retryCount = 0) {
}
const mapCharge = (obj, oldObj = {}) => {
- const charge = _.clone(obj)
+ const charge = {...obj, ...oldObj}
charge.progress = obj.time_left < 0 ? 1 : 1 - obj.time_left / obj.time
charge.time = minutesToTime(obj.time)
From c59e5025197d5dd3cd6467bfec69c464b55398c6 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Wed, 23 Nov 2022 17:32:34 +0200
Subject: [PATCH 448/696] fix: remove de-selected wallet
---
lnbits/extensions/satspay/templates/satspay/index.html | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html
index 396200cf..af0dacc0 100644
--- a/lnbits/extensions/satspay/templates/satspay/index.html
+++ b/lnbits/extensions/satspay/templates/satspay/index.html
@@ -409,7 +409,7 @@
balance: null,
walletLinks: [],
chargeLinks: [],
- onchainwallet: '',
+ onchainwallet: null,
rescanning: false,
mempool: {
endpoint: ''
@@ -505,6 +505,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
@@ -577,7 +578,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 ? this.lnbitswallet : null
+ data.onchainwallet = data.onchain ? this.onchainwallet?.id : null
this.createCharge(wallet, data)
},
refreshActiveChargesBalance: async function () {
From 755ce9c7da23c0f58a1984654ff015b81c36154a Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 24 Nov 2022 10:42:28 +0200
Subject: [PATCH 449/696] fix: use `lnbitswallet` form data
---
lnbits/extensions/satspay/templates/satspay/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/satspay/templates/satspay/index.html b/lnbits/extensions/satspay/templates/satspay/index.html
index af0dacc0..a1ca2de7 100644
--- a/lnbits/extensions/satspay/templates/satspay/index.html
+++ b/lnbits/extensions/satspay/templates/satspay/index.html
@@ -578,7 +578,7 @@
const data = this.formDialogCharge.data
data.amount = parseInt(data.amount)
data.time = parseInt(data.time)
- data.lnbitswallet = data.lnbits ? this.lnbitswallet : null
+ data.lnbitswallet = data.lnbits ? data.lnbitswallet : null
data.onchainwallet = data.onchain ? this.onchainwallet?.id : null
this.createCharge(wallet, data)
},
From 5ddad18f64cd05c95a5f404ab1dc2964db39dd1f Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 24 Nov 2022 11:39:35 +0200
Subject: [PATCH 450/696] fix: oldObj should not overwrite properties
---
lnbits/extensions/satspay/static/js/utils.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/satspay/static/js/utils.js b/lnbits/extensions/satspay/static/js/utils.js
index 5ce21a5d..92927955 100644
--- a/lnbits/extensions/satspay/static/js/utils.js
+++ b/lnbits/extensions/satspay/static/js/utils.js
@@ -14,15 +14,14 @@ const retryWithDelay = async function (fn, retryCount = 0) {
}
const mapCharge = (obj, oldObj = {}) => {
- const charge = {...obj, ...oldObj}
+ const charge = {...oldObj, ...obj}
charge.progress = obj.time_left < 0 ? 1 : 1 - obj.time_left / obj.time
charge.time = minutesToTime(obj.time)
charge.timeLeft = minutesToTime(obj.time_left)
- charge.expanded = false
charge.displayUrl = ['/satspay/', obj.id].join('')
- charge.expanded = oldObj.expanded
+ charge.expanded = oldObj.expanded || false
charge.pendingBalance = oldObj.pendingBalance || 0
return charge
}
From a301b97766dc70f126cc7cd851998ec80c802bc1 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 24 Nov 2022 16:55:35 +0200
Subject: [PATCH 451/696] refactor: remove duplicate code
---
lnbits/extensions/satspay/crud.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py
index 23d391b7..512bcaa3 100644
--- a/lnbits/extensions/satspay/crud.py
+++ b/lnbits/extensions/satspay/crud.py
@@ -98,7 +98,7 @@ async def delete_charge(charge_id: str) -> None:
await db.execute("DELETE FROM satspay.charges WHERE id = ?", (charge_id,))
-async def check_address_balance(charge_id: str) -> List[Charges]:
+async def check_address_balance(charge_id: str) -> Optional[Charges]:
charge = await get_charge(charge_id)
if not charge.paid:
if charge.onchainaddress:
@@ -120,8 +120,7 @@ async def check_address_balance(charge_id: str) -> List[Charges]:
if invoice_status["paid"]:
return await update_charge(charge_id=charge_id, balance=charge.amount)
- row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,))
- return Charges.from_row(row) if row else None
+ return await get_charge(charge_id)
async def get_charge_config(charge_id: str):
From 948e7f0a4e220d937e7d48a3008e282e5ef84e36 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 24 Nov 2022 16:57:59 +0200
Subject: [PATCH 452/696] fix: call webhook in the background (from the
`task.py`)
---
lnbits/extensions/satspay/tasks.py | 24 ++++++++++++++++++++++--
lnbits/extensions/satspay/views_api.py | 11 -----------
2 files changed, 22 insertions(+), 13 deletions(-)
diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py
index 46c16bbc..2cac6431 100644
--- a/lnbits/extensions/satspay/tasks.py
+++ b/lnbits/extensions/satspay/tasks.py
@@ -1,5 +1,6 @@
import asyncio
+import httpx
from loguru import logger
from lnbits.core.models import Payment
@@ -7,7 +8,8 @@ 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 .crud import get_ticket, set_ticket_paid
+from .helpers import compact_charge
+from .models import Charges
async def wait_for_paid_invoices():
@@ -30,4 +32,22 @@ async def on_invoice_paid(payment: Payment) -> None:
return
await payment.set_pending(False)
- await check_address_balance(charge_id=charge.id)
+ charge = await check_address_balance(charge_id=charge.id)
+
+ if charge.paid and charge.webhook:
+ await call_webhook(charge)
+
+
+async def call_webhook(charge: Charges):
+ async with httpx.AsyncClient() as client:
+ try:
+ r = await client.post(
+ charge.webhook,
+ json=compact_charge(charge),
+ timeout=40,
+ )
+ except AssertionError:
+ charge.webhook = None
+ except Exception as e:
+ logger.warning(f"Failed to call webhook for charge {charge.id}")
+ logger.warning(e)
diff --git a/lnbits/extensions/satspay/views_api.py b/lnbits/extensions/satspay/views_api.py
index e1b87c41..e516219e 100644
--- a/lnbits/extensions/satspay/views_api.py
+++ b/lnbits/extensions/satspay/views_api.py
@@ -1,6 +1,5 @@
from http import HTTPStatus
-import httpx
from fastapi.params import Depends
from starlette.exceptions import HTTPException
@@ -119,16 +118,6 @@ 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},
From 13203055e91a8b4fc927e4b9704526c97b34e8cd Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 24 Nov 2022 17:08:32 +0200
Subject: [PATCH 453/696] fix: remove wallet `inkey`
---
.../satspay/templates/satspay/display.html | 14 --------------
lnbits/extensions/satspay/views.py | 1 -
2 files changed, 15 deletions(-)
diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html
index 12288c80..5d0ebe40 100644
--- a/lnbits/extensions/satspay/templates/satspay/display.html
+++ b/lnbits/extensions/satspay/templates/satspay/display.html
@@ -317,16 +317,6 @@
}
},
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
try {
@@ -432,10 +422,6 @@
else this.payOnchain()
await this.checkBalances()
- // empty for onchain
- this.wallet.inkey = '{{ wallet_inkey }}'
- this.startPaymentNotifier()
-
if (!this.charge.paid) {
this.loopRefresh()
}
diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py
index b789bf8f..d98a93d9 100644
--- a/lnbits/extensions/satspay/views.py
+++ b/lnbits/extensions/satspay/views.py
@@ -41,7 +41,6 @@ async def display(request: Request, charge_id: str):
{
"request": request,
"charge_data": charge.dict(),
- "wallet_inkey": inkey,
"mempool_endpoint": mempool_endpoint,
},
)
From 683eb6992a20d012f9595f6a401d66e9ee49638c Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 24 Nov 2022 17:20:12 +0200
Subject: [PATCH 454/696] fix: mempool hostname
---
.../satspay/templates/satspay/display.html | 20 ++++++++++++++-----
lnbits/extensions/satspay/views.py | 10 +++++-----
2 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html
index 5d0ebe40..b69cc829 100644
--- a/lnbits/extensions/satspay/templates/satspay/display.html
+++ b/lnbits/extensions/satspay/templates/satspay/display.html
@@ -218,7 +218,7 @@
{}
}
},
+ computed: {
+ mempoolHostname: function () {
+ let hostname = new URL(this.mempoolEndpoint).hostname
+ if (this.network === 'Testnet') {
+ hostname += '/testnet'
+ }
+ return hostname
+ }
+ },
methods: {
checkBalances: async function () {
if (this.charge.hasStaleBalance) return
@@ -335,7 +345,7 @@
const {
bitcoin: {addresses: addressesAPI}
} = mempoolJS({
- hostname: new URL(this.mempool_endpoint).hostname
+ hostname: new URL(this.mempoolEndpoint).hostname
})
try {
@@ -378,10 +388,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)
diff --git a/lnbits/extensions/satspay/views.py b/lnbits/extensions/satspay/views.py
index d98a93d9..5029f168 100644
--- a/lnbits/extensions/satspay/views.py
+++ b/lnbits/extensions/satspay/views.py
@@ -30,17 +30,17 @@ 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
- )
+ if onchainwallet_config:
+ mempool_endpoint = onchainwallet_config.mempool_endpoint
+ network = onchainwallet_config.network
return satspay_renderer().TemplateResponse(
"satspay/display.html",
{
"request": request,
"charge_data": charge.dict(),
"mempool_endpoint": mempool_endpoint,
+ "network": network,
},
)
From 2ca48a26a6007431ad654e60456f637a62961ccc Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 24 Nov 2022 17:33:58 +0200
Subject: [PATCH 455/696] fix: refresh when both onchain and ln are present
---
lnbits/extensions/satspay/templates/satspay/display.html | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html
index b69cc829..57b0e7c6 100644
--- a/lnbits/extensions/satspay/templates/satspay/display.html
+++ b/lnbits/extensions/satspay/templates/satspay/display.html
@@ -328,7 +328,8 @@
},
methods: {
checkBalances: async function () {
- if (this.charge.hasStaleBalance) return
+ if (!this.charge.lnbitswallet && this.charge.hasOnchainStaleBalance)
+ return
try {
const {data} = await LNbits.api.request(
'GET',
@@ -353,7 +354,7 @@
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)
@@ -430,6 +431,7 @@
created: async function () {
if (this.charge.lnbitswallet) this.payInvoice()
else this.payOnchain()
+
await this.checkBalances()
if (!this.charge.paid) {
From bb16870c66da27ddb96c53bb23de014e6c2d4571 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Thu, 24 Nov 2022 17:39:40 +0200
Subject: [PATCH 456/696] fix: center back button
---
.../satspay/templates/satspay/display.html | 39 ++++++++++++-------
1 file changed, 24 insertions(+), 15 deletions(-)
diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html
index 57b0e7c6..cfc702b1 100644
--- a/lnbits/extensions/satspay/templates/satspay/display.html
+++ b/lnbits/extensions/satspay/templates/satspay/display.html
@@ -170,13 +170,17 @@
name="check"
style="color: green; font-size: 21.4em"
>
-
+
@@ -241,13 +245,17 @@
name="check"
style="color: green; font-size: 21.4em"
>
-
+
@@ -354,7 +362,8 @@
address: this.charge.onchainaddress
})
const newBalance = utxos.reduce((t, u) => t + u.value, 0)
- this.charge.hasOnchainStaleBalance = this.charge.balance === newBalance
+ this.charge.hasOnchainStaleBalance =
+ this.charge.balance === newBalance
this.pendingFunds = utxos
.filter(u => !u.status.confirmed)
From 871d7165390def25c9ef09f12c162443a37f7c61 Mon Sep 17 00:00:00 2001
From: Vlad Stan
Date: Fri, 25 Nov 2022 10:48:57 +0200
Subject: [PATCH 457/696] feat: store onchain charge config with the charge
---
lnbits/extensions/satspay/crud.py | 34 +++++++++++--------
lnbits/extensions/satspay/helpers.py | 13 +++++--
lnbits/extensions/satspay/migrations.py | 11 ++++++
lnbits/extensions/satspay/models.py | 7 ++++
lnbits/extensions/satspay/tasks.py | 4 +--
.../satspay/templates/satspay/display.html | 10 +++---
.../satspay/templates/satspay/index.html | 9 +++--
lnbits/extensions/satspay/views.py | 23 ++++++-------
lnbits/extensions/satspay/views_api.py | 9 ++---
9 files changed, 73 insertions(+), 47 deletions(-)
diff --git a/lnbits/extensions/satspay/crud.py b/lnbits/extensions/satspay/crud.py
index 512bcaa3..6c0c1cb3 100644
--- a/lnbits/extensions/satspay/crud.py
+++ b/lnbits/extensions/satspay/crud.py
@@ -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)
diff --git a/lnbits/extensions/satspay/helpers.py b/lnbits/extensions/satspay/helpers.py
index 2d15b557..1ebaa062 100644
--- a/lnbits/extensions/satspay/helpers.py
+++ b/lnbits/extensions/satspay/helpers.py
@@ -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
diff --git a/lnbits/extensions/satspay/migrations.py b/lnbits/extensions/satspay/migrations.py
index 87446c80..d5f6ba13 100644
--- a/lnbits/extensions/satspay/migrations.py
+++ b/lnbits/extensions/satspay/migrations.py
@@ -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"}';
+ """
+ )
diff --git a/lnbits/extensions/satspay/models.py b/lnbits/extensions/satspay/models.py
index daf63f42..8d2602e1 100644
--- a/lnbits/extensions/satspay/models.py
+++ b/lnbits/extensions/satspay/models.py
@@ -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)
diff --git a/lnbits/extensions/satspay/tasks.py b/lnbits/extensions/satspay/tasks.py
index 2cac6431..3a77501b 100644
--- a/lnbits/extensions/satspay/tasks.py
+++ b/lnbits/extensions/satspay/tasks.py
@@ -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:
diff --git a/lnbits/extensions/satspay/templates/satspay/display.html b/lnbits/extensions/satspay/templates/satspay/display.html
index cfc702b1..a24ed84c 100644
--- a/lnbits/extensions/satspay/templates/satspay/display.html
+++ b/lnbits/extensions/satspay/templates/satspay/display.html
@@ -109,7 +109,7 @@
@@ -131,7 +131,7 @@
@@ -222,7 +222,7 @@