[Wallet] Define responses for API (#233)

* Define responses for wallet API

* Make format
This commit is contained in:
sihamon
2023-05-22 14:31:29 +02:00
committed by GitHub
parent a3ded1cd94
commit 08ceeda36f
3 changed files with 172 additions and 91 deletions

View 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

View File

@@ -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

View File

@@ -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