This commit is contained in:
benarc 2022-01-30 19:43:30 +00:00
parent d6f145eee9
commit 228717195b
31 changed files with 278 additions and 182 deletions

View File

@ -48,10 +48,7 @@ def create_app(config_object="lnbits.settings") -> FastAPI:
origins = ["*"] origins = ["*"]
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware, allow_origins=origins, allow_methods=["*"], allow_headers=["*"]
allow_origins=origins,
allow_methods=["*"],
allow_headers=["*"],
) )
g().config = lnbits.settings g().config = lnbits.settings

View File

@ -116,7 +116,6 @@ def decode(pr: str) -> Invoice:
return invoice return invoice
def encode(options): def encode(options):
""" Convert options into LnAddr and pass it to the encoder """ Convert options into LnAddr and pass it to the encoder
""" """
@ -131,27 +130,31 @@ def encode(options):
addr.paymenthash = unhexlify(options.paymenthash) addr.paymenthash = unhexlify(options.paymenthash)
if options.description: if options.description:
addr.tags.append(('d', options.description)) addr.tags.append(("d", options.description))
if options.description_hashed: if options.description_hashed:
addr.tags.append(('h', options.description_hashed)) addr.tags.append(("h", options.description_hashed))
if options.expires: if options.expires:
addr.tags.append(('x', options.expires)) addr.tags.append(("x", options.expires))
if options.fallback: if options.fallback:
addr.tags.append(('f', options.fallback)) addr.tags.append(("f", options.fallback))
for r in options.route: for r in options.route:
splits = r.split('/') splits = r.split("/")
route=[] route = []
while len(splits) >= 5: while len(splits) >= 5:
route.append((unhexlify(splits[0]), route.append(
unhexlify(splits[1]), (
int(splits[2]), unhexlify(splits[0]),
int(splits[3]), unhexlify(splits[1]),
int(splits[4]))) int(splits[2]),
int(splits[3]),
int(splits[4]),
)
)
splits = splits[5:] splits = splits[5:]
assert(len(splits) == 0) assert len(splits) == 0
addr.tags.append(('r', route)) addr.tags.append(("r", route))
return lnencode(addr, options.privkey) return lnencode(addr, options.privkey)
@ -159,21 +162,22 @@ def lnencode(addr, privkey):
if addr.amount: if addr.amount:
amount = Decimal(str(addr.amount)) amount = Decimal(str(addr.amount))
# We can only send down to millisatoshi. # We can only send down to millisatoshi.
if amount * 10**12 % 10: if amount * 10 ** 12 % 10:
raise ValueError("Cannot encode {}: too many decimal places".format( raise ValueError(
addr.amount)) "Cannot encode {}: too many decimal places".format(addr.amount)
)
amount = addr.currency + shorten_amount(amount) amount = addr.currency + shorten_amount(amount)
else: else:
amount = addr.currency if addr.currency else '' amount = addr.currency if addr.currency else ""
hrp = 'ln' + amount hrp = "ln" + amount
# Start with the timestamp # Start with the timestamp
data = bitstring.pack('uint:35', addr.date) data = bitstring.pack("uint:35", addr.date)
# Payment hash # Payment hash
data += tagged_bytes('p', addr.paymenthash) data += tagged_bytes("p", addr.paymenthash)
tags_set = set() tags_set = set()
for k, v in addr.tags: for k, v in addr.tags:
@ -181,30 +185,36 @@ def lnencode(addr, privkey):
# BOLT #11: # BOLT #11:
# #
# A writer MUST NOT include more than one `d`, `h`, `n` or `x` fields, # A writer MUST NOT include more than one `d`, `h`, `n` or `x` fields,
if k in ('d', 'h', 'n', 'x'): if k in ("d", "h", "n", "x"):
if k in tags_set: if k in tags_set:
raise ValueError("Duplicate '{}' tag".format(k)) raise ValueError("Duplicate '{}' tag".format(k))
if k == 'r': if k == "r":
route = bitstring.BitArray() route = bitstring.BitArray()
for step in v: for step in v:
pubkey, channel, feebase, feerate, cltv = step pubkey, channel, feebase, feerate, cltv = step
route.append(bitstring.BitArray(pubkey) + bitstring.BitArray(channel) + bitstring.pack('intbe:32', feebase) + bitstring.pack('intbe:32', feerate) + bitstring.pack('intbe:16', cltv)) route.append(
data += tagged('r', route) bitstring.BitArray(pubkey)
elif k == 'f': + bitstring.BitArray(channel)
+ bitstring.pack("intbe:32", feebase)
+ bitstring.pack("intbe:32", feerate)
+ bitstring.pack("intbe:16", cltv)
)
data += tagged("r", route)
elif k == "f":
data += encode_fallback(v, addr.currency) data += encode_fallback(v, addr.currency)
elif k == 'd': elif k == "d":
data += tagged_bytes('d', v.encode()) data += tagged_bytes("d", v.encode())
elif k == 'x': elif k == "x":
# Get minimal length by trimming leading 5 bits at a time. # Get minimal length by trimming leading 5 bits at a time.
expirybits = bitstring.pack('intbe:64', v)[4:64] expirybits = bitstring.pack("intbe:64", v)[4:64]
while expirybits.startswith('0b00000'): while expirybits.startswith("0b00000"):
expirybits = expirybits[5:] expirybits = expirybits[5:]
data += tagged('x', expirybits) data += tagged("x", expirybits)
elif k == 'h': elif k == "h":
data += tagged_bytes('h', hashlib.sha256(v.encode('utf-8')).digest()) data += tagged_bytes("h", hashlib.sha256(v.encode("utf-8")).digest())
elif k == 'n': elif k == "n":
data += tagged_bytes('n', v) data += tagged_bytes("n", v)
else: else:
# FIXME: Support unknown tags? # FIXME: Support unknown tags?
raise ValueError("Unknown tag {}".format(k)) raise ValueError("Unknown tag {}".format(k))
@ -215,26 +225,31 @@ def lnencode(addr, privkey):
# #
# A writer MUST include either a `d` or `h` field, and MUST NOT include # A writer MUST include either a `d` or `h` field, and MUST NOT include
# both. # both.
if 'd' in tags_set and 'h' in tags_set: if "d" in tags_set and "h" in tags_set:
raise ValueError("Cannot include both 'd' and 'h'") raise ValueError("Cannot include both 'd' and 'h'")
if not 'd' in tags_set and not 'h' in tags_set: if not "d" in tags_set and not "h" in tags_set:
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(unhexlify(privkey)))
sig = privkey.ecdsa_sign_recoverable(bytearray([ord(c) for c in hrp]) + data.tobytes()) sig = privkey.ecdsa_sign_recoverable(
bytearray([ord(c) for c in hrp]) + data.tobytes()
)
# This doesn't actually serialize, but returns a pair of values :( # This doesn't actually serialize, but returns a pair of values :(
sig, recid = privkey.ecdsa_recoverable_serialize(sig) sig, recid = privkey.ecdsa_recoverable_serialize(sig)
data += bytes(sig) + bytes([recid]) data += bytes(sig) + bytes([recid])
return bech32_encode(hrp, bitarray_to_u5(data)) return bech32_encode(hrp, bitarray_to_u5(data))
class LnAddr(object): class LnAddr(object):
def __init__(self, paymenthash=None, amount=None, currency='bc', tags=None, date=None): def __init__(
self, paymenthash=None, amount=None, currency="bc", tags=None, date=None
):
self.date = int(time.time()) if not date else int(date) self.date = int(time.time()) if not date else int(date)
self.tags = [] if not tags else tags self.tags = [] if not tags else tags
self.unknown_tags = [] self.unknown_tags = []
self.paymenthash=paymenthash self.paymenthash = paymenthash
self.signature = None self.signature = None
self.pubkey = None self.pubkey = None
self.currency = currency self.currency = currency
@ -242,13 +257,13 @@ 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'), hexlify(self.pubkey.serialize()).decode("utf-8"),
self.amount, self.currency, self.amount,
", ".join([k + '=' + str(v) for k, v in self.tags]) self.currency,
", ".join([k + "=" + str(v) for k, v in self.tags]),
) )
def _unshorten_amount(amount: str) -> int: def _unshorten_amount(amount: str) -> int:
"""Given a shortened amount, return millisatoshis""" """Given a shortened amount, return millisatoshis"""
# BOLT #11: # BOLT #11:

