mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-23 03:34:19 +01:00
[Wallet] Define responses for API (#233)
* Define responses for wallet API * Make format
This commit is contained in:
75
cashu/wallet/api/responses.py
Normal file
75
cashu/wallet/api/responses.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from typing import Dict, List, Union
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from ...core.base import Invoice, P2SHScript
|
||||||
|
|
||||||
|
|
||||||
|
class PayResponse(BaseModel):
|
||||||
|
amount: int
|
||||||
|
fee: int
|
||||||
|
amount_with_fee: int
|
||||||
|
initial_balance: int
|
||||||
|
balance: int
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceResponse(BaseModel):
|
||||||
|
amount: int
|
||||||
|
invoice: Union[Invoice, None] = None
|
||||||
|
hash: Union[str, None] = None
|
||||||
|
initial_balance: int
|
||||||
|
balance: int
|
||||||
|
|
||||||
|
|
||||||
|
class BalanceResponse(BaseModel):
|
||||||
|
balance: int
|
||||||
|
keysets: Union[Dict, None] = None
|
||||||
|
mints: Union[Dict, None] = None
|
||||||
|
|
||||||
|
|
||||||
|
class SendResponse(BaseModel):
|
||||||
|
balance: int
|
||||||
|
token: str
|
||||||
|
npub: Union[str, None] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveResponse(BaseModel):
|
||||||
|
initial_balance: int
|
||||||
|
balance: int
|
||||||
|
|
||||||
|
|
||||||
|
class BurnResponse(BaseModel):
|
||||||
|
balance: int
|
||||||
|
|
||||||
|
|
||||||
|
class PendingResponse(BaseModel):
|
||||||
|
pending_token: Dict
|
||||||
|
|
||||||
|
|
||||||
|
class LockResponse(BaseModel):
|
||||||
|
P2SH: Union[str, None]
|
||||||
|
|
||||||
|
|
||||||
|
class LocksResponse(BaseModel):
|
||||||
|
locks: List[P2SHScript]
|
||||||
|
|
||||||
|
|
||||||
|
class InvoicesResponse(BaseModel):
|
||||||
|
invoices: List[Invoice]
|
||||||
|
|
||||||
|
|
||||||
|
class WalletsResponse(BaseModel):
|
||||||
|
wallets: Dict
|
||||||
|
|
||||||
|
|
||||||
|
class InfoResponse(BaseModel):
|
||||||
|
version: str
|
||||||
|
wallet: str
|
||||||
|
debug: bool
|
||||||
|
cashu_dir: str
|
||||||
|
mint_url: str
|
||||||
|
settings: Union[str, None]
|
||||||
|
tor: bool
|
||||||
|
nostr_public_key: Union[str, None] = None
|
||||||
|
nostr_relays: List[str] = []
|
||||||
|
socks_proxy: Union[str, None] = None
|
||||||
@@ -18,6 +18,20 @@ from ...wallet.helpers import deserialize_token_from_string, init_wallet, receiv
|
|||||||
from ...wallet.nostr import receive_nostr, send_nostr
|
from ...wallet.nostr import receive_nostr, send_nostr
|
||||||
from ...wallet.wallet import Wallet as Wallet
|
from ...wallet.wallet import Wallet as Wallet
|
||||||
from .api_helpers import verify_mints
|
from .api_helpers import verify_mints
|
||||||
|
from .responses import (
|
||||||
|
BalanceResponse,
|
||||||
|
BurnResponse,
|
||||||
|
InfoResponse,
|
||||||
|
InvoiceResponse,
|
||||||
|
InvoicesResponse,
|
||||||
|
LockResponse,
|
||||||
|
LocksResponse,
|
||||||
|
PayResponse,
|
||||||
|
PendingResponse,
|
||||||
|
ReceiveResponse,
|
||||||
|
SendResponse,
|
||||||
|
WalletsResponse,
|
||||||
|
)
|
||||||
|
|
||||||
router: APIRouter = APIRouter()
|
router: APIRouter = APIRouter()
|
||||||
|
|
||||||
@@ -48,7 +62,7 @@ async def start_wallet():
|
|||||||
await init_wallet(wallet)
|
await init_wallet(wallet)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/pay", name="Pay lightning invoice")
|
@router.post("/pay", name="Pay lightning invoice", response_model=PayResponse)
|
||||||
async def pay(
|
async def pay(
|
||||||
invoice: str = Query(default=..., description="Lightning invoice to pay"),
|
invoice: str = Query(default=..., description="Lightning invoice to pay"),
|
||||||
mint: str = Query(
|
mint: str = Query(
|
||||||
@@ -77,16 +91,18 @@ async def pay(
|
|||||||
_, send_proofs = await wallet.split_to_send(wallet.proofs, total_amount)
|
_, send_proofs = await wallet.split_to_send(wallet.proofs, total_amount)
|
||||||
await wallet.pay_lightning(send_proofs, invoice)
|
await wallet.pay_lightning(send_proofs, invoice)
|
||||||
await wallet.load_proofs()
|
await wallet.load_proofs()
|
||||||
return {
|
return PayResponse(
|
||||||
"amount": total_amount - fee_reserve_sat,
|
amount=total_amount - fee_reserve_sat,
|
||||||
"fee": fee_reserve_sat,
|
fee=fee_reserve_sat,
|
||||||
"amount_with_fee": total_amount,
|
amount_with_fee=total_amount,
|
||||||
"initial_balance": initial_balance,
|
initial_balance=initial_balance,
|
||||||
"balance": wallet.available_balance,
|
balance=wallet.available_balance,
|
||||||
}
|
)
|
||||||
|
|
||||||
|
|
||||||
@router.post("/invoice", name="Request lightning invoice")
|
@router.post(
|
||||||
|
"/invoice", name="Request lightning invoice", response_model=InvoiceResponse
|
||||||
|
)
|
||||||
async def invoice(
|
async def invoice(
|
||||||
amount: int = Query(default=..., description="Amount to request in invoice"),
|
amount: int = Query(default=..., description="Amount to request in invoice"),
|
||||||
hash: str = Query(default=None, description="Hash of paid invoice"),
|
hash: str = Query(default=None, description="Hash of paid invoice"),
|
||||||
@@ -100,44 +116,45 @@ async def invoice(
|
|||||||
initial_balance = wallet.available_balance
|
initial_balance = wallet.available_balance
|
||||||
if not settings.lightning:
|
if not settings.lightning:
|
||||||
r = await wallet.mint(amount)
|
r = await wallet.mint(amount)
|
||||||
return {
|
return InvoiceResponse(
|
||||||
"amount": amount,
|
amount=amount,
|
||||||
"balance": wallet.available_balance,
|
balance=wallet.available_balance,
|
||||||
"initial_balance": initial_balance,
|
initial_balance=initial_balance,
|
||||||
}
|
)
|
||||||
elif amount and not hash:
|
elif amount and not hash:
|
||||||
invoice = await wallet.request_mint(amount)
|
invoice = await wallet.request_mint(amount)
|
||||||
return {
|
return InvoiceResponse(
|
||||||
"invoice": invoice,
|
invoice=invoice,
|
||||||
"balance": wallet.available_balance,
|
balance=wallet.available_balance,
|
||||||
"initial_balance": initial_balance,
|
initial_balance=initial_balance,
|
||||||
}
|
)
|
||||||
elif amount and hash:
|
elif amount and hash:
|
||||||
await wallet.mint(amount, hash)
|
await wallet.mint(amount, hash)
|
||||||
return {
|
return InvoiceResponse(
|
||||||
"amount": amount,
|
amount=amount,
|
||||||
"hash": hash,
|
hash=hash,
|
||||||
"balance": wallet.available_balance,
|
balance=wallet.available_balance,
|
||||||
"initial_balance": initial_balance,
|
initial_balance=initial_balance,
|
||||||
}
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@router.get("/balance", name="Balance", summary="Display balance.")
|
@router.get(
|
||||||
|
"/balance",
|
||||||
|
name="Balance",
|
||||||
|
summary="Display balance.",
|
||||||
|
response_model=BalanceResponse,
|
||||||
|
)
|
||||||
async def balance():
|
async def balance():
|
||||||
await wallet.load_proofs()
|
await wallet.load_proofs()
|
||||||
result: dict = {"balance": wallet.available_balance}
|
|
||||||
keyset_balances = wallet.balance_per_keyset()
|
keyset_balances = wallet.balance_per_keyset()
|
||||||
if len(keyset_balances) > 0:
|
|
||||||
result.update({"keysets": keyset_balances})
|
|
||||||
mint_balances = await wallet.balance_per_minturl()
|
mint_balances = await wallet.balance_per_minturl()
|
||||||
if len(mint_balances) > 0:
|
return BalanceResponse(
|
||||||
result.update({"mints": mint_balances})
|
balance=wallet.available_balance, keysets=keyset_balances, mints=mint_balances
|
||||||
|
)
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/send", name="Send tokens")
|
@router.post("/send", name="Send tokens", response_model=SendResponse)
|
||||||
async def send_command(
|
async def send_command(
|
||||||
amount: int = Query(default=..., description="Amount to send"),
|
amount: int = Query(default=..., description="Amount to send"),
|
||||||
nostr: str = Query(default=None, description="Send to nostr pubkey"),
|
nostr: str = Query(default=None, description="Send to nostr pubkey"),
|
||||||
@@ -156,27 +173,23 @@ async def send_command(
|
|||||||
balance, token = await send(wallet, amount, lock, legacy=False)
|
balance, token = await send(wallet, amount, lock, legacy=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||||
return {"balance": balance, "token": token}
|
return SendResponse(balance=balance, token=token)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
token, pubkey = await send_nostr(wallet, amount, nostr)
|
token, pubkey = await send_nostr(wallet, amount, nostr)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
|
||||||
return {
|
return SendResponse(balance=wallet.available_balance, token=token, npub=pubkey)
|
||||||
"balance": wallet.available_balance,
|
|
||||||
"token": token,
|
|
||||||
"npub": pubkey,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/receive", name="Receive tokens")
|
@router.post("/receive", name="Receive tokens", response_model=ReceiveResponse)
|
||||||
async def receive_command(
|
async def receive_command(
|
||||||
token: str = Query(default=None, description="Token to receive"),
|
token: str = Query(default=None, description="Token to receive"),
|
||||||
lock: str = Query(default=None, description="Unlock tokens"),
|
lock: str = Query(default=None, description="Unlock tokens"),
|
||||||
nostr: bool = Query(default=False, description="Receive tokens via nostr"),
|
nostr: bool = Query(default=False, description="Receive tokens via nostr"),
|
||||||
all: bool = Query(default=False, description="Receive all pending tokens"),
|
all: bool = Query(default=False, description="Receive all pending tokens"),
|
||||||
):
|
):
|
||||||
result = {"initial_balance": wallet.available_balance}
|
initial_balance = wallet.available_balance
|
||||||
if token:
|
if token:
|
||||||
try:
|
try:
|
||||||
tokenObj: TokenV3 = await deserialize_token_from_string(token)
|
tokenObj: TokenV3 = await deserialize_token_from_string(token)
|
||||||
@@ -223,11 +236,10 @@ async def receive_command(
|
|||||||
detail="enter token or use either flag --nostr or --all.",
|
detail="enter token or use either flag --nostr or --all.",
|
||||||
)
|
)
|
||||||
assert balance
|
assert balance
|
||||||
result.update({"balance": balance})
|
return ReceiveResponse(initial_balance=initial_balance, balance=balance)
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@router.post("/burn", name="Burn spent tokens")
|
@router.post("/burn", name="Burn spent tokens", response_model=BurnResponse)
|
||||||
async def burn(
|
async def burn(
|
||||||
token: str = Query(default=None, description="Token to burn"),
|
token: str = Query(default=None, description="Token to burn"),
|
||||||
all: bool = Query(default=False, description="Burn all spent tokens"),
|
all: bool = Query(default=False, description="Burn all spent tokens"),
|
||||||
@@ -268,10 +280,10 @@ async def burn(
|
|||||||
await wallet.invalidate(proofs, check_spendable=False)
|
await wallet.invalidate(proofs, check_spendable=False)
|
||||||
else:
|
else:
|
||||||
await wallet.invalidate(proofs)
|
await wallet.invalidate(proofs)
|
||||||
return {"balance": wallet.available_balance}
|
return BurnResponse(balance=wallet.available_balance)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/pending", name="Show pending tokens")
|
@router.get("/pending", name="Show pending tokens", response_model=PendingResponse)
|
||||||
async def pending(
|
async def pending(
|
||||||
number: int = Query(default=None, description="Show only n pending tokens"),
|
number: int = Query(default=None, description="Show only n pending tokens"),
|
||||||
offset: int = Query(
|
offset: int = Query(
|
||||||
@@ -312,35 +324,33 @@ async def pending(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return result
|
return PendingResponse(pending_token=result)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/lock", name="Generate receiving lock")
|
@router.get("/lock", name="Generate receiving lock", response_model=LockResponse)
|
||||||
async def lock():
|
async def lock():
|
||||||
p2shscript = await wallet.create_p2sh_lock()
|
p2shscript = await wallet.create_p2sh_lock()
|
||||||
txin_p2sh_address = p2shscript.address
|
txin_p2sh_address = p2shscript.address
|
||||||
return {"P2SH": txin_p2sh_address}
|
return LockResponse(P2SH=txin_p2sh_address)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/locks", name="Show unused receiving locks")
|
@router.get("/locks", name="Show unused receiving locks", response_model=LocksResponse)
|
||||||
async def locks():
|
async def locks():
|
||||||
locks = await get_unused_locks(db=wallet.db)
|
locks = await get_unused_locks(db=wallet.db)
|
||||||
if len(locks):
|
return LocksResponse(locks=locks)
|
||||||
return {"locks": locks}
|
|
||||||
else:
|
|
||||||
return {"locks": []}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/invoices", name="List all pending invoices")
|
@router.get(
|
||||||
|
"/invoices", name="List all pending invoices", response_model=InvoicesResponse
|
||||||
|
)
|
||||||
async def invoices():
|
async def invoices():
|
||||||
invoices = await get_lightning_invoices(db=wallet.db)
|
invoices = await get_lightning_invoices(db=wallet.db)
|
||||||
if len(invoices):
|
return InvoicesResponse(invoices=invoices)
|
||||||
return {"invoices": invoices}
|
|
||||||
else:
|
|
||||||
return {"invoices": []}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/wallets", name="List all available wallets")
|
@router.get(
|
||||||
|
"/wallets", name="List all available wallets", response_model=WalletsResponse
|
||||||
|
)
|
||||||
async def wallets():
|
async def wallets():
|
||||||
wallets = [
|
wallets = [
|
||||||
d for d in listdir(settings.cashu_dir) if isdir(join(settings.cashu_dir, d))
|
d for d in listdir(settings.cashu_dir) if isdir(join(settings.cashu_dir, d))
|
||||||
@@ -371,38 +381,35 @@ async def wallets():
|
|||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
return result
|
return WalletsResponse(wallets=result)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/info", name="Information about Cashu wallet")
|
@router.get("/info", name="Information about Cashu wallet", response_model=InfoResponse)
|
||||||
async def info():
|
async def info():
|
||||||
general = {
|
|
||||||
"version": settings.version,
|
|
||||||
"wallet": wallet.name,
|
|
||||||
"debug": settings.debug,
|
|
||||||
"cashu_dir": settings.cashu_dir,
|
|
||||||
"mint_url": settings.mint_url,
|
|
||||||
}
|
|
||||||
if settings.env_file:
|
|
||||||
general.update({"settings": settings.env_file})
|
|
||||||
if settings.tor:
|
|
||||||
general.update({"tor": settings.tor})
|
|
||||||
if settings.nostr_private_key:
|
if settings.nostr_private_key:
|
||||||
try:
|
try:
|
||||||
client = NostrClient(private_key=settings.nostr_private_key, connect=False)
|
client = NostrClient(private_key=settings.nostr_private_key, connect=False)
|
||||||
general.update(
|
nostr_public_key = client.private_key.bech32()
|
||||||
{
|
nostr_relays = settings.nostr_relays
|
||||||
"nostr": {
|
|
||||||
"public_key": client.private_key.bech32(),
|
|
||||||
"relays": settings.nostr_relays,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except:
|
except:
|
||||||
general.update({"nostr": "Invalid key"})
|
nostr_public_key = "Invalid key"
|
||||||
|
nostr_relays = []
|
||||||
|
else:
|
||||||
|
nostr_public_key = None
|
||||||
|
nostr_relays = []
|
||||||
if settings.socks_host:
|
if settings.socks_host:
|
||||||
general.update(
|
socks_proxy = settings.socks_host + ":" + str(settings.socks_host)
|
||||||
{"socks proxy": settings.socks_host + ":" + str(settings.socks_host)}
|
else:
|
||||||
|
socks_proxy = None
|
||||||
|
return InfoResponse(
|
||||||
|
version=settings.version,
|
||||||
|
wallet=wallet.name,
|
||||||
|
debug=settings.debug,
|
||||||
|
cashu_dir=settings.cashu_dir,
|
||||||
|
mint_url=settings.mint_url,
|
||||||
|
settings=settings.env_file,
|
||||||
|
tor=settings.tor,
|
||||||
|
nostr_public_key=nostr_public_key,
|
||||||
|
nostr_relays=nostr_relays,
|
||||||
|
socks_proxy=socks_proxy,
|
||||||
)
|
)
|
||||||
|
|
||||||
return general
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ def test_pending():
|
|||||||
with TestClient(app) as client:
|
with TestClient(app) as client:
|
||||||
response = client.get("/pending")
|
response = client.get("/pending")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json()["0"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_receive_all(mint):
|
def test_receive_all(mint):
|
||||||
@@ -114,7 +113,7 @@ def test_flow(mint):
|
|||||||
response = client.post("/send?amount=50")
|
response = client.post("/send?amount=50")
|
||||||
assert response.json()["balance"] == initial_balance
|
assert response.json()["balance"] == initial_balance
|
||||||
response = client.get("/pending")
|
response = client.get("/pending")
|
||||||
token = response.json()["0"]["token"]
|
token = response.json()["pending_token"]["0"]["token"]
|
||||||
amount = response.json()["0"]["amount"]
|
amount = response.json()["pending_token"]["0"]["amount"]
|
||||||
response = client.post(f"/receive?token={token}")
|
response = client.post(f"/receive?token={token}")
|
||||||
assert response.json()["balance"] == initial_balance + amount
|
assert response.json()["balance"] == initial_balance + amount
|
||||||
|
|||||||
Reference in New Issue
Block a user