mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-11 11:47:50 +00:00
fix nip60 wallet handling
This commit is contained in:
+3
-5
@@ -3,11 +3,10 @@ from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
from sixty_nuts import Wallet
|
||||
from sqlmodel import select
|
||||
|
||||
from .db import ApiKey, create_session
|
||||
from .cashu import NSEC, MINT
|
||||
from .cashu import WALLET
|
||||
|
||||
admin_router = APIRouter(prefix="/admin")
|
||||
|
||||
@@ -112,9 +111,8 @@ async def dashboard(request: Request) -> str:
|
||||
# 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
|
||||
async with Wallet(nsec=NSEC, mint_urls=[MINT]) as wallet:
|
||||
current_balance = (await wallet.fetch_wallet_state()).balance
|
||||
owner_balance = current_balance - total_user_balance
|
||||
current_balance = (await WALLET.fetch_wallet_state()).balance
|
||||
owner_balance = current_balance - total_user_balance
|
||||
|
||||
return f"""<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
+46
-38
@@ -15,6 +15,15 @@ DEV_LN_ADDRESS = "routstr@minibits.cash"
|
||||
DEVS_DONATION_RATE = float(os.environ.get("DEVS_DONATION_RATE", 0.021)) # 2.1%
|
||||
NSEC = os.environ["NSEC"] # Nostr private key for the wallet
|
||||
|
||||
WALLET = Wallet(nsec=NSEC, mint_urls=[MINT])
|
||||
|
||||
async def init_wallet():
|
||||
global WALLET
|
||||
WALLET = await Wallet.create(nsec=NSEC, mint_urls=[MINT])
|
||||
|
||||
async def close_wallet():
|
||||
global WALLET
|
||||
await WALLET.aclose()
|
||||
|
||||
async def pay_out() -> None:
|
||||
"""
|
||||
@@ -34,9 +43,8 @@ async def pay_out() -> None:
|
||||
return
|
||||
|
||||
user_balance_sats = balance // 1000
|
||||
async with Wallet(nsec=NSEC, mint_urls=[MINT]) as wallet:
|
||||
state = await wallet.fetch_wallet_state()
|
||||
wallet_balance_sats = state.balance
|
||||
state = await WALLET.fetch_wallet_state()
|
||||
wallet_balance_sats = state.balance
|
||||
|
||||
# Handle edge cases more gracefully
|
||||
if wallet_balance_sats < user_balance_sats:
|
||||
@@ -53,9 +61,8 @@ async def pay_out() -> None:
|
||||
owners_draw = revenue - devs_donation
|
||||
|
||||
# Send payouts
|
||||
async with Wallet(nsec=NSEC, mint_urls=[MINT]) as wallet:
|
||||
await wallet.send_to_lnurl(RECEIVE_LN_ADDRESS, owners_draw)
|
||||
await wallet.send_to_lnurl(DEV_LN_ADDRESS, devs_donation)
|
||||
await WALLET.send_to_lnurl(RECEIVE_LN_ADDRESS, owners_draw)
|
||||
await WALLET.send_to_lnurl(DEV_LN_ADDRESS, devs_donation)
|
||||
|
||||
except Exception as e:
|
||||
# Log the error but don't crash - payouts can be retried later
|
||||
@@ -63,15 +70,14 @@ async def pay_out() -> None:
|
||||
|
||||
|
||||
async def credit_balance(cashu_token: str, key: ApiKey, session: AsyncSession) -> int:
|
||||
async with Wallet(nsec=NSEC, mint_urls=[MINT]) as wallet:
|
||||
state_before = await wallet.fetch_wallet_state()
|
||||
await wallet.redeem(cashu_token)
|
||||
state_after = await wallet.fetch_wallet_state()
|
||||
amount = (state_after.balance - state_before.balance) * 1000
|
||||
key.balance += amount
|
||||
session.add(key)
|
||||
await session.commit()
|
||||
return amount
|
||||
state_before = await WALLET.fetch_wallet_state()
|
||||
await WALLET.redeem(cashu_token)
|
||||
state_after = await WALLET.fetch_wallet_state()
|
||||
amount = (state_after.balance - state_before.balance) * 1000
|
||||
key.balance += amount
|
||||
session.add(key)
|
||||
await session.commit()
|
||||
return amount
|
||||
|
||||
|
||||
async def check_for_refunds() -> None:
|
||||
@@ -81,7 +87,6 @@ async def check_for_refunds() -> None:
|
||||
Raises:
|
||||
Exception: If an error occurs during the refund check process.
|
||||
"""
|
||||
raise Exception("TODO migrate to sixty-nuts")
|
||||
# Setting REFUND_PROCESSING_INTERVAL to 0 disables it
|
||||
if REFUND_PROCESSING_INTERVAL == 0:
|
||||
print("Automatic refund processing is disabled.")
|
||||
@@ -112,31 +117,34 @@ async def check_for_refunds() -> None:
|
||||
print(f"Error during refund check: {e}")
|
||||
|
||||
|
||||
async def refund_balance(amount: int, key: ApiKey, session: AsyncSession) -> int:
|
||||
async with Wallet(nsec=NSEC, mint_urls=[MINT]) as wallet:
|
||||
if key.balance < amount:
|
||||
raise ValueError("Insufficient balance.")
|
||||
if amount <= 0:
|
||||
amount = key.balance
|
||||
async def refund_balance(amount_msats: int, key: ApiKey, session: AsyncSession) -> int:
|
||||
if key.balance < amount_msats:
|
||||
raise ValueError("Insufficient balance.")
|
||||
if amount_msats <= 0:
|
||||
amount_msats = key.balance
|
||||
|
||||
key.balance -= amount
|
||||
session.add(key)
|
||||
await session.commit()
|
||||
# Convert msats to sats for cashu wallet
|
||||
amount_sats = amount_msats // 1000
|
||||
if amount_sats == 0:
|
||||
raise ValueError("Amount too small to refund (less than 1 sat)")
|
||||
|
||||
if key.refund_address is None:
|
||||
raise ValueError("Refund address not set.")
|
||||
key.balance -= amount_msats
|
||||
session.add(key)
|
||||
await session.commit()
|
||||
|
||||
return await wallet.send_to_lnurl(
|
||||
key.refund_address,
|
||||
amount=amount,
|
||||
)
|
||||
if key.refund_address is None:
|
||||
raise ValueError("Refund address not set.")
|
||||
|
||||
return await WALLET.send_to_lnurl(
|
||||
key.refund_address,
|
||||
amount=amount_sats,
|
||||
)
|
||||
|
||||
|
||||
async def redeem(cashu_token: str, lnurl: str) -> int:
|
||||
async with Wallet(nsec=NSEC, mint_urls=[MINT]) as wallet:
|
||||
state_before = await wallet.fetch_wallet_state()
|
||||
await wallet.redeem(cashu_token)
|
||||
state_after = await wallet.fetch_wallet_state()
|
||||
amount = state_after.balance - state_before.balance
|
||||
await wallet.send_to_lnurl(lnurl, amount=amount)
|
||||
return amount
|
||||
state_before = await WALLET.fetch_wallet_state()
|
||||
await WALLET.redeem(cashu_token)
|
||||
state_after = await WALLET.fetch_wallet_state()
|
||||
amount = state_after.balance - state_before.balance
|
||||
await WALLET.send_to_lnurl(lnurl, amount=amount)
|
||||
return amount
|
||||
|
||||
+17
-11
@@ -1,4 +1,5 @@
|
||||
import asyncio
|
||||
from contextlib import asynccontextmanager
|
||||
import os
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@@ -6,19 +7,31 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||
from .db import init_db
|
||||
from .admin import admin_router
|
||||
from .proxy import proxy_router
|
||||
from .account import account_router
|
||||
from .account import wallet_router
|
||||
from .models import MODELS, update_sats_pricing
|
||||
from .cashu import check_for_refunds
|
||||
from .cashu import check_for_refunds, init_wallet, close_wallet
|
||||
from .discovery import providers_router
|
||||
|
||||
|
||||
__version__ = "0.0.1"
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(_: FastAPI):
|
||||
await init_db()
|
||||
await init_wallet()
|
||||
asyncio.create_task(update_sats_pricing())
|
||||
asyncio.create_task(check_for_refunds())
|
||||
|
||||
yield
|
||||
|
||||
await close_wallet()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
version=__version__,
|
||||
title=os.environ.get("NAME", "ARoutstrNode" + __version__),
|
||||
description=os.environ.get("DESCRIPTION", "A Routstr Node"),
|
||||
contact={"name": os.environ.get("NAME", ""), "npub": os.environ.get("NPUB", "")},
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# Configure CORS
|
||||
@@ -46,13 +59,6 @@ async def info():
|
||||
|
||||
|
||||
app.include_router(admin_router)
|
||||
app.include_router(account_router)
|
||||
app.include_router(wallet_router)
|
||||
app.include_router(providers_router)
|
||||
app.include_router(proxy_router)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
await init_db()
|
||||
asyncio.create_task(update_sats_pricing())
|
||||
asyncio.create_task(check_for_refunds())
|
||||
|
||||
+4
-11
@@ -116,17 +116,10 @@ async def test_refund_balance_without_address(
|
||||
test_session.add(key)
|
||||
await test_session.commit()
|
||||
|
||||
# Mock the Wallet class at the router.account module level
|
||||
with patch("router.account.Wallet") as mock_wallet_class:
|
||||
# Create a mock wallet instance
|
||||
mock_wallet = AsyncMock()
|
||||
mock_wallet.__aenter__ = AsyncMock(return_value=mock_wallet)
|
||||
mock_wallet.__aexit__ = AsyncMock(return_value=None)
|
||||
# Mock the WALLET instance at the router.account module level
|
||||
with patch("router.account.WALLET") as mock_wallet:
|
||||
mock_wallet.send = AsyncMock(return_value="cashuBqQSEQ...")
|
||||
|
||||
# Make the Wallet class return our mock when instantiated
|
||||
mock_wallet_class.return_value = mock_wallet
|
||||
|
||||
response = await async_client.post(
|
||||
"/v1/wallet/refund", headers={"Authorization": f"Bearer sk-{api_key}"}
|
||||
)
|
||||
@@ -138,8 +131,8 @@ async def test_refund_balance_without_address(
|
||||
assert data["msats"] == 500000
|
||||
assert data["token"] == "cashuBqQSEQ..."
|
||||
|
||||
# Verify wallet.send was called with the correct amount
|
||||
mock_wallet.send.assert_called_once_with(500000)
|
||||
# Verify wallet.send was called with the correct amount (msats converted to sats)
|
||||
mock_wallet.send.assert_called_once_with(500)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user