View File

@ -30,8 +30,7 @@ async def get_account(
user_id: str, conn: Optional[Connection] = None user_id: str, conn: Optional[Connection] = None
) -> Optional[User]: ) -> Optional[User]:
row = await (conn or db).fetchone( row = await (conn or db).fetchone(
"SELECT id, email, pass as password FROM accounts WHERE id = ?", ( "SELECT id, email, pass as password FROM accounts WHERE id = ?", (user_id,)
user_id,)
) )
return User(**row) if row else None return User(**row) if row else None
@ -305,8 +304,7 @@ async def delete_expired_invoices(conn: Optional[Connection] = None,) -> None:
except: except:
continue continue
expiration_date = datetime.datetime.fromtimestamp( expiration_date = datetime.datetime.fromtimestamp(invoice.date + invoice.expiry)
invoice.date + invoice.expiry)
if expiration_date > datetime.datetime.utcnow(): if expiration_date > datetime.datetime.utcnow():
continue continue

View File

@ -95,7 +95,7 @@ async def pay_invoice(
if max_sat and invoice.amount_msat > max_sat * 1000: if max_sat and invoice.amount_msat > max_sat * 1000:
raise ValueError("Amount in invoice is too high.") raise ValueError("Amount in invoice is too high.")
wallet = await get_wallet(wallet_id, conn=conn) wallet = await get_wallet(wallet_id, conn=conn)
# put all parameters that don't change here # put all parameters that don't change here
PaymentKwargs = TypedDict( PaymentKwargs = TypedDict(
@ -141,15 +141,19 @@ async def pay_invoice(
# do the balance check if internal payment # do the balance check if internal payment
if internal_checking_id: if internal_checking_id:
wallet = await get_wallet(wallet_id, conn=conn) wallet = await get_wallet(wallet_id, conn=conn)
assert wallet assert wallet
if wallet.balance_msat < 0: if wallet.balance_msat < 0:
raise PermissionError("Insufficient balance.") raise PermissionError("Insufficient balance.")
# do the balance check if external payment # do the balance check if external payment
else: else:
if invoice.amount_msat > wallet.balance_msat - (wallet.balance_msat / 100 * 2): if invoice.amount_msat > wallet.balance_msat - (
raise PermissionError("LNbits requires you keep at least 2% reserve to cover potential routing fees.") wallet.balance_msat / 100 * 2
):
raise PermissionError(
"LNbits requires you keep at least 2% reserve to cover potential routing fees."
)
if internal_checking_id: if internal_checking_id:
# mark the invoice from the other side as not pending anymore # mark the invoice from the other side as not pending anymore
@ -326,8 +330,7 @@ async def check_invoice_status(
if not payment.pending: if not payment.pending:
return status return status
if payment.is_out and status.failed: if payment.is_out and status.failed:
print( print(f" - deleting outgoing failed payment {payment.checking_id}: {status}")
f" - deleting outgoing failed payment {payment.checking_id}: {status}")
await payment.delete() await payment.delete()
elif not status.pending: elif not status.pending:
print( print(

View File

@ -77,7 +77,9 @@ async def api_update_wallet(
@core_app.get("/api/v1/payments") @core_app.get("/api/v1/payments")
async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)): async def api_payments(wallet: WalletTypeInfo = Depends(get_key_type)):
await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True) await get_payments(wallet_id=wallet.wallet.id, pending=True, complete=True)
pendingPayments = await get_payments(wallet_id=wallet.wallet.id, pending=True, exclude_uncheckable=True) pendingPayments = await get_payments(
wallet_id=wallet.wallet.id, pending=True, exclude_uncheckable=True
)
for payment in pendingPayments: for payment in pendingPayments:
await check_invoice_status( await check_invoice_status(
wallet_id=payment.wallet_id, payment_hash=payment.payment_hash wallet_id=payment.wallet_id, payment_hash=payment.payment_hash
@ -198,8 +200,7 @@ async def api_payments_create(
invoiceData: CreateInvoiceData = Body(...), invoiceData: CreateInvoiceData = Body(...),
): ):
if wallet.wallet_type < 0 or wallet.wallet_type > 2: if wallet.wallet_type < 0 or wallet.wallet_type > 2:
raise HTTPException( raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid")
status_code=HTTPStatus.BAD_REQUEST, detail="Key is invalid")
if invoiceData.out is True and wallet.wallet_type == 0: if invoiceData.out is True and wallet.wallet_type == 0:
if not invoiceData.bolt11: if not invoiceData.bolt11:
@ -379,16 +380,14 @@ async def api_lnurlscan(code: str):
params.update(callback=url) # with k1 already in it params.update(callback=url) # with k1 already in it
lnurlauth_key = g().wallet.lnurlauth_key(domain) lnurlauth_key = g().wallet.lnurlauth_key(domain)
params.update( params.update(pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex())
pubkey=lnurlauth_key.verifying_key.to_string("compressed").hex())
else: else:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
r = await client.get(url, timeout=5) r = await client.get(url, timeout=5)
if r.is_error: if r.is_error:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.SERVICE_UNAVAILABLE, status_code=HTTPStatus.SERVICE_UNAVAILABLE,
detail={"domain": domain, detail={"domain": domain, "message": "failed to get parameters"},
"message": "failed to get parameters"},
) )
try: try:
@ -418,8 +417,7 @@ async def api_lnurlscan(code: str):
if tag == "withdrawRequest": if tag == "withdrawRequest":
params.update(kind="withdraw") params.update(kind="withdraw")
params.update(fixed=data["minWithdrawable"] params.update(fixed=data["minWithdrawable"] == data["maxWithdrawable"])
== data["maxWithdrawable"])
# callback with k1 already in it # callback with k1 already in it
parsed_callback: ParseResult = urlparse(data["callback"]) parsed_callback: ParseResult = urlparse(data["callback"])
@ -509,18 +507,21 @@ async def api_list_currencies_available():
class ConversionData(BaseModel): class ConversionData(BaseModel):
from_: str = Field('sat', alias="from") from_: str = Field("sat", alias="from")
amount: float amount: float
to: str = Query('usd') to: str = Query("usd")
@core_app.post("/api/v1/conversion") @core_app.post("/api/v1/conversion")
async def api_fiat_as_sats(data: ConversionData): async def api_fiat_as_sats(data: ConversionData):
output = {} output = {}
if data.from_ == 'sat': if data.from_ == "sat":
output["sats"] = int(data.amount) output["sats"] = int(data.amount)
output["BTC"] = data.amount / 100000000 output["BTC"] = data.amount / 100000000
for currency in data.to.split(','): for currency in data.to.split(","):
output[currency.strip().upper()] = await satoshis_amount_as_fiat(data.amount, currency.strip()) output[currency.strip().upper()] = await satoshis_amount_as_fiat(
data.amount, currency.strip()
)
return output return output
else: else:
output[data.from_.upper()] = data.amount output[data.from_.upper()] = data.amount

View File

@ -43,9 +43,7 @@ async def home(request: Request, lightning: str = None):
@core_html_routes.get( @core_html_routes.get(
"/extensions", "/extensions", name="core.extensions", response_class=HTMLResponse
name="core.extensions",
response_class=HTMLResponse,
) )
async def extensions( async def extensions(
request: Request, request: Request,

View File

@ -171,6 +171,7 @@ async def require_admin_key(
else: else:
return wallet return wallet
async def require_invoice_key( async def require_invoice_key(
r: Request, r: Request,
api_key_header: str = Security(api_key_header), api_key_header: str = Security(api_key_header),
@ -184,7 +185,8 @@ async def require_invoice_key(
# If wallet type is not invoice then return the unauthorized status # If wallet type is not invoice then return the unauthorized status
# This also covers when the user passes an invalid key type # This also covers when the user passes an invalid key type
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Invoice (or Admin) key required." status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invoice (or Admin) key required.",
) )
else: else:
return wallet return wallet

View File

@ -52,7 +52,7 @@ async def lnurl_callback(address_id, amount: int = Query(...)):
amount_received = amount amount_received = amount
domain = await get_domain(address.domain) domain = await get_domain(address.domain)
base_url = ( base_url = (
address.wallet_endpoint[:-1] address.wallet_endpoint[:-1]
if address.wallet_endpoint.endswith("/") if address.wallet_endpoint.endswith("/")
@ -71,7 +71,9 @@ async def lnurl_callback(address_id, amount: int = Query(...)):
"out": False, "out": False,
"amount": int(amount_received / 1000), "amount": int(amount_received / 1000),
"description_hash": hashlib.sha256( "description_hash": hashlib.sha256(
(await address.lnurlpay_metadata(domain=domain.domain)).encode("utf-8") (await address.lnurlpay_metadata(domain=domain.domain)).encode(
"utf-8"
)
).hexdigest(), ).hexdigest(),
"extra": {"tag": f"Payment to {address.username}@{domain.domain}"}, "extra": {"tag": f"Payment to {address.username}@{domain.domain}"},
}, },

View File

@ -53,5 +53,5 @@ class Addresses(BaseModel):
text = f"Payment to {self.username}" text = f"Payment to {self.username}"
identifier = f"{self.username}@{domain}" identifier = f"{self.username}@{domain}"
metadata = [["text/plain", text], ["text/identifier", identifier]] metadata = [["text/plain", text], ["text/identifier", identifier]]
return LnurlPayMetadata(json.dumps(metadata)) return LnurlPayMetadata(json.dumps(metadata))

View File

@ -36,7 +36,7 @@ async def display(domain_id, request: Request):
wallet = await get_wallet(domain.wallet) wallet = await get_wallet(domain.wallet)
url = urlparse(str(request.url)) url = urlparse(str(request.url))
return lnaddress_renderer().TemplateResponse( return lnaddress_renderer().TemplateResponse(
"lnaddress/display.html", "lnaddress/display.html",
{ {
@ -45,6 +45,6 @@ async def display(domain_id, request: Request):
"domain_domain": domain.domain, "domain_domain": domain.domain,
"domain_cost": domain.cost, "domain_cost": domain.cost,
"domain_wallet_inkey": wallet.inkey, "domain_wallet_inkey": wallet.inkey,
"root_url": f"{url.scheme}://{url.netloc}" "root_url": f"{url.scheme}://{url.netloc}",
}, },
) )

View File

@ -24,7 +24,15 @@ async def create_lnurldevice(data: createLnurldevice,) -> lnurldevices:
) )
VALUES (?, ?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
""", """,
(lnurldevice_id, lnurldevice_key, data.title, data.wallet, data.currency, data.device, data.profit,), (
lnurldevice_id,
lnurldevice_key,
data.title,
data.wallet,
data.currency,
data.device,
data.profit,
),
) )
return await get_lnurldevice(lnurldevice_id) return await get_lnurldevice(lnurldevice_id)
@ -63,7 +71,9 @@ async def get_lnurldevices(wallet_ids: Union[str, List[str]]) -> List[lnurldevic
async def delete_lnurldevice(lnurldevice_id: str) -> None: async def delete_lnurldevice(lnurldevice_id: str) -> None:
await db.execute("DELETE FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,)) await db.execute(
"DELETE FROM lnurldevice.lnurldevices WHERE id = ?", (lnurldevice_id,)
)
########################lnuldevice payments########################### ########################lnuldevice payments###########################
@ -102,19 +112,23 @@ async def update_lnurldevicepayment(
(*kwargs.values(), lnurldevicepayment_id), (*kwargs.values(), lnurldevicepayment_id),
) )
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", (lnurldevicepayment_id,) "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?",
(lnurldevicepayment_id,),
) )
return lnurldevicepayment(**row) if row else None return lnurldevicepayment(**row) if row else None
async def get_lnurldevicepayment(lnurldevicepayment_id: str) -> lnurldevicepayment: async def get_lnurldevicepayment(lnurldevicepayment_id: str) -> lnurldevicepayment:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?", (lnurldevicepayment_id,) "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?",
(lnurldevicepayment_id,),
) )
return lnurldevicepayment(**row) if row else None return lnurldevicepayment(**row) if row else None
async def get_lnurlpayload(lnurldevicepayment_payload: str) -> lnurldevicepayment: async def get_lnurlpayload(lnurldevicepayment_payload: str) -> lnurldevicepayment:
row = await db.fetchone( row = await db.fetchone(
"SELECT * FROM lnurldevice.lnurldevicepayment WHERE payload = ?", (lnurldevicepayment_payload,) "SELECT * FROM lnurldevice.lnurldevicepayment WHERE payload = ?",
(lnurldevicepayment_payload,),
) )
return lnurldevicepayment(**row) if row else None return lnurldevicepayment(**row) if row else None

View File

@ -105,10 +105,7 @@ async def lnurl_v1_params(
paymentcheck = await get_lnurlpayload(p) paymentcheck = await get_lnurlpayload(p)
if device.device == "atm": if device.device == "atm":
if paymentcheck: if paymentcheck:
return { return {"status": "ERROR", "reason": f"Payment already claimed"}
"status": "ERROR",
"reason": f"Payment already claimed",
}
if len(p) % 4 > 0: if len(p) % 4 > 0:
p += "=" * (4 - (len(p) % 4)) p += "=" * (4 - (len(p) % 4))
@ -174,27 +171,28 @@ async def lnurl_v1_params(
} }
@lnurldevice_ext.get( @lnurldevice_ext.get(
"/api/v1/lnurl/cb/{paymentid}", "/api/v1/lnurl/cb/{paymentid}",
status_code=HTTPStatus.OK, status_code=HTTPStatus.OK,
name="lnurldevice.lnurl_callback", name="lnurldevice.lnurl_callback",
) )
async def lnurl_callback(request: Request, paymentid: str = Query(None), pr: str = Query(None), k1: str = Query(None)): async def lnurl_callback(
request: Request,
paymentid: str = Query(None),
pr: str = Query(None),
k1: str = Query(None),
):
lnurldevicepayment = await get_lnurldevicepayment(paymentid) lnurldevicepayment = await get_lnurldevicepayment(paymentid)
device = await get_lnurldevice(lnurldevicepayment.deviceid) device = await get_lnurldevice(lnurldevicepayment.deviceid)
if not device: if not device:
raise HTTPException( raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found." status_code=HTTPStatus.FORBIDDEN, detail="lnurldevice not found."
) )
if pr: if pr:
if lnurldevicepayment.id != k1: if lnurldevicepayment.id != k1:
return {"status": "ERROR", "reason": "Bad K1"} return {"status": "ERROR", "reason": "Bad K1"}
if lnurldevicepayment.payhash != "payment_hash": if lnurldevicepayment.payhash != "payment_hash":
return { return {"status": "ERROR", "reason": f"Payment already claimed"}
"status": "ERROR",
"reason": f"Payment already claimed",
}
lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment = await update_lnurldevicepayment(
lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload lnurldevicepayment_id=paymentid, payhash=lnurldevicepayment.payload
) )

