Merge remote-tracking branch 'origin/main' into icontiles

This commit is contained in:
ben 2023-01-03 19:36:05 +00:00
commit 1b201086ba
32 changed files with 134 additions and 232 deletions

View file

@ -6,7 +6,7 @@ format: prettier isort black
check: mypy checkprettier checkisort checkblack check: mypy checkprettier checkisort checkblack
prettier: $(shell find lnbits -name "*.js" -name ".html") prettier: $(shell find lnbits -name "*.js" -o -name ".html")
./node_modules/.bin/prettier --write lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js lnbits/extensions/*/static/components/*/*.js lnbits/extensions/*/static/components/*/*.html ./node_modules/.bin/prettier --write lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js lnbits/extensions/*/static/components/*/*.js lnbits/extensions/*/static/components/*/*.html
black: black:
@ -18,7 +18,7 @@ mypy:
isort: isort:
poetry run isort . poetry run isort .
checkprettier: $(shell find lnbits -name "*.js" -name ".html") checkprettier: $(shell find lnbits -name "*.js" -o -name ".html")
./node_modules/.bin/prettier --check lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js lnbits/extensions/*/static/components/*/*.js lnbits/extensions/*/static/components/*/*.html ./node_modules/.bin/prettier --check lnbits/static/js/*.js lnbits/core/static/js/*.js lnbits/extensions/*/templates/*/*.html ./lnbits/core/templates/core/*.html lnbits/templates/*.html lnbits/extensions/*/static/js/*.js lnbits/extensions/*/static/components/*/*.js lnbits/extensions/*/static/components/*/*.html
checkblack: checkblack:

View file

@ -5,7 +5,7 @@
}; };
outputs = { self, nixpkgs, poetry2nix }@inputs: outputs = { self, nixpkgs, poetry2nix }@inputs:
let let
supportedSystems = [ "x86_64-linux" "aarch64-linux" ]; supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forSystems = systems: f: forSystems = systems: f:
nixpkgs.lib.genAttrs systems nixpkgs.lib.genAttrs systems
(system: f system (import nixpkgs { inherit system; overlays = [ poetry2nix.overlay self.overlays.default ]; })); (system: f system (import nixpkgs { inherit system; overlays = [ poetry2nix.overlay self.overlays.default ]; }));

View file

@ -1,7 +1,6 @@
import hashlib import hashlib
import re import re
import time import time
from binascii import unhexlify
from decimal import Decimal from decimal import Decimal
from typing import List, NamedTuple, Optional from typing import List, NamedTuple, Optional
@ -108,7 +107,7 @@ def decode(pr: str) -> Invoice:
message = bytearray([ord(c) for c in hrp]) + data.tobytes() message = bytearray([ord(c) for c in hrp]) + data.tobytes()
sig = signature[0:64] sig = signature[0:64]
if invoice.payee: if invoice.payee:
key = VerifyingKey.from_string(unhexlify(invoice.payee), curve=SECP256k1) key = VerifyingKey.from_string(bytes.fromhex(invoice.payee), curve=SECP256k1)
key.verify(sig, message, hashlib.sha256, sigdecode=sigdecode_string) key.verify(sig, message, hashlib.sha256, sigdecode=sigdecode_string)
else: else:
keys = VerifyingKey.from_public_key_recovery( keys = VerifyingKey.from_public_key_recovery(
@ -131,7 +130,7 @@ def encode(options):
if options["timestamp"]: if options["timestamp"]:
addr.date = int(options["timestamp"]) addr.date = int(options["timestamp"])
addr.paymenthash = unhexlify(options["paymenthash"]) addr.paymenthash = bytes.fromhex(options["paymenthash"])
if options["description"]: if options["description"]:
addr.tags.append(("d", options["description"])) addr.tags.append(("d", options["description"]))
@ -149,8 +148,8 @@ def encode(options):
while len(splits) >= 5: while len(splits) >= 5:
route.append( route.append(
( (
unhexlify(splits[0]), bytes.fromhex(splits[0]),
unhexlify(splits[1]), bytes.fromhex(splits[1]),
int(splits[2]), int(splits[2]),
int(splits[3]), int(splits[3]),
int(splits[4]), int(splits[4]),
@ -235,7 +234,7 @@ def lnencode(addr, privkey):
raise ValueError("Must include either 'd' or 'h'") raise ValueError("Must include either 'd' or 'h'")
# We actually sign the hrp, then data (padded to 8 bits with zeroes). # We actually sign the hrp, then data (padded to 8 bits with zeroes).
privkey = secp256k1.PrivateKey(bytes(unhexlify(privkey))) privkey = secp256k1.PrivateKey(bytes.fromhex(privkey))
sig = privkey.ecdsa_sign_recoverable( sig = privkey.ecdsa_sign_recoverable(
bytearray([ord(c) for c in hrp]) + data.tobytes() bytearray([ord(c) for c in hrp]) + data.tobytes()
) )
@ -261,7 +260,7 @@ class LnAddr(object):
def __str__(self): def __str__(self):
return "LnAddr[{}, amount={}{} tags=[{}]]".format( return "LnAddr[{}, amount={}{} tags=[{}]]".format(
hexlify(self.pubkey.serialize()).decode("utf-8"), bytes.hex(self.pubkey.serialize()).decode("utf-8"),
self.amount, self.amount,
self.currency, self.currency,
", ".join([k + "=" + str(v) for k, v in self.tags]), ", ".join([k + "=" + str(v) for k, v in self.tags]),

View file

@ -1,6 +1,5 @@
import asyncio import asyncio
import json import json
from binascii import unhexlify
from io import BytesIO from io import BytesIO
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
from urllib.parse import parse_qs, urlparse from urllib.parse import parse_qs, urlparse
@ -303,7 +302,7 @@ async def perform_lnurlauth(
) -> Optional[LnurlErrorResponse]: ) -> Optional[LnurlErrorResponse]:
cb = urlparse(callback) cb = urlparse(callback)
k1 = unhexlify(parse_qs(cb.query)["k1"][0]) k1 = bytes.fromhex(parse_qs(cb.query)["k1"][0])
key = wallet.wallet.lnurlauth_key(cb.netloc) key = wallet.wallet.lnurlauth_key(cb.netloc)

View file

@ -1,5 +1,4 @@
import asyncio import asyncio
import binascii
import hashlib import hashlib
import json import json
import time import time
@ -142,16 +141,14 @@ async def api_payments_create_invoice(data: CreateInvoiceData, wallet: Wallet):
if data.description_hash or data.unhashed_description: if data.description_hash or data.unhashed_description:
try: try:
description_hash = ( description_hash = (
binascii.unhexlify(data.description_hash) bytes.fromhex(data.description_hash) if data.description_hash else b""
if data.description_hash
else b""
) )
unhashed_description = ( unhashed_description = (
binascii.unhexlify(data.unhashed_description) bytes.fromhex(data.unhashed_description)
if data.unhashed_description if data.unhashed_description
else b"" else b""
) )
except binascii.Error: except ValueError:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,
detail="'description_hash' and 'unhashed_description' must be a valid hex strings", detail="'description_hash' and 'unhashed_description' must be a valid hex strings",

View file

@ -2,7 +2,6 @@ import base64
import hashlib import hashlib
import hmac import hmac
import urllib import urllib
from binascii import unhexlify
from http import HTTPStatus from http import HTTPStatus
from typing import Dict from typing import Dict
@ -19,7 +18,7 @@ def generate_bleskomat_lnurl_signature(
payload: str, api_key_secret: str, api_key_encoding: str = "hex" payload: str, api_key_secret: str, api_key_encoding: str = "hex"
): ):
if api_key_encoding == "hex": if api_key_encoding == "hex":
key = unhexlify(api_key_secret) key = bytes.fromhex(api_key_secret)
elif api_key_encoding == "base64": elif api_key_encoding == "base64":
key = base64.b64decode(api_key_secret) key = base64.b64decode(api_key_secret)
else: else:

View file

@ -42,23 +42,20 @@ Updated for v0.1.3
- 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). - 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 approach 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!
## Rewriting / Erasing the card - Boltcard NFC Card Creator ## Erasing the card - Boltcard NFC Card Creator
Updated for v0.1.3
It is possible not only to reset the keys but also to disable the SUN function and completely erase the card so it can be used again as a static tag or set up as a new Bolt Card. 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).
IMPORTANT: - Click the QR code button next to a card to view its details and select WIPE
* It is immanent that you have access to your old keys so do not erase this card in LNbits before you copied those strings! - OR click the red cross icon on the right side to reach the same
* If you tried to write to them and failed you will need the same amount of positive writing sessions to unlock the card. - In the android app (Advanced -> Reset Keys)
- Click SCAN QR CODE to scan the QR
- in the BoltCard-Extension click the QR code button next to your old card and copy Key0 - Or click WIPE DATA in LNbits to copy and paste in to the app (PASTE KEY JSON)
- in the BoltApp click Advanced - Reset keys and paste the Key0 into the first field named Key0 - Click RESET CARD NOW and approach the NFC card to erase it. DO NOT REMOVE THE CARD PREMATURELY!
- repeat with Key1/Key2/Key3/Key0 - Now if there is all success the card can be safely delete from LNbits (but keep the keys backuped anyway; batter safe than brick).
- when done pasting all 4 keys scan your card with the BoltApp
- Thats it 🥳
- If everything was successful the card can be safely deleted from LNbits (but keep the keys backed up anyway; batter safe than brick).
You can watch a video of this process here https://www.youtube.com/watch?time_continue=230&v=Pe0YXHawHvQ&feature=emb_logo
If you somehow find yourself in some non-standard state (for instance only k3 and k4 remains filled after previous unsuccessful reset), then you need edit the key fields manually (for instance leave k0-k2 to zeroes and provide the right k3 and k4).
## Setting the card - computer (hard way) ## Setting the card - computer (hard way)

View file

@ -147,7 +147,7 @@ async def api_hits(
@boltcards_ext.get("/api/v1/refunds") @boltcards_ext.get("/api/v1/refunds")
async def api_hits( async def api_refunds(
g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False) g: WalletTypeInfo = Depends(get_key_type), all_wallets: bool = Query(False)
): ):
wallet_ids = [g.wallet.id] wallet_ids = [g.wallet.id]

View file

@ -1,6 +1,5 @@
import asyncio import asyncio
import os import os
from binascii import hexlify, unhexlify
from hashlib import sha256 from hashlib import sha256
from typing import Awaitable, Union from typing import Awaitable, Union
@ -56,7 +55,7 @@ async def create_swap(data: CreateSubmarineSwap) -> SubmarineSwap:
raise raise
refund_privkey = ec.PrivateKey(os.urandom(32), True, net) refund_privkey = ec.PrivateKey(os.urandom(32), True, net)
refund_pubkey_hex = hexlify(refund_privkey.sec()).decode("UTF-8") refund_pubkey_hex = bytes.hex(refund_privkey.sec()).decode("UTF-8")
res = req_wrap( res = req_wrap(
"post", "post",
@ -121,7 +120,7 @@ async def create_reverse_swap(
return False return False
claim_privkey = ec.PrivateKey(os.urandom(32), True, net) claim_privkey = ec.PrivateKey(os.urandom(32), True, net)
claim_pubkey_hex = hexlify(claim_privkey.sec()).decode("UTF-8") claim_pubkey_hex = bytes.hex(claim_privkey.sec()).decode("UTF-8")
preimage = os.urandom(32) preimage = os.urandom(32)
preimage_hash = sha256(preimage).hexdigest() preimage_hash = sha256(preimage).hexdigest()
@ -311,12 +310,12 @@ async def create_onchain_tx(
sequence = 0xFFFFFFFE sequence = 0xFFFFFFFE
else: else:
privkey = ec.PrivateKey.from_wif(swap.claim_privkey) privkey = ec.PrivateKey.from_wif(swap.claim_privkey)
preimage = unhexlify(swap.preimage) preimage = bytes.fromhex(swap.preimage)
onchain_address = swap.onchain_address onchain_address = swap.onchain_address
sequence = 0xFFFFFFFF sequence = 0xFFFFFFFF
locktime = swap.timeout_block_height locktime = swap.timeout_block_height
redeem_script = unhexlify(swap.redeem_script) redeem_script = bytes.fromhex(swap.redeem_script)
fees = get_fee_estimation() fees = get_fee_estimation()
@ -324,7 +323,7 @@ async def create_onchain_tx(
script_pubkey = script.address_to_scriptpubkey(onchain_address) script_pubkey = script.address_to_scriptpubkey(onchain_address)
vin = [TransactionInput(unhexlify(txid), vout_cnt, sequence=sequence)] vin = [TransactionInput(bytes.fromhex(txid), vout_cnt, sequence=sequence)]
vout = [TransactionOutput(vout_amount - fees, script_pubkey)] vout = [TransactionOutput(vout_amount - fees, script_pubkey)]
tx = Transaction(vin=vin, vout=vout) tx = Transaction(vin=vin, vout=vout)

View file

@ -1,6 +1,5 @@
import asyncio import asyncio
import json import json
from binascii import hexlify
import httpx import httpx
import websockets import websockets
@ -84,7 +83,7 @@ def get_mempool_blockheight() -> int:
async def send_onchain_tx(tx: Transaction): async def send_onchain_tx(tx: Transaction):
raw = hexlify(tx.serialize()) raw = bytes.hex(tx.serialize())
logger.debug(f"Boltz - mempool sending onchain tx...") logger.debug(f"Boltz - mempool sending onchain tx...")
req_wrap( req_wrap(
"post", "post",

View file

@ -1,7 +1,6 @@
import os import os
import random import random
import time import time
from binascii import hexlify, unhexlify
from typing import Any, List, Optional, Union from typing import Any, List, Optional, Union
from cashu.core.base import MintKeyset from cashu.core.base import MintKeyset

View file

@ -301,34 +301,6 @@ def gerty_should_sleep(utc_offset: int = 0):
return False 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])
async def get_mining_stat(stat_slug: str, gerty): async def get_mining_stat(stat_slug: str, gerty):
text = [] text = []
if stat_slug == "mining_current_hash_rate": if stat_slug == "mining_current_hash_rate":

View file

@ -30,7 +30,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
@invoices_ext.get("/pay/{invoice_id}", response_class=HTMLResponse) @invoices_ext.get("/pay/{invoice_id}", response_class=HTMLResponse)
async def index(request: Request, invoice_id: str): async def pay(request: Request, invoice_id: str):
invoice = await get_invoice(invoice_id) invoice = await get_invoice(invoice_id)
if not invoice: if not invoice:

View file

@ -1,10 +1,8 @@
from binascii import unhexlify
from lnbits.bolt11 import Invoice from lnbits.bolt11 import Invoice
def to_buffer(payment_hash: str): def to_buffer(payment_hash: str):
return {"type": "Buffer", "data": [b for b in unhexlify(payment_hash)]} return {"type": "Buffer", "data": [b for b in bytes.fromhex(payment_hash)]}
def decoded_as_lndhub(invoice: Invoice): def decoded_as_lndhub(invoice: Invoice):

View file

@ -71,7 +71,7 @@ async def get_all_addresses(wallet_ids: Union[str, List[str]]) -> List[Address]:
q = ",".join(["?"] * len(wallet_ids)) q = ",".join(["?"] * len(wallet_ids))
rows = await db.fetchall( rows = await db.fetchall(
f""" f"""
SELECT a.* SELECT a.*
FROM nostrnip5.addresses a FROM nostrnip5.addresses a
JOIN nostrnip5.domains d ON d.id = a.domain_id JOIN nostrnip5.domains d ON d.id = a.domain_id
WHERE d.wallet IN ({q}) WHERE d.wallet IN ({q})
@ -139,7 +139,7 @@ async def delete_domain(domain_id) -> bool:
return True return True
async def delete_address(address_id) -> bool: async def delete_address(address_id):
await db.execute( await db.execute(
""" """
DELETE FROM nostrnip5.addresses WHERE id = ? DELETE FROM nostrnip5.addresses WHERE id = ?

View file

@ -1,9 +1,9 @@
import asyncio import asyncio
import json
from loguru import logger
from lnbits.core.models import Payment from lnbits.core.models import Payment
from lnbits.helpers import urlsafe_short_hash from lnbits.tasks import register_invoice_listener
from lnbits.tasks import internal_invoice_queue, register_invoice_listener
from .crud import activate_address from .crud import activate_address
@ -18,17 +18,18 @@ async def wait_for_paid_invoices():
async def on_invoice_paid(payment: Payment) -> None: async def on_invoice_paid(payment: Payment) -> None:
if not payment.extra:
return
if payment.extra.get("tag") != "nostrnip5": if payment.extra.get("tag") != "nostrnip5":
# not relevant
return return
domain_id = payment.extra.get("domain_id") domain_id = payment.extra.get("domain_id")
address_id = payment.extra.get("address_id") address_id = payment.extra.get("address_id")
print("Activating NOSTR NIP-05") if domain_id and address_id:
print(domain_id) logger.info("Activating NOSTR NIP-05")
print(address_id) logger.info(domain_id)
logger.info(address_id)
active = await activate_address(domain_id, address_id) await activate_address(domain_id, address_id)
return return

View file

@ -1,8 +1,7 @@
from datetime import datetime from datetime import datetime
from http import HTTPStatus from http import HTTPStatus
from fastapi import FastAPI, Request from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse
@ -24,7 +23,7 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
@nostrnip5_ext.get("/signup/{domain_id}", response_class=HTMLResponse) @nostrnip5_ext.get("/signup/{domain_id}", response_class=HTMLResponse)
async def index(request: Request, domain_id: str): async def signup(request: Request, domain_id: str):
domain = await get_domain(domain_id) domain = await get_domain(domain_id)
if not domain: if not domain:
@ -43,7 +42,7 @@ async def index(request: Request, domain_id: str):
@nostrnip5_ext.get("/rotate/{domain_id}/{address_id}", response_class=HTMLResponse) @nostrnip5_ext.get("/rotate/{domain_id}/{address_id}", response_class=HTMLResponse)
async def index(request: Request, domain_id: str, address_id: str): async def rotate(request: Request, domain_id: str, address_id: str):
domain = await get_domain(domain_id) domain = await get_domain(domain_id)
address = await get_address(domain_id, address_id) address = await get_address(domain_id, address_id)

View file

@ -1,10 +1,8 @@
import re import re
from http import HTTPStatus from http import HTTPStatus
from typing import Optional
from bech32 import bech32_decode, convertbits from bech32 import bech32_decode, convertbits
from fastapi import Query, Request, Response from fastapi import Depends, Query, Response
from fastapi.params import Depends
from loguru import logger from loguru import logger
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
@ -38,7 +36,10 @@ async def api_domains(
): ):
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
if not user:
return []
wallet_ids = user.wallet_ids
return [domain.dict() for domain in await get_domains(wallet_ids)] return [domain.dict() for domain in await get_domains(wallet_ids)]
@ -49,13 +50,20 @@ async def api_addresses(
): ):
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
if not user:
return []
wallet_ids = user.wallet_ids
return [address.dict() for address in await get_all_addresses(wallet_ids)] return [address.dict() for address in await get_all_addresses(wallet_ids)]
@nostrnip5_ext.get("/api/v1/domain/{domain_id}", status_code=HTTPStatus.OK) @nostrnip5_ext.get(
async def api_invoice(domain_id: str, wallet: WalletTypeInfo = Depends(get_key_type)): "/api/v1/domain/{domain_id}",
status_code=HTTPStatus.OK,
dependencies=[Depends(get_key_type)],
)
async def api_invoice(domain_id: str):
domain = await get_domain(domain_id) domain = await get_domain(domain_id)
if not domain: if not domain:
raise HTTPException( raise HTTPException(
@ -104,11 +112,11 @@ async def api_address_delete(
@nostrnip5_ext.post( @nostrnip5_ext.post(
"/api/v1/domain/{domain_id}/address/{address_id}/activate", "/api/v1/domain/{domain_id}/address/{address_id}/activate",
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK,
dependencies=[Depends(require_admin_key)],
) )
async def api_address_activate( async def api_address_activate(
domain_id: str, domain_id: str,
address_id: str, address_id: str,
wallet: WalletTypeInfo = Depends(require_admin_key),
): ):
await activate_address(domain_id, address_id) await activate_address(domain_id, address_id)
@ -126,9 +134,11 @@ async def api_address_rotate(
): ):
if post_data.pubkey.startswith("npub"): if post_data.pubkey.startswith("npub"):
hrp, data = bech32_decode(post_data.pubkey) _, data = bech32_decode(post_data.pubkey)
decoded_data = convertbits(data, 5, 8, False) if data:
post_data.pubkey = bytes(decoded_data).hex() decoded_data = convertbits(data, 5, 8, False)
if decoded_data:
post_data.pubkey = bytes(decoded_data).hex()
if len(bytes.fromhex(post_data.pubkey)) != 32: if len(bytes.fromhex(post_data.pubkey)) != 32:
raise HTTPException( raise HTTPException(
@ -173,10 +183,12 @@ async def api_address_create(
status_code=HTTPStatus.NOT_FOUND, detail="Local part already exists." status_code=HTTPStatus.NOT_FOUND, detail="Local part already exists."
) )
if post_data.pubkey.startswith("npub"): if post_data and post_data.pubkey.startswith("npub"):
hrp, data = bech32_decode(post_data.pubkey) _, data = bech32_decode(post_data.pubkey)
decoded_data = convertbits(data, 5, 8, False) if data:
post_data.pubkey = bytes(decoded_data).hex() decoded_data = convertbits(data, 5, 8, False)
if decoded_data:
post_data.pubkey = bytes(decoded_data).hex()
if len(bytes.fromhex(post_data.pubkey)) != 32: if len(bytes.fromhex(post_data.pubkey)) != 32:
raise HTTPException( raise HTTPException(
@ -233,15 +245,17 @@ async def api_get_nostr_json(
output = {} output = {}
for address in addresses: for address in addresses:
local_part = address.get("local_part").lower() local_part = address.get("local_part")
if not local_part:
continue
if address.get("active") == False: if address.get("active") == False:
continue continue
if name and name.lower() != local_part: if name and name.lower() != local_part.lower():
continue continue
output[local_part] = address.get("pubkey") output[local_part.lower()] = address.get("pubkey")
response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS" response.headers["Access-Control-Allow-Methods"] = "GET,OPTIONS"

View file

@ -131,53 +131,6 @@ async def check_address_balance(charge_id: str) -> Optional[Charges]:
################## SETTINGS ################### ################## SETTINGS ###################
async def save_theme(data: SatsPayThemes, css_id: str = None):
# insert or update
if css_id:
await db.execute(
"""
UPDATE satspay.themes SET custom_css = ?, title = ? WHERE css_id = ?
""",
(data.custom_css, data.title, css_id),
)
else:
css_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO satspay.themes (
css_id,
title,
user,
custom_css
)
VALUES (?, ?, ?, ?)
""",
(
css_id,
data.title,
data.user,
data.custom_css,
),
)
return await get_theme(css_id)
async def get_theme(css_id: str) -> SatsPayThemes:
row = await db.fetchone("SELECT * FROM satspay.themes WHERE css_id = ?", (css_id,))
return SatsPayThemes.from_row(row) if row else None
async def get_themes(user_id: str) -> List[SatsPayThemes]:
rows = await db.fetchall(
"""SELECT * FROM satspay.themes WHERE "user" = ? ORDER BY "timestamp" DESC """,
(user_id,),
)
return await get_config(row.user)
################## SETTINGS ###################
async def save_theme(data: SatsPayThemes, css_id: str = None): async def save_theme(data: SatsPayThemes, css_id: str = None):
# insert or update # insert or update
if css_id: if css_id:

View file

@ -171,7 +171,7 @@ async def api_themes_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
@satspay_ext.delete("/api/v1/themes/{theme_id}") @satspay_ext.delete("/api/v1/themes/{theme_id}")
async def api_charge_delete(theme_id, wallet: WalletTypeInfo = Depends(get_key_type)): async def api_theme_delete(theme_id, wallet: WalletTypeInfo = Depends(get_key_type)):
theme = await get_theme(theme_id) theme = await get_theme(theme_id)
if not theme: if not theme:

View file

@ -25,9 +25,9 @@ async def wait_for_paid_invoices():
await on_invoice_paid(payment) await on_invoice_paid(payment)
async def on_invoice_paid(payment: Payment) -> None: async def on_invoice_paid(payment: Payment):
# (avoid loops) # (avoid loops)
if payment.extra.get("tag") == "scrubed": if payment.extra and payment.extra.get("tag") == "scrubed":
# already scrubbed # already scrubbed
return return
@ -53,7 +53,7 @@ async def on_invoice_paid(payment: Payment) -> None:
timeout=40, timeout=40,
) )
if r.is_error: if r.is_error:
raise httpx.ConnectError raise httpx.ConnectError("issue with scrub callback")
except (httpx.ConnectError, httpx.RequestError): except (httpx.ConnectError, httpx.RequestError):
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, status_code=HTTPStatus.BAD_REQUEST,

View file

@ -1,5 +1,4 @@
from fastapi import Request from fastapi import Depends, Request
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.responses import HTMLResponse from starlette.responses import HTMLResponse

View file

@ -1,9 +1,6 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import Request from fastapi import Depends, Query
from fastapi.param_functions import Query
from fastapi.params import Depends
from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
@ -23,14 +20,14 @@ from .models import CreateScrubLink
@scrub_ext.get("/api/v1/links", status_code=HTTPStatus.OK) @scrub_ext.get("/api/v1/links", status_code=HTTPStatus.OK)
async def api_links( async def api_links(
req: Request,
wallet: WalletTypeInfo = Depends(get_key_type), wallet: WalletTypeInfo = Depends(get_key_type),
all_wallets: bool = Query(False), all_wallets: bool = Query(False),
): ):
wallet_ids = [wallet.wallet.id] wallet_ids = [wallet.wallet.id]
if all_wallets: if all_wallets:
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
wallet_ids = user.wallet_ids if user else []
try: try:
return [link.dict() for link in await get_scrub_links(wallet_ids)] return [link.dict() for link in await get_scrub_links(wallet_ids)]
@ -43,9 +40,7 @@ async def api_links(
@scrub_ext.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK) @scrub_ext.get("/api/v1/links/{link_id}", status_code=HTTPStatus.OK)
async def api_link_retrieve( async def api_link_retrieve(link_id, wallet: WalletTypeInfo = Depends(get_key_type)):
r: Request, link_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
link = await get_scrub_link(link_id) link = await get_scrub_link(link_id)
if not link: if not link:

View file

@ -8,7 +8,7 @@ from .models import Tip, TipJar, createTipJar
async def create_tip( async def create_tip(
id: int, wallet: str, message: str, name: str, sats: int, tipjar: str id: str, wallet: str, message: str, name: str, sats: int, tipjar: str
) -> Tip: ) -> Tip:
"""Create a new Tip""" """Create a new Tip"""
await db.execute( await db.execute(
@ -33,11 +33,7 @@ async def create_tip(
async def create_tipjar(data: createTipJar) -> TipJar: async def create_tipjar(data: createTipJar) -> TipJar:
"""Create a new TipJar""" """Create a new TipJar"""
await db.execute(
returning = "" if db.type == SQLITE else "RETURNING ID"
method = db.execute if db.type == SQLITE else db.fetchone
result = await (method)(
f""" f"""
INSERT INTO tipjar.TipJars ( INSERT INTO tipjar.TipJars (
name, name,
@ -46,16 +42,11 @@ async def create_tipjar(data: createTipJar) -> TipJar:
onchain onchain
) )
VALUES (?, ?, ?, ?) VALUES (?, ?, ?, ?)
{returning}
""", """,
(data.name, data.wallet, data.webhook, data.onchain), (data.name, data.wallet, data.webhook, data.onchain),
) )
if db.type == SQLITE: row = await db.fetchone("SELECT * FROM tipjar.TipJars LIMIT 1")
tipjar_id = result._result_proxy.lastrowid tipjar = TipJar(**row)
else:
tipjar_id = result[0]
tipjar = await get_tipjar(tipjar_id)
assert tipjar assert tipjar
return tipjar return tipjar

View file

@ -1,20 +1,7 @@
from sqlite3 import Row from sqlite3 import Row
from typing import Optional from typing import Optional
from fastapi.param_functions import Query
from pydantic import BaseModel from pydantic import BaseModel
from pydantic.main import BaseModel
class CreateCharge(BaseModel):
onchainwallet: str = Query(None)
lnbitswallet: str = Query(None)
description: str = Query(...)
webhook: str = Query(None)
completelink: str = Query(None)
completelinktext: str = Query(None)
time: int = Query(..., ge=1)
amount: int = Query(..., ge=1)
class createTip(BaseModel): class createTip(BaseModel):
@ -44,8 +31,8 @@ class Tip(BaseModel):
class createTipJar(BaseModel): class createTipJar(BaseModel):
name: str name: str
wallet: str wallet: str
webhook: str = None webhook: Optional[str]
onchain: str = None onchain: Optional[str]
class createTips(BaseModel): class createTips(BaseModel):

View file

@ -1,8 +1,7 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi import Request from fastapi import Depends, Request
from fastapi.param_functions import Query from fastapi.param_functions import Query
from fastapi.params import Depends
from fastapi.templating import Jinja2Templates from fastapi.templating import Jinja2Templates
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException

View file

@ -1,13 +1,13 @@
from http import HTTPStatus from http import HTTPStatus
from fastapi.param_functions import Query from fastapi import Depends, Query
from fastapi.params import Depends
from starlette.exceptions import HTTPException from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.decorators import WalletTypeInfo, get_key_type from lnbits.decorators import WalletTypeInfo, get_key_type
from ..satspay.crud import create_charge from ..satspay.crud import create_charge
from ..satspay.models import CreateCharge
from . import tipjar_ext from . import tipjar_ext
from .crud import ( from .crud import (
create_tip, create_tip,
@ -22,7 +22,7 @@ from .crud import (
update_tipjar, update_tipjar,
) )
from .helpers import get_charge_details from .helpers import get_charge_details
from .models import CreateCharge, createTipJar, createTips from .models import createTip, createTipJar, createTips
@tipjar_ext.post("/api/v1/tipjars") @tipjar_ext.post("/api/v1/tipjars")
@ -43,12 +43,16 @@ async def user_from_wallet(wallet: WalletTypeInfo = Depends(get_key_type)):
@tipjar_ext.post("/api/v1/tips") @tipjar_ext.post("/api/v1/tips")
async def api_create_tip(data: createTips): async def api_create_tip(data: createTips):
"""Take data from tip form and return satspay charge""" """Take data from tip form and return satspay charge"""
sats = data.sats sats = int(data.sats)
message = data.message message = data.message
if not message: if not message:
message = "No message" message = "No message"
tipjar_id = data.tipjar tipjar_id = int(data.tipjar)
tipjar = await get_tipjar(tipjar_id) tipjar = await get_tipjar(tipjar_id)
if not tipjar:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Tipjar does not exist."
)
webhook = tipjar.webhook webhook = tipjar.webhook
charge_details = await get_charge_details(tipjar.id) charge_details = await get_charge_details(tipjar.id)
@ -62,13 +66,14 @@ async def api_create_tip(data: createTips):
user=charge_details["user"], user=charge_details["user"],
data=CreateCharge( data=CreateCharge(
amount=sats, amount=sats,
webhook=webhook, webhook=webhook or "",
description=description, description=description,
onchainwallet=charge_details["onchainwallet"], onchainwallet=charge_details["onchainwallet"],
lnbitswallet=charge_details["lnbitswallet"], lnbitswallet=charge_details["lnbitswallet"],
completelink=charge_details["completelink"], completelink=charge_details["completelink"],
completelinktext=charge_details["completelinktext"], completelinktext=charge_details["completelinktext"],
time=charge_details["time"], time=charge_details["time"],
custom_css="",
), ),
) )
@ -77,7 +82,7 @@ async def api_create_tip(data: createTips):
wallet=tipjar.wallet, wallet=tipjar.wallet,
message=message, message=message,
name=name, name=name,
sats=data.sats, sats=int(data.sats),
tipjar=data.tipjar, tipjar=data.tipjar,
) )
@ -87,28 +92,34 @@ async def api_create_tip(data: createTips):
@tipjar_ext.get("/api/v1/tipjars") @tipjar_ext.get("/api/v1/tipjars")
async def api_get_tipjars(wallet: WalletTypeInfo = Depends(get_key_type)): async def api_get_tipjars(wallet: WalletTypeInfo = Depends(get_key_type)):
"""Return list of all tipjars assigned to wallet with given invoice key""" """Return list of all tipjars assigned to wallet with given invoice key"""
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
if not user:
return []
tipjars = [] tipjars = []
for wallet_id in wallet_ids: for wallet_id in user.wallet_ids:
new_tipjars = await get_tipjars(wallet_id) new_tipjars = await get_tipjars(wallet_id)
tipjars += new_tipjars if new_tipjars else [] tipjars += new_tipjars if new_tipjars else []
return [tipjar.dict() for tipjar in tipjars] if tipjars else [] return [tipjar.dict() for tipjar in tipjars]
@tipjar_ext.get("/api/v1/tips") @tipjar_ext.get("/api/v1/tips")
async def api_get_tips(wallet: WalletTypeInfo = Depends(get_key_type)): async def api_get_tips(wallet: WalletTypeInfo = Depends(get_key_type)):
"""Return list of all tips assigned to wallet with given invoice key""" """Return list of all tips assigned to wallet with given invoice key"""
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids user = await get_user(wallet.wallet.user)
if not user:
return []
tips = [] tips = []
for wallet_id in wallet_ids: for wallet_id in user.wallet_ids:
new_tips = await get_tips(wallet_id) new_tips = await get_tips(wallet_id)
tips += new_tips if new_tips else [] tips += new_tips if new_tips else []
return [tip.dict() for tip in tips] if tips else [] return [tip.dict() for tip in tips]
@tipjar_ext.put("/api/v1/tips/{tip_id}") @tipjar_ext.put("/api/v1/tips/{tip_id}")
async def api_update_tip( async def api_update_tip(
wallet: WalletTypeInfo = Depends(get_key_type), tip_id: str = Query(None) data: createTip,
wallet: WalletTypeInfo = Depends(get_key_type),
tip_id: str = Query(None),
): ):
"""Update a tip with the data given in the request""" """Update a tip with the data given in the request"""
if tip_id: if tip_id:
@ -125,7 +136,7 @@ async def api_update_tip(
status_code=HTTPStatus.FORBIDDEN, detail="Not your tip." status_code=HTTPStatus.FORBIDDEN, detail="Not your tip."
) )
tip = await update_tip(tip_id, **g.data) tip = await update_tip(tip_id, **data.dict())
else: else:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="No tip ID specified" status_code=HTTPStatus.BAD_REQUEST, detail="No tip ID specified"
@ -135,7 +146,9 @@ async def api_update_tip(
@tipjar_ext.put("/api/v1/tipjars/{tipjar_id}") @tipjar_ext.put("/api/v1/tipjars/{tipjar_id}")
async def api_update_tipjar( async def api_update_tipjar(
wallet: WalletTypeInfo = Depends(get_key_type), tipjar_id: str = Query(None) data: createTipJar,
wallet: WalletTypeInfo = Depends(get_key_type),
tipjar_id: int = Query(None),
): ):
"""Update a tipjar with the data given in the request""" """Update a tipjar with the data given in the request"""
if tipjar_id: if tipjar_id:
@ -151,7 +164,7 @@ async def api_update_tipjar(
status_code=HTTPStatus.FORBIDDEN, detail="Not your tipjar." status_code=HTTPStatus.FORBIDDEN, detail="Not your tipjar."
) )
tipjar = await update_tipjar(tipjar_id, **data) tipjar = await update_tipjar(str(tipjar_id), **data.dict())
else: else:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="No tipjar ID specified" status_code=HTTPStatus.BAD_REQUEST, detail="No tipjar ID specified"
@ -181,7 +194,7 @@ async def api_delete_tip(
@tipjar_ext.delete("/api/v1/tipjars/{tipjar_id}") @tipjar_ext.delete("/api/v1/tipjars/{tipjar_id}")
async def api_delete_tipjar( async def api_delete_tipjar(
wallet: WalletTypeInfo = Depends(get_key_type), tipjar_id: str = Query(None) wallet: WalletTypeInfo = Depends(get_key_type), tipjar_id: int = Query(None)
): ):
"""Delete the tipjar with the given tipjar_id""" """Delete the tipjar with the given tipjar_id"""
tipjar = await get_tipjar(tipjar_id) tipjar = await get_tipjar(tipjar_id)

View file

@ -292,7 +292,7 @@ async def api_psbt_create(
@watchonly_ext.put("/api/v1/psbt/utxos") @watchonly_ext.put("/api/v1/psbt/utxos")
async def api_psbt_extract_tx( async def api_psbt_utxos_tx(
req: Request, w: WalletTypeInfo = Depends(require_admin_key) req: Request, w: WalletTypeInfo = Depends(require_admin_key)
): ):
"""Extract previous unspent transaction outputs (tx_id, vout) from PSBT""" """Extract previous unspent transaction outputs (tx_id, vout) from PSBT"""

View file

@ -117,7 +117,7 @@ async def print_qr(request: Request, link_id):
@withdraw_ext.get("/csv/{link_id}", response_class=HTMLResponse) @withdraw_ext.get("/csv/{link_id}", response_class=HTMLResponse)
async def print_qr(request: Request, link_id): async def csv(request: Request, link_id):
link = await get_withdraw_link(link_id) link = await get_withdraw_link(link_id)
if not link: if not link:
raise HTTPException( raise HTTPException(

View file

@ -8,7 +8,6 @@ except ImportError: # pragma: nocover
import asyncio import asyncio
import base64 import base64
import binascii
import hashlib import hashlib
from os import environ, error from os import environ, error
from typing import AsyncGenerator, Dict, Optional from typing import AsyncGenerator, Dict, Optional
@ -229,8 +228,8 @@ class LndWallet(Wallet):
try: try:
r_hash = hex_to_bytes(checking_id) r_hash = hex_to_bytes(checking_id)
if len(r_hash) != 32: if len(r_hash) != 32:
raise binascii.Error raise ValueError
except binascii.Error: except ValueError:
# this may happen if we switch between backend wallets # this may happen if we switch between backend wallets
# that use different checking_id formats # that use different checking_id formats
return PaymentStatus(None) return PaymentStatus(None)
@ -250,8 +249,8 @@ class LndWallet(Wallet):
try: try:
r_hash = hex_to_bytes(checking_id) r_hash = hex_to_bytes(checking_id)
if len(r_hash) != 32: if len(r_hash) != 32:
raise binascii.Error raise ValueError
except binascii.Error: except ValueError:
# this may happen if we switch between backend wallets # this may happen if we switch between backend wallets
# that use different checking_id formats # that use different checking_id formats
return PaymentStatus(None) return PaymentStatus(None)

View file

@ -94,7 +94,6 @@ exclude = """(?x)(
| ^lnbits/extensions/boltcards. | ^lnbits/extensions/boltcards.
| ^lnbits/extensions/events. | ^lnbits/extensions/events.
| ^lnbits/extensions/gerty. | ^lnbits/extensions/gerty.
| ^lnbits/extensions/hivemind.
| ^lnbits/extensions/invoices. | ^lnbits/extensions/invoices.
| ^lnbits/extensions/livestream. | ^lnbits/extensions/livestream.
| ^lnbits/extensions/lnaddress. | ^lnbits/extensions/lnaddress.
@ -102,15 +101,11 @@ exclude = """(?x)(
| ^lnbits/extensions/lnticket. | ^lnbits/extensions/lnticket.
| ^lnbits/extensions/lnurldevice. | ^lnbits/extensions/lnurldevice.
| ^lnbits/extensions/lnurlp. | ^lnbits/extensions/lnurlp.
| ^lnbits/extensions/lnurlpayout.
| ^lnbits/extensions/nostrnip5.
| ^lnbits/extensions/offlineshop. | ^lnbits/extensions/offlineshop.
| ^lnbits/extensions/paywall. | ^lnbits/extensions/paywall.
| ^lnbits/extensions/satspay. | ^lnbits/extensions/satspay.
| ^lnbits/extensions/scrub.
| ^lnbits/extensions/splitpayments. | ^lnbits/extensions/splitpayments.
| ^lnbits/extensions/streamalerts. | ^lnbits/extensions/streamalerts.
| ^lnbits/extensions/tipjar.
| ^lnbits/extensions/tpos. | ^lnbits/extensions/tpos.
| ^lnbits/extensions/watchonly. | ^lnbits/extensions/watchonly.
| ^lnbits/extensions/withdraw. | ^lnbits/extensions/withdraw.

View file

@ -1,5 +1,4 @@
import hashlib import hashlib
from binascii import hexlify
import pytest import pytest
import pytest_asyncio import pytest_asyncio