async def lnurl_callback(track_id): track = await get_track(track_id) if not track: return jsonify({"status": "ERROR", "reason": "Couldn't find track."}) amount_received = int(request.args.get("amount")) if amount_received < track.min_sendable: return (jsonify( LnurlErrorResponse( reason= f"Amount {round(amount_received / 1000)} is smaller than minimum {math.floor(track.min_sendable)}." ).dict()), ) elif track.max_sendable < amount_received: return (jsonify( LnurlErrorResponse( reason= f"Amount {round(amount_received / 1000)} is greater than maximum {math.floor(track.max_sendable)}." ).dict()), ) comment = request.args.get("comment") if len(comment or "") > 300: return jsonify( LnurlErrorResponse( reason= f"Got a comment with {len(comment)} characters, but can only accept 300" ).dict()) ls = await get_livestream_by_track(track_id) payment_hash, payment_request = await create_invoice( wallet_id=ls.wallet, amount=int(amount_received / 1000), memo=await track.fullname(), description_hash=hashlib.sha256( (await track.lnurlpay_metadata()).encode("utf-8")).digest(), extra={ "tag": "livestream", "track": track.id, "comment": comment }, ) if amount_received < track.price_msat: success_action = None else: success_action = track.success_action(payment_hash) resp = LnurlPayActionResponse( pr=payment_request, success_action=success_action, routes=[], ) return jsonify(resp.dict())
async def api_lnurl_callback(link_id): link = increment_pay_link(link_id, served_pr=1) if not link: return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK min, max = link.min, link.max rate = await get_fiat_rate(link.currency) if link.currency else 1 if link.currency: # allow some fluctuation (as the fiat price may have changed between the calls) min = rate * 995 * link.min max = rate * 1010 * link.max else: min = link.min * 1000 max = link.max * 1000 amount_received = int(request.args.get("amount")) if amount_received < min: return ( jsonify(LnurlErrorResponse(reason=f"Amount {amount_received} is smaller than minimum {min}.").dict()), HTTPStatus.OK, ) elif amount_received > max: return ( jsonify(LnurlErrorResponse(reason=f"Amount {amount_received} is greater than maximum {max}.").dict()), HTTPStatus.OK, ) comment = request.args.get("comment") if len(comment or "") > link.comment_chars: return ( jsonify( LnurlErrorResponse( reason=f"Got a comment with {len(comment)} characters, but can only accept {link.comment_chars}" ).dict() ), HTTPStatus.OK, ) payment_hash, payment_request = create_invoice( wallet_id=link.wallet, amount=int(amount_received / 1000), memo=link.description, description_hash=hashlib.sha256(link.lnurlpay_metadata.encode("utf-8")).digest(), extra={"tag": "lnurlp", "link": link.id, "comment": comment}, ) resp = LnurlPayActionResponse( pr=payment_request, success_action=link.success_action(payment_hash), routes=[], ) return jsonify(resp.dict()), HTTPStatus.OK
async def lnurl_callback(cp_id): cp = await get_copilot(cp_id) if not cp: return jsonify({"status": "ERROR", "reason": "Copilot not found."}) amount_received = int(request.args.get("amount")) if amount_received < 10000: return (jsonify( LnurlErrorResponse( reason= f"Amount {round(amount_received / 1000)} is smaller than minimum 10 sats." ).dict()), ) elif amount_received / 1000 > 10000000: return (jsonify( LnurlErrorResponse( reason= f"Amount {round(amount_received / 1000)} is greater than maximum 50000." ).dict()), ) comment = "" if request.args.get("comment"): comment = request.args.get("comment") if len(comment or "") > 300: return jsonify( LnurlErrorResponse( reason= f"Got a comment with {len(comment)} characters, but can only accept 300" ).dict()) if len(comment) < 1: comment = "none" payment_hash, payment_request = await create_invoice( wallet_id=cp.wallet, amount=int(amount_received / 1000), memo=cp.lnurl_title, description_hash=hashlib.sha256( (LnurlPayMetadata(json.dumps([["text/plain", str(cp.lnurl_title)] ]))).encode("utf-8")).digest(), extra={ "tag": "copilot", "copilot": cp.id, "comment": comment }, ) resp = LnurlPayActionResponse( pr=payment_request, success_action=None, disposable=False, routes=[], ) return jsonify(resp.dict())
async def lnurl_callback(item_id): item = await get_item(item_id) if not item: return jsonify({"status": "ERROR", "reason": "Couldn't find item."}) if item.unit == "sat": min = item.price * 1000 max = item.price * 1000 else: price = await fiat_amount_as_satoshis(item.price, item.unit) # allow some fluctuation (the fiat price may have changed between the calls) min = price * 995 max = price * 1010 amount_received = int(request.args.get("amount") or 0) if amount_received < min: return jsonify( LnurlErrorResponse( reason=f"Amount {amount_received} is smaller than minimum {min}." ).dict()) elif amount_received > max: return jsonify( LnurlErrorResponse( reason=f"Amount {amount_received} is greater than maximum {max}." ).dict()) shop = await get_shop(item.shop) try: payment_hash, payment_request = await create_invoice( wallet_id=shop.wallet, amount=int(amount_received / 1000), memo=item.name, description_hash=hashlib.sha256( (await item.lnurlpay_metadata()).encode("utf-8")).digest(), extra={ "tag": "offlineshop", "item": item.id }, ) except Exception as exc: return jsonify(LnurlErrorResponse(reason=exc.message).dict()) resp = LnurlPayActionResponse( pr=payment_request, success_action=item.success_action(shop, payment_hash) if shop.method else None, routes=[], ) return jsonify(resp.dict())
async def perform_lnurlauth( callback: str, conn: Optional[Connection] = None, ) -> Optional[LnurlErrorResponse]: cb = urlparse(callback) k1 = unhexlify(parse_qs(cb.query)["k1"][0]) key = g.wallet.lnurlauth_key(cb.netloc) def int_to_bytes_suitable_der(x: int) -> bytes: """for strict DER we need to encode the integer with some quirks""" b = x.to_bytes((x.bit_length() + 7) // 8, "big") if len(b) == 0: # ensure there's at least one byte when the int is zero return bytes([0]) if b[0] & 0x80 != 0: # ensure it doesn't start with a 0x80 and so it isn't # interpreted as a negative number return bytes([0]) + b return b def encode_strict_der(r_int, s_int, order): # if s > order/2 verification will fail sometimes # so we must fix it here (see https://github.com/indutny/elliptic/blob/e71b2d9359c5fe9437fbf46f1f05096de447de57/lib/elliptic/ec/index.js#L146-L147) if s_int > order // 2: s_int = order - s_int # now we do the strict DER encoding copied from # https://github.com/KiriKiri/bip66 (without any checks) r = int_to_bytes_suitable_der(r_int) s = int_to_bytes_suitable_der(s_int) r_len = len(r) s_len = len(s) sign_len = 6 + r_len + s_len signature = BytesIO() signature.write(0x30.to_bytes(1, "big", signed=False)) signature.write((sign_len - 2).to_bytes(1, "big", signed=False)) signature.write(0x02.to_bytes(1, "big", signed=False)) signature.write(r_len.to_bytes(1, "big", signed=False)) signature.write(r) signature.write(0x02.to_bytes(1, "big", signed=False)) signature.write(s_len.to_bytes(1, "big", signed=False)) signature.write(s) return signature.getvalue() sig = key.sign_digest_deterministic(k1, sigencode=encode_strict_der) async with httpx.AsyncClient() as client: r = await client.get( callback, params={ "k1": k1.hex(), "key": key.verifying_key.to_string("compressed").hex(), "sig": sig.hex(), }, ) try: resp = json.loads(r.text) if resp["status"] == "OK": return None return LnurlErrorResponse(reason=resp["reason"]) except (KeyError, json.decoder.JSONDecodeError): return LnurlErrorResponse(reason=r.text[:200] + "..." if len(r.text) > 200 else r.text, )
async def api_lnurlp_callback(link_id): link = await get_satsdice_pay(link_id) if not link: return ( jsonify({ "status": "ERROR", "reason": "LNUeL-pay not found." }), HTTPStatus.OK, ) min, max = link.min_bet, link.max_bet min = link.min_bet * 1000 max = link.max_bet * 1000 amount_received = int(request.args.get("amount") or 0) if amount_received < min: return ( jsonify( LnurlErrorResponse( reason= f"Amount {amount_received} is smaller than minimum {min}." ).dict()), HTTPStatus.OK, ) elif amount_received > max: return ( jsonify( LnurlErrorResponse( reason= f"Amount {amount_received} is greater than maximum {max}." ).dict()), HTTPStatus.OK, ) payment_hash, payment_request = await create_invoice( wallet_id=link.wallet, amount=int(amount_received / 1000), memo="Satsdice bet", description_hash=hashlib.sha256( link.lnurlpay_metadata.encode("utf-8")).digest(), extra={ "tag": "satsdice", "link": link.id, "comment": "comment" }, ) success_action = link.success_action(payment_hash) link = await create_satsdice_payment(satsdice_pay=link.id, value=amount_received / 1000, payment_hash=payment_hash) if success_action: resp = LnurlPayActionResponse( pr=payment_request, success_action=success_action, routes=[], ) else: resp = LnurlPayActionResponse( pr=payment_request, routes=[], ) return jsonify(resp.dict()), HTTPStatus.OK