View File

@ -2,6 +2,7 @@ from lnbits.db import Database
db2 = Database("ext_lnurlpos") db2 = Database("ext_lnurlpos")
async def m001_initial(db): async def m001_initial(db):
""" """
Initial lnurldevice table. Initial lnurldevice table.
@ -59,7 +60,8 @@ async def m002_redux(db):
(row[0], row[1], row[2], row[3], row[4], "pos", 0), (row[0], row[1], row[2], row[3], row[4], "pos", 0),
) )
for row in [ for row in [
list(row) for row in await db2.fetchall("SELECT * FROM lnurlpos.lnurlpospayment") list(row)
for row in await db2.fetchall("SELECT * FROM lnurlpos.lnurlpospayment")
]: ]:
await db.execute( await db.execute(
""" """
@ -76,4 +78,4 @@ async def m002_redux(db):
(row[0], row[1], row[3], row[4], row[5], row[6]), (row[0], row[1], row[3], row[4], row[5], row[6]),
) )
except: except:
return return

View File

@ -33,12 +33,15 @@ class lnurldevices(BaseModel):
return cls(**dict(row)) return cls(**dict(row))
def lnurl(self, req: Request) -> Lnurl: def lnurl(self, req: Request) -> Lnurl:
url = req.url_for("lnurldevice.lnurl_response", device_id=self.id, _external=True) url = req.url_for(
"lnurldevice.lnurl_response", device_id=self.id, _external=True
)
return lnurl_encode(url) return lnurl_encode(url)
async def lnurlpay_metadata(self) -> LnurlPayMetadata: async def lnurlpay_metadata(self) -> LnurlPayMetadata:
return LnurlPayMetadata(json.dumps([["text/plain", self.title]])) return LnurlPayMetadata(json.dumps([["text/plain", self.title]]))
class lnurldevicepayment(BaseModel): class lnurldevicepayment(BaseModel):
id: str id: str
deviceid: str deviceid: str

