From b76414dc00359196c638fed68362f084c67b54e7 Mon Sep 17 00:00:00 2001 From: GitHappens2Me Date: Wed, 28 May 2025 20:30:24 +0200 Subject: [PATCH] clean up and .env variables --- .env.example | 8 ++++++++ router/admin.py | 23 ++++++----------------- router/cashu.py | 35 +++++++++++++++++++++++------------ router/proxy.py | 1 - 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/.env.example b/.env.example index 9e402b08..3dcf8ec9 100644 --- a/.env.example +++ b/.env.example @@ -9,6 +9,9 @@ UPSTREAM_API_KEY="sk-21212121212121212121212121212121" # Lightning address used to receive funds RECEIVE_LN_ADDRESS="shroominic@walletofsatoshi.com" +# When your cashu balance reaches this number of sats, send the funds to RECEIVE_LN_ADDRESS. +MINIMUM_PAYOUT = "100" + # Costs in Sats, if MODEL_BASED_PRICING is set to false COST_PER_REQUEST="10" COST_PER_1K_INPUT_TOKENS = "0" @@ -17,6 +20,11 @@ COST_PER_1K_OUTPUT_TOKENS = "0" # If set to true, make sure model pricings are defined in models.json MODEL_BASED_PRICING = "false" +# Time in seconds between each automatically refunding funds to users whose API keys have expired +# Setting this to "0" disables automatic refunds +REFUND_PROCESSING_INTERVAL = "3600" + + # password used to log into admin interface ADMIN_PASSWORD="XXX" diff --git a/router/admin.py b/router/admin.py index ca9a20db..5cfb7a5d 100644 --- a/router/admin.py +++ b/router/admin.py @@ -1,7 +1,6 @@ import os from datetime import datetime, timezone - from fastapi import APIRouter, Request from fastapi.responses import HTMLResponse @@ -9,7 +8,6 @@ from .cashu import _initialize_wallet admin_router = APIRouter(prefix="/admin") - def login_form() -> str: return """ @@ -97,32 +95,23 @@ async def dashboard(request: Request) -> str: api_keys_table_rows = [] for key in api_keys: - if key.key_expiry_time is not None: - expiry_time_utc = datetime.fromtimestamp(key.key_expiry_time, tz=timezone.utc) - expiry_time_human_readable = expiry_time_utc.strftime('%Y-%m-%d %H:%M:%S') - api_keys_table_rows.append( - f"{key.hashed_key}{key.balance}{key.total_spent}{key.total_requests}{key.refund_address}{key.key_expiry_time} ({expiry_time_human_readable} UTC)" - ) - else: - expiry_time_human_readable = "" - api_keys_table_rows.append( - f"{key.hashed_key}{key.balance}{key.total_spent}{key.total_requests}{key.refund_address}{key.key_expiry_time}" - ) + expiry_time_utc = datetime.fromtimestamp(key.key_expiry_time, tz=timezone.utc) if key.key_expiry_time is not None else None + expiry_time_human_readable = expiry_time_utc.strftime('%Y-%m-%d %H:%M:%S') if expiry_time_utc else "" + + api_keys_table_rows.append( + f"{key.hashed_key}{key.balance}{key.total_spent}{key.total_requests}{key.refund_address}{'{} ({} UTC)'.format(key.key_expiry_time, expiry_time_human_readable) if key.key_expiry_time else key.key_expiry_time}" + ) api_keys_table_rows = "".join(api_keys_table_rows) - # Calculate the total balance of all API keys total_user_balance = int(sum(key.balance / 1000 for key in api_keys)) - # Fetch balance from cashu wallet = await _initialize_wallet() wallet_balance = wallet.balance - # calculate owner balance owner_balance = wallet_balance - total_user_balance - return f""" diff --git a/router/cashu.py b/router/cashu.py index 6d93c47b..41f8e580 100644 --- a/router/cashu.py +++ b/router/cashu.py @@ -12,6 +12,7 @@ from .db import ApiKey, AsyncSession, get_session RECEIVE_LN_ADDRESS = os.environ["RECEIVE_LN_ADDRESS"] MINT = os.environ.get("MINT", "https://mint.minibits.cash/Bitcoin") MINIMUM_PAYOUT = int(os.environ.get("MINIMUM_PAYOUT", 100)) +REFUND_PROCESSING_INTERVAL = int(os.environ.get("REFUND_PROCESSING_INTERVAL", 3600)) DEV_LN_ADDRESS = "routstr@minibits.cash" DEVS_DONATION_RATE = 0.021 # 2.1% WALLET = None @@ -81,7 +82,9 @@ async def _pay_invoice_with_cashu( proofs_to_melt, _ = await wallet.select_to_send( wallet.proofs, quote.amount + quote.fee_reserve ) - print(f"Proofs to melt: {proofs_to_melt}") + + # Debugging Cashu Proofs + #print(f"Proofs to melt: {proofs_to_melt}") _ = await wallet.melt( proofs_to_melt, bolt11_invoice, quote.fee_reserve, quote.quote @@ -164,25 +167,35 @@ async def credit_balance(cashu_token: str, key: ApiKey, session: AsyncSession) - else: # crediting a token created using a different mint as specified in .env print("Received a token from a different mint", flush=True) - #TODO + #TODO This fails, and needs to be fixed -# TODO i think the wallet.balance is not updated correctly after refunds -# TODO what happens if minimum payout gets reached by a request that needs to be refunded async def check_for_refunds() -> None: + """ + Periodically checks for API keys that are eligible for refunds and processes them. + + Raises: + Exception: If an error occurs during the refund check process. + """ + + # Setting REFUND_PROCESSING_INTERVAL to 0 disables it + if REFUND_PROCESSING_INTERVAL == 0: + print("Automatic refund processing is disabled.") + return + while True: try: async for session in get_session(): result = await session.exec(select(ApiKey)) keys = result.all() - + current_time = int(time.time()) for key in keys: - if(key.balance > 0 and key.refund_address and key.key_expiry_time and key.key_expiry_time < time.time()): - print(f" DEBUG Refunding key {key.hashed_key[:3] + '[...]' + key.hashed_key[-3:]}, Current Time: {int(time.time())}, Expirary Time: {key.key_expiry_time}", flush = True) + if(key.balance > 0 and key.refund_address and key.key_expiry_time and key.key_expiry_time < current_time): + print(f" DEBUG Refunding key {key.hashed_key[:3] + '[...]' + key.hashed_key[-3:]}, Current Time: {current_time}, Expirary Time: {key.key_expiry_time}", flush = True) await refund_balance(key.balance, key, session) - #TODO Error balance to low - await asyncio.sleep(30) - #TODO Define time to sleep # hour: 3600 + + # Sleep for the specified interval before checking again + await asyncio.sleep(REFUND_PROCESSING_INTERVAL) except Exception as e: print(f"Error during refund check: {e}") @@ -290,7 +303,6 @@ async def send_to_lnurl(wallet: Wallet, lnurl: str, amount_msat: int) -> int: print(f" DEBUG Trying to pay {amount_to_send} msats to {lnurl}, with Wallet balance = {wallet.balance}", flush = True) - print(f"trying to pay {amount_to_send} msats to {lnurl}. Available balance: {wallet.balance}", flush=True) # Note: We pass amount_msat directly. The actual amount paid might be adjusted # slightly by the melt quote based on the invoice details. bolt11_invoice, _ = await _get_lnurl_invoice(callback_url, amount_to_send) @@ -300,7 +312,6 @@ async def send_to_lnurl(wallet: Wallet, lnurl: str, amount_msat: int) -> int: print(f" DEBUG {amount_paid} sats paid to lnurl", flush=True) - print(f"Amount paid: {amount_paid / 1000} sat") return amount_paid diff --git a/router/proxy.py b/router/proxy.py index 9b06125e..99f4fe7f 100644 --- a/router/proxy.py +++ b/router/proxy.py @@ -193,7 +193,6 @@ async def proxy( background_tasks = BackgroundTasks() background_tasks.add_task(response.aclose) background_tasks.add_task(client.aclose) - return StreamingResponse( stream_with_cost(), status_code=response.status_code,