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/844] 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/844] 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/844] 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/844] 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 14:38:55 +0100
Subject: [PATCH 005/844] Basics working
---
lnbits/extensions/gerty/README.md | 13 +
lnbits/extensions/gerty/__init__.py | 20 +
lnbits/extensions/gerty/config.json | 6 +
lnbits/extensions/gerty/crud.py | 51 +
lnbits/extensions/gerty/migrations.py | 17 +
lnbits/extensions/gerty/models.py | 21 +
lnbits/extensions/gerty/static/satoshi.json | 1099 +++++++++++++++++
.../gerty/templates/gerty/_api_docs.html | 79 ++
.../gerty/templates/gerty/index.html | 475 +++++++
lnbits/extensions/gerty/views.py | 23 +
lnbits/extensions/gerty/views_api.py | 134 ++
11 files changed, 1938 insertions(+)
create mode 100644 lnbits/extensions/gerty/README.md
create mode 100644 lnbits/extensions/gerty/__init__.py
create mode 100644 lnbits/extensions/gerty/config.json
create mode 100644 lnbits/extensions/gerty/crud.py
create mode 100644 lnbits/extensions/gerty/migrations.py
create mode 100644 lnbits/extensions/gerty/models.py
create mode 100644 lnbits/extensions/gerty/static/satoshi.json
create mode 100644 lnbits/extensions/gerty/templates/gerty/_api_docs.html
create mode 100644 lnbits/extensions/gerty/templates/gerty/index.html
create mode 100644 lnbits/extensions/gerty/views.py
create mode 100644 lnbits/extensions/gerty/views_api.py
diff --git a/lnbits/extensions/gerty/README.md b/lnbits/extensions/gerty/README.md
new file mode 100644
index 00000000..6186ab01
--- /dev/null
+++ b/lnbits/extensions/gerty/README.md
@@ -0,0 +1,13 @@
+# Gerty
+
+## Your desktop bitcoin assistant
+
+Buy here ` `
+
+blah blah blah
+
+### Usage
+
+1. Enable extension
+2. Fill out form
+3. point gerty at the server and give it the Gerty ID
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/__init__.py b/lnbits/extensions/gerty/__init__.py
new file mode 100644
index 00000000..6ec5f6b3
--- /dev/null
+++ b/lnbits/extensions/gerty/__init__.py
@@ -0,0 +1,20 @@
+import asyncio
+
+from fastapi import APIRouter
+from fastapi.staticfiles import StaticFiles
+
+from lnbits.db import Database
+from lnbits.helpers import template_renderer
+from lnbits.tasks import catch_everything_and_restart
+
+db = Database("ext_gerty")
+
+
+gerty_ext: APIRouter = APIRouter(prefix="/gerty", tags=["Gerty"])
+
+
+def gerty_renderer():
+ return template_renderer(["lnbits/extensions/gerty/templates"])
+
+from .views import * # noqa
+from .views_api import * # noqa
diff --git a/lnbits/extensions/gerty/config.json b/lnbits/extensions/gerty/config.json
new file mode 100644
index 00000000..158ac52a
--- /dev/null
+++ b/lnbits/extensions/gerty/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Gerty",
+ "short_description": "Desktop bitcoin Assistant",
+ "icon": "sentiment_satisfied",
+ "contributors": ["arcbtc"]
+}
diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py
new file mode 100644
index 00000000..ffe8c1bb
--- /dev/null
+++ b/lnbits/extensions/gerty/crud.py
@@ -0,0 +1,51 @@
+from typing import List, Optional, Union
+
+from lnbits.helpers import urlsafe_short_hash
+
+from . import db
+from .models import Gerty
+
+
+async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
+ gerty_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO gerty.gertys (id, name, wallet, lnbits_wallets, sats_quote, exchange, onchain_sats, ln_stats)
+ VALUES (?, ?, ?, ?, ?, ?)
+ """,
+ (
+ gerty_id,
+ data.name,
+ data.wallet,
+ data.lnbits_wallets,
+ data.sats_quote,
+ data.exchange,
+ data.onchain_sats,
+ data.ln_stats,
+ ),
+ )
+
+ gerty = await get_gerty(gerty_id)
+ assert gerty, "Newly created gerty couldn't be retrieved"
+ return gerty
+
+
+async def get_gerty(gerty_id: str) -> Optional[Gerty]:
+ row = await db.fetchone("SELECT * FROM gerty.gertys WHERE id = ?", (gerty_id,))
+ return Gerty(**row) if row else None
+
+
+async def get_gertys(wallet_ids: Union[str, List[str]]) -> List[Gerty]:
+ if isinstance(wallet_ids, str):
+ wallet_ids = [wallet_ids]
+
+ q = ",".join(["?"] * len(wallet_ids))
+ rows = await db.fetchall(
+ f"SELECT * FROM gerty.gertys WHERE wallet IN ({q})", (*wallet_ids,)
+ )
+
+ return [Gerty(**row) for row in rows]
+
+
+async def delete_gerty(gerty_id: str) -> None:
+ await db.execute("DELETE FROM gerty.gertys WHERE id = ?", (gerty_id,))
diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py
new file mode 100644
index 00000000..06ca7513
--- /dev/null
+++ b/lnbits/extensions/gerty/migrations.py
@@ -0,0 +1,17 @@
+async def m001_initial(db):
+ """
+ Initial gertys table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE gerty.gertys (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ lnbits_wallets TEXT NOT NULL,
+ sats_quote BOOL NOT NULL,
+ exchange TEXT NOT NULL,
+ onchain_sats BOOL NOT NULL,
+ ln_stats BOOL NOT NULL
+ );
+ """
+ )
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
new file mode 100644
index 00000000..9338ee47
--- /dev/null
+++ b/lnbits/extensions/gerty/models.py
@@ -0,0 +1,21 @@
+from sqlite3 import Row
+from typing import Optional
+
+from fastapi import Query
+from pydantic import BaseModel
+
+
+
+class Gerty(BaseModel):
+ id: str
+ name: str
+ wallet: str
+ lnbits_wallets: str # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
+ sats_quote: bool = Query(False) # Fetch Satoshi quotes
+ exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
+ onchain_sats: bool = Query(False) # Onchain stats
+ ln_stats: bool = Query(False) # ln Sats
+
+ @classmethod
+ def from_row(cls, row: Row) -> "Gerty":
+ return cls(**dict(row))
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/static/satoshi.json b/lnbits/extensions/gerty/static/satoshi.json
new file mode 100644
index 00000000..1cff822a
--- /dev/null
+++ b/lnbits/extensions/gerty/static/satoshi.json
@@ -0,0 +1,1099 @@
+[
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "It would have been nice to get this attention in any other context. WikiLeaks has kicked the hornet's nest, and the swarm is headed towards us.",
+ "post_id": "542",
+ "date": "December 11, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "The project needs to grow gradually so the software can be strengthened along the way. I make this appeal to WikiLeaks not to try to use Bitcoin. Bitcoin is a small beta community in its infancy.",
+ "post_id": "523",
+ "date": "December 5, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "I'm happy if someone with artistic skill wants to contribute alternatives. The icon/logo was meant to be good as an icon at the 16x16 and 20x20 pixel sizes. I think it's the best program icon, but there's room for improvement at larger sizes for a graphic for use on websites. It'll be a lot simpler if authors could make their graphics public domain.",
+ "post_id": "500",
+ "date": "November 13, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "I wish rather than deleting the article, they put a length restriction. If something is not famous enough, there could at least be a stub article identifying what it is. I often come across annoying red links of things that Wiki ought to at least have heard of. \nThe article could be as simple as something like: \"Bitcoin is a peer-to-peer decentralised /link/electronic currency/link/.\" \nThe more standard Wiki thing to do is that we should have a paragraph in one of the more general categories that we are an instance of, like Electronic Currency or Electronic Cash. We can probably establish a paragraph there. Again, keep it short. Just identifying what it is.",
+ "post_id": "467",
+ "date": "September 30, 2010"
+ },
+ {
+ "category": "transactions",
+ "medium": "bitcointalk",
+ "text": "As you figured out, the root problem is we shouldn't be counting or spending transactions until they have at least 1 confirmation. 0/unconfirmed transactions are very much second class citizens. At most, they are advice that something has been received, but counting them as balance or spending them is premature.",
+ "post_id": "464",
+ "date": "September 30, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Bitcoin would be convenient for people who don't have a credit card or don't want to use the cards they have, either don't want the spouse to see it on the bill or don't trust giving their number to \"porn guys\", or afraid of recurring billing.",
+ "post_id": "460",
+ "date": "September 23, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "I don't know anything about any of the bug trackers. If we were to have one, we would have to make a thoroughly researched choice. We're managing pretty well just using the forum. I'm more likely to see bugs posted in the forum, and I think other users are much more likely to help resolve and ask follow up questions here than if they were in a bug tracker. A key step is other users helping resolve the simple stuff that's not really a bug but some misunderstanding or confusion. I keep a list of all unresolved bugs I've seen on the forum. In some cases, I'm still thinking about the best design for the fix. This isn't the kind of software where we can leave so many unresolved bugs that we need a tracker for them.",
+ "post_id": "454",
+ "date": "September 19, 2010"
+ },
+ {
+ "category": "scalability",
+ "medium": "bitcointalk",
+ "text": "The threshold can easily be changed in the future. We can decide to increase it when the time comes. It's a good idea to keep it lower as a circuit breaker and increase it as needed. If we hit the threshold now, it would almost certainly be some kind of flood and not actual use. Keeping the threshold lower would help limit the amount of wasted disk space in that event.",
+ "post_id": "441",
+ "date": "September 8, 2010"
+ },
+ {
+ "category": "fees",
+ "medium": "bitcointalk",
+ "text": "Currently, paying a fee is controlled manually with the -paytxfee switch. It would be very easy to make the software automatically check the size of recent blocks to see if it should pay a fee. We're so far from reaching the threshold, we don't need that yet. It's a good idea to see how things go with controlling it manually first anyway.",
+ "post_id": "441",
+ "date": "September 8, 2010"
+ },
+ {
+ "category": "fees, nodes",
+ "medium": "bitcointalk",
+ "text": "Another option is to reduce the number of free transactions allowed per block before transaction fees are required. Nodes only take so many KB of free transactions per block before they start requiring at least 0.01 transaction fee. The threshold should probably be lower than it currently is. I don't think the threshold should ever be 0. We should always allow at least some free transactions.",
+ "post_id": "439",
+ "date": "September 7, 2010"
+ },
+ {
+ "category": "economics",
+ "medium": "bitcointalk",
+ "text": "As a thought experiment, imagine there was a base metal as scarce as gold but with the following properties:\n- boring grey in colour\n- not a good conductor of electricity\n- not particularly strong, but not ductile or easily malleable either\n- not useful for any practical or ornamental purpose\n\nand one special, magical property:\n- can be transported over a communications channel\n\nIf it somehow acquired any value at all for whatever reason, then anyone wanting to transfer wealth over a long distance could buy some, transmit it, and have the recipient sell it.\n\nMaybe it could get an initial value circularly as you've suggested, by people foreseeing its potential usefulness for exchange. (I would definitely want some) Maybe collectors, any random reason could spark it.\n\nI think the traditional qualifications for money were written with the assumption that there are so many competing objects in the world that are scarce, an object with the automatic bootstrap of intrinsic value will surely win out over those without intrinsic value. But if there were nothing in the world with intrinsic value that could be used as money, only scarce but no intrinsic value, I think people would still take up something.\n\n(I'm using the word scarce here to only mean limited potential supply)",
+ "post_id": "428",
+ "date": "August 27, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "Bitcoins have no dividend or potential future dividend, therefore not like a stock.\n\nMore like a collectible or commodity.",
+ "post_id": "427",
+ "date": "August 27, 2010"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "bitcointalk",
+ "text": "There is no way for the software to automatically know if one chain is better than another except by the greatest proof-of-work. In the design it was necessary for it to switch to a longer chain no matter how far back it has to go.",
+ "post_id": "394",
+ "date": "August 16, 2010"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "Some places where generation will gravitate to: \n1) places where it's cheapest or free\n2) people who want to help for idealogical reasons\n3) people who want to get some coins without the inconvenience of doing a transaction to buy them\n\nThere are legitimate places where it's free. Generation is basically free anywhere that has electric heat, since your computer's heat is offsetting your baseboard electric heating. Many small flats have electric heat out of convenience.",
+ "post_id": "364",
+ "date": "August 15, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Then you must also be against the common system of payment up front, where the customer loses.\nPayment up front: customer loses, and the thief gets the money.\nSimple escrow: customer loses, but the thief doesn't get the money either.\nAre you guys saying payment up front is better, because at least the thief gets the money, so at least someone gets it?\nImagine someone stole something from you. You can't get it back, but if you could, if it had a kill switch that could be remote triggered, would you do it? Would it be a good thing for thieves to know that everything you own has a kill switch and if they steal it, it'll be useless to them, although you still lose it too? If they give it back, you can re-activate it.\nImagine if gold turned to lead when stolen. If the thief gives it back, it turns to gold again.\nIt still seems to me the problem may be one of presenting it the right way. For one thing, not being so blunt about \"money burning\" for the purposes of game theory discussion. The money is never truly burned. You have the option to release it at any time forever.",
+ "post_id": "340",
+ "date": "August 11, 2010"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "The heat from your computer is not wasted if you need to heat your home. If you're using electric heat where you live, then your computer's heat isn't a waste. It's equal cost if you generate the heat with your computer. \nIf you have other cheaper heating than electric, then the waste is only the difference in cost.\nIf it's summer and you're using A/C, then it's twice. \nBitcoin generation should end up where it's cheapest. Maybe that will be in cold climates where there's electric heat, where it would be essentially free.",
+ "post_id": "337",
+ "date": "August 9, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "It's the same situation as gold and gold mining. The marginal cost of gold mining tends to stay near the price of gold. Gold mining is a waste, but that waste is far less than the utility of having gold available as a medium of exchange. \nI think the case will be the same for Bitcoin. The utility of the exchanges made possible by Bitcoin will far exceed the cost of electricity used. Therefore, not having Bitcoin would be the net waste.",
+ "post_id": "327",
+ "date": "August 7, 2010"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "bitcointalk",
+ "text": "Proof-of-work has the nice property that it can be relayed through untrusted middlemen. We don't have to worry about a chain of custody of communication. It doesn't matter who tells you a longest chain, the proof-of-work speaks for itself.",
+ "post_id": "327",
+ "date": "August 7, 2010"
+ },
+ {
+ "category": "micropayments",
+ "medium": "bitcointalk",
+ "text": "Forgot to add the good part about micropayments. While I don't think Bitcoin is practical for smaller micropayments right now, it will eventually be as storage and bandwidth costs continue to fall. If Bitcoin catches on on a big scale, it may already be the case by that time. Another way they can become more practical is if I implement client-only mode and the number of network nodes consolidates into a smaller number of professional server farms. Whatever size micropayments you need will eventually be practical. I think in 5 or 10 years, the bandwidth and storage will seem trivial.",
+ "post_id": "318",
+ "date": "August 5, 2010"
+ },
+ {
+ "category": "micropayments",
+ "medium": "bitcointalk",
+ "text": "Bitcoin isn't currently practical for very small micropayments. Not for things like pay per search or per page view without an aggregating mechanism, not things needing to pay less than 0.01. The dust spam limit is a first try at intentionally trying to prevent overly small micropayments like that. \nBitcoin is practical for smaller transactions than are practical with existing payment methods. Small enough to include what you might call the top of the micropayment range. But it doesn't claim to be practical for arbitrarily small micropayments.",
+ "post_id": "317",
+ "date": "August 4, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Actually, it works well to just PM me. I'm the one who's going to be fixing it. If you find a security flaw, I would definitely like to hear from you privately to fix it before it goes public.",
+ "post_id": "294",
+ "date": "July 29, 2010"
+ },
+ {
+ "category": "nodes",
+ "medium": "bitcointalk",
+ "text": "The current system where every user is a network node is not the intended configuration for large scale. That would be like every Usenet user runs their own NNTP server. The design supports letting users just be users. The more burden it is to run a node, the fewer nodes there will be. Those few nodes will be big server farms. The rest will be client nodes that only do transactions and don't generate.",
+ "post_id": "287",
+ "date": "July 29, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "For future reference, here's my public key. It's the same one that's been there since the bitcoin.org site first went up in 2008. Grab it now in case you need it later. http://www.bitcoin.org/Satoshi_Nakamoto.asc",
+ "post_id": "276",
+ "date": "July 25, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "By making some adjustments to the database settings, I was able to make the initial block download about 5 times faster. It downloads in about 30 minutes. \n \nThe database default had it writing each block to disk synchronously, which is not necessary. I changed the settings to let it cache the changes in memory and write them out in a batch. Blocks are still written transactionally, so either the complete change occurs or none of it does, in either case the data is left in a valid state. \n \nI only enabled this change during the initial block download. When you come within 2000 blocks of the latest block, these changes turn off and it slows down to the old way.",
+ "post_id": "258",
+ "date": "July 23, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "The timing is strange, just as we are getting a rapid increase in 3rd party coverage after getting slashdotted. I hope there's not a big hurry to wrap the discussion and decide. How long does Wikipedia typically leave a question like that open for comment? \nIt would help to condense the article and make it less promotional sounding as soon as possible. Just letting people know what it is, where it fits into the electronic money space, not trying to convince them that it's good. They probably want something that just generally identifies what it is, not tries to explain all about how it works.",
+ "post_id": "249",
+ "date": "July 10, 2010"
+ },
+ {
+ "category": "difficulty",
+ "medium": "bitcointalk",
+ "text": "Right, the difficulty adjustment is trying to keep it so the network as a whole generates an average of 6 blocks per hour. The time for your block to mature will always be around 20 hours.",
+ "post_id": "225",
+ "date": "July 16, 2010"
+ },
+ {
+ "category": "difficulty",
+ "medium": "bitcointalk",
+ "text": "Difficulty just increased by 4 times, so now your cost is US$0.02/BTC.",
+ "post_id": "223",
+ "date": "July 16, 2010"
+ },
+ {
+ "category": "scalability, nodes",
+ "medium": "bitcointalk",
+ "text": "The design outlines a lightweight client that does not need the full block chain. In the design PDF it's called Simplified Payment Verification. The lightweight client can send and receive transactions, it just can't generate blocks. It does not need to trust a node to verify payments, it can still verify them itself. \nThe lightweight client is not implemented yet, but the plan is to implement it when it's needed. For now, everyone just runs a full network node.",
+ "post_id": "188",
+ "date": "July 14, 2010"
+ },
+ {
+ "category": "scalability, nodes",
+ "medium": "bitcointalk",
+ "text": "I anticipate there will never be more than 100K nodes, probably less. It will reach an equilibrium where it's not worth it for more nodes to join in. The rest will be lightweight clients, which could be millions.",
+ "post_id": "188",
+ "date": "July 14, 2010"
+ },
+ {
+ "category": "nodes",
+ "medium": "bitcointalk",
+ "text": "At equilibrium size, many nodes will be server farms with one or two network nodes that feed the rest of the farm over a LAN.",
+ "post_id": "188",
+ "date": "July 14, 2010"
+ },
+ {
+ "category": "economics",
+ "medium": "bitcointalk",
+ "text": "When someone tries to buy all the world's supply of a scarce asset, the more they buy the higher the price goes. At some point, it gets too expensive for them to buy any more. It's great for the people who owned it beforehand because they get to sell it to the corner at crazy high prices. As the price keeps going up and up, some people keep holding out for yet higher prices and refuse to sell.",
+ "post_id": "174",
+ "date": "July 9, 2010"
+ },
+ {
+ "category": "releases",
+ "medium": "bitcointalk",
+ "text": "Announcing version 0.3 of Bitcoin, the P2P cryptocurrency! Bitcoin is a digital currency using cryptography and a distributed network to replace the need for a trusted central server. Escape the arbitrary inflation risk of centrally managed currencies! Bitcoin's total circulation is limited to 21 million coins. The coins are gradually released to the network's nodes based on the CPU proof-of-worker they contribute, so you can get a share of them by contributing your idle CPU time.",
+ "post_id": "168",
+ "date": "July 6, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Writing a description for this thing for general audiences is bloody hard. There's nothing to relate it to.",
+ "post_id": "167",
+ "date": "July 5, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "Lost coins only make everyone else's coins worth slightly more. Think of it as a donation to everyone.",
+ "post_id": "131",
+ "date": "June 21, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Excellent choice of a first project, nice work. I had planned to do this exact thing if someone else didn't do it, so when it gets too hard for mortals to generate 50BTC, new users could get some coins to play with right away. Donations should be able to keep it filled. The display showing the balance in the dispenser encourages people to top it up.\n\nYou should put a donation bitcoin address on the page for those who want to add funds to it, which ideally should update to a new address whenever it receives something.",
+ "post_id": "129",
+ "date": "June 18, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Since 2007. At some point I became convinced there was a way to do this without any trust required at all and couldn't resist to keep thinking about it. Much more of the work was designing than coding.\n\nFortunately, so far all the issues raised have been things I previously considered and planned for.",
+ "post_id": "127",
+ "date": "June 18, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "The nature of Bitcoin is such that once version 0.1 was released, the core design was set in stone for the rest of its lifetime. Because of that, I wanted to design it to support every possible transaction type I could think of. The problem was, each thing required special support code and data fields whether it was used or not, and only covered one special case at a time. It would have been an explosion of special cases. The solution was script, which generalizes the problem so transacting parties can describe their transaction as a predicate that the node network evaluates. The nodes only need to understand the transaction to the extent of evaluating whether the sender's conditions are met.",
+ "post_id": "126",
+ "date": "June 17, 2010"
+ },
+ {
+ "category": "transactions, bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "The design supports a tremendous variety of possible transaction types that I designed years ago. Escrow transactions, bonded contracts, third party arbitration, multi-party signature, etc. If Bitcoin catches on in a big way, these are things we'll want to explore in the future, but they all had to be designed at the beginning to make sure they would be possible later.",
+ "post_id": "126",
+ "date": "June 17, 2010"
+ },
+ {
+ "category": "encryption",
+ "medium": "bitcointalk",
+ "text": "SHA-256 is very strong. It's not like the incremental step from MD5 to SHA1. It can last several decades unless there's some massive breakthrough attack.",
+ "post_id": "119",
+ "date": "June 14, 2010"
+ },
+ {
+ "category": "encryption",
+ "medium": "bitcointalk",
+ "text": "If SHA-256 became completely broken, I think we could come to some agreement about what the honest block chain was before the trouble started, lock that in and continue from there with a new hash function.",
+ "post_id": "119",
+ "date": "June 14, 2010"
+ },
+ {
+ "category": "releases",
+ "medium": "bitcointalk",
+ "text": "Does anyone want to translate the Bitcoin client itself? It would be great to have at least one other language in the 0.3 release.",
+ "post_id": "111",
+ "date": "May 26, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Simplified Payment Verification is for lightweight client-only users who only do transactions and don't generate and don't participate in the node network. They wouldn't need to download blocks, just the hash chain, which is currently about 2MB and very quick to verify (less than a second to verify the whole chain). If the network becomes very large, like over 100,000 nodes, this is what we'll use to allow common users to do transactions without being full blown nodes. At that stage, most users should start running client-only software and only the specialist server farms keep running full network nodes, kind of like how the usenet network has consolidated. \nSPV is not implemented yet, and won't be implemented until far in the future, but all the current implementation is designed around supporting it.",
+ "post_id": "105",
+ "date": "May 18, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Bitcoin addresses you generate are kept forever. A bitcoin address must be kept to show ownership of anything sent to it. If you were able to delete a bitcoin address and someone sent to it, the money would be lost. They're only about 500 bytes.",
+ "post_id": "102",
+ "date": "May 16, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "When you generate a new bitcoin address, it only takes disk space on your own computer (like 500 bytes). It's like generating a new PGP private key, but less CPU intensive because it's ECC. The address space is effectively unlimited. It doesn't hurt anyone, so generate all you want.",
+ "post_id": "98",
+ "date": "May 16, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "The price of .com registrations is lower than it should be, therefore any good name you might think of is always already taken by some domain name speculator. Fortunately, it's standard for open source projects to be .org.",
+ "post_id": "94",
+ "date": "March 23, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "How does everyone feel about the B symbol with the two lines through the outside? Can we live with that as our logo?",
+ "post_id": "83",
+ "date": "February 26, 2010"
+ },
+ {
+ "category": "transactions",
+ "medium": "bitcointalk",
+ "text": "That would be nice at point-of-sale. The cash register displays a QR-code encoding a bitcoin address and amount on a screen and you photo it with your mobile.",
+ "post_id": "73",
+ "date": "February 24, 2010"
+ },
+ {
+ "category": "economics",
+ "medium": "bitcointalk",
+ "text": "A rational market price for something that is expected to increase in value will already reflect the present value of the expected future increases. In your head, you do a probability estimate balancing the odds that it keeps increasing.",
+ "post_id": "65",
+ "date": "February 21, 2010"
+ },
+ {
+ "category": "economics, bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "The price of any commodity tends to gravitate toward the production cost. If the price is below cost, then production slows down. If the price is above cost, profit can be made by generating and selling more. At the same time, the increased production would increase the difficulty, pushing the cost of generating towards the price.",
+ "post_id": "65",
+ "date": "February 21, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "At the moment, generation effort is rapidly increasing, suggesting people are estimating the present value to be higher than the current cost of production.",
+ "post_id": "65",
+ "date": "February 21, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "I'm sure that in 20 years there will either be very large transaction volume or no volume.",
+ "post_id": "57",
+ "date": "February 14, 2010"
+ },
+ {
+ "category": "bitcoin-economics, fees",
+ "medium": "bitcointalk",
+ "text": "In a few decades when the reward gets too small, the transaction fee will become the main compensation for nodes.",
+ "post_id": "57",
+ "date": "February 14, 2010"
+ },
+ {
+ "category": "nodes, mining, fees",
+ "medium": "bitcointalk",
+ "text": "If you're sad about paying the fee, you could always turn the tables and run a node yourself and maybe someday rake in a 0.44 fee yourself.",
+ "post_id": "56",
+ "date": "February 14, 2010"
+ },
+ {
+ "category": "bitcoin-economics, bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Eventually at most only 21 million coins for 6.8 billion people in the world if it really gets huge.\n\nBut don't worry, there are another 6 decimal places that aren't shown, for a total of 8 decimal places internally. It shows 1.00 but internally it's 1.00000000. If there's massive deflation in the future, the software could show more decimal places.",
+ "post_id": "46",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "If it gets tiresome working with small numbers, we could change where the display shows the decimal point. Same amount of money, just different convention for where the \",\"'s and \".\"'s go. e.g. moving the decimal place 3 places would mean if you had 1.00000 before, now it shows it as 1,000.00.",
+ "post_id": "46",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "privacy",
+ "medium": "bitcointalk",
+ "text": "Bitcoin is still very new and has not been independently analysed. If you're serious about privacy, TOR is an advisable precaution.",
+ "post_id": "45",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "privacy",
+ "medium": "bitcointalk",
+ "text": "You could use TOR if you don't want anyone to know you're even using Bitcoin.",
+ "post_id": "45",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "I very much wanted to find some way to include a short message, but the problem is, the whole world would be able to see the message. As much as you may keep reminding people that the message is completely non-private, it would be an accident waiting to happen.",
+ "post_id": "33",
+ "date": "January 28, 2010"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "The average total coins generated across the network per day stays the same. Faster machines just get a larger share than slower machines. If everyone bought faster machines, they wouldn't get more coins than before.",
+ "post_id": "20",
+ "date": "December 12, 2009"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "We should have a gentleman's agreement to postpone the GPU arms race as long as we can for the good of the network. It's much easer to get new users up to speed if they don't have to worry about GPU drivers and compatibility. It's nice how anyone with just a CPU can compete fairly equally right now.",
+ "post_id": "20",
+ "date": "December 12, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "Those coins can never be recovered, and the total circulation is less. Since the effective circulation is reduced, all the remaining coins are worth slightly more. It's the opposite of when a government prints money and the value of existing money goes down.",
+ "post_id": "17",
+ "date": "December 10, 2009"
+ },
+ {
+ "category": "trusted-third-parties",
+ "text": "Being open source means anyone can independently review the code. If it was closed source, nobody could verify the security. I think it's essential for a program of this nature to be open source.",
+ "medium": "bitcointalk",
+ "post_id": "17",
+ "date": "December 10, 2009"
+ },
+ {
+ "category": "privacy, transactions",
+ "medium": "bitcointalk",
+ "text": "For greater privacy, it's best to use bitcoin addresses only once.",
+ "post_id": "11",
+ "date": "November 25, 2009"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "Think of it as a cooperative effort to make a chain. When you add a link, you must first find the current end of the chain. If you were to locate the last link, then go off for an hour and forge your link, come back and link it to the link that was the end an hour ago, others may have added several links since then and they're not going to want to use your link that now branches off the middle.",
+ "post_id": "8",
+ "date": "November 22, 2009"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "p2pfoundation",
+ "text": "It is a global distributed database, with additions to the database by consent of the majority, based on a set of rules they follow: \n\n- Whenever someone finds proof-of-work to generate a block, they get some new coins\n- The proof-of-work difficulty is adjusted every two weeks to target an average of 6 blocks per hour (for the whole network)\n- The coins given per block is cut in half every 4 years",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "p2pfoundation",
+ "text": "You could say coins are issued by the majority. They are issued in a limited, predetermined amount.",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "p2pfoundation",
+ "text": "To Sepp's question, indeed there is nobody to act as central bank or federal reserve to adjust the money supply as the population of users grows. That would have required a trusted party to determine the value, because I don't know a way for software to know the real world value of things.",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "p2pfoundation",
+ "text": "In this sense, it's more typical of a precious metal. Instead of the supply changing to keep the value the same, the supply is predetermined and the value changes. As the number of users grows, the value per coin increases. It has the potential for a positive feedback loop; as users increase, the value goes up, which could attract more users to take advantage of the increasing value.",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "medium": "p2pfoundation",
+ "text": "A lot of people automatically dismiss e-currency as a lost cause because of all the companies that failed since the 1990's. I hope it's obvious it was only the centrally controlled nature of those systems that doomed them. I think this is the first time we're trying a decentralized, non-trust-based system.",
+ "post_id": "2",
+ "date": "February 15, 2009"
+ },
+ {
+ "category": "releases, bitcoin-design",
+ "medium": "p2pfoundation",
+ "text": "I've developed a new open source P2P e-cash system called Bitcoin. It's completely decentralized, with no central server or trusted parties, because everything is based on crypto proof instead of trust. Give it a try, or take a look at the screenshots and design paper: \n\nDownload Bitcoin v0.1 at http://www.bitcoin.org",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "economics",
+ "medium": "p2pfoundation",
+ "text": "The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "micropayments, privacy, banks",
+ "medium": "p2pfoundation",
+ "text": "Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "encryption",
+ "medium": "p2pfoundation",
+ "text": "A generation ago, multi-user time-sharing computer systems had a similar problem. Before strong encryption, users had to rely on password protection to secure their files, placing trust in the system administrator to keep their information private. Privacy could always be overridden by the admin based on his judgment call weighing the principle of privacy against other concerns, or at the behest of his superiors. Then strong encryption became available to the masses, and trust was no longer required. Data could be secured in a way that was physically impossible for others to access, no matter for what reason, no matter how good the excuse, no matter what.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "medium": "p2pfoundation",
+ "text": "With e-currency based on cryptographic proof, without the need to trust a third party middleman, money can be secure and transactions effortless.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "transactions",
+ "medium": "p2pfoundation",
+ "text": "A digital coin contains the public key of its owner. To transfer it, the owner signs the coin together with the public key of the next owner. Anyone can check the signatures to verify the chain of ownership.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "double-spending",
+ "medium": "p2pfoundation",
+ "text": "Any owner could try to re-spend an already spent coin by signing it again to another owner. The usual solution is for a trusted company with a central database to check for double-spending, but that just gets back to the trust model. In its central position, the company can override the users, and the fees needed to support the company make micropayments impractical. \nBitcoin's solution is to use a peer-to-peer network to check for double-spending. In a nutshell, the network works like a distributed timestamp server, stamping the first transaction to spend a coin. It takes advantage of the nature of information being easy to spread but hard to stifle.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "p2pfoundation",
+ "text": "The result is a distributed system with no single point of failure. Users hold the crypto keys to their own money and transact directly with each other, with the help of the P2P network to check for double-spending.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "identity",
+ "medium": "p2pfoundation",
+ "text": "I am not Dorian Nakamoto.",
+ "post_id": "4",
+ "date": "March 7, 2014"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "email",
+ "email_id": "1",
+ "text": "I've been working on a new electronic cash system that's fully peer-to-peer, with no trusted third party.",
+ "date": "November 1, 2008"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "email",
+ "email_id": "1",
+ "text": "The main properties: \n Double-spending is prevented with a peer-to-peer network.\n No mint or other trusted parties.\n Participants can be anonymous.\n New coins are made from Hashcash style proof-of-work.\n The proof-of-work for new coin generation also proof-of-workers the network to prevent double-spending.",
+ "date": "November 1, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "2",
+ "text": "Long before the network gets anywhere near as large as that, it would be safe for users to use Simplified Payment Verification (section 8) to check for double spending, which only requires having the chain of block headers, or about 12KB per day.",
+ "date": "November 2, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "2",
+ "text": "Only people trying to create new coins would need to run network nodes.",
+ "date": "November 2, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "2",
+ "text": "At first, most users would run network nodes, but as the network grows beyond a certain point, it would be left more and more to specialists with server farms of specialized hardware. A server farm would only need to have one node on the network and the rest of the LAN connects with that one node.",
+ "date": "November 2, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "The requirement is that the good guys collectively have more CPU proof-of-worker than any single attacker.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "There would be many smaller zombie farms that are not big enough to overproof-of-worker the network, and they could still make money by generating bitcoins. The smaller farms are then the \"honest nodes\". (I need a better term than \"honest\") The more smaller farms resort to generating bitcoins, the higher the bar gets to overproof-of-worker the network, making larger farms also too small to overproof-of-worker it so that they may as well generate bitcoins too. According to the \"long tail\" theory, the small, medium and merely large farms put together should add up to a lot more than the biggest zombie farm.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "Even if a bad guy does overproof-of-worker the network, it's not like he's instantly rich. All he can accomplish is to take back money he himself spent, like bouncing a check. To exploit it, he would have to buy something from a merchant, wait till it ships, then overproof-of-worker the network and try to take his money back. I don't think he could make as much money trying to pull a carding scheme like that as he could by generating bitcoins. With a zombie farm that big, he could generate more bitcoins than everyone else combined.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "The Bitcoin network might actually reduce spam by diverting zombie farms to generating bitcoins instead.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "motives",
+ "medium": "email",
+ "email_id": "4",
+ "text": "Yes, but we can win a major battle in the arms race and gain a new territory of freedom for several years.",
+ "date": "November 7, 2008"
+ },
+ {
+ "category": "p2p-networks, government",
+ "medium": "email",
+ "email_id": "4",
+ "text": "Governments are good at cutting off the heads of a centrally controlled networks like Napster, but pure P2P networks like Gnutella and Tor seem to be holding their own.",
+ "date": "November 7, 2008"
+ },
+ {
+ "category": "mining, difficulty",
+ "medium": "email",
+ "email_id": "5",
+ "text": "As computers get faster and the total computing proof-of-worker applied to creating bitcoins increases, the difficulty increases proportionally to keep the total new production constant. Thus, it is known in advance how many new bitcoins will be created every year in the future.",
+ "date": "November 8, 2008"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "email",
+ "email_id": "5",
+ "text": "The fact that new coins are produced means the money supply increases by a planned amount, but this does not necessarily result in inflation. If the supply of money increases at the same rate that the number of people using it increases, prices remain stable. If it does not increase as fast as demand, there will be deflation and early holders of money will see its value increase. Coins have to get initially distributed somehow, and a constant rate seems like the best formula.",
+ "date": "November 8, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "6",
+ "text": "Right, nodes keep transactions in their working set until they get into a block. If a transaction reaches 90% of nodes, then each time a new block is found, it has a 90% chance of being in it.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "6",
+ "text": "Receivers of transactions will normally need to hold transactions for perhaps an hour or more to allow time for this kind of possibility to be resolved. They can still re-spend the coins immediately, but they should wait before taking an action such as shipping goods.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "6",
+ "text": "The attacker isn't adding blocks to the end. He has to go back and redo the block his transaction is in and all the blocks after it, as well as any new blocks the network keeps adding to the end while he's doing that. He's rewriting history. Once his branch is longer, it becomes the new valid one.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes, mining, proof-of-work",
+ "medium": "email",
+ "email_id": "6",
+ "text": "It is strictly necessary that the longest chain is always considered the valid one. Nodes that were present may remember that one branch was there first and got replaced by another, but there would be no way for them to convince those who were not present of this. We can't have subfactions of nodes that cling to one branch that they think was first, others that saw another branch first, and others that joined later and never saw what happened. The CPU proof-of-worker proof-of-work vote must have the final say. The only way for everyone to stay on the same page is to believe that the longest chain is always the valid one, no matter what.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "6",
+ "text": "The recipient just needs to verify it back to a depth that is sufficiently far back in the block chain, which will often only require a depth of 2 transactions. All transactions before that can be discarded.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "6",
+ "text": "When a node receives a block, it checks the signatures of every transaction in it against previous transactions in blocks. Blocks can only contain transactions that depend on valid transactions in previous blocks or the same block. Transaction C could depend on transaction B in the same block and B depends on transaction A in an earlier block.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "7",
+ "text": "It's not a problem if transactions have to wait one or a few extra cycles to get into a block.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "8",
+ "text": "The proof-of-work chain is the solution to the synchronisation problem, and to knowing what the globally shared view is without having to trust anyone.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "8",
+ "text": "A transaction will quickly propagate throughout the network, so if two versions of the same transaction were reported at close to the same time, the one with the head start would have a big advantage in reaching many more nodes first. Nodes will only accept the first one they see, refusing the second one to arrive, so the earlier transaction would have many more nodes working on incorporating it into the next proof-of-work. In effect, each node votes for its viewpoint of which transaction it saw first by including it in its proof-of-work effort. If the transactions did come at exactly the same time and there was an even split, it's a toss up based on which gets into a proof-of-work first, and that decides which is valid.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes, proof-of-work",
+ "medium": "email",
+ "email_id": "8",
+ "text": "When a node finds a proof-of-work, the new block is propagated throughout the network and everyone adds it to the chain and starts working on the next block after it. Any nodes that had the other transaction will stop trying to include it in a block, since it's now invalid according to the accepted chain.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "8",
+ "text": "The proof-of-work chain is itself self-evident proof that it came from the globally shared view. Only the majority of the network together has enough CPU proof-of-worker to generate such a difficult chain of proof-of-work. Any user, upon receiving the proof-of-work chain, can see what the majority of the network has approved. Once a transaction is hashed into a link that's a few links back in the chain, it is firmly etched into the global history.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "fees, bitcoin-economics",
+ "medium": "email",
+ "email_id": "9",
+ "text": "If you're having trouble with the inflation issue, it's easy to tweak it for transaction fees instead. It's as simple as this: let the output value from any transaction be 1 cent less than the input value. Either the client software automatically writes transactions for 1 cent more than the intended payment value, or it could come out of the payee's side. The incentive value when a node finds a proof-of-work for a block could be the total of the fees in the block.",
+ "date": "November 10, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "10",
+ "text": "When there are multiple double-spent versions of the same transaction, one and only one will become valid.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "10",
+ "text": "The receiver of a payment must wait an hour or so before believing that it's valid. The network will resolve any possible double-spend races by then.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "10",
+ "text": "The guy who received the double-spend that became invalid never thought he had it in the first place. His software would have shown the transaction go from \"unconfirmed\" to \"invalid\". If necessary, the UI can be made to hide transactions until they're sufficiently deep in the block chain.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "difficulty",
+ "medium": "email",
+ "email_id": "10",
+ "text": "The target time between blocks will probably be 10 minutes. Every block includes its creation time. If the time is off by more than 36 hours, other nodes won't work on it. If the timespan over the last 6*24*30 blocks is less than 15 days, blocks are being generated too fast and the proof-of-work difficulty doubles. Everyone does the same calculation with the same chain data, so they all get the same result at the same link in the chain.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "10",
+ "text": "Instantant non-repudiability is not a feature, but it's still much faster than existing systems. Paper cheques can bounce up to a week or two later. Credit card transactions can be contested up to 60 to 180 days later. Bitcoin transactions can be sufficiently irreversible in an hour or two.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "10",
+ "text": "With the transaction fee based incentive system I recently posted, nodes would have an incentive to include all the paying transactions they receive.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "11",
+ "text": "The proof-of-work chain is a solution to the Byzantine Generals' Problem. I'll try to rephrase it in that context.\nA number of Byzantine Generals each have a computer and want to attack the King's wi-fi by brute forcing the password, which they've learned is a certain number of characters in length. Once they stimulate the network to generate a packet, they must crack the password within a limited time to break in and erase the logs, otherwise they will be discovered and get in trouble. They only have enough CPU proof-of-worker to crack it fast enough if a majority of them attack at the same time. \n They don't particularly care when the attack will be, just that they all agree. It has been decided that anyone who feels like it will announce a time, and whatever time is heard first will be the official attack time. The problem is that the network is not instantaneous, and if two generals announce different attack times at close to the same time, some may hear one first and others hear the other first. They use a proof-of-work chain to solve the problem. Once each general receives whatever attack time he hears first, he sets his computer to solve an extremely difficult proof-of-work problem that includes the attack time in its hash. The proof-of-work is so difficult, it's expected to take 10 minutes of them all working at once before one of them finds a solution. Once one of the generals finds a proof-of-work, he broadcasts it to the network, and everyone changes their current proof-of-work computation to include that proof-of-work in the hash they're working on. If anyone was working on a different attack time, they switch to this one, because its proof-of-work chain is now longer.\n After two hours, one attack time should be hashed by a chain of 12 proofs-of-work. Every general, just by verifying the difficulty of the proof-of-work chain, can estimate how much parallel CPU proof-of-worker per hour was expended on it and see that it must have required the majority of the computers to produce that much proof-of-work in the allotted time. They had to all have seen it because the proof-of-work is proof that they worked on it. If the CPU proof-of-worker exhibited by the proof-of-work chain is sufficient to crack the password, they can safely attack at the agreed time.\n The proof-of-work chain is how all the synchronisation, distributed database and global view problems you've asked about are solved.",
+ "date": "November 13, 2008"
+ },
+ {
+ "category": "nodes, mining",
+ "medium": "email",
+ "email_id": "12",
+ "text": "Broadcasts will probably be almost completely reliable. TCP transmissions are rarely ever dropped these days, and the broadcast protocol has a retry mechanism to get the data from other nodes after a while. If broadcasts turn out to be slower in practice than expected, the target time between blocks may have to be increased to avoid wasting resources. We want blocks to usually propagate in much less time than it takes to generate them, otherwise nodes would spend too much time working on obsolete blocks.",
+ "date": "November 14, 2008"
+ },
+ {
+ "category": "motives",
+ "medium": "email",
+ "email_id": "12",
+ "text": "It's very attractive to the libertarian viewpoint if we can explain it properly. I'm better with code than with words though.",
+ "date": "November 13, 2008"
+ },
+ {
+ "category": "releases",
+ "medium": "email",
+ "email_id": "13",
+ "text": "I'll try and hurry up and release the sourcecode as soon as possible to serve as a reference to help clear up all these implementation questions.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "13",
+ "text": "A basic transaction is just what you see in the figure in section 2. A signature (of the buyer) satisfying the public key of the previous transaction, and a new public key (of the seller) that must be satisfied to spend it the next time.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "13",
+ "text": "There's no need for reporting of \"proof of double spending\" like that. If the same chain contains both spends, then the block is invalid and rejected. \n Same if a block didn't have enough proof-of-work. That block is invalid and rejected. There's no need to circulate a report about it. Every node could see that and reject it before relaying it.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "13",
+ "text": "We're not \"on the lookout\" for double spends to sound the alarm and catch the cheater. We merely adjudicate which one of the spends is valid. Receivers of transactions must wait a few blocks to make sure that resolution has had time to complete. Would be cheaters can try and simultaneously double-spend all they want, and all they accomplish is that within a few blocks, one of the spends becomes valid and the others become invalid. Any later double-spends are immediately rejected once there's already a spend in the main chain.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "proof-of-work, mining",
+ "medium": "email",
+ "email_id": "13",
+ "text": "The proof-of-work is a Hashcash style SHA-256 collision finding. It's a memoryless process where you do millions of hashes a second, with a small chance of finding one each time. The 3 or 4 fastest nodes' dominance would only be proportional to their share of the total CPU proof-of-worker. Anyone's chance of finding a solution at any time is proportional to their CPU proof-of-worker.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "email",
+ "email_id": "13",
+ "text": "There will be transaction fees, so nodes will have an incentive to receive and include all the transactions they can. Nodes will eventually be compensated by transaction fees alone when the total coins created hits the pre-determined ceiling.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "14",
+ "text": "The credential that establishes someone as real is the ability to supply CPU proof-of-worker.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "14",
+ "text": "The race is to spread your transaction on the network first. Think 6 degrees of freedom -- it spreads exponentially. It would only take something like 2 minutes for a transaction to spread widely enough that a competitor starting late would have little chance of grabbing very many nodes before the first one is overtaking the whole network. During those 2 minutes, the merchant's nodes can be watching for a double-spent transaction. The double-spender would not be able to blast his alternate transaction out to the world without the merchant getting it, so he has to wait before starting. \n If the real transaction reaches 90% and the double-spent tx reaches 10%, the double-spender only gets a 10% chance of not paying, and 90% chance his money gets spent. For almost any type of goods, that's not going to be worth it for the scammer.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "14",
+ "text": "If a merchant actually has a problem with theft, they can make the customer wait 2 minutes, or wait for something in e-mail, which many already do. If they really want to optimize, and it's a large download, they could cancel the download in the middle if the transaction comes back double-spent. If it's website access, typically it wouldn't be a big deal to let the customer have access for 5 minutes and then cut off access if it's rejected. Many such sites have a free trial anyway.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "releases, bitcoin-design",
+ "medium": "email",
+ "email_id": "15",
+ "text": "I believe I've worked through all those little details over the last year and a half while coding it, and there were a lot of them. The functional details are not covered in the paper, but the sourcecode is coming soon. I sent you the main files. (available by request at the moment, full release soon)",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "releases",
+ "medium": "email",
+ "email_id": "16",
+ "text": "Announcing the first release of Bitcoin, a new electronic cash system that uses a peer-to-peer network to prevent double-spending. It's completely decentralized with no server or central authority.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "16",
+ "text": "If you can keep a node running that accepts incoming connections, you'll really be helping the network a lot. Port 8333 on your firewall needs to be open to receive incoming connections.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "16",
+ "text": "You can get coins by getting someone to send you some, or turn on Options->Generate Coins to run a node and generate blocks. I made the proof-of-work difficulty ridiculously easy to start with, so for a little while in the beginning a typical PC will be able to generate coins in just a few hours. It'll get a lot harder when competition makes the automatic adjustment drive up the difficulty. Generated coins must wait 120 blocks to mature before they can be spent.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "16",
+ "text": "There are two ways to send money. If the recipient is online, you can enter their IP address and it will connect, get a new public key and send the transaction with comments. If the recipient is not online, it is possible to send to their Bitcoin address, which is a hash of their public key that they give you. They'll receive the transaction the next time they connect and get the block it's in. This method has the disadvantage that no comment information is sent, and a bit of privacy may be lost if the address is used multiple times, but it is a useful alternative if both users can't be online at the same time or the recipient can't receive incoming connections.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "email",
+ "email_id": "16",
+ "text": "Total circulation will be 21,000,000 coins. It'll be distributed to network nodes when they make blocks, with the amount cut in half every 4 years.\n\nfirst 4 years: 10,500,000 coins\nnext 4 years: 5,250,000 coins\nnext 4 years: 2,625,000 coins\nnext 4 years: 1,312,500 coins\netc...\n\nWhen that runs out, the system can support transaction fees if needed. It's based on open market competition, and there will probably always be nodes willing to process transactions for free.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "medium": "email",
+ "email_id": "17",
+ "text": "I would be surprised if 10 years from now we're not using electronic currency in some way, now that we know a way to do it that won't inevitably get dumbed down when the trusted third party gets cold feet.",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "micropayments",
+ "medium": "email",
+ "email_id": "17",
+ "text": "It can already be used for pay-to-send e-mail. The send dialog is resizeable and you can enter as long of a message as you like. It's sent directly when it connects. The recipient doubleclicks on the transaction to see the full message. If someone famous is getting more e-mail than they can read, but would still like to have a way for fans to contact them, they could set up Bitcoin and give out the IP address on their website. \"Send X bitcoins to my priority hotline at this IP and I'll read the message personally.\"",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "micropayments",
+ "medium": "email",
+ "email_id": "17",
+ "text": "Subscription sites that need some extra proof-of-work for their free trial so it doesn't cannibalize subscriptions could charge bitcoins for the trial.",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "micropayments, bitcoin-economics",
+ "medium": "email",
+ "email_id": "17",
+ "text": "It might make sense just to get some in case it catches on. If enough people think the same way, that becomes a self fulfilling prophecy. Once it gets bootstrapped, there are so many applications if you could effortlessly pay a few cents to a website as easily as dropping coins in a vending machine.",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "text": "A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work, double-spending",
+ "text": "We propose a solution to the double-spending problem using a peer-to-peer network. The network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU proof-of-worker. As long as a majority of CPU proof-of-worker is controlled by nodes that are not cooperating to attack the network, they'll generate the longest chain and outpace attackers. The network itself requires minimal structure.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "trusted-third-parties",
+ "text": "Commerce on the Internet has come to rely almost exclusively on financial institutions serving as trusted third parties to process electronic payments. While the system works well enough for most transactions, it still suffers from the inherent weaknesses of the trust based model.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "trusted-third-parties",
+ "text": "Completely non-reversible transactions are not really possible, since financial institutions cannot avoid mediating disputes. The cost of mediation increases transaction costs, limiting the minimum practical transaction size and cutting off the possibility for small casual transactions, and there is a broader cost in the loss of ability to make non-reversible payments for non-reversible services. With the possibility of reversal, the need for trust spreads.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "trusted-third-parties, cryptocurrency",
+ "text": "What is needed is an electronic payment system based on cryptographic proof instead of trust, allowing any two willing parties to transact directly with each other without the need for a trusted third party. Transactions that are computationally impractical to reverse would protect sellers from fraud, and routine escrow mechanisms could easily be implemented to protect buyers.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "double-spending, proof-of-work",
+ "text": "In this paper, we propose a solution to the double-spending problem using a peer-to-peer distributed timestamp server to generate computational proof of the chronological order of transactions. The system is secure as long as honest nodes collectively control more CPU proof-of-worker than any cooperating group of attacker nodes.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "We define an electronic coin as a chain of digital signatures. Each owner transfers the coin to the next by digitally signing a hash of the previous transaction and the public key of the next owner and adding these to the end of the coin. A payee can verify the signatures to verify the chain of ownership.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "economics, double-spending",
+ "text": "The problem of course is the payee can't verify that one of the owners did not double-spend the coin. A common solution is to introduce a trusted central authority, or mint, that checks every transaction for double spending. After each transaction, the coin must be returned to the mint to issue a new coin, and only coins issued directly from the mint are trusted not to be double-spent. The problem with this solution is that the fate of the entire money system depends on the company running the mint, with every transaction having to go through them, just like a bank.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "nodes, cryptocurrency, transactions",
+ "text": "We need a way for the payee to know that the previous owners did not sign any earlier transactions. For our purposes, the earliest transaction is the one that counts, so we don't care about later attempts to double-spend. The only way to confirm the absence of a transaction is to be aware of all transactions. In the mint based model, the mint was aware of all transactions and decided which arrived first. To accomplish this without a trusted party, transactions must be publicly announced, and we need a system for participants to agree on a single history of the order in which they were received. The payee needs proof that at the time of each transaction, the majority of nodes agreed it was the first received.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "The solution we propose begins with a timestamp server. A timestamp server works by taking a hash of a block of items to be timestamped and widely publishing the hash, such as in a newspaper or Usenet post. The timestamp proves that the data must have existed at the time, obviously, in order to get into the hash. Each timestamp includes the previous timestamp in its hash, forming a chain, with each additional timestamp reinforcing the ones before it.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "text": "To implement a distributed timestamp server on a peer-to-peer basis, we will need to use a proof-of-work system similar to Adam Back's Hashcash, rather than newspaper or Usenet posts. The proof-of-work involves scanning for a value that when hashed, such as with SHA-256, the hash begins with a number of zero bits. The average work required is exponential in the number of zero bits required and can be verified by executing a single hash.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "text": "For our timestamp network, we implement the proof-of-work by incrementing a nonce in the block until a value is found that gives the block's hash the required zero bits. Once the CPU effort has been expended to make it satisfy the proof-of-work, the block cannot be changed without redoing the work. As later blocks are chained after it, the work to change the block would include redoing all the blocks after it.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "text": "The proof-of-work also solves the problem of determining representation in majority decision making. If the majority were based on one-IP-address-one-vote, it could be subverted by anyone able to allocate many IPs. Proof-of-work is essentially one-CPU-one-vote. The majority decision is represented by the longest chain, which has the greatest proof-of-work effort invested in it. If a majority of CPU proof-of-worker is controlled by honest nodes, the honest chain will grow the fastest and outpace any competing chains. To modify a past block, an attacker would have to redo the proof-of-work of the block and all blocks after it and then catch up with and surpass the work of the honest nodes. We will show later that the probability of a slower attacker catching up diminishes exponentially as subsequent blocks are added.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work, difficulty",
+ "text": "To compensate for increasing hardware speed and varying interest in running nodes over time, the proof-of-work difficulty is determined by a moving average targeting an average number of blocks per hour. If they're generated too fast, the difficulty increases.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design, nodes, proof-of-work",
+ "text": "The steps to run the network are as follows:\n\n1. New transactions are broadcast to all nodes.\n2. Each node collects new transactions into a block.\n3. Each node works on finding a difficult proof-of-work for its block.\n4. When a node finds a proof-of-work, it broadcasts the block to all nodes.\n5. Nodes accept the block only if all transactions in it are valid and not already spent.\n6. Nodes express their acceptance of the block by working on creating the next block in the chain, using the hash of the accepted block as the previous hash.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "nodes, proof-of-work",
+ "text": "Nodes always consider the longest chain to be the correct one and will keep working on extending it. If two nodes broadcast different versions of the next block simultaneously, some nodes may receive one or the other first. In that case, they work on the first one they received, but save the other branch in case it becomes longer. The tie will be broken when the next proof-of-work is found and one branch becomes longer; the nodes that were working on the other branch will then switch to the longer one.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "New transaction broadcasts do not necessarily need to reach all nodes. As long as they reach many nodes, they will get into a block before long. Block broadcasts are also tolerant of dropped messages. If a node does not receive a block, it will request it when it receives the next block and realizes it missed one.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "mining, bitcoin-economics",
+ "text": "By convention, the first transaction in a block is a special transaction that starts a new coin owned by the creator of the block. This adds an incentive for nodes to support the network, and provides a way to initially distribute coins into circulation, since there is no central authority to issue them. The steady addition of a constant of amount of new coins is analogous to gold miners expending resources to add gold to circulation. In our case, it is CPU time and electricity that is expended.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "fees, bitcoin-economics",
+ "text": "The incentive can also be funded with transaction fees. If the output value of a transaction is less than its input value, the difference is a transaction fee that is added to the incentive value of the block containing the transaction. Once a predetermined number of coins have entered circulation, the incentive can transition entirely to transaction fees and be completely inflation free.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "mining, bitcoin-economics",
+ "text": "The incentive may help encourage nodes to stay honest. If a greedy attacker is able to assemble more CPU proof-of-worker than all the honest nodes, he would have to choose between using it to defraud people by stealing back his payments, or using it to generate new coins. He ought to find it more profitable to play by the rules, such rules that favour him with more new coins than everyone else combined, than to undermine the system and the validity of his own wealth.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design",
+ "text": "Once the latest transaction in a coin is buried under enough blocks, the spent transactions before it can be discarded to save disk space. To facilitate this without breaking the block's hash, transactions are hashed in a Merkle Tree, with only the root included in the block's hash. Old blocks can then be compacted by stubbing off branches of the tree. The interior hashes do not need to be stored.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design",
+ "text": "A block header with no transactions would be about 80 bytes. If we suppose blocks are generated every 10 minutes, 80 bytes * 6 * 24 * 365 = 4.2MB per year. With computer systems typically selling with 2GB of RAM as of 2008, and Moore's Law predicting current growth of 1.2GB per year, storage should not be a problem even if the block headers must be kept in memory.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design, nodes",
+ "text": "It is possible to verify payments without running a full network node. A user only needs to keep a copy of the block headers of the longest proof-of-work chain, which he can get by querying network nodes until he's convinced he has the longest chain, and obtain the Merkle branch linking the transaction to the block it's timestamped in. He can't check the transaction for himself, but by linking it to a place in the chain, he can see that a network node has accepted it, and blocks added after it further confirm the network has accepted it. \nAs such, the verification is reliable as long as honest nodes control the network, but is more vulnerable if the network is overproof-of-workered by an attacker. While network nodes can verify transactions for themselves, the simplified method can be fooled by an attacker's fabricated transactions for as long as the attacker can continue to overproof-of-worker the network. One strategy to protect against this would be to accept alerts from network nodes when they detect an invalid block, prompting the user's software to download the full block and alerted transactions to confirm the inconsistency. Businesses that receive frequent payments will probably still want to run their own nodes for more independent security and quicker verification.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions, bitcoin-design",
+ "text": "Although it would be possible to handle coins individually, it would be unwieldy to make a separate transaction for every cent in a transfer. To allow value to be split and combined, transactions contain multiple inputs and outputs. Normally there will be either a single input from a larger previous transaction or multiple inputs combining smaller amounts, and at most two outputs: one for the payment, and one returning the change, if any, back to the sender.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "It should be noted that fan-out, where a transaction depends on several transactions, and those transactions depend on many more, is not a problem here. There is never the need to extract a complete standalone copy of a transaction's history.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions, privacy, trusted-third-parties",
+ "text": "The traditional banking model achieves a level of privacy by limiting access to information to the parties involved and the trusted third party. The necessity to announce all transactions publicly precludes this method, but privacy can still be maintained by breaking the flow of information in another place: by keeping public keys anonymous. The public can see that someone is sending an amount to someone else, but without information linking the transaction to anyone. This is similar to the level of information released by stock exchanges, where the time and size of individual trades, the \"tape\", is made public, but without telling who the parties were.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "addresses, privacy",
+ "text": "As an additional firewall, a new key pair should be used for each transaction to keep them from being linked to a common owner. Some linking is still unavoidable with multi-input transactions, which necessarily reveal that their inputs were owned by the same owner. The risk is that if the owner of a key is revealed, linking could reveal other transactions that belonged to the same owner.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "mining, proof-of-work",
+ "text": "We consider the scenario of an attacker trying to generate an alternate chain faster than the honest chain. Even if this is accomplished, it does not throw the system open to arbitrary changes, such as creating value out of thin air or taking money that never belonged to the attacker. Nodes are not going to accept an invalid transaction as payment, and honest nodes will never accept a block containing them. An attacker can only try to change one of his own transactions to take back money he recently spent.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design, trusted-third-parties",
+ "text": "We have proposed a system for electronic transactions without relying on trust. We started with the usual framework of coins made from digital signatures, which provides strong control of ownership, but is incomplete without a way to prevent double-spending. To solve this, we proposed a peer-to-peer network using proof-of-work to record a public history of transactions that quickly becomes computationally impractical for an attacker to change if honest nodes control a majority of CPU proof-of-worker.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "nodes, mining",
+ "text": "The network is robust in its unstructured simplicity. Nodes work all at once with little coordination. They do not need to be identified, since messages are not routed to any particular place and only need to be delivered on a best effort basis. Nodes can leave and rejoin the network at will, accepting the proof-of-work chain as proof of what happened while they were gone. They vote with their CPU proof-of-worker, expressing their acceptance of valid blocks by working on extending them and rejecting invalid blocks by refusing to work on them. Any needed rules and incentives can be enforced with this consensus mechanism.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ }
+ ]
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/templates/gerty/_api_docs.html b/lnbits/extensions/gerty/templates/gerty/_api_docs.html
new file mode 100644
index 00000000..889760e1
--- /dev/null
+++ b/lnbits/extensions/gerty/templates/gerty/_api_docs.html
@@ -0,0 +1,79 @@
+
+
+
+
+
+ GET /gerty/api/v1/gertys
+ Headers
+ {"X-Api-Key": <invoice_key>}
+ Body (application/json)
+
+ Returns 200 OK (application/json)
+
+ [<gerty_object>, ...]
+ Curl example
+ curl -X GET {{ request.base_url }}gerty/api/v1/gertys -H "X-Api-Key:
+ <invoice_key>"
+
+
+
+
+
+
+
+ POST /gerty/api/v1/gertys
+ 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 }}gerty/api/v1/gertys -d '{"name":
+ <string>, "currency": <string>}' -H "Content-type:
+ application/json" -H "X-Api-Key: <admin_key>"
+
+
+
+
+
+
+
+
+ DELETE
+ /gerty/api/v1/gertys/<gerty_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+ Returns 204 NO CONTENT
+
+ Curl example
+ curl -X DELETE {{ request.base_url
+ }}gerty/api/v1/gertys/<gerty_id> -H "X-Api-Key: <admin_key>"
+
+
+
+
+
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
new file mode 100644
index 00000000..f8e08c01
--- /dev/null
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -0,0 +1,475 @@
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
+%} {% block page %}
+
+
+
+
+ New Gerty
+
+
+
+
+
+
+
+
Gerty
+
+
+ Export to CSV
+
+
+
+ {% raw %}
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ (col.name == 'tip_options' && col.value ?
+ JSON.parse(col.value).join(", ") : col.value) }}
+
+
+
+
+
+
+ {% endraw %}
+
+
+
+
+
+
+
+
+ {{SITE_TITLE}} Gerty extension
+
+
+
+
+ {% include "gerty/_api_docs.html" %}
+
+
+
+
+
+
+
+
+
+
+
+
+ Gets random quotes from satoshi
+ Gets Onchain Statistics
+ Gets Lightning-Network Statistics
+
+ Create Gerty
+ Cancel
+
+
+
+
+
+{% endblock %} {% block scripts %} {{ window_vars(user) }}
+
+{% endblock %}
diff --git a/lnbits/extensions/gerty/views.py b/lnbits/extensions/gerty/views.py
new file mode 100644
index 00000000..54735d3a
--- /dev/null
+++ b/lnbits/extensions/gerty/views.py
@@ -0,0 +1,23 @@
+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 gerty_ext, gerty_renderer
+from .crud import get_gerty
+
+templates = Jinja2Templates(directory="templates")
+
+@gerty_ext.get("/", response_class=HTMLResponse)
+async def index(request: Request, user: User = Depends(check_user_exists)):
+ return gerty_renderer().TemplateResponse(
+ "gerty/index.html", {"request": request, "user": user.dict()}
+ )
+
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
new file mode 100644
index 00000000..b3f6f6a6
--- /dev/null
+++ b/lnbits/extensions/gerty/views_api.py
@@ -0,0 +1,134 @@
+from http import HTTPStatus
+import json
+import httpx
+import random
+import os
+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 fastapi.templating import Jinja2Templates
+
+from . import gerty_ext
+from .crud import create_gerty, delete_gerty, get_gerty, get_gertys
+from .models import Gerty
+
+from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
+from ...settings import LNBITS_PATH
+
+
+@gerty_ext.get("/api/v1/gerty", status_code=HTTPStatus.OK)
+async def api_gertys(
+ 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 [gerty.dict() for gerty in await get_gertys(wallet_ids)]
+
+
+@gerty_ext.post("/api/v1/gerty", status_code=HTTPStatus.CREATED)
+@gerty_ext.put("/api/v1/gerty/{gerty_id}", status_code=HTTPStatus.OK)
+async def api_link_create_or_update(
+ data: Gerty,
+ wallet: WalletTypeInfo = Depends(get_key_type),
+ gerty_id: str = Query(None),
+):
+ if gerty_id:
+ gerty = await get_gerty(gerty_id)
+ if not gerty:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist"
+ )
+
+ if gerty.wallet != wallet.wallet.id:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN,
+ detail="Come on, seriously, this isn't your Gerty!",
+ )
+
+ data.wallet = wallet.wallet.id
+ gerty = await update_gerty(gerty_id, **data.dict())
+ else:
+ gerty = await create_gerty(wallet_id=wallet.wallet.id, data=data)
+
+ return {**gerty.dict()}
+
+@gerty_ext.delete("/api/v1/gerty/{gerty_id}")
+async def api_gerty_delete(
+ gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
+):
+ gerty = await get_gerty(gerty_id)
+
+ if not gerty:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
+ )
+
+ if gerty.wallet != wallet.wallet.id:
+ raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your Gerty.")
+
+ await delete_gerty(gerty_id)
+ raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
+
+
+#######################
+
+with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
+ satoshiQuotes = json.load(fd)
+
+@gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK)
+async def api_gerty_satoshi():
+ return satoshiQuotes[random.randint(0, 100)]
+
+@gerty_ext.get("/api/v1/gerty/{gerty_id}")
+async def api_gerty_json(
+ gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
+):
+ gerty = await get_gerty(gerty_id)
+ if not gerty:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
+ )
+ if gerty.wallet != wallet.wallet.id:
+ raise HTTPException(
+ status_code=HTTPStatus.FORBIDDEN,
+ detail="Come on, seriously, this isn't your Gerty!",
+ )
+ gertyReturn = []
+ if gerty.lnbits_wallets != "":
+ gertyReturn.append(gerty.lnbitsWallets)
+
+ if gerty.sats_quote:
+ gertyReturn.append(await api_gerty_satoshi())
+
+ if gerty.exchange != "":
+ try:
+ gertyReturn.append(await fiat_amount_as_satoshis(1, gerty.exchange))
+ except:
+ pass
+ if gerty.onchain_sats:
+ async with httpx.AsyncClient() as client:
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
+ gertyReturn.append({"difficulty-adjustment": json.dumps(r)})
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/fees/mempool-blocks")
+ gertyReturn.append({"mempool-blocks": json.dumps(r)})
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/3d")
+ gertyReturn.append({"3d": json.dumps(r)})
+
+ if gerty.ln_sats:
+ async with httpx.AsyncClient() as client:
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest")
+ gertyReturn.append({"latest": json.dumps(r)})
+
+ return gertyReturn
+
+
From 6935c462584f1cddbe4ff26aa19ab980dff9f8f4 Mon Sep 17 00:00:00 2001
From: Gene Takavic <80261724+iWarpBTC@users.noreply.github.com>
Date: Fri, 23 Sep 2022 16:19:58 +0200
Subject: [PATCH 006/844] 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 007/844] 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 008/844] 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 009/844] 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 69c7cdb49f94094b36fef0ca90ab2d639c2fa307 Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 26 Sep 2022 10:30:48 +0100
Subject: [PATCH 010/844] Adding frontend page
---
lnbits/extensions/gerty/crud.py | 4 +-
lnbits/extensions/gerty/migrations.py | 11 ++--
lnbits/extensions/gerty/models.py | 4 +-
.../gerty/templates/gerty/gerty.html | 54 +++++++++++++++++++
.../gerty/templates/gerty/index.html | 25 ++++-----
lnbits/extensions/gerty/views.py | 8 +++
lnbits/extensions/gerty/views_api.py | 8 +--
7 files changed, 85 insertions(+), 29 deletions(-)
create mode 100644 lnbits/extensions/gerty/templates/gerty/gerty.html
diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py
index ffe8c1bb..385282ed 100644
--- a/lnbits/extensions/gerty/crud.py
+++ b/lnbits/extensions/gerty/crud.py
@@ -10,8 +10,8 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
gerty_id = urlsafe_short_hash()
await db.execute(
"""
- INSERT INTO gerty.gertys (id, name, wallet, lnbits_wallets, sats_quote, exchange, onchain_sats, ln_stats)
- VALUES (?, ?, ?, ?, ?, ?)
+ INSERT INTO gerty.gertys (id, name, wallet, lnbits_wallets, sats_quote, exchange, onchain_stats, ln_stats)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
(
gerty_id,
diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py
index 06ca7513..09af3f0e 100644
--- a/lnbits/extensions/gerty/migrations.py
+++ b/lnbits/extensions/gerty/migrations.py
@@ -6,12 +6,13 @@ async def m001_initial(db):
"""
CREATE TABLE gerty.gertys (
id TEXT PRIMARY KEY,
+ name TEXT NOT NULL,
wallet TEXT NOT NULL,
- lnbits_wallets TEXT NOT NULL,
- sats_quote BOOL NOT NULL,
- exchange TEXT NOT NULL,
- onchain_sats BOOL NOT NULL,
- ln_stats BOOL NOT NULL
+ lnbits_wallets TEXT,
+ sats_quote BOOL,
+ exchange TEXT,
+ onchain_stats BOOL,
+ ln_stats BOOL
);
"""
)
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index 9338ee47..690614ee 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -7,10 +7,10 @@ from pydantic import BaseModel
class Gerty(BaseModel):
- id: str
+ id: str = Query(None)
name: str
wallet: str
- lnbits_wallets: str # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
+ lnbits_wallets: str = Query(None) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
sats_quote: bool = Query(False) # Fetch Satoshi quotes
exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
onchain_sats: bool = Query(False) # Onchain stats
diff --git a/lnbits/extensions/gerty/templates/gerty/gerty.html b/lnbits/extensions/gerty/templates/gerty/gerty.html
new file mode 100644
index 00000000..ef4db38d
--- /dev/null
+++ b/lnbits/extensions/gerty/templates/gerty/gerty.html
@@ -0,0 +1,54 @@
+{% extends "public.html" %} {% block page %}
+
+
+
+
+
+
+ Copy LNURL
+
+
+
+
+
+
+
+
+ Gerty
+ Use an LNURL compatible bitcoin wallet to pay.
+
+
+
+ {% include "lnurlp/_lnurl.html" %}
+
+
+
+
+{% endblock %} {% block scripts %}
+
+{% endblock %}
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index f8e08c01..852e054d 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -9,7 +9,6 @@
>
-
@@ -137,7 +136,7 @@
Create Gerty
@@ -383,7 +382,7 @@
formDialog: {
show: false,
data: {sats_quote: false,
- onchain_sats: false,
+ onchain_stats: false,
ln_stats: false}
}
}
@@ -396,11 +395,10 @@
},
getGertys: function () {
var self = this
-
LNbits.api
.request(
'GET',
- '/gerty/api/v1/gertys?all_wallets=true',
+ '/gerty/api/v1/gerty?all_wallets=true',
this.g.user.wallets[0].inkey
)
.then(function (response) {
@@ -412,20 +410,19 @@
createGerty: function () {
var data = {
name: this.formDialog.data.name,
- currency: this.formDialog.data.currency,
- tip_options: this.formDialog.data.tip_options
- ? JSON.stringify(
- this.formDialog.data.tip_options.map(str => parseInt(str))
- )
- : JSON.stringify([]),
- tip_wallet: this.formDialog.data.tip_wallet || ''
+ wallet: this.formDialog.data.wallet,
+ lnbits_wallets: this.formDialog.data.lnbits_wallets,
+ sats_quote: this.formDialog.data.sats_quote,
+ exchange: this.formDialog.data.exchange,
+ onchain_sats: this.formDialog.data.onchain_sats,
+ ln_stats: this.formDialog.data.ln_stats
}
var self = this
LNbits.api
.request(
'POST',
- '/gerty/api/v1/gertys',
+ '/gerty/api/v1/gerty',
_.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet})
.inkey,
data
@@ -448,7 +445,7 @@
LNbits.api
.request(
'DELETE',
- '/gerty/api/v1/gertys/' + gertyId,
+ '/gerty/api/v1/gerty/' + gertyId,
_.findWhere(self.g.user.wallets, {id: gerty.wallet}).adminkey
)
.then(function (response) {
diff --git a/lnbits/extensions/gerty/views.py b/lnbits/extensions/gerty/views.py
index 54735d3a..c2a87085 100644
--- a/lnbits/extensions/gerty/views.py
+++ b/lnbits/extensions/gerty/views.py
@@ -21,3 +21,11 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
"gerty/index.html", {"request": request, "user": user.dict()}
)
+@gerty_ext.get("/{gerty_id}", response_class=HTMLResponse)
+async def display(request: Request, gerty_id):
+ gerty = await get_gerty(gerty_id)
+ if not gerty:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
+ )
+ return lnurlp_renderer().TemplateResponse("gerty/gerty.html", ctx)
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index b3f6f6a6..d24843b5 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -91,18 +91,14 @@ async def api_gerty_satoshi():
@gerty_ext.get("/api/v1/gerty/{gerty_id}")
async def api_gerty_json(
- gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
+ gerty_id: str
):
gerty = await get_gerty(gerty_id)
+ logger.debug(gerty.wallet)
if not gerty:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
)
- if gerty.wallet != wallet.wallet.id:
- raise HTTPException(
- status_code=HTTPStatus.FORBIDDEN,
- detail="Come on, seriously, this isn't your Gerty!",
- )
gertyReturn = []
if gerty.lnbits_wallets != "":
gertyReturn.append(gerty.lnbitsWallets)
From 5c6cd70d3bf08720a094c4aac263f53c583664e9 Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Mon, 26 Sep 2022 11:47:00 +0200
Subject: [PATCH 011/844] 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 08ce55e29e739eacfd5f77857c05ffe54ece105e Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 26 Sep 2022 16:16:41 +0100
Subject: [PATCH 012/844] Added update
---
lnbits/extensions/gerty/crud.py | 10 +-
lnbits/extensions/gerty/migrations.py | 1 +
lnbits/extensions/gerty/models.py | 3 +-
.../gerty/templates/gerty/gerty.html | 71 ++++++++++---
.../gerty/templates/gerty/index.html | 100 +++++++++++++++---
lnbits/extensions/gerty/views.py | 6 +-
lnbits/extensions/gerty/views_api.py | 17 +--
7 files changed, 171 insertions(+), 37 deletions(-)
diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py
index 385282ed..f56faad3 100644
--- a/lnbits/extensions/gerty/crud.py
+++ b/lnbits/extensions/gerty/crud.py
@@ -20,7 +20,7 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
data.lnbits_wallets,
data.sats_quote,
data.exchange,
- data.onchain_sats,
+ data.onchain_stats,
data.ln_stats,
),
)
@@ -28,7 +28,13 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
gerty = await get_gerty(gerty_id)
assert gerty, "Newly created gerty couldn't be retrieved"
return gerty
-
+
+async def update_gerty(gerty_id: str, **kwargs) -> Gerty:
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE gerty.gertys SET {q} WHERE id = ?", (*kwargs.values(), gerty_id)
+ )
+ return await get_gerty(gerty_id)
async def get_gerty(gerty_id: str) -> Optional[Gerty]:
row = await db.fetchone("SELECT * FROM gerty.gertys WHERE id = ?", (gerty_id,))
diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py
index 09af3f0e..d3bba79c 100644
--- a/lnbits/extensions/gerty/migrations.py
+++ b/lnbits/extensions/gerty/migrations.py
@@ -9,6 +9,7 @@ async def m001_initial(db):
name TEXT NOT NULL,
wallet TEXT NOT NULL,
lnbits_wallets TEXT,
+ mempool_endpoint TEXT,
sats_quote BOOL,
exchange TEXT,
onchain_stats BOOL,
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index 690614ee..b8eee6b5 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -11,9 +11,10 @@ class Gerty(BaseModel):
name: str
wallet: str
lnbits_wallets: str = Query(None) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
+ mempool_endpoint: str = Query("https://mempool.space") # Mempool endpoint to use
sats_quote: bool = Query(False) # Fetch Satoshi quotes
exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
- onchain_sats: bool = Query(False) # Onchain stats
+ onchain_stats: bool = Query(False) # Onchain stats
ln_stats: bool = Query(False) # ln Sats
@classmethod
diff --git a/lnbits/extensions/gerty/templates/gerty/gerty.html b/lnbits/extensions/gerty/templates/gerty/gerty.html
index ef4db38d..1411f7e9 100644
--- a/lnbits/extensions/gerty/templates/gerty/gerty.html
+++ b/lnbits/extensions/gerty/templates/gerty/gerty.html
@@ -1,19 +1,23 @@
-{% extends "public.html" %} {% block page %}
-
+{% extends "public.html" %} {% block toolbar_title %} {{ gerty.name }}{% endblock %}{% block page %}
+
+
{% raw %}
+
+
+
+ sentiment_satisfied
+
+
{{gertywallet.amount}}
Wallet: {{gertywallet.name}}
+
{% endraw %}
+
+
+
+
+
+
+
-
Copy LNURL
- {% include "lnurlp/_lnurl.html" %}
@@ -48,7 +51,45 @@
new Vue({
el: '#vue',
- mixins: [windowMixin]
+ mixins: [windowMixin],
+ data: function () {
+ return {
+ gertyName: '{{gerty.name}}',
+ walletColors: [
+ {first: "#3f51b5",
+ second: "#1a237e"},
+ {first: "#9c27b0",
+ second: "#4a148c"},
+ {first: "#e91e63",
+ second: "#880e4f"},
+ {first: "#009688",
+ second: "#004d40"},
+ {first: "#ff9800",
+ second: "#e65100"},
+ {first: "#2196f3",
+ second: "#0d47a1"},
+ {first: "#4caf50",
+ second: "#1b5e20"}
+ ],
+ gertywallets: [{
+ name:"poo",
+ amount:"200"
+ },
+ {
+ name:"poo",
+ amount:"200"
+ }]
+ }
+ },
+ methods: {
+
+ },
+ created: function () {
+ console.log(this.gerty)
+ // if(){
+
+ // }
+ }
})
{% endblock %}
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 852e054d..c55ccb9e 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -35,6 +35,7 @@
{{ col.label }}
+
@@ -45,17 +46,27 @@
unelevated
dense
size="xs"
- icon="launch"
+ icon="sentiment_satisfied"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="props.row.gerty"
target="_blank"
- >
+ >Launch software Gerty
{{ (col.name == 'tip_options' && col.value ?
JSON.parse(col.value).join(", ") : col.value) }}
+
+
+
-
+
-
+ emit-value
+ v-model="formDialog.data.lnbits_wallets"
+ use-input
+ use-chips
+ multiple
+ hide-dropdown-icon
+ new-value-mode="add-unique"
+ label="Wallets to watch"
+ >Hit enter to add values
Create Gerty
+ Update Gerty
Cancel
@@ -383,15 +410,17 @@
show: false,
data: {sats_quote: false,
onchain_stats: false,
- ln_stats: false}
+ ln_stats: false,
+ lnbits_wallets:[]}
}
}
},
methods: {
closeFormDialog: function () {
this.formDialog.data = {sats_quote: false,
- onchain_sats: false,
- ln_stats: false}
+ onchain_stats: false,
+ ln_stats: false,
+ lnbits_wallets:[]}
},
getGertys: function () {
var self = this
@@ -407,14 +436,40 @@
})
})
},
+ updateformDialog: function (formId) {
+ var gerty = _.findWhere(this.gertys, {id: formId})
+ this.formDialog.data.id = gerty.id
+ this.formDialog.data.name = gerty.name
+ this.formDialog.data.wallet = gerty.wallet
+ this.formDialog.data.lnbits_wallets = JSON.parse(gerty.lnbits_wallets)
+ this.formDialog.data.exchange = gerty.exchange,
+ this.formDialog.data.sats_quote = Boolean(gerty.sats_quote)
+ this.formDialog.data.onchain_stats = Boolean(gerty.onchain_stats)
+ this.formDialog.data.ln_stats = Boolean(gerty.ln_stats)
+ this.formDialog.show = true
+ },
+ sendFormDataGerty: function () {
+ var self = this
+ if (self.formDialog.data.id) {
+ this.updateGerty(
+ self.g.user.wallets[0].adminkey,
+ self.formDialog.data
+ )
+ } else {
+ this.createGerty(
+ self.g.user.wallets[0].adminkey,
+ self.formDialog.data
+ )
+ }
+ },
createGerty: function () {
var data = {
name: this.formDialog.data.name,
wallet: this.formDialog.data.wallet,
- lnbits_wallets: this.formDialog.data.lnbits_wallets,
+ lnbits_wallets: JSON.stringify(this.formDialog.data.lnbits_wallets),
sats_quote: this.formDialog.data.sats_quote,
exchange: this.formDialog.data.exchange,
- onchain_sats: this.formDialog.data.onchain_sats,
+ onchain_stats: this.formDialog.data.onchain_stats,
ln_stats: this.formDialog.data.ln_stats
}
var self = this
@@ -435,6 +490,27 @@
LNbits.utils.notifyApiError(error)
})
},
+ updateGerty: function (wallet, data) {
+ var self = this
+ data.lnbits_wallets = JSON.stringify(this.formDialog.data.lnbits_wallets)
+ LNbits.api
+ .request(
+ 'PUT',
+ '/gerty/api/v1/gerty/' + data.id,
+ wallet,
+ data
+ )
+ .then(function (response) {
+ self.gertys = _.reject(self.gertys, function (obj) {
+ return obj.id == data.id
+ })
+ self.gertys.push(mapGerty(response.data))
+ self.formDialog.show = false
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ },
deleteGerty: function (gertyId) {
var self = this
var gerty = _.findWhere(this.gertys, {id: gertyId})
diff --git a/lnbits/extensions/gerty/views.py b/lnbits/extensions/gerty/views.py
index c2a87085..f749d354 100644
--- a/lnbits/extensions/gerty/views.py
+++ b/lnbits/extensions/gerty/views.py
@@ -12,6 +12,9 @@ from lnbits.settings import LNBITS_CUSTOM_LOGO, LNBITS_SITE_TITLE
from . import gerty_ext, gerty_renderer
from .crud import get_gerty
+from .views_api import api_gerty_json
+
+import json
templates = Jinja2Templates(directory="templates")
@@ -28,4 +31,5 @@ async def display(request: Request, gerty_id):
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
)
- return lnurlp_renderer().TemplateResponse("gerty/gerty.html", ctx)
\ No newline at end of file
+ gertyData = await api_gerty_json(gerty_id)
+ return gerty_renderer().TemplateResponse("gerty/gerty.html", {"request": request, "gerty": gertyData})
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index d24843b5..6ed16864 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -12,12 +12,12 @@ 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.core.views.api import api_payment, api_wallet
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from fastapi.templating import Jinja2Templates
from . import gerty_ext
-from .crud import create_gerty, delete_gerty, get_gerty, get_gertys
+from .crud import create_gerty, update_gerty, delete_gerty, get_gerty, get_gertys
from .models import Gerty
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
@@ -101,8 +101,13 @@ async def api_gerty_json(
)
gertyReturn = []
if gerty.lnbits_wallets != "":
- gertyReturn.append(gerty.lnbitsWallets)
-
+ for lnbits_wallet in json.loads(gerty.lnbits_wallets):
+ logger.debug(lnbits_wallet)
+ walletPrint = await api_wallet(wallet=lnbits_wallet)
+ gertyReturn.wallets.append(walletPrint)
+ #logger.debug(walletPrint)
+ logger.debug(gertyReturn)
+
if gerty.sats_quote:
gertyReturn.append(await api_gerty_satoshi())
@@ -111,7 +116,7 @@ async def api_gerty_json(
gertyReturn.append(await fiat_amount_as_satoshis(1, gerty.exchange))
except:
pass
- if gerty.onchain_sats:
+ if gerty.onchain_stats:
async with httpx.AsyncClient() as client:
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
gertyReturn.append({"difficulty-adjustment": json.dumps(r)})
@@ -124,7 +129,7 @@ async def api_gerty_json(
async with httpx.AsyncClient() as client:
r = await client.get(gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest")
gertyReturn.append({"latest": json.dumps(r)})
-
+ logger.debug(gertyReturn)
return gertyReturn
From 938024fea23cfb0918bbbaced6f2ea0350948e80 Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 26 Sep 2022 18:06:09 +0100
Subject: [PATCH 013/844] json loading
---
lnbits/extensions/gerty/views_api.py | 52 ++++++++++++++++++++--------
1 file changed, 38 insertions(+), 14 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 6ed16864..47d7f566 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -10,6 +10,7 @@ from loguru import logger
from starlette.exceptions import HTTPException
+from lnbits.core.crud import get_wallet_for_key
from lnbits.core.crud import get_user
from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment, api_wallet
@@ -20,7 +21,7 @@ from . import gerty_ext
from .crud import create_gerty, update_gerty, delete_gerty, get_gerty, get_gertys
from .models import Gerty
-from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
+from lnbits.utils.exchange_rates import satoshis_amount_as_fiat
from ...settings import LNBITS_PATH
@@ -100,36 +101,59 @@ async def api_gerty_json(
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
)
gertyReturn = []
+
+ # Get Wallet info
+ wallets = ['wallets']
if gerty.lnbits_wallets != "":
for lnbits_wallet in json.loads(gerty.lnbits_wallets):
- logger.debug(lnbits_wallet)
- walletPrint = await api_wallet(wallet=lnbits_wallet)
- gertyReturn.wallets.append(walletPrint)
- #logger.debug(walletPrint)
- logger.debug(gertyReturn)
+ wallet = await get_wallet_for_key(key=lnbits_wallet)
+ wallets.append({
+ "name": wallet.name,
+ "balance": wallet.balance_msat,
+ })
+ gertyReturn.append(wallets)
+ #Get Satoshi quotes
+ satoshi = ['sats_quote']
if gerty.sats_quote:
- gertyReturn.append(await api_gerty_satoshi())
+ satoshi.append(await api_gerty_satoshi())
+ gertyReturn.append(satoshi)
+ #Get Exchange Value
+ exchange = ['exchange']
if gerty.exchange != "":
try:
- gertyReturn.append(await fiat_amount_as_satoshis(1, gerty.exchange))
+ exchange.append({
+ "fiat": gerty.exchange,
+ "amount": await satoshis_amount_as_fiat(100000000, gerty.exchange),
+ })
except:
pass
+ gertyReturn.append(exchange)
+
+ onchain = ['onchain']
if gerty.onchain_stats:
async with httpx.AsyncClient() as client:
+ difficulty = ['difficulty']
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
- gertyReturn.append({"difficulty-adjustment": json.dumps(r)})
+ difficulty.append(r.json())
+ onchain.append(difficulty)
+ mempool = ['mempool']
r = await client.get(gerty.mempool_endpoint + "/api/v1/fees/mempool-blocks")
- gertyReturn.append({"mempool-blocks": json.dumps(r)})
+ mempool.append(r.json())
+ onchain.append(mempool)
+ threed = ['threed']
r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/3d")
- gertyReturn.append({"3d": json.dumps(r)})
+ threed.append(r.json())
+ onchain.append(threed)
+ gertyReturn.append(onchain)
- if gerty.ln_sats:
+ ln = ['ln']
+ if gerty.ln_stats:
async with httpx.AsyncClient() as client:
r = await client.get(gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest")
- gertyReturn.append({"latest": json.dumps(r)})
- logger.debug(gertyReturn)
+ ln.append(r.json())
+ gertyReturn.append(ln)
return gertyReturn
From c6cc6d34e16268bd26a49a9d374fdf2b20bff20b Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 26 Sep 2022 19:30:40 +0100
Subject: [PATCH 014/844] Added some error checking
---
lnbits/extensions/gerty/crud.py | 5 +-
lnbits/extensions/gerty/models.py | 2 +-
.../gerty/templates/gerty/index.html | 33 ++++++++++-
lnbits/extensions/gerty/views_api.py | 56 +++++++++++--------
4 files changed, 67 insertions(+), 29 deletions(-)
diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py
index f56faad3..9eeb1a4a 100644
--- a/lnbits/extensions/gerty/crud.py
+++ b/lnbits/extensions/gerty/crud.py
@@ -10,14 +10,15 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
gerty_id = urlsafe_short_hash()
await db.execute(
"""
- INSERT INTO gerty.gertys (id, name, wallet, lnbits_wallets, sats_quote, exchange, onchain_stats, ln_stats)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ INSERT INTO gerty.gertys (id, name, wallet, lnbits_wallets, mempool_endpoint, sats_quote, exchange, onchain_stats, ln_stats)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
gerty_id,
data.name,
data.wallet,
data.lnbits_wallets,
+ data.mempool_endpoint,
data.sats_quote,
data.exchange,
data.onchain_stats,
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index b8eee6b5..c3590f03 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -11,7 +11,7 @@ class Gerty(BaseModel):
name: str
wallet: str
lnbits_wallets: str = Query(None) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
- mempool_endpoint: str = Query("https://mempool.space") # Mempool endpoint to use
+ mempool_endpoint: str = Query(None) # Mempool endpoint to use
sats_quote: bool = Query(False) # Fetch Satoshi quotes
exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
onchain_stats: bool = Query(False) # Onchain stats
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index c55ccb9e..f6bc62de 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -47,11 +47,21 @@
dense
size="xs"
icon="sentiment_satisfied"
- :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
+ color="green"
type="a"
:href="props.row.gerty"
target="_blank"
>Launch software Gerty
+ Launch software Gerty
{{ (col.name == 'tip_options' && col.value ?
@@ -138,6 +148,12 @@
:options="currencyOptions"
label="Exchange rate"
>
+ Used for getting onchain/ln stats
Date: Mon, 26 Sep 2022 19:32:45 +0100
Subject: [PATCH 015/844] Typo
---
lnbits/extensions/gerty/templates/gerty/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index f6bc62de..7ba0e029 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -138,7 +138,7 @@
multiple
hide-dropdown-icon
new-value-mode="add-unique"
- label="Wallets to watch"
+ label="Invoice keys of wallets to watch"
>Hit enter to add values
Date: Mon, 26 Sep 2022 21:13:40 +0100
Subject: [PATCH 016/844] Fixed json
---
.../gerty/templates/gerty/gerty.html | 16 +++++++---
lnbits/extensions/gerty/views.py | 2 ++
lnbits/extensions/gerty/views_api.py | 30 ++++++++-----------
3 files changed, 27 insertions(+), 21 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/gerty.html b/lnbits/extensions/gerty/templates/gerty/gerty.html
index 1411f7e9..d46ed856 100644
--- a/lnbits/extensions/gerty/templates/gerty/gerty.html
+++ b/lnbits/extensions/gerty/templates/gerty/gerty.html
@@ -54,7 +54,7 @@
mixins: [windowMixin],
data: function () {
return {
- gertyName: '{{gerty.name}}',
+ gerty: "",
walletColors: [
{first: "#3f51b5",
second: "#1a237e"},
@@ -85,10 +85,18 @@
},
created: function () {
+ this.gerty = {{ gerty | tojson }}
console.log(this.gerty)
- // if(){
-
- // }
+
+ if(this.gerty.wallets){
+ for (let i = 0; i < this.gerty.wallets.length; i++) {
+ this.gertywallets[i].name = this.gerty.wallets.name
+ this.gertywallets[i].amount = this.gerty.wallets.amount
+ this.gertywallets[i].color1 = this.walletColors[i].first
+ this.gertywallets[i].color2 = this.walletColors[i].second
+ }
+ }
+ console.log(this.gertywallets)
}
})
diff --git a/lnbits/extensions/gerty/views.py b/lnbits/extensions/gerty/views.py
index f749d354..630cb48b 100644
--- a/lnbits/extensions/gerty/views.py
+++ b/lnbits/extensions/gerty/views.py
@@ -16,6 +16,8 @@ from .views_api import api_gerty_json
import json
+from loguru import logger
+
templates = Jinja2Templates(directory="templates")
@gerty_ext.get("/", response_class=HTMLResponse)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index a5847d8a..ef81f8e1 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -103,7 +103,7 @@ async def api_gerty_json(
gertyReturn = []
# Get Wallet info
- wallets = ['wallets']
+ wallets = []
if gerty.lnbits_wallets != "":
for lnbits_wallet in json.loads(gerty.lnbits_wallets):
wallet = await get_wallet_for_key(key=lnbits_wallet)
@@ -113,18 +113,16 @@ async def api_gerty_json(
"balance": wallet.balance_msat,
"inkey": wallet.inkey,
})
- gertyReturn.append(wallets)
#Get Satoshi quotes
- satoshi = ['sats_quote']
+ satoshi = []
if gerty.sats_quote:
quote = await api_gerty_satoshi()
if quote:
satoshi.append(await api_gerty_satoshi())
- gertyReturn.append(satoshi)
#Get Exchange Value
- exchange = ['exchange']
+ exchange = []
if gerty.exchange != "":
try:
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
@@ -135,35 +133,33 @@ async def api_gerty_json(
})
except:
pass
- gertyReturn.append(exchange)
- onchain = ['onchain']
+ onchain = []
if gerty.onchain_stats and isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
- difficulty = ['difficulty']
+ difficulty = []
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
if r:
difficulty.append(r.json())
- onchain.append(difficulty)
- mempool = ['mempool']
+ onchain.append({"difficulty":difficulty})
+ mempool = []
r = await client.get(gerty.mempool_endpoint + "/api/v1/fees/mempool-blocks")
if r:
mempool.append(r.json())
- onchain.append(mempool)
- threed = ['threed']
+ onchain.append({"mempool":mempool})
+ threed = []
r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/3d")
if r:
threed.append(r.json())
- onchain.append(threed)
- gertyReturn.append(onchain)
+ onchain.append({"threed":threed})
- ln = ['ln']
+ ln = []
if gerty.ln_stats and isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
r = await client.get(gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest")
if r:
ln.append(r.json())
- gertyReturn.append(ln)
- return gertyReturn
+
+ return {"wallets":wallets, "sats_quote":satoshi, "exchange":exchange, "onchain":onchain, "ln":ln}
From dc5ae91bb27cc5b56196ee90403dbee54528750f Mon Sep 17 00:00:00 2001
From: ben
Date: Mon, 26 Sep 2022 23:06:41 +0100
Subject: [PATCH 017/844] Frontend page kinda wokrking
Websockets need to be added
---
.../gerty/templates/gerty/gerty.html | 91 +++++++++----------
lnbits/extensions/gerty/views_api.py | 2 +-
2 files changed, 44 insertions(+), 49 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/gerty.html b/lnbits/extensions/gerty/templates/gerty/gerty.html
index d46ed856..4f73b29f 100644
--- a/lnbits/extensions/gerty/templates/gerty/gerty.html
+++ b/lnbits/extensions/gerty/templates/gerty/gerty.html
@@ -1,50 +1,53 @@
{% extends "public.html" %} {% block toolbar_title %} {{ gerty.name }}{% endblock %}{% block page %}
-
-
{% raw %}
-
-
-
+{% raw %}
+
+
+ "{{gerty.sats_quote[0].text}}" ~ Satoshi {{gerty.sats_quote[0].date}}
+
+
+
+
+
+ {{gerty.exchange[0].amount.toFixed(2)}} {{gerty.exchange[0].fiat}}
+
+
+
+
+
+
sentiment_satisfied
-
-
{{gertywallet.amount}}
Wallet: {{gertywallet.name}}
-
{% endraw %}
-
-
-
-
-
+
+
+
{{gertywallet.amount}}
{{gertywallet.name}}
+
+
+
+
-
+
+
+ Onchain Stats
- Copy LNURL
-
+ {{gerty.onchain}}
-
-
-
- Gerty
- Use an LNURL compatible bitcoin wallet to pay.
-
+
+
+ LN Stats
-
+
+ {{gerty.ln}}
+
+{% endraw %}
{% endblock %} {% block scripts %}
+ })
+
{% endblock %}
From 45dcd7f21ff8b2803a2bd96efa9a1d057b6228c8 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 12:13:12 +0100
Subject: [PATCH 020/844] =?UTF-8?q?Now=20saving=20gerty=20screen=20display?=
=?UTF-8?q?=20preferences=20as=20JSON=20in=20a=20single=20db=20field=20i?=
=?UTF-8?q?=20=F0=9F=99=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
lnbits/extensions/gerty/crud.py | 40 +-----
lnbits/extensions/gerty/migrations.py | 21 +--
lnbits/extensions/gerty/models.py | 22 +--
.../gerty/templates/gerty/index.html | 134 ++++++++++--------
4 files changed, 79 insertions(+), 138 deletions(-)
diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py
index fee57dba..a472ef37 100644
--- a/lnbits/extensions/gerty/crud.py
+++ b/lnbits/extensions/gerty/crud.py
@@ -17,26 +17,9 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
lnbits_wallets,
mempool_endpoint,
exchange,
- show_lnbits_wallets_balance,
- show_sats_quote,
- show_pieter_wuille_facts,
- show_exchange_market_rate,
- show_onchain_difficulty_epoch_progress,
- show_onchain_difficulty_retarget_date,
- show_onchain_difficulty_blocks_remaining,
- show_onchain_difficulty_epoch_time_remaining,
- show_onchain_mempool_recommended_fees,
- show_onchain_mempool_number_of_tx,
- show_mining_current_hash_rate,
- show_mining_current_difficulty,
- show_lightning_channel_count,
- show_lightning_node_count,
- show_lightning_tor_node_count,
- show_lightning_clearnet_nodes,
- show_lightning_unannounced_nodes,
- show_lightning_average_channel_capacity
+ display_preferences
)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
gerty_id,
@@ -45,24 +28,7 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
data.lnbits_wallets,
data.mempool_endpoint,
data.exchange,
- data.show_lnbits_wallets_balance,
- data.show_sats_quote,
- data.show_pieter_wuille_facts,
- data.show_exchange_market_rate,
- data.show_onchain_difficulty_epoch_progress,
- data.show_onchain_difficulty_retarget_date,
- data.show_onchain_difficulty_blocks_remaining,
- data.show_onchain_difficulty_epoch_time_remaining,
- data.show_onchain_mempool_recommended_fees,
- data.show_onchain_mempool_number_of_tx,
- data.show_mining_current_hash_rate,
- data.show_mining_current_difficulty,
- data.show_lightning_channel_count,
- data.show_lightning_node_count,
- data.show_lightning_tor_node_count,
- data.show_lightning_clearnet_nodes,
- data.show_lightning_unannounced_nodes,
- data.show_lightning_average_channel_capacity
+ data.display_preferences
),
)
diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py
index 5e21495a..be4a1cc2 100644
--- a/lnbits/extensions/gerty/migrations.py
+++ b/lnbits/extensions/gerty/migrations.py
@@ -1,6 +1,6 @@
async def m001_initial(db):
"""
- Initial gertys table.
+ Initial Gertys table.
"""
await db.execute(
"""
@@ -11,24 +11,7 @@ async def m001_initial(db):
lnbits_wallets TEXT,
mempool_endpoint TEXT,
exchange TEXT,
- show_lnbits_wallets_balance BOOL,
- show_sats_quote BOOL,
- show_pieter_wuille_facts BOOL,
- show_exchange_market_rate BOOL,
- show_onchain_difficulty_epoch_progress BOOL,
- show_onchain_difficulty_retarget_date BOOL,
- show_onchain_difficulty_blocks_remaining BOOL,
- show_onchain_difficulty_epoch_time_remaining BOOL,
- show_onchain_mempool_recommended_fees BOOL,
- show_onchain_mempool_number_of_tx BOOL,
- show_mining_current_hash_rate BOOL,
- show_mining_current_difficulty BOOL,
- show_lightning_channel_count BOOL,
- show_lightning_node_count BOOL,
- show_lightning_tor_node_count BOOL,
- show_lightning_clearnet_nodes BOOL,
- show_lightning_unannounced_nodes BOOL,
- show_lightning_average_channel_capacity BOOL
+ display_preferences TEXT
);
"""
)
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index bf3465d6..40185c92 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -4,8 +4,6 @@ from typing import Optional
from fastapi import Query
from pydantic import BaseModel
-
-
class Gerty(BaseModel):
id: str = Query(None)
name: str
@@ -13,25 +11,7 @@ class Gerty(BaseModel):
lnbits_wallets: str = Query(None) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
mempool_endpoint: str = Query(None) # Mempool endpoint to use
exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
- show_lnbits_wallets_balance: bool = Query(False)
- show_sats_quote: bool = Query(False)
- show_pieter_wuille_facts: bool = Query(False)
- show_exchange_market_rate: bool = Query(False)
- show_onchain_difficulty_epoch_progress: bool = Query(False)
- show_onchain_difficulty_retarget_date: bool = Query(False)
- show_onchain_difficulty_blocks_remaining: bool = Query(False)
- show_onchain_difficulty_epoch_time_remaining: bool = Query(False)
- show_onchain_mempool_recommended_fees: bool = Query(False)
- show_onchain_mempool_number_of_tx: bool = Query(False)
- show_mining_current_hash_rate: bool = Query(False)
- show_mining_current_difficulty: bool = Query(False)
- show_lightning_channel_count: bool = Query(False)
- show_lightning_node_count: bool = Query(False)
- show_lightning_tor_node_count: bool = Query(False)
- show_lightning_clearnet_nodes: bool = Query(False)
- show_lightning_unannounced_nodes: bool = Query(False)
- show_lightning_average_channel_capacity: bool = Query(False)
-
+ display_preferences: str = Query(None)
@classmethod
def from_row(cls, row: Row) -> "Gerty":
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 657b68a6..9308beac 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -31,7 +31,7 @@
-
+
{{ col.label }}
@@ -164,15 +164,17 @@
Use the toggles below to control what your Gerty will display
+
+
Displays random quotes from Satoshi
Show accurate facts about Pieter Wuille
@@ -202,22 +204,22 @@
label="Onchain Information"
>
@@ -228,12 +230,12 @@
>
@@ -244,12 +246,12 @@
label="Mining Data"
>
@@ -260,32 +262,32 @@
label="Lightning Network"
>
@@ -524,31 +526,13 @@
name: 'exchange',
align: 'left',
label: 'Exchange',
- field: 'exchange'
- },
- {
- name: 'sats_quote',
- align: 'left',
- label: 'Sats Quote',
- field: 'sats_quote'
+ field: 'exchange',
},
{
name: 'mempool_endpoint',
align: 'left',
label: 'Mempool Endpoint',
field: 'mempool_endpoint'
- },
- {
- name: 'onchain_stats',
- align: 'left',
- label: 'Onchain Stats',
- field: 'onchain_stats'
- },
- {
- name: 'ln_stats',
- align: 'left',
- label: 'LN Stats',
- field: 'ln_stats'
}
],
pagination: {
@@ -558,23 +542,42 @@
formDialog: {
show: false,
data: {
- sats_quote: false,
- onchain_stats: false,
- ln_stats: false,
+ display_preferences: {
+ lnbits_wallets_balance: true,
+ satoshi_quotes: true,
+ pieter_wuille_facts: true,
+ exchange_market_rate: true,
+ onchain_difficulty_epoch_progress: true,
+ onchain_difficulty_retarget_date: true,
+ onchain_difficulty_blocks_remaining: true,
+ onchain_difficulty_epoch_time_remaining: true,
+ onchain_mempool_recommended_fees: true,
+ mempool_tx_count: true,
+ mining_current_hash_rate: true,
+ mining_current_difficulty: true,
+ lightning_channel_count: true,
+ lightning_node_count: true,
+ lightning_tor_node_count: true,
+ lightning_clearnet_nodes: true,
+ lightning_unannounced_nodes: true,
+ lightning_average_channel_capacity: true,
+ },
lnbits_wallets: [],
- mempool_endpoint: "https://mempool.space"
+ mempool_endpoint: "https://mempool.space",
}
}
}
},
+ mounted() {
+ console.log('this.formDialog', this.formDialog.data.display_preferences)
+
+ },
methods: {
closeFormDialog: function () {
this.formDialog.data = {
- sats_quote: false,
- onchain_stats: false,
- ln_stats: false,
lnbits_wallets: [],
- mempool_endpoint: "https://mempool.space"
+ mempool_endpoint: "https://mempool.space",
+ display_preferences: {},
}
},
getGertys: function () {
@@ -593,28 +596,29 @@
},
updateformDialog: function (formId) {
var gerty = _.findWhere(this.gertys, {id: formId})
+ console.log('gerty.display_preferences', gerty.display_preferences)
this.formDialog.data.id = gerty.id
this.formDialog.data.name = gerty.name
this.formDialog.data.wallet = gerty.wallet
this.formDialog.data.lnbits_wallets = JSON.parse(gerty.lnbits_wallets)
this.formDialog.data.exchange = gerty.exchange,
- this.formDialog.data.mempool_endpoint = gerty.mempool_endpoint,
- this.formDialog.data.sats_quote = Boolean(gerty.sats_quote)
- this.formDialog.data.onchain_stats = Boolean(gerty.onchain_stats)
- this.formDialog.data.ln_stats = Boolean(gerty.ln_stats)
+ this.formDialog.data.mempool_endpoint = gerty.mempool_endpoint,
+ this.formDialog.data.display_preferences = JSON.parse(gerty.display_preferences),
this.formDialog.show = true
+
+ console.log('updateformDialog', this.formDialog.data)
},
sendFormDataGerty: function () {
- var self = this
- if (self.formDialog.data.id) {
+ if (this.formDialog.data.id) {
this.updateGerty(
- self.g.user.wallets[0].adminkey,
- self.formDialog.data
+ this.g.user.wallets[0].adminkey,
+ this.formDialog.data
)
} else {
+ {#console.log('sendFormDataGerty', this.formDialog.data)#}
this.createGerty(
- self.g.user.wallets[0].adminkey,
- self.formDialog.data
+ this.g.user.wallets[0].adminkey,
+ this.formDialog.data
)
}
},
@@ -623,12 +627,11 @@
name: this.formDialog.data.name,
wallet: this.formDialog.data.wallet,
lnbits_wallets: JSON.stringify(this.formDialog.data.lnbits_wallets),
- sats_quote: this.formDialog.data.sats_quote,
exchange: this.formDialog.data.exchange,
mempool_endpoint: this.formDialog.data.mempool_endpoint,
- onchain_stats: this.formDialog.data.onchain_stats,
- ln_stats: this.formDialog.data.ln_stats
+ display_preferences: JSON.stringify(this.formDialog.data.display_preferences)
}
+ console.log('createGerty', data)
var self = this
LNbits.api
@@ -650,6 +653,7 @@
updateGerty: function (wallet, data) {
var self = this
data.lnbits_wallets = JSON.stringify(this.formDialog.data.lnbits_wallets)
+ data.display_preferences = JSON.stringify(this.formDialog.data.display_preferences)
LNbits.api
.request(
'PUT',
@@ -703,3 +707,11 @@
})
{% endblock %}
+
+{% block styles %}
+
+{% endblock %}
\ No newline at end of file
From 99762dce946ac561e9b307f29cfbd05eba474dfe Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 12:38:18 +0100
Subject: [PATCH 021/844] Added gerty toggle alls
---
.../gerty/templates/gerty/index.html | 100 ++++++++++++++----
1 file changed, 79 insertions(+), 21 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 9308beac..b46fa812 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -31,7 +31,8 @@
-
+
{{ col.label }}
@@ -182,19 +183,26 @@
label="The Fun Stuff"
>
+ Toggle all
+
+
+
Displays random quotes from Satoshi
Show accurate facts about Pieter Wuille
@@ -203,6 +211,13 @@
icon="link"
label="Onchain Information"
>
+
+ Toggle all
+
+
+
+ Toggle all
+
+
@@ -245,6 +267,13 @@
icon="reorder"
label="Mining Data"
>
+
+ Toggle all
+
+
+
+ Toggle all
+
+
{% endblock %}
{% block styles %}
-
-{% endblock %}
\ No newline at end of file
+
+{% endblock %}
\ No newline at end of file
From 43f4228e196da23660cdc26a7a436514ab10f4d4 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 12:59:52 +0100
Subject: [PATCH 022/844] Bug fix on toggle all
---
.../extensions/gerty/templates/gerty/index.html | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index b46fa812..59df4c1e 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -380,6 +380,7 @@
mining: true,
lightning: true
},
+ oldToggleStates: {},
gertys: [],
currencyOptions: [
'USD',
@@ -749,15 +750,21 @@
},
watch: {
toggleStates: {
- handler(toggleStatesValue, oldValue) {
+ handler(toggleStatesValue) {
// Switch all the toggles in each section to the relevant state
for (const [toggleKey, toggleValue] of Object.entries(toggleStatesValue)) {
- for (const [dpKey, dpValue] of Object.entries(this.formDialog.data.display_preferences)) {
- if(dpKey.indexOf(toggleKey) === 0) {
- this.formDialog.data.display_preferences[dpKey] = toggleValue
+ if (this.oldToggleStates[toggleKey] !== toggleValue) {
+ for (const [dpKey, dpValue] of Object.entries(this.formDialog.data.display_preferences)) {
+ if (dpKey.indexOf(toggleKey) === 0) {
+ this.formDialog.data.display_preferences[dpKey] = toggleValue
+ }
}
}
}
+ // This is a weird hack we have to use to get VueJS to persist the previous toggle state between
+ // watches. VueJS passes the old and new values by reference so when comparing objects they
+ // will have the same values unless we do this
+ this.oldToggleStates = JSON.parse(JSON.stringify(toggleStatesValue))
},
deep: true
}
From c0edd15edb0437117b705b112db7b0ce0ba13fb6 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 15:00:21 +0100
Subject: [PATCH 023/844] new icon for mining
---
lnbits/extensions/gerty/templates/gerty/index.html | 2 +-
lnbits/extensions/gerty/views_api.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 59df4c1e..b211e57c 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -264,7 +264,7 @@
Date: Thu, 29 Sep 2022 15:08:01 +0100
Subject: [PATCH 024/844] Added refresh time to gerty settings
---
lnbits/extensions/gerty/migrations.py | 1 +
lnbits/extensions/gerty/models.py | 1 +
.../gerty/templates/gerty/index.html | 21 +++--
lnbits/extensions/gerty/views_api.py | 80 +++++++++----------
4 files changed, 58 insertions(+), 45 deletions(-)
diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py
index be4a1cc2..459fc880 100644
--- a/lnbits/extensions/gerty/migrations.py
+++ b/lnbits/extensions/gerty/migrations.py
@@ -6,6 +6,7 @@ async def m001_initial(db):
"""
CREATE TABLE gerty.gertys (
id TEXT PRIMARY KEY,
+ refresh_time INT,
name TEXT NOT NULL,
wallet TEXT NOT NULL,
lnbits_wallets TEXT,
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index 40185c92..fc7a3377 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -8,6 +8,7 @@ class Gerty(BaseModel):
id: str = Query(None)
name: str
wallet: str
+ refresh_time: int = Query(None)
lnbits_wallets: str = Query(None) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
mempool_endpoint: str = Query(None) # Mempool endpoint to use
exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index b211e57c..a59bf15d 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -164,6 +164,15 @@
Used for getting onchain/ln stats
+
+ The amount of time in seconds between screen updates
+
+
Use the toggles below to control what your Gerty will display
Date: Thu, 29 Sep 2022 16:06:45 +0100
Subject: [PATCH 025/844] Work on api
---
lnbits/extensions/gerty/views_api.py | 103 ++++++++++++++++++++-------
1 file changed, 78 insertions(+), 25 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 42329c94..4285216e 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -1,8 +1,10 @@
+import math
from http import HTTPStatus
import json
import httpx
import random
import os
+import time
from fastapi import Query
from fastapi.params import Depends
from lnurl import decode as decode_lnurl
@@ -26,7 +28,7 @@ from ...settings import LNBITS_PATH
@gerty_ext.get("/api/v1/gerty", status_code=HTTPStatus.OK)
async def api_gertys(
- all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
+ all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
wallet_ids = [wallet.wallet.id]
if all_wallets:
@@ -38,13 +40,12 @@ async def api_gertys(
@gerty_ext.post("/api/v1/gerty", status_code=HTTPStatus.CREATED)
@gerty_ext.put("/api/v1/gerty/{gerty_id}", status_code=HTTPStatus.OK)
async def api_link_create_or_update(
- data: Gerty,
- wallet: WalletTypeInfo = Depends(get_key_type),
- gerty_id: str = Query(None),
+ data: Gerty,
+ wallet: WalletTypeInfo = Depends(get_key_type),
+ gerty_id: str = Query(None),
):
if gerty_id:
gerty = await get_gerty(gerty_id)
- logger.debug(gerty)
if not gerty:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist"
@@ -63,9 +64,10 @@ async def api_link_create_or_update(
return {**gerty.dict()}
+
@gerty_ext.delete("/api/v1/gerty/{gerty_id}")
async def api_gerty_delete(
- gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
+ gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
):
gerty = await get_gerty(gerty_id)
@@ -84,44 +86,50 @@ async def api_gerty_delete(
#######################
with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
- satoshiQuotes = json.load(fd)
+ satoshiQuotes = json.load(fd)
+
@gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK)
async def api_gerty_satoshi():
return satoshiQuotes[random.randint(0, 100)]
-
+
+
@gerty_ext.get("/api/v1/gerty/{gerty_id}")
async def api_gerty_json(
- gerty_id: str
+ gerty_id: str,
+ p: int = None # page number
):
gerty = await get_gerty(gerty_id)
- logger.debug(gerty.wallet)
+
if not gerty:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
)
- gertyReturn = []
- # Get Wallet info
- wallets = []
- if gerty.lnbits_wallets != "":
- for lnbits_wallet in json.loads(gerty.lnbits_wallets):
- wallet = await get_wallet_for_key(key=lnbits_wallet)
- if wallet:
- wallets.append({
- "name": wallet.name,
- "balance": wallet.balance_msat,
- "inkey": wallet.inkey,
- })
+ display_preferences = json.loads(gerty.display_preferences)
- #Get Satoshi quotes
+ enabled_screen_count = 0
+
+ enabled_screens = []
+
+ for screen_slug in display_preferences:
+ is_screen_enabled = display_preferences[screen_slug]
+ if is_screen_enabled:
+ enabled_screen_count += 1
+ enabled_screens.append(screen_slug)
+
+ get_screen_text(p, enabled_screens)
+
+ next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1;
+
+ # Get Satoshi quotes
satoshi = []
# if gerty.sats_quote:
# quote = await api_gerty_satoshi()
# if quote:
# satoshi.append(await api_gerty_satoshi())
- #Get Exchange Value
+ # Get Exchange Value
exchange = []
# if gerty.exchange != "":
# try:
@@ -160,6 +168,51 @@ async def api_gerty_json(
# if r:
# ln.append(r.json())
- return {"name":gerty.name}
+ return {
+ "settings": {
+ "refreshTime": gerty.refresh_time,
+ "requestTimestamp": math.ceil(time.time()),
+ "nextScreenNumber": next_screen_number,
+ "name": gerty.name
+ },
+ "screen": {
+ "slug": "x",
+ "group": "x",
+ "text": [
+ {
+ "value": "Craig Steven Wright is a liar and\na fraud",
+ "size": 20,
+ "x": 20,
+ "y": 70
+ }
+ ],
+ }
+ }
+
+def get_screen_text(screen_num: int, display_preferences: dict):
+ # first get the relevant slug from the display_preferences
+ screen_slug = list(display_preferences)[screen_num]
+ # logger.debug('screen_slug')
+ # logger.debug(screen_slug)
+ if screen_slug == "lnbits_wallets_balance":
+
+ return screen_slug
+
+def get_lnbits_wallet_balances(gerty):
+ # Get Wallet info
+ wallets = []
+ if gerty.lnbits_wallets != "":
+ logger.debug("wallets")
+ logger.debug(gerty.lnbits_wallets)
+ for lnbits_wallet in json.loads(gerty.lnbits_wallets):
+ wallet = await get_wallet_for_key(key=lnbits_wallet)
+ if wallet:
+ wallets.append({
+ "name": wallet.name,
+ "balance": wallet.balance_msat,
+ "inkey": wallet.inkey,
+ })
+ logger.debug(lnbits_wallet)
+ return wallets
From 67803cfdd33f4b40009e52946f8e0510f45574d1 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 16:21:02 +0100
Subject: [PATCH 026/844] Got placeholder responses in place for api requests
---
lnbits/extensions/gerty/views_api.py | 86 +++++++++++++++++++++-------
1 file changed, 65 insertions(+), 21 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 4285216e..d2f471bb 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -118,7 +118,7 @@ async def api_gerty_json(
enabled_screen_count += 1
enabled_screens.append(screen_slug)
- get_screen_text(p, enabled_screens)
+ text = await get_screen_text(p, enabled_screens, gerty)
next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1;
@@ -176,43 +176,87 @@ async def api_gerty_json(
"name": gerty.name
},
"screen": {
- "slug": "x",
- "group": "x",
- "text": [
- {
- "value": "Craig Steven Wright is a liar and\na fraud",
- "size": 20,
- "x": 20,
- "y": 70
- }
- ],
+ "slug": get_screen_slug_by_index(p, enabled_screens),
+ "group": get_screen_slug_by_index(p, enabled_screens),
+ "text": text
}
}
-def get_screen_text(screen_num: int, display_preferences: dict):
+def get_screen_slug_by_index(index: int, screens_list):
+ return list(screens_list)[index]
+
+async def get_screen_text(screen_num: int, screens_list: dict, gerty):
+ screen_slug = get_screen_slug_by_index(screen_num, screens_list)
# first get the relevant slug from the display_preferences
- screen_slug = list(display_preferences)[screen_num]
- # logger.debug('screen_slug')
- # logger.debug(screen_slug)
+ logger.debug('screen_slug')
+ logger.debug(screen_slug)
+ # text = []
if screen_slug == "lnbits_wallets_balance":
+ text = await get_lnbits_wallet_balances(gerty)
+ elif screen_slug == "fun_satoshi_quotes":
+ text = await get_placeholder_text()
+ elif screen_slug == "fun_pieter_wuille_facts":
+ text = await get_placeholder_text()
+ elif screen_slug == "fun_exchange_market_rate":
+ text = await get_placeholder_text()
+ elif screen_slug == "onchain_difficulty_epoch_progress":
+ text = await get_placeholder_text()
+ elif screen_slug == "onchain_difficulty_retarget_date":
+ text = await get_placeholder_text()
+ elif screen_slug == "onchain_difficulty_blocks_remaining":
+ text = await get_placeholder_text()
+ elif screen_slug == "onchain_difficulty_epoch_time_remaining":
+ text = await get_placeholder_text()
+ elif screen_slug == "mempool_recommended_fees":
+ text = await get_placeholder_text()
+ elif screen_slug == "mempool_tx_count":
+ text = await get_placeholder_text()
+ elif screen_slug == "mining_current_hash_rate":
+ text = await get_placeholder_text()
+ elif screen_slug == "mining_current_difficulty":
+ text = await get_placeholder_text()
+ elif screen_slug == "lightning_channel_count":
+ text = await get_placeholder_text()
+ elif screen_slug == "lightning_node_count":
+ text = await get_placeholder_text()
+ elif screen_slug == "lightning_tor_node_count":
+ text = await get_placeholder_text()
+ elif screen_slug == "lightning_clearnet_nodes":
+ text = await get_placeholder_text()
+ elif screen_slug == "lightning_unannounced_nodes":
+ text = await get_placeholder_text()
+ elif screen_slug == "lightning_average_channel_capacity":
+ text = await get_placeholder_text()
+ return text
- return screen_slug
-
-def get_lnbits_wallet_balances(gerty):
+async def get_lnbits_wallet_balances(gerty):
# Get Wallet info
wallets = []
if gerty.lnbits_wallets != "":
- logger.debug("wallets")
- logger.debug(gerty.lnbits_wallets)
for lnbits_wallet in json.loads(gerty.lnbits_wallets):
wallet = await get_wallet_for_key(key=lnbits_wallet)
+ logger.debug(wallet)
if wallet:
wallets.append({
"name": wallet.name,
"balance": wallet.balance_msat,
"inkey": wallet.inkey,
})
- logger.debug(lnbits_wallet)
return wallets
+async def get_placeholder_text():
+ return [
+ {
+ "value": "Some placeholder text",
+ "size": 16,
+ "x": 10,
+ "y": 10,
+ },
+ {
+ "value": "Some placeholder text",
+ "size": 16,
+ "x": 10,
+ "y": 50,
+ }
+ ]
From 84b48eac604c20e34100da75205401b9cac3feb4 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 16:34:47 +0100
Subject: [PATCH 027/844] Added astoshi quotes api endpoint
---
lnbits/extensions/gerty/views_api.py | 51 ++++++++++++++++------------
1 file changed, 30 insertions(+), 21 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index d2f471bb..94f473dd 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -122,13 +122,6 @@ async def api_gerty_json(
next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1;
- # Get Satoshi quotes
- satoshi = []
- # if gerty.sats_quote:
- # quote = await api_gerty_satoshi()
- # if quote:
- # satoshi.append(await api_gerty_satoshi())
-
# Get Exchange Value
exchange = []
# if gerty.exchange != "":
@@ -180,12 +173,13 @@ async def api_gerty_json(
"group": get_screen_slug_by_index(p, enabled_screens),
"text": text
}
-
}
+# Get a screen slug by its position in the screens_list
def get_screen_slug_by_index(index: int, screens_list):
return list(screens_list)[index]
+# Get a list of text items for the screen number
async def get_screen_text(screen_num: int, screens_list: dict, gerty):
screen_slug = get_screen_slug_by_index(screen_num, screens_list)
# first get the relevant slug from the display_preferences
@@ -195,7 +189,7 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
if screen_slug == "lnbits_wallets_balance":
text = await get_lnbits_wallet_balances(gerty)
elif screen_slug == "fun_satoshi_quotes":
- text = await get_placeholder_text()
+ text = await get_satoshi_quotes()
elif screen_slug == "fun_pieter_wuille_facts":
text = await get_placeholder_text()
elif screen_slug == "fun_exchange_market_rate":
@@ -247,16 +241,31 @@ async def get_lnbits_wallet_balances(gerty):
async def get_placeholder_text():
return [
- {
- "value": "Some placeholder text",
- "size": 16,
- "x": 10,
- "y": 10,
- },
- {
- "value": "Some placeholder text",
- "size": 16,
- "x": 10,
- "y": 50,
- }
+ get_text_item_dict("Some placeholder text", 16, 10, 50),
+ get_text_item_dict("Some placeholder text", 16, 10, 50)
]
+
+async def get_satoshi_quotes():
+ # Get Satoshi quotes
+ text = []
+ quote = await api_gerty_satoshi()
+ if quote:
+ if quote['text']:
+ text.append(get_text_item_dict(quote['text'], 16))
+ if quote['date']:
+ text.append(get_text_item_dict(quote['date'], 12))
+
+ return text
+
+# A helper function get a nicely formated dict for the text
+def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None):
+ text = {
+ "value": text,
+ "size": font_size
+ }
+ if x_pos is None and y_pos is None:
+ text['position'] = 'center'
+ else:
+ text['x'] = x_pos
+ text['y'] = y_pos
+ return text
\ No newline at end of file
From b9f8a8b4b8790d0dc377fd64ffaf416206aa41ea Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 16:44:56 +0100
Subject: [PATCH 028/844] Added Pieter Wuille facts
---
.../gerty/static/pieter_wuille.json | 14 +++++++++++
lnbits/extensions/gerty/views_api.py | 23 +++++++++++++++----
2 files changed, 32 insertions(+), 5 deletions(-)
create mode 100644 lnbits/extensions/gerty/static/pieter_wuille.json
diff --git a/lnbits/extensions/gerty/static/pieter_wuille.json b/lnbits/extensions/gerty/static/pieter_wuille.json
new file mode 100644
index 00000000..986150ed
--- /dev/null
+++ b/lnbits/extensions/gerty/static/pieter_wuille.json
@@ -0,0 +1,14 @@
+{
+ "facts": [
+ "When a woman asked Pieter Wuille to talk dirty to her, he described the OpenSSL DER implementation.",
+ "Pieter Wuille recently visited an event horizon and escaped with a cryptographic proof.",
+ "Pieter Wuille's PhD thesis defence in full: \"Pieter Wuille, thank you\".",
+ "Pieter Wuille is an acronym for Programmatic Intelligent Encrypted Telemetric Encapsulated Recursive Witness Upscaling Integrated Load-Balancing Logical Entity.",
+ "Dan Bernstein only trusts one source of random numbers: Pieter Wuille.",
+ "Putting Pieter Wuille in the title of an r/Bitcoin submission gets more upvotes than the same post from Pieter Wuille himself.",
+ "Pieter Wuille won the underhanded crypto contest but his entry was so underhanded nobody even knows he entered.",
+ "Greg Maxwell is a bot created by Pieter Wuille to argue on reddit while he can get code done.",
+ "Pieter Wuille doesn't need the public key to calculate the corresponding private key.",
+ "When the Wikipedia servers corrupted all data including backups, Pieter Wuille had to stay awake all night to retype it."
+ ]
+}
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 94f473dd..c5e9df9d 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -85,15 +85,20 @@ async def api_gerty_delete(
#######################
-with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
- satoshiQuotes = json.load(fd)
-
-
@gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK)
async def api_gerty_satoshi():
+ with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
+ satoshiQuotes = json.load(fd)
return satoshiQuotes[random.randint(0, 100)]
+@gerty_ext.get("/api/v1/gerty/pieterwielliequote", status_code=HTTPStatus.OK)
+async def api_gerty_wuille():
+ with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/pieter_wuille.json')) as fd:
+ data = json.load(fd)
+ return data['facts'][random.randint(0, (len(data['facts']) - 1))]
+
+
@gerty_ext.get("/api/v1/gerty/{gerty_id}")
async def api_gerty_json(
gerty_id: str,
@@ -191,7 +196,7 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
elif screen_slug == "fun_satoshi_quotes":
text = await get_satoshi_quotes()
elif screen_slug == "fun_pieter_wuille_facts":
- text = await get_placeholder_text()
+ text = await get_pieter_wuille_fact()
elif screen_slug == "fun_exchange_market_rate":
text = await get_placeholder_text()
elif screen_slug == "onchain_difficulty_epoch_progress":
@@ -254,7 +259,15 @@ async def get_satoshi_quotes():
text.append(get_text_item_dict(quote['text'], 16))
if quote['date']:
text.append(get_text_item_dict(quote['date'], 12))
+ return text
+
+async def get_pieter_wuille_fact():
+ text = []
+ quote = await api_gerty_wuille()
+ if quote:
+ text.append(get_text_item_dict(quote, 16))
+ text.append(get_text_item_dict("Pieter Wuille facts", 12))
return text
# A helper function get a nicely formated dict for the text
From 33b8b94f22e113829db2be65fb742ace1997da78 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 16:54:37 +0100
Subject: [PATCH 029/844] Added exchange price to api
---
lnbits/extensions/gerty/views_api.py | 29 ++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index c5e9df9d..8d0da451 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -126,19 +126,6 @@ async def api_gerty_json(
text = await get_screen_text(p, enabled_screens, gerty)
next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1;
-
- # Get Exchange Value
- exchange = []
- # if gerty.exchange != "":
- # try:
- # amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
- # if amount:
- # exchange.append({
- # "fiat": gerty.exchange,
- # "amount": amount,
- # })
- # except:
- # pass
#
# onchain = []
# if gerty.onchain_stats and isinstance(gerty.mempool_endpoint, str):
@@ -198,7 +185,7 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
elif screen_slug == "fun_pieter_wuille_facts":
text = await get_pieter_wuille_fact()
elif screen_slug == "fun_exchange_market_rate":
- text = await get_placeholder_text()
+ text = await get_exchange_rate(gerty)
elif screen_slug == "onchain_difficulty_epoch_progress":
text = await get_placeholder_text()
elif screen_slug == "onchain_difficulty_retarget_date":
@@ -270,6 +257,20 @@ async def get_pieter_wuille_fact():
text.append(get_text_item_dict("Pieter Wuille facts", 12))
return text
+# Get Exchange Value
+async def get_exchange_rate(gerty):
+ text = []
+ if gerty.exchange != "":
+ try:
+ amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
+ if amount:
+ price = ('{0} {1}').format(("{:,}".format(math.ceil(amount))), gerty.exchange)
+ text.append(get_text_item_dict(price, 40))
+ text.append(get_text_item_dict("Current BTC price", 12))
+ except:
+ pass
+ return text
+
# A helper function get a nicely formated dict for the text
def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None):
text = {
From df700d80e4af4563480b6b103f88cfea3b84fd85 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 17:10:26 +0100
Subject: [PATCH 030/844] Added start of onchain stats function and getting
epocch progress stat
---
lnbits/extensions/gerty/views_api.py | 46 +++++++++++++++-------------
1 file changed, 24 insertions(+), 22 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 8d0da451..08dc5317 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -102,7 +102,7 @@ async def api_gerty_wuille():
@gerty_ext.get("/api/v1/gerty/{gerty_id}")
async def api_gerty_json(
gerty_id: str,
- p: int = None # page number
+ p: int = None # page number
):
gerty = await get_gerty(gerty_id)
@@ -126,25 +126,6 @@ async def api_gerty_json(
text = await get_screen_text(p, enabled_screens, gerty)
next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1;
- #
- # onchain = []
- # if gerty.onchain_stats and isinstance(gerty.mempool_endpoint, str):
- # async with httpx.AsyncClient() as client:
- # difficulty = []
- # r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
- # if r:
- # difficulty.append(r.json())
- # onchain.append({"difficulty":difficulty})
- # mempool = []
- # r = await client.get(gerty.mempool_endpoint + "/api/v1/fees/mempool-blocks")
- # if r:
- # mempool.append(r.json())
- # onchain.append({"mempool":mempool})
- # threed = []
- # r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/3d")
- # if r:
- # threed.append(r.json())
- # onchain.append({"threed":threed})
# ln = []
# if gerty.ln_stats and isinstance(gerty.mempool_endpoint, str):
@@ -167,10 +148,12 @@ async def api_gerty_json(
}
}
+
# Get a screen slug by its position in the screens_list
def get_screen_slug_by_index(index: int, screens_list):
return list(screens_list)[index]
+
# Get a list of text items for the screen number
async def get_screen_text(screen_num: int, screens_list: dict, gerty):
screen_slug = get_screen_slug_by_index(screen_num, screens_list)
@@ -187,7 +170,7 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
elif screen_slug == "fun_exchange_market_rate":
text = await get_exchange_rate(gerty)
elif screen_slug == "onchain_difficulty_epoch_progress":
- text = await get_placeholder_text()
+ text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_retarget_date":
text = await get_placeholder_text()
elif screen_slug == "onchain_difficulty_blocks_remaining":
@@ -216,6 +199,7 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
text = await get_placeholder_text()
return text
+
async def get_lnbits_wallet_balances(gerty):
# Get Wallet info
wallets = []
@@ -231,12 +215,14 @@ async def get_lnbits_wallet_balances(gerty):
})
return wallets
+
async def get_placeholder_text():
return [
get_text_item_dict("Some placeholder text", 16, 10, 50),
get_text_item_dict("Some placeholder text", 16, 10, 50)
]
+
async def get_satoshi_quotes():
# Get Satoshi quotes
text = []
@@ -257,6 +243,7 @@ async def get_pieter_wuille_fact():
text.append(get_text_item_dict("Pieter Wuille facts", 12))
return text
+
# Get Exchange Value
async def get_exchange_rate(gerty):
text = []
@@ -271,6 +258,7 @@ async def get_exchange_rate(gerty):
pass
return text
+
# A helper function get a nicely formated dict for the text
def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None):
text = {
@@ -282,4 +270,18 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
else:
text['x'] = x_pos
text['y'] = y_pos
- return text
\ No newline at end of file
+ return text
+
+
+async def get_onchain_stat(stat_slug: str, gerty):
+ text = []
+ if isinstance(gerty.mempool_endpoint, str):
+ async with httpx.AsyncClient() as client:
+ if stat_slug == "onchain_difficulty_epoch_progress":
+ # # or stat_slug == "onchain_difficulty_retarget_date" or stat_slug == "onchain_difficulty_blocks_remaining" or stat_slug == "onchain_difficulty_epoch_time_remaining"
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
+ if stat_slug == "onchain_difficulty_epoch_progress":
+ progressPercent = math.ceil(r.json()['progressPercent'])
+ text.append(get_text_item_dict("{0}%".format(progressPercent), 40))
+ text.append(get_text_item_dict("Progress through current difficulty epoch", 16))
+ return text
From c77a9d1747ee15ea5c46035ef8c3ceb65322c926 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 17:23:27 +0100
Subject: [PATCH 031/844] Add next difficulty adjustment data api output
---
lnbits/extensions/gerty/views_api.py | 26 +++++++++++++++++++++-----
1 file changed, 21 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 08dc5317..cf48491a 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -5,6 +5,7 @@ import httpx
import random
import os
import time
+from datetime import datetime
from fastapi import Query
from fastapi.params import Depends
from lnurl import decode as decode_lnurl
@@ -172,7 +173,7 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
elif screen_slug == "onchain_difficulty_epoch_progress":
text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_retarget_date":
- text = await get_placeholder_text()
+ text = await get_onchain_stat("onchain_difficulty_retarget_date", gerty)
elif screen_slug == "onchain_difficulty_blocks_remaining":
text = await get_placeholder_text()
elif screen_slug == "onchain_difficulty_epoch_time_remaining":
@@ -277,11 +278,26 @@ async def get_onchain_stat(stat_slug: str, gerty):
text = []
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
- if stat_slug == "onchain_difficulty_epoch_progress":
- # # or stat_slug == "onchain_difficulty_retarget_date" or stat_slug == "onchain_difficulty_blocks_remaining" or stat_slug == "onchain_difficulty_epoch_time_remaining"
+ if (
+ stat_slug == "onchain_difficulty_epoch_progress" or
+ stat_slug == "onchain_difficulty_retarget_date" or
+ stat_slug == "onchain_difficulty_blocks_remaining" or
+ stat_slug == "onchain_difficulty_epoch_time_remaining"
+ ):
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
if stat_slug == "onchain_difficulty_epoch_progress":
- progressPercent = math.ceil(r.json()['progressPercent'])
- text.append(get_text_item_dict("{0}%".format(progressPercent), 40))
+ stat = math.ceil(r.json()['progressPercent'])
+ text.append(get_text_item_dict("{0}%".format(stat), 40))
text.append(get_text_item_dict("Progress through current difficulty epoch", 16))
+ elif stat_slug == "onchain_difficulty_retarget_date":
+ stat = r.json()['estimatedRetargetDate']
+ dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
+ text.append(get_text_item_dict(dt, 40))
+ text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 16))
return text
+
+def get_date_suffix(dayNumber):
+ if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
+ return "th"
+ else:
+ return ["st", "nd", "rd"][dayNumber % 10 - 1]
\ No newline at end of file
From 90ed8c0e0ad4a4ea3e8cd7e30babfd829cef5126 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 17:27:14 +0100
Subject: [PATCH 032/844] Added Blocks remaining until next difficulty
adjustment
---
lnbits/extensions/gerty/views_api.py | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index cf48491a..a1890374 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -173,9 +173,9 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
elif screen_slug == "onchain_difficulty_epoch_progress":
text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_retarget_date":
- text = await get_onchain_stat("onchain_difficulty_retarget_date", gerty)
+ text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_blocks_remaining":
- text = await get_placeholder_text()
+ text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_epoch_time_remaining":
text = await get_placeholder_text()
elif screen_slug == "mempool_recommended_fees":
@@ -252,7 +252,7 @@ async def get_exchange_rate(gerty):
try:
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
if amount:
- price = ('{0} {1}').format(("{:,}".format(math.ceil(amount))), gerty.exchange)
+ price = ('{0} {1}').format(format_number(amount), gerty.exchange)
text.append(get_text_item_dict(price, 40))
text.append(get_text_item_dict("Current BTC price", 12))
except:
@@ -294,10 +294,18 @@ async def get_onchain_stat(stat_slug: str, gerty):
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
text.append(get_text_item_dict(dt, 40))
text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 16))
+ elif stat_slug == "onchain_difficulty_blocks_remaining":
+ stat = r.json()['remainingBlocks']
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
+ text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
return text
def get_date_suffix(dayNumber):
if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
return "th"
else:
- return ["st", "nd", "rd"][dayNumber % 10 - 1]
\ No newline at end of file
+ return ["st", "nd", "rd"][dayNumber % 10 - 1]
+
+# format a number for nice display output
+def format_number(number):
+ return ("{:,}".format(math.ceil(number)))
\ No newline at end of file
From 9973e9dbbb8cc995ee1349bc38d46696bd91ec96 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 17:33:44 +0100
Subject: [PATCH 033/844] Added time until next diff adjustment
---
lnbits/extensions/gerty/views_api.py | 34 ++++++++++++++++++++++++----
1 file changed, 30 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index a1890374..54e489bb 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -138,7 +138,7 @@ async def api_gerty_json(
return {
"settings": {
"refreshTime": gerty.refresh_time,
- "requestTimestamp": math.ceil(time.time()),
+ "requestTimestamp": round(time.time()),
"nextScreenNumber": next_screen_number,
"name": gerty.name
},
@@ -177,7 +177,7 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
elif screen_slug == "onchain_difficulty_blocks_remaining":
text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_epoch_time_remaining":
- text = await get_placeholder_text()
+ text = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "mempool_recommended_fees":
text = await get_placeholder_text()
elif screen_slug == "mempool_tx_count":
@@ -286,7 +286,7 @@ async def get_onchain_stat(stat_slug: str, gerty):
):
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
if stat_slug == "onchain_difficulty_epoch_progress":
- stat = math.ceil(r.json()['progressPercent'])
+ stat = round(r.json()['progressPercent'])
text.append(get_text_item_dict("{0}%".format(stat), 40))
text.append(get_text_item_dict("Progress through current difficulty epoch", 16))
elif stat_slug == "onchain_difficulty_retarget_date":
@@ -298,6 +298,10 @@ async def get_onchain_stat(stat_slug: str, gerty):
stat = r.json()['remainingBlocks']
text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
+ elif stat_slug == "onchain_difficulty_epoch_time_remaining":
+ stat = r.json()['remainingTime']
+ text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
+ text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
return text
def get_date_suffix(dayNumber):
@@ -308,4 +312,26 @@ def get_date_suffix(dayNumber):
# format a number for nice display output
def format_number(number):
- return ("{:,}".format(math.ceil(number)))
\ No newline at end of file
+ return ("{:,}".format(round(number)))
+
+
+def get_time_remaining(seconds, granularity=2):
+
+ intervals = (
+ ('weeks', 604800), # 60 * 60 * 24 * 7
+ ('days', 86400), # 60 * 60 * 24
+ ('hours', 3600), # 60 * 60
+ ('minutes', 60),
+ ('seconds', 1),
+ )
+
+ result = []
+
+ for name, count in intervals:
+ value = seconds // count
+ if value:
+ seconds -= value * count
+ if value == 1:
+ name = name.rstrip('s')
+ result.append("{} {}".format(round(value), name))
+ return ', '.join(result[:granularity])
\ No newline at end of file
From f8ccb97d607082a41c3fc26c8622374b7f182df5 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 17:38:51 +0100
Subject: [PATCH 034/844] Added tx in mempool stat
---
lnbits/extensions/gerty/views_api.py | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 54e489bb..8c3f9897 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -181,7 +181,7 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
elif screen_slug == "mempool_recommended_fees":
text = await get_placeholder_text()
elif screen_slug == "mempool_tx_count":
- text = await get_placeholder_text()
+ text = await get_mempool_stat(screen_slug, gerty)
elif screen_slug == "mining_current_hash_rate":
text = await get_placeholder_text()
elif screen_slug == "mining_current_difficulty":
@@ -304,6 +304,20 @@ async def get_onchain_stat(stat_slug: str, gerty):
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
return text
+async def get_mempool_stat(stat_slug: str, gerty):
+ text = []
+ if isinstance(gerty.mempool_endpoint, str):
+ async with httpx.AsyncClient() as client:
+ if (
+ stat_slug == "mempool_tx_count"
+ ):
+ r = await client.get(gerty.mempool_endpoint + "/api/mempool")
+ if stat_slug == "mempool_tx_count":
+ stat = round(r.json()['count'])
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
+ text.append(get_text_item_dict("Transactions in the mempool", 16))
+ return text
+
def get_date_suffix(dayNumber):
if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
return "th"
From a2b61558d3e1c20de5e9b242f6b1854c93a4f549 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 17:53:53 +0100
Subject: [PATCH 035/844] Change p from query var to route arg
---
lnbits/extensions/gerty/views_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 8c3f9897..9de21cac 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -100,7 +100,7 @@ async def api_gerty_wuille():
return data['facts'][random.randint(0, (len(data['facts']) - 1))]
-@gerty_ext.get("/api/v1/gerty/{gerty_id}")
+@gerty_ext.get("/api/v1/gerty/{gerty_id}/{p}")
async def api_gerty_json(
gerty_id: str,
p: int = None # page number
From f99eae6b5afb77ea3027faee49ac52d35a6091f1 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 18:02:52 +0100
Subject: [PATCH 036/844] font size to 60
---
lnbits/extensions/gerty/views_api.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 9de21cac..f4205247 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -253,7 +253,7 @@ async def get_exchange_rate(gerty):
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
if amount:
price = ('{0} {1}').format(format_number(amount), gerty.exchange)
- text.append(get_text_item_dict(price, 40))
+ text.append(get_text_item_dict(price, 60))
text.append(get_text_item_dict("Current BTC price", 12))
except:
pass
@@ -287,16 +287,16 @@ async def get_onchain_stat(stat_slug: str, gerty):
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
if stat_slug == "onchain_difficulty_epoch_progress":
stat = round(r.json()['progressPercent'])
- text.append(get_text_item_dict("{0}%".format(stat), 40))
+ text.append(get_text_item_dict("{0}%".format(stat), 60))
text.append(get_text_item_dict("Progress through current difficulty epoch", 16))
elif stat_slug == "onchain_difficulty_retarget_date":
stat = r.json()['estimatedRetargetDate']
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
- text.append(get_text_item_dict(dt, 40))
+ text.append(get_text_item_dict(dt, 60))
text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 16))
elif stat_slug == "onchain_difficulty_blocks_remaining":
stat = r.json()['remainingBlocks']
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
elif stat_slug == "onchain_difficulty_epoch_time_remaining":
stat = r.json()['remainingTime']
@@ -314,7 +314,7 @@ async def get_mempool_stat(stat_slug: str, gerty):
r = await client.get(gerty.mempool_endpoint + "/api/mempool")
if stat_slug == "mempool_tx_count":
stat = round(r.json()['count'])
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
text.append(get_text_item_dict("Transactions in the mempool", 16))
return text
From 5cddae5ba90cc4babdb02015776837faf42f8d08 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 19:25:02 +0100
Subject: [PATCH 037/844] Move title over stat
---
lnbits/extensions/gerty/views_api.py | 18 +++++++++++-------
1 file changed, 11 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index f4205247..82d476d3 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -204,8 +204,10 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
async def get_lnbits_wallet_balances(gerty):
# Get Wallet info
wallets = []
+ text = []
if gerty.lnbits_wallets != "":
for lnbits_wallet in json.loads(gerty.lnbits_wallets):
+
wallet = await get_wallet_for_key(key=lnbits_wallet)
logger.debug(wallet)
if wallet:
@@ -214,7 +216,9 @@ async def get_lnbits_wallet_balances(gerty):
"balance": wallet.balance_msat,
"inkey": wallet.inkey,
})
- return wallets
+ text.append(get_text_item_dict(wallet.name, 20))
+ text.append(get_text_item_dict(wallet.balance, 60))
+ return text
async def get_placeholder_text():
@@ -253,8 +257,8 @@ async def get_exchange_rate(gerty):
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
if amount:
price = ('{0} {1}').format(format_number(amount), gerty.exchange)
- text.append(get_text_item_dict(price, 60))
text.append(get_text_item_dict("Current BTC price", 12))
+ text.append(get_text_item_dict(price, 60))
except:
pass
return text
@@ -287,21 +291,21 @@ async def get_onchain_stat(stat_slug: str, gerty):
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
if stat_slug == "onchain_difficulty_epoch_progress":
stat = round(r.json()['progressPercent'])
- text.append(get_text_item_dict("{0}%".format(stat), 60))
text.append(get_text_item_dict("Progress through current difficulty epoch", 16))
+ text.append(get_text_item_dict("{0}%".format(stat), 60))
elif stat_slug == "onchain_difficulty_retarget_date":
stat = r.json()['estimatedRetargetDate']
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
- text.append(get_text_item_dict(dt, 60))
text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 16))
+ text.append(get_text_item_dict(dt, 60))
elif stat_slug == "onchain_difficulty_blocks_remaining":
stat = r.json()['remainingBlocks']
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
elif stat_slug == "onchain_difficulty_epoch_time_remaining":
stat = r.json()['remainingTime']
- text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
+ text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
return text
async def get_mempool_stat(stat_slug: str, gerty):
@@ -314,8 +318,8 @@ async def get_mempool_stat(stat_slug: str, gerty):
r = await client.get(gerty.mempool_endpoint + "/api/mempool")
if stat_slug == "mempool_tx_count":
stat = round(r.json()['count'])
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
text.append(get_text_item_dict("Transactions in the mempool", 16))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
return text
def get_date_suffix(dayNumber):
From 6f21c0c34b99fa2cb14752cba7d38a5b2e65610b Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 19:27:54 +0100
Subject: [PATCH 038/844] Font size tweaks
---
lnbits/extensions/gerty/views_api.py | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 82d476d3..fd5fdafe 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -217,7 +217,7 @@ async def get_lnbits_wallet_balances(gerty):
"inkey": wallet.inkey,
})
text.append(get_text_item_dict(wallet.name, 20))
- text.append(get_text_item_dict(wallet.balance, 60))
+ text.append(get_text_item_dict(wallet.balance, 40))
return text
@@ -236,7 +236,7 @@ async def get_satoshi_quotes():
if quote['text']:
text.append(get_text_item_dict(quote['text'], 16))
if quote['date']:
- text.append(get_text_item_dict(quote['date'], 12))
+ text.append(get_text_item_dict(quote['date'], 15))
return text
@@ -245,7 +245,7 @@ async def get_pieter_wuille_fact():
quote = await api_gerty_wuille()
if quote:
text.append(get_text_item_dict(quote, 16))
- text.append(get_text_item_dict("Pieter Wuille facts", 12))
+ text.append(get_text_item_dict("Pieter Wuille facts", 15))
return text
@@ -257,8 +257,8 @@ async def get_exchange_rate(gerty):
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
if amount:
price = ('{0} {1}').format(format_number(amount), gerty.exchange)
- text.append(get_text_item_dict("Current BTC price", 12))
- text.append(get_text_item_dict(price, 60))
+ text.append(get_text_item_dict("Current BTC price", 15))
+ text.append(get_text_item_dict(price, 40))
except:
pass
return text
@@ -292,16 +292,16 @@ async def get_onchain_stat(stat_slug: str, gerty):
if stat_slug == "onchain_difficulty_epoch_progress":
stat = round(r.json()['progressPercent'])
text.append(get_text_item_dict("Progress through current difficulty epoch", 16))
- text.append(get_text_item_dict("{0}%".format(stat), 60))
+ text.append(get_text_item_dict("{0}%".format(stat), 40))
elif stat_slug == "onchain_difficulty_retarget_date":
stat = r.json()['estimatedRetargetDate']
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 16))
- text.append(get_text_item_dict(dt, 60))
+ text.append(get_text_item_dict(dt, 40))
elif stat_slug == "onchain_difficulty_blocks_remaining":
stat = r.json()['remainingBlocks']
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
elif stat_slug == "onchain_difficulty_epoch_time_remaining":
stat = r.json()['remainingTime']
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
@@ -319,7 +319,7 @@ async def get_mempool_stat(stat_slug: str, gerty):
if stat_slug == "mempool_tx_count":
stat = round(r.json()['count'])
text.append(get_text_item_dict("Transactions in the mempool", 16))
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
return text
def get_date_suffix(dayNumber):
From c73bfcb1ea7be215293528ac09e823c20c22e053 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 19:28:50 +0100
Subject: [PATCH 039/844] Font size tweaks
---
lnbits/extensions/gerty/views_api.py | 18 +++++++++---------
1 file changed, 9 insertions(+), 9 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index fd5fdafe..5f1d436a 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -223,8 +223,8 @@ async def get_lnbits_wallet_balances(gerty):
async def get_placeholder_text():
return [
- get_text_item_dict("Some placeholder text", 16, 10, 50),
- get_text_item_dict("Some placeholder text", 16, 10, 50)
+ get_text_item_dict("Some placeholder text", 15, 10, 50),
+ get_text_item_dict("Some placeholder text", 15, 10, 50)
]
@@ -234,7 +234,7 @@ async def get_satoshi_quotes():
quote = await api_gerty_satoshi()
if quote:
if quote['text']:
- text.append(get_text_item_dict(quote['text'], 16))
+ text.append(get_text_item_dict(quote['text'], 15))
if quote['date']:
text.append(get_text_item_dict(quote['date'], 15))
return text
@@ -244,7 +244,7 @@ async def get_pieter_wuille_fact():
text = []
quote = await api_gerty_wuille()
if quote:
- text.append(get_text_item_dict(quote, 16))
+ text.append(get_text_item_dict(quote, 15))
text.append(get_text_item_dict("Pieter Wuille facts", 15))
return text
@@ -291,20 +291,20 @@ async def get_onchain_stat(stat_slug: str, gerty):
r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
if stat_slug == "onchain_difficulty_epoch_progress":
stat = round(r.json()['progressPercent'])
- text.append(get_text_item_dict("Progress through current difficulty epoch", 16))
+ text.append(get_text_item_dict("Progress through current difficulty epoch", 15))
text.append(get_text_item_dict("{0}%".format(stat), 40))
elif stat_slug == "onchain_difficulty_retarget_date":
stat = r.json()['estimatedRetargetDate']
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
- text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 16))
+ text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 15))
text.append(get_text_item_dict(dt, 40))
elif stat_slug == "onchain_difficulty_blocks_remaining":
stat = r.json()['remainingBlocks']
- text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
+ text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
elif stat_slug == "onchain_difficulty_epoch_time_remaining":
stat = r.json()['remainingTime']
- text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 16))
+ text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
return text
@@ -318,7 +318,7 @@ async def get_mempool_stat(stat_slug: str, gerty):
r = await client.get(gerty.mempool_endpoint + "/api/mempool")
if stat_slug == "mempool_tx_count":
stat = round(r.json()['count'])
- text.append(get_text_item_dict("Transactions in the mempool", 16))
+ text.append(get_text_item_dict("Transactions in the mempool", 15))
text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
return text
From e38c0d2ff86b265283773ea314b5c29721115209 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 21:37:12 +0100
Subject: [PATCH 040/844] Font size to 80
---
lnbits/extensions/gerty/views_api.py | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 5f1d436a..cb6b0cfa 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -140,6 +140,7 @@ async def api_gerty_json(
"refreshTime": gerty.refresh_time,
"requestTimestamp": round(time.time()),
"nextScreenNumber": next_screen_number,
+ "showTextBoundRect": true,
"name": gerty.name
},
"screen": {
@@ -258,7 +259,7 @@ async def get_exchange_rate(gerty):
if amount:
price = ('{0} {1}').format(format_number(amount), gerty.exchange)
text.append(get_text_item_dict("Current BTC price", 15))
- text.append(get_text_item_dict(price, 40))
+ text.append(get_text_item_dict(price, 80))
except:
pass
return text
@@ -292,7 +293,7 @@ async def get_onchain_stat(stat_slug: str, gerty):
if stat_slug == "onchain_difficulty_epoch_progress":
stat = round(r.json()['progressPercent'])
text.append(get_text_item_dict("Progress through current difficulty epoch", 15))
- text.append(get_text_item_dict("{0}%".format(stat), 40))
+ text.append(get_text_item_dict("{0}%".format(stat), 80))
elif stat_slug == "onchain_difficulty_retarget_date":
stat = r.json()['estimatedRetargetDate']
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
@@ -301,7 +302,7 @@ async def get_onchain_stat(stat_slug: str, gerty):
elif stat_slug == "onchain_difficulty_blocks_remaining":
stat = r.json()['remainingBlocks']
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 80))
elif stat_slug == "onchain_difficulty_epoch_time_remaining":
stat = r.json()['remainingTime']
text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
@@ -319,7 +320,7 @@ async def get_mempool_stat(stat_slug: str, gerty):
if stat_slug == "mempool_tx_count":
stat = round(r.json()['count'])
text.append(get_text_item_dict("Transactions in the mempool", 15))
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 40))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 80))
return text
def get_date_suffix(dayNumber):
From 2135fd8656902bf48159f4685162e22569df3721 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 21:38:16 +0100
Subject: [PATCH 041/844] bug fix
---
lnbits/extensions/gerty/views_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index cb6b0cfa..b5852ee6 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -140,7 +140,7 @@ async def api_gerty_json(
"refreshTime": gerty.refresh_time,
"requestTimestamp": round(time.time()),
"nextScreenNumber": next_screen_number,
- "showTextBoundRect": true,
+ "showTextBoundRect": True,
"name": gerty.name
},
"screen": {
From ff198d4728814c12c4b3b7c49bc66a4a32d5f286 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 29 Sep 2022 22:01:28 +0100
Subject: [PATCH 042/844] Disable debug
---
lnbits/extensions/gerty/views_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index b5852ee6..da903f68 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -140,7 +140,7 @@ async def api_gerty_json(
"refreshTime": gerty.refresh_time,
"requestTimestamp": round(time.time()),
"nextScreenNumber": next_screen_number,
- "showTextBoundRect": True,
+ "showTextBoundRect": False,
"name": gerty.name
},
"screen": {
From 659f95ccd316921804619975e7da40ea87c3a00e Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Fri, 30 Sep 2022 09:00:48 +0100
Subject: [PATCH 043/844] enable debug
---
lnbits/extensions/gerty/views_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index da903f68..b5852ee6 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -140,7 +140,7 @@ async def api_gerty_json(
"refreshTime": gerty.refresh_time,
"requestTimestamp": round(time.time()),
"nextScreenNumber": next_screen_number,
- "showTextBoundRect": False,
+ "showTextBoundRect": True,
"name": gerty.name
},
"screen": {
From 61201a65732e2f653c409c0b9c1202f3e748e3e5 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Fri, 30 Sep 2022 09:17:20 +0100
Subject: [PATCH 044/844] Disable debug
---
lnbits/extensions/gerty/models.py | 1 +
lnbits/extensions/gerty/views_api.py | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index fc7a3377..5cbb08f4 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -9,6 +9,7 @@ class Gerty(BaseModel):
name: str
wallet: str
refresh_time: int = Query(None)
+ debug_enabled: int = Query(None)
lnbits_wallets: str = Query(None) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
mempool_endpoint: str = Query(None) # Mempool endpoint to use
exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index b5852ee6..da903f68 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -140,7 +140,7 @@ async def api_gerty_json(
"refreshTime": gerty.refresh_time,
"requestTimestamp": round(time.time()),
"nextScreenNumber": next_screen_number,
- "showTextBoundRect": True,
+ "showTextBoundRect": False,
"name": gerty.name
},
"screen": {
From d2b4d6c837440990ed395512a73734a131802692 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Fri, 30 Sep 2022 09:30:08 +0100
Subject: [PATCH 045/844] bug squash
---
lnbits/extensions/gerty/models.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index 5cbb08f4..fc7a3377 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -9,7 +9,6 @@ class Gerty(BaseModel):
name: str
wallet: str
refresh_time: int = Query(None)
- debug_enabled: int = Query(None)
lnbits_wallets: str = Query(None) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
mempool_endpoint: str = Query(None) # Mempool endpoint to use
exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
From 454ae1bf95689cdd3cd8b306b53648bd07b4afbe Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Fri, 30 Sep 2022 09:37:28 +0100
Subject: [PATCH 046/844] Split text for gerty
---
lnbits/extensions/gerty/views_api.py | 25 ++++++++++++++++++++++++-
1 file changed, 24 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index da903f68..1f4cce6e 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -1,6 +1,7 @@
import math
from http import HTTPStatus
import json
+import textwrap
import httpx
import random
import os
@@ -267,8 +268,30 @@ async def get_exchange_rate(gerty):
# A helper function get a nicely formated dict for the text
def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None):
+ # Get line size by font size
+ line_width = 60
+ if font_size <= 12:
+ line_width = 80
+ elif font_size <= 15:
+ line_width = 60
+ elif font_size <= 20:
+ line_width = 40
+ elif font_size <= 40:
+ line_width = 30
+ else:
+ line_width = 20
+
+ # wrap the text
+ wrapper = textwrap.TextWrapper(width=line_width)
+ word_list = wrapper.wrap(text=text)
+
+ multilineText = '\n'.join(word_list)
+
+ # logger.debug('multilineText')
+ # logger.debug(multilineText)
+
text = {
- "value": text,
+ "value": multilineText,
"size": font_size
}
if x_pos is None and y_pos is None:
From a1fbd1056157542c81e4f5982f5a047b7b4377a8 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Fri, 30 Sep 2022 09:48:04 +0100
Subject: [PATCH 047/844] Drop satoshit quote font size
---
lnbits/extensions/gerty/views_api.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 1f4cce6e..19930484 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -236,9 +236,9 @@ async def get_satoshi_quotes():
quote = await api_gerty_satoshi()
if quote:
if quote['text']:
- text.append(get_text_item_dict(quote['text'], 15))
+ text.append(get_text_item_dict(quote['text'], 12))
if quote['date']:
- text.append(get_text_item_dict(quote['date'], 15))
+ text.append(get_text_item_dict("Satoshi Nakamoto - {0}".format(quote['date']), 15))
return text
From ebc0e60cc9a0b691ff499e5e25ab6c09fd066a75 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sat, 1 Oct 2022 16:21:46 +0100
Subject: [PATCH 048/844] Added Wuille facts and tweaked line lengths
---
lnbits/extensions/gerty/static/pieter_wuille.json | 14 ++++++++++++--
lnbits/extensions/gerty/views_api.py | 4 ++--
2 files changed, 14 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/gerty/static/pieter_wuille.json b/lnbits/extensions/gerty/static/pieter_wuille.json
index 986150ed..9dec9f67 100644
--- a/lnbits/extensions/gerty/static/pieter_wuille.json
+++ b/lnbits/extensions/gerty/static/pieter_wuille.json
@@ -7,8 +7,18 @@
"Dan Bernstein only trusts one source of random numbers: Pieter Wuille.",
"Putting Pieter Wuille in the title of an r/Bitcoin submission gets more upvotes than the same post from Pieter Wuille himself.",
"Pieter Wuille won the underhanded crypto contest but his entry was so underhanded nobody even knows he entered.",
- "Greg Maxwell is a bot created by Pieter Wuille to argue on reddit while he can get code done.",
+ "Greg Maxwell is a bot created by Pieter Wuille to argue on reddit so he can get code done.",
"Pieter Wuille doesn't need the public key to calculate the corresponding private key.",
- "When the Wikipedia servers corrupted all data including backups, Pieter Wuille had to stay awake all night to retype it."
+ "When the Wikipedia servers corrupted all data including backups, Pieter Wuille had to stay awake all night to retype it.",
+ "It is a Bitcoin consensus rule that when Pieter's hard drive is full no more blocks can be added.",
+ "When they go out, Pieter Wuille pays for his parents.",
+ "Pieter Wuille replaced the existing monetary system by writing a few thousand lines of code.",
+ "Putting Pieter Wuille in the title of an r/Bitcoin submission gets more upvotes than the same post from Pieter Wuille himself.",
+ "Only Pieter Wuille can name things harder to pronounce than Pieter Wuille.",
+ "Pieter Wuille doesn't write code, he wills it into existence.",
+ "If every copy of the blockchain were deleted Pieter Wuille would recreate it from memory.",
+ "If all else fails, bitcoin should be restarted by syncing the code and the blockchain directly from Wuille's mind.",
+ "Pieter Wuille codes // Enlightened Zen master floats // Haikus trickle down.",
+ "Pieter Wuille once wrote a constant time generator for generating constant time cryptographic code."
]
}
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 19930484..952142db 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -271,9 +271,9 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
# Get line size by font size
line_width = 60
if font_size <= 12:
- line_width = 80
+ line_width = 75
elif font_size <= 15:
- line_width = 60
+ line_width = 58
elif font_size <= 20:
line_width = 40
elif font_size <= 40:
From 168941a0ad315e79763155a4a449bf9632002fa9 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sat, 1 Oct 2022 16:25:46 +0100
Subject: [PATCH 049/844] Removed really long satoshi quotes
---
lnbits/extensions/gerty/static/satoshi.json | 839 -------------
.../extensions/gerty/static/satoshi_long.json | 1099 +++++++++++++++++
2 files changed, 1099 insertions(+), 839 deletions(-)
create mode 100644 lnbits/extensions/gerty/static/satoshi_long.json
diff --git a/lnbits/extensions/gerty/static/satoshi.json b/lnbits/extensions/gerty/static/satoshi.json
index 1cff822a..b2869737 100644
--- a/lnbits/extensions/gerty/static/satoshi.json
+++ b/lnbits/extensions/gerty/static/satoshi.json
@@ -6,76 +6,6 @@
"post_id": "542",
"date": "December 11, 2010"
},
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "The project needs to grow gradually so the software can be strengthened along the way. I make this appeal to WikiLeaks not to try to use Bitcoin. Bitcoin is a small beta community in its infancy.",
- "post_id": "523",
- "date": "December 5, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "I'm happy if someone with artistic skill wants to contribute alternatives. The icon/logo was meant to be good as an icon at the 16x16 and 20x20 pixel sizes. I think it's the best program icon, but there's room for improvement at larger sizes for a graphic for use on websites. It'll be a lot simpler if authors could make their graphics public domain.",
- "post_id": "500",
- "date": "November 13, 2010"
- },
- {
- "category": "general",
- "medium": "bitcointalk",
- "text": "I wish rather than deleting the article, they put a length restriction. If something is not famous enough, there could at least be a stub article identifying what it is. I often come across annoying red links of things that Wiki ought to at least have heard of. \nThe article could be as simple as something like: \"Bitcoin is a peer-to-peer decentralised /link/electronic currency/link/.\" \nThe more standard Wiki thing to do is that we should have a paragraph in one of the more general categories that we are an instance of, like Electronic Currency or Electronic Cash. We can probably establish a paragraph there. Again, keep it short. Just identifying what it is.",
- "post_id": "467",
- "date": "September 30, 2010"
- },
- {
- "category": "transactions",
- "medium": "bitcointalk",
- "text": "As you figured out, the root problem is we shouldn't be counting or spending transactions until they have at least 1 confirmation. 0/unconfirmed transactions are very much second class citizens. At most, they are advice that something has been received, but counting them as balance or spending them is premature.",
- "post_id": "464",
- "date": "September 30, 2010"
- },
- {
- "category": "general",
- "medium": "bitcointalk",
- "text": "Bitcoin would be convenient for people who don't have a credit card or don't want to use the cards they have, either don't want the spouse to see it on the bill or don't trust giving their number to \"porn guys\", or afraid of recurring billing.",
- "post_id": "460",
- "date": "September 23, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "I don't know anything about any of the bug trackers. If we were to have one, we would have to make a thoroughly researched choice. We're managing pretty well just using the forum. I'm more likely to see bugs posted in the forum, and I think other users are much more likely to help resolve and ask follow up questions here than if they were in a bug tracker. A key step is other users helping resolve the simple stuff that's not really a bug but some misunderstanding or confusion. I keep a list of all unresolved bugs I've seen on the forum. In some cases, I'm still thinking about the best design for the fix. This isn't the kind of software where we can leave so many unresolved bugs that we need a tracker for them.",
- "post_id": "454",
- "date": "September 19, 2010"
- },
- {
- "category": "scalability",
- "medium": "bitcointalk",
- "text": "The threshold can easily be changed in the future. We can decide to increase it when the time comes. It's a good idea to keep it lower as a circuit breaker and increase it as needed. If we hit the threshold now, it would almost certainly be some kind of flood and not actual use. Keeping the threshold lower would help limit the amount of wasted disk space in that event.",
- "post_id": "441",
- "date": "September 8, 2010"
- },
- {
- "category": "fees",
- "medium": "bitcointalk",
- "text": "Currently, paying a fee is controlled manually with the -paytxfee switch. It would be very easy to make the software automatically check the size of recent blocks to see if it should pay a fee. We're so far from reaching the threshold, we don't need that yet. It's a good idea to see how things go with controlling it manually first anyway.",
- "post_id": "441",
- "date": "September 8, 2010"
- },
- {
- "category": "fees, nodes",
- "medium": "bitcointalk",
- "text": "Another option is to reduce the number of free transactions allowed per block before transaction fees are required. Nodes only take so many KB of free transactions per block before they start requiring at least 0.01 transaction fee. The threshold should probably be lower than it currently is. I don't think the threshold should ever be 0. We should always allow at least some free transactions.",
- "post_id": "439",
- "date": "September 7, 2010"
- },
- {
- "category": "economics",
- "medium": "bitcointalk",
- "text": "As a thought experiment, imagine there was a base metal as scarce as gold but with the following properties:\n- boring grey in colour\n- not a good conductor of electricity\n- not particularly strong, but not ductile or easily malleable either\n- not useful for any practical or ornamental purpose\n\nand one special, magical property:\n- can be transported over a communications channel\n\nIf it somehow acquired any value at all for whatever reason, then anyone wanting to transfer wealth over a long distance could buy some, transmit it, and have the recipient sell it.\n\nMaybe it could get an initial value circularly as you've suggested, by people foreseeing its potential usefulness for exchange. (I would definitely want some) Maybe collectors, any random reason could spark it.\n\nI think the traditional qualifications for money were written with the assumption that there are so many competing objects in the world that are scarce, an object with the automatic bootstrap of intrinsic value will surely win out over those without intrinsic value. But if there were nothing in the world with intrinsic value that could be used as money, only scarce but no intrinsic value, I think people would still take up something.\n\n(I'm using the word scarce here to only mean limited potential supply)",
- "post_id": "428",
- "date": "August 27, 2010"
- },
{
"category": "bitcoin-economics",
"medium": "bitcointalk",
@@ -83,104 +13,6 @@
"post_id": "427",
"date": "August 27, 2010"
},
- {
- "category": "proof-of-work",
- "medium": "bitcointalk",
- "text": "There is no way for the software to automatically know if one chain is better than another except by the greatest proof-of-work. In the design it was necessary for it to switch to a longer chain no matter how far back it has to go.",
- "post_id": "394",
- "date": "August 16, 2010"
- },
- {
- "category": "mining",
- "medium": "bitcointalk",
- "text": "Some places where generation will gravitate to: \n1) places where it's cheapest or free\n2) people who want to help for idealogical reasons\n3) people who want to get some coins without the inconvenience of doing a transaction to buy them\n\nThere are legitimate places where it's free. Generation is basically free anywhere that has electric heat, since your computer's heat is offsetting your baseboard electric heating. Many small flats have electric heat out of convenience.",
- "post_id": "364",
- "date": "August 15, 2010"
- },
- {
- "category": "general",
- "medium": "bitcointalk",
- "text": "Then you must also be against the common system of payment up front, where the customer loses.\nPayment up front: customer loses, and the thief gets the money.\nSimple escrow: customer loses, but the thief doesn't get the money either.\nAre you guys saying payment up front is better, because at least the thief gets the money, so at least someone gets it?\nImagine someone stole something from you. You can't get it back, but if you could, if it had a kill switch that could be remote triggered, would you do it? Would it be a good thing for thieves to know that everything you own has a kill switch and if they steal it, it'll be useless to them, although you still lose it too? If they give it back, you can re-activate it.\nImagine if gold turned to lead when stolen. If the thief gives it back, it turns to gold again.\nIt still seems to me the problem may be one of presenting it the right way. For one thing, not being so blunt about \"money burning\" for the purposes of game theory discussion. The money is never truly burned. You have the option to release it at any time forever.",
- "post_id": "340",
- "date": "August 11, 2010"
- },
- {
- "category": "mining",
- "medium": "bitcointalk",
- "text": "The heat from your computer is not wasted if you need to heat your home. If you're using electric heat where you live, then your computer's heat isn't a waste. It's equal cost if you generate the heat with your computer. \nIf you have other cheaper heating than electric, then the waste is only the difference in cost.\nIf it's summer and you're using A/C, then it's twice. \nBitcoin generation should end up where it's cheapest. Maybe that will be in cold climates where there's electric heat, where it would be essentially free.",
- "post_id": "337",
- "date": "August 9, 2010"
- },
- {
- "category": "bitcoin-economics",
- "medium": "bitcointalk",
- "text": "It's the same situation as gold and gold mining. The marginal cost of gold mining tends to stay near the price of gold. Gold mining is a waste, but that waste is far less than the utility of having gold available as a medium of exchange. \nI think the case will be the same for Bitcoin. The utility of the exchanges made possible by Bitcoin will far exceed the cost of electricity used. Therefore, not having Bitcoin would be the net waste.",
- "post_id": "327",
- "date": "August 7, 2010"
- },
- {
- "category": "proof-of-work",
- "medium": "bitcointalk",
- "text": "Proof-of-work has the nice property that it can be relayed through untrusted middlemen. We don't have to worry about a chain of custody of communication. It doesn't matter who tells you a longest chain, the proof-of-work speaks for itself.",
- "post_id": "327",
- "date": "August 7, 2010"
- },
- {
- "category": "micropayments",
- "medium": "bitcointalk",
- "text": "Forgot to add the good part about micropayments. While I don't think Bitcoin is practical for smaller micropayments right now, it will eventually be as storage and bandwidth costs continue to fall. If Bitcoin catches on on a big scale, it may already be the case by that time. Another way they can become more practical is if I implement client-only mode and the number of network nodes consolidates into a smaller number of professional server farms. Whatever size micropayments you need will eventually be practical. I think in 5 or 10 years, the bandwidth and storage will seem trivial.",
- "post_id": "318",
- "date": "August 5, 2010"
- },
- {
- "category": "micropayments",
- "medium": "bitcointalk",
- "text": "Bitcoin isn't currently practical for very small micropayments. Not for things like pay per search or per page view without an aggregating mechanism, not things needing to pay less than 0.01. The dust spam limit is a first try at intentionally trying to prevent overly small micropayments like that. \nBitcoin is practical for smaller transactions than are practical with existing payment methods. Small enough to include what you might call the top of the micropayment range. But it doesn't claim to be practical for arbitrarily small micropayments.",
- "post_id": "317",
- "date": "August 4, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "Actually, it works well to just PM me. I'm the one who's going to be fixing it. If you find a security flaw, I would definitely like to hear from you privately to fix it before it goes public.",
- "post_id": "294",
- "date": "July 29, 2010"
- },
- {
- "category": "nodes",
- "medium": "bitcointalk",
- "text": "The current system where every user is a network node is not the intended configuration for large scale. That would be like every Usenet user runs their own NNTP server. The design supports letting users just be users. The more burden it is to run a node, the fewer nodes there will be. Those few nodes will be big server farms. The rest will be client nodes that only do transactions and don't generate.",
- "post_id": "287",
- "date": "July 29, 2010"
- },
- {
- "category": "general",
- "medium": "bitcointalk",
- "text": "For future reference, here's my public key. It's the same one that's been there since the bitcoin.org site first went up in 2008. Grab it now in case you need it later. http://www.bitcoin.org/Satoshi_Nakamoto.asc",
- "post_id": "276",
- "date": "July 25, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "By making some adjustments to the database settings, I was able to make the initial block download about 5 times faster. It downloads in about 30 minutes. \n \nThe database default had it writing each block to disk synchronously, which is not necessary. I changed the settings to let it cache the changes in memory and write them out in a batch. Blocks are still written transactionally, so either the complete change occurs or none of it does, in either case the data is left in a valid state. \n \nI only enabled this change during the initial block download. When you come within 2000 blocks of the latest block, these changes turn off and it slows down to the old way.",
- "post_id": "258",
- "date": "July 23, 2010"
- },
- {
- "category": "general",
- "medium": "bitcointalk",
- "text": "The timing is strange, just as we are getting a rapid increase in 3rd party coverage after getting slashdotted. I hope there's not a big hurry to wrap the discussion and decide. How long does Wikipedia typically leave a question like that open for comment? \nIt would help to condense the article and make it less promotional sounding as soon as possible. Just letting people know what it is, where it fits into the electronic money space, not trying to convince them that it's good. They probably want something that just generally identifies what it is, not tries to explain all about how it works.",
- "post_id": "249",
- "date": "July 10, 2010"
- },
- {
- "category": "difficulty",
- "medium": "bitcointalk",
- "text": "Right, the difficulty adjustment is trying to keep it so the network as a whole generates an average of 6 blocks per hour. The time for your block to mature will always be around 20 hours.",
- "post_id": "225",
- "date": "July 16, 2010"
- },
{
"category": "difficulty",
"medium": "bitcointalk",
@@ -188,20 +20,6 @@
"post_id": "223",
"date": "July 16, 2010"
},
- {
- "category": "scalability, nodes",
- "medium": "bitcointalk",
- "text": "The design outlines a lightweight client that does not need the full block chain. In the design PDF it's called Simplified Payment Verification. The lightweight client can send and receive transactions, it just can't generate blocks. It does not need to trust a node to verify payments, it can still verify them itself. \nThe lightweight client is not implemented yet, but the plan is to implement it when it's needed. For now, everyone just runs a full network node.",
- "post_id": "188",
- "date": "July 14, 2010"
- },
- {
- "category": "scalability, nodes",
- "medium": "bitcointalk",
- "text": "I anticipate there will never be more than 100K nodes, probably less. It will reach an equilibrium where it's not worth it for more nodes to join in. The rest will be lightweight clients, which could be millions.",
- "post_id": "188",
- "date": "July 14, 2010"
- },
{
"category": "nodes",
"medium": "bitcointalk",
@@ -209,20 +27,6 @@
"post_id": "188",
"date": "July 14, 2010"
},
- {
- "category": "economics",
- "medium": "bitcointalk",
- "text": "When someone tries to buy all the world's supply of a scarce asset, the more they buy the higher the price goes. At some point, it gets too expensive for them to buy any more. It's great for the people who owned it beforehand because they get to sell it to the corner at crazy high prices. As the price keeps going up and up, some people keep holding out for yet higher prices and refuse to sell.",
- "post_id": "174",
- "date": "July 9, 2010"
- },
- {
- "category": "releases",
- "medium": "bitcointalk",
- "text": "Announcing version 0.3 of Bitcoin, the P2P cryptocurrency! Bitcoin is a digital currency using cryptography and a distributed network to replace the need for a trusted central server. Escape the arbitrary inflation risk of centrally managed currencies! Bitcoin's total circulation is limited to 21 million coins. The coins are gradually released to the network's nodes based on the CPU proof-of-worker they contribute, so you can get a share of them by contributing your idle CPU time.",
- "post_id": "168",
- "date": "July 6, 2010"
- },
{
"category": "general",
"medium": "bitcointalk",
@@ -237,34 +41,6 @@
"post_id": "131",
"date": "June 21, 2010"
},
- {
- "category": "general",
- "medium": "bitcointalk",
- "text": "Excellent choice of a first project, nice work. I had planned to do this exact thing if someone else didn't do it, so when it gets too hard for mortals to generate 50BTC, new users could get some coins to play with right away. Donations should be able to keep it filled. The display showing the balance in the dispenser encourages people to top it up.\n\nYou should put a donation bitcoin address on the page for those who want to add funds to it, which ideally should update to a new address whenever it receives something.",
- "post_id": "129",
- "date": "June 18, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "Since 2007. At some point I became convinced there was a way to do this without any trust required at all and couldn't resist to keep thinking about it. Much more of the work was designing than coding.\n\nFortunately, so far all the issues raised have been things I previously considered and planned for.",
- "post_id": "127",
- "date": "June 18, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "The nature of Bitcoin is such that once version 0.1 was released, the core design was set in stone for the rest of its lifetime. Because of that, I wanted to design it to support every possible transaction type I could think of. The problem was, each thing required special support code and data fields whether it was used or not, and only covered one special case at a time. It would have been an explosion of special cases. The solution was script, which generalizes the problem so transacting parties can describe their transaction as a predicate that the node network evaluates. The nodes only need to understand the transaction to the extent of evaluating whether the sender's conditions are met.",
- "post_id": "126",
- "date": "June 17, 2010"
- },
- {
- "category": "transactions, bitcoin-design",
- "medium": "bitcointalk",
- "text": "The design supports a tremendous variety of possible transaction types that I designed years ago. Escrow transactions, bonded contracts, third party arbitration, multi-party signature, etc. If Bitcoin catches on in a big way, these are things we'll want to explore in the future, but they all had to be designed at the beginning to make sure they would be possible later.",
- "post_id": "126",
- "date": "June 17, 2010"
- },
{
"category": "encryption",
"medium": "bitcointalk",
@@ -272,13 +48,6 @@
"post_id": "119",
"date": "June 14, 2010"
},
- {
- "category": "encryption",
- "medium": "bitcointalk",
- "text": "If SHA-256 became completely broken, I think we could come to some agreement about what the honest block chain was before the trouble started, lock that in and continue from there with a new hash function.",
- "post_id": "119",
- "date": "June 14, 2010"
- },
{
"category": "releases",
"medium": "bitcointalk",
@@ -286,34 +55,6 @@
"post_id": "111",
"date": "May 26, 2010"
},
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "Simplified Payment Verification is for lightweight client-only users who only do transactions and don't generate and don't participate in the node network. They wouldn't need to download blocks, just the hash chain, which is currently about 2MB and very quick to verify (less than a second to verify the whole chain). If the network becomes very large, like over 100,000 nodes, this is what we'll use to allow common users to do transactions without being full blown nodes. At that stage, most users should start running client-only software and only the specialist server farms keep running full network nodes, kind of like how the usenet network has consolidated. \nSPV is not implemented yet, and won't be implemented until far in the future, but all the current implementation is designed around supporting it.",
- "post_id": "105",
- "date": "May 18, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "Bitcoin addresses you generate are kept forever. A bitcoin address must be kept to show ownership of anything sent to it. If you were able to delete a bitcoin address and someone sent to it, the money would be lost. They're only about 500 bytes.",
- "post_id": "102",
- "date": "May 16, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "When you generate a new bitcoin address, it only takes disk space on your own computer (like 500 bytes). It's like generating a new PGP private key, but less CPU intensive because it's ECC. The address space is effectively unlimited. It doesn't hurt anyone, so generate all you want.",
- "post_id": "98",
- "date": "May 16, 2010"
- },
- {
- "category": "general",
- "medium": "bitcointalk",
- "text": "The price of .com registrations is lower than it should be, therefore any good name you might think of is always already taken by some domain name speculator. Fortunately, it's standard for open source projects to be .org.",
- "post_id": "94",
- "date": "March 23, 2010"
- },
{
"category": "bitcoin-design",
"medium": "bitcointalk",
@@ -328,20 +69,6 @@
"post_id": "73",
"date": "February 24, 2010"
},
- {
- "category": "economics",
- "medium": "bitcointalk",
- "text": "A rational market price for something that is expected to increase in value will already reflect the present value of the expected future increases. In your head, you do a probability estimate balancing the odds that it keeps increasing.",
- "post_id": "65",
- "date": "February 21, 2010"
- },
- {
- "category": "economics, bitcoin-economics",
- "medium": "bitcointalk",
- "text": "The price of any commodity tends to gravitate toward the production cost. If the price is below cost, then production slows down. If the price is above cost, profit can be made by generating and selling more. At the same time, the increased production would increase the difficulty, pushing the cost of generating towards the price.",
- "post_id": "65",
- "date": "February 21, 2010"
- },
{
"category": "bitcoin-economics",
"medium": "bitcointalk",
@@ -370,20 +97,6 @@
"post_id": "56",
"date": "February 14, 2010"
},
- {
- "category": "bitcoin-economics, bitcoin-design",
- "medium": "bitcointalk",
- "text": "Eventually at most only 21 million coins for 6.8 billion people in the world if it really gets huge.\n\nBut don't worry, there are another 6 decimal places that aren't shown, for a total of 8 decimal places internally. It shows 1.00 but internally it's 1.00000000. If there's massive deflation in the future, the software could show more decimal places.",
- "post_id": "46",
- "date": "February 6, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "If it gets tiresome working with small numbers, we could change where the display shows the decimal point. Same amount of money, just different convention for where the \",\"'s and \".\"'s go. e.g. moving the decimal place 3 places would mean if you had 1.00000 before, now it shows it as 1,000.00.",
- "post_id": "46",
- "date": "February 6, 2010"
- },
{
"category": "privacy",
"medium": "bitcointalk",
@@ -398,41 +111,6 @@
"post_id": "45",
"date": "February 6, 2010"
},
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "I very much wanted to find some way to include a short message, but the problem is, the whole world would be able to see the message. As much as you may keep reminding people that the message is completely non-private, it would be an accident waiting to happen.",
- "post_id": "33",
- "date": "January 28, 2010"
- },
- {
- "category": "mining",
- "medium": "bitcointalk",
- "text": "The average total coins generated across the network per day stays the same. Faster machines just get a larger share than slower machines. If everyone bought faster machines, they wouldn't get more coins than before.",
- "post_id": "20",
- "date": "December 12, 2009"
- },
- {
- "category": "mining",
- "medium": "bitcointalk",
- "text": "We should have a gentleman's agreement to postpone the GPU arms race as long as we can for the good of the network. It's much easer to get new users up to speed if they don't have to worry about GPU drivers and compatibility. It's nice how anyone with just a CPU can compete fairly equally right now.",
- "post_id": "20",
- "date": "December 12, 2009"
- },
- {
- "category": "bitcoin-economics",
- "medium": "bitcointalk",
- "text": "Those coins can never be recovered, and the total circulation is less. Since the effective circulation is reduced, all the remaining coins are worth slightly more. It's the opposite of when a government prints money and the value of existing money goes down.",
- "post_id": "17",
- "date": "December 10, 2009"
- },
- {
- "category": "trusted-third-parties",
- "text": "Being open source means anyone can independently review the code. If it was closed source, nobody could verify the security. I think it's essential for a program of this nature to be open source.",
- "medium": "bitcointalk",
- "post_id": "17",
- "date": "December 10, 2009"
- },
{
"category": "privacy, transactions",
"medium": "bitcointalk",
@@ -440,20 +118,6 @@
"post_id": "11",
"date": "November 25, 2009"
},
- {
- "category": "mining",
- "medium": "bitcointalk",
- "text": "Think of it as a cooperative effort to make a chain. When you add a link, you must first find the current end of the chain. If you were to locate the last link, then go off for an hour and forge your link, come back and link it to the link that was the end an hour ago, others may have added several links since then and they're not going to want to use your link that now branches off the middle.",
- "post_id": "8",
- "date": "November 22, 2009"
- },
- {
- "category": "bitcoin-design",
- "medium": "p2pfoundation",
- "text": "It is a global distributed database, with additions to the database by consent of the majority, based on a set of rules they follow: \n\n- Whenever someone finds proof-of-work to generate a block, they get some new coins\n- The proof-of-work difficulty is adjusted every two weeks to target an average of 6 blocks per hour (for the whole network)\n- The coins given per block is cut in half every 4 years",
- "post_id": "3",
- "date": "February 18, 2009"
- },
{
"category": "bitcoin-economics",
"medium": "p2pfoundation",
@@ -461,55 +125,6 @@
"post_id": "3",
"date": "February 18, 2009"
},
- {
- "category": "bitcoin-economics",
- "medium": "p2pfoundation",
- "text": "To Sepp's question, indeed there is nobody to act as central bank or federal reserve to adjust the money supply as the population of users grows. That would have required a trusted party to determine the value, because I don't know a way for software to know the real world value of things.",
- "post_id": "3",
- "date": "February 18, 2009"
- },
- {
- "category": "bitcoin-economics",
- "medium": "p2pfoundation",
- "text": "In this sense, it's more typical of a precious metal. Instead of the supply changing to keep the value the same, the supply is predetermined and the value changes. As the number of users grows, the value per coin increases. It has the potential for a positive feedback loop; as users increase, the value goes up, which could attract more users to take advantage of the increasing value.",
- "post_id": "3",
- "date": "February 18, 2009"
- },
- {
- "category": "cryptocurrency",
- "medium": "p2pfoundation",
- "text": "A lot of people automatically dismiss e-currency as a lost cause because of all the companies that failed since the 1990's. I hope it's obvious it was only the centrally controlled nature of those systems that doomed them. I think this is the first time we're trying a decentralized, non-trust-based system.",
- "post_id": "2",
- "date": "February 15, 2009"
- },
- {
- "category": "releases, bitcoin-design",
- "medium": "p2pfoundation",
- "text": "I've developed a new open source P2P e-cash system called Bitcoin. It's completely decentralized, with no central server or trusted parties, because everything is based on crypto proof instead of trust. Give it a try, or take a look at the screenshots and design paper: \n\nDownload Bitcoin v0.1 at http://www.bitcoin.org",
- "post_id": "1",
- "date": "February 11, 2009"
- },
- {
- "category": "economics",
- "medium": "p2pfoundation",
- "text": "The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust.",
- "post_id": "1",
- "date": "February 11, 2009"
- },
- {
- "category": "micropayments, privacy, banks",
- "medium": "p2pfoundation",
- "text": "Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible.",
- "post_id": "1",
- "date": "February 11, 2009"
- },
- {
- "category": "encryption",
- "medium": "p2pfoundation",
- "text": "A generation ago, multi-user time-sharing computer systems had a similar problem. Before strong encryption, users had to rely on password protection to secure their files, placing trust in the system administrator to keep their information private. Privacy could always be overridden by the admin based on his judgment call weighing the principle of privacy against other concerns, or at the behest of his superiors. Then strong encryption became available to the masses, and trust was no longer required. Data could be secured in a way that was physically impossible for others to access, no matter for what reason, no matter how good the excuse, no matter what.",
- "post_id": "1",
- "date": "February 11, 2009"
- },
{
"category": "cryptocurrency",
"medium": "p2pfoundation",
@@ -517,27 +132,6 @@
"post_id": "1",
"date": "February 11, 2009"
},
- {
- "category": "transactions",
- "medium": "p2pfoundation",
- "text": "A digital coin contains the public key of its owner. To transfer it, the owner signs the coin together with the public key of the next owner. Anyone can check the signatures to verify the chain of ownership.",
- "post_id": "1",
- "date": "February 11, 2009"
- },
- {
- "category": "double-spending",
- "medium": "p2pfoundation",
- "text": "Any owner could try to re-spend an already spent coin by signing it again to another owner. The usual solution is for a trusted company with a central database to check for double-spending, but that just gets back to the trust model. In its central position, the company can override the users, and the fees needed to support the company make micropayments impractical. \nBitcoin's solution is to use a peer-to-peer network to check for double-spending. In a nutshell, the network works like a distributed timestamp server, stamping the first transaction to spend a coin. It takes advantage of the nature of information being easy to spread but hard to stifle.",
- "post_id": "1",
- "date": "February 11, 2009"
- },
- {
- "category": "bitcoin-design",
- "medium": "p2pfoundation",
- "text": "The result is a distributed system with no single point of failure. Users hold the crypto keys to their own money and transact directly with each other, with the help of the P2P network to check for double-spending.",
- "post_id": "1",
- "date": "February 11, 2009"
- },
{
"category": "identity",
"medium": "p2pfoundation",
@@ -552,20 +146,6 @@
"text": "I've been working on a new electronic cash system that's fully peer-to-peer, with no trusted third party.",
"date": "November 1, 2008"
},
- {
- "category": "bitcoin-design",
- "medium": "email",
- "email_id": "1",
- "text": "The main properties: \n Double-spending is prevented with a peer-to-peer network.\n No mint or other trusted parties.\n Participants can be anonymous.\n New coins are made from Hashcash style proof-of-work.\n The proof-of-work for new coin generation also proof-of-workers the network to prevent double-spending.",
- "date": "November 1, 2008"
- },
- {
- "category": "double-spending",
- "medium": "email",
- "email_id": "2",
- "text": "Long before the network gets anywhere near as large as that, it would be safe for users to use Simplified Payment Verification (section 8) to check for double spending, which only requires having the chain of block headers, or about 12KB per day.",
- "date": "November 2, 2008"
- },
{
"category": "nodes",
"medium": "email",
@@ -573,13 +153,6 @@
"text": "Only people trying to create new coins would need to run network nodes.",
"date": "November 2, 2008"
},
- {
- "category": "nodes",
- "medium": "email",
- "email_id": "2",
- "text": "At first, most users would run network nodes, but as the network grows beyond a certain point, it would be left more and more to specialists with server farms of specialized hardware. A server farm would only need to have one node on the network and the rest of the LAN connects with that one node.",
- "date": "November 2, 2008"
- },
{
"category": "mining",
"medium": "email",
@@ -587,20 +160,6 @@
"text": "The requirement is that the good guys collectively have more CPU proof-of-worker than any single attacker.",
"date": "November 3, 2008"
},
- {
- "category": "mining",
- "medium": "email",
- "email_id": "3",
- "text": "There would be many smaller zombie farms that are not big enough to overproof-of-worker the network, and they could still make money by generating bitcoins. The smaller farms are then the \"honest nodes\". (I need a better term than \"honest\") The more smaller farms resort to generating bitcoins, the higher the bar gets to overproof-of-worker the network, making larger farms also too small to overproof-of-worker it so that they may as well generate bitcoins too. According to the \"long tail\" theory, the small, medium and merely large farms put together should add up to a lot more than the biggest zombie farm.",
- "date": "November 3, 2008"
- },
- {
- "category": "mining",
- "medium": "email",
- "email_id": "3",
- "text": "Even if a bad guy does overproof-of-worker the network, it's not like he's instantly rich. All he can accomplish is to take back money he himself spent, like bouncing a check. To exploit it, he would have to buy something from a merchant, wait till it ships, then overproof-of-worker the network and try to take his money back. I don't think he could make as much money trying to pull a carding scheme like that as he could by generating bitcoins. With a zombie farm that big, he could generate more bitcoins than everyone else combined.",
- "date": "November 3, 2008"
- },
{
"category": "mining",
"medium": "email",
@@ -622,62 +181,6 @@
"text": "Governments are good at cutting off the heads of a centrally controlled networks like Napster, but pure P2P networks like Gnutella and Tor seem to be holding their own.",
"date": "November 7, 2008"
},
- {
- "category": "mining, difficulty",
- "medium": "email",
- "email_id": "5",
- "text": "As computers get faster and the total computing proof-of-worker applied to creating bitcoins increases, the difficulty increases proportionally to keep the total new production constant. Thus, it is known in advance how many new bitcoins will be created every year in the future.",
- "date": "November 8, 2008"
- },
- {
- "category": "bitcoin-economics",
- "medium": "email",
- "email_id": "5",
- "text": "The fact that new coins are produced means the money supply increases by a planned amount, but this does not necessarily result in inflation. If the supply of money increases at the same rate that the number of people using it increases, prices remain stable. If it does not increase as fast as demand, there will be deflation and early holders of money will see its value increase. Coins have to get initially distributed somehow, and a constant rate seems like the best formula.",
- "date": "November 8, 2008"
- },
- {
- "category": "nodes",
- "medium": "email",
- "email_id": "6",
- "text": "Right, nodes keep transactions in their working set until they get into a block. If a transaction reaches 90% of nodes, then each time a new block is found, it has a 90% chance of being in it.",
- "date": "November 9, 2008"
- },
- {
- "category": "transactions",
- "medium": "email",
- "email_id": "6",
- "text": "Receivers of transactions will normally need to hold transactions for perhaps an hour or more to allow time for this kind of possibility to be resolved. They can still re-spend the coins immediately, but they should wait before taking an action such as shipping goods.",
- "date": "November 9, 2008"
- },
- {
- "category": "double-spending",
- "medium": "email",
- "email_id": "6",
- "text": "The attacker isn't adding blocks to the end. He has to go back and redo the block his transaction is in and all the blocks after it, as well as any new blocks the network keeps adding to the end while he's doing that. He's rewriting history. Once his branch is longer, it becomes the new valid one.",
- "date": "November 9, 2008"
- },
- {
- "category": "nodes, mining, proof-of-work",
- "medium": "email",
- "email_id": "6",
- "text": "It is strictly necessary that the longest chain is always considered the valid one. Nodes that were present may remember that one branch was there first and got replaced by another, but there would be no way for them to convince those who were not present of this. We can't have subfactions of nodes that cling to one branch that they think was first, others that saw another branch first, and others that joined later and never saw what happened. The CPU proof-of-worker proof-of-work vote must have the final say. The only way for everyone to stay on the same page is to believe that the longest chain is always the valid one, no matter what.",
- "date": "November 9, 2008"
- },
- {
- "category": "transactions",
- "medium": "email",
- "email_id": "6",
- "text": "The recipient just needs to verify it back to a depth that is sufficiently far back in the block chain, which will often only require a depth of 2 transactions. All transactions before that can be discarded.",
- "date": "November 9, 2008"
- },
- {
- "category": "nodes",
- "medium": "email",
- "email_id": "6",
- "text": "When a node receives a block, it checks the signatures of every transaction in it against previous transactions in blocks. Blocks can only contain transactions that depend on valid transactions in previous blocks or the same block. Transaction C could depend on transaction B in the same block and B depends on transaction A in an earlier block.",
- "date": "November 9, 2008"
- },
{
"category": "transactions",
"medium": "email",
@@ -692,34 +195,6 @@
"text": "The proof-of-work chain is the solution to the synchronisation problem, and to knowing what the globally shared view is without having to trust anyone.",
"date": "November 9, 2008"
},
- {
- "category": "nodes",
- "medium": "email",
- "email_id": "8",
- "text": "A transaction will quickly propagate throughout the network, so if two versions of the same transaction were reported at close to the same time, the one with the head start would have a big advantage in reaching many more nodes first. Nodes will only accept the first one they see, refusing the second one to arrive, so the earlier transaction would have many more nodes working on incorporating it into the next proof-of-work. In effect, each node votes for its viewpoint of which transaction it saw first by including it in its proof-of-work effort. If the transactions did come at exactly the same time and there was an even split, it's a toss up based on which gets into a proof-of-work first, and that decides which is valid.",
- "date": "November 9, 2008"
- },
- {
- "category": "nodes, proof-of-work",
- "medium": "email",
- "email_id": "8",
- "text": "When a node finds a proof-of-work, the new block is propagated throughout the network and everyone adds it to the chain and starts working on the next block after it. Any nodes that had the other transaction will stop trying to include it in a block, since it's now invalid according to the accepted chain.",
- "date": "November 9, 2008"
- },
- {
- "category": "proof-of-work",
- "medium": "email",
- "email_id": "8",
- "text": "The proof-of-work chain is itself self-evident proof that it came from the globally shared view. Only the majority of the network together has enough CPU proof-of-worker to generate such a difficult chain of proof-of-work. Any user, upon receiving the proof-of-work chain, can see what the majority of the network has approved. Once a transaction is hashed into a link that's a few links back in the chain, it is firmly etched into the global history.",
- "date": "November 9, 2008"
- },
- {
- "category": "fees, bitcoin-economics",
- "medium": "email",
- "email_id": "9",
- "text": "If you're having trouble with the inflation issue, it's easy to tweak it for transaction fees instead. It's as simple as this: let the output value from any transaction be 1 cent less than the input value. Either the client software automatically writes transactions for 1 cent more than the intended payment value, or it could come out of the payee's side. The incentive value when a node finds a proof-of-work for a block could be the total of the fees in the block.",
- "date": "November 10, 2008"
- },
{
"category": "double-spending",
"medium": "email",
@@ -734,27 +209,6 @@
"text": "The receiver of a payment must wait an hour or so before believing that it's valid. The network will resolve any possible double-spend races by then.",
"date": "November 11, 2008"
},
- {
- "category": "double-spending",
- "medium": "email",
- "email_id": "10",
- "text": "The guy who received the double-spend that became invalid never thought he had it in the first place. His software would have shown the transaction go from \"unconfirmed\" to \"invalid\". If necessary, the UI can be made to hide transactions until they're sufficiently deep in the block chain.",
- "date": "November 11, 2008"
- },
- {
- "category": "difficulty",
- "medium": "email",
- "email_id": "10",
- "text": "The target time between blocks will probably be 10 minutes. Every block includes its creation time. If the time is off by more than 36 hours, other nodes won't work on it. If the timespan over the last 6*24*30 blocks is less than 15 days, blocks are being generated too fast and the proof-of-work difficulty doubles. Everyone does the same calculation with the same chain data, so they all get the same result at the same link in the chain.",
- "date": "November 11, 2008"
- },
- {
- "category": "transactions",
- "medium": "email",
- "email_id": "10",
- "text": "Instantant non-repudiability is not a feature, but it's still much faster than existing systems. Paper cheques can bounce up to a week or two later. Credit card transactions can be contested up to 60 to 180 days later. Bitcoin transactions can be sufficiently irreversible in an hour or two.",
- "date": "November 11, 2008"
- },
{
"category": "nodes",
"medium": "email",
@@ -762,20 +216,6 @@
"text": "With the transaction fee based incentive system I recently posted, nodes would have an incentive to include all the paying transactions they receive.",
"date": "November 11, 2008"
},
- {
- "category": "proof-of-work",
- "medium": "email",
- "email_id": "11",
- "text": "The proof-of-work chain is a solution to the Byzantine Generals' Problem. I'll try to rephrase it in that context.\nA number of Byzantine Generals each have a computer and want to attack the King's wi-fi by brute forcing the password, which they've learned is a certain number of characters in length. Once they stimulate the network to generate a packet, they must crack the password within a limited time to break in and erase the logs, otherwise they will be discovered and get in trouble. They only have enough CPU proof-of-worker to crack it fast enough if a majority of them attack at the same time. \n They don't particularly care when the attack will be, just that they all agree. It has been decided that anyone who feels like it will announce a time, and whatever time is heard first will be the official attack time. The problem is that the network is not instantaneous, and if two generals announce different attack times at close to the same time, some may hear one first and others hear the other first. They use a proof-of-work chain to solve the problem. Once each general receives whatever attack time he hears first, he sets his computer to solve an extremely difficult proof-of-work problem that includes the attack time in its hash. The proof-of-work is so difficult, it's expected to take 10 minutes of them all working at once before one of them finds a solution. Once one of the generals finds a proof-of-work, he broadcasts it to the network, and everyone changes their current proof-of-work computation to include that proof-of-work in the hash they're working on. If anyone was working on a different attack time, they switch to this one, because its proof-of-work chain is now longer.\n After two hours, one attack time should be hashed by a chain of 12 proofs-of-work. Every general, just by verifying the difficulty of the proof-of-work chain, can estimate how much parallel CPU proof-of-worker per hour was expended on it and see that it must have required the majority of the computers to produce that much proof-of-work in the allotted time. They had to all have seen it because the proof-of-work is proof that they worked on it. If the CPU proof-of-worker exhibited by the proof-of-work chain is sufficient to crack the password, they can safely attack at the agreed time.\n The proof-of-work chain is how all the synchronisation, distributed database and global view problems you've asked about are solved.",
- "date": "November 13, 2008"
- },
- {
- "category": "nodes, mining",
- "medium": "email",
- "email_id": "12",
- "text": "Broadcasts will probably be almost completely reliable. TCP transmissions are rarely ever dropped these days, and the broadcast protocol has a retry mechanism to get the data from other nodes after a while. If broadcasts turn out to be slower in practice than expected, the target time between blocks may have to be increased to avoid wasting resources. We want blocks to usually propagate in much less time than it takes to generate them, otherwise nodes would spend too much time working on obsolete blocks.",
- "date": "November 14, 2008"
- },
{
"category": "motives",
"medium": "email",
@@ -790,41 +230,6 @@
"text": "I'll try and hurry up and release the sourcecode as soon as possible to serve as a reference to help clear up all these implementation questions.",
"date": "November 17, 2008"
},
- {
- "category": "transactions",
- "medium": "email",
- "email_id": "13",
- "text": "A basic transaction is just what you see in the figure in section 2. A signature (of the buyer) satisfying the public key of the previous transaction, and a new public key (of the seller) that must be satisfied to spend it the next time.",
- "date": "November 17, 2008"
- },
- {
- "category": "double-spending",
- "medium": "email",
- "email_id": "13",
- "text": "There's no need for reporting of \"proof of double spending\" like that. If the same chain contains both spends, then the block is invalid and rejected. \n Same if a block didn't have enough proof-of-work. That block is invalid and rejected. There's no need to circulate a report about it. Every node could see that and reject it before relaying it.",
- "date": "November 17, 2008"
- },
- {
- "category": "double-spending",
- "medium": "email",
- "email_id": "13",
- "text": "We're not \"on the lookout\" for double spends to sound the alarm and catch the cheater. We merely adjudicate which one of the spends is valid. Receivers of transactions must wait a few blocks to make sure that resolution has had time to complete. Would be cheaters can try and simultaneously double-spend all they want, and all they accomplish is that within a few blocks, one of the spends becomes valid and the others become invalid. Any later double-spends are immediately rejected once there's already a spend in the main chain.",
- "date": "November 17, 2008"
- },
- {
- "category": "proof-of-work, mining",
- "medium": "email",
- "email_id": "13",
- "text": "The proof-of-work is a Hashcash style SHA-256 collision finding. It's a memoryless process where you do millions of hashes a second, with a small chance of finding one each time. The 3 or 4 fastest nodes' dominance would only be proportional to their share of the total CPU proof-of-worker. Anyone's chance of finding a solution at any time is proportional to their CPU proof-of-worker.",
- "date": "November 17, 2008"
- },
- {
- "category": "bitcoin-economics",
- "medium": "email",
- "email_id": "13",
- "text": "There will be transaction fees, so nodes will have an incentive to receive and include all the transactions they can. Nodes will eventually be compensated by transaction fees alone when the total coins created hits the pre-determined ceiling.",
- "date": "November 17, 2008"
- },
{
"category": "proof-of-work",
"medium": "email",
@@ -832,34 +237,6 @@
"text": "The credential that establishes someone as real is the ability to supply CPU proof-of-worker.",
"date": "November 17, 2008"
},
- {
- "category": "double-spending",
- "medium": "email",
- "email_id": "14",
- "text": "The race is to spread your transaction on the network first. Think 6 degrees of freedom -- it spreads exponentially. It would only take something like 2 minutes for a transaction to spread widely enough that a competitor starting late would have little chance of grabbing very many nodes before the first one is overtaking the whole network. During those 2 minutes, the merchant's nodes can be watching for a double-spent transaction. The double-spender would not be able to blast his alternate transaction out to the world without the merchant getting it, so he has to wait before starting. \n If the real transaction reaches 90% and the double-spent tx reaches 10%, the double-spender only gets a 10% chance of not paying, and 90% chance his money gets spent. For almost any type of goods, that's not going to be worth it for the scammer.",
- "date": "November 17, 2008"
- },
- {
- "category": "transactions",
- "medium": "email",
- "email_id": "14",
- "text": "If a merchant actually has a problem with theft, they can make the customer wait 2 minutes, or wait for something in e-mail, which many already do. If they really want to optimize, and it's a large download, they could cancel the download in the middle if the transaction comes back double-spent. If it's website access, typically it wouldn't be a big deal to let the customer have access for 5 minutes and then cut off access if it's rejected. Many such sites have a free trial anyway.",
- "date": "November 17, 2008"
- },
- {
- "category": "releases, bitcoin-design",
- "medium": "email",
- "email_id": "15",
- "text": "I believe I've worked through all those little details over the last year and a half while coding it, and there were a lot of them. The functional details are not covered in the paper, but the sourcecode is coming soon. I sent you the main files. (available by request at the moment, full release soon)",
- "date": "November 17, 2008"
- },
- {
- "category": "releases",
- "medium": "email",
- "email_id": "16",
- "text": "Announcing the first release of Bitcoin, a new electronic cash system that uses a peer-to-peer network to prevent double-spending. It's completely decentralized with no server or central authority.",
- "date": "January 9, 2009"
- },
{
"category": "nodes",
"medium": "email",
@@ -867,41 +244,6 @@
"text": "If you can keep a node running that accepts incoming connections, you'll really be helping the network a lot. Port 8333 on your firewall needs to be open to receive incoming connections.",
"date": "January 9, 2009"
},
- {
- "category": "mining",
- "medium": "email",
- "email_id": "16",
- "text": "You can get coins by getting someone to send you some, or turn on Options->Generate Coins to run a node and generate blocks. I made the proof-of-work difficulty ridiculously easy to start with, so for a little while in the beginning a typical PC will be able to generate coins in just a few hours. It'll get a lot harder when competition makes the automatic adjustment drive up the difficulty. Generated coins must wait 120 blocks to mature before they can be spent.",
- "date": "January 9, 2009"
- },
- {
- "category": "transactions",
- "medium": "email",
- "email_id": "16",
- "text": "There are two ways to send money. If the recipient is online, you can enter their IP address and it will connect, get a new public key and send the transaction with comments. If the recipient is not online, it is possible to send to their Bitcoin address, which is a hash of their public key that they give you. They'll receive the transaction the next time they connect and get the block it's in. This method has the disadvantage that no comment information is sent, and a bit of privacy may be lost if the address is used multiple times, but it is a useful alternative if both users can't be online at the same time or the recipient can't receive incoming connections.",
- "date": "January 9, 2009"
- },
- {
- "category": "bitcoin-economics",
- "medium": "email",
- "email_id": "16",
- "text": "Total circulation will be 21,000,000 coins. It'll be distributed to network nodes when they make blocks, with the amount cut in half every 4 years.\n\nfirst 4 years: 10,500,000 coins\nnext 4 years: 5,250,000 coins\nnext 4 years: 2,625,000 coins\nnext 4 years: 1,312,500 coins\netc...\n\nWhen that runs out, the system can support transaction fees if needed. It's based on open market competition, and there will probably always be nodes willing to process transactions for free.",
- "date": "January 9, 2009"
- },
- {
- "category": "cryptocurrency",
- "medium": "email",
- "email_id": "17",
- "text": "I would be surprised if 10 years from now we're not using electronic currency in some way, now that we know a way to do it that won't inevitably get dumbed down when the trusted third party gets cold feet.",
- "date": "January 17, 2009"
- },
- {
- "category": "micropayments",
- "medium": "email",
- "email_id": "17",
- "text": "It can already be used for pay-to-send e-mail. The send dialog is resizeable and you can enter as long of a message as you like. It's sent directly when it connects. The recipient doubleclicks on the transaction to see the full message. If someone famous is getting more e-mail than they can read, but would still like to have a way for fans to contact them, they could set up Bitcoin and give out the IP address on their website. \"Send X bitcoins to my priority hotline at this IP and I'll read the message personally.\"",
- "date": "January 17, 2009"
- },
{
"category": "micropayments",
"medium": "email",
@@ -909,191 +251,10 @@
"text": "Subscription sites that need some extra proof-of-work for their free trial so it doesn't cannibalize subscriptions could charge bitcoins for the trial.",
"date": "January 17, 2009"
},
- {
- "category": "micropayments, bitcoin-economics",
- "medium": "email",
- "email_id": "17",
- "text": "It might make sense just to get some in case it catches on. If enough people think the same way, that becomes a self fulfilling prophecy. Once it gets bootstrapped, there are so many applications if you could effortlessly pay a few cents to a website as easily as dropping coins in a vending machine.",
- "date": "January 17, 2009"
- },
{
"category": "cryptocurrency",
"text": "A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution.",
"medium": "whitepaper",
"date": "October 31, 2008"
- },
- {
- "category": "proof-of-work, double-spending",
- "text": "We propose a solution to the double-spending problem using a peer-to-peer network. The network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU proof-of-worker. As long as a majority of CPU proof-of-worker is controlled by nodes that are not cooperating to attack the network, they'll generate the longest chain and outpace attackers. The network itself requires minimal structure.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "trusted-third-parties",
- "text": "Commerce on the Internet has come to rely almost exclusively on financial institutions serving as trusted third parties to process electronic payments. While the system works well enough for most transactions, it still suffers from the inherent weaknesses of the trust based model.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "trusted-third-parties",
- "text": "Completely non-reversible transactions are not really possible, since financial institutions cannot avoid mediating disputes. The cost of mediation increases transaction costs, limiting the minimum practical transaction size and cutting off the possibility for small casual transactions, and there is a broader cost in the loss of ability to make non-reversible payments for non-reversible services. With the possibility of reversal, the need for trust spreads.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "trusted-third-parties, cryptocurrency",
- "text": "What is needed is an electronic payment system based on cryptographic proof instead of trust, allowing any two willing parties to transact directly with each other without the need for a trusted third party. Transactions that are computationally impractical to reverse would protect sellers from fraud, and routine escrow mechanisms could easily be implemented to protect buyers.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "double-spending, proof-of-work",
- "text": "In this paper, we propose a solution to the double-spending problem using a peer-to-peer distributed timestamp server to generate computational proof of the chronological order of transactions. The system is secure as long as honest nodes collectively control more CPU proof-of-worker than any cooperating group of attacker nodes.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "transactions",
- "text": "We define an electronic coin as a chain of digital signatures. Each owner transfers the coin to the next by digitally signing a hash of the previous transaction and the public key of the next owner and adding these to the end of the coin. A payee can verify the signatures to verify the chain of ownership.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "economics, double-spending",
- "text": "The problem of course is the payee can't verify that one of the owners did not double-spend the coin. A common solution is to introduce a trusted central authority, or mint, that checks every transaction for double spending. After each transaction, the coin must be returned to the mint to issue a new coin, and only coins issued directly from the mint are trusted not to be double-spent. The problem with this solution is that the fate of the entire money system depends on the company running the mint, with every transaction having to go through them, just like a bank.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "nodes, cryptocurrency, transactions",
- "text": "We need a way for the payee to know that the previous owners did not sign any earlier transactions. For our purposes, the earliest transaction is the one that counts, so we don't care about later attempts to double-spend. The only way to confirm the absence of a transaction is to be aware of all transactions. In the mint based model, the mint was aware of all transactions and decided which arrived first. To accomplish this without a trusted party, transactions must be publicly announced, and we need a system for participants to agree on a single history of the order in which they were received. The payee needs proof that at the time of each transaction, the majority of nodes agreed it was the first received.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "transactions",
- "text": "The solution we propose begins with a timestamp server. A timestamp server works by taking a hash of a block of items to be timestamped and widely publishing the hash, such as in a newspaper or Usenet post. The timestamp proves that the data must have existed at the time, obviously, in order to get into the hash. Each timestamp includes the previous timestamp in its hash, forming a chain, with each additional timestamp reinforcing the ones before it.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "proof-of-work",
- "text": "To implement a distributed timestamp server on a peer-to-peer basis, we will need to use a proof-of-work system similar to Adam Back's Hashcash, rather than newspaper or Usenet posts. The proof-of-work involves scanning for a value that when hashed, such as with SHA-256, the hash begins with a number of zero bits. The average work required is exponential in the number of zero bits required and can be verified by executing a single hash.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "proof-of-work",
- "text": "For our timestamp network, we implement the proof-of-work by incrementing a nonce in the block until a value is found that gives the block's hash the required zero bits. Once the CPU effort has been expended to make it satisfy the proof-of-work, the block cannot be changed without redoing the work. As later blocks are chained after it, the work to change the block would include redoing all the blocks after it.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "proof-of-work",
- "text": "The proof-of-work also solves the problem of determining representation in majority decision making. If the majority were based on one-IP-address-one-vote, it could be subverted by anyone able to allocate many IPs. Proof-of-work is essentially one-CPU-one-vote. The majority decision is represented by the longest chain, which has the greatest proof-of-work effort invested in it. If a majority of CPU proof-of-worker is controlled by honest nodes, the honest chain will grow the fastest and outpace any competing chains. To modify a past block, an attacker would have to redo the proof-of-work of the block and all blocks after it and then catch up with and surpass the work of the honest nodes. We will show later that the probability of a slower attacker catching up diminishes exponentially as subsequent blocks are added.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "proof-of-work, difficulty",
- "text": "To compensate for increasing hardware speed and varying interest in running nodes over time, the proof-of-work difficulty is determined by a moving average targeting an average number of blocks per hour. If they're generated too fast, the difficulty increases.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "bitcoin-design, nodes, proof-of-work",
- "text": "The steps to run the network are as follows:\n\n1. New transactions are broadcast to all nodes.\n2. Each node collects new transactions into a block.\n3. Each node works on finding a difficult proof-of-work for its block.\n4. When a node finds a proof-of-work, it broadcasts the block to all nodes.\n5. Nodes accept the block only if all transactions in it are valid and not already spent.\n6. Nodes express their acceptance of the block by working on creating the next block in the chain, using the hash of the accepted block as the previous hash.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "nodes, proof-of-work",
- "text": "Nodes always consider the longest chain to be the correct one and will keep working on extending it. If two nodes broadcast different versions of the next block simultaneously, some nodes may receive one or the other first. In that case, they work on the first one they received, but save the other branch in case it becomes longer. The tie will be broken when the next proof-of-work is found and one branch becomes longer; the nodes that were working on the other branch will then switch to the longer one.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "transactions",
- "text": "New transaction broadcasts do not necessarily need to reach all nodes. As long as they reach many nodes, they will get into a block before long. Block broadcasts are also tolerant of dropped messages. If a node does not receive a block, it will request it when it receives the next block and realizes it missed one.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "mining, bitcoin-economics",
- "text": "By convention, the first transaction in a block is a special transaction that starts a new coin owned by the creator of the block. This adds an incentive for nodes to support the network, and provides a way to initially distribute coins into circulation, since there is no central authority to issue them. The steady addition of a constant of amount of new coins is analogous to gold miners expending resources to add gold to circulation. In our case, it is CPU time and electricity that is expended.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "fees, bitcoin-economics",
- "text": "The incentive can also be funded with transaction fees. If the output value of a transaction is less than its input value, the difference is a transaction fee that is added to the incentive value of the block containing the transaction. Once a predetermined number of coins have entered circulation, the incentive can transition entirely to transaction fees and be completely inflation free.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "mining, bitcoin-economics",
- "text": "The incentive may help encourage nodes to stay honest. If a greedy attacker is able to assemble more CPU proof-of-worker than all the honest nodes, he would have to choose between using it to defraud people by stealing back his payments, or using it to generate new coins. He ought to find it more profitable to play by the rules, such rules that favour him with more new coins than everyone else combined, than to undermine the system and the validity of his own wealth.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "bitcoin-design",
- "text": "Once the latest transaction in a coin is buried under enough blocks, the spent transactions before it can be discarded to save disk space. To facilitate this without breaking the block's hash, transactions are hashed in a Merkle Tree, with only the root included in the block's hash. Old blocks can then be compacted by stubbing off branches of the tree. The interior hashes do not need to be stored.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "bitcoin-design",
- "text": "A block header with no transactions would be about 80 bytes. If we suppose blocks are generated every 10 minutes, 80 bytes * 6 * 24 * 365 = 4.2MB per year. With computer systems typically selling with 2GB of RAM as of 2008, and Moore's Law predicting current growth of 1.2GB per year, storage should not be a problem even if the block headers must be kept in memory.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "bitcoin-design, nodes",
- "text": "It is possible to verify payments without running a full network node. A user only needs to keep a copy of the block headers of the longest proof-of-work chain, which he can get by querying network nodes until he's convinced he has the longest chain, and obtain the Merkle branch linking the transaction to the block it's timestamped in. He can't check the transaction for himself, but by linking it to a place in the chain, he can see that a network node has accepted it, and blocks added after it further confirm the network has accepted it. \nAs such, the verification is reliable as long as honest nodes control the network, but is more vulnerable if the network is overproof-of-workered by an attacker. While network nodes can verify transactions for themselves, the simplified method can be fooled by an attacker's fabricated transactions for as long as the attacker can continue to overproof-of-worker the network. One strategy to protect against this would be to accept alerts from network nodes when they detect an invalid block, prompting the user's software to download the full block and alerted transactions to confirm the inconsistency. Businesses that receive frequent payments will probably still want to run their own nodes for more independent security and quicker verification.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "transactions, bitcoin-design",
- "text": "Although it would be possible to handle coins individually, it would be unwieldy to make a separate transaction for every cent in a transfer. To allow value to be split and combined, transactions contain multiple inputs and outputs. Normally there will be either a single input from a larger previous transaction or multiple inputs combining smaller amounts, and at most two outputs: one for the payment, and one returning the change, if any, back to the sender.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "transactions",
- "text": "It should be noted that fan-out, where a transaction depends on several transactions, and those transactions depend on many more, is not a problem here. There is never the need to extract a complete standalone copy of a transaction's history.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "transactions, privacy, trusted-third-parties",
- "text": "The traditional banking model achieves a level of privacy by limiting access to information to the parties involved and the trusted third party. The necessity to announce all transactions publicly precludes this method, but privacy can still be maintained by breaking the flow of information in another place: by keeping public keys anonymous. The public can see that someone is sending an amount to someone else, but without information linking the transaction to anyone. This is similar to the level of information released by stock exchanges, where the time and size of individual trades, the \"tape\", is made public, but without telling who the parties were.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "addresses, privacy",
- "text": "As an additional firewall, a new key pair should be used for each transaction to keep them from being linked to a common owner. Some linking is still unavoidable with multi-input transactions, which necessarily reveal that their inputs were owned by the same owner. The risk is that if the owner of a key is revealed, linking could reveal other transactions that belonged to the same owner.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "mining, proof-of-work",
- "text": "We consider the scenario of an attacker trying to generate an alternate chain faster than the honest chain. Even if this is accomplished, it does not throw the system open to arbitrary changes, such as creating value out of thin air or taking money that never belonged to the attacker. Nodes are not going to accept an invalid transaction as payment, and honest nodes will never accept a block containing them. An attacker can only try to change one of his own transactions to take back money he recently spent.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "bitcoin-design, trusted-third-parties",
- "text": "We have proposed a system for electronic transactions without relying on trust. We started with the usual framework of coins made from digital signatures, which provides strong control of ownership, but is incomplete without a way to prevent double-spending. To solve this, we proposed a peer-to-peer network using proof-of-work to record a public history of transactions that quickly becomes computationally impractical for an attacker to change if honest nodes control a majority of CPU proof-of-worker.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
- },
- {
- "category": "nodes, mining",
- "text": "The network is robust in its unstructured simplicity. Nodes work all at once with little coordination. They do not need to be identified, since messages are not routed to any particular place and only need to be delivered on a best effort basis. Nodes can leave and rejoin the network at will, accepting the proof-of-work chain as proof of what happened while they were gone. They vote with their CPU proof-of-worker, expressing their acceptance of valid blocks by working on extending them and rejecting invalid blocks by refusing to work on them. Any needed rules and incentives can be enforced with this consensus mechanism.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
}
]
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/static/satoshi_long.json b/lnbits/extensions/gerty/static/satoshi_long.json
new file mode 100644
index 00000000..1cff822a
--- /dev/null
+++ b/lnbits/extensions/gerty/static/satoshi_long.json
@@ -0,0 +1,1099 @@
+[
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "It would have been nice to get this attention in any other context. WikiLeaks has kicked the hornet's nest, and the swarm is headed towards us.",
+ "post_id": "542",
+ "date": "December 11, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "The project needs to grow gradually so the software can be strengthened along the way. I make this appeal to WikiLeaks not to try to use Bitcoin. Bitcoin is a small beta community in its infancy.",
+ "post_id": "523",
+ "date": "December 5, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "I'm happy if someone with artistic skill wants to contribute alternatives. The icon/logo was meant to be good as an icon at the 16x16 and 20x20 pixel sizes. I think it's the best program icon, but there's room for improvement at larger sizes for a graphic for use on websites. It'll be a lot simpler if authors could make their graphics public domain.",
+ "post_id": "500",
+ "date": "November 13, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "I wish rather than deleting the article, they put a length restriction. If something is not famous enough, there could at least be a stub article identifying what it is. I often come across annoying red links of things that Wiki ought to at least have heard of. \nThe article could be as simple as something like: \"Bitcoin is a peer-to-peer decentralised /link/electronic currency/link/.\" \nThe more standard Wiki thing to do is that we should have a paragraph in one of the more general categories that we are an instance of, like Electronic Currency or Electronic Cash. We can probably establish a paragraph there. Again, keep it short. Just identifying what it is.",
+ "post_id": "467",
+ "date": "September 30, 2010"
+ },
+ {
+ "category": "transactions",
+ "medium": "bitcointalk",
+ "text": "As you figured out, the root problem is we shouldn't be counting or spending transactions until they have at least 1 confirmation. 0/unconfirmed transactions are very much second class citizens. At most, they are advice that something has been received, but counting them as balance or spending them is premature.",
+ "post_id": "464",
+ "date": "September 30, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Bitcoin would be convenient for people who don't have a credit card or don't want to use the cards they have, either don't want the spouse to see it on the bill or don't trust giving their number to \"porn guys\", or afraid of recurring billing.",
+ "post_id": "460",
+ "date": "September 23, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "I don't know anything about any of the bug trackers. If we were to have one, we would have to make a thoroughly researched choice. We're managing pretty well just using the forum. I'm more likely to see bugs posted in the forum, and I think other users are much more likely to help resolve and ask follow up questions here than if they were in a bug tracker. A key step is other users helping resolve the simple stuff that's not really a bug but some misunderstanding or confusion. I keep a list of all unresolved bugs I've seen on the forum. In some cases, I'm still thinking about the best design for the fix. This isn't the kind of software where we can leave so many unresolved bugs that we need a tracker for them.",
+ "post_id": "454",
+ "date": "September 19, 2010"
+ },
+ {
+ "category": "scalability",
+ "medium": "bitcointalk",
+ "text": "The threshold can easily be changed in the future. We can decide to increase it when the time comes. It's a good idea to keep it lower as a circuit breaker and increase it as needed. If we hit the threshold now, it would almost certainly be some kind of flood and not actual use. Keeping the threshold lower would help limit the amount of wasted disk space in that event.",
+ "post_id": "441",
+ "date": "September 8, 2010"
+ },
+ {
+ "category": "fees",
+ "medium": "bitcointalk",
+ "text": "Currently, paying a fee is controlled manually with the -paytxfee switch. It would be very easy to make the software automatically check the size of recent blocks to see if it should pay a fee. We're so far from reaching the threshold, we don't need that yet. It's a good idea to see how things go with controlling it manually first anyway.",
+ "post_id": "441",
+ "date": "September 8, 2010"
+ },
+ {
+ "category": "fees, nodes",
+ "medium": "bitcointalk",
+ "text": "Another option is to reduce the number of free transactions allowed per block before transaction fees are required. Nodes only take so many KB of free transactions per block before they start requiring at least 0.01 transaction fee. The threshold should probably be lower than it currently is. I don't think the threshold should ever be 0. We should always allow at least some free transactions.",
+ "post_id": "439",
+ "date": "September 7, 2010"
+ },
+ {
+ "category": "economics",
+ "medium": "bitcointalk",
+ "text": "As a thought experiment, imagine there was a base metal as scarce as gold but with the following properties:\n- boring grey in colour\n- not a good conductor of electricity\n- not particularly strong, but not ductile or easily malleable either\n- not useful for any practical or ornamental purpose\n\nand one special, magical property:\n- can be transported over a communications channel\n\nIf it somehow acquired any value at all for whatever reason, then anyone wanting to transfer wealth over a long distance could buy some, transmit it, and have the recipient sell it.\n\nMaybe it could get an initial value circularly as you've suggested, by people foreseeing its potential usefulness for exchange. (I would definitely want some) Maybe collectors, any random reason could spark it.\n\nI think the traditional qualifications for money were written with the assumption that there are so many competing objects in the world that are scarce, an object with the automatic bootstrap of intrinsic value will surely win out over those without intrinsic value. But if there were nothing in the world with intrinsic value that could be used as money, only scarce but no intrinsic value, I think people would still take up something.\n\n(I'm using the word scarce here to only mean limited potential supply)",
+ "post_id": "428",
+ "date": "August 27, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "Bitcoins have no dividend or potential future dividend, therefore not like a stock.\n\nMore like a collectible or commodity.",
+ "post_id": "427",
+ "date": "August 27, 2010"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "bitcointalk",
+ "text": "There is no way for the software to automatically know if one chain is better than another except by the greatest proof-of-work. In the design it was necessary for it to switch to a longer chain no matter how far back it has to go.",
+ "post_id": "394",
+ "date": "August 16, 2010"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "Some places where generation will gravitate to: \n1) places where it's cheapest or free\n2) people who want to help for idealogical reasons\n3) people who want to get some coins without the inconvenience of doing a transaction to buy them\n\nThere are legitimate places where it's free. Generation is basically free anywhere that has electric heat, since your computer's heat is offsetting your baseboard electric heating. Many small flats have electric heat out of convenience.",
+ "post_id": "364",
+ "date": "August 15, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Then you must also be against the common system of payment up front, where the customer loses.\nPayment up front: customer loses, and the thief gets the money.\nSimple escrow: customer loses, but the thief doesn't get the money either.\nAre you guys saying payment up front is better, because at least the thief gets the money, so at least someone gets it?\nImagine someone stole something from you. You can't get it back, but if you could, if it had a kill switch that could be remote triggered, would you do it? Would it be a good thing for thieves to know that everything you own has a kill switch and if they steal it, it'll be useless to them, although you still lose it too? If they give it back, you can re-activate it.\nImagine if gold turned to lead when stolen. If the thief gives it back, it turns to gold again.\nIt still seems to me the problem may be one of presenting it the right way. For one thing, not being so blunt about \"money burning\" for the purposes of game theory discussion. The money is never truly burned. You have the option to release it at any time forever.",
+ "post_id": "340",
+ "date": "August 11, 2010"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "The heat from your computer is not wasted if you need to heat your home. If you're using electric heat where you live, then your computer's heat isn't a waste. It's equal cost if you generate the heat with your computer. \nIf you have other cheaper heating than electric, then the waste is only the difference in cost.\nIf it's summer and you're using A/C, then it's twice. \nBitcoin generation should end up where it's cheapest. Maybe that will be in cold climates where there's electric heat, where it would be essentially free.",
+ "post_id": "337",
+ "date": "August 9, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "It's the same situation as gold and gold mining. The marginal cost of gold mining tends to stay near the price of gold. Gold mining is a waste, but that waste is far less than the utility of having gold available as a medium of exchange. \nI think the case will be the same for Bitcoin. The utility of the exchanges made possible by Bitcoin will far exceed the cost of electricity used. Therefore, not having Bitcoin would be the net waste.",
+ "post_id": "327",
+ "date": "August 7, 2010"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "bitcointalk",
+ "text": "Proof-of-work has the nice property that it can be relayed through untrusted middlemen. We don't have to worry about a chain of custody of communication. It doesn't matter who tells you a longest chain, the proof-of-work speaks for itself.",
+ "post_id": "327",
+ "date": "August 7, 2010"
+ },
+ {
+ "category": "micropayments",
+ "medium": "bitcointalk",
+ "text": "Forgot to add the good part about micropayments. While I don't think Bitcoin is practical for smaller micropayments right now, it will eventually be as storage and bandwidth costs continue to fall. If Bitcoin catches on on a big scale, it may already be the case by that time. Another way they can become more practical is if I implement client-only mode and the number of network nodes consolidates into a smaller number of professional server farms. Whatever size micropayments you need will eventually be practical. I think in 5 or 10 years, the bandwidth and storage will seem trivial.",
+ "post_id": "318",
+ "date": "August 5, 2010"
+ },
+ {
+ "category": "micropayments",
+ "medium": "bitcointalk",
+ "text": "Bitcoin isn't currently practical for very small micropayments. Not for things like pay per search or per page view without an aggregating mechanism, not things needing to pay less than 0.01. The dust spam limit is a first try at intentionally trying to prevent overly small micropayments like that. \nBitcoin is practical for smaller transactions than are practical with existing payment methods. Small enough to include what you might call the top of the micropayment range. But it doesn't claim to be practical for arbitrarily small micropayments.",
+ "post_id": "317",
+ "date": "August 4, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Actually, it works well to just PM me. I'm the one who's going to be fixing it. If you find a security flaw, I would definitely like to hear from you privately to fix it before it goes public.",
+ "post_id": "294",
+ "date": "July 29, 2010"
+ },
+ {
+ "category": "nodes",
+ "medium": "bitcointalk",
+ "text": "The current system where every user is a network node is not the intended configuration for large scale. That would be like every Usenet user runs their own NNTP server. The design supports letting users just be users. The more burden it is to run a node, the fewer nodes there will be. Those few nodes will be big server farms. The rest will be client nodes that only do transactions and don't generate.",
+ "post_id": "287",
+ "date": "July 29, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "For future reference, here's my public key. It's the same one that's been there since the bitcoin.org site first went up in 2008. Grab it now in case you need it later. http://www.bitcoin.org/Satoshi_Nakamoto.asc",
+ "post_id": "276",
+ "date": "July 25, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "By making some adjustments to the database settings, I was able to make the initial block download about 5 times faster. It downloads in about 30 minutes. \n \nThe database default had it writing each block to disk synchronously, which is not necessary. I changed the settings to let it cache the changes in memory and write them out in a batch. Blocks are still written transactionally, so either the complete change occurs or none of it does, in either case the data is left in a valid state. \n \nI only enabled this change during the initial block download. When you come within 2000 blocks of the latest block, these changes turn off and it slows down to the old way.",
+ "post_id": "258",
+ "date": "July 23, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "The timing is strange, just as we are getting a rapid increase in 3rd party coverage after getting slashdotted. I hope there's not a big hurry to wrap the discussion and decide. How long does Wikipedia typically leave a question like that open for comment? \nIt would help to condense the article and make it less promotional sounding as soon as possible. Just letting people know what it is, where it fits into the electronic money space, not trying to convince them that it's good. They probably want something that just generally identifies what it is, not tries to explain all about how it works.",
+ "post_id": "249",
+ "date": "July 10, 2010"
+ },
+ {
+ "category": "difficulty",
+ "medium": "bitcointalk",
+ "text": "Right, the difficulty adjustment is trying to keep it so the network as a whole generates an average of 6 blocks per hour. The time for your block to mature will always be around 20 hours.",
+ "post_id": "225",
+ "date": "July 16, 2010"
+ },
+ {
+ "category": "difficulty",
+ "medium": "bitcointalk",
+ "text": "Difficulty just increased by 4 times, so now your cost is US$0.02/BTC.",
+ "post_id": "223",
+ "date": "July 16, 2010"
+ },
+ {
+ "category": "scalability, nodes",
+ "medium": "bitcointalk",
+ "text": "The design outlines a lightweight client that does not need the full block chain. In the design PDF it's called Simplified Payment Verification. The lightweight client can send and receive transactions, it just can't generate blocks. It does not need to trust a node to verify payments, it can still verify them itself. \nThe lightweight client is not implemented yet, but the plan is to implement it when it's needed. For now, everyone just runs a full network node.",
+ "post_id": "188",
+ "date": "July 14, 2010"
+ },
+ {
+ "category": "scalability, nodes",
+ "medium": "bitcointalk",
+ "text": "I anticipate there will never be more than 100K nodes, probably less. It will reach an equilibrium where it's not worth it for more nodes to join in. The rest will be lightweight clients, which could be millions.",
+ "post_id": "188",
+ "date": "July 14, 2010"
+ },
+ {
+ "category": "nodes",
+ "medium": "bitcointalk",
+ "text": "At equilibrium size, many nodes will be server farms with one or two network nodes that feed the rest of the farm over a LAN.",
+ "post_id": "188",
+ "date": "July 14, 2010"
+ },
+ {
+ "category": "economics",
+ "medium": "bitcointalk",
+ "text": "When someone tries to buy all the world's supply of a scarce asset, the more they buy the higher the price goes. At some point, it gets too expensive for them to buy any more. It's great for the people who owned it beforehand because they get to sell it to the corner at crazy high prices. As the price keeps going up and up, some people keep holding out for yet higher prices and refuse to sell.",
+ "post_id": "174",
+ "date": "July 9, 2010"
+ },
+ {
+ "category": "releases",
+ "medium": "bitcointalk",
+ "text": "Announcing version 0.3 of Bitcoin, the P2P cryptocurrency! Bitcoin is a digital currency using cryptography and a distributed network to replace the need for a trusted central server. Escape the arbitrary inflation risk of centrally managed currencies! Bitcoin's total circulation is limited to 21 million coins. The coins are gradually released to the network's nodes based on the CPU proof-of-worker they contribute, so you can get a share of them by contributing your idle CPU time.",
+ "post_id": "168",
+ "date": "July 6, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Writing a description for this thing for general audiences is bloody hard. There's nothing to relate it to.",
+ "post_id": "167",
+ "date": "July 5, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "Lost coins only make everyone else's coins worth slightly more. Think of it as a donation to everyone.",
+ "post_id": "131",
+ "date": "June 21, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Excellent choice of a first project, nice work. I had planned to do this exact thing if someone else didn't do it, so when it gets too hard for mortals to generate 50BTC, new users could get some coins to play with right away. Donations should be able to keep it filled. The display showing the balance in the dispenser encourages people to top it up.\n\nYou should put a donation bitcoin address on the page for those who want to add funds to it, which ideally should update to a new address whenever it receives something.",
+ "post_id": "129",
+ "date": "June 18, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Since 2007. At some point I became convinced there was a way to do this without any trust required at all and couldn't resist to keep thinking about it. Much more of the work was designing than coding.\n\nFortunately, so far all the issues raised have been things I previously considered and planned for.",
+ "post_id": "127",
+ "date": "June 18, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "The nature of Bitcoin is such that once version 0.1 was released, the core design was set in stone for the rest of its lifetime. Because of that, I wanted to design it to support every possible transaction type I could think of. The problem was, each thing required special support code and data fields whether it was used or not, and only covered one special case at a time. It would have been an explosion of special cases. The solution was script, which generalizes the problem so transacting parties can describe their transaction as a predicate that the node network evaluates. The nodes only need to understand the transaction to the extent of evaluating whether the sender's conditions are met.",
+ "post_id": "126",
+ "date": "June 17, 2010"
+ },
+ {
+ "category": "transactions, bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "The design supports a tremendous variety of possible transaction types that I designed years ago. Escrow transactions, bonded contracts, third party arbitration, multi-party signature, etc. If Bitcoin catches on in a big way, these are things we'll want to explore in the future, but they all had to be designed at the beginning to make sure they would be possible later.",
+ "post_id": "126",
+ "date": "June 17, 2010"
+ },
+ {
+ "category": "encryption",
+ "medium": "bitcointalk",
+ "text": "SHA-256 is very strong. It's not like the incremental step from MD5 to SHA1. It can last several decades unless there's some massive breakthrough attack.",
+ "post_id": "119",
+ "date": "June 14, 2010"
+ },
+ {
+ "category": "encryption",
+ "medium": "bitcointalk",
+ "text": "If SHA-256 became completely broken, I think we could come to some agreement about what the honest block chain was before the trouble started, lock that in and continue from there with a new hash function.",
+ "post_id": "119",
+ "date": "June 14, 2010"
+ },
+ {
+ "category": "releases",
+ "medium": "bitcointalk",
+ "text": "Does anyone want to translate the Bitcoin client itself? It would be great to have at least one other language in the 0.3 release.",
+ "post_id": "111",
+ "date": "May 26, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Simplified Payment Verification is for lightweight client-only users who only do transactions and don't generate and don't participate in the node network. They wouldn't need to download blocks, just the hash chain, which is currently about 2MB and very quick to verify (less than a second to verify the whole chain). If the network becomes very large, like over 100,000 nodes, this is what we'll use to allow common users to do transactions without being full blown nodes. At that stage, most users should start running client-only software and only the specialist server farms keep running full network nodes, kind of like how the usenet network has consolidated. \nSPV is not implemented yet, and won't be implemented until far in the future, but all the current implementation is designed around supporting it.",
+ "post_id": "105",
+ "date": "May 18, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Bitcoin addresses you generate are kept forever. A bitcoin address must be kept to show ownership of anything sent to it. If you were able to delete a bitcoin address and someone sent to it, the money would be lost. They're only about 500 bytes.",
+ "post_id": "102",
+ "date": "May 16, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "When you generate a new bitcoin address, it only takes disk space on your own computer (like 500 bytes). It's like generating a new PGP private key, but less CPU intensive because it's ECC. The address space is effectively unlimited. It doesn't hurt anyone, so generate all you want.",
+ "post_id": "98",
+ "date": "May 16, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "The price of .com registrations is lower than it should be, therefore any good name you might think of is always already taken by some domain name speculator. Fortunately, it's standard for open source projects to be .org.",
+ "post_id": "94",
+ "date": "March 23, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "How does everyone feel about the B symbol with the two lines through the outside? Can we live with that as our logo?",
+ "post_id": "83",
+ "date": "February 26, 2010"
+ },
+ {
+ "category": "transactions",
+ "medium": "bitcointalk",
+ "text": "That would be nice at point-of-sale. The cash register displays a QR-code encoding a bitcoin address and amount on a screen and you photo it with your mobile.",
+ "post_id": "73",
+ "date": "February 24, 2010"
+ },
+ {
+ "category": "economics",
+ "medium": "bitcointalk",
+ "text": "A rational market price for something that is expected to increase in value will already reflect the present value of the expected future increases. In your head, you do a probability estimate balancing the odds that it keeps increasing.",
+ "post_id": "65",
+ "date": "February 21, 2010"
+ },
+ {
+ "category": "economics, bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "The price of any commodity tends to gravitate toward the production cost. If the price is below cost, then production slows down. If the price is above cost, profit can be made by generating and selling more. At the same time, the increased production would increase the difficulty, pushing the cost of generating towards the price.",
+ "post_id": "65",
+ "date": "February 21, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "At the moment, generation effort is rapidly increasing, suggesting people are estimating the present value to be higher than the current cost of production.",
+ "post_id": "65",
+ "date": "February 21, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "I'm sure that in 20 years there will either be very large transaction volume or no volume.",
+ "post_id": "57",
+ "date": "February 14, 2010"
+ },
+ {
+ "category": "bitcoin-economics, fees",
+ "medium": "bitcointalk",
+ "text": "In a few decades when the reward gets too small, the transaction fee will become the main compensation for nodes.",
+ "post_id": "57",
+ "date": "February 14, 2010"
+ },
+ {
+ "category": "nodes, mining, fees",
+ "medium": "bitcointalk",
+ "text": "If you're sad about paying the fee, you could always turn the tables and run a node yourself and maybe someday rake in a 0.44 fee yourself.",
+ "post_id": "56",
+ "date": "February 14, 2010"
+ },
+ {
+ "category": "bitcoin-economics, bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Eventually at most only 21 million coins for 6.8 billion people in the world if it really gets huge.\n\nBut don't worry, there are another 6 decimal places that aren't shown, for a total of 8 decimal places internally. It shows 1.00 but internally it's 1.00000000. If there's massive deflation in the future, the software could show more decimal places.",
+ "post_id": "46",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "If it gets tiresome working with small numbers, we could change where the display shows the decimal point. Same amount of money, just different convention for where the \",\"'s and \".\"'s go. e.g. moving the decimal place 3 places would mean if you had 1.00000 before, now it shows it as 1,000.00.",
+ "post_id": "46",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "privacy",
+ "medium": "bitcointalk",
+ "text": "Bitcoin is still very new and has not been independently analysed. If you're serious about privacy, TOR is an advisable precaution.",
+ "post_id": "45",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "privacy",
+ "medium": "bitcointalk",
+ "text": "You could use TOR if you don't want anyone to know you're even using Bitcoin.",
+ "post_id": "45",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "I very much wanted to find some way to include a short message, but the problem is, the whole world would be able to see the message. As much as you may keep reminding people that the message is completely non-private, it would be an accident waiting to happen.",
+ "post_id": "33",
+ "date": "January 28, 2010"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "The average total coins generated across the network per day stays the same. Faster machines just get a larger share than slower machines. If everyone bought faster machines, they wouldn't get more coins than before.",
+ "post_id": "20",
+ "date": "December 12, 2009"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "We should have a gentleman's agreement to postpone the GPU arms race as long as we can for the good of the network. It's much easer to get new users up to speed if they don't have to worry about GPU drivers and compatibility. It's nice how anyone with just a CPU can compete fairly equally right now.",
+ "post_id": "20",
+ "date": "December 12, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "Those coins can never be recovered, and the total circulation is less. Since the effective circulation is reduced, all the remaining coins are worth slightly more. It's the opposite of when a government prints money and the value of existing money goes down.",
+ "post_id": "17",
+ "date": "December 10, 2009"
+ },
+ {
+ "category": "trusted-third-parties",
+ "text": "Being open source means anyone can independently review the code. If it was closed source, nobody could verify the security. I think it's essential for a program of this nature to be open source.",
+ "medium": "bitcointalk",
+ "post_id": "17",
+ "date": "December 10, 2009"
+ },
+ {
+ "category": "privacy, transactions",
+ "medium": "bitcointalk",
+ "text": "For greater privacy, it's best to use bitcoin addresses only once.",
+ "post_id": "11",
+ "date": "November 25, 2009"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "Think of it as a cooperative effort to make a chain. When you add a link, you must first find the current end of the chain. If you were to locate the last link, then go off for an hour and forge your link, come back and link it to the link that was the end an hour ago, others may have added several links since then and they're not going to want to use your link that now branches off the middle.",
+ "post_id": "8",
+ "date": "November 22, 2009"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "p2pfoundation",
+ "text": "It is a global distributed database, with additions to the database by consent of the majority, based on a set of rules they follow: \n\n- Whenever someone finds proof-of-work to generate a block, they get some new coins\n- The proof-of-work difficulty is adjusted every two weeks to target an average of 6 blocks per hour (for the whole network)\n- The coins given per block is cut in half every 4 years",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "p2pfoundation",
+ "text": "You could say coins are issued by the majority. They are issued in a limited, predetermined amount.",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "p2pfoundation",
+ "text": "To Sepp's question, indeed there is nobody to act as central bank or federal reserve to adjust the money supply as the population of users grows. That would have required a trusted party to determine the value, because I don't know a way for software to know the real world value of things.",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "p2pfoundation",
+ "text": "In this sense, it's more typical of a precious metal. Instead of the supply changing to keep the value the same, the supply is predetermined and the value changes. As the number of users grows, the value per coin increases. It has the potential for a positive feedback loop; as users increase, the value goes up, which could attract more users to take advantage of the increasing value.",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "medium": "p2pfoundation",
+ "text": "A lot of people automatically dismiss e-currency as a lost cause because of all the companies that failed since the 1990's. I hope it's obvious it was only the centrally controlled nature of those systems that doomed them. I think this is the first time we're trying a decentralized, non-trust-based system.",
+ "post_id": "2",
+ "date": "February 15, 2009"
+ },
+ {
+ "category": "releases, bitcoin-design",
+ "medium": "p2pfoundation",
+ "text": "I've developed a new open source P2P e-cash system called Bitcoin. It's completely decentralized, with no central server or trusted parties, because everything is based on crypto proof instead of trust. Give it a try, or take a look at the screenshots and design paper: \n\nDownload Bitcoin v0.1 at http://www.bitcoin.org",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "economics",
+ "medium": "p2pfoundation",
+ "text": "The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "micropayments, privacy, banks",
+ "medium": "p2pfoundation",
+ "text": "Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "encryption",
+ "medium": "p2pfoundation",
+ "text": "A generation ago, multi-user time-sharing computer systems had a similar problem. Before strong encryption, users had to rely on password protection to secure their files, placing trust in the system administrator to keep their information private. Privacy could always be overridden by the admin based on his judgment call weighing the principle of privacy against other concerns, or at the behest of his superiors. Then strong encryption became available to the masses, and trust was no longer required. Data could be secured in a way that was physically impossible for others to access, no matter for what reason, no matter how good the excuse, no matter what.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "medium": "p2pfoundation",
+ "text": "With e-currency based on cryptographic proof, without the need to trust a third party middleman, money can be secure and transactions effortless.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "transactions",
+ "medium": "p2pfoundation",
+ "text": "A digital coin contains the public key of its owner. To transfer it, the owner signs the coin together with the public key of the next owner. Anyone can check the signatures to verify the chain of ownership.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "double-spending",
+ "medium": "p2pfoundation",
+ "text": "Any owner could try to re-spend an already spent coin by signing it again to another owner. The usual solution is for a trusted company with a central database to check for double-spending, but that just gets back to the trust model. In its central position, the company can override the users, and the fees needed to support the company make micropayments impractical. \nBitcoin's solution is to use a peer-to-peer network to check for double-spending. In a nutshell, the network works like a distributed timestamp server, stamping the first transaction to spend a coin. It takes advantage of the nature of information being easy to spread but hard to stifle.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "p2pfoundation",
+ "text": "The result is a distributed system with no single point of failure. Users hold the crypto keys to their own money and transact directly with each other, with the help of the P2P network to check for double-spending.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "identity",
+ "medium": "p2pfoundation",
+ "text": "I am not Dorian Nakamoto.",
+ "post_id": "4",
+ "date": "March 7, 2014"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "email",
+ "email_id": "1",
+ "text": "I've been working on a new electronic cash system that's fully peer-to-peer, with no trusted third party.",
+ "date": "November 1, 2008"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "email",
+ "email_id": "1",
+ "text": "The main properties: \n Double-spending is prevented with a peer-to-peer network.\n No mint or other trusted parties.\n Participants can be anonymous.\n New coins are made from Hashcash style proof-of-work.\n The proof-of-work for new coin generation also proof-of-workers the network to prevent double-spending.",
+ "date": "November 1, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "2",
+ "text": "Long before the network gets anywhere near as large as that, it would be safe for users to use Simplified Payment Verification (section 8) to check for double spending, which only requires having the chain of block headers, or about 12KB per day.",
+ "date": "November 2, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "2",
+ "text": "Only people trying to create new coins would need to run network nodes.",
+ "date": "November 2, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "2",
+ "text": "At first, most users would run network nodes, but as the network grows beyond a certain point, it would be left more and more to specialists with server farms of specialized hardware. A server farm would only need to have one node on the network and the rest of the LAN connects with that one node.",
+ "date": "November 2, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "The requirement is that the good guys collectively have more CPU proof-of-worker than any single attacker.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "There would be many smaller zombie farms that are not big enough to overproof-of-worker the network, and they could still make money by generating bitcoins. The smaller farms are then the \"honest nodes\". (I need a better term than \"honest\") The more smaller farms resort to generating bitcoins, the higher the bar gets to overproof-of-worker the network, making larger farms also too small to overproof-of-worker it so that they may as well generate bitcoins too. According to the \"long tail\" theory, the small, medium and merely large farms put together should add up to a lot more than the biggest zombie farm.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "Even if a bad guy does overproof-of-worker the network, it's not like he's instantly rich. All he can accomplish is to take back money he himself spent, like bouncing a check. To exploit it, he would have to buy something from a merchant, wait till it ships, then overproof-of-worker the network and try to take his money back. I don't think he could make as much money trying to pull a carding scheme like that as he could by generating bitcoins. With a zombie farm that big, he could generate more bitcoins than everyone else combined.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "The Bitcoin network might actually reduce spam by diverting zombie farms to generating bitcoins instead.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "motives",
+ "medium": "email",
+ "email_id": "4",
+ "text": "Yes, but we can win a major battle in the arms race and gain a new territory of freedom for several years.",
+ "date": "November 7, 2008"
+ },
+ {
+ "category": "p2p-networks, government",
+ "medium": "email",
+ "email_id": "4",
+ "text": "Governments are good at cutting off the heads of a centrally controlled networks like Napster, but pure P2P networks like Gnutella and Tor seem to be holding their own.",
+ "date": "November 7, 2008"
+ },
+ {
+ "category": "mining, difficulty",
+ "medium": "email",
+ "email_id": "5",
+ "text": "As computers get faster and the total computing proof-of-worker applied to creating bitcoins increases, the difficulty increases proportionally to keep the total new production constant. Thus, it is known in advance how many new bitcoins will be created every year in the future.",
+ "date": "November 8, 2008"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "email",
+ "email_id": "5",
+ "text": "The fact that new coins are produced means the money supply increases by a planned amount, but this does not necessarily result in inflation. If the supply of money increases at the same rate that the number of people using it increases, prices remain stable. If it does not increase as fast as demand, there will be deflation and early holders of money will see its value increase. Coins have to get initially distributed somehow, and a constant rate seems like the best formula.",
+ "date": "November 8, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "6",
+ "text": "Right, nodes keep transactions in their working set until they get into a block. If a transaction reaches 90% of nodes, then each time a new block is found, it has a 90% chance of being in it.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "6",
+ "text": "Receivers of transactions will normally need to hold transactions for perhaps an hour or more to allow time for this kind of possibility to be resolved. They can still re-spend the coins immediately, but they should wait before taking an action such as shipping goods.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "6",
+ "text": "The attacker isn't adding blocks to the end. He has to go back and redo the block his transaction is in and all the blocks after it, as well as any new blocks the network keeps adding to the end while he's doing that. He's rewriting history. Once his branch is longer, it becomes the new valid one.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes, mining, proof-of-work",
+ "medium": "email",
+ "email_id": "6",
+ "text": "It is strictly necessary that the longest chain is always considered the valid one. Nodes that were present may remember that one branch was there first and got replaced by another, but there would be no way for them to convince those who were not present of this. We can't have subfactions of nodes that cling to one branch that they think was first, others that saw another branch first, and others that joined later and never saw what happened. The CPU proof-of-worker proof-of-work vote must have the final say. The only way for everyone to stay on the same page is to believe that the longest chain is always the valid one, no matter what.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "6",
+ "text": "The recipient just needs to verify it back to a depth that is sufficiently far back in the block chain, which will often only require a depth of 2 transactions. All transactions before that can be discarded.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "6",
+ "text": "When a node receives a block, it checks the signatures of every transaction in it against previous transactions in blocks. Blocks can only contain transactions that depend on valid transactions in previous blocks or the same block. Transaction C could depend on transaction B in the same block and B depends on transaction A in an earlier block.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "7",
+ "text": "It's not a problem if transactions have to wait one or a few extra cycles to get into a block.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "8",
+ "text": "The proof-of-work chain is the solution to the synchronisation problem, and to knowing what the globally shared view is without having to trust anyone.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "8",
+ "text": "A transaction will quickly propagate throughout the network, so if two versions of the same transaction were reported at close to the same time, the one with the head start would have a big advantage in reaching many more nodes first. Nodes will only accept the first one they see, refusing the second one to arrive, so the earlier transaction would have many more nodes working on incorporating it into the next proof-of-work. In effect, each node votes for its viewpoint of which transaction it saw first by including it in its proof-of-work effort. If the transactions did come at exactly the same time and there was an even split, it's a toss up based on which gets into a proof-of-work first, and that decides which is valid.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes, proof-of-work",
+ "medium": "email",
+ "email_id": "8",
+ "text": "When a node finds a proof-of-work, the new block is propagated throughout the network and everyone adds it to the chain and starts working on the next block after it. Any nodes that had the other transaction will stop trying to include it in a block, since it's now invalid according to the accepted chain.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "8",
+ "text": "The proof-of-work chain is itself self-evident proof that it came from the globally shared view. Only the majority of the network together has enough CPU proof-of-worker to generate such a difficult chain of proof-of-work. Any user, upon receiving the proof-of-work chain, can see what the majority of the network has approved. Once a transaction is hashed into a link that's a few links back in the chain, it is firmly etched into the global history.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "fees, bitcoin-economics",
+ "medium": "email",
+ "email_id": "9",
+ "text": "If you're having trouble with the inflation issue, it's easy to tweak it for transaction fees instead. It's as simple as this: let the output value from any transaction be 1 cent less than the input value. Either the client software automatically writes transactions for 1 cent more than the intended payment value, or it could come out of the payee's side. The incentive value when a node finds a proof-of-work for a block could be the total of the fees in the block.",
+ "date": "November 10, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "10",
+ "text": "When there are multiple double-spent versions of the same transaction, one and only one will become valid.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "10",
+ "text": "The receiver of a payment must wait an hour or so before believing that it's valid. The network will resolve any possible double-spend races by then.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "10",
+ "text": "The guy who received the double-spend that became invalid never thought he had it in the first place. His software would have shown the transaction go from \"unconfirmed\" to \"invalid\". If necessary, the UI can be made to hide transactions until they're sufficiently deep in the block chain.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "difficulty",
+ "medium": "email",
+ "email_id": "10",
+ "text": "The target time between blocks will probably be 10 minutes. Every block includes its creation time. If the time is off by more than 36 hours, other nodes won't work on it. If the timespan over the last 6*24*30 blocks is less than 15 days, blocks are being generated too fast and the proof-of-work difficulty doubles. Everyone does the same calculation with the same chain data, so they all get the same result at the same link in the chain.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "10",
+ "text": "Instantant non-repudiability is not a feature, but it's still much faster than existing systems. Paper cheques can bounce up to a week or two later. Credit card transactions can be contested up to 60 to 180 days later. Bitcoin transactions can be sufficiently irreversible in an hour or two.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "10",
+ "text": "With the transaction fee based incentive system I recently posted, nodes would have an incentive to include all the paying transactions they receive.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "11",
+ "text": "The proof-of-work chain is a solution to the Byzantine Generals' Problem. I'll try to rephrase it in that context.\nA number of Byzantine Generals each have a computer and want to attack the King's wi-fi by brute forcing the password, which they've learned is a certain number of characters in length. Once they stimulate the network to generate a packet, they must crack the password within a limited time to break in and erase the logs, otherwise they will be discovered and get in trouble. They only have enough CPU proof-of-worker to crack it fast enough if a majority of them attack at the same time. \n They don't particularly care when the attack will be, just that they all agree. It has been decided that anyone who feels like it will announce a time, and whatever time is heard first will be the official attack time. The problem is that the network is not instantaneous, and if two generals announce different attack times at close to the same time, some may hear one first and others hear the other first. They use a proof-of-work chain to solve the problem. Once each general receives whatever attack time he hears first, he sets his computer to solve an extremely difficult proof-of-work problem that includes the attack time in its hash. The proof-of-work is so difficult, it's expected to take 10 minutes of them all working at once before one of them finds a solution. Once one of the generals finds a proof-of-work, he broadcasts it to the network, and everyone changes their current proof-of-work computation to include that proof-of-work in the hash they're working on. If anyone was working on a different attack time, they switch to this one, because its proof-of-work chain is now longer.\n After two hours, one attack time should be hashed by a chain of 12 proofs-of-work. Every general, just by verifying the difficulty of the proof-of-work chain, can estimate how much parallel CPU proof-of-worker per hour was expended on it and see that it must have required the majority of the computers to produce that much proof-of-work in the allotted time. They had to all have seen it because the proof-of-work is proof that they worked on it. If the CPU proof-of-worker exhibited by the proof-of-work chain is sufficient to crack the password, they can safely attack at the agreed time.\n The proof-of-work chain is how all the synchronisation, distributed database and global view problems you've asked about are solved.",
+ "date": "November 13, 2008"
+ },
+ {
+ "category": "nodes, mining",
+ "medium": "email",
+ "email_id": "12",
+ "text": "Broadcasts will probably be almost completely reliable. TCP transmissions are rarely ever dropped these days, and the broadcast protocol has a retry mechanism to get the data from other nodes after a while. If broadcasts turn out to be slower in practice than expected, the target time between blocks may have to be increased to avoid wasting resources. We want blocks to usually propagate in much less time than it takes to generate them, otherwise nodes would spend too much time working on obsolete blocks.",
+ "date": "November 14, 2008"
+ },
+ {
+ "category": "motives",
+ "medium": "email",
+ "email_id": "12",
+ "text": "It's very attractive to the libertarian viewpoint if we can explain it properly. I'm better with code than with words though.",
+ "date": "November 13, 2008"
+ },
+ {
+ "category": "releases",
+ "medium": "email",
+ "email_id": "13",
+ "text": "I'll try and hurry up and release the sourcecode as soon as possible to serve as a reference to help clear up all these implementation questions.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "13",
+ "text": "A basic transaction is just what you see in the figure in section 2. A signature (of the buyer) satisfying the public key of the previous transaction, and a new public key (of the seller) that must be satisfied to spend it the next time.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "13",
+ "text": "There's no need for reporting of \"proof of double spending\" like that. If the same chain contains both spends, then the block is invalid and rejected. \n Same if a block didn't have enough proof-of-work. That block is invalid and rejected. There's no need to circulate a report about it. Every node could see that and reject it before relaying it.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "13",
+ "text": "We're not \"on the lookout\" for double spends to sound the alarm and catch the cheater. We merely adjudicate which one of the spends is valid. Receivers of transactions must wait a few blocks to make sure that resolution has had time to complete. Would be cheaters can try and simultaneously double-spend all they want, and all they accomplish is that within a few blocks, one of the spends becomes valid and the others become invalid. Any later double-spends are immediately rejected once there's already a spend in the main chain.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "proof-of-work, mining",
+ "medium": "email",
+ "email_id": "13",
+ "text": "The proof-of-work is a Hashcash style SHA-256 collision finding. It's a memoryless process where you do millions of hashes a second, with a small chance of finding one each time. The 3 or 4 fastest nodes' dominance would only be proportional to their share of the total CPU proof-of-worker. Anyone's chance of finding a solution at any time is proportional to their CPU proof-of-worker.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "email",
+ "email_id": "13",
+ "text": "There will be transaction fees, so nodes will have an incentive to receive and include all the transactions they can. Nodes will eventually be compensated by transaction fees alone when the total coins created hits the pre-determined ceiling.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "14",
+ "text": "The credential that establishes someone as real is the ability to supply CPU proof-of-worker.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "14",
+ "text": "The race is to spread your transaction on the network first. Think 6 degrees of freedom -- it spreads exponentially. It would only take something like 2 minutes for a transaction to spread widely enough that a competitor starting late would have little chance of grabbing very many nodes before the first one is overtaking the whole network. During those 2 minutes, the merchant's nodes can be watching for a double-spent transaction. The double-spender would not be able to blast his alternate transaction out to the world without the merchant getting it, so he has to wait before starting. \n If the real transaction reaches 90% and the double-spent tx reaches 10%, the double-spender only gets a 10% chance of not paying, and 90% chance his money gets spent. For almost any type of goods, that's not going to be worth it for the scammer.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "14",
+ "text": "If a merchant actually has a problem with theft, they can make the customer wait 2 minutes, or wait for something in e-mail, which many already do. If they really want to optimize, and it's a large download, they could cancel the download in the middle if the transaction comes back double-spent. If it's website access, typically it wouldn't be a big deal to let the customer have access for 5 minutes and then cut off access if it's rejected. Many such sites have a free trial anyway.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "releases, bitcoin-design",
+ "medium": "email",
+ "email_id": "15",
+ "text": "I believe I've worked through all those little details over the last year and a half while coding it, and there were a lot of them. The functional details are not covered in the paper, but the sourcecode is coming soon. I sent you the main files. (available by request at the moment, full release soon)",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "releases",
+ "medium": "email",
+ "email_id": "16",
+ "text": "Announcing the first release of Bitcoin, a new electronic cash system that uses a peer-to-peer network to prevent double-spending. It's completely decentralized with no server or central authority.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "16",
+ "text": "If you can keep a node running that accepts incoming connections, you'll really be helping the network a lot. Port 8333 on your firewall needs to be open to receive incoming connections.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "16",
+ "text": "You can get coins by getting someone to send you some, or turn on Options->Generate Coins to run a node and generate blocks. I made the proof-of-work difficulty ridiculously easy to start with, so for a little while in the beginning a typical PC will be able to generate coins in just a few hours. It'll get a lot harder when competition makes the automatic adjustment drive up the difficulty. Generated coins must wait 120 blocks to mature before they can be spent.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "16",
+ "text": "There are two ways to send money. If the recipient is online, you can enter their IP address and it will connect, get a new public key and send the transaction with comments. If the recipient is not online, it is possible to send to their Bitcoin address, which is a hash of their public key that they give you. They'll receive the transaction the next time they connect and get the block it's in. This method has the disadvantage that no comment information is sent, and a bit of privacy may be lost if the address is used multiple times, but it is a useful alternative if both users can't be online at the same time or the recipient can't receive incoming connections.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "email",
+ "email_id": "16",
+ "text": "Total circulation will be 21,000,000 coins. It'll be distributed to network nodes when they make blocks, with the amount cut in half every 4 years.\n\nfirst 4 years: 10,500,000 coins\nnext 4 years: 5,250,000 coins\nnext 4 years: 2,625,000 coins\nnext 4 years: 1,312,500 coins\netc...\n\nWhen that runs out, the system can support transaction fees if needed. It's based on open market competition, and there will probably always be nodes willing to process transactions for free.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "medium": "email",
+ "email_id": "17",
+ "text": "I would be surprised if 10 years from now we're not using electronic currency in some way, now that we know a way to do it that won't inevitably get dumbed down when the trusted third party gets cold feet.",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "micropayments",
+ "medium": "email",
+ "email_id": "17",
+ "text": "It can already be used for pay-to-send e-mail. The send dialog is resizeable and you can enter as long of a message as you like. It's sent directly when it connects. The recipient doubleclicks on the transaction to see the full message. If someone famous is getting more e-mail than they can read, but would still like to have a way for fans to contact them, they could set up Bitcoin and give out the IP address on their website. \"Send X bitcoins to my priority hotline at this IP and I'll read the message personally.\"",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "micropayments",
+ "medium": "email",
+ "email_id": "17",
+ "text": "Subscription sites that need some extra proof-of-work for their free trial so it doesn't cannibalize subscriptions could charge bitcoins for the trial.",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "micropayments, bitcoin-economics",
+ "medium": "email",
+ "email_id": "17",
+ "text": "It might make sense just to get some in case it catches on. If enough people think the same way, that becomes a self fulfilling prophecy. Once it gets bootstrapped, there are so many applications if you could effortlessly pay a few cents to a website as easily as dropping coins in a vending machine.",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "text": "A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work, double-spending",
+ "text": "We propose a solution to the double-spending problem using a peer-to-peer network. The network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU proof-of-worker. As long as a majority of CPU proof-of-worker is controlled by nodes that are not cooperating to attack the network, they'll generate the longest chain and outpace attackers. The network itself requires minimal structure.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "trusted-third-parties",
+ "text": "Commerce on the Internet has come to rely almost exclusively on financial institutions serving as trusted third parties to process electronic payments. While the system works well enough for most transactions, it still suffers from the inherent weaknesses of the trust based model.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "trusted-third-parties",
+ "text": "Completely non-reversible transactions are not really possible, since financial institutions cannot avoid mediating disputes. The cost of mediation increases transaction costs, limiting the minimum practical transaction size and cutting off the possibility for small casual transactions, and there is a broader cost in the loss of ability to make non-reversible payments for non-reversible services. With the possibility of reversal, the need for trust spreads.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "trusted-third-parties, cryptocurrency",
+ "text": "What is needed is an electronic payment system based on cryptographic proof instead of trust, allowing any two willing parties to transact directly with each other without the need for a trusted third party. Transactions that are computationally impractical to reverse would protect sellers from fraud, and routine escrow mechanisms could easily be implemented to protect buyers.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "double-spending, proof-of-work",
+ "text": "In this paper, we propose a solution to the double-spending problem using a peer-to-peer distributed timestamp server to generate computational proof of the chronological order of transactions. The system is secure as long as honest nodes collectively control more CPU proof-of-worker than any cooperating group of attacker nodes.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "We define an electronic coin as a chain of digital signatures. Each owner transfers the coin to the next by digitally signing a hash of the previous transaction and the public key of the next owner and adding these to the end of the coin. A payee can verify the signatures to verify the chain of ownership.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "economics, double-spending",
+ "text": "The problem of course is the payee can't verify that one of the owners did not double-spend the coin. A common solution is to introduce a trusted central authority, or mint, that checks every transaction for double spending. After each transaction, the coin must be returned to the mint to issue a new coin, and only coins issued directly from the mint are trusted not to be double-spent. The problem with this solution is that the fate of the entire money system depends on the company running the mint, with every transaction having to go through them, just like a bank.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "nodes, cryptocurrency, transactions",
+ "text": "We need a way for the payee to know that the previous owners did not sign any earlier transactions. For our purposes, the earliest transaction is the one that counts, so we don't care about later attempts to double-spend. The only way to confirm the absence of a transaction is to be aware of all transactions. In the mint based model, the mint was aware of all transactions and decided which arrived first. To accomplish this without a trusted party, transactions must be publicly announced, and we need a system for participants to agree on a single history of the order in which they were received. The payee needs proof that at the time of each transaction, the majority of nodes agreed it was the first received.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "The solution we propose begins with a timestamp server. A timestamp server works by taking a hash of a block of items to be timestamped and widely publishing the hash, such as in a newspaper or Usenet post. The timestamp proves that the data must have existed at the time, obviously, in order to get into the hash. Each timestamp includes the previous timestamp in its hash, forming a chain, with each additional timestamp reinforcing the ones before it.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "text": "To implement a distributed timestamp server on a peer-to-peer basis, we will need to use a proof-of-work system similar to Adam Back's Hashcash, rather than newspaper or Usenet posts. The proof-of-work involves scanning for a value that when hashed, such as with SHA-256, the hash begins with a number of zero bits. The average work required is exponential in the number of zero bits required and can be verified by executing a single hash.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "text": "For our timestamp network, we implement the proof-of-work by incrementing a nonce in the block until a value is found that gives the block's hash the required zero bits. Once the CPU effort has been expended to make it satisfy the proof-of-work, the block cannot be changed without redoing the work. As later blocks are chained after it, the work to change the block would include redoing all the blocks after it.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "text": "The proof-of-work also solves the problem of determining representation in majority decision making. If the majority were based on one-IP-address-one-vote, it could be subverted by anyone able to allocate many IPs. Proof-of-work is essentially one-CPU-one-vote. The majority decision is represented by the longest chain, which has the greatest proof-of-work effort invested in it. If a majority of CPU proof-of-worker is controlled by honest nodes, the honest chain will grow the fastest and outpace any competing chains. To modify a past block, an attacker would have to redo the proof-of-work of the block and all blocks after it and then catch up with and surpass the work of the honest nodes. We will show later that the probability of a slower attacker catching up diminishes exponentially as subsequent blocks are added.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work, difficulty",
+ "text": "To compensate for increasing hardware speed and varying interest in running nodes over time, the proof-of-work difficulty is determined by a moving average targeting an average number of blocks per hour. If they're generated too fast, the difficulty increases.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design, nodes, proof-of-work",
+ "text": "The steps to run the network are as follows:\n\n1. New transactions are broadcast to all nodes.\n2. Each node collects new transactions into a block.\n3. Each node works on finding a difficult proof-of-work for its block.\n4. When a node finds a proof-of-work, it broadcasts the block to all nodes.\n5. Nodes accept the block only if all transactions in it are valid and not already spent.\n6. Nodes express their acceptance of the block by working on creating the next block in the chain, using the hash of the accepted block as the previous hash.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "nodes, proof-of-work",
+ "text": "Nodes always consider the longest chain to be the correct one and will keep working on extending it. If two nodes broadcast different versions of the next block simultaneously, some nodes may receive one or the other first. In that case, they work on the first one they received, but save the other branch in case it becomes longer. The tie will be broken when the next proof-of-work is found and one branch becomes longer; the nodes that were working on the other branch will then switch to the longer one.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "New transaction broadcasts do not necessarily need to reach all nodes. As long as they reach many nodes, they will get into a block before long. Block broadcasts are also tolerant of dropped messages. If a node does not receive a block, it will request it when it receives the next block and realizes it missed one.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "mining, bitcoin-economics",
+ "text": "By convention, the first transaction in a block is a special transaction that starts a new coin owned by the creator of the block. This adds an incentive for nodes to support the network, and provides a way to initially distribute coins into circulation, since there is no central authority to issue them. The steady addition of a constant of amount of new coins is analogous to gold miners expending resources to add gold to circulation. In our case, it is CPU time and electricity that is expended.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "fees, bitcoin-economics",
+ "text": "The incentive can also be funded with transaction fees. If the output value of a transaction is less than its input value, the difference is a transaction fee that is added to the incentive value of the block containing the transaction. Once a predetermined number of coins have entered circulation, the incentive can transition entirely to transaction fees and be completely inflation free.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "mining, bitcoin-economics",
+ "text": "The incentive may help encourage nodes to stay honest. If a greedy attacker is able to assemble more CPU proof-of-worker than all the honest nodes, he would have to choose between using it to defraud people by stealing back his payments, or using it to generate new coins. He ought to find it more profitable to play by the rules, such rules that favour him with more new coins than everyone else combined, than to undermine the system and the validity of his own wealth.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design",
+ "text": "Once the latest transaction in a coin is buried under enough blocks, the spent transactions before it can be discarded to save disk space. To facilitate this without breaking the block's hash, transactions are hashed in a Merkle Tree, with only the root included in the block's hash. Old blocks can then be compacted by stubbing off branches of the tree. The interior hashes do not need to be stored.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design",
+ "text": "A block header with no transactions would be about 80 bytes. If we suppose blocks are generated every 10 minutes, 80 bytes * 6 * 24 * 365 = 4.2MB per year. With computer systems typically selling with 2GB of RAM as of 2008, and Moore's Law predicting current growth of 1.2GB per year, storage should not be a problem even if the block headers must be kept in memory.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design, nodes",
+ "text": "It is possible to verify payments without running a full network node. A user only needs to keep a copy of the block headers of the longest proof-of-work chain, which he can get by querying network nodes until he's convinced he has the longest chain, and obtain the Merkle branch linking the transaction to the block it's timestamped in. He can't check the transaction for himself, but by linking it to a place in the chain, he can see that a network node has accepted it, and blocks added after it further confirm the network has accepted it. \nAs such, the verification is reliable as long as honest nodes control the network, but is more vulnerable if the network is overproof-of-workered by an attacker. While network nodes can verify transactions for themselves, the simplified method can be fooled by an attacker's fabricated transactions for as long as the attacker can continue to overproof-of-worker the network. One strategy to protect against this would be to accept alerts from network nodes when they detect an invalid block, prompting the user's software to download the full block and alerted transactions to confirm the inconsistency. Businesses that receive frequent payments will probably still want to run their own nodes for more independent security and quicker verification.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions, bitcoin-design",
+ "text": "Although it would be possible to handle coins individually, it would be unwieldy to make a separate transaction for every cent in a transfer. To allow value to be split and combined, transactions contain multiple inputs and outputs. Normally there will be either a single input from a larger previous transaction or multiple inputs combining smaller amounts, and at most two outputs: one for the payment, and one returning the change, if any, back to the sender.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "It should be noted that fan-out, where a transaction depends on several transactions, and those transactions depend on many more, is not a problem here. There is never the need to extract a complete standalone copy of a transaction's history.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions, privacy, trusted-third-parties",
+ "text": "The traditional banking model achieves a level of privacy by limiting access to information to the parties involved and the trusted third party. The necessity to announce all transactions publicly precludes this method, but privacy can still be maintained by breaking the flow of information in another place: by keeping public keys anonymous. The public can see that someone is sending an amount to someone else, but without information linking the transaction to anyone. This is similar to the level of information released by stock exchanges, where the time and size of individual trades, the \"tape\", is made public, but without telling who the parties were.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "addresses, privacy",
+ "text": "As an additional firewall, a new key pair should be used for each transaction to keep them from being linked to a common owner. Some linking is still unavoidable with multi-input transactions, which necessarily reveal that their inputs were owned by the same owner. The risk is that if the owner of a key is revealed, linking could reveal other transactions that belonged to the same owner.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "mining, proof-of-work",
+ "text": "We consider the scenario of an attacker trying to generate an alternate chain faster than the honest chain. Even if this is accomplished, it does not throw the system open to arbitrary changes, such as creating value out of thin air or taking money that never belonged to the attacker. Nodes are not going to accept an invalid transaction as payment, and honest nodes will never accept a block containing them. An attacker can only try to change one of his own transactions to take back money he recently spent.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design, trusted-third-parties",
+ "text": "We have proposed a system for electronic transactions without relying on trust. We started with the usual framework of coins made from digital signatures, which provides strong control of ownership, but is incomplete without a way to prevent double-spending. To solve this, we proposed a peer-to-peer network using proof-of-work to record a public history of transactions that quickly becomes computationally impractical for an attacker to change if honest nodes control a majority of CPU proof-of-worker.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "nodes, mining",
+ "text": "The network is robust in its unstructured simplicity. Nodes work all at once with little coordination. They do not need to be identified, since messages are not routed to any particular place and only need to be delivered on a best effort basis. Nodes can leave and rejoin the network at will, accepting the proof-of-work chain as proof of what happened while they were gone. They vote with their CPU proof-of-worker, expressing their acceptance of valid blocks by working on extending them and rejecting invalid blocks by refusing to work on them. Any needed rules and incentives can be enforced with this consensus mechanism.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ }
+ ]
\ No newline at end of file
From 01681c3af21591f4100918ed8648d4469b895c4d Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sun, 2 Oct 2022 10:20:05 +0100
Subject: [PATCH 050/844] x
---
lnbits/extensions/gerty/views_api.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 952142db..bcf4c427 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -258,8 +258,8 @@ async def get_exchange_rate(gerty):
try:
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
if amount:
- price = ('{0} {1}').format(format_number(amount), gerty.exchange)
- text.append(get_text_item_dict("Current BTC price", 15))
+ price = format_number(amount)
+ text.append(get_text_item_dict("Current {0}/BTC price".format(gerty.exchange), 15))
text.append(get_text_item_dict(price, 80))
except:
pass
@@ -360,7 +360,7 @@ def format_number(number):
def get_time_remaining(seconds, granularity=2):
intervals = (
- ('weeks', 604800), # 60 * 60 * 24 * 7
+ # ('weeks', 604800), # 60 * 60 * 24 * 7
('days', 86400), # 60 * 60 * 24
('hours', 3600), # 60 * 60
('minutes', 60),
From 1f660d669455f193ef0efc36d09596d476f92a17 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sun, 2 Oct 2022 11:25:07 +0100
Subject: [PATCH 051/844] Fix satoshi quotes bug
---
lnbits/extensions/gerty/views_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index bcf4c427..5845b1d5 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -91,7 +91,7 @@ async def api_gerty_delete(
async def api_gerty_satoshi():
with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
satoshiQuotes = json.load(fd)
- return satoshiQuotes[random.randint(0, 100)]
+ return satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)]
@gerty_ext.get("/api/v1/gerty/pieterwielliequote", status_code=HTTPStatus.OK)
From 6f09136f736f147369bc0a387f45f69542610b9e Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sun, 2 Oct 2022 16:05:26 +0100
Subject: [PATCH 052/844] Trimmed satoshi quotes down to one with 5 lines for
testing memory issues
---
lnbits/extensions/gerty/static/satoshi.json | 257 +-------------------
lnbits/extensions/gerty/views_api.py | 1 +
2 files changed, 4 insertions(+), 254 deletions(-)
diff --git a/lnbits/extensions/gerty/static/satoshi.json b/lnbits/extensions/gerty/static/satoshi.json
index b2869737..499f55a1 100644
--- a/lnbits/extensions/gerty/static/satoshi.json
+++ b/lnbits/extensions/gerty/static/satoshi.json
@@ -1,260 +1,9 @@
[
{
- "category": "general",
+ "category": "bitcoin-economics, bitcoin-design",
"medium": "bitcointalk",
- "text": "It would have been nice to get this attention in any other context. WikiLeaks has kicked the hornet's nest, and the swarm is headed towards us.",
- "post_id": "542",
- "date": "December 11, 2010"
- },
- {
- "category": "bitcoin-economics",
- "medium": "bitcointalk",
- "text": "Bitcoins have no dividend or potential future dividend, therefore not like a stock.\n\nMore like a collectible or commodity.",
- "post_id": "427",
- "date": "August 27, 2010"
- },
- {
- "category": "difficulty",
- "medium": "bitcointalk",
- "text": "Difficulty just increased by 4 times, so now your cost is US$0.02/BTC.",
- "post_id": "223",
- "date": "July 16, 2010"
- },
- {
- "category": "nodes",
- "medium": "bitcointalk",
- "text": "At equilibrium size, many nodes will be server farms with one or two network nodes that feed the rest of the farm over a LAN.",
- "post_id": "188",
- "date": "July 14, 2010"
- },
- {
- "category": "general",
- "medium": "bitcointalk",
- "text": "Writing a description for this thing for general audiences is bloody hard. There's nothing to relate it to.",
- "post_id": "167",
- "date": "July 5, 2010"
- },
- {
- "category": "bitcoin-economics",
- "medium": "bitcointalk",
- "text": "Lost coins only make everyone else's coins worth slightly more. Think of it as a donation to everyone.",
- "post_id": "131",
- "date": "June 21, 2010"
- },
- {
- "category": "encryption",
- "medium": "bitcointalk",
- "text": "SHA-256 is very strong. It's not like the incremental step from MD5 to SHA1. It can last several decades unless there's some massive breakthrough attack.",
- "post_id": "119",
- "date": "June 14, 2010"
- },
- {
- "category": "releases",
- "medium": "bitcointalk",
- "text": "Does anyone want to translate the Bitcoin client itself? It would be great to have at least one other language in the 0.3 release.",
- "post_id": "111",
- "date": "May 26, 2010"
- },
- {
- "category": "bitcoin-design",
- "medium": "bitcointalk",
- "text": "How does everyone feel about the B symbol with the two lines through the outside? Can we live with that as our logo?",
- "post_id": "83",
- "date": "February 26, 2010"
- },
- {
- "category": "transactions",
- "medium": "bitcointalk",
- "text": "That would be nice at point-of-sale. The cash register displays a QR-code encoding a bitcoin address and amount on a screen and you photo it with your mobile.",
- "post_id": "73",
- "date": "February 24, 2010"
- },
- {
- "category": "bitcoin-economics",
- "medium": "bitcointalk",
- "text": "At the moment, generation effort is rapidly increasing, suggesting people are estimating the present value to be higher than the current cost of production.",
- "post_id": "65",
- "date": "February 21, 2010"
- },
- {
- "category": "bitcoin-economics",
- "medium": "bitcointalk",
- "text": "I'm sure that in 20 years there will either be very large transaction volume or no volume.",
- "post_id": "57",
- "date": "February 14, 2010"
- },
- {
- "category": "bitcoin-economics, fees",
- "medium": "bitcointalk",
- "text": "In a few decades when the reward gets too small, the transaction fee will become the main compensation for nodes.",
- "post_id": "57",
- "date": "February 14, 2010"
- },
- {
- "category": "nodes, mining, fees",
- "medium": "bitcointalk",
- "text": "If you're sad about paying the fee, you could always turn the tables and run a node yourself and maybe someday rake in a 0.44 fee yourself.",
- "post_id": "56",
- "date": "February 14, 2010"
- },
- {
- "category": "privacy",
- "medium": "bitcointalk",
- "text": "Bitcoin is still very new and has not been independently analysed. If you're serious about privacy, TOR is an advisable precaution.",
- "post_id": "45",
+ "text": "Eventually at most only 21 million coins for 6.8 billion people in the world if it really gets huge.\n\nBut don't worry, there are another 6 decimal places that aren't shown, for a total of 8 decimal places internally. It shows 1.00 but internally it's 1.00000000. If there's massive deflation in the future, the software could show more decimal places.",
+ "post_id": "46",
"date": "February 6, 2010"
- },
- {
- "category": "privacy",
- "medium": "bitcointalk",
- "text": "You could use TOR if you don't want anyone to know you're even using Bitcoin.",
- "post_id": "45",
- "date": "February 6, 2010"
- },
- {
- "category": "privacy, transactions",
- "medium": "bitcointalk",
- "text": "For greater privacy, it's best to use bitcoin addresses only once.",
- "post_id": "11",
- "date": "November 25, 2009"
- },
- {
- "category": "bitcoin-economics",
- "medium": "p2pfoundation",
- "text": "You could say coins are issued by the majority. They are issued in a limited, predetermined amount.",
- "post_id": "3",
- "date": "February 18, 2009"
- },
- {
- "category": "cryptocurrency",
- "medium": "p2pfoundation",
- "text": "With e-currency based on cryptographic proof, without the need to trust a third party middleman, money can be secure and transactions effortless.",
- "post_id": "1",
- "date": "February 11, 2009"
- },
- {
- "category": "identity",
- "medium": "p2pfoundation",
- "text": "I am not Dorian Nakamoto.",
- "post_id": "4",
- "date": "March 7, 2014"
- },
- {
- "category": "bitcoin-design",
- "medium": "email",
- "email_id": "1",
- "text": "I've been working on a new electronic cash system that's fully peer-to-peer, with no trusted third party.",
- "date": "November 1, 2008"
- },
- {
- "category": "nodes",
- "medium": "email",
- "email_id": "2",
- "text": "Only people trying to create new coins would need to run network nodes.",
- "date": "November 2, 2008"
- },
- {
- "category": "mining",
- "medium": "email",
- "email_id": "3",
- "text": "The requirement is that the good guys collectively have more CPU proof-of-worker than any single attacker.",
- "date": "November 3, 2008"
- },
- {
- "category": "mining",
- "medium": "email",
- "email_id": "3",
- "text": "The Bitcoin network might actually reduce spam by diverting zombie farms to generating bitcoins instead.",
- "date": "November 3, 2008"
- },
- {
- "category": "motives",
- "medium": "email",
- "email_id": "4",
- "text": "Yes, but we can win a major battle in the arms race and gain a new territory of freedom for several years.",
- "date": "November 7, 2008"
- },
- {
- "category": "p2p-networks, government",
- "medium": "email",
- "email_id": "4",
- "text": "Governments are good at cutting off the heads of a centrally controlled networks like Napster, but pure P2P networks like Gnutella and Tor seem to be holding their own.",
- "date": "November 7, 2008"
- },
- {
- "category": "transactions",
- "medium": "email",
- "email_id": "7",
- "text": "It's not a problem if transactions have to wait one or a few extra cycles to get into a block.",
- "date": "November 9, 2008"
- },
- {
- "category": "proof-of-work",
- "medium": "email",
- "email_id": "8",
- "text": "The proof-of-work chain is the solution to the synchronisation problem, and to knowing what the globally shared view is without having to trust anyone.",
- "date": "November 9, 2008"
- },
- {
- "category": "double-spending",
- "medium": "email",
- "email_id": "10",
- "text": "When there are multiple double-spent versions of the same transaction, one and only one will become valid.",
- "date": "November 11, 2008"
- },
- {
- "category": "double-spending",
- "medium": "email",
- "email_id": "10",
- "text": "The receiver of a payment must wait an hour or so before believing that it's valid. The network will resolve any possible double-spend races by then.",
- "date": "November 11, 2008"
- },
- {
- "category": "nodes",
- "medium": "email",
- "email_id": "10",
- "text": "With the transaction fee based incentive system I recently posted, nodes would have an incentive to include all the paying transactions they receive.",
- "date": "November 11, 2008"
- },
- {
- "category": "motives",
- "medium": "email",
- "email_id": "12",
- "text": "It's very attractive to the libertarian viewpoint if we can explain it properly. I'm better with code than with words though.",
- "date": "November 13, 2008"
- },
- {
- "category": "releases",
- "medium": "email",
- "email_id": "13",
- "text": "I'll try and hurry up and release the sourcecode as soon as possible to serve as a reference to help clear up all these implementation questions.",
- "date": "November 17, 2008"
- },
- {
- "category": "proof-of-work",
- "medium": "email",
- "email_id": "14",
- "text": "The credential that establishes someone as real is the ability to supply CPU proof-of-worker.",
- "date": "November 17, 2008"
- },
- {
- "category": "nodes",
- "medium": "email",
- "email_id": "16",
- "text": "If you can keep a node running that accepts incoming connections, you'll really be helping the network a lot. Port 8333 on your firewall needs to be open to receive incoming connections.",
- "date": "January 9, 2009"
- },
- {
- "category": "micropayments",
- "medium": "email",
- "email_id": "17",
- "text": "Subscription sites that need some extra proof-of-work for their free trial so it doesn't cannibalize subscriptions could charge bitcoins for the trial.",
- "date": "January 17, 2009"
- },
- {
- "category": "cryptocurrency",
- "text": "A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution.",
- "medium": "whitepaper",
- "date": "October 31, 2008"
}
]
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 5845b1d5..d003e7bd 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -286,6 +286,7 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
word_list = wrapper.wrap(text=text)
multilineText = '\n'.join(word_list)
+ logger.debug("number of lines = {0}".format(len(word_list)))
# logger.debug('multilineText')
# logger.debug(multilineText)
From 2e5a7b6a8654a83b089ef58b54b84eeb4b18af12 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sun, 2 Oct 2022 16:17:16 +0100
Subject: [PATCH 053/844] Reinstated all sat quotes and added temp max char
limit in views_api for testing
---
lnbits/extensions/gerty/static/satoshi.json | 1090 +++++++++++++++++++
lnbits/extensions/gerty/views_api.py | 13 +-
2 files changed, 1100 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/gerty/static/satoshi.json b/lnbits/extensions/gerty/static/satoshi.json
index 499f55a1..1cff822a 100644
--- a/lnbits/extensions/gerty/static/satoshi.json
+++ b/lnbits/extensions/gerty/static/satoshi.json
@@ -1,9 +1,1099 @@
[
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "It would have been nice to get this attention in any other context. WikiLeaks has kicked the hornet's nest, and the swarm is headed towards us.",
+ "post_id": "542",
+ "date": "December 11, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "The project needs to grow gradually so the software can be strengthened along the way. I make this appeal to WikiLeaks not to try to use Bitcoin. Bitcoin is a small beta community in its infancy.",
+ "post_id": "523",
+ "date": "December 5, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "I'm happy if someone with artistic skill wants to contribute alternatives. The icon/logo was meant to be good as an icon at the 16x16 and 20x20 pixel sizes. I think it's the best program icon, but there's room for improvement at larger sizes for a graphic for use on websites. It'll be a lot simpler if authors could make their graphics public domain.",
+ "post_id": "500",
+ "date": "November 13, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "I wish rather than deleting the article, they put a length restriction. If something is not famous enough, there could at least be a stub article identifying what it is. I often come across annoying red links of things that Wiki ought to at least have heard of. \nThe article could be as simple as something like: \"Bitcoin is a peer-to-peer decentralised /link/electronic currency/link/.\" \nThe more standard Wiki thing to do is that we should have a paragraph in one of the more general categories that we are an instance of, like Electronic Currency or Electronic Cash. We can probably establish a paragraph there. Again, keep it short. Just identifying what it is.",
+ "post_id": "467",
+ "date": "September 30, 2010"
+ },
+ {
+ "category": "transactions",
+ "medium": "bitcointalk",
+ "text": "As you figured out, the root problem is we shouldn't be counting or spending transactions until they have at least 1 confirmation. 0/unconfirmed transactions are very much second class citizens. At most, they are advice that something has been received, but counting them as balance or spending them is premature.",
+ "post_id": "464",
+ "date": "September 30, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Bitcoin would be convenient for people who don't have a credit card or don't want to use the cards they have, either don't want the spouse to see it on the bill or don't trust giving their number to \"porn guys\", or afraid of recurring billing.",
+ "post_id": "460",
+ "date": "September 23, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "I don't know anything about any of the bug trackers. If we were to have one, we would have to make a thoroughly researched choice. We're managing pretty well just using the forum. I'm more likely to see bugs posted in the forum, and I think other users are much more likely to help resolve and ask follow up questions here than if they were in a bug tracker. A key step is other users helping resolve the simple stuff that's not really a bug but some misunderstanding or confusion. I keep a list of all unresolved bugs I've seen on the forum. In some cases, I'm still thinking about the best design for the fix. This isn't the kind of software where we can leave so many unresolved bugs that we need a tracker for them.",
+ "post_id": "454",
+ "date": "September 19, 2010"
+ },
+ {
+ "category": "scalability",
+ "medium": "bitcointalk",
+ "text": "The threshold can easily be changed in the future. We can decide to increase it when the time comes. It's a good idea to keep it lower as a circuit breaker and increase it as needed. If we hit the threshold now, it would almost certainly be some kind of flood and not actual use. Keeping the threshold lower would help limit the amount of wasted disk space in that event.",
+ "post_id": "441",
+ "date": "September 8, 2010"
+ },
+ {
+ "category": "fees",
+ "medium": "bitcointalk",
+ "text": "Currently, paying a fee is controlled manually with the -paytxfee switch. It would be very easy to make the software automatically check the size of recent blocks to see if it should pay a fee. We're so far from reaching the threshold, we don't need that yet. It's a good idea to see how things go with controlling it manually first anyway.",
+ "post_id": "441",
+ "date": "September 8, 2010"
+ },
+ {
+ "category": "fees, nodes",
+ "medium": "bitcointalk",
+ "text": "Another option is to reduce the number of free transactions allowed per block before transaction fees are required. Nodes only take so many KB of free transactions per block before they start requiring at least 0.01 transaction fee. The threshold should probably be lower than it currently is. I don't think the threshold should ever be 0. We should always allow at least some free transactions.",
+ "post_id": "439",
+ "date": "September 7, 2010"
+ },
+ {
+ "category": "economics",
+ "medium": "bitcointalk",
+ "text": "As a thought experiment, imagine there was a base metal as scarce as gold but with the following properties:\n- boring grey in colour\n- not a good conductor of electricity\n- not particularly strong, but not ductile or easily malleable either\n- not useful for any practical or ornamental purpose\n\nand one special, magical property:\n- can be transported over a communications channel\n\nIf it somehow acquired any value at all for whatever reason, then anyone wanting to transfer wealth over a long distance could buy some, transmit it, and have the recipient sell it.\n\nMaybe it could get an initial value circularly as you've suggested, by people foreseeing its potential usefulness for exchange. (I would definitely want some) Maybe collectors, any random reason could spark it.\n\nI think the traditional qualifications for money were written with the assumption that there are so many competing objects in the world that are scarce, an object with the automatic bootstrap of intrinsic value will surely win out over those without intrinsic value. But if there were nothing in the world with intrinsic value that could be used as money, only scarce but no intrinsic value, I think people would still take up something.\n\n(I'm using the word scarce here to only mean limited potential supply)",
+ "post_id": "428",
+ "date": "August 27, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "Bitcoins have no dividend or potential future dividend, therefore not like a stock.\n\nMore like a collectible or commodity.",
+ "post_id": "427",
+ "date": "August 27, 2010"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "bitcointalk",
+ "text": "There is no way for the software to automatically know if one chain is better than another except by the greatest proof-of-work. In the design it was necessary for it to switch to a longer chain no matter how far back it has to go.",
+ "post_id": "394",
+ "date": "August 16, 2010"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "Some places where generation will gravitate to: \n1) places where it's cheapest or free\n2) people who want to help for idealogical reasons\n3) people who want to get some coins without the inconvenience of doing a transaction to buy them\n\nThere are legitimate places where it's free. Generation is basically free anywhere that has electric heat, since your computer's heat is offsetting your baseboard electric heating. Many small flats have electric heat out of convenience.",
+ "post_id": "364",
+ "date": "August 15, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Then you must also be against the common system of payment up front, where the customer loses.\nPayment up front: customer loses, and the thief gets the money.\nSimple escrow: customer loses, but the thief doesn't get the money either.\nAre you guys saying payment up front is better, because at least the thief gets the money, so at least someone gets it?\nImagine someone stole something from you. You can't get it back, but if you could, if it had a kill switch that could be remote triggered, would you do it? Would it be a good thing for thieves to know that everything you own has a kill switch and if they steal it, it'll be useless to them, although you still lose it too? If they give it back, you can re-activate it.\nImagine if gold turned to lead when stolen. If the thief gives it back, it turns to gold again.\nIt still seems to me the problem may be one of presenting it the right way. For one thing, not being so blunt about \"money burning\" for the purposes of game theory discussion. The money is never truly burned. You have the option to release it at any time forever.",
+ "post_id": "340",
+ "date": "August 11, 2010"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "The heat from your computer is not wasted if you need to heat your home. If you're using electric heat where you live, then your computer's heat isn't a waste. It's equal cost if you generate the heat with your computer. \nIf you have other cheaper heating than electric, then the waste is only the difference in cost.\nIf it's summer and you're using A/C, then it's twice. \nBitcoin generation should end up where it's cheapest. Maybe that will be in cold climates where there's electric heat, where it would be essentially free.",
+ "post_id": "337",
+ "date": "August 9, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "It's the same situation as gold and gold mining. The marginal cost of gold mining tends to stay near the price of gold. Gold mining is a waste, but that waste is far less than the utility of having gold available as a medium of exchange. \nI think the case will be the same for Bitcoin. The utility of the exchanges made possible by Bitcoin will far exceed the cost of electricity used. Therefore, not having Bitcoin would be the net waste.",
+ "post_id": "327",
+ "date": "August 7, 2010"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "bitcointalk",
+ "text": "Proof-of-work has the nice property that it can be relayed through untrusted middlemen. We don't have to worry about a chain of custody of communication. It doesn't matter who tells you a longest chain, the proof-of-work speaks for itself.",
+ "post_id": "327",
+ "date": "August 7, 2010"
+ },
+ {
+ "category": "micropayments",
+ "medium": "bitcointalk",
+ "text": "Forgot to add the good part about micropayments. While I don't think Bitcoin is practical for smaller micropayments right now, it will eventually be as storage and bandwidth costs continue to fall. If Bitcoin catches on on a big scale, it may already be the case by that time. Another way they can become more practical is if I implement client-only mode and the number of network nodes consolidates into a smaller number of professional server farms. Whatever size micropayments you need will eventually be practical. I think in 5 or 10 years, the bandwidth and storage will seem trivial.",
+ "post_id": "318",
+ "date": "August 5, 2010"
+ },
+ {
+ "category": "micropayments",
+ "medium": "bitcointalk",
+ "text": "Bitcoin isn't currently practical for very small micropayments. Not for things like pay per search or per page view without an aggregating mechanism, not things needing to pay less than 0.01. The dust spam limit is a first try at intentionally trying to prevent overly small micropayments like that. \nBitcoin is practical for smaller transactions than are practical with existing payment methods. Small enough to include what you might call the top of the micropayment range. But it doesn't claim to be practical for arbitrarily small micropayments.",
+ "post_id": "317",
+ "date": "August 4, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Actually, it works well to just PM me. I'm the one who's going to be fixing it. If you find a security flaw, I would definitely like to hear from you privately to fix it before it goes public.",
+ "post_id": "294",
+ "date": "July 29, 2010"
+ },
+ {
+ "category": "nodes",
+ "medium": "bitcointalk",
+ "text": "The current system where every user is a network node is not the intended configuration for large scale. That would be like every Usenet user runs their own NNTP server. The design supports letting users just be users. The more burden it is to run a node, the fewer nodes there will be. Those few nodes will be big server farms. The rest will be client nodes that only do transactions and don't generate.",
+ "post_id": "287",
+ "date": "July 29, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "For future reference, here's my public key. It's the same one that's been there since the bitcoin.org site first went up in 2008. Grab it now in case you need it later. http://www.bitcoin.org/Satoshi_Nakamoto.asc",
+ "post_id": "276",
+ "date": "July 25, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "By making some adjustments to the database settings, I was able to make the initial block download about 5 times faster. It downloads in about 30 minutes. \n \nThe database default had it writing each block to disk synchronously, which is not necessary. I changed the settings to let it cache the changes in memory and write them out in a batch. Blocks are still written transactionally, so either the complete change occurs or none of it does, in either case the data is left in a valid state. \n \nI only enabled this change during the initial block download. When you come within 2000 blocks of the latest block, these changes turn off and it slows down to the old way.",
+ "post_id": "258",
+ "date": "July 23, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "The timing is strange, just as we are getting a rapid increase in 3rd party coverage after getting slashdotted. I hope there's not a big hurry to wrap the discussion and decide. How long does Wikipedia typically leave a question like that open for comment? \nIt would help to condense the article and make it less promotional sounding as soon as possible. Just letting people know what it is, where it fits into the electronic money space, not trying to convince them that it's good. They probably want something that just generally identifies what it is, not tries to explain all about how it works.",
+ "post_id": "249",
+ "date": "July 10, 2010"
+ },
+ {
+ "category": "difficulty",
+ "medium": "bitcointalk",
+ "text": "Right, the difficulty adjustment is trying to keep it so the network as a whole generates an average of 6 blocks per hour. The time for your block to mature will always be around 20 hours.",
+ "post_id": "225",
+ "date": "July 16, 2010"
+ },
+ {
+ "category": "difficulty",
+ "medium": "bitcointalk",
+ "text": "Difficulty just increased by 4 times, so now your cost is US$0.02/BTC.",
+ "post_id": "223",
+ "date": "July 16, 2010"
+ },
+ {
+ "category": "scalability, nodes",
+ "medium": "bitcointalk",
+ "text": "The design outlines a lightweight client that does not need the full block chain. In the design PDF it's called Simplified Payment Verification. The lightweight client can send and receive transactions, it just can't generate blocks. It does not need to trust a node to verify payments, it can still verify them itself. \nThe lightweight client is not implemented yet, but the plan is to implement it when it's needed. For now, everyone just runs a full network node.",
+ "post_id": "188",
+ "date": "July 14, 2010"
+ },
+ {
+ "category": "scalability, nodes",
+ "medium": "bitcointalk",
+ "text": "I anticipate there will never be more than 100K nodes, probably less. It will reach an equilibrium where it's not worth it for more nodes to join in. The rest will be lightweight clients, which could be millions.",
+ "post_id": "188",
+ "date": "July 14, 2010"
+ },
+ {
+ "category": "nodes",
+ "medium": "bitcointalk",
+ "text": "At equilibrium size, many nodes will be server farms with one or two network nodes that feed the rest of the farm over a LAN.",
+ "post_id": "188",
+ "date": "July 14, 2010"
+ },
+ {
+ "category": "economics",
+ "medium": "bitcointalk",
+ "text": "When someone tries to buy all the world's supply of a scarce asset, the more they buy the higher the price goes. At some point, it gets too expensive for them to buy any more. It's great for the people who owned it beforehand because they get to sell it to the corner at crazy high prices. As the price keeps going up and up, some people keep holding out for yet higher prices and refuse to sell.",
+ "post_id": "174",
+ "date": "July 9, 2010"
+ },
+ {
+ "category": "releases",
+ "medium": "bitcointalk",
+ "text": "Announcing version 0.3 of Bitcoin, the P2P cryptocurrency! Bitcoin is a digital currency using cryptography and a distributed network to replace the need for a trusted central server. Escape the arbitrary inflation risk of centrally managed currencies! Bitcoin's total circulation is limited to 21 million coins. The coins are gradually released to the network's nodes based on the CPU proof-of-worker they contribute, so you can get a share of them by contributing your idle CPU time.",
+ "post_id": "168",
+ "date": "July 6, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Writing a description for this thing for general audiences is bloody hard. There's nothing to relate it to.",
+ "post_id": "167",
+ "date": "July 5, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "Lost coins only make everyone else's coins worth slightly more. Think of it as a donation to everyone.",
+ "post_id": "131",
+ "date": "June 21, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "Excellent choice of a first project, nice work. I had planned to do this exact thing if someone else didn't do it, so when it gets too hard for mortals to generate 50BTC, new users could get some coins to play with right away. Donations should be able to keep it filled. The display showing the balance in the dispenser encourages people to top it up.\n\nYou should put a donation bitcoin address on the page for those who want to add funds to it, which ideally should update to a new address whenever it receives something.",
+ "post_id": "129",
+ "date": "June 18, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Since 2007. At some point I became convinced there was a way to do this without any trust required at all and couldn't resist to keep thinking about it. Much more of the work was designing than coding.\n\nFortunately, so far all the issues raised have been things I previously considered and planned for.",
+ "post_id": "127",
+ "date": "June 18, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "The nature of Bitcoin is such that once version 0.1 was released, the core design was set in stone for the rest of its lifetime. Because of that, I wanted to design it to support every possible transaction type I could think of. The problem was, each thing required special support code and data fields whether it was used or not, and only covered one special case at a time. It would have been an explosion of special cases. The solution was script, which generalizes the problem so transacting parties can describe their transaction as a predicate that the node network evaluates. The nodes only need to understand the transaction to the extent of evaluating whether the sender's conditions are met.",
+ "post_id": "126",
+ "date": "June 17, 2010"
+ },
+ {
+ "category": "transactions, bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "The design supports a tremendous variety of possible transaction types that I designed years ago. Escrow transactions, bonded contracts, third party arbitration, multi-party signature, etc. If Bitcoin catches on in a big way, these are things we'll want to explore in the future, but they all had to be designed at the beginning to make sure they would be possible later.",
+ "post_id": "126",
+ "date": "June 17, 2010"
+ },
+ {
+ "category": "encryption",
+ "medium": "bitcointalk",
+ "text": "SHA-256 is very strong. It's not like the incremental step from MD5 to SHA1. It can last several decades unless there's some massive breakthrough attack.",
+ "post_id": "119",
+ "date": "June 14, 2010"
+ },
+ {
+ "category": "encryption",
+ "medium": "bitcointalk",
+ "text": "If SHA-256 became completely broken, I think we could come to some agreement about what the honest block chain was before the trouble started, lock that in and continue from there with a new hash function.",
+ "post_id": "119",
+ "date": "June 14, 2010"
+ },
+ {
+ "category": "releases",
+ "medium": "bitcointalk",
+ "text": "Does anyone want to translate the Bitcoin client itself? It would be great to have at least one other language in the 0.3 release.",
+ "post_id": "111",
+ "date": "May 26, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Simplified Payment Verification is for lightweight client-only users who only do transactions and don't generate and don't participate in the node network. They wouldn't need to download blocks, just the hash chain, which is currently about 2MB and very quick to verify (less than a second to verify the whole chain). If the network becomes very large, like over 100,000 nodes, this is what we'll use to allow common users to do transactions without being full blown nodes. At that stage, most users should start running client-only software and only the specialist server farms keep running full network nodes, kind of like how the usenet network has consolidated. \nSPV is not implemented yet, and won't be implemented until far in the future, but all the current implementation is designed around supporting it.",
+ "post_id": "105",
+ "date": "May 18, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "Bitcoin addresses you generate are kept forever. A bitcoin address must be kept to show ownership of anything sent to it. If you were able to delete a bitcoin address and someone sent to it, the money would be lost. They're only about 500 bytes.",
+ "post_id": "102",
+ "date": "May 16, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "When you generate a new bitcoin address, it only takes disk space on your own computer (like 500 bytes). It's like generating a new PGP private key, but less CPU intensive because it's ECC. The address space is effectively unlimited. It doesn't hurt anyone, so generate all you want.",
+ "post_id": "98",
+ "date": "May 16, 2010"
+ },
+ {
+ "category": "general",
+ "medium": "bitcointalk",
+ "text": "The price of .com registrations is lower than it should be, therefore any good name you might think of is always already taken by some domain name speculator. Fortunately, it's standard for open source projects to be .org.",
+ "post_id": "94",
+ "date": "March 23, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "How does everyone feel about the B symbol with the two lines through the outside? Can we live with that as our logo?",
+ "post_id": "83",
+ "date": "February 26, 2010"
+ },
+ {
+ "category": "transactions",
+ "medium": "bitcointalk",
+ "text": "That would be nice at point-of-sale. The cash register displays a QR-code encoding a bitcoin address and amount on a screen and you photo it with your mobile.",
+ "post_id": "73",
+ "date": "February 24, 2010"
+ },
+ {
+ "category": "economics",
+ "medium": "bitcointalk",
+ "text": "A rational market price for something that is expected to increase in value will already reflect the present value of the expected future increases. In your head, you do a probability estimate balancing the odds that it keeps increasing.",
+ "post_id": "65",
+ "date": "February 21, 2010"
+ },
+ {
+ "category": "economics, bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "The price of any commodity tends to gravitate toward the production cost. If the price is below cost, then production slows down. If the price is above cost, profit can be made by generating and selling more. At the same time, the increased production would increase the difficulty, pushing the cost of generating towards the price.",
+ "post_id": "65",
+ "date": "February 21, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "At the moment, generation effort is rapidly increasing, suggesting people are estimating the present value to be higher than the current cost of production.",
+ "post_id": "65",
+ "date": "February 21, 2010"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "I'm sure that in 20 years there will either be very large transaction volume or no volume.",
+ "post_id": "57",
+ "date": "February 14, 2010"
+ },
+ {
+ "category": "bitcoin-economics, fees",
+ "medium": "bitcointalk",
+ "text": "In a few decades when the reward gets too small, the transaction fee will become the main compensation for nodes.",
+ "post_id": "57",
+ "date": "February 14, 2010"
+ },
+ {
+ "category": "nodes, mining, fees",
+ "medium": "bitcointalk",
+ "text": "If you're sad about paying the fee, you could always turn the tables and run a node yourself and maybe someday rake in a 0.44 fee yourself.",
+ "post_id": "56",
+ "date": "February 14, 2010"
+ },
{
"category": "bitcoin-economics, bitcoin-design",
"medium": "bitcointalk",
"text": "Eventually at most only 21 million coins for 6.8 billion people in the world if it really gets huge.\n\nBut don't worry, there are another 6 decimal places that aren't shown, for a total of 8 decimal places internally. It shows 1.00 but internally it's 1.00000000. If there's massive deflation in the future, the software could show more decimal places.",
"post_id": "46",
"date": "February 6, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "If it gets tiresome working with small numbers, we could change where the display shows the decimal point. Same amount of money, just different convention for where the \",\"'s and \".\"'s go. e.g. moving the decimal place 3 places would mean if you had 1.00000 before, now it shows it as 1,000.00.",
+ "post_id": "46",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "privacy",
+ "medium": "bitcointalk",
+ "text": "Bitcoin is still very new and has not been independently analysed. If you're serious about privacy, TOR is an advisable precaution.",
+ "post_id": "45",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "privacy",
+ "medium": "bitcointalk",
+ "text": "You could use TOR if you don't want anyone to know you're even using Bitcoin.",
+ "post_id": "45",
+ "date": "February 6, 2010"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "bitcointalk",
+ "text": "I very much wanted to find some way to include a short message, but the problem is, the whole world would be able to see the message. As much as you may keep reminding people that the message is completely non-private, it would be an accident waiting to happen.",
+ "post_id": "33",
+ "date": "January 28, 2010"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "The average total coins generated across the network per day stays the same. Faster machines just get a larger share than slower machines. If everyone bought faster machines, they wouldn't get more coins than before.",
+ "post_id": "20",
+ "date": "December 12, 2009"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "We should have a gentleman's agreement to postpone the GPU arms race as long as we can for the good of the network. It's much easer to get new users up to speed if they don't have to worry about GPU drivers and compatibility. It's nice how anyone with just a CPU can compete fairly equally right now.",
+ "post_id": "20",
+ "date": "December 12, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "bitcointalk",
+ "text": "Those coins can never be recovered, and the total circulation is less. Since the effective circulation is reduced, all the remaining coins are worth slightly more. It's the opposite of when a government prints money and the value of existing money goes down.",
+ "post_id": "17",
+ "date": "December 10, 2009"
+ },
+ {
+ "category": "trusted-third-parties",
+ "text": "Being open source means anyone can independently review the code. If it was closed source, nobody could verify the security. I think it's essential for a program of this nature to be open source.",
+ "medium": "bitcointalk",
+ "post_id": "17",
+ "date": "December 10, 2009"
+ },
+ {
+ "category": "privacy, transactions",
+ "medium": "bitcointalk",
+ "text": "For greater privacy, it's best to use bitcoin addresses only once.",
+ "post_id": "11",
+ "date": "November 25, 2009"
+ },
+ {
+ "category": "mining",
+ "medium": "bitcointalk",
+ "text": "Think of it as a cooperative effort to make a chain. When you add a link, you must first find the current end of the chain. If you were to locate the last link, then go off for an hour and forge your link, come back and link it to the link that was the end an hour ago, others may have added several links since then and they're not going to want to use your link that now branches off the middle.",
+ "post_id": "8",
+ "date": "November 22, 2009"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "p2pfoundation",
+ "text": "It is a global distributed database, with additions to the database by consent of the majority, based on a set of rules they follow: \n\n- Whenever someone finds proof-of-work to generate a block, they get some new coins\n- The proof-of-work difficulty is adjusted every two weeks to target an average of 6 blocks per hour (for the whole network)\n- The coins given per block is cut in half every 4 years",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "p2pfoundation",
+ "text": "You could say coins are issued by the majority. They are issued in a limited, predetermined amount.",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "p2pfoundation",
+ "text": "To Sepp's question, indeed there is nobody to act as central bank or federal reserve to adjust the money supply as the population of users grows. That would have required a trusted party to determine the value, because I don't know a way for software to know the real world value of things.",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "p2pfoundation",
+ "text": "In this sense, it's more typical of a precious metal. Instead of the supply changing to keep the value the same, the supply is predetermined and the value changes. As the number of users grows, the value per coin increases. It has the potential for a positive feedback loop; as users increase, the value goes up, which could attract more users to take advantage of the increasing value.",
+ "post_id": "3",
+ "date": "February 18, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "medium": "p2pfoundation",
+ "text": "A lot of people automatically dismiss e-currency as a lost cause because of all the companies that failed since the 1990's. I hope it's obvious it was only the centrally controlled nature of those systems that doomed them. I think this is the first time we're trying a decentralized, non-trust-based system.",
+ "post_id": "2",
+ "date": "February 15, 2009"
+ },
+ {
+ "category": "releases, bitcoin-design",
+ "medium": "p2pfoundation",
+ "text": "I've developed a new open source P2P e-cash system called Bitcoin. It's completely decentralized, with no central server or trusted parties, because everything is based on crypto proof instead of trust. Give it a try, or take a look at the screenshots and design paper: \n\nDownload Bitcoin v0.1 at http://www.bitcoin.org",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "economics",
+ "medium": "p2pfoundation",
+ "text": "The root problem with conventional currency is all the trust that's required to make it work. The central bank must be trusted not to debase the currency, but the history of fiat currencies is full of breaches of that trust.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "micropayments, privacy, banks",
+ "medium": "p2pfoundation",
+ "text": "Banks must be trusted to hold our money and transfer it electronically, but they lend it out in waves of credit bubbles with barely a fraction in reserve. We have to trust them with our privacy, trust them not to let identity thieves drain our accounts. Their massive overhead costs make micropayments impossible.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "encryption",
+ "medium": "p2pfoundation",
+ "text": "A generation ago, multi-user time-sharing computer systems had a similar problem. Before strong encryption, users had to rely on password protection to secure their files, placing trust in the system administrator to keep their information private. Privacy could always be overridden by the admin based on his judgment call weighing the principle of privacy against other concerns, or at the behest of his superiors. Then strong encryption became available to the masses, and trust was no longer required. Data could be secured in a way that was physically impossible for others to access, no matter for what reason, no matter how good the excuse, no matter what.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "medium": "p2pfoundation",
+ "text": "With e-currency based on cryptographic proof, without the need to trust a third party middleman, money can be secure and transactions effortless.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "transactions",
+ "medium": "p2pfoundation",
+ "text": "A digital coin contains the public key of its owner. To transfer it, the owner signs the coin together with the public key of the next owner. Anyone can check the signatures to verify the chain of ownership.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "double-spending",
+ "medium": "p2pfoundation",
+ "text": "Any owner could try to re-spend an already spent coin by signing it again to another owner. The usual solution is for a trusted company with a central database to check for double-spending, but that just gets back to the trust model. In its central position, the company can override the users, and the fees needed to support the company make micropayments impractical. \nBitcoin's solution is to use a peer-to-peer network to check for double-spending. In a nutshell, the network works like a distributed timestamp server, stamping the first transaction to spend a coin. It takes advantage of the nature of information being easy to spread but hard to stifle.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "p2pfoundation",
+ "text": "The result is a distributed system with no single point of failure. Users hold the crypto keys to their own money and transact directly with each other, with the help of the P2P network to check for double-spending.",
+ "post_id": "1",
+ "date": "February 11, 2009"
+ },
+ {
+ "category": "identity",
+ "medium": "p2pfoundation",
+ "text": "I am not Dorian Nakamoto.",
+ "post_id": "4",
+ "date": "March 7, 2014"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "email",
+ "email_id": "1",
+ "text": "I've been working on a new electronic cash system that's fully peer-to-peer, with no trusted third party.",
+ "date": "November 1, 2008"
+ },
+ {
+ "category": "bitcoin-design",
+ "medium": "email",
+ "email_id": "1",
+ "text": "The main properties: \n Double-spending is prevented with a peer-to-peer network.\n No mint or other trusted parties.\n Participants can be anonymous.\n New coins are made from Hashcash style proof-of-work.\n The proof-of-work for new coin generation also proof-of-workers the network to prevent double-spending.",
+ "date": "November 1, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "2",
+ "text": "Long before the network gets anywhere near as large as that, it would be safe for users to use Simplified Payment Verification (section 8) to check for double spending, which only requires having the chain of block headers, or about 12KB per day.",
+ "date": "November 2, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "2",
+ "text": "Only people trying to create new coins would need to run network nodes.",
+ "date": "November 2, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "2",
+ "text": "At first, most users would run network nodes, but as the network grows beyond a certain point, it would be left more and more to specialists with server farms of specialized hardware. A server farm would only need to have one node on the network and the rest of the LAN connects with that one node.",
+ "date": "November 2, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "The requirement is that the good guys collectively have more CPU proof-of-worker than any single attacker.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "There would be many smaller zombie farms that are not big enough to overproof-of-worker the network, and they could still make money by generating bitcoins. The smaller farms are then the \"honest nodes\". (I need a better term than \"honest\") The more smaller farms resort to generating bitcoins, the higher the bar gets to overproof-of-worker the network, making larger farms also too small to overproof-of-worker it so that they may as well generate bitcoins too. According to the \"long tail\" theory, the small, medium and merely large farms put together should add up to a lot more than the biggest zombie farm.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "Even if a bad guy does overproof-of-worker the network, it's not like he's instantly rich. All he can accomplish is to take back money he himself spent, like bouncing a check. To exploit it, he would have to buy something from a merchant, wait till it ships, then overproof-of-worker the network and try to take his money back. I don't think he could make as much money trying to pull a carding scheme like that as he could by generating bitcoins. With a zombie farm that big, he could generate more bitcoins than everyone else combined.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "3",
+ "text": "The Bitcoin network might actually reduce spam by diverting zombie farms to generating bitcoins instead.",
+ "date": "November 3, 2008"
+ },
+ {
+ "category": "motives",
+ "medium": "email",
+ "email_id": "4",
+ "text": "Yes, but we can win a major battle in the arms race and gain a new territory of freedom for several years.",
+ "date": "November 7, 2008"
+ },
+ {
+ "category": "p2p-networks, government",
+ "medium": "email",
+ "email_id": "4",
+ "text": "Governments are good at cutting off the heads of a centrally controlled networks like Napster, but pure P2P networks like Gnutella and Tor seem to be holding their own.",
+ "date": "November 7, 2008"
+ },
+ {
+ "category": "mining, difficulty",
+ "medium": "email",
+ "email_id": "5",
+ "text": "As computers get faster and the total computing proof-of-worker applied to creating bitcoins increases, the difficulty increases proportionally to keep the total new production constant. Thus, it is known in advance how many new bitcoins will be created every year in the future.",
+ "date": "November 8, 2008"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "email",
+ "email_id": "5",
+ "text": "The fact that new coins are produced means the money supply increases by a planned amount, but this does not necessarily result in inflation. If the supply of money increases at the same rate that the number of people using it increases, prices remain stable. If it does not increase as fast as demand, there will be deflation and early holders of money will see its value increase. Coins have to get initially distributed somehow, and a constant rate seems like the best formula.",
+ "date": "November 8, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "6",
+ "text": "Right, nodes keep transactions in their working set until they get into a block. If a transaction reaches 90% of nodes, then each time a new block is found, it has a 90% chance of being in it.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "6",
+ "text": "Receivers of transactions will normally need to hold transactions for perhaps an hour or more to allow time for this kind of possibility to be resolved. They can still re-spend the coins immediately, but they should wait before taking an action such as shipping goods.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "6",
+ "text": "The attacker isn't adding blocks to the end. He has to go back and redo the block his transaction is in and all the blocks after it, as well as any new blocks the network keeps adding to the end while he's doing that. He's rewriting history. Once his branch is longer, it becomes the new valid one.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes, mining, proof-of-work",
+ "medium": "email",
+ "email_id": "6",
+ "text": "It is strictly necessary that the longest chain is always considered the valid one. Nodes that were present may remember that one branch was there first and got replaced by another, but there would be no way for them to convince those who were not present of this. We can't have subfactions of nodes that cling to one branch that they think was first, others that saw another branch first, and others that joined later and never saw what happened. The CPU proof-of-worker proof-of-work vote must have the final say. The only way for everyone to stay on the same page is to believe that the longest chain is always the valid one, no matter what.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "6",
+ "text": "The recipient just needs to verify it back to a depth that is sufficiently far back in the block chain, which will often only require a depth of 2 transactions. All transactions before that can be discarded.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "6",
+ "text": "When a node receives a block, it checks the signatures of every transaction in it against previous transactions in blocks. Blocks can only contain transactions that depend on valid transactions in previous blocks or the same block. Transaction C could depend on transaction B in the same block and B depends on transaction A in an earlier block.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "7",
+ "text": "It's not a problem if transactions have to wait one or a few extra cycles to get into a block.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "8",
+ "text": "The proof-of-work chain is the solution to the synchronisation problem, and to knowing what the globally shared view is without having to trust anyone.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "8",
+ "text": "A transaction will quickly propagate throughout the network, so if two versions of the same transaction were reported at close to the same time, the one with the head start would have a big advantage in reaching many more nodes first. Nodes will only accept the first one they see, refusing the second one to arrive, so the earlier transaction would have many more nodes working on incorporating it into the next proof-of-work. In effect, each node votes for its viewpoint of which transaction it saw first by including it in its proof-of-work effort. If the transactions did come at exactly the same time and there was an even split, it's a toss up based on which gets into a proof-of-work first, and that decides which is valid.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "nodes, proof-of-work",
+ "medium": "email",
+ "email_id": "8",
+ "text": "When a node finds a proof-of-work, the new block is propagated throughout the network and everyone adds it to the chain and starts working on the next block after it. Any nodes that had the other transaction will stop trying to include it in a block, since it's now invalid according to the accepted chain.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "8",
+ "text": "The proof-of-work chain is itself self-evident proof that it came from the globally shared view. Only the majority of the network together has enough CPU proof-of-worker to generate such a difficult chain of proof-of-work. Any user, upon receiving the proof-of-work chain, can see what the majority of the network has approved. Once a transaction is hashed into a link that's a few links back in the chain, it is firmly etched into the global history.",
+ "date": "November 9, 2008"
+ },
+ {
+ "category": "fees, bitcoin-economics",
+ "medium": "email",
+ "email_id": "9",
+ "text": "If you're having trouble with the inflation issue, it's easy to tweak it for transaction fees instead. It's as simple as this: let the output value from any transaction be 1 cent less than the input value. Either the client software automatically writes transactions for 1 cent more than the intended payment value, or it could come out of the payee's side. The incentive value when a node finds a proof-of-work for a block could be the total of the fees in the block.",
+ "date": "November 10, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "10",
+ "text": "When there are multiple double-spent versions of the same transaction, one and only one will become valid.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "10",
+ "text": "The receiver of a payment must wait an hour or so before believing that it's valid. The network will resolve any possible double-spend races by then.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "10",
+ "text": "The guy who received the double-spend that became invalid never thought he had it in the first place. His software would have shown the transaction go from \"unconfirmed\" to \"invalid\". If necessary, the UI can be made to hide transactions until they're sufficiently deep in the block chain.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "difficulty",
+ "medium": "email",
+ "email_id": "10",
+ "text": "The target time between blocks will probably be 10 minutes. Every block includes its creation time. If the time is off by more than 36 hours, other nodes won't work on it. If the timespan over the last 6*24*30 blocks is less than 15 days, blocks are being generated too fast and the proof-of-work difficulty doubles. Everyone does the same calculation with the same chain data, so they all get the same result at the same link in the chain.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "10",
+ "text": "Instantant non-repudiability is not a feature, but it's still much faster than existing systems. Paper cheques can bounce up to a week or two later. Credit card transactions can be contested up to 60 to 180 days later. Bitcoin transactions can be sufficiently irreversible in an hour or two.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "10",
+ "text": "With the transaction fee based incentive system I recently posted, nodes would have an incentive to include all the paying transactions they receive.",
+ "date": "November 11, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "11",
+ "text": "The proof-of-work chain is a solution to the Byzantine Generals' Problem. I'll try to rephrase it in that context.\nA number of Byzantine Generals each have a computer and want to attack the King's wi-fi by brute forcing the password, which they've learned is a certain number of characters in length. Once they stimulate the network to generate a packet, they must crack the password within a limited time to break in and erase the logs, otherwise they will be discovered and get in trouble. They only have enough CPU proof-of-worker to crack it fast enough if a majority of them attack at the same time. \n They don't particularly care when the attack will be, just that they all agree. It has been decided that anyone who feels like it will announce a time, and whatever time is heard first will be the official attack time. The problem is that the network is not instantaneous, and if two generals announce different attack times at close to the same time, some may hear one first and others hear the other first. They use a proof-of-work chain to solve the problem. Once each general receives whatever attack time he hears first, he sets his computer to solve an extremely difficult proof-of-work problem that includes the attack time in its hash. The proof-of-work is so difficult, it's expected to take 10 minutes of them all working at once before one of them finds a solution. Once one of the generals finds a proof-of-work, he broadcasts it to the network, and everyone changes their current proof-of-work computation to include that proof-of-work in the hash they're working on. If anyone was working on a different attack time, they switch to this one, because its proof-of-work chain is now longer.\n After two hours, one attack time should be hashed by a chain of 12 proofs-of-work. Every general, just by verifying the difficulty of the proof-of-work chain, can estimate how much parallel CPU proof-of-worker per hour was expended on it and see that it must have required the majority of the computers to produce that much proof-of-work in the allotted time. They had to all have seen it because the proof-of-work is proof that they worked on it. If the CPU proof-of-worker exhibited by the proof-of-work chain is sufficient to crack the password, they can safely attack at the agreed time.\n The proof-of-work chain is how all the synchronisation, distributed database and global view problems you've asked about are solved.",
+ "date": "November 13, 2008"
+ },
+ {
+ "category": "nodes, mining",
+ "medium": "email",
+ "email_id": "12",
+ "text": "Broadcasts will probably be almost completely reliable. TCP transmissions are rarely ever dropped these days, and the broadcast protocol has a retry mechanism to get the data from other nodes after a while. If broadcasts turn out to be slower in practice than expected, the target time between blocks may have to be increased to avoid wasting resources. We want blocks to usually propagate in much less time than it takes to generate them, otherwise nodes would spend too much time working on obsolete blocks.",
+ "date": "November 14, 2008"
+ },
+ {
+ "category": "motives",
+ "medium": "email",
+ "email_id": "12",
+ "text": "It's very attractive to the libertarian viewpoint if we can explain it properly. I'm better with code than with words though.",
+ "date": "November 13, 2008"
+ },
+ {
+ "category": "releases",
+ "medium": "email",
+ "email_id": "13",
+ "text": "I'll try and hurry up and release the sourcecode as soon as possible to serve as a reference to help clear up all these implementation questions.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "13",
+ "text": "A basic transaction is just what you see in the figure in section 2. A signature (of the buyer) satisfying the public key of the previous transaction, and a new public key (of the seller) that must be satisfied to spend it the next time.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "13",
+ "text": "There's no need for reporting of \"proof of double spending\" like that. If the same chain contains both spends, then the block is invalid and rejected. \n Same if a block didn't have enough proof-of-work. That block is invalid and rejected. There's no need to circulate a report about it. Every node could see that and reject it before relaying it.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "13",
+ "text": "We're not \"on the lookout\" for double spends to sound the alarm and catch the cheater. We merely adjudicate which one of the spends is valid. Receivers of transactions must wait a few blocks to make sure that resolution has had time to complete. Would be cheaters can try and simultaneously double-spend all they want, and all they accomplish is that within a few blocks, one of the spends becomes valid and the others become invalid. Any later double-spends are immediately rejected once there's already a spend in the main chain.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "proof-of-work, mining",
+ "medium": "email",
+ "email_id": "13",
+ "text": "The proof-of-work is a Hashcash style SHA-256 collision finding. It's a memoryless process where you do millions of hashes a second, with a small chance of finding one each time. The 3 or 4 fastest nodes' dominance would only be proportional to their share of the total CPU proof-of-worker. Anyone's chance of finding a solution at any time is proportional to their CPU proof-of-worker.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "email",
+ "email_id": "13",
+ "text": "There will be transaction fees, so nodes will have an incentive to receive and include all the transactions they can. Nodes will eventually be compensated by transaction fees alone when the total coins created hits the pre-determined ceiling.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "medium": "email",
+ "email_id": "14",
+ "text": "The credential that establishes someone as real is the ability to supply CPU proof-of-worker.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "double-spending",
+ "medium": "email",
+ "email_id": "14",
+ "text": "The race is to spread your transaction on the network first. Think 6 degrees of freedom -- it spreads exponentially. It would only take something like 2 minutes for a transaction to spread widely enough that a competitor starting late would have little chance of grabbing very many nodes before the first one is overtaking the whole network. During those 2 minutes, the merchant's nodes can be watching for a double-spent transaction. The double-spender would not be able to blast his alternate transaction out to the world without the merchant getting it, so he has to wait before starting. \n If the real transaction reaches 90% and the double-spent tx reaches 10%, the double-spender only gets a 10% chance of not paying, and 90% chance his money gets spent. For almost any type of goods, that's not going to be worth it for the scammer.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "14",
+ "text": "If a merchant actually has a problem with theft, they can make the customer wait 2 minutes, or wait for something in e-mail, which many already do. If they really want to optimize, and it's a large download, they could cancel the download in the middle if the transaction comes back double-spent. If it's website access, typically it wouldn't be a big deal to let the customer have access for 5 minutes and then cut off access if it's rejected. Many such sites have a free trial anyway.",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "releases, bitcoin-design",
+ "medium": "email",
+ "email_id": "15",
+ "text": "I believe I've worked through all those little details over the last year and a half while coding it, and there were a lot of them. The functional details are not covered in the paper, but the sourcecode is coming soon. I sent you the main files. (available by request at the moment, full release soon)",
+ "date": "November 17, 2008"
+ },
+ {
+ "category": "releases",
+ "medium": "email",
+ "email_id": "16",
+ "text": "Announcing the first release of Bitcoin, a new electronic cash system that uses a peer-to-peer network to prevent double-spending. It's completely decentralized with no server or central authority.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "nodes",
+ "medium": "email",
+ "email_id": "16",
+ "text": "If you can keep a node running that accepts incoming connections, you'll really be helping the network a lot. Port 8333 on your firewall needs to be open to receive incoming connections.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "mining",
+ "medium": "email",
+ "email_id": "16",
+ "text": "You can get coins by getting someone to send you some, or turn on Options->Generate Coins to run a node and generate blocks. I made the proof-of-work difficulty ridiculously easy to start with, so for a little while in the beginning a typical PC will be able to generate coins in just a few hours. It'll get a lot harder when competition makes the automatic adjustment drive up the difficulty. Generated coins must wait 120 blocks to mature before they can be spent.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "transactions",
+ "medium": "email",
+ "email_id": "16",
+ "text": "There are two ways to send money. If the recipient is online, you can enter their IP address and it will connect, get a new public key and send the transaction with comments. If the recipient is not online, it is possible to send to their Bitcoin address, which is a hash of their public key that they give you. They'll receive the transaction the next time they connect and get the block it's in. This method has the disadvantage that no comment information is sent, and a bit of privacy may be lost if the address is used multiple times, but it is a useful alternative if both users can't be online at the same time or the recipient can't receive incoming connections.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "bitcoin-economics",
+ "medium": "email",
+ "email_id": "16",
+ "text": "Total circulation will be 21,000,000 coins. It'll be distributed to network nodes when they make blocks, with the amount cut in half every 4 years.\n\nfirst 4 years: 10,500,000 coins\nnext 4 years: 5,250,000 coins\nnext 4 years: 2,625,000 coins\nnext 4 years: 1,312,500 coins\netc...\n\nWhen that runs out, the system can support transaction fees if needed. It's based on open market competition, and there will probably always be nodes willing to process transactions for free.",
+ "date": "January 9, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "medium": "email",
+ "email_id": "17",
+ "text": "I would be surprised if 10 years from now we're not using electronic currency in some way, now that we know a way to do it that won't inevitably get dumbed down when the trusted third party gets cold feet.",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "micropayments",
+ "medium": "email",
+ "email_id": "17",
+ "text": "It can already be used for pay-to-send e-mail. The send dialog is resizeable and you can enter as long of a message as you like. It's sent directly when it connects. The recipient doubleclicks on the transaction to see the full message. If someone famous is getting more e-mail than they can read, but would still like to have a way for fans to contact them, they could set up Bitcoin and give out the IP address on their website. \"Send X bitcoins to my priority hotline at this IP and I'll read the message personally.\"",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "micropayments",
+ "medium": "email",
+ "email_id": "17",
+ "text": "Subscription sites that need some extra proof-of-work for their free trial so it doesn't cannibalize subscriptions could charge bitcoins for the trial.",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "micropayments, bitcoin-economics",
+ "medium": "email",
+ "email_id": "17",
+ "text": "It might make sense just to get some in case it catches on. If enough people think the same way, that becomes a self fulfilling prophecy. Once it gets bootstrapped, there are so many applications if you could effortlessly pay a few cents to a website as easily as dropping coins in a vending machine.",
+ "date": "January 17, 2009"
+ },
+ {
+ "category": "cryptocurrency",
+ "text": "A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work, double-spending",
+ "text": "We propose a solution to the double-spending problem using a peer-to-peer network. The network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU proof-of-worker. As long as a majority of CPU proof-of-worker is controlled by nodes that are not cooperating to attack the network, they'll generate the longest chain and outpace attackers. The network itself requires minimal structure.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "trusted-third-parties",
+ "text": "Commerce on the Internet has come to rely almost exclusively on financial institutions serving as trusted third parties to process electronic payments. While the system works well enough for most transactions, it still suffers from the inherent weaknesses of the trust based model.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "trusted-third-parties",
+ "text": "Completely non-reversible transactions are not really possible, since financial institutions cannot avoid mediating disputes. The cost of mediation increases transaction costs, limiting the minimum practical transaction size and cutting off the possibility for small casual transactions, and there is a broader cost in the loss of ability to make non-reversible payments for non-reversible services. With the possibility of reversal, the need for trust spreads.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "trusted-third-parties, cryptocurrency",
+ "text": "What is needed is an electronic payment system based on cryptographic proof instead of trust, allowing any two willing parties to transact directly with each other without the need for a trusted third party. Transactions that are computationally impractical to reverse would protect sellers from fraud, and routine escrow mechanisms could easily be implemented to protect buyers.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "double-spending, proof-of-work",
+ "text": "In this paper, we propose a solution to the double-spending problem using a peer-to-peer distributed timestamp server to generate computational proof of the chronological order of transactions. The system is secure as long as honest nodes collectively control more CPU proof-of-worker than any cooperating group of attacker nodes.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "We define an electronic coin as a chain of digital signatures. Each owner transfers the coin to the next by digitally signing a hash of the previous transaction and the public key of the next owner and adding these to the end of the coin. A payee can verify the signatures to verify the chain of ownership.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "economics, double-spending",
+ "text": "The problem of course is the payee can't verify that one of the owners did not double-spend the coin. A common solution is to introduce a trusted central authority, or mint, that checks every transaction for double spending. After each transaction, the coin must be returned to the mint to issue a new coin, and only coins issued directly from the mint are trusted not to be double-spent. The problem with this solution is that the fate of the entire money system depends on the company running the mint, with every transaction having to go through them, just like a bank.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "nodes, cryptocurrency, transactions",
+ "text": "We need a way for the payee to know that the previous owners did not sign any earlier transactions. For our purposes, the earliest transaction is the one that counts, so we don't care about later attempts to double-spend. The only way to confirm the absence of a transaction is to be aware of all transactions. In the mint based model, the mint was aware of all transactions and decided which arrived first. To accomplish this without a trusted party, transactions must be publicly announced, and we need a system for participants to agree on a single history of the order in which they were received. The payee needs proof that at the time of each transaction, the majority of nodes agreed it was the first received.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "The solution we propose begins with a timestamp server. A timestamp server works by taking a hash of a block of items to be timestamped and widely publishing the hash, such as in a newspaper or Usenet post. The timestamp proves that the data must have existed at the time, obviously, in order to get into the hash. Each timestamp includes the previous timestamp in its hash, forming a chain, with each additional timestamp reinforcing the ones before it.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "text": "To implement a distributed timestamp server on a peer-to-peer basis, we will need to use a proof-of-work system similar to Adam Back's Hashcash, rather than newspaper or Usenet posts. The proof-of-work involves scanning for a value that when hashed, such as with SHA-256, the hash begins with a number of zero bits. The average work required is exponential in the number of zero bits required and can be verified by executing a single hash.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "text": "For our timestamp network, we implement the proof-of-work by incrementing a nonce in the block until a value is found that gives the block's hash the required zero bits. Once the CPU effort has been expended to make it satisfy the proof-of-work, the block cannot be changed without redoing the work. As later blocks are chained after it, the work to change the block would include redoing all the blocks after it.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work",
+ "text": "The proof-of-work also solves the problem of determining representation in majority decision making. If the majority were based on one-IP-address-one-vote, it could be subverted by anyone able to allocate many IPs. Proof-of-work is essentially one-CPU-one-vote. The majority decision is represented by the longest chain, which has the greatest proof-of-work effort invested in it. If a majority of CPU proof-of-worker is controlled by honest nodes, the honest chain will grow the fastest and outpace any competing chains. To modify a past block, an attacker would have to redo the proof-of-work of the block and all blocks after it and then catch up with and surpass the work of the honest nodes. We will show later that the probability of a slower attacker catching up diminishes exponentially as subsequent blocks are added.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "proof-of-work, difficulty",
+ "text": "To compensate for increasing hardware speed and varying interest in running nodes over time, the proof-of-work difficulty is determined by a moving average targeting an average number of blocks per hour. If they're generated too fast, the difficulty increases.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design, nodes, proof-of-work",
+ "text": "The steps to run the network are as follows:\n\n1. New transactions are broadcast to all nodes.\n2. Each node collects new transactions into a block.\n3. Each node works on finding a difficult proof-of-work for its block.\n4. When a node finds a proof-of-work, it broadcasts the block to all nodes.\n5. Nodes accept the block only if all transactions in it are valid and not already spent.\n6. Nodes express their acceptance of the block by working on creating the next block in the chain, using the hash of the accepted block as the previous hash.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "nodes, proof-of-work",
+ "text": "Nodes always consider the longest chain to be the correct one and will keep working on extending it. If two nodes broadcast different versions of the next block simultaneously, some nodes may receive one or the other first. In that case, they work on the first one they received, but save the other branch in case it becomes longer. The tie will be broken when the next proof-of-work is found and one branch becomes longer; the nodes that were working on the other branch will then switch to the longer one.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "New transaction broadcasts do not necessarily need to reach all nodes. As long as they reach many nodes, they will get into a block before long. Block broadcasts are also tolerant of dropped messages. If a node does not receive a block, it will request it when it receives the next block and realizes it missed one.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "mining, bitcoin-economics",
+ "text": "By convention, the first transaction in a block is a special transaction that starts a new coin owned by the creator of the block. This adds an incentive for nodes to support the network, and provides a way to initially distribute coins into circulation, since there is no central authority to issue them. The steady addition of a constant of amount of new coins is analogous to gold miners expending resources to add gold to circulation. In our case, it is CPU time and electricity that is expended.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "fees, bitcoin-economics",
+ "text": "The incentive can also be funded with transaction fees. If the output value of a transaction is less than its input value, the difference is a transaction fee that is added to the incentive value of the block containing the transaction. Once a predetermined number of coins have entered circulation, the incentive can transition entirely to transaction fees and be completely inflation free.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "mining, bitcoin-economics",
+ "text": "The incentive may help encourage nodes to stay honest. If a greedy attacker is able to assemble more CPU proof-of-worker than all the honest nodes, he would have to choose between using it to defraud people by stealing back his payments, or using it to generate new coins. He ought to find it more profitable to play by the rules, such rules that favour him with more new coins than everyone else combined, than to undermine the system and the validity of his own wealth.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design",
+ "text": "Once the latest transaction in a coin is buried under enough blocks, the spent transactions before it can be discarded to save disk space. To facilitate this without breaking the block's hash, transactions are hashed in a Merkle Tree, with only the root included in the block's hash. Old blocks can then be compacted by stubbing off branches of the tree. The interior hashes do not need to be stored.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design",
+ "text": "A block header with no transactions would be about 80 bytes. If we suppose blocks are generated every 10 minutes, 80 bytes * 6 * 24 * 365 = 4.2MB per year. With computer systems typically selling with 2GB of RAM as of 2008, and Moore's Law predicting current growth of 1.2GB per year, storage should not be a problem even if the block headers must be kept in memory.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design, nodes",
+ "text": "It is possible to verify payments without running a full network node. A user only needs to keep a copy of the block headers of the longest proof-of-work chain, which he can get by querying network nodes until he's convinced he has the longest chain, and obtain the Merkle branch linking the transaction to the block it's timestamped in. He can't check the transaction for himself, but by linking it to a place in the chain, he can see that a network node has accepted it, and blocks added after it further confirm the network has accepted it. \nAs such, the verification is reliable as long as honest nodes control the network, but is more vulnerable if the network is overproof-of-workered by an attacker. While network nodes can verify transactions for themselves, the simplified method can be fooled by an attacker's fabricated transactions for as long as the attacker can continue to overproof-of-worker the network. One strategy to protect against this would be to accept alerts from network nodes when they detect an invalid block, prompting the user's software to download the full block and alerted transactions to confirm the inconsistency. Businesses that receive frequent payments will probably still want to run their own nodes for more independent security and quicker verification.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions, bitcoin-design",
+ "text": "Although it would be possible to handle coins individually, it would be unwieldy to make a separate transaction for every cent in a transfer. To allow value to be split and combined, transactions contain multiple inputs and outputs. Normally there will be either a single input from a larger previous transaction or multiple inputs combining smaller amounts, and at most two outputs: one for the payment, and one returning the change, if any, back to the sender.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions",
+ "text": "It should be noted that fan-out, where a transaction depends on several transactions, and those transactions depend on many more, is not a problem here. There is never the need to extract a complete standalone copy of a transaction's history.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "transactions, privacy, trusted-third-parties",
+ "text": "The traditional banking model achieves a level of privacy by limiting access to information to the parties involved and the trusted third party. The necessity to announce all transactions publicly precludes this method, but privacy can still be maintained by breaking the flow of information in another place: by keeping public keys anonymous. The public can see that someone is sending an amount to someone else, but without information linking the transaction to anyone. This is similar to the level of information released by stock exchanges, where the time and size of individual trades, the \"tape\", is made public, but without telling who the parties were.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "addresses, privacy",
+ "text": "As an additional firewall, a new key pair should be used for each transaction to keep them from being linked to a common owner. Some linking is still unavoidable with multi-input transactions, which necessarily reveal that their inputs were owned by the same owner. The risk is that if the owner of a key is revealed, linking could reveal other transactions that belonged to the same owner.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "mining, proof-of-work",
+ "text": "We consider the scenario of an attacker trying to generate an alternate chain faster than the honest chain. Even if this is accomplished, it does not throw the system open to arbitrary changes, such as creating value out of thin air or taking money that never belonged to the attacker. Nodes are not going to accept an invalid transaction as payment, and honest nodes will never accept a block containing them. An attacker can only try to change one of his own transactions to take back money he recently spent.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "bitcoin-design, trusted-third-parties",
+ "text": "We have proposed a system for electronic transactions without relying on trust. We started with the usual framework of coins made from digital signatures, which provides strong control of ownership, but is incomplete without a way to prevent double-spending. To solve this, we proposed a peer-to-peer network using proof-of-work to record a public history of transactions that quickly becomes computationally impractical for an attacker to change if honest nodes control a majority of CPU proof-of-worker.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
+ },
+ {
+ "category": "nodes, mining",
+ "text": "The network is robust in its unstructured simplicity. Nodes work all at once with little coordination. They do not need to be identified, since messages are not routed to any particular place and only need to be delivered on a best effort basis. Nodes can leave and rejoin the network at will, accepting the proof-of-work chain as proof of what happened while they were gone. They vote with their CPU proof-of-worker, expressing their acceptance of valid blocks by working on extending them and rejecting invalid blocks by refusing to work on them. Any needed rules and incentives can be enforced with this consensus mechanism.",
+ "medium": "whitepaper",
+ "date": "October 31, 2008"
}
]
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index d003e7bd..865971cd 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -89,10 +89,16 @@ async def api_gerty_delete(
@gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK)
async def api_gerty_satoshi():
+ maxQuoteLength = 353;
with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
satoshiQuotes = json.load(fd)
- return satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)]
-
+ quote = satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)]
+ # logger.debug(quote.text)
+ if len(quote["text"]) > maxQuoteLength:
+ logger.debug("Quote is too long, getting another")
+ return await api_gerty_satoshi()
+ else:
+ return quote
@gerty_ext.get("/api/v1/gerty/pieterwielliequote", status_code=HTTPStatus.OK)
async def api_gerty_wuille():
@@ -284,9 +290,10 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
# wrap the text
wrapper = textwrap.TextWrapper(width=line_width)
word_list = wrapper.wrap(text=text)
+ logger.debug("number of chars = {0}".format(len(text)))
multilineText = '\n'.join(word_list)
- logger.debug("number of lines = {0}".format(len(word_list)))
+ # logger.debug("number of lines = {0}".format(len(word_list)))
# logger.debug('multilineText')
# logger.debug(multilineText)
From ce398d26c30e95d060e29dce0a19eb34897c1b1b Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sun, 2 Oct 2022 16:23:20 +0100
Subject: [PATCH 054/844] decrease sat char count to 180
---
lnbits/extensions/gerty/views_api.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 865971cd..7bc97af5 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -89,7 +89,7 @@ async def api_gerty_delete(
@gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK)
async def api_gerty_satoshi():
- maxQuoteLength = 353;
+ maxQuoteLength = 186;
with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
satoshiQuotes = json.load(fd)
quote = satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)]
@@ -293,7 +293,7 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
logger.debug("number of chars = {0}".format(len(text)))
multilineText = '\n'.join(word_list)
- # logger.debug("number of lines = {0}".format(len(word_list)))
+ logger.debug("number of lines = {0}".format(len(word_list)))
# logger.debug('multilineText')
# logger.debug(multilineText)
From 5a2aa8b42b02a15302e0fff264374b84c99baffb Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sun, 2 Oct 2022 16:23:59 +0100
Subject: [PATCH 055/844] sat quote font size to 15
---
lnbits/extensions/gerty/views_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 7bc97af5..486162c5 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -242,7 +242,7 @@ async def get_satoshi_quotes():
quote = await api_gerty_satoshi()
if quote:
if quote['text']:
- text.append(get_text_item_dict(quote['text'], 12))
+ text.append(get_text_item_dict(quote['text'], 15))
if quote['date']:
text.append(get_text_item_dict("Satoshi Nakamoto - {0}".format(quote['date']), 15))
return text
From 72b953b354bd9ab23d29bb3d6921e1ac66e0f9e6 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Mon, 3 Oct 2022 17:18:53 +0100
Subject: [PATCH 056/844] Got basic dashboard layout working
---
.../gerty/templates/gerty/index.html | 6 ++++
lnbits/extensions/gerty/views_api.py | 29 +++++++++++++++++--
2 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index a59bf15d..d341ce98 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -175,6 +175,11 @@
Use the toggles below to control what your Gerty will display
+
+
= enabled_screen_count) else p + 1;
@@ -170,6 +171,8 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
logger.debug('screen_slug')
logger.debug(screen_slug)
# text = []
+ if screen_slug == "dashboard":
+ text = await get_dashboard(gerty)
if screen_slug == "lnbits_wallets_balance":
text = await get_lnbits_wallet_balances(gerty)
elif screen_slug == "fun_satoshi_quotes":
@@ -208,6 +211,28 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
text = await get_placeholder_text()
return text
+# Get the dashboard screen
+async def get_dashboard(gerty):
+ text = []
+ # XC rate
+ text.append(get_text_item_dict("19,255", 40, 145, 161))
+ text.append(get_text_item_dict("BTCUSD price", 15, 155, 199))
+ # balance
+ text.append(get_text_item_dict("Alice's wallet balance", 15, 524, 50))
+ text.append(get_text_item_dict("102,101", 40, 524, 126))
+ text.append(get_text_item_dict("Bob's wallet balance", 15, 524, 211))
+ text.append(get_text_item_dict("102", 40, 524, 286))
+
+ # Mempool fees
+ text.append(get_text_item_dict("756,885", 40, 115, 416))
+ text.append(get_text_item_dict("Current block height", 15, 115, 456))
+
+ # difficulty adjustment time
+ text.append(get_text_item_dict("7 days, 2 hours, 0 minutes", 15, 514, 390))
+ text.append(get_text_item_dict("until next difficulty adjustment", 12, 514, 420))
+
+ return text
+
async def get_lnbits_wallet_balances(gerty):
# Get Wallet info
@@ -290,10 +315,10 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
# wrap the text
wrapper = textwrap.TextWrapper(width=line_width)
word_list = wrapper.wrap(text=text)
- logger.debug("number of chars = {0}".format(len(text)))
+ # logger.debug("number of chars = {0}".format(len(text)))
multilineText = '\n'.join(word_list)
- logger.debug("number of lines = {0}".format(len(word_list)))
+ # logger.debug("number of lines = {0}".format(len(word_list)))
# logger.debug('multilineText')
# logger.debug(multilineText)
From 46daf57cacb3f56277f6bbe43c0b7a37c2f6e80b Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Tue, 4 Oct 2022 10:52:53 +0100
Subject: [PATCH 057/844] Moved text into an areas list
---
lnbits/extensions/gerty/views_api.py | 104 ++++++++++++++++++---------
1 file changed, 70 insertions(+), 34 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 87cd4f13..d564c6c5 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -154,7 +154,7 @@ async def api_gerty_json(
"screen": {
"slug": get_screen_slug_by_index(p, enabled_screens),
"group": get_screen_slug_by_index(p, enabled_screens),
- "text": text
+ "areas": text
}
}
@@ -170,68 +170,77 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
# first get the relevant slug from the display_preferences
logger.debug('screen_slug')
logger.debug(screen_slug)
- # text = []
if screen_slug == "dashboard":
- text = await get_dashboard(gerty)
+ areas = await get_dashboard(gerty)
if screen_slug == "lnbits_wallets_balance":
- text = await get_lnbits_wallet_balances(gerty)
+ areas = await get_lnbits_wallet_balances(gerty)
elif screen_slug == "fun_satoshi_quotes":
- text = await get_satoshi_quotes()
+ areas = await get_satoshi_quotes()
elif screen_slug == "fun_pieter_wuille_facts":
- text = await get_pieter_wuille_fact()
+ areas = await get_pieter_wuille_fact()
elif screen_slug == "fun_exchange_market_rate":
- text = await get_exchange_rate(gerty)
+ areas = await get_exchange_rate(gerty)
elif screen_slug == "onchain_difficulty_epoch_progress":
- text = await get_onchain_stat(screen_slug, gerty)
+ areas = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_retarget_date":
- text = await get_onchain_stat(screen_slug, gerty)
+ areas = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_blocks_remaining":
- text = await get_onchain_stat(screen_slug, gerty)
+ areas = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "onchain_difficulty_epoch_time_remaining":
- text = await get_onchain_stat(screen_slug, gerty)
+ areas = await get_onchain_stat(screen_slug, gerty)
elif screen_slug == "mempool_recommended_fees":
- text = await get_placeholder_text()
+ areas = await get_placeholder_text()
elif screen_slug == "mempool_tx_count":
- text = await get_mempool_stat(screen_slug, gerty)
+ areas = await get_mempool_stat(screen_slug, gerty)
elif screen_slug == "mining_current_hash_rate":
- text = await get_placeholder_text()
+ areas = await get_placeholder_text()
elif screen_slug == "mining_current_difficulty":
- text = await get_placeholder_text()
+ areas = await get_placeholder_text()
elif screen_slug == "lightning_channel_count":
- text = await get_placeholder_text()
+ areas = await get_placeholder_text()
elif screen_slug == "lightning_node_count":
- text = await get_placeholder_text()
+ areas = await get_placeholder_text()
elif screen_slug == "lightning_tor_node_count":
- text = await get_placeholder_text()
+ areas = await get_placeholder_text()
elif screen_slug == "lightning_clearnet_nodes":
- text = await get_placeholder_text()
+ areas = await get_placeholder_text()
elif screen_slug == "lightning_unannounced_nodes":
- text = await get_placeholder_text()
+ areas = await get_placeholder_text()
elif screen_slug == "lightning_average_channel_capacity":
- text = await get_placeholder_text()
- return text
+ areas = await get_placeholder_text()
+
+ return areas
# Get the dashboard screen
async def get_dashboard(gerty):
- text = []
+ screens = []
# XC rate
- text.append(get_text_item_dict("19,255", 40, 145, 161))
- text.append(get_text_item_dict("BTCUSD price", 15, 155, 199))
+ text = []
+ amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
+ text.append(get_text_item_dict(format_number(amount), 40, 145, 161))
+ text.append(get_text_item_dict("BTC{0} price".format(gerty.exchange), 15, 155, 199))
+ screens.append(text)
# balance
+ text = []
text.append(get_text_item_dict("Alice's wallet balance", 15, 524, 50))
text.append(get_text_item_dict("102,101", 40, 524, 126))
text.append(get_text_item_dict("Bob's wallet balance", 15, 524, 211))
text.append(get_text_item_dict("102", 40, 524, 286))
+ screens.append(text)
# Mempool fees
- text.append(get_text_item_dict("756,885", 40, 115, 416))
+ text = []
+ text.append(get_text_item_dict(format_number(await get_block_height(gerty)), 40, 115, 416))
text.append(get_text_item_dict("Current block height", 15, 115, 456))
+ screens.append(text)
# difficulty adjustment time
- text.append(get_text_item_dict("7 days, 2 hours, 0 minutes", 15, 514, 390))
+ text = []
+ text.append(get_text_item_dict(await get_time_remaining_next_difficulty_adjustment(gerty), 15, 514, 390))
text.append(get_text_item_dict("until next difficulty adjustment", 12, 514, 420))
+ screens.append(text)
- return text
+ return screens
async def get_lnbits_wallet_balances(gerty):
@@ -244,13 +253,13 @@ async def get_lnbits_wallet_balances(gerty):
wallet = await get_wallet_for_key(key=lnbits_wallet)
logger.debug(wallet)
if wallet:
- wallets.append({
- "name": wallet.name,
- "balance": wallet.balance_msat,
- "inkey": wallet.inkey,
- })
+ # wallets.append({
+ # "name": wallet.name,
+ # "balance": wallet.balance_msat,
+ # "inkey": wallet.inkey,
+ # })
text.append(get_text_item_dict(wallet.name, 20))
- text.append(get_text_item_dict(wallet.balance, 40))
+ text.append(get_text_item_dict(format_number(wallet.balance_msat), 40))
return text
@@ -365,6 +374,33 @@ async def get_onchain_stat(stat_slug: str, gerty):
text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
return text
+
+async def get_time_remaining_next_difficulty_adjustment(gerty):
+ if isinstance(gerty.mempool_endpoint, str):
+ async with httpx.AsyncClient() as client:
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
+ stat = r.json()['remainingTime']
+ time = get_time_remaining(stat / 1000, 3)
+ return time
+
+async def get_block_height(gerty):
+ if isinstance(gerty.mempool_endpoint, str):
+ async with httpx.AsyncClient() as client:
+ r = await client.get(gerty.mempool_endpoint + "/api/blocks/tip/height")
+
+ return r.json()
+
+async def get_mempool_recommended_fees(gerty):
+ if isinstance(gerty.mempool_endpoint, str):
+ async with httpx.AsyncClient() as client:
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/fees/recommended")
+ return {
+ "high": r.fastestFee,
+ "medium": r.halfHourFee,
+ "low": r.economyFee,
+ }
+
+
async def get_mempool_stat(stat_slug: str, gerty):
text = []
if isinstance(gerty.mempool_endpoint, str):
From 0a63576a6ac43155cdb38d99487ba9732b5f70d7 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Tue, 4 Oct 2022 10:54:34 +0100
Subject: [PATCH 058/844] Added refresh time
---
lnbits/extensions/gerty/views_api.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index d564c6c5..1560ca81 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -146,7 +146,7 @@ async def api_gerty_json(
return {
"settings": {
"refreshTime": gerty.refresh_time,
- "requestTimestamp": round(time.time()),
+ "requestTimestamp": datetime.fromtimestamp(time.time()).strftime("%e %b %Y at %H:%M"),
"nextScreenNumber": next_screen_number,
"showTextBoundRect": False,
"name": gerty.name
From 0f0d24a7ecd5c090c90f915a531c5ce714fc8937 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Tue, 4 Oct 2022 19:08:24 +0100
Subject: [PATCH 059/844] move non dashboard data into areas list
---
lnbits/extensions/gerty/views_api.py | 69 ++++++++++++++--------------
1 file changed, 35 insertions(+), 34 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 1560ca81..ebe8d72d 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -170,77 +170,78 @@ async def get_screen_text(screen_num: int, screens_list: dict, gerty):
# first get the relevant slug from the display_preferences
logger.debug('screen_slug')
logger.debug(screen_slug)
+ areas = []
if screen_slug == "dashboard":
areas = await get_dashboard(gerty)
if screen_slug == "lnbits_wallets_balance":
- areas = await get_lnbits_wallet_balances(gerty)
+ areas.append(await get_lnbits_wallet_balances(gerty))
elif screen_slug == "fun_satoshi_quotes":
- areas = await get_satoshi_quotes()
+ areas.append(await get_satoshi_quotes())
elif screen_slug == "fun_pieter_wuille_facts":
- areas = await get_pieter_wuille_fact()
+ areas.append(await get_pieter_wuille_fact())
elif screen_slug == "fun_exchange_market_rate":
- areas = await get_exchange_rate(gerty)
+ areas.append(await get_exchange_rate(gerty))
elif screen_slug == "onchain_difficulty_epoch_progress":
- areas = await get_onchain_stat(screen_slug, gerty)
+ areas.append(await get_onchain_stat(screen_slug, gerty))
elif screen_slug == "onchain_difficulty_retarget_date":
- areas = await get_onchain_stat(screen_slug, gerty)
+ areas.append(await get_onchain_stat(screen_slug, gerty))
elif screen_slug == "onchain_difficulty_blocks_remaining":
- areas = await get_onchain_stat(screen_slug, gerty)
+ areas.append(await get_onchain_stat(screen_slug, gerty))
elif screen_slug == "onchain_difficulty_epoch_time_remaining":
- areas = await get_onchain_stat(screen_slug, gerty)
+ areas.append(await get_onchain_stat(screen_slug, gerty))
elif screen_slug == "mempool_recommended_fees":
- areas = await get_placeholder_text()
+ areas.append(await get_placeholder_text())
elif screen_slug == "mempool_tx_count":
- areas = await get_mempool_stat(screen_slug, gerty)
+ areas.append(await get_mempool_stat(screen_slug, gerty))
elif screen_slug == "mining_current_hash_rate":
- areas = await get_placeholder_text()
+ areas.append(await get_placeholder_text())
elif screen_slug == "mining_current_difficulty":
- areas = await get_placeholder_text()
+ areas.append(await get_placeholder_text())
elif screen_slug == "lightning_channel_count":
- areas = await get_placeholder_text()
+ areas.append(await get_placeholder_text())
elif screen_slug == "lightning_node_count":
- areas = await get_placeholder_text()
+ areas.append(await get_placeholder_text())
elif screen_slug == "lightning_tor_node_count":
- areas = await get_placeholder_text()
+ areas.append(await get_placeholder_text())
elif screen_slug == "lightning_clearnet_nodes":
- areas = await get_placeholder_text()
+ areas.append(await get_placeholder_text())
elif screen_slug == "lightning_unannounced_nodes":
- areas = await get_placeholder_text()
+ areas.append(await get_placeholder_text())
elif screen_slug == "lightning_average_channel_capacity":
- areas = await get_placeholder_text()
+ areas.append(await get_placeholder_text())
return areas
# Get the dashboard screen
async def get_dashboard(gerty):
- screens = []
+ areas = []
# XC rate
text = []
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
- text.append(get_text_item_dict(format_number(amount), 40, 145, 161))
- text.append(get_text_item_dict("BTC{0} price".format(gerty.exchange), 15, 155, 199))
- screens.append(text)
+ text.append(get_text_item_dict(format_number(amount), 40))
+ text.append(get_text_item_dict("BTC{0} price".format(gerty.exchange), 15))
+ areas.append(text)
# balance
text = []
- text.append(get_text_item_dict("Alice's wallet balance", 15, 524, 50))
- text.append(get_text_item_dict("102,101", 40, 524, 126))
- text.append(get_text_item_dict("Bob's wallet balance", 15, 524, 211))
- text.append(get_text_item_dict("102", 40, 524, 286))
- screens.append(text)
+ text.append(get_text_item_dict("Alice's wallet balance", 15))
+ text.append(get_text_item_dict("102,101", 40))
+ text.append(get_text_item_dict("Bob's wallet balance", 15))
+ text.append(get_text_item_dict("102", 40))
+ areas.append(text)
# Mempool fees
text = []
- text.append(get_text_item_dict(format_number(await get_block_height(gerty)), 40, 115, 416))
- text.append(get_text_item_dict("Current block height", 15, 115, 456))
- screens.append(text)
+ text.append(get_text_item_dict(format_number(await get_block_height(gerty)), 40))
+ text.append(get_text_item_dict("Current block height", 15))
+ areas.append(text)
# difficulty adjustment time
text = []
- text.append(get_text_item_dict(await get_time_remaining_next_difficulty_adjustment(gerty), 15, 514, 390))
- text.append(get_text_item_dict("until next difficulty adjustment", 12, 514, 420))
- screens.append(text)
+ text.append(get_text_item_dict(await get_time_remaining_next_difficulty_adjustment(gerty), 15))
+ text.append(get_text_item_dict("until next difficulty adjustment", 12))
+ areas.append(text)
- return screens
+ return areas
async def get_lnbits_wallet_balances(gerty):
From 166530eb0c985575a140124fb4c9e5a23ee9e5a7 Mon Sep 17 00:00:00 2001
From: benarc
Date: Mon, 7 Mar 2022 05:03:32 +0000
Subject: [PATCH 060/844] 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 061/844] 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 062/844] 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 063/844] 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 064/844] 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 065/844] 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 066/844] 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 067/844] 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 068/844] 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 069/844] 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 070/844] 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 071/844] 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 072/844] 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 073/844] 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 074/844] 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 075/844] 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 076/844] 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 077/844] 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 078/844] 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 079/844] 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 080/844] 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 081/844] 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 082/844] 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 083/844] 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 084/844] 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 085/844] 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 086/844] 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 087/844] 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 088/844] 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 089/844] 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 091/844] 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 092/844] 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 093/844] 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 094/844] 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 095/844] 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 096/844] 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 097/844] 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 098/844] 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 099/844] 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 100/844] 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 101/844] 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 102/844] 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 103/844] 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 104/844] 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 105/844] 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 106/844] 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 107/844] 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 108/844] 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 109/844] 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 110/844] 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 111/844] 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 112/844] 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 113/844] 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 167/844] 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 168/844] 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: Fri, 7 Oct 2022 22:12:48 +0100
Subject: [PATCH 169/844] bug fix for sleep hours
---
lnbits/extensions/gerty/views_api.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 3daca8bd..c0c643de 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -140,8 +140,8 @@ async def api_gerty_json(
# get the sleep time
sleep_time = gerty.refresh_time
if gerty_should_sleep():
- sleep_time_hours = 7
- sleep_time = 60 * sleep_time_hours
+ sleep_time_hours = 8
+ sleep_time = 60 * 60 * sleep_time_hours
return {
"settings": {
From 0b63db46db06876a4edd787181bd4ba4f0d496ef Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sat, 8 Oct 2022 18:08:58 +0100
Subject: [PATCH 170/844] Tweak wallet balance text
---
lnbits/extensions/gerty/views_api.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index c0c643de..b7cc3e74 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -230,8 +230,8 @@ async def get_dashboard(gerty):
wallets = await get_lnbits_wallet_balances(gerty)
text = []
for wallet in wallets:
- text.append(get_text_item_dict("{0}'s Wallet".format(wallet['name']), 15))
- text.append(get_text_item_dict("{0} sats".format(format_number(wallet['balance'])), 40))
+ text.append(get_text_item_dict("{0}".format(wallet['name']), 15))
+ text.append(get_text_item_dict("{0} sats".format(format_number(wallet['balance'])), 20))
areas.append(text)
# Mempool fees
From 0a7b0819bbb5bac210d7000aef4a15fcebbe55d8 Mon Sep 17 00:00:00 2001
From: Tiago vasconcelos
Date: Mon, 10 Oct 2022 12:17:35 +0100
Subject: [PATCH 171/844] 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 172/844] 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 aa040f3a6c88972d508372b053ef917a1f3e50f8 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Mon, 10 Oct 2022 13:38:21 +0100
Subject: [PATCH 173/844] Bug fixes
---
lnbits/extensions/gerty/helpers.py | 10 +++++-----
lnbits/extensions/gerty/views_api.py | 2 +-
2 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index ddf9dd19..eb307d60 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -1,4 +1,4 @@
-import datetime
+from datetime import datetime, timedelta
import pytz
import httpx
import textwrap
@@ -143,13 +143,13 @@ async def get_mining_stat(stat_slug: str, gerty):
return text
def get_next_update_time(sleep_time_seconds: int = 0, timezone: str = "Europe/London"):
- utc_now = pytz.utc.localize(datetime.datetime.utcnow())
- next_refresh_time = utc_now + datetime.timedelta(0, sleep_time_seconds)
+ utc_now = pytz.utc.localize(datetime.utcnow())
+ next_refresh_time = utc_now + timedelta(0, sleep_time_seconds)
local_refresh_time = next_refresh_time.astimezone(pytz.timezone(timezone))
- return "{0} {1}".format("I'll wake up at" if gerty_should_sleep() else "Next update at",local_refresh_time.strftime("%H:%M on%e %b %Y"))
+ return "{0} {1}".format("I'll wake up at" if gerty_should_sleep() else "Next update at",local_refresh_time.strftime("%H:%M on %e %b %Y"))
def gerty_should_sleep(timezone: str = "Europe/London"):
- utc_now = pytz.utc.localize(datetime.datetime.utcnow())
+ utc_now = pytz.utc.localize(datetime.utcnow())
local_time = utc_now.astimezone(pytz.timezone(timezone))
hours = local_time.strftime("%H")
hours = int(hours)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index b7cc3e74..4849840a 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -138,7 +138,7 @@ async def api_gerty_json(
next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1;
# get the sleep time
- sleep_time = gerty.refresh_time
+ sleep_time = gerty.refresh_time if gerty.refresh_time else 300
if gerty_should_sleep():
sleep_time_hours = 8
sleep_time = 60 * 60 * sleep_time_hours
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 174/844] 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 175/844] 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 176/844] 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 177/844] 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 178/844] 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 179/844] 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: Thu, 20 Oct 2022 14:59:41 +0100
Subject: [PATCH 180/844] Removed pieter wuille quotes
---
.../gerty/static/pieter_wuille.json | 24 -------------------
.../gerty/templates/gerty/index.html | 20 ----------------
lnbits/extensions/gerty/views_api.py | 18 --------------
3 files changed, 62 deletions(-)
delete mode 100644 lnbits/extensions/gerty/static/pieter_wuille.json
diff --git a/lnbits/extensions/gerty/static/pieter_wuille.json b/lnbits/extensions/gerty/static/pieter_wuille.json
deleted file mode 100644
index 9dec9f67..00000000
--- a/lnbits/extensions/gerty/static/pieter_wuille.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "facts": [
- "When a woman asked Pieter Wuille to talk dirty to her, he described the OpenSSL DER implementation.",
- "Pieter Wuille recently visited an event horizon and escaped with a cryptographic proof.",
- "Pieter Wuille's PhD thesis defence in full: \"Pieter Wuille, thank you\".",
- "Pieter Wuille is an acronym for Programmatic Intelligent Encrypted Telemetric Encapsulated Recursive Witness Upscaling Integrated Load-Balancing Logical Entity.",
- "Dan Bernstein only trusts one source of random numbers: Pieter Wuille.",
- "Putting Pieter Wuille in the title of an r/Bitcoin submission gets more upvotes than the same post from Pieter Wuille himself.",
- "Pieter Wuille won the underhanded crypto contest but his entry was so underhanded nobody even knows he entered.",
- "Greg Maxwell is a bot created by Pieter Wuille to argue on reddit so he can get code done.",
- "Pieter Wuille doesn't need the public key to calculate the corresponding private key.",
- "When the Wikipedia servers corrupted all data including backups, Pieter Wuille had to stay awake all night to retype it.",
- "It is a Bitcoin consensus rule that when Pieter's hard drive is full no more blocks can be added.",
- "When they go out, Pieter Wuille pays for his parents.",
- "Pieter Wuille replaced the existing monetary system by writing a few thousand lines of code.",
- "Putting Pieter Wuille in the title of an r/Bitcoin submission gets more upvotes than the same post from Pieter Wuille himself.",
- "Only Pieter Wuille can name things harder to pronounce than Pieter Wuille.",
- "Pieter Wuille doesn't write code, he wills it into existence.",
- "If every copy of the blockchain were deleted Pieter Wuille would recreate it from memory.",
- "If all else fails, bitcoin should be restarted by syncing the code and the blockchain directly from Wuille's mind.",
- "Pieter Wuille codes // Enlightened Zen master floats // Haikus trickle down.",
- "Pieter Wuille once wrote a constant time generator for generating constant time cryptographic code."
- ]
-}
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 01b38f60..0681f217 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -191,35 +191,16 @@
>
-
-
- Toggle all
-
-
Displays random quotes from Satoshi
-
- Show accurate facts about Pieter Wuille
-
-
Date: Thu, 20 Oct 2022 15:00:49 +0100
Subject: [PATCH 181/844] Removed LNbits wallets balance screen from options
---
lnbits/extensions/gerty/templates/gerty/index.html | 12 ------------
lnbits/extensions/gerty/views_api.py | 7 -------
2 files changed, 19 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 0681f217..bfbd029f 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -180,17 +180,6 @@
label="Show the dashboard"
>
-
-
-
-
Date: Thu, 20 Oct 2022 15:09:09 +0100
Subject: [PATCH 182/844] Merged all onchain items into single dashboard
---
.../gerty/templates/gerty/index.html | 72 +++++---------
lnbits/extensions/gerty/views_api.py | 93 ++++++++++---------
2 files changed, 68 insertions(+), 97 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index bfbd029f..e066eca7 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -176,52 +176,25 @@
Use the toggles below to control what your Gerty will display
+ v-model="formDialog.data.display_preferences.dashboard"
+ label="Show the dashboard"
+ >
-
- Displays random quotes from Satoshi
-
-
-
-
- Toggle all
-
-
-
+ Displays random quotes from Satoshi
+
+
-
-
-
-
-
-
+
= enabled_screen_count) else p + 1;
# get the sleep time
- sleep_time = gerty.refresh_time if gerty.refresh_time else 300
+ sleep_time = gerty.refresh_time if gerty.refresh_time else 300
if gerty_should_sleep():
sleep_time_hours = 8
sleep_time = 60 * 60 * sleep_time_hours
@@ -174,14 +175,8 @@ async def get_screen_data(screen_num: int, screens_list: dict, gerty):
areas.append(await get_satoshi_quotes())
elif screen_slug == "fun_exchange_market_rate":
areas.append(await get_exchange_rate(gerty))
- elif screen_slug == "onchain_difficulty_epoch_progress":
- areas.append(await get_onchain_stat(screen_slug, gerty))
- elif screen_slug == "onchain_difficulty_retarget_date":
- areas.append(await get_onchain_stat(screen_slug, gerty))
- elif screen_slug == "onchain_difficulty_blocks_remaining":
- areas.append(await get_onchain_stat(screen_slug, gerty))
- elif screen_slug == "onchain_difficulty_epoch_time_remaining":
- areas.append(await get_onchain_stat(screen_slug, gerty))
+ elif screen_slug == "onchain_dashboard":
+ areas.append(await get_onchain_dashboard(gerty))
elif screen_slug == "mempool_recommended_fees":
areas.append(await get_mempool_stat(screen_slug, gerty))
elif screen_slug == "mempool_tx_count":
@@ -200,6 +195,7 @@ async def get_screen_data(screen_num: int, screens_list: dict, gerty):
return data
+
# Get the dashboard screen
async def get_dashboard(gerty):
areas = []
@@ -283,39 +279,37 @@ async def get_exchange_rate(gerty):
return text
-
-
-
-async def get_onchain_stat(stat_slug: str, gerty):
- text = []
+async def get_onchain_dashboard(gerty):
+ areas = []
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
- if (
- stat_slug == "onchain_difficulty_epoch_progress" or
- stat_slug == "onchain_difficulty_retarget_date" or
- stat_slug == "onchain_difficulty_blocks_remaining" or
- stat_slug == "onchain_difficulty_epoch_time_remaining"
- ):
- r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
- if stat_slug == "onchain_difficulty_epoch_progress":
- stat = round(r.json()['progressPercent'])
- text.append(get_text_item_dict("Progress through current difficulty epoch", 15))
- text.append(get_text_item_dict("{0}%".format(stat), 80))
- elif stat_slug == "onchain_difficulty_retarget_date":
- stat = r.json()['estimatedRetargetDate']
- dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
- text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 15))
- text.append(get_text_item_dict(dt, 40))
- elif stat_slug == "onchain_difficulty_blocks_remaining":
- stat = r.json()['remainingBlocks']
- text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 80))
- elif stat_slug == "onchain_difficulty_epoch_time_remaining":
- stat = r.json()['remainingTime']
- text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
- text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
- return text
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
+ text = []
+ stat = round(r.json()['progressPercent'])
+ text.append(get_text_item_dict("Progress through current difficulty epoch", 12))
+ text.append(get_text_item_dict("{0}%".format(stat), 20))
+ areas.append(text)
+ text = []
+ stat = r.json()['estimatedRetargetDate']
+ dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
+ text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 12))
+ text.append(get_text_item_dict(dt, 20))
+ areas.append(text)
+
+ text = []
+ stat = r.json()['remainingBlocks']
+ text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 12))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 20))
+ areas.append(text)
+
+ text = []
+ stat = r.json()['remainingTime']
+ text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 12))
+ text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
+ areas.append(text)
+
+ return areas
async def get_time_remaining_next_difficulty_adjustment(gerty):
if isinstance(gerty.mempool_endpoint, str):
@@ -325,6 +319,7 @@ async def get_time_remaining_next_difficulty_adjustment(gerty):
time = get_time_remaining(stat / 1000, 3)
return time
+
async def get_block_height(gerty):
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
@@ -332,6 +327,7 @@ async def get_block_height(gerty):
return r.json()
+
async def get_mempool_stat(stat_slug: str, gerty):
text = []
if isinstance(gerty.mempool_endpoint, str):
@@ -345,7 +341,7 @@ async def get_mempool_stat(stat_slug: str, gerty):
text.append(get_text_item_dict("Transactions in the mempool", 15))
text.append(get_text_item_dict("{0}".format(format_number(stat)), 80))
elif (
- stat_slug == "mempool_recommended_fees"
+ stat_slug == "mempool_recommended_fees"
):
y_offset = 60
fees = await get_mempool_recommended_fees(gerty)
@@ -365,33 +361,38 @@ async def get_mempool_stat(stat_slug: str, gerty):
fee_append = "/vB"
fee_rate = fees["economyFee"]
text.append(get_text_item_dict(
- "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append), font_size,
+ "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append),
+ font_size,
30, pos_y))
fee_rate = fees["hourFee"]
text.append(get_text_item_dict(
- "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append), font_size,
+ "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append),
+ font_size,
235, pos_y))
fee_rate = fees["halfHourFee"]
text.append(get_text_item_dict(
- "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append), font_size,
+ "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append),
+ font_size,
460, pos_y))
fee_rate = fees["fastestFee"]
text.append(get_text_item_dict(
- "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append), font_size,
+ "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append),
+ font_size,
750, pos_y))
return text
+
def get_date_suffix(dayNumber):
if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
return "th"
else:
return ["st", "nd", "rd"][dayNumber % 10 - 1]
-def get_time_remaining(seconds, granularity=2):
+def get_time_remaining(seconds, granularity=2):
intervals = (
# ('weeks', 604800), # 60 * 60 * 24 * 7
('days', 86400), # 60 * 60 * 24
@@ -409,4 +410,4 @@ def get_time_remaining(seconds, granularity=2):
if value == 1:
name = name.rstrip('s')
result.append("{} {}".format(round(value), name))
- return ', '.join(result[:granularity])
\ No newline at end of file
+ return ', '.join(result[:granularity])
From 004cd059731b3e62f937e8c1a1737d0b7d523933 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 15:10:08 +0100
Subject: [PATCH 183/844] Mempool fees now standalone item
---
.../gerty/templates/gerty/index.html | 19 +------------------
1 file changed, 1 insertion(+), 18 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index e066eca7..8b78a071 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -195,29 +195,12 @@
v-model="formDialog.data.display_preferences.onchain_dashboard"
label="Onchain dashboard"
>
-
-
- Toggle all
-
-
-
-
Date: Thu, 20 Oct 2022 15:13:16 +0100
Subject: [PATCH 184/844] Moved lightninig network dashboard out of expansion
group
---
.../gerty/templates/gerty/index.html | 24 ++++++++-----------
1 file changed, 10 insertions(+), 14 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 8b78a071..abdefbc7 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -196,10 +196,10 @@
label="Onchain dashboard"
>
-
+
-
-
-
+
+
+
Date: Thu, 20 Oct 2022 15:14:22 +0100
Subject: [PATCH 185/844] Removed mempool_tx_count from api
---
lnbits/extensions/gerty/views_api.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index dfd11051..c8c4efb7 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -179,8 +179,6 @@ async def get_screen_data(screen_num: int, screens_list: dict, gerty):
areas.append(await get_onchain_dashboard(gerty))
elif screen_slug == "mempool_recommended_fees":
areas.append(await get_mempool_stat(screen_slug, gerty))
- elif screen_slug == "mempool_tx_count":
- areas.append(await get_mempool_stat(screen_slug, gerty))
elif screen_slug == "mining_current_hash_rate":
areas.append(await get_mining_stat(screen_slug, gerty))
elif screen_slug == "mining_current_difficulty":
From 9f3b4c48ea2e3a4126c1aed4a9d0f182227140ae Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 15:59:55 +0100
Subject: [PATCH 186/844] Created mining dashboard
---
lnbits/extensions/gerty/helpers.py | 112 ++++++++++++------
.../gerty/templates/gerty/index.html | 33 +-----
lnbits/extensions/gerty/views_api.py | 40 +------
3 files changed, 87 insertions(+), 98 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index eb307d60..c29139fb 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -58,23 +58,54 @@ async def get_mempool_recommended_fees(gerty):
r = await client.get(gerty.mempool_endpoint + "/api/v1/fees/recommended")
return r.json()
-async def api_get_mining_stat(stat_slug: str, gerty):
- stat = "";
+async def get_mining_dashboard(gerty):
+ areas = []
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
- if stat_slug == "mining_current_hash_rate":
- r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1m")
- data = r.json()
- stat = {}
- stat['current'] = data['currentHashrate']
- stat['1w'] = data['hashrates'][len(data['hashrates']) - 7]['avgHashrate']
- elif stat_slug == "mining_current_difficulty":
- r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1m")
- data = r.json()
- stat = {}
- stat['current'] = data['currentDifficulty']
- stat['previous'] = data['difficulty'][len(data['difficulty']) - 2]['difficulty']
- return stat
+ # current hashrate
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1w")
+ data = r.json()
+ hashrateNow = data['currentHashrate']
+ hashrateOneWeekAgo = data['hashrates'][6]['avgHashrate']
+
+ text = []
+ text.append(get_text_item_dict("Current mining hashrate", 12))
+ text.append(get_text_item_dict("{0}hash".format(si_format(hashrateNow, 6, True, " ")), 20))
+ text.append(get_text_item_dict("{0} vs 7 days ago".format(get_percent_difference(hashrateNow, hashrateOneWeekAgo, 3)), 12))
+ areas.append(text)
+
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
+
+ # timeAvg
+ text = []
+ time_avg = r.json()['timeAvg'] / 1000
+ hours, remainder = divmod(time_avg, 3600)
+ minutes, seconds = divmod(remainder, 60)
+ time_avg = '{:02} minutes {:02} seconds'.format(int(minutes), int(seconds))
+ text.append(get_text_item_dict("Current block time", 12))
+ text.append(get_text_item_dict(str(time_avg), 20))
+ areas.append(text)
+
+ # difficulty adjustment
+ text = []
+ stat = r.json()['remainingTime']
+ text.append(get_text_item_dict("Time to next difficulty adjustment", 12))
+ text.append(get_text_item_dict(get_time_remaining(stat / 1000, 3), 20))
+ areas.append(text)
+
+ # difficultyChange
+ text = []
+ difficultyChange = round(r.json()['difficultyChange'], 2)
+ text.append(get_text_item_dict("Estimated difficulty change", 12))
+ text.append(get_text_item_dict("{0}{1}%".format("+" if difficultyChange > 0 else "", round(difficultyChange, 2)), 20))
+ areas.append(text)
+
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1m")
+ data = r.json()
+ stat = {}
+ stat['current'] = data['currentDifficulty']
+ stat['previous'] = data['difficulty'][len(data['difficulty']) - 2]['difficulty']
+ return areas
async def api_get_lightning_stats(gerty):
stat = {}
@@ -88,8 +119,6 @@ async def get_lightning_stats(gerty):
data = await api_get_lightning_stats(gerty)
areas = []
- logger.debug(data['latest']['channel_count'])
-
text = []
text.append(get_text_item_dict("Channel Count", 12))
text.append(get_text_item_dict(format_number(data['latest']['channel_count']), 20))
@@ -122,26 +151,6 @@ async def get_lightning_stats(gerty):
return areas
-async def get_mining_stat(stat_slug: str, gerty):
- text = []
- if stat_slug == "mining_current_hash_rate":
- stat = await api_get_mining_stat(stat_slug, gerty)
- logger.debug(stat)
- current = "{0}hash".format(si_format(stat['current'], 6, True, " "))
- text.append(get_text_item_dict("Current Mining Hashrate", 20))
- text.append(get_text_item_dict(current, 40))
- # compare vs previous time period
- difference = get_percent_difference(current=stat['current'], previous=stat['1w'])
- text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
- elif stat_slug == "mining_current_difficulty":
- stat = await api_get_mining_stat(stat_slug, gerty)
- text.append(get_text_item_dict("Current Mining Difficulty", 20))
- text.append(get_text_item_dict(format_number(stat['current']), 40))
- difference = get_percent_difference(current=stat['current'], previous=stat['previous'])
- text.append(get_text_item_dict("{0} since last adjustment".format(difference), 12))
- # text.append(get_text_item_dict("Required threshold for mining proof-of-work", 12))
- return text
-
def get_next_update_time(sleep_time_seconds: int = 0, timezone: str = "Europe/London"):
utc_now = pytz.utc.localize(datetime.utcnow())
next_refresh_time = utc_now + timedelta(0, sleep_time_seconds)
@@ -159,3 +168,32 @@ def gerty_should_sleep(timezone: str = "Europe/London"):
return True
else:
return False
+
+
+
+def get_date_suffix(dayNumber):
+ if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
+ return "th"
+ else:
+ return ["st", "nd", "rd"][dayNumber % 10 - 1]
+
+
+def get_time_remaining(seconds, granularity=2):
+ intervals = (
+ # ('weeks', 604800), # 60 * 60 * 24 * 7
+ ('days', 86400), # 60 * 60 * 24
+ ('hours', 3600), # 60 * 60
+ ('minutes', 60),
+ ('seconds', 1),
+ )
+
+ result = []
+
+ for name, count in intervals:
+ value = seconds // count
+ if value:
+ seconds -= value * count
+ if value == 1:
+ name = name.rstrip('s')
+ result.append("{} {}".format(round(value), name))
+ return ', '.join(result[:granularity])
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index abdefbc7..d6e779cf 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -201,36 +201,16 @@
label="mempool.space recommended fees"
>
-
-
-
- Toggle all
-
-
-
-
-
-
-
+
-
+
Date: Thu, 20 Oct 2022 16:02:21 +0100
Subject: [PATCH 187/844] Replaced current block itme with progress through
current epoch
---
lnbits/extensions/gerty/helpers.py | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index c29139fb..3f7f88e3 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -78,12 +78,9 @@ async def get_mining_dashboard(gerty):
# timeAvg
text = []
- time_avg = r.json()['timeAvg'] / 1000
- hours, remainder = divmod(time_avg, 3600)
- minutes, seconds = divmod(remainder, 60)
- time_avg = '{:02} minutes {:02} seconds'.format(int(minutes), int(seconds))
- text.append(get_text_item_dict("Current block time", 12))
- text.append(get_text_item_dict(str(time_avg), 20))
+ progress = "{0}%".format(round(r.json()['progressPercent'], 2))
+ text.append(get_text_item_dict("Progress through current epoch", 12))
+ text.append(get_text_item_dict(progress, 20))
areas.append(text)
# difficulty adjustment
From dc02601db1d05cc140374c45b7d69840748fcaba Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 16:03:28 +0100
Subject: [PATCH 188/844] Fixed casing on Gerty extension modal UI
---
lnbits/extensions/gerty/templates/gerty/index.html | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index d6e779cf..7007c8cd 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -177,7 +177,7 @@
Date: Thu, 20 Oct 2022 16:05:15 +0100
Subject: [PATCH 189/844] Removed unused toggleAll logic from gerty modal
---
.../gerty/templates/gerty/index.html | 32 ++-----------------
1 file changed, 2 insertions(+), 30 deletions(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 7007c8cd..7dc39fed 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -218,11 +218,12 @@
:disable="formDialog.data.wallet == null || formDialog.data.name == null"
type="submit"
class="q-mr-md"
+ v-if="!formDialog.data.id"
>Create Gerty
From 1c56622d89e358e5817858f9beee335c76455403 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 16:14:45 +0100
Subject: [PATCH 190/844] formatted
---
lnbits/extensions/gerty/views_api.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index ec2612d3..4151e7a0 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -309,6 +309,7 @@ async def get_onchain_dashboard(gerty):
return areas
+
async def get_time_remaining_next_difficulty_adjustment(gerty):
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
@@ -380,4 +381,4 @@ async def get_mempool_stat(stat_slug: str, gerty):
"{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append),
font_size,
750, pos_y))
- return text
\ No newline at end of file
+ return text
From 07042a7d4ff0f6602e10c6d8703730778e24b20b Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 16:31:09 +0100
Subject: [PATCH 191/844] Formatted gerty extension
---
lnbits/core/views/api.py | 1 +
lnbits/core/views/generic.py | 3 +-
lnbits/core/views/public_api.py | 1 +
lnbits/extensions/gerty/__init__.py | 2 +
lnbits/extensions/gerty/crud.py | 4 +-
lnbits/extensions/gerty/helpers.py | 134 +-
lnbits/extensions/gerty/migrations.py | 2 +-
lnbits/extensions/gerty/models.py | 13 +-
lnbits/extensions/gerty/number_prefixer.py | 58 +-
.../gerty/templates/gerty/_api_docs.html | 3 +-
.../gerty/templates/gerty/gerty.html | 93 +-
.../gerty/templates/gerty/index.html | 1243 +++++++++--------
lnbits/extensions/gerty/views.py | 12 +-
lnbits/extensions/gerty/views_api.py | 236 ++--
tests/core/views/test_api.py | 5 +-
tests/extensions/boltz/conftest.py | 4 +-
tests/mocks.py | 1 +
17 files changed, 988 insertions(+), 827 deletions(-)
diff --git a/lnbits/core/views/api.py b/lnbits/core/views/api.py
index c33e874c..b533a8e2 100644
--- a/lnbits/core/views/api.py
+++ b/lnbits/core/views/api.py
@@ -34,6 +34,7 @@ from lnbits.utils.exchange_rates import (
fiat_amount_as_satoshis,
satoshis_amount_as_fiat,
)
+
from .. import core_app, db
from ..crud import (
create_payment,
diff --git a/lnbits/core/views/generic.py b/lnbits/core/views/generic.py
index fcc0365b..31a7b030 100644
--- a/lnbits/core/views/generic.py
+++ b/lnbits/core/views/generic.py
@@ -22,6 +22,8 @@ from lnbits.settings import (
LNBITS_SITE_TITLE,
SERVICE_FEE,
)
+
+from ...helpers import get_valid_extensions
from ..crud import (
create_account,
create_wallet,
@@ -32,7 +34,6 @@ from ..crud import (
update_user_extension,
)
from ..services import pay_invoice, redeem_lnurl_withdraw
-from ...helpers import get_valid_extensions
core_html_routes: APIRouter = APIRouter(tags=["Core NON-API Website Routes"])
diff --git a/lnbits/core/views/public_api.py b/lnbits/core/views/public_api.py
index 465693d9..ef8dc056 100644
--- a/lnbits/core/views/public_api.py
+++ b/lnbits/core/views/public_api.py
@@ -8,6 +8,7 @@ from loguru import logger
from starlette.requests import Request
from lnbits import bolt11
+
from .. import core_app
from ..crud import get_standalone_payment
from ..tasks import api_invoice_listeners
diff --git a/lnbits/extensions/gerty/__init__.py b/lnbits/extensions/gerty/__init__.py
index c5f526b5..03fdef12 100644
--- a/lnbits/extensions/gerty/__init__.py
+++ b/lnbits/extensions/gerty/__init__.py
@@ -11,8 +11,10 @@ db = Database("ext_gerty")
gerty_ext: APIRouter = APIRouter(prefix="/gerty", tags=["Gerty"])
+
def gerty_renderer():
return template_renderer(["lnbits/extensions/gerty/templates"])
+
from .views import * # noqa
from .views_api import * # noqa
diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py
index a472ef37..10b17df1 100644
--- a/lnbits/extensions/gerty/crud.py
+++ b/lnbits/extensions/gerty/crud.py
@@ -28,7 +28,7 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
data.lnbits_wallets,
data.mempool_endpoint,
data.exchange,
- data.display_preferences
+ data.display_preferences,
),
)
@@ -36,6 +36,7 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
assert gerty, "Newly created gerty couldn't be retrieved"
return gerty
+
async def update_gerty(gerty_id: str, **kwargs) -> Gerty:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
await db.execute(
@@ -43,6 +44,7 @@ async def update_gerty(gerty_id: str, **kwargs) -> Gerty:
)
return await get_gerty(gerty_id)
+
async def get_gerty(gerty_id: str) -> Optional[Gerty]:
row = await db.fetchone("SELECT * FROM gerty.gertys WHERE id = ?", (gerty_id,))
return Gerty(**row) if row else None
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index 3f7f88e3..b2c757a6 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -1,15 +1,18 @@
-from datetime import datetime, timedelta
-import pytz
-import httpx
import textwrap
+from datetime import datetime, timedelta
+
+import httpx
+import pytz
from loguru import logger
from .number_prefixer import *
+
def get_percent_difference(current, previous, precision=4):
difference = (current - previous) / current * 100
return "{0}{1}%".format("+" if difference > 0 else "", round(difference, precision))
+
# A helper function get a nicely formated dict for the text
def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None):
# Get line size by font size
@@ -30,26 +33,24 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
word_list = wrapper.wrap(text=text)
# logger.debug("number of chars = {0}".format(len(text)))
- multilineText = '\n'.join(word_list)
+ multilineText = "\n".join(word_list)
# logger.debug("number of lines = {0}".format(len(word_list)))
# logger.debug('multilineText')
# logger.debug(multilineText)
- text = {
- "value": multilineText,
- "size": font_size
- }
+ text = {"value": multilineText, "size": font_size}
if x_pos is None and y_pos is None:
- text['position'] = 'center'
+ text["position"] = "center"
else:
- text['x'] = x_pos
- text['y'] = y_pos
+ text["x"] = x_pos
+ text["y"] = y_pos
return text
+
# format a number for nice display output
def format_number(number, precision=None):
- return ("{:,}".format(round(number, precision)))
+ return "{:,}".format(round(number, precision))
async def get_mempool_recommended_fees(gerty):
@@ -58,6 +59,7 @@ async def get_mempool_recommended_fees(gerty):
r = await client.get(gerty.mempool_endpoint + "/api/v1/fees/recommended")
return r.json()
+
async def get_mining_dashboard(gerty):
areas = []
if isinstance(gerty.mempool_endpoint, str):
@@ -65,94 +67,141 @@ async def get_mining_dashboard(gerty):
# current hashrate
r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1w")
data = r.json()
- hashrateNow = data['currentHashrate']
- hashrateOneWeekAgo = data['hashrates'][6]['avgHashrate']
+ hashrateNow = data["currentHashrate"]
+ hashrateOneWeekAgo = data["hashrates"][6]["avgHashrate"]
text = []
text.append(get_text_item_dict("Current mining hashrate", 12))
- text.append(get_text_item_dict("{0}hash".format(si_format(hashrateNow, 6, True, " ")), 20))
- text.append(get_text_item_dict("{0} vs 7 days ago".format(get_percent_difference(hashrateNow, hashrateOneWeekAgo, 3)), 12))
+ text.append(
+ get_text_item_dict(
+ "{0}hash".format(si_format(hashrateNow, 6, True, " ")), 20
+ )
+ )
+ text.append(
+ get_text_item_dict(
+ "{0} vs 7 days ago".format(
+ get_percent_difference(hashrateNow, hashrateOneWeekAgo, 3)
+ ),
+ 12,
+ )
+ )
areas.append(text)
- r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
+ r = await client.get(
+ gerty.mempool_endpoint + "/api/v1/difficulty-adjustment"
+ )
# timeAvg
text = []
- progress = "{0}%".format(round(r.json()['progressPercent'], 2))
+ progress = "{0}%".format(round(r.json()["progressPercent"], 2))
text.append(get_text_item_dict("Progress through current epoch", 12))
text.append(get_text_item_dict(progress, 20))
areas.append(text)
# difficulty adjustment
text = []
- stat = r.json()['remainingTime']
+ stat = r.json()["remainingTime"]
text.append(get_text_item_dict("Time to next difficulty adjustment", 12))
text.append(get_text_item_dict(get_time_remaining(stat / 1000, 3), 20))
areas.append(text)
# difficultyChange
text = []
- difficultyChange = round(r.json()['difficultyChange'], 2)
+ difficultyChange = round(r.json()["difficultyChange"], 2)
text.append(get_text_item_dict("Estimated difficulty change", 12))
- text.append(get_text_item_dict("{0}{1}%".format("+" if difficultyChange > 0 else "", round(difficultyChange, 2)), 20))
+ text.append(
+ get_text_item_dict(
+ "{0}{1}%".format(
+ "+" if difficultyChange > 0 else "", round(difficultyChange, 2)
+ ),
+ 20,
+ )
+ )
areas.append(text)
r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1m")
data = r.json()
stat = {}
- stat['current'] = data['currentDifficulty']
- stat['previous'] = data['difficulty'][len(data['difficulty']) - 2]['difficulty']
+ stat["current"] = data["currentDifficulty"]
+ stat["previous"] = data["difficulty"][len(data["difficulty"]) - 2][
+ "difficulty"
+ ]
return areas
+
async def api_get_lightning_stats(gerty):
stat = {}
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
- r = await client.get(gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest")
+ r = await client.get(
+ gerty.mempool_endpoint + "/api/v1/lightning/statistics/latest"
+ )
data = r.json()
return data
+
async def get_lightning_stats(gerty):
data = await api_get_lightning_stats(gerty)
areas = []
text = []
text.append(get_text_item_dict("Channel Count", 12))
- text.append(get_text_item_dict(format_number(data['latest']['channel_count']), 20))
- difference = get_percent_difference(current=data['latest']['channel_count'],
- previous=data['previous']['channel_count'])
+ text.append(get_text_item_dict(format_number(data["latest"]["channel_count"]), 20))
+ difference = get_percent_difference(
+ current=data["latest"]["channel_count"],
+ previous=data["previous"]["channel_count"],
+ )
text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
areas.append(text)
text = []
text.append(get_text_item_dict("Number of Nodes", 12))
- text.append(get_text_item_dict(format_number(data['latest']['node_count']), 20))
- difference = get_percent_difference(current=data['latest']['node_count'], previous=data['previous']['node_count'])
+ text.append(get_text_item_dict(format_number(data["latest"]["node_count"]), 20))
+ difference = get_percent_difference(
+ current=data["latest"]["node_count"], previous=data["previous"]["node_count"]
+ )
text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
areas.append(text)
text = []
text.append(get_text_item_dict("Total Capacity", 12))
- avg_capacity = float(data['latest']['total_capacity']) / float(100000000)
- text.append(get_text_item_dict("{0} BTC".format(format_number(avg_capacity, 2)), 20))
- difference = get_percent_difference(current=data['latest']['total_capacity'], previous=data['previous']['total_capacity'])
+ avg_capacity = float(data["latest"]["total_capacity"]) / float(100000000)
+ text.append(
+ get_text_item_dict("{0} BTC".format(format_number(avg_capacity, 2)), 20)
+ )
+ difference = get_percent_difference(
+ current=data["latest"]["total_capacity"],
+ previous=data["previous"]["total_capacity"],
+ )
text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
areas.append(text)
text = []
text.append(get_text_item_dict("Average Channel Capacity", 12))
- text.append(get_text_item_dict("{0} sats".format(format_number(data['latest']['avg_capacity'])), 20))
- difference = get_percent_difference(current=data['latest']['avg_capacity'], previous=data['previous']['avg_capacity'])
+ text.append(
+ get_text_item_dict(
+ "{0} sats".format(format_number(data["latest"]["avg_capacity"])), 20
+ )
+ )
+ difference = get_percent_difference(
+ current=data["latest"]["avg_capacity"],
+ previous=data["previous"]["avg_capacity"],
+ )
text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
areas.append(text)
return areas
+
def get_next_update_time(sleep_time_seconds: int = 0, timezone: str = "Europe/London"):
utc_now = pytz.utc.localize(datetime.utcnow())
next_refresh_time = utc_now + timedelta(0, sleep_time_seconds)
local_refresh_time = next_refresh_time.astimezone(pytz.timezone(timezone))
- return "{0} {1}".format("I'll wake up at" if gerty_should_sleep() else "Next update at",local_refresh_time.strftime("%H:%M on %e %b %Y"))
+ return "{0} {1}".format(
+ "I'll wake up at" if gerty_should_sleep() else "Next update at",
+ local_refresh_time.strftime("%H:%M on %e %b %Y"),
+ )
+
def gerty_should_sleep(timezone: str = "Europe/London"):
utc_now = pytz.utc.localize(datetime.utcnow())
@@ -161,13 +210,12 @@ def gerty_should_sleep(timezone: str = "Europe/London"):
hours = int(hours)
logger.debug("HOURS")
logger.debug(hours)
- if(hours >= 22 and hours <= 23):
+ if hours >= 22 and hours <= 23:
return True
else:
return False
-
def get_date_suffix(dayNumber):
if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
return "th"
@@ -178,10 +226,10 @@ def get_date_suffix(dayNumber):
def get_time_remaining(seconds, granularity=2):
intervals = (
# ('weeks', 604800), # 60 * 60 * 24 * 7
- ('days', 86400), # 60 * 60 * 24
- ('hours', 3600), # 60 * 60
- ('minutes', 60),
- ('seconds', 1),
+ ("days", 86400), # 60 * 60 * 24
+ ("hours", 3600), # 60 * 60
+ ("minutes", 60),
+ ("seconds", 1),
)
result = []
@@ -191,6 +239,6 @@ def get_time_remaining(seconds, granularity=2):
if value:
seconds -= value * count
if value == 1:
- name = name.rstrip('s')
+ name = name.rstrip("s")
result.append("{} {}".format(round(value), name))
- return ', '.join(result[:granularity])
+ return ", ".join(result[:granularity])
diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py
index 459fc880..0e15b68e 100644
--- a/lnbits/extensions/gerty/migrations.py
+++ b/lnbits/extensions/gerty/migrations.py
@@ -15,4 +15,4 @@ async def m001_initial(db):
display_preferences TEXT
);
"""
- )
\ No newline at end of file
+ )
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index fc7a3377..89707a86 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -4,16 +4,21 @@ from typing import Optional
from fastapi import Query
from pydantic import BaseModel
+
class Gerty(BaseModel):
id: str = Query(None)
name: str
wallet: str
refresh_time: int = Query(None)
- lnbits_wallets: str = Query(None) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
- mempool_endpoint: str = Query(None) # Mempool endpoint to use
- exchange: str = Query(None) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
+ lnbits_wallets: str = Query(
+ None
+ ) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
+ mempool_endpoint: str = Query(None) # Mempool endpoint to use
+ exchange: str = Query(
+ None
+ ) # BTC <-> Fiat exchange rate to pull ie "USD", in 0.0001 and sats
display_preferences: str = Query(None)
@classmethod
def from_row(cls, row: Row) -> "Gerty":
- return cls(**dict(row))
\ No newline at end of file
+ return cls(**dict(row))
diff --git a/lnbits/extensions/gerty/number_prefixer.py b/lnbits/extensions/gerty/number_prefixer.py
index 1ba8c024..eab684e7 100644
--- a/lnbits/extensions/gerty/number_prefixer.py
+++ b/lnbits/extensions/gerty/number_prefixer.py
@@ -1,48 +1,51 @@
import math
+
def si_classifier(val):
suffixes = {
- 24:{'long_suffix':'yotta', 'short_suffix':'Y', 'scalar':10**24},
- 21:{'long_suffix':'zetta', 'short_suffix':'Z', 'scalar':10**21},
- 18:{'long_suffix':'exa', 'short_suffix':'E', 'scalar':10**18},
- 15:{'long_suffix':'peta', 'short_suffix':'P', 'scalar':10**15},
- 12:{'long_suffix':'tera', 'short_suffix':'T', 'scalar':10**12},
- 9:{'long_suffix':'giga', 'short_suffix':'G', 'scalar':10**9},
- 6:{'long_suffix':'mega', 'short_suffix':'M', 'scalar':10**6},
- 3:{'long_suffix':'kilo', 'short_suffix':'k', 'scalar':10**3},
- 0:{'long_suffix':'', 'short_suffix':'', 'scalar':10**0},
- -3:{'long_suffix':'milli', 'short_suffix':'m', 'scalar':10**-3},
- -6:{'long_suffix':'micro', 'short_suffix':'µ', 'scalar':10**-6},
- -9:{'long_suffix':'nano', 'short_suffix':'n', 'scalar':10**-9},
- -12:{'long_suffix':'pico', 'short_suffix':'p', 'scalar':10**-12},
- -15:{'long_suffix':'femto', 'short_suffix':'f', 'scalar':10**-15},
- -18:{'long_suffix':'atto', 'short_suffix':'a', 'scalar':10**-18},
- -21:{'long_suffix':'zepto', 'short_suffix':'z', 'scalar':10**-21},
- -24:{'long_suffix':'yocto', 'short_suffix':'y', 'scalar':10**-24}
+ 24: {"long_suffix": "yotta", "short_suffix": "Y", "scalar": 10**24},
+ 21: {"long_suffix": "zetta", "short_suffix": "Z", "scalar": 10**21},
+ 18: {"long_suffix": "exa", "short_suffix": "E", "scalar": 10**18},
+ 15: {"long_suffix": "peta", "short_suffix": "P", "scalar": 10**15},
+ 12: {"long_suffix": "tera", "short_suffix": "T", "scalar": 10**12},
+ 9: {"long_suffix": "giga", "short_suffix": "G", "scalar": 10**9},
+ 6: {"long_suffix": "mega", "short_suffix": "M", "scalar": 10**6},
+ 3: {"long_suffix": "kilo", "short_suffix": "k", "scalar": 10**3},
+ 0: {"long_suffix": "", "short_suffix": "", "scalar": 10**0},
+ -3: {"long_suffix": "milli", "short_suffix": "m", "scalar": 10**-3},
+ -6: {"long_suffix": "micro", "short_suffix": "µ", "scalar": 10**-6},
+ -9: {"long_suffix": "nano", "short_suffix": "n", "scalar": 10**-9},
+ -12: {"long_suffix": "pico", "short_suffix": "p", "scalar": 10**-12},
+ -15: {"long_suffix": "femto", "short_suffix": "f", "scalar": 10**-15},
+ -18: {"long_suffix": "atto", "short_suffix": "a", "scalar": 10**-18},
+ -21: {"long_suffix": "zepto", "short_suffix": "z", "scalar": 10**-21},
+ -24: {"long_suffix": "yocto", "short_suffix": "y", "scalar": 10**-24},
}
- exponent = int(math.floor(math.log10(abs(val))/3.0)*3)
+ exponent = int(math.floor(math.log10(abs(val)) / 3.0) * 3)
return suffixes.get(exponent, None)
+
def si_formatter(value):
- '''
+ """
Return a triple of scaled value, short suffix, long suffix, or None if
the value cannot be classified.
- '''
+ """
classifier = si_classifier(value)
if classifier == None:
# Don't know how to classify this value
return None
- scaled = value / classifier['scalar']
- return (scaled, classifier['short_suffix'], classifier['long_suffix'])
+ scaled = value / classifier["scalar"]
+ return (scaled, classifier["short_suffix"], classifier["long_suffix"])
-def si_format(value, precision=4, long_form=False, separator=''):
- '''
+
+def si_format(value, precision=4, long_form=False, separator=""):
+ """
"SI prefix" formatted string: return a string with the given precision
and an appropriate order-of-3-magnitudes suffix, e.g.:
si_format(1001.0) => '1.00K'
si_format(0.00000000123, long_form=True, separator=' ') => '1.230 nano'
- '''
+ """
scaled, short_suffix, long_suffix = si_formatter(value)
if scaled == None:
@@ -58,5 +61,6 @@ def si_format(value, precision=4, long_form=False, separator=''):
else:
precision = precision - 3
- return '{scaled:.{precision}f}{separator}{suffix}'.format(
- scaled=scaled, precision=precision, separator=separator, suffix=suffix)
\ No newline at end of file
+ return "{scaled:.{precision}f}{separator}{suffix}".format(
+ scaled=scaled, precision=precision, separator=separator, suffix=suffix
+ )
diff --git a/lnbits/extensions/gerty/templates/gerty/_api_docs.html b/lnbits/extensions/gerty/templates/gerty/_api_docs.html
index 889760e1..db141279 100644
--- a/lnbits/extensions/gerty/templates/gerty/_api_docs.html
+++ b/lnbits/extensions/gerty/templates/gerty/_api_docs.html
@@ -71,7 +71,8 @@
Curl example
curl -X DELETE {{ request.base_url
- }}gerty/api/v1/gertys/<gerty_id> -H "X-Api-Key: <admin_key>"
+ }}gerty/api/v1/gertys/<gerty_id> -H "X-Api-Key:
+ <admin_key>"
diff --git a/lnbits/extensions/gerty/templates/gerty/gerty.html b/lnbits/extensions/gerty/templates/gerty/gerty.html
index e4401fe1..216e5721 100644
--- a/lnbits/extensions/gerty/templates/gerty/gerty.html
+++ b/lnbits/extensions/gerty/templates/gerty/gerty.html
@@ -1,40 +1,74 @@
-{% extends "public.html" %} {% block toolbar_title %} {{ gerty.name }}{% endblock %}{% block page %}
-{% raw %}
+{% extends "public.html" %} {% block toolbar_title %} {{ gerty.name }}{%
+endblock %}{% block page %} {% raw %}
-
- "{{gerty.sats_quote[0].text}}" ~ Satoshi {{gerty.sats_quote[0].date}}
-
+
+ "{{gerty.sats_quote[0].text}}" ~ Satoshi {{gerty.sats_quote[0].date}}
+
-
+
{{gerty.exchange[0].amount.toFixed(2)}} {{gerty.exchange[0].fiat}}
-
-
-
-
- sentiment_satisfied
-
+
+
+
+
+ sentiment_satisfied
+
+
-
-
{{gertywallet.amount}}
{{gertywallet.name}}
-
-
-
+
+
+ {{gertywallet.amount}}
+
+
{{gertywallet.name}}
+
+
+
-
+
- Onchain Stats
- Difficulty Progress Percent
-
-
-
-
-
+ Onchain Stats
+ Difficulty Progress Percent
+
+
+
+
+
@@ -46,17 +80,14 @@
- LN Stats
+ LN Stats
-
- {{gerty.ln}}
-
+ {{gerty.ln}}
-{% endraw %}
-{% endblock %} {% block scripts %}
+{% endraw %} {% endblock %} {% block scripts %}
+ LNbits.utils
+ .confirmDialog('Are you sure you want to delete this Gerty?')
+ .onOk(function () {
+ LNbits.api
+ .request(
+ 'DELETE',
+ '/gerty/api/v1/gerty/' + gertyId,
+ _.findWhere(self.g.user.wallets, {id: gerty.wallet}).adminkey
+ )
+ .then(function (response) {
+ self.gertys = _.reject(self.gertys, function (obj) {
+ return obj.id == gertyId
+ })
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ })
+ },
+ exportCSV: function () {
+ LNbits.utils.exportCSV(this.gertysTable.columns, this.gertys)
+ }
+ },
+ created: function () {
+ if (this.g.user.wallets.length) {
+ this.getGertys()
+ }
+ }
+ })
+
+{% endblock %} {% block styles %}
+
{% endblock %}
-
-{% block styles %}
-
-{% endblock %}
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/views.py b/lnbits/extensions/gerty/views.py
index 630cb48b..e0586169 100644
--- a/lnbits/extensions/gerty/views.py
+++ b/lnbits/extensions/gerty/views.py
@@ -1,8 +1,10 @@
+import json
from http import HTTPStatus
from fastapi import Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates
+from loguru import logger
from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse
@@ -14,18 +16,16 @@ from . import gerty_ext, gerty_renderer
from .crud import get_gerty
from .views_api import api_gerty_json
-import json
-
-from loguru import logger
-
templates = Jinja2Templates(directory="templates")
+
@gerty_ext.get("/", response_class=HTMLResponse)
async def index(request: Request, user: User = Depends(check_user_exists)):
return gerty_renderer().TemplateResponse(
"gerty/index.html", {"request": request, "user": user.dict()}
)
+
@gerty_ext.get("/{gerty_id}", response_class=HTMLResponse)
async def display(request: Request, gerty_id):
gerty = await get_gerty(gerty_id)
@@ -34,4 +34,6 @@ async def display(request: Request, gerty_id):
status_code=HTTPStatus.NOT_FOUND, detail="Gerty does not exist."
)
gertyData = await api_gerty_json(gerty_id)
- return gerty_renderer().TemplateResponse("gerty/gerty.html", {"request": request, "gerty": gertyData})
\ No newline at end of file
+ return gerty_renderer().TemplateResponse(
+ "gerty/gerty.html", {"request": request, "gerty": gertyData}
+ )
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 4151e7a0..d89a557c 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -1,37 +1,35 @@
-import math
-from http import HTTPStatus
import json
-import httpx
-import random
+import math
import os
+import random
import time
from datetime import datetime
+from http import HTTPStatus
+
+import httpx
from fastapi import Query
from fastapi.params import Depends
+from fastapi.templating import Jinja2Templates
from lnurl import decode as decode_lnurl
from loguru import logger
from starlette.exceptions import HTTPException
-from lnbits.core.crud import get_wallet_for_key
-from lnbits.core.crud import get_user
+from lnbits.core.crud import get_user, get_wallet_for_key
from lnbits.core.services import create_invoice
from lnbits.core.views.api import api_payment, api_wallet
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from fastapi.templating import Jinja2Templates
-
-from .helpers import *
-
-from . import gerty_ext
-from .crud import create_gerty, update_gerty, delete_gerty, get_gerty, get_gertys
-from .models import Gerty
-
from lnbits.utils.exchange_rates import satoshis_amount_as_fiat
+
from ...settings import LNBITS_PATH
+from . import gerty_ext
+from .crud import create_gerty, delete_gerty, get_gerty, get_gertys, update_gerty
+from .helpers import *
+from .models import Gerty
@gerty_ext.get("/api/v1/gerty", status_code=HTTPStatus.OK)
async def api_gertys(
- all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
+ all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
):
wallet_ids = [wallet.wallet.id]
if all_wallets:
@@ -43,9 +41,9 @@ async def api_gertys(
@gerty_ext.post("/api/v1/gerty", status_code=HTTPStatus.CREATED)
@gerty_ext.put("/api/v1/gerty/{gerty_id}", status_code=HTTPStatus.OK)
async def api_link_create_or_update(
- data: Gerty,
- wallet: WalletTypeInfo = Depends(get_key_type),
- gerty_id: str = Query(None),
+ data: Gerty,
+ wallet: WalletTypeInfo = Depends(get_key_type),
+ gerty_id: str = Query(None),
):
if gerty_id:
gerty = await get_gerty(gerty_id)
@@ -70,7 +68,7 @@ async def api_link_create_or_update(
@gerty_ext.delete("/api/v1/gerty/{gerty_id}")
async def api_gerty_delete(
- gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
+ gerty_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
):
gerty = await get_gerty(gerty_id)
@@ -88,10 +86,11 @@ async def api_gerty_delete(
#######################
+
@gerty_ext.get("/api/v1/gerty/satoshiquote", status_code=HTTPStatus.OK)
async def api_gerty_satoshi():
- maxQuoteLength = 186;
- with open(os.path.join(LNBITS_PATH, 'extensions/gerty/static/satoshi.json')) as fd:
+ maxQuoteLength = 186
+ with open(os.path.join(LNBITS_PATH, "extensions/gerty/static/satoshi.json")) as fd:
satoshiQuotes = json.load(fd)
quote = satoshiQuotes[random.randint(0, len(satoshiQuotes) - 1)]
# logger.debug(quote.text)
@@ -103,10 +102,7 @@ async def api_gerty_satoshi():
@gerty_ext.get("/api/v1/gerty/{gerty_id}/{p}")
-async def api_gerty_json(
- gerty_id: str,
- p: int = None # page number
-):
+async def api_gerty_json(gerty_id: str, p: int = None): # page number
gerty = await get_gerty(gerty_id)
if not gerty:
@@ -129,7 +125,7 @@ async def api_gerty_json(
logger.debug("Screeens " + str(enabled_screens))
data = await get_screen_data(p, enabled_screens, gerty)
- next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1;
+ next_screen_number = 0 if ((p + 1) >= enabled_screen_count) else p + 1
# get the sleep time
sleep_time = gerty.refresh_time if gerty.refresh_time else 300
@@ -143,14 +139,14 @@ async def api_gerty_json(
"requestTimestamp": get_next_update_time(sleep_time),
"nextScreenNumber": next_screen_number,
"showTextBoundRect": False,
- "name": gerty.name
+ "name": gerty.name,
},
"screen": {
"slug": get_screen_slug_by_index(p, enabled_screens),
"group": get_screen_slug_by_index(p, enabled_screens),
- "title": data['title'],
- "areas": data['areas']
- }
+ "title": data["title"],
+ "areas": data["areas"],
+ },
}
@@ -163,7 +159,7 @@ def get_screen_slug_by_index(index: int, screens_list):
async def get_screen_data(screen_num: int, screens_list: dict, gerty):
screen_slug = get_screen_slug_by_index(screen_num, screens_list)
# first get the relevant slug from the display_preferences
- logger.debug('screen_slug')
+ logger.debug("screen_slug")
logger.debug(screen_slug)
areas = []
title = ""
@@ -188,8 +184,8 @@ async def get_screen_data(screen_num: int, screens_list: dict, gerty):
areas = await get_lightning_stats(gerty)
data = {}
- data['title'] = title
- data['areas'] = areas
+ data["title"] = title
+ data["areas"] = areas
return data
@@ -208,8 +204,10 @@ async def get_dashboard(gerty):
wallets = await get_lnbits_wallet_balances(gerty)
text = []
for wallet in wallets:
- text.append(get_text_item_dict("{0}".format(wallet['name']), 15))
- text.append(get_text_item_dict("{0} sats".format(format_number(wallet['balance'])), 20))
+ text.append(get_text_item_dict("{0}".format(wallet["name"]), 15))
+ text.append(
+ get_text_item_dict("{0} sats".format(format_number(wallet["balance"])), 20)
+ )
areas.append(text)
# Mempool fees
@@ -220,7 +218,11 @@ async def get_dashboard(gerty):
# difficulty adjustment time
text = []
- text.append(get_text_item_dict(await get_time_remaining_next_difficulty_adjustment(gerty), 15))
+ text.append(
+ get_text_item_dict(
+ await get_time_remaining_next_difficulty_adjustment(gerty), 15
+ )
+ )
text.append(get_text_item_dict("until next difficulty adjustment", 12))
areas.append(text)
@@ -235,18 +237,20 @@ async def get_lnbits_wallet_balances(gerty):
wallet = await get_wallet_for_key(key=lnbits_wallet)
logger.debug(wallet.name)
if wallet:
- wallets.append({
- "name": wallet.name,
- "balance": wallet.balance_msat / 1000,
- "inkey": wallet.inkey,
- })
+ wallets.append(
+ {
+ "name": wallet.name,
+ "balance": wallet.balance_msat / 1000,
+ "inkey": wallet.inkey,
+ }
+ )
return wallets
async def get_placeholder_text():
return [
get_text_item_dict("Some placeholder text", 15, 10, 50),
- get_text_item_dict("Some placeholder text", 15, 10, 50)
+ get_text_item_dict("Some placeholder text", 15, 10, 50),
]
@@ -255,10 +259,12 @@ async def get_satoshi_quotes():
text = []
quote = await api_gerty_satoshi()
if quote:
- if quote['text']:
- text.append(get_text_item_dict(quote['text'], 15))
- if quote['date']:
- text.append(get_text_item_dict("Satoshi Nakamoto - {0}".format(quote['date']), 15))
+ if quote["text"]:
+ text.append(get_text_item_dict(quote["text"], 15))
+ if quote["date"]:
+ text.append(
+ get_text_item_dict("Satoshi Nakamoto - {0}".format(quote["date"]), 15)
+ )
return text
@@ -270,7 +276,11 @@ async def get_exchange_rate(gerty):
amount = await satoshis_amount_as_fiat(100000000, gerty.exchange)
if amount:
price = format_number(amount)
- text.append(get_text_item_dict("Current {0}/BTC price".format(gerty.exchange), 15))
+ text.append(
+ get_text_item_dict(
+ "Current {0}/BTC price".format(gerty.exchange), 15
+ )
+ )
text.append(get_text_item_dict(price, 80))
except:
pass
@@ -281,29 +291,43 @@ async def get_onchain_dashboard(gerty):
areas = []
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
- r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
+ r = await client.get(
+ gerty.mempool_endpoint + "/api/v1/difficulty-adjustment"
+ )
text = []
- stat = round(r.json()['progressPercent'])
- text.append(get_text_item_dict("Progress through current difficulty epoch", 12))
+ stat = round(r.json()["progressPercent"])
+ text.append(
+ get_text_item_dict("Progress through current difficulty epoch", 12)
+ )
text.append(get_text_item_dict("{0}%".format(stat), 20))
areas.append(text)
text = []
- stat = r.json()['estimatedRetargetDate']
+ stat = r.json()["estimatedRetargetDate"]
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
- text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 12))
+ text.append(
+ get_text_item_dict("Estimated date of next difficulty adjustment", 12)
+ )
text.append(get_text_item_dict(dt, 20))
areas.append(text)
text = []
- stat = r.json()['remainingBlocks']
- text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 12))
+ stat = r.json()["remainingBlocks"]
+ text.append(
+ get_text_item_dict(
+ "Blocks remaining until next difficulty adjustment", 12
+ )
+ )
text.append(get_text_item_dict("{0}".format(format_number(stat)), 20))
areas.append(text)
text = []
- stat = r.json()['remainingTime']
- text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 12))
+ stat = r.json()["remainingTime"]
+ text.append(
+ get_text_item_dict(
+ "Blocks remaining until next difficulty adjustment", 12
+ )
+ )
text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
areas.append(text)
@@ -313,8 +337,10 @@ async def get_onchain_dashboard(gerty):
async def get_time_remaining_next_difficulty_adjustment(gerty):
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
- r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
- stat = r.json()['remainingTime']
+ r = await client.get(
+ gerty.mempool_endpoint + "/api/v1/difficulty-adjustment"
+ )
+ stat = r.json()["remainingTime"]
time = get_time_remaining(stat / 1000, 3)
return time
@@ -331,17 +357,15 @@ async def get_mempool_stat(stat_slug: str, gerty):
text = []
if isinstance(gerty.mempool_endpoint, str):
async with httpx.AsyncClient() as client:
- if (
- stat_slug == "mempool_tx_count"
- ):
+ if stat_slug == "mempool_tx_count":
r = await client.get(gerty.mempool_endpoint + "/api/mempool")
if stat_slug == "mempool_tx_count":
- stat = round(r.json()['count'])
+ stat = round(r.json()["count"])
text.append(get_text_item_dict("Transactions in the mempool", 15))
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 80))
- elif (
- stat_slug == "mempool_recommended_fees"
- ):
+ text.append(
+ get_text_item_dict("{0}".format(format_number(stat)), 80)
+ )
+ elif stat_slug == "mempool_recommended_fees":
y_offset = 60
fees = await get_mempool_recommended_fees(gerty)
pos_y = 80 + y_offset
@@ -350,35 +374,75 @@ async def get_mempool_stat(stat_slug: str, gerty):
text.append(get_text_item_dict("Recommended Tx Fees", 20, 240, pos_y))
pos_y = 280 + y_offset
- text.append(get_text_item_dict("{0}".format("No Priority"), 15, 30, pos_y))
- text.append(get_text_item_dict("{0}".format("Low Priority"), 15, 235, pos_y))
- text.append(get_text_item_dict("{0}".format("Medium Priority"), 15, 460, pos_y))
- text.append(get_text_item_dict("{0}".format("High Priority"), 15, 750, pos_y))
+ text.append(
+ get_text_item_dict("{0}".format("No Priority"), 15, 30, pos_y)
+ )
+ text.append(
+ get_text_item_dict("{0}".format("Low Priority"), 15, 235, pos_y)
+ )
+ text.append(
+ get_text_item_dict("{0}".format("Medium Priority"), 15, 460, pos_y)
+ )
+ text.append(
+ get_text_item_dict("{0}".format("High Priority"), 15, 750, pos_y)
+ )
pos_y = 340 + y_offset
font_size = 15
fee_append = "/vB"
fee_rate = fees["economyFee"]
- text.append(get_text_item_dict(
- "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append),
- font_size,
- 30, pos_y))
+ text.append(
+ get_text_item_dict(
+ "{0} {1}{2}".format(
+ format_number(fee_rate),
+ ("sat" if fee_rate == 1 else "sats"),
+ fee_append,
+ ),
+ font_size,
+ 30,
+ pos_y,
+ )
+ )
fee_rate = fees["hourFee"]
- text.append(get_text_item_dict(
- "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append),
- font_size,
- 235, pos_y))
+ text.append(
+ get_text_item_dict(
+ "{0} {1}{2}".format(
+ format_number(fee_rate),
+ ("sat" if fee_rate == 1 else "sats"),
+ fee_append,
+ ),
+ font_size,
+ 235,
+ pos_y,
+ )
+ )
fee_rate = fees["halfHourFee"]
- text.append(get_text_item_dict(
- "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append),
- font_size,
- 460, pos_y))
+ text.append(
+ get_text_item_dict(
+ "{0} {1}{2}".format(
+ format_number(fee_rate),
+ ("sat" if fee_rate == 1 else "sats"),
+ fee_append,
+ ),
+ font_size,
+ 460,
+ pos_y,
+ )
+ )
fee_rate = fees["fastestFee"]
- text.append(get_text_item_dict(
- "{0} {1}{2}".format(format_number(fee_rate), ("sat" if fee_rate == 1 else "sats"), fee_append),
- font_size,
- 750, pos_y))
+ text.append(
+ get_text_item_dict(
+ "{0} {1}{2}".format(
+ format_number(fee_rate),
+ ("sat" if fee_rate == 1 else "sats"),
+ fee_append,
+ ),
+ font_size,
+ 750,
+ pos_y,
+ )
+ )
return text
diff --git a/tests/core/views/test_api.py b/tests/core/views/test_api.py
index 81468fd1..c6280153 100644
--- a/tests/core/views/test_api.py
+++ b/tests/core/views/test_api.py
@@ -3,10 +3,9 @@ import hashlib
import pytest
from lnbits import bolt11
-from lnbits.core.views.api import (
- api_payment,
-)
+from lnbits.core.views.api import api_payment
from lnbits.settings import wallet_class
+
from ...helpers import get_random_invoice_data, is_regtest
diff --git a/tests/extensions/boltz/conftest.py b/tests/extensions/boltz/conftest.py
index 1bd1c638..930a1bfb 100644
--- a/tests/extensions/boltz/conftest.py
+++ b/tests/extensions/boltz/conftest.py
@@ -1,9 +1,7 @@
import pytest_asyncio
from lnbits.extensions.boltz.boltz import create_reverse_swap
-from lnbits.extensions.boltz.models import (
- CreateReverseSubmarineSwap,
-)
+from lnbits.extensions.boltz.models import CreateReverseSubmarineSwap
@pytest_asyncio.fixture(scope="session")
diff --git a/tests/mocks.py b/tests/mocks.py
index 7e2df4f7..3fc0efae 100644
--- a/tests/mocks.py
+++ b/tests/mocks.py
@@ -4,6 +4,7 @@ from lnbits import bolt11
from lnbits.settings import WALLET
from lnbits.wallets.base import PaymentResponse, PaymentStatus, StatusResponse
from lnbits.wallets.fake import FakeWallet
+
from .helpers import get_random_string, is_fake
From f636951d3ce056c1775d22d4102677e325eb6ef4 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 16:40:33 +0100
Subject: [PATCH 192/844] Added gerty migration for UTC offset col
---
lnbits/extensions/gerty/migrations.py | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py
index 0e15b68e..61722835 100644
--- a/lnbits/extensions/gerty/migrations.py
+++ b/lnbits/extensions/gerty/migrations.py
@@ -16,3 +16,11 @@ async def m001_initial(db):
);
"""
)
+
+async def m002_add_utc_offset_col(db):
+ """
+ support for UTC offset
+ """
+ await db.execute(
+ "ALTER TABLE gerty.gertys ADD COLUMN utc_offset INT;"
+ )
From ecfd4b3d66927c87489866a906bb448ed53828f3 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 16:58:14 +0100
Subject: [PATCH 193/844] Added UTC field to gerty modal
---
lnbits/extensions/gerty/crud.py | 8 ++++--
lnbits/extensions/gerty/models.py | 1 +
.../gerty/templates/gerty/index.html | 28 +++++++++++++------
3 files changed, 26 insertions(+), 11 deletions(-)
diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py
index 10b17df1..3850737f 100644
--- a/lnbits/extensions/gerty/crud.py
+++ b/lnbits/extensions/gerty/crud.py
@@ -14,21 +14,25 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
id,
name,
wallet,
+ utc_offset,
lnbits_wallets,
mempool_endpoint,
exchange,
- display_preferences
+ display_preferences,
+ refresh_time
)
- VALUES (?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
gerty_id,
data.name,
data.wallet,
+ data.utc_offset,
data.lnbits_wallets,
data.mempool_endpoint,
data.exchange,
data.display_preferences,
+ data.refresh_time,
),
)
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index 89707a86..855a30c9 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -10,6 +10,7 @@ class Gerty(BaseModel):
name: str
wallet: str
refresh_time: int = Query(None)
+ utc_offset: int = Query(None)
lnbits_wallets: str = Query(
None
) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 64afc683..dc6cea2a 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -179,6 +179,17 @@
>
+
+ Enter a UTC time offset value (e.g. -1)
+
+
Use the toggles below to control what your Gerty will display
Date: Thu, 20 Oct 2022 17:01:03 +0100
Subject: [PATCH 194/844] Fix link to API
---
lnbits/extensions/gerty/helpers.py | 2 +-
lnbits/extensions/gerty/templates/gerty/index.html | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index b2c757a6..c3055357 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -194,7 +194,7 @@ async def get_lightning_stats(gerty):
def get_next_update_time(sleep_time_seconds: int = 0, timezone: str = "Europe/London"):
- utc_now = pytz.utc.localize(datetime.utcnow())
+ utc_now = datetime.utcnow()
next_refresh_time = utc_now + timedelta(0, sleep_time_seconds)
local_refresh_time = next_refresh_time.astimezone(pytz.timezone(timezone))
return "{0} {1}".format(
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index dc6cea2a..10967fe5 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -263,7 +263,7 @@
)
obj.fsat = new Intl.NumberFormat(LOCALE).format(obj.amount)
obj.gerty = ['/gerty/', obj.id].join('')
- obj.gertyJson = ['/gerty/api/v1/gerty/', obj.id].join('')
+ obj.gertyJson = ['/gerty/api/v1/gerty/', obj.id, '/0'].join('')
return obj
}
From 82d4933d74b71b8461f0a14aaa915d05cee9d627 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 17:01:21 +0100
Subject: [PATCH 195/844] Fix link to API
---
lnbits/extensions/gerty/templates/gerty/index.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 10967fe5..3c258c1c 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -69,7 +69,7 @@
:href="props.row.gertyJson"
target="_blank"
>
- Launch software Gerty
+ View Gerty API
From 65a3e4feb29a5ea0a28213efbaa12ac3d0512b39 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 17:04:34 +0100
Subject: [PATCH 196/844] Font size changes on onchain data dashboard
---
lnbits/extensions/gerty/helpers.py | 2 +-
lnbits/extensions/gerty/views_api.py | 14 +++++++-------
2 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index c3055357..336094b3 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -102,7 +102,7 @@ async def get_mining_dashboard(gerty):
text = []
stat = r.json()["remainingTime"]
text.append(get_text_item_dict("Time to next difficulty adjustment", 12))
- text.append(get_text_item_dict(get_time_remaining(stat / 1000, 3), 20))
+ text.append(get_text_item_dict(get_time_remaining(stat / 1000, 3), 12))
areas.append(text)
# difficultyChange
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index d89a557c..9962a99d 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -297,16 +297,16 @@ async def get_onchain_dashboard(gerty):
text = []
stat = round(r.json()["progressPercent"])
text.append(
- get_text_item_dict("Progress through current difficulty epoch", 12)
+ get_text_item_dict("Progress through current epoch", 12)
)
- text.append(get_text_item_dict("{0}%".format(stat), 20))
+ text.append(get_text_item_dict("{0}%".format(stat), 60))
areas.append(text)
text = []
stat = r.json()["estimatedRetargetDate"]
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
text.append(
- get_text_item_dict("Estimated date of next difficulty adjustment", 12)
+ get_text_item_dict("Date of next difficulty adjustment", 12)
)
text.append(get_text_item_dict(dt, 20))
areas.append(text)
@@ -315,20 +315,20 @@ async def get_onchain_dashboard(gerty):
stat = r.json()["remainingBlocks"]
text.append(
get_text_item_dict(
- "Blocks remaining until next difficulty adjustment", 12
+ "Blocks until next adjustment", 12
)
)
- text.append(get_text_item_dict("{0}".format(format_number(stat)), 20))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
areas.append(text)
text = []
stat = r.json()["remainingTime"]
text.append(
get_text_item_dict(
- "Blocks remaining until next difficulty adjustment", 12
+ "Blocks until next adjustment", 12
)
)
- text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
+ text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 60))
areas.append(text)
return areas
From e193c4ef4736be2637f08c498429ce51bc5c9851 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 17:15:12 +0100
Subject: [PATCH 197/844] Replaced pytz with utc_offset
---
lnbits/extensions/gerty/helpers.py | 17 ++++++++---------
lnbits/extensions/gerty/views_api.py | 5 +++--
poetry.lock | 14 +-------------
pyproject.toml | 1 -
requirements.txt | 1 -
5 files changed, 12 insertions(+), 26 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index 336094b3..d7e0e951 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -2,7 +2,6 @@ import textwrap
from datetime import datetime, timedelta
import httpx
-import pytz
from loguru import logger
from .number_prefixer import *
@@ -95,7 +94,7 @@ async def get_mining_dashboard(gerty):
text = []
progress = "{0}%".format(round(r.json()["progressPercent"], 2))
text.append(get_text_item_dict("Progress through current epoch", 12))
- text.append(get_text_item_dict(progress, 20))
+ text.append(get_text_item_dict(progress, 60))
areas.append(text)
# difficulty adjustment
@@ -114,7 +113,7 @@ async def get_mining_dashboard(gerty):
"{0}{1}%".format(
"+" if difficultyChange > 0 else "", round(difficultyChange, 2)
),
- 20,
+ 60,
)
)
areas.append(text)
@@ -193,19 +192,19 @@ async def get_lightning_stats(gerty):
return areas
-def get_next_update_time(sleep_time_seconds: int = 0, timezone: str = "Europe/London"):
+def get_next_update_time(sleep_time_seconds: int = 0, utc_offset: int = 0):
utc_now = datetime.utcnow()
next_refresh_time = utc_now + timedelta(0, sleep_time_seconds)
- local_refresh_time = next_refresh_time.astimezone(pytz.timezone(timezone))
+ local_refresh_time = next_refresh_time + timedelta(hours=utc_offset)
return "{0} {1}".format(
- "I'll wake up at" if gerty_should_sleep() else "Next update at",
+ "I'll wake up at" if gerty_should_sleep(utc_offset) else "Next update at",
local_refresh_time.strftime("%H:%M on %e %b %Y"),
)
-def gerty_should_sleep(timezone: str = "Europe/London"):
- utc_now = pytz.utc.localize(datetime.utcnow())
- local_time = utc_now.astimezone(pytz.timezone(timezone))
+def gerty_should_sleep(utc_offset: int = 0):
+ utc_now = datetime.utcnow()
+ local_time = utc_now + timedelta(hours=utc_offset)
hours = local_time.strftime("%H")
hours = int(hours)
logger.debug("HOURS")
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 9962a99d..51800730 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -129,14 +129,15 @@ async def api_gerty_json(gerty_id: str, p: int = None): # page number
# get the sleep time
sleep_time = gerty.refresh_time if gerty.refresh_time else 300
- if gerty_should_sleep():
+ utc_offset = gerty.utc_offset if gerty.utc_offset else 0
+ if gerty_should_sleep(utc_offset):
sleep_time_hours = 8
sleep_time = 60 * 60 * sleep_time_hours
return {
"settings": {
"refreshTime": sleep_time,
- "requestTimestamp": get_next_update_time(sleep_time),
+ "requestTimestamp": get_next_update_time(sleep_time, utc_offset),
"nextScreenNumber": next_screen_number,
"showTextBoundRect": False,
"name": gerty.name,
diff --git a/poetry.lock b/poetry.lock
index 8e9541fc..d5e61070 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -664,14 +664,6 @@ python-versions = ">=3.5"
[package.extras]
cli = ["click (>=5.0)"]
-[[package]]
-name = "pytz"
-version = "2022.4"
-description = "World timezone definitions, modern and historical"
-category = "main"
-optional = false
-python-versions = "*"
-
[[package]]
name = "PyYAML"
version = "5.4.1"
@@ -933,7 +925,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 = "401fa2739c9209df26cb1b2defaf90c5a4fcdafacc8eb2627f8d324857870281"
+content-hash = "2db4d8b644c07a599b10ecdd1d532f8fce5dea7afa0332cbebc9a37223f79ed4"
[metadata.files]
aiofiles = [
@@ -1466,10 +1458,6 @@ 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"},
]
-pytz = [
- {file = "pytz-2022.4-py2.py3-none-any.whl", hash = "sha256:2c0784747071402c6e99f0bafdb7da0fa22645f06554c7ae06bf6358897e9c91"},
- {file = "pytz-2022.4.tar.gz", hash = "sha256:48ce799d83b6f8aab2020e369b627446696619e79645419610b9facd909b3174"},
-]
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"},
diff --git a/pyproject.toml b/pyproject.toml
index 0484ca9e..e95c6a2e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -60,7 +60,6 @@ zipp = "3.5.0"
loguru = "0.5.3"
cffi = "1.15.0"
websocket-client = "1.3.3"
-pytz = "^2022.4"
[tool.poetry.dev-dependencies]
isort = "^5.10.1"
diff --git a/requirements.txt b/requirements.txt
index fd213ceb..697ea1d4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -33,7 +33,6 @@ pyparsing==3.0.9
pypng==0.20220715.0
pyqrcode==1.2.1
pyscss==1.4.0
-pytz=2022.4
python-dotenv==0.20.0
pyyaml==6.0
represent==1.6.0.post0
From aa33f74f5d70284fdaccc3ee1d45b588a2afef88 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 20 Oct 2022 17:20:47 +0100
Subject: [PATCH 198/844] Bug fix wwhen trying to get a gerty screen that
doesnt exist
---
lnbits/extensions/gerty/views_api.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 51800730..05a7f5d7 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -153,7 +153,10 @@ async def api_gerty_json(gerty_id: str, p: int = None): # page number
# Get a screen slug by its position in the screens_list
def get_screen_slug_by_index(index: int, screens_list):
- return list(screens_list)[index]
+ if(index < len(screens_list) - 1):
+ return list(screens_list)[index]
+ else:
+ return None
# Get a list of text items for the screen number
From 47c334de7a548fd82418e8a266e11d836345286f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?dni=20=E2=9A=A1?=
Date: Fri, 21 Oct 2022 10:00:47 +0200
Subject: [PATCH 199/844] 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 200/844] 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 201/844] 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 202/844] 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 203/844] 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 204/844] 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 205/844] 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 206/844] 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 207/844] 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 208/844] 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 209/844] 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 210/844] 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 211/844] 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 212/844] 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 213/844] 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 214/844] 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 215/844] 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 216/844] 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 217/844] 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 218/844] 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 219/844] 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 220/844] 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 221/844] 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 222/844] 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 223/844] 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 224/844] 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 225/844] 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 226/844] 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 227/844] 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 228/844] 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 229/844] 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 230/844] 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 231/844] 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 233/844] 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 234/844] 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 235/844] 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 236/844] 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 237/844] 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 238/844] 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 239/844] 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 240/844] 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 241/844] 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 242/844] 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 243/844] 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 244/844] 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 245/844] 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 246/844] 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 247/844] 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 248/844] 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 249/844] 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 250/844] 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 251/844] 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 252/844] 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 253/844] 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 254/844] 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 255/844] 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 281/844] 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 282/844] 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 283/844] 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 284/844] 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 285/844] 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 286/844] 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 287/844] 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 288/844] 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 289/844] 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 290/844] 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 291/844] 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 292/844] 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 293/844] 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 1a17ec08ee6cff8ab385a59f3c614038c1b2cb80 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Sun, 6 Nov 2022 10:04:18 +0000
Subject: [PATCH 294/844] Bug fix
---
lnbits/extensions/gerty/views_api.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 05a7f5d7..802db233 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -153,8 +153,10 @@ async def api_gerty_json(gerty_id: str, p: int = None): # page number
# Get a screen slug by its position in the screens_list
def get_screen_slug_by_index(index: int, screens_list):
- if(index < len(screens_list) - 1):
- return list(screens_list)[index]
+ logger.debug("Index: {0}".format(index))
+ logger.debug("len(screens_list) - 1: {0} ".format(len(screens_list) - 1))
+ if(index <= len(screens_list) - 1):
+ return list(screens_list)[index - 1]
else:
return None
From d856d4c920ed9ad9b879302ac75d3480b1689101 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Mon, 7 Nov 2022 15:00:45 +0000
Subject: [PATCH 295/844] Ran make format
---
lnbits/extensions/gerty/migrations.py | 5 ++---
.../gerty/templates/gerty/index.html | 6 ++---
lnbits/extensions/gerty/views_api.py | 22 +++++--------------
.../templates/lnurldevice/index.html | 2 +-
.../templates/usermanager/_api_docs.html | 15 +++++++------
5 files changed, 18 insertions(+), 32 deletions(-)
diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py
index 61722835..e98fc4f2 100644
--- a/lnbits/extensions/gerty/migrations.py
+++ b/lnbits/extensions/gerty/migrations.py
@@ -17,10 +17,9 @@ async def m001_initial(db):
"""
)
+
async def m002_add_utc_offset_col(db):
"""
support for UTC offset
"""
- await db.execute(
- "ALTER TABLE gerty.gertys ADD COLUMN utc_offset INT;"
- )
+ await db.execute("ALTER TABLE gerty.gertys ADD COLUMN utc_offset INT;")
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 3c258c1c..5d67f46e 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -179,15 +179,13 @@
>
-
- Enter a UTC time offset value (e.g. -1)
+ Enter a UTC time offset value (e.g. -1)
Use the toggles below to control what your Gerty will display
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 802db233..d636b8d4 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -155,7 +155,7 @@ async def api_gerty_json(gerty_id: str, p: int = None): # page number
def get_screen_slug_by_index(index: int, screens_list):
logger.debug("Index: {0}".format(index))
logger.debug("len(screens_list) - 1: {0} ".format(len(screens_list) - 1))
- if(index <= len(screens_list) - 1):
+ if index <= len(screens_list) - 1:
return list(screens_list)[index - 1]
else:
return None
@@ -302,38 +302,26 @@ async def get_onchain_dashboard(gerty):
)
text = []
stat = round(r.json()["progressPercent"])
- text.append(
- get_text_item_dict("Progress through current epoch", 12)
- )
+ text.append(get_text_item_dict("Progress through current epoch", 12))
text.append(get_text_item_dict("{0}%".format(stat), 60))
areas.append(text)
text = []
stat = r.json()["estimatedRetargetDate"]
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
- text.append(
- get_text_item_dict("Date of next difficulty adjustment", 12)
- )
+ text.append(get_text_item_dict("Date of next difficulty adjustment", 12))
text.append(get_text_item_dict(dt, 20))
areas.append(text)
text = []
stat = r.json()["remainingBlocks"]
- text.append(
- get_text_item_dict(
- "Blocks until next adjustment", 12
- )
- )
+ text.append(get_text_item_dict("Blocks until next adjustment", 12))
text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
areas.append(text)
text = []
stat = r.json()["remainingTime"]
- text.append(
- get_text_item_dict(
- "Blocks until next adjustment", 12
- )
- )
+ text.append(get_text_item_dict("Blocks until next adjustment", 12))
text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 60))
areas.append(text)
diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
index 83ff4571..b0b223ff 100644
--- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
+++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html
@@ -797,7 +797,7 @@
LNbits.utils.notifyApiError(error)
})
},
- clearFormDialoglnurldevice () {
+ clearFormDialoglnurldevice() {
this.formDialoglnurldevice.data = {
lnurl_toggle: false,
show_message: false,
diff --git a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html
index de477834..36593d74 100644
--- a/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html
+++ b/lnbits/extensions/usermanager/templates/usermanager/_api_docs.html
@@ -57,13 +57,14 @@
/usermanager/api/v1/users/<user_id>
Body (application/json)
-
+
Returns 200 OK (application/json)
{"id": <string>, "name": <string>, "admin":
- <string>, "email": <string>, "password": <string>}
Curl example
@@ -259,15 +260,15 @@
{"X-Api-Key": <string>}
Curl example
curl -X POST {{ request.base_url }}usermanager/api/v1/extensions?extension=withdraw&userid=user_id&active=true -H "X-Api-Key: {{ user.wallets[0].inkey }}" -H
- "Content-type: application/json"
+ >curl -X POST {{ request.base_url
+ }}usermanager/api/v1/extensions?extension=withdraw&userid=user_id&active=true
+ -H "X-Api-Key: {{ user.wallets[0].inkey }}" -H "Content-type:
+ application/json"
Returns 200 OK (application/json)
- {"extension": "updated"}
+ {"extension": "updated"}
From 6cb837d2b743259de3215a2489100f555370406d Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Mon, 7 Nov 2022 15:01:26 +0000
Subject: [PATCH 296/844] Removed "priority" text from Gerty mempool display
---
lnbits/extensions/gerty/views_api.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index d636b8d4..84a9b967 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -369,16 +369,16 @@ async def get_mempool_stat(stat_slug: str, gerty):
pos_y = 280 + y_offset
text.append(
- get_text_item_dict("{0}".format("No Priority"), 15, 30, pos_y)
+ get_text_item_dict("{0}".format("None"), 15, 30, pos_y)
)
text.append(
- get_text_item_dict("{0}".format("Low Priority"), 15, 235, pos_y)
+ get_text_item_dict("{0}".format("Low"), 15, 235, pos_y)
)
text.append(
- get_text_item_dict("{0}".format("Medium Priority"), 15, 460, pos_y)
+ get_text_item_dict("{0}".format("Medium"), 15, 460, pos_y)
)
text.append(
- get_text_item_dict("{0}".format("High Priority"), 15, 750, pos_y)
+ get_text_item_dict("{0}".format("High"), 15, 750, pos_y)
)
pos_y = 340 + y_offset
From b1c9e9a87e66105e3c5ec7b242a291cc965ce95f Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Mon, 7 Nov 2022 16:18:40 +0000
Subject: [PATCH 297/844] Update gerty api line lengths
---
lnbits/extensions/gerty/helpers.py | 12 +++++-------
1 file changed, 5 insertions(+), 7 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index d7e0e951..ccb1bb55 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -15,17 +15,15 @@ def get_percent_difference(current, previous, precision=4):
# A helper function get a nicely formated dict for the text
def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None):
# Get line size by font size
- line_width = 60
+ line_width = 20
if font_size <= 12:
- line_width = 75
+ line_width = 60
elif font_size <= 15:
- line_width = 58
+ line_width = 45
elif font_size <= 20:
- line_width = 40
+ line_width = 35
elif font_size <= 40:
- line_width = 30
- else:
- line_width = 20
+ line_width = 25
# wrap the text
wrapper = textwrap.TextWrapper(width=line_width)
From a0a7ae9be17f7769f15ed3a9569d7c2dd516b0b4 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Mon, 14 Nov 2022 12:48:15 +0000
Subject: [PATCH 298/844] Added single page stats back to Gerty
---
lnbits/extensions/gerty/helpers.py | 39 +
.../gerty/templates/gerty/index.html | 1398 +++++++++--------
lnbits/extensions/gerty/views_api.py | 80 +-
3 files changed, 887 insertions(+), 630 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index ccb1bb55..4852fb58 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -239,3 +239,42 @@ def get_time_remaining(seconds, granularity=2):
name = name.rstrip("s")
result.append("{} {}".format(round(value), name))
return ", ".join(result[:granularity])
+
+
+async def get_mining_stat(stat_slug: str, gerty):
+ text = []
+ if stat_slug == "mining_current_hash_rate":
+ stat = await api_get_mining_stat(stat_slug, gerty)
+ logger.debug(stat)
+ current = "{0}hash".format(si_format(stat['current'], 6, True, " "))
+ text.append(get_text_item_dict("Current Mining Hashrate", 20))
+ text.append(get_text_item_dict(current, 40))
+ # compare vs previous time period
+ difference = get_percent_difference(current=stat['current'], previous=stat['1w'])
+ text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
+ elif stat_slug == "mining_current_difficulty":
+ stat = await api_get_mining_stat(stat_slug, gerty)
+ text.append(get_text_item_dict("Current Mining Difficulty", 20))
+ text.append(get_text_item_dict(format_number(stat['current']), 40))
+ difference = get_percent_difference(current=stat['current'], previous=stat['previous'])
+ text.append(get_text_item_dict("{0} since last adjustment".format(difference), 12))
+ # text.append(get_text_item_dict("Required threshold for mining proof-of-work", 12))
+ return text
+
+async def api_get_mining_stat(stat_slug: str, gerty):
+ stat = ""
+ if stat_slug == "mining_current_hash_rate":
+ async with httpx.AsyncClient() as client:
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1m")
+ data = r.json()
+ stat = {}
+ stat['current'] = data['currentHashrate']
+ stat['1w'] = data['hashrates'][len(data['hashrates']) - 7]['avgHashrate']
+ elif stat_slug == "mining_current_difficulty":
+ async with httpx.AsyncClient() as client:
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/mining/hashrate/1m")
+ data = r.json()
+ stat = {}
+ stat['current'] = data['currentDifficulty']
+ stat['previous'] = data['difficulty'][len(data['difficulty']) - 2]['difficulty']
+ return stat
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 5d67f46e..55e67a2d 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -1,639 +1,781 @@
-{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
-%} {% block page %}
-
-
-
-
- New Gerty
-
-
-
-
-
-
-
-
Gerty
-
-
- Export to CSV
-
+{% extends "base.html" %} {% from "macros.jinja" import window_vars with context %} {% block page %}
+
+
+
+
+ New Gerty
+
+
+
+
+
+
+
+
Gerty
+
+
+ Export to CSV
+
+
+
+ {% raw %}
+
+
+
+
+ {{ col.label }}
+
+
+
+
+
+
+
+
+
+
+ Launch software Gerty
+
+
+ View Gerty API
+
+
+
+ {{ (col.name == 'tip_options' && col.value ?
+ JSON.parse(col.value).join(", ") : col.value) }}
+
+
+
+
+
+
+
+
+
+ {% endraw %}
+
+
+
-
- {% raw %}
-
-
-
-
- {{ col.label }}
-
-
-
-
-
-
-
-
-
- Launch software Gerty
-
-
- View Gerty API
-
-
-
- {{ (col.name == 'tip_options' && col.value ?
- JSON.parse(col.value).join(", ") : col.value) }}
-
-
-
-
-
-
-
-
-
- {% endraw %}
-
-
-
-
-
-
-
-
-
- {{ SITE_TITLE }} Gerty extension
-
-
-
-
- {% include "gerty/_api_docs.html" %}
-
-
-
-
-
-
-
-
-
-
- Hit enter to add values
-
-
-
- Used for getting onchain/ln stats
-
-
-
- The amount of time in seconds between screen updates
-
-
-
- Enter a UTC time offset value (e.g. -1)
-
-
- Use the toggles below to control what your Gerty will display
-
-
-
-
- Displays random quotes from Satoshi
-
-
-
-
-
-
-
-
-
-
-
-
-
Create Gerty
-
-
Update Gerty
-
-
Cancel
-
+
+
+
+
+ {{ SITE_TITLE }} Gerty extension
+
+
+
+
+ {% include "gerty/_api_docs.html" %}
+
+
-
-
-
-
+
+
+
+
+
+
+
+ Hit enter to add values
+
+
+
+ Used for getting onchain/ln stats
+
+
+
+ The amount of time in seconds between screen updates
+
+
+
+
+ Enter a UTC time offset value (e.g. -1)
+
+
+ Use the toggles below to control what your Gerty will display
+
+
+
+
+
+ Displays random quotes from Satoshi
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Toggle all
+
+
+
+
+
+
+
+
+
+
+
+
+ Toggle all
+
+
+
+
+
+
+
+
+
+
+ Toggle all
+
+
+
+
+
+
+
+
+
+
+
+ Create Gerty
+
+ Update Gerty
+
+ Cancel
+
+
+
+
+
+
{% endblock %} {% block scripts %} {{ window_vars(user) }}
-
+ LNbits.utils
+ .confirmDialog('Are you sure you want to delete this Gerty?')
+ .onOk(function () {
+ LNbits.api
+ .request(
+ 'DELETE',
+ '/gerty/api/v1/gerty/' + gertyId,
+ _.findWhere(self.g.user.wallets, {id: gerty.wallet}).adminkey
+ )
+ .then(function (response) {
+ self.gertys = _.reject(self.gertys, function (obj) {
+ return obj.id == gertyId
+ })
+ })
+ .catch(function (error) {
+ LNbits.utils.notifyApiError(error)
+ })
+ })
+ },
+ exportCSV: function () {
+ LNbits.utils.exportCSV(this.gertysTable.columns, this.gertys)
+ }
+ },
+ created: function () {
+ if (this.g.user.wallets.length) {
+ this.getGertys()
+ }
+ },
+ watch: {
+ toggleStates: {
+ handler(toggleStatesValue) {
+ // Switch all the toggles in each section to the relevant state
+ for (const [toggleKey, toggleValue] of Object.entries(toggleStatesValue)) {
+ if (this.oldToggleStates[toggleKey] !== toggleValue) {
+ for (const [dpKey, dpValue] of Object.entries(this.formDialog.data.display_preferences)) {
+ if (dpKey.indexOf(toggleKey) === 0) {
+ this.formDialog.data.display_preferences[dpKey] = toggleValue
+ }
+ }
+ }
+ }
+ // This is a weird hack we have to use to get VueJS to persist the previous toggle state between
+ // watches. VueJS passes the old and new values by reference so when comparing objects they
+ // will have the same values unless we do this
+ this.oldToggleStates = JSON.parse(JSON.stringify(toggleStatesValue))
+ },
+ deep: true
+ }
+ }
+ })
+
{% endblock %} {% block styles %}
-
+
{% endblock %}
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index 84a9b967..b400f3ec 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -173,16 +173,37 @@ async def get_screen_data(screen_num: int, screens_list: dict, gerty):
if screen_slug == "dashboard":
title = gerty.name
areas = await get_dashboard(gerty)
+ if screen_slug == "lnbits_wallets_balance":
+ wallets = await get_lnbits_wallet_balances(gerty)
+ text = []
+ for wallet in wallets:
+ text.append(get_text_item_dict("{0}'s Wallet".format(wallet['name']), 20))
+ text.append(get_text_item_dict("{0} sats".format(format_number(wallet['balance'])), 40))
+ areas.append(text)
elif screen_slug == "fun_satoshi_quotes":
areas.append(await get_satoshi_quotes())
elif screen_slug == "fun_exchange_market_rate":
areas.append(await get_exchange_rate(gerty))
- elif screen_slug == "onchain_dashboard":
+ elif screen_slug == "onchain_difficulty_epoch_progress":
+ areas.append(await get_onchain_stat(screen_slug, gerty))
+ elif screen_slug == "onchain_difficulty_retarget_date":
+ areas.append(await get_onchain_stat(screen_slug, gerty))
+ elif screen_slug == "onchain_difficulty_blocks_remaining":
+ areas.append(await get_onchain_stat(screen_slug, gerty))
+ elif screen_slug == "onchain_difficulty_epoch_time_remaining":
+ areas.append(await get_onchain_stat(screen_slug, gerty))
+ elif screen_slug == "dashboard_onchain":
title = "Onchain Data"
areas = await get_onchain_dashboard(gerty)
elif screen_slug == "mempool_recommended_fees":
areas.append(await get_mempool_stat(screen_slug, gerty))
- elif screen_slug == "mining_dashboard":
+ elif screen_slug == "mempool_tx_count":
+ areas.append(await get_mempool_stat(screen_slug, gerty))
+ elif screen_slug == "mining_current_hash_rate":
+ areas.append(await get_mining_stat(screen_slug, gerty))
+ elif screen_slug == "mining_current_difficulty":
+ areas.append(await get_mining_stat(screen_slug, gerty))
+ elif screen_slug == "dashboard_mining":
title = "Mining Data"
areas = await get_mining_dashboard(gerty)
elif screen_slug == "lightning_dashboard":
@@ -292,6 +313,34 @@ async def get_exchange_rate(gerty):
pass
return text
+async def get_onchain_stat(stat_slug: str, gerty):
+ text = []
+ if (
+ stat_slug == "onchain_difficulty_epoch_progress" or
+ stat_slug == "onchain_difficulty_retarget_date" or
+ stat_slug == "onchain_difficulty_blocks_remaining" or
+ stat_slug == "onchain_difficulty_epoch_time_remaining"
+ ):
+ async with httpx.AsyncClient() as client:
+ r = await client.get(gerty.mempool_endpoint + "/api/v1/difficulty-adjustment")
+ if stat_slug == "onchain_difficulty_epoch_progress":
+ stat = round(r.json()['progressPercent'])
+ text.append(get_text_item_dict("Progress through current difficulty epoch", 15))
+ text.append(get_text_item_dict("{0}%".format(stat), 80))
+ elif stat_slug == "onchain_difficulty_retarget_date":
+ stat = r.json()['estimatedRetargetDate']
+ dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
+ text.append(get_text_item_dict("Estimated date of next difficulty adjustment", 15))
+ text.append(get_text_item_dict(dt, 40))
+ elif stat_slug == "onchain_difficulty_blocks_remaining":
+ stat = r.json()['remainingBlocks']
+ text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
+ text.append(get_text_item_dict("{0}".format(format_number(stat)), 80))
+ elif stat_slug == "onchain_difficulty_epoch_time_remaining":
+ stat = r.json()['remainingTime']
+ text.append(get_text_item_dict("Blocks remaining until next difficulty adjustment", 15))
+ text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
+ return text
async def get_onchain_dashboard(gerty):
areas = []
@@ -440,3 +489,30 @@ async def get_mempool_stat(stat_slug: str, gerty):
)
)
return text
+
+
+def get_date_suffix(dayNumber):
+ if 4 <= dayNumber <= 20 or 24 <= dayNumber <= 30:
+ return "th"
+ else:
+ return ["st", "nd", "rd"][dayNumber % 10 - 1]
+
+def get_time_remaining(seconds, granularity=2):
+ intervals = (
+ # ('weeks', 604800), # 60 * 60 * 24 * 7
+ ('days', 86400), # 60 * 60 * 24
+ ('hours', 3600), # 60 * 60
+ ('minutes', 60),
+ ('seconds', 1),
+ )
+
+ result = []
+
+ for name, count in intervals:
+ value = seconds // count
+ if value:
+ seconds -= value * count
+ if value == 1:
+ name = name.rstrip('s')
+ result.append("{} {}".format(round(value), name))
+ return ', '.join(result[:granularity])
From df56b84ad212247e9d1bd9766f5606ae21685f2d Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Mon, 14 Nov 2022 14:07:26 +0000
Subject: [PATCH 299/844] Tweaks
---
lnbits/extensions/gerty/views_api.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index b400f3ec..2eea366c 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -351,27 +351,27 @@ async def get_onchain_dashboard(gerty):
)
text = []
stat = round(r.json()["progressPercent"])
- text.append(get_text_item_dict("Progress through current epoch", 12))
+ text.append(get_text_item_dict("Progress through epoch", 12))
text.append(get_text_item_dict("{0}%".format(stat), 60))
areas.append(text)
text = []
stat = r.json()["estimatedRetargetDate"]
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
- text.append(get_text_item_dict("Date of next difficulty adjustment", 12))
+ text.append(get_text_item_dict("Date of next adjustment", 12))
text.append(get_text_item_dict(dt, 20))
areas.append(text)
text = []
stat = r.json()["remainingBlocks"]
- text.append(get_text_item_dict("Blocks until next adjustment", 12))
+ text.append(get_text_item_dict("Blocks until adjustment", 12))
text.append(get_text_item_dict("{0}".format(format_number(stat)), 60))
areas.append(text)
text = []
stat = r.json()["remainingTime"]
- text.append(get_text_item_dict("Blocks until next adjustment", 12))
- text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 60))
+ text.append(get_text_item_dict("Time until adjustment", 12))
+ text.append(get_text_item_dict(get_time_remaining(stat / 1000, 4), 20))
areas.append(text)
return areas
From 592a52e9a2df228534271ee2a07946bfceda7aca Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 17 Nov 2022 14:13:20 +0000
Subject: [PATCH 300/844] Added type to Gerty extension Vue, migration and
Crud
---
lnbits/extensions/gerty/crud.py | 4 +++-
lnbits/extensions/gerty/migrations.py | 6 ++++++
lnbits/extensions/gerty/models.py | 1 +
lnbits/extensions/gerty/templates/gerty/index.html | 13 +++++++++++++
4 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/crud.py b/lnbits/extensions/gerty/crud.py
index 3850737f..1b179d50 100644
--- a/lnbits/extensions/gerty/crud.py
+++ b/lnbits/extensions/gerty/crud.py
@@ -15,19 +15,21 @@ async def create_gerty(wallet_id: str, data: Gerty) -> Gerty:
name,
wallet,
utc_offset,
+ type,
lnbits_wallets,
mempool_endpoint,
exchange,
display_preferences,
refresh_time
)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
gerty_id,
data.name,
data.wallet,
data.utc_offset,
+ data.type,
data.lnbits_wallets,
data.mempool_endpoint,
data.exchange,
diff --git a/lnbits/extensions/gerty/migrations.py b/lnbits/extensions/gerty/migrations.py
index e98fc4f2..b283ee56 100644
--- a/lnbits/extensions/gerty/migrations.py
+++ b/lnbits/extensions/gerty/migrations.py
@@ -23,3 +23,9 @@ async def m002_add_utc_offset_col(db):
support for UTC offset
"""
await db.execute("ALTER TABLE gerty.gertys ADD COLUMN utc_offset INT;")
+
+async def m003_add_gerty_model_col(db):
+ """
+ support for Gerty model col
+ """
+ await db.execute("ALTER TABLE gerty.gertys ADD COLUMN type TEXT;")
\ No newline at end of file
diff --git a/lnbits/extensions/gerty/models.py b/lnbits/extensions/gerty/models.py
index 855a30c9..44704f82 100644
--- a/lnbits/extensions/gerty/models.py
+++ b/lnbits/extensions/gerty/models.py
@@ -11,6 +11,7 @@ class Gerty(BaseModel):
wallet: str
refresh_time: int = Query(None)
utc_offset: int = Query(None)
+ type: str
lnbits_wallets: str = Query(
None
) # Wallets to keep an eye on, {"wallet-id": "wallet-read-key, etc"}
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 55e67a2d..c91f6997 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -127,6 +127,14 @@
label="Name"
placeholder="Son of Gerty"
>
+
Date: Thu, 17 Nov 2022 15:11:31 +0000
Subject: [PATCH 301/844] Added mini gerty api support to front end
---
lnbits/extensions/gerty/helpers.py | 73 ++++++-----
.../gerty/templates/gerty/index.html | 43 +++++--
lnbits/extensions/gerty/views_api.py | 120 +++++++++---------
3 files changed, 137 insertions(+), 99 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index 4852fb58..46b83273 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -13,7 +13,7 @@ def get_percent_difference(current, previous, precision=4):
# A helper function get a nicely formated dict for the text
-def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None):
+def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int = None, gerty_type: str = 'Gerty'):
# Get line size by font size
line_width = 20
if font_size <= 12:
@@ -25,6 +25,19 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
elif font_size <= 40:
line_width = 25
+ # Get font sizes for Gerty mini
+ if(gerty_type.lower() == 'mini gerty'):
+ if font_size <= 15:
+ font_size = 2
+ elif font_size <= 20:
+ font_size = 3
+ elif font_size <= 40:
+ font_size = 4
+ else:
+ font_size = 5
+
+
+
# wrap the text
wrapper = textwrap.TextWrapper(width=line_width)
word_list = wrapper.wrap(text=text)
@@ -68,18 +81,18 @@ async def get_mining_dashboard(gerty):
hashrateOneWeekAgo = data["hashrates"][6]["avgHashrate"]
text = []
- text.append(get_text_item_dict("Current mining hashrate", 12))
+ text.append(get_text_item_dict(text="Current mining hashrate", font_size=12,gerty_type=gerty.type))
text.append(
get_text_item_dict(
- "{0}hash".format(si_format(hashrateNow, 6, True, " ")), 20
+ text="{0}hash".format(si_format(hashrateNow, 6, True, " ")), font_size=20,gerty_type=gerty.type
)
)
text.append(
get_text_item_dict(
- "{0} vs 7 days ago".format(
+ text="{0} vs 7 days ago".format(
get_percent_difference(hashrateNow, hashrateOneWeekAgo, 3)
),
- 12,
+ font_size=12,gerty_type=gerty.type
)
)
areas.append(text)
@@ -91,27 +104,27 @@ async def get_mining_dashboard(gerty):
# timeAvg
text = []
progress = "{0}%".format(round(r.json()["progressPercent"], 2))
- text.append(get_text_item_dict("Progress through current epoch", 12))
- text.append(get_text_item_dict(progress, 60))
+ text.append(get_text_item_dict(text="Progress through current epoch", font_size=12,gerty_type=gerty.type))
+ text.append(get_text_item_dict(text=progress, font_size=60,gerty_type=gerty.type))
areas.append(text)
# difficulty adjustment
text = []
stat = r.json()["remainingTime"]
- text.append(get_text_item_dict("Time to next difficulty adjustment", 12))
- text.append(get_text_item_dict(get_time_remaining(stat / 1000, 3), 12))
+ text.append(get_text_item_dict(text="Time to next difficulty adjustment", font_size=12,gerty_type=gerty.type))
+ text.append(get_text_item_dict(text=get_time_remaining(stat / 1000, 3), font_size=12,gerty_type=gerty.type))
areas.append(text)
# difficultyChange
text = []
difficultyChange = round(r.json()["difficultyChange"], 2)
- text.append(get_text_item_dict("Estimated difficulty change", 12))
+ text.append(get_text_item_dict(text="Estimated difficulty change", font_size=12,gerty_type=gerty.type))
text.append(
get_text_item_dict(
- "{0}{1}%".format(
+ text="{0}{1}%".format(
"+" if difficultyChange > 0 else "", round(difficultyChange, 2)
),
- 60,
+ font_size=60,gerty_type=gerty.type
)
)
areas.append(text)
@@ -142,49 +155,49 @@ async def get_lightning_stats(gerty):
areas = []
text = []
- text.append(get_text_item_dict("Channel Count", 12))
- text.append(get_text_item_dict(format_number(data["latest"]["channel_count"]), 20))
+ text.append(get_text_item_dict(text="Channel Count", font_size=12, gerty_type=gerty.type))
+ text.append(get_text_item_dict(text=format_number(data["latest"]["channel_count"]), font_size=20, gerty_type=gerty.type))
difference = get_percent_difference(
current=data["latest"]["channel_count"],
previous=data["previous"]["channel_count"],
)
- text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
+ text.append(get_text_item_dict(text="{0} in last 7 days".format(difference), font_size=12, gerty_type=gerty.type))
areas.append(text)
text = []
- text.append(get_text_item_dict("Number of Nodes", 12))
- text.append(get_text_item_dict(format_number(data["latest"]["node_count"]), 20))
+ text.append(get_text_item_dict(text="Number of Nodes", font_size=12,gerty_type=gerty.type))
+ text.append(get_text_item_dict(text=format_number(data["latest"]["node_count"]), font_size=20,gerty_type=gerty.type))
difference = get_percent_difference(
current=data["latest"]["node_count"], previous=data["previous"]["node_count"]
)
- text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
+ text.append(get_text_item_dict(text="{0} in last 7 days".format(difference), font_size=12,gerty_type=gerty.type))
areas.append(text)
text = []
- text.append(get_text_item_dict("Total Capacity", 12))
+ text.append(get_text_item_dict(text="Total Capacity", font_size=12,gerty_type=gerty.type))
avg_capacity = float(data["latest"]["total_capacity"]) / float(100000000)
text.append(
- get_text_item_dict("{0} BTC".format(format_number(avg_capacity, 2)), 20)
+ get_text_item_dict(text="{0} BTC".format(format_number(avg_capacity, 2)), font_size=20,gerty_type=gerty.type)
)
difference = get_percent_difference(
current=data["latest"]["total_capacity"],
previous=data["previous"]["total_capacity"],
)
- text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
+ text.append(get_text_item_dict(text="{0} in last 7 days".format(difference), font_size=12,gerty_type=gerty.type))
areas.append(text)
text = []
- text.append(get_text_item_dict("Average Channel Capacity", 12))
+ text.append(get_text_item_dict(text="Average Channel Capacity", font_size=12,gerty_type=gerty.type))
text.append(
get_text_item_dict(
- "{0} sats".format(format_number(data["latest"]["avg_capacity"])), 20
+ text="{0} sats".format(format_number(data["latest"]["avg_capacity"])), font_size=20,gerty_type=gerty.type
)
)
difference = get_percent_difference(
current=data["latest"]["avg_capacity"],
previous=data["previous"]["avg_capacity"],
)
- text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
+ text.append(get_text_item_dict(text="{0} in last 7 days".format(difference), font_size=12, gerty_type=gerty.type))
areas.append(text)
return areas
@@ -247,17 +260,17 @@ async def get_mining_stat(stat_slug: str, gerty):
stat = await api_get_mining_stat(stat_slug, gerty)
logger.debug(stat)
current = "{0}hash".format(si_format(stat['current'], 6, True, " "))
- text.append(get_text_item_dict("Current Mining Hashrate", 20))
- text.append(get_text_item_dict(current, 40))
+ text.append(get_text_item_dict(text="Current Mining Hashrate", font_size=20,gerty_type=gerty.type))
+ text.append(get_text_item_dict(text=current, font_size=40,gerty_type=gerty.type))
# compare vs previous time period
difference = get_percent_difference(current=stat['current'], previous=stat['1w'])
- text.append(get_text_item_dict("{0} in last 7 days".format(difference), 12))
+ text.append(get_text_item_dict(text="{0} in last 7 days".format(difference), font_size=12,gerty_type=gerty.type))
elif stat_slug == "mining_current_difficulty":
stat = await api_get_mining_stat(stat_slug, gerty)
- text.append(get_text_item_dict("Current Mining Difficulty", 20))
- text.append(get_text_item_dict(format_number(stat['current']), 40))
+ text.append(get_text_item_dict(text="Current Mining Difficulty", font_size=20,gerty_type=gerty.type))
+ text.append(get_text_item_dict(text=format_number(stat['current']), font_size=40,gerty_type=gerty.type))
difference = get_percent_difference(current=stat['current'], previous=stat['previous'])
- text.append(get_text_item_dict("{0} since last adjustment".format(difference), 12))
+ text.append(get_text_item_dict(text="{0} since last adjustment".format(difference), font_size=12,gerty_type=gerty.type))
# text.append(get_text_item_dict("Required threshold for mining proof-of-work", 12))
return text
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index c91f6997..6a528aad 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -198,7 +198,9 @@
Use the toggles below to control what your Gerty will display
+
-
- Displays random quotes from Satoshi
-
-
-
+
+
+ Displays random quotes from Satoshi
+
Date: Thu, 17 Nov 2022 15:14:45 +0000
Subject: [PATCH 302/844] Disable mempool fees for mini gerty
---
lnbits/extensions/gerty/templates/gerty/index.html | 1 +
1 file changed, 1 insertion(+)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 6a528aad..8dd43d34 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -306,6 +306,7 @@
From 1d7109ddf9d9bffe5d97e995a27a3bc26fc37ea4 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 17 Nov 2022 17:39:04 +0000
Subject: [PATCH 303/844] gerty tweaks
---
lnbits/extensions/gerty/helpers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index 46b83273..2726b739 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -30,7 +30,7 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
if font_size <= 15:
font_size = 2
elif font_size <= 20:
- font_size = 3
+ font_size = 2
elif font_size <= 40:
font_size = 4
else:
From 4a6f7050cdcf43c23ce91f90d0f3fc86f7dade9a Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 17 Nov 2022 17:40:15 +0000
Subject: [PATCH 304/844] gerty tweaks
---
lnbits/extensions/gerty/helpers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index 2726b739..c534e40a 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -28,11 +28,11 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
# Get font sizes for Gerty mini
if(gerty_type.lower() == 'mini gerty'):
if font_size <= 15:
- font_size = 2
+ font_size = 1
elif font_size <= 20:
font_size = 2
elif font_size <= 40:
- font_size = 4
+ font_size = 3
else:
font_size = 5
From b8be50a592da2bfe001dc7baf4d27d1d436cef11 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 17 Nov 2022 17:42:15 +0000
Subject: [PATCH 305/844] gerty tweaks
---
lnbits/extensions/gerty/helpers.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index c534e40a..5684ab17 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -28,11 +28,11 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
# Get font sizes for Gerty mini
if(gerty_type.lower() == 'mini gerty'):
if font_size <= 15:
- font_size = 1
+ font_size = 2
elif font_size <= 20:
font_size = 2
elif font_size <= 40:
- font_size = 3
+ font_size = 2
else:
font_size = 5
From 60962713f40f1105bb0011f2dca1c5296b73faf6 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 17 Nov 2022 17:43:09 +0000
Subject: [PATCH 306/844] gerty tweaks
---
lnbits/extensions/gerty/helpers.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index 5684ab17..94599b5b 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -27,6 +27,8 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
# Get font sizes for Gerty mini
if(gerty_type.lower() == 'mini gerty'):
+ if font_size <= 12:
+ font_size = 1
if font_size <= 15:
font_size = 2
elif font_size <= 20:
From 7ef61b6d3cd9c7fad292a229c30721434b28ed4b Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 17 Nov 2022 17:43:52 +0000
Subject: [PATCH 307/844] gerty tweaks
---
lnbits/extensions/gerty/helpers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index 94599b5b..dc82c885 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -30,7 +30,7 @@ def get_text_item_dict(text: str, font_size: int, x_pos: int = None, y_pos: int
if font_size <= 12:
font_size = 1
if font_size <= 15:
- font_size = 2
+ font_size = 1
elif font_size <= 20:
font_size = 2
elif font_size <= 40:
From 189593af753b134c7a82a9e6ca4fa7f98a6bd5f3 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Thu, 17 Nov 2022 21:55:23 +0000
Subject: [PATCH 308/844] Added blockheight as standalone screen to Gerty
---
lnbits/extensions/gerty/templates/gerty/index.html | 5 +++++
lnbits/extensions/gerty/views_api.py | 6 ++++++
2 files changed, 11 insertions(+)
diff --git a/lnbits/extensions/gerty/templates/gerty/index.html b/lnbits/extensions/gerty/templates/gerty/index.html
index 8dd43d34..db3dd7e6 100644
--- a/lnbits/extensions/gerty/templates/gerty/index.html
+++ b/lnbits/extensions/gerty/templates/gerty/index.html
@@ -290,6 +290,10 @@
v-model="formDialog.data.display_preferences.onchain_difficulty_epoch_time_remaining"
label="Estimated time until next difficulty adjustment"
>
+
Date: Fri, 18 Nov 2022 09:46:26 +0000
Subject: [PATCH 309/844] Gerty label tweaks
---
lnbits/extensions/gerty/views_api.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/lnbits/extensions/gerty/views_api.py b/lnbits/extensions/gerty/views_api.py
index fb1dbaad..43c6e29a 100644
--- a/lnbits/extensions/gerty/views_api.py
+++ b/lnbits/extensions/gerty/views_api.py
@@ -336,15 +336,15 @@ async def get_onchain_stat(stat_slug: str, gerty):
elif stat_slug == "onchain_difficulty_retarget_date":
stat = r.json()['estimatedRetargetDate']
dt = datetime.fromtimestamp(stat / 1000).strftime("%e %b %Y at %H:%M")
- text.append(get_text_item_dict(text="Estimated date of next difficulty adjustment", font_size=15,gerty_type=gerty.type))
+ text.append(get_text_item_dict(text="Date of next difficulty adjustment", font_size=15,gerty_type=gerty.type))
text.append(get_text_item_dict(text=dt, font_size=40,gerty_type=gerty.type))
elif stat_slug == "onchain_difficulty_blocks_remaining":
stat = r.json()['remainingBlocks']
- text.append(get_text_item_dict(text="Blocks remaining until next difficulty adjustment", font_size=15,gerty_type=gerty.type))
+ text.append(get_text_item_dict(text="Blocks until next difficulty adjustment", font_size=15,gerty_type=gerty.type))
text.append(get_text_item_dict(text="{0}".format(format_number(stat)), font_size=80,gerty_type=gerty.type))
elif stat_slug == "onchain_difficulty_epoch_time_remaining":
stat = r.json()['remainingTime']
- text.append(get_text_item_dict(text="Blocks remaining until next difficulty adjustment", font_size=15,gerty_type=gerty.type))
+ text.append(get_text_item_dict(text="Time until next difficulty adjustment", font_size=15,gerty_type=gerty.type))
text.append(get_text_item_dict(text=get_time_remaining(stat / 1000, 4), font_size=20,gerty_type=gerty.type))
return text
From 00e846b49bf5ba61c4b4669f8f540a0a33f56f84 Mon Sep 17 00:00:00 2001
From: Black Coffee
Date: Fri, 18 Nov 2022 09:48:55 +0000
Subject: [PATCH 310/844] Reduce precision in get_percent_difference func
---
lnbits/extensions/gerty/helpers.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lnbits/extensions/gerty/helpers.py b/lnbits/extensions/gerty/helpers.py
index dc82c885..991381b5 100644
--- a/lnbits/extensions/gerty/helpers.py
+++ b/lnbits/extensions/gerty/helpers.py
@@ -7,7 +7,7 @@ from loguru import logger
from .number_prefixer import *
-def get_percent_difference(current, previous, precision=4):
+def get_percent_difference(current, previous, precision=3):
difference = (current - previous) / current * 100
return "{0}{1}%".format("+" if difference > 0 else "", round(difference, precision))
From 678c269a9124a967300a34d75542ed0c0c35150a Mon Sep 17 00:00:00 2001
From: Gene Takavic
Date: Fri, 18 Nov 2022 16:57:39 +0100
Subject: [PATCH 311/844] 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 312/844] 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 313/844] 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 )
+
-