View File

@ -41,10 +41,13 @@ async def displaypin(request: Request, paymentid: str = Query(None)):
) )
status = await api_payment(lnurldevicepayment.payhash) status = await api_payment(lnurldevicepayment.payhash)
if status["paid"]: if status["paid"]:
await update_payment_status(checking_id=lnurldevicepayment.payhash, pending=True) await update_payment_status(
checking_id=lnurldevicepayment.payhash, pending=True
)
return lnurldevice_renderer().TemplateResponse( return lnurldevice_renderer().TemplateResponse(
"lnurldevice/paid.html", {"request": request, "pin": lnurldevicepayment.pin} "lnurldevice/paid.html", {"request": request, "pin": lnurldevicepayment.pin}
) )
return lnurldevice_renderer().TemplateResponse( return lnurldevice_renderer().TemplateResponse(
"lnurldevice/error.html", {"request": request, "pin": "filler", "not_paid": True} "lnurldevice/error.html",
{"request": request, "pin": "filler", "not_paid": True},
) )

View File

@ -48,7 +48,9 @@ async def api_lnurldevice_create_or_update(
async def api_lnurldevices_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)): async def api_lnurldevices_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
try: try:
return [{**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)] return [
{**lnurldevice.dict()} for lnurldevice in await get_lnurldevices(wallet_ids)
]
except: except:
return "" return ""
@ -71,7 +73,8 @@ async def api_lnurldevice_retrieve(
@lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}") @lnurldevice_ext.delete("/api/v1/lnurlpos/{lnurldevice_id}")
async def api_lnurldevice_delete( async def api_lnurldevice_delete(
wallet: WalletTypeInfo = Depends(require_admin_key), lnurldevice_id: str = Query(None) wallet: WalletTypeInfo = Depends(require_admin_key),
lnurldevice_id: str = Query(None),
): ):
lnurldevice = await get_lnurldevice(lnurldevice_id) lnurldevice = await get_lnurldevice(lnurldevice_id)

View File

@ -13,10 +13,12 @@ lnurlpayout_ext: APIRouter = APIRouter(prefix="/lnurlpayout", tags=["lnurlpayout
def lnurlpayout_renderer(): def lnurlpayout_renderer():
return template_renderer(["lnbits/extensions/lnurlpayout/templates"]) return template_renderer(["lnbits/extensions/lnurlpayout/templates"])
from .tasks import wait_for_paid_invoices from .tasks import wait_for_paid_invoices
from .views import * # noqa from .views import * # noqa
from .views_api import * # noqa from .views_api import * # noqa
def lnurlpayout_start(): def lnurlpayout_start():
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices)) loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))

View File

@ -6,14 +6,23 @@ from . import db
from .models import lnurlpayout, CreateLnurlPayoutData from .models import lnurlpayout, CreateLnurlPayoutData
async def create_lnurlpayout(wallet_id: str, admin_key: str, data: CreateLnurlPayoutData) -> lnurlpayout: async def create_lnurlpayout(
wallet_id: str, admin_key: str, data: CreateLnurlPayoutData
) -> lnurlpayout:
lnurlpayout_id = urlsafe_short_hash() lnurlpayout_id = urlsafe_short_hash()
await db.execute( await db.execute(
""" """
INSERT INTO lnurlpayout.lnurlpayouts (id, title, wallet, admin_key, lnurlpay, threshold) INSERT INTO lnurlpayout.lnurlpayouts (id, title, wallet, admin_key, lnurlpay, threshold)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?)
""", """,
(lnurlpayout_id, data.title, wallet_id, admin_key, data.lnurlpay, data.threshold), (
lnurlpayout_id,
data.title,
wallet_id,
admin_key,
data.lnurlpay,
data.threshold,
),
) )
lnurlpayout = await get_lnurlpayout(lnurlpayout_id) lnurlpayout = await get_lnurlpayout(lnurlpayout_id)
@ -22,13 +31,19 @@ async def create_lnurlpayout(wallet_id: str, admin_key: str, data: CreateLnurlPa
async def get_lnurlpayout(lnurlpayout_id: str) -> Optional[lnurlpayout]: async def get_lnurlpayout(lnurlpayout_id: str) -> Optional[lnurlpayout]:
row = await db.fetchone("SELECT * FROM lnurlpayout.lnurlpayouts WHERE id = ?", (lnurlpayout_id,)) row = await db.fetchone(
"SELECT * FROM lnurlpayout.lnurlpayouts WHERE id = ?", (lnurlpayout_id,)
)
return lnurlpayout(**row) if row else None return lnurlpayout(**row) if row else None
async def get_lnurlpayout_from_wallet(wallet_id: str) -> Optional[lnurlpayout]: async def get_lnurlpayout_from_wallet(wallet_id: str) -> Optional[lnurlpayout]:
row = await db.fetchone("SELECT * FROM lnurlpayout.lnurlpayouts WHERE wallet = ?", (wallet_id,)) row = await db.fetchone(
"SELECT * FROM lnurlpayout.lnurlpayouts WHERE wallet = ?", (wallet_id,)
)
return lnurlpayout(**row) if row else None return lnurlpayout(**row) if row else None
async def get_lnurlpayouts(wallet_ids: Union[str, List[str]]) -> List[lnurlpayout]: async def get_lnurlpayouts(wallet_ids: Union[str, List[str]]) -> List[lnurlpayout]:
if isinstance(wallet_ids, str): if isinstance(wallet_ids, str):
wallet_ids = [wallet_ids] wallet_ids = [wallet_ids]
@ -42,4 +57,6 @@ async def get_lnurlpayouts(wallet_ids: Union[str, List[str]]) -> List[lnurlpayou
async def delete_lnurlpayout(lnurlpayout_id: str) -> None: async def delete_lnurlpayout(lnurlpayout_id: str) -> None:
await db.execute("DELETE FROM lnurlpayout.lnurlpayouts WHERE id = ?", (lnurlpayout_id,)) await db.execute(
"DELETE FROM lnurlpayout.lnurlpayouts WHERE id = ?", (lnurlpayout_id,)
)

View File

@ -2,11 +2,13 @@ from sqlite3 import Row
from pydantic import BaseModel from pydantic import BaseModel
class CreateLnurlPayoutData(BaseModel): class CreateLnurlPayoutData(BaseModel):
title: str title: str
lnurlpay: str lnurlpay: str
threshold: int threshold: int
class lnurlpayout(BaseModel): class lnurlpayout(BaseModel):
id: str id: str
title: str title: str

View File

@ -26,28 +26,33 @@ async def on_invoice_paid(payment: Payment) -> None:
# Check its got a payout associated with it # Check its got a payout associated with it
lnurlpayout_link = await get_lnurlpayout_from_wallet(payment.wallet_id) lnurlpayout_link = await get_lnurlpayout_from_wallet(payment.wallet_id)
if lnurlpayout_link: if lnurlpayout_link:
# Check the wallet balance is more than the threshold # Check the wallet balance is more than the threshold
wallet = await get_wallet(lnurlpayout_link.wallet) wallet = await get_wallet(lnurlpayout_link.wallet)
if wallet.balance < lnurlpayout_link.threshold + (lnurlpayout_link.threshold*0.02): if wallet.balance < lnurlpayout_link.threshold + (
lnurlpayout_link.threshold * 0.02
):
return return
# Get the invoice from the LNURL to pay # Get the invoice from the LNURL to pay
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
try: try:
url = await api_payments_decode({"data": lnurlpayout_link.lnurlpay}) url = await api_payments_decode({"data": lnurlpayout_link.lnurlpay})
if str(url["domain"])[0:4] != "http": if str(url["domain"])[0:4] != "http":
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="LNURL broken") raise HTTPException(
try: status_code=HTTPStatus.FORBIDDEN, detail="LNURL broken"
r = await client.get(
str(url["domain"]),
timeout=40,
) )
try:
r = await client.get(str(url["domain"]), timeout=40)
res = r.json() res = r.json()
try: try:
r = await client.get( r = await client.get(
res["callback"] + "?amount=" + str(int((wallet.balance - wallet.balance*0.02) * 1000)), res["callback"]
+ "?amount="
+ str(
int((wallet.balance - wallet.balance * 0.02) * 1000)
),
timeout=40, timeout=40,
) )
res = r.json() res = r.json()
@ -65,6 +70,9 @@ async def on_invoice_paid(payment: Payment) -> None:
except (httpx.ConnectError, httpx.RequestError): except (httpx.ConnectError, httpx.RequestError):
return return
except Exception: except Exception:
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Failed to save LNURLPayout") raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail="Failed to save LNURLPayout",
)
except: except:
return return

View File

@ -10,10 +10,17 @@ from lnbits.core.views.api import api_payment, api_payments_decode
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
from . import lnurlpayout_ext from . import lnurlpayout_ext
from .crud import create_lnurlpayout, delete_lnurlpayout, get_lnurlpayout, get_lnurlpayouts, get_lnurlpayout_from_wallet from .crud import (
create_lnurlpayout,
delete_lnurlpayout,
get_lnurlpayout,
get_lnurlpayouts,
get_lnurlpayout_from_wallet,
)
from .models import lnurlpayout, CreateLnurlPayoutData from .models import lnurlpayout, CreateLnurlPayoutData
from .tasks import on_invoice_paid from .tasks import on_invoice_paid
@lnurlpayout_ext.get("/api/v1/lnurlpayouts", status_code=HTTPStatus.OK) @lnurlpayout_ext.get("/api/v1/lnurlpayouts", status_code=HTTPStatus.OK)
async def api_lnurlpayouts( async def api_lnurlpayouts(
all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type) all_wallets: bool = Query(None), wallet: WalletTypeInfo = Depends(get_key_type)
@ -28,23 +35,33 @@ async def api_lnurlpayouts(
@lnurlpayout_ext.post("/api/v1/lnurlpayouts", status_code=HTTPStatus.CREATED) @lnurlpayout_ext.post("/api/v1/lnurlpayouts", status_code=HTTPStatus.CREATED)
async def api_lnurlpayout_create( async def api_lnurlpayout_create(
data: CreateLnurlPayoutData, wallet: WalletTypeInfo = Depends(get_key_type) data: CreateLnurlPayoutData, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
if await get_lnurlpayout_from_wallet(wallet.wallet.id): if await get_lnurlpayout_from_wallet(wallet.wallet.id):
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Wallet already has lnurlpayout set") raise HTTPException(
status_code=HTTPStatus.FORBIDDEN,
detail="Wallet already has lnurlpayout set",
)
return return
url = await api_payments_decode({"data": data.lnurlpay}) url = await api_payments_decode({"data": data.lnurlpay})
if "domain" not in url: if "domain" not in url:
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="LNURL could not be decoded") raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="LNURL could not be decoded"
)
return return
if str(url["domain"])[0:4] != "http": if str(url["domain"])[0:4] != "http":
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not valid LNURL") raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not valid LNURL")
return return
lnurlpayout = await create_lnurlpayout(wallet_id=wallet.wallet.id, admin_key=wallet.wallet.adminkey, data=data) lnurlpayout = await create_lnurlpayout(
wallet_id=wallet.wallet.id, admin_key=wallet.wallet.adminkey, data=data
)
if not lnurlpayout: if not lnurlpayout:
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Failed to save LNURLPayout") raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Failed to save LNURLPayout"
)
return return
return lnurlpayout.dict() return lnurlpayout.dict()
@lnurlpayout_ext.delete("/api/v1/lnurlpayouts/{lnurlpayout_id}") @lnurlpayout_ext.delete("/api/v1/lnurlpayouts/{lnurlpayout_id}")
async def api_lnurlpayout_delete( async def api_lnurlpayout_delete(
lnurlpayout_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) lnurlpayout_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
@ -57,26 +74,34 @@ async def api_lnurlpayout_delete(
) )
if lnurlpayout.wallet != wallet.wallet.id: if lnurlpayout.wallet != wallet.wallet.id:
raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your lnurlpayout.") raise HTTPException(
status_code=HTTPStatus.FORBIDDEN, detail="Not your lnurlpayout."
)
await delete_lnurlpayout(lnurlpayout_id) await delete_lnurlpayout(lnurlpayout_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT) raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
@lnurlpayout_ext.get("/api/v1/lnurlpayouts/{lnurlpayout_id}", status_code=HTTPStatus.OK) @lnurlpayout_ext.get("/api/v1/lnurlpayouts/{lnurlpayout_id}", status_code=HTTPStatus.OK)
async def api_lnurlpayout_check( async def api_lnurlpayout_check(
lnurlpayout_id: str, wallet: WalletTypeInfo = Depends(get_key_type) lnurlpayout_id: str, wallet: WalletTypeInfo = Depends(get_key_type)
): ):
lnurlpayout = await get_lnurlpayout(lnurlpayout_id) lnurlpayout = await get_lnurlpayout(lnurlpayout_id)
payments = await get_payments( payments = await get_payments(
wallet_id=lnurlpayout.wallet, complete=True, pending=False, outgoing=True, incoming=True wallet_id=lnurlpayout.wallet,
complete=True,
pending=False,
outgoing=True,
incoming=True,
) )
result = await on_invoice_paid(payments[0]) result = await on_invoice_paid(payments[0])
return return
# get payouts func
# lnurlpayouts = await get_lnurlpayouts(wallet_ids) # get payouts func
# for lnurlpayout in lnurlpayouts: # lnurlpayouts = await get_lnurlpayouts(wallet_ids)
# payments = await get_payments( # for lnurlpayout in lnurlpayouts:
# wallet_id=lnurlpayout.wallet, complete=True, pending=False, outgoing=True, incoming=True # payments = await get_payments(
# ) # wallet_id=lnurlpayout.wallet, complete=True, pending=False, outgoing=True, incoming=True
# await on_invoice_paid(payments[0]) # )
# await on_invoice_paid(payments[0])

View File

@ -15,6 +15,7 @@ satspay_ext: APIRouter = APIRouter(prefix="/satspay", tags=["satspay"])
def satspay_renderer(): def satspay_renderer():
return template_renderer(["lnbits/extensions/satspay/templates"]) return template_renderer(["lnbits/extensions/satspay/templates"])
from .tasks import wait_for_paid_invoices from .tasks import wait_for_paid_invoices
from .views import * # noqa from .views import * # noqa
from .views_api import * # noqa from .views_api import * # noqa

View File

@ -115,7 +115,7 @@ async def check_address_balance(charge_id: str) -> List[Charges]:
pass pass
if charge.lnbitswallet: if charge.lnbitswallet:
invoice_status = await api_payment(charge.payment_hash) invoice_status = await api_payment(charge.payment_hash)
if invoice_status["paid"]: if invoice_status["paid"]:
return await update_charge(charge_id=charge_id, balance=charge.amount) return await update_charge(charge_id=charge_id, balance=charge.amount)
row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,)) row = await db.fetchone("SELECT * FROM satspay.charges WHERE id = ?", (charge_id,))

View File

@ -28,4 +28,3 @@ async def on_invoice_paid(payment: Payment) -> None:
await payment.set_pending(False) await payment.set_pending(False)
await check_address_balance(charge_id=charge.id) await check_address_balance(charge_id=charge.id)

View File

@ -32,5 +32,6 @@ async def display(request: Request, charge_id):
) )
wallet = await get_wallet(charge.lnbitswallet) wallet = await get_wallet(charge.lnbitswallet)
return satspay_renderer().TemplateResponse( return satspay_renderer().TemplateResponse(
"satspay/display.html", {"request": request, "charge": charge, "wallet_key": wallet.inkey} "satspay/display.html",
{"request": request, "charge": charge, "wallet_key": wallet.inkey},
) )

View File

@ -25,14 +25,15 @@ from .models import CreateCharge
#############################CHARGES########################## #############################CHARGES##########################
@satspay_ext.post("/api/v1/charge") @satspay_ext.post("/api/v1/charge")
async def api_charge_create( async def api_charge_create(
data: CreateCharge, data: CreateCharge, wallet: WalletTypeInfo = Depends(require_invoice_key)
wallet: WalletTypeInfo = Depends(require_invoice_key)
): ):
charge = await create_charge(user=wallet.wallet.user, data=data) charge = await create_charge(user=wallet.wallet.user, data=data)
return charge.dict() return charge.dict()
@satspay_ext.put("/api/v1/charge/{charge_id}") @satspay_ext.put("/api/v1/charge/{charge_id}")
async def api_charge_update( async def api_charge_update(
data: CreateCharge, data: CreateCharge,

View File

@ -285,5 +285,6 @@ async def get_fiat_rate_satoshis(currency: str) -> float:
async def fiat_amount_as_satoshis(amount: float, currency: str) -> int: async def fiat_amount_as_satoshis(amount: float, currency: str) -> int:
return int(amount * (await get_fiat_rate_satoshis(currency))) return int(amount * (await get_fiat_rate_satoshis(currency)))
async def satoshis_amount_as_fiat(amount: float, currency: str) -> float: async def satoshis_amount_as_fiat(amount: float, currency: str) -> float:
return float(amount / (await get_fiat_rate_satoshis(currency))) return float(amount / (await get_fiat_rate_satoshis(currency)))

View File

@ -2,6 +2,7 @@ import asyncio
import json import json
import httpx import httpx
from os import getenv from os import getenv
from datetime import datetime, timedelta
from typing import Optional, Dict, AsyncGenerator from typing import Optional, Dict, AsyncGenerator
import hashlib import hashlib
from ..bolt11 import encode from ..bolt11 import encode
@ -13,60 +14,65 @@ from .base import (
Wallet, Wallet,
) )
class FakeWallet(Wallet): class FakeWallet(Wallet):
"""https://github.com/lnbits/lnbits""" """https://github.com/lnbits/lnbits"""
async def status(self) -> StatusResponse:
print("This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits.") print(
return StatusResponse( "The FakeWallet backend is for using LNbits as a centralised, stand-alone payment system."
None,
21000000000,
) )
return StatusResponse(None, 21000000000)
async def create_invoice( async def create_invoice(
self, self,
amount: int, amount: int,
memo: Optional[str] = None, memo: Optional[str] = None,
description_hash: Optional[bytes] = None, description_hash: Optional[bytes] = None,
) -> InvoiceResponse: ) -> InvoiceResponse:
class options:
options.amount = amount def __init__(self, amount, timestamp, payments_hash, privkey, memo, ):
options.timestamp = datetime.now().timestamp() self.name = name
self.age = age
async def status(self) -> StatusResponse:
randomHash = hashlib.sha256(b"some random data").hexdigest() randomHash = hashlib.sha256(b"some random data").hexdigest()
options.payments_hash = hex(randomHash) options = {
options.privkey = "v3qrevqrevm39qin0vq3r0ivmrewvmq3rimq03ig" "amount": amount,
"timestamp": datetime.now().timestamp(),
"payments_hash": randomHash,
"privkey": "v3qrevqrevm39qin0vq3r0ivmrewvmq3rimq03ig",
"memo": "",
"description_hashed": "",
}
if description_hash: if description_hash:
options.description_hashed = description_hash options.description_hashed = description_hash
else: else:
options.memo = memo options.memo = memo
payment_request = encode(options) payment_request = encode(options)
print(payment_request)
checking_id = randomHash checking_id = randomHash
return InvoiceResponse(ok, checking_id, payment_request) return InvoiceResponse(ok, checking_id, payment_request)
async def pay_invoice(self, bolt11: str) -> PaymentResponse: async def pay_invoice(self, bolt11: str) -> PaymentResponse:
return "" return ""
async def get_invoice_status(self, checking_id: str) -> PaymentStatus: async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
return "" return ""
async def get_payment_status(self, checking_id: str) -> PaymentStatus: async def get_payment_status(self, checking_id: str) -> PaymentStatus:
return "" return ""
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
url = f"{self.endpoint}/api/v1/payments/sse" yield ""
print("lost connection to lnbits /payments/sse, retrying in 5 seconds")
await asyncio.sleep(5)
#invoice = "lnbc" # invoice = "lnbc"
#invoice += str(data.amount) + "m1" # invoice += str(data.amount) + "m1"
#invoice += str(datetime.now().timestamp()).to_bytes(35, byteorder='big')) # invoice += str(datetime.now().timestamp()).to_bytes(35, byteorder='big'))
#invoice += str(hashlib.sha256(b"some random data").hexdigest()) # hash of preimage, can be fake as invoice handled internally # invoice += str(hashlib.sha256(b"some random data").hexdigest()) # hash of preimage, can be fake as invoice handled internally
#invoice += "dpl" # d then pl (p = 1, l = 31; 1 * 32 + 31 == 63) # invoice += "dpl" # d then pl (p = 1, l = 31; 1 * 32 + 31 == 63)
#invoice += "2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq" #description, how do I encode this? # invoice += "2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq" #description, how do I encode this?
#invoice += str(hashlib.sha224("lnbc" + str(data.amount) + "m1").hexdigest()) # invoice += str(hashlib.sha224("lnbc" + str(data.amount) + "m1").hexdigest())

View File

@ -23,8 +23,7 @@ class LndRestWallet(Wallet):
endpoint = getenv("LND_REST_ENDPOINT") endpoint = getenv("LND_REST_ENDPOINT")
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
endpoint = ( endpoint = (
"https://" + "https://" + endpoint if not endpoint.startswith("http") else endpoint
endpoint if not endpoint.startswith("http") else endpoint
) )
self.endpoint = endpoint self.endpoint = endpoint
@ -103,10 +102,7 @@ class LndRestWallet(Wallet):
r = await client.post( r = await client.post(
url=f"{self.endpoint}/v1/channels/transactions", url=f"{self.endpoint}/v1/channels/transactions",
headers=self.auth, headers=self.auth,
json={ json={"payment_request": bolt11, "fee_limit": lnrpcFeeLimit},
"payment_request": bolt11,
"fee_limit": lnrpcFeeLimit,
},
timeout=180, timeout=180,
) )
@ -183,8 +179,7 @@ class LndRestWallet(Wallet):
except: except:
continue continue
payment_hash = base64.b64decode( payment_hash = base64.b64decode(inv["r_hash"]).hex()
inv["r_hash"]).hex()
yield payment_hash yield payment_hash
except (OSError, httpx.ConnectError, httpx.ReadError): except (OSError, httpx.ConnectError, httpx.ReadError):
pass pass

View File

@ -20,11 +20,10 @@ class VoidWallet(Wallet):
raise Unsupported("") raise Unsupported("")
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
print("This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits.") print(
return StatusResponse( "This backend does nothing, it is here just as a placeholder, you must configure an actual backend before being able to do anything useful with LNbits."
None,
0,
) )
return StatusResponse(None, 0)
async def pay_invoice(self, bolt11: str) -> PaymentResponse: async def pay_invoice(self, bolt11: str) -> PaymentResponse:
raise Unsupported("") raise Unsupported("")

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{ {
"name": "lnbits", "name": "lnbits-legend",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {