mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-22 03:24:18 +01:00
Nutshell cleanup wishlist (#332)
* fix keys * fix tests * backwards compatible api upgrade * upgrade seems to work * fix tests * add deprecated api functions * add more tests of backwards compat * add test serialization for nut00 * remove a redundant test * move mint and melt to new api * mypy works * CI: mypy --check-untyped-defs * add deprecated router * add hints and remove logs * fix tests * cleanup * use new mint and melt endpoints * tests passing? * fix mypy * make format * make format * make format * commit * errors gone * save * adjust the API * store quotes in db * make mypy happy * add fakewallet settings * remove LIGHTNING=True and pass quote id for melt * format * tests passing * add CoreLightningRestWallet * add macaroon loader * add correct config * preimage -> proof * move wallet.status() to cli.helpers.print_status() * remove statuses from tests * remove * make format * Use httpx in deprecated wallet * fix cln interface * create invoice before quote * internal transactions and deprecated api testing * fix tests * add deprecated API tests * fastapi type hints break things * fix duplicate wallet error * make format * update poetry in CI to 1.7.1 * precommit restore * remove bolt11 * oops * default poetry * store fee reserve for melt quotes and refactor melt() * works? * make format * test * finally * fix deprecated models * rename v1 endpoints to bolt11 * raise restore and check to v1, bump version to 0.15.0 * add version byte to keyset id * remove redundant fields in json * checks * generate bip32 keyset wip * migrate old keysets * load duplicate keys * duplicate old keysets * revert router changes * add deprecated /check and /restore endpoints * try except invalidate * parse unit from derivation path, adjust keyset id calculation with bytes * remove keyest id from functions again and rely on self.keyset_id * mosts tests work * mint loads multiple derivation paths * make format * properly print units * fix tests * wallet works with multiple units * add strike wallet and choose backend dynamically * fix mypy * add get_payment_quote to lightning backends * make format * fix startup * fix lnbitswallet * fix tests * LightningWallet -> LightningBackend * remove comments * make format * remove msat conversion * add Amount type * fix regtest * use melt_quote as argument for pay_invoice * test old api * fees in sats * fix deprecated fees * fixes * print balance correctly * internally index keyset response by int * add pydantic validation to input models * add timestamps to mint db * store timestamps for invoices, promises, proofs_used * fix wallet migration * rotate keys correctly for testing * remove print * update latest keyset * fix tests * fix test * make format * make format with correct black version * remove nsat and cheese * test against deprecated mint * fix tests? * actually use env var * mint run with env vars * moar test * cleanup * simplify tests, load all keys * try out testing with internal invoices * fix internal melt test * fix test * deprecated checkfees expects appropriate fees * adjust comment * drop lightning table * split migration for testing for now, remove it later * remove unused lightning table * skip_private_key -> skip_db_read * throw error on migration error * reorder * fix migrations * fix lnbits fee return value negative * fix typo * comments * add type * make format * split must use correct amount * fix tests * test deprecated api with internal/external melts * do not split if not necessary * refactor * fix test * make format with new black * cleanup and add comments * add quote state check endpoints * fix deprecated wallet response * split -> swap endpoint * make format * add expiry to quotes, get quote endpoints, and adjust to nut review comments * allow overpayment of melt * add lightning wallet tests * commiting to save * fix tests a bit * make format * remove comments * get mint info * check_spendable default False, and return payment quote checking id * make format * bump version in pyproject * update to /v1/checkstate * make format * fix mint api checks * return witness on /v1/checkstate * no failfast * try fail-fast: false in ci.yaml * fix db lookup * clean up literals
This commit is contained in:
363
cashu/mint/router_deprecated.py
Normal file
363
cashu/mint/router_deprecated.py
Normal file
@@ -0,0 +1,363 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from fastapi import APIRouter
|
||||
from loguru import logger
|
||||
|
||||
from ..core.base import (
|
||||
BlindedSignature,
|
||||
CheckFeesRequest_deprecated,
|
||||
CheckFeesResponse_deprecated,
|
||||
CheckSpendableRequest_deprecated,
|
||||
CheckSpendableResponse_deprecated,
|
||||
GetInfoResponse_deprecated,
|
||||
GetMintResponse_deprecated,
|
||||
KeysetsResponse_deprecated,
|
||||
KeysResponse_deprecated,
|
||||
PostMeltQuoteRequest,
|
||||
PostMeltRequest_deprecated,
|
||||
PostMeltResponse_deprecated,
|
||||
PostMintQuoteRequest,
|
||||
PostMintRequest_deprecated,
|
||||
PostMintResponse_deprecated,
|
||||
PostRestoreResponse,
|
||||
PostSplitRequest_Deprecated,
|
||||
PostSplitResponse_Deprecated,
|
||||
PostSplitResponse_Very_Deprecated,
|
||||
SpentState,
|
||||
)
|
||||
from ..core.errors import CashuError
|
||||
from ..core.settings import settings
|
||||
from .startup import ledger
|
||||
|
||||
router_deprecated: APIRouter = APIRouter()
|
||||
|
||||
|
||||
@router_deprecated.get(
|
||||
"/info",
|
||||
name="Mint information",
|
||||
summary="Mint information, operator contact information, and other info.",
|
||||
response_model=GetInfoResponse_deprecated,
|
||||
response_model_exclude_none=True,
|
||||
deprecated=True,
|
||||
)
|
||||
async def info() -> GetInfoResponse_deprecated:
|
||||
logger.trace("> GET /info")
|
||||
return GetInfoResponse_deprecated(
|
||||
name=settings.mint_info_name,
|
||||
pubkey=ledger.pubkey.serialize().hex() if ledger.pubkey else None,
|
||||
version=f"Nutshell/{settings.version}",
|
||||
description=settings.mint_info_description,
|
||||
description_long=settings.mint_info_description_long,
|
||||
contact=settings.mint_info_contact,
|
||||
nuts=settings.mint_info_nuts,
|
||||
motd=settings.mint_info_motd,
|
||||
parameter={
|
||||
"max_peg_in": settings.mint_max_peg_in,
|
||||
"max_peg_out": settings.mint_max_peg_out,
|
||||
"peg_out_only": settings.mint_peg_out_only,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router_deprecated.get(
|
||||
"/keys",
|
||||
name="Mint public keys",
|
||||
summary="Get the public keys of the newest mint keyset",
|
||||
response_description=(
|
||||
"A dictionary of all supported token values of the mint and their associated"
|
||||
" public key of the current keyset."
|
||||
),
|
||||
response_model=KeysResponse_deprecated,
|
||||
deprecated=True,
|
||||
)
|
||||
async def keys_deprecated():
|
||||
"""This endpoint returns a dictionary of all supported token values of the mint and their associated public key."""
|
||||
logger.trace("> GET /keys")
|
||||
keyset = ledger.get_keyset()
|
||||
keys = KeysResponse_deprecated.parse_obj(keyset)
|
||||
return keys.__root__
|
||||
|
||||
|
||||
@router_deprecated.get(
|
||||
"/keys/{idBase64Urlsafe}",
|
||||
name="Keyset public keys",
|
||||
summary="Public keys of a specific keyset",
|
||||
response_description=(
|
||||
"A dictionary of all supported token values of the mint and their associated"
|
||||
" public key for a specific keyset."
|
||||
),
|
||||
response_model=KeysResponse_deprecated,
|
||||
deprecated=True,
|
||||
)
|
||||
async def keyset_deprecated(idBase64Urlsafe: str):
|
||||
"""
|
||||
Get the public keys of the mint from a specific keyset id.
|
||||
The id is encoded in idBase64Urlsafe (by a wallet) and is converted back to
|
||||
normal base64 before it can be processed (by the mint).
|
||||
"""
|
||||
logger.trace(f"> GET /keys/{idBase64Urlsafe}")
|
||||
id = idBase64Urlsafe.replace("-", "+").replace("_", "/")
|
||||
keyset = ledger.get_keyset(keyset_id=id)
|
||||
keys = KeysResponse_deprecated.parse_obj(keyset)
|
||||
return keys.__root__
|
||||
|
||||
|
||||
@router_deprecated.get(
|
||||
"/keysets",
|
||||
name="Active keysets",
|
||||
summary="Get all active keyset id of the mind",
|
||||
response_model=KeysetsResponse_deprecated,
|
||||
response_description="A list of all active keyset ids of the mint.",
|
||||
deprecated=True,
|
||||
)
|
||||
async def keysets_deprecated() -> KeysetsResponse_deprecated:
|
||||
"""This endpoint returns a list of keysets that the mint currently supports and will accept tokens from."""
|
||||
logger.trace("> GET /keysets")
|
||||
keysets = KeysetsResponse_deprecated(keysets=list(ledger.keysets.keys()))
|
||||
return keysets
|
||||
|
||||
|
||||
@router_deprecated.get(
|
||||
"/mint",
|
||||
name="Request mint",
|
||||
summary="Request minting of new tokens",
|
||||
response_model=GetMintResponse_deprecated,
|
||||
response_description=(
|
||||
"A Lightning invoice to be paid and a hash to request minting of new tokens"
|
||||
" after payment."
|
||||
),
|
||||
deprecated=True,
|
||||
)
|
||||
async def request_mint_deprecated(amount: int = 0) -> GetMintResponse_deprecated:
|
||||
"""
|
||||
Request minting of new tokens. The mint responds with a Lightning invoice.
|
||||
This endpoint can be used for a Lightning invoice UX flow.
|
||||
|
||||
Call `POST /mint` after paying the invoice.
|
||||
"""
|
||||
logger.trace(f"> GET /mint: amount={amount}")
|
||||
if amount > 21_000_000 * 100_000_000 or amount <= 0:
|
||||
raise CashuError(code=0, detail="Amount must be a valid amount of sat.")
|
||||
if settings.mint_peg_out_only:
|
||||
raise CashuError(code=0, detail="Mint does not allow minting new tokens.")
|
||||
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=amount, unit="sat"))
|
||||
resp = GetMintResponse_deprecated(pr=quote.request, hash=quote.quote)
|
||||
logger.trace(f"< GET /mint: {resp}")
|
||||
return resp
|
||||
|
||||
|
||||
@router_deprecated.post(
|
||||
"/mint",
|
||||
name="Mint tokens",
|
||||
summary="Mint tokens in exchange for a Bitcoin payment that the user has made",
|
||||
response_model=PostMintResponse_deprecated,
|
||||
response_description=(
|
||||
"A list of blinded signatures that can be used to create proofs."
|
||||
),
|
||||
deprecated=True,
|
||||
)
|
||||
async def mint_deprecated(
|
||||
payload: PostMintRequest_deprecated,
|
||||
hash: Optional[str] = None,
|
||||
payment_hash: Optional[str] = None,
|
||||
) -> PostMintResponse_deprecated:
|
||||
"""
|
||||
Requests the minting of tokens belonging to a paid payment request.
|
||||
|
||||
Call this endpoint after `GET /mint`.
|
||||
"""
|
||||
logger.trace(f"> POST /mint: {payload}")
|
||||
|
||||
# BEGIN BACKWARDS COMPATIBILITY < 0.15
|
||||
# Mint expects "id" in outputs to know which keyset to use to sign them.
|
||||
for output in payload.outputs:
|
||||
if not output.id:
|
||||
# use the deprecated version of the current keyset
|
||||
output.id = ledger.keyset.duplicate_keyset_id
|
||||
# END BACKWARDS COMPATIBILITY < 0.15
|
||||
|
||||
# BEGIN: backwards compatibility < 0.12 where we used to lookup payments with payment_hash
|
||||
# We use the payment_hash to lookup the hash from the database and pass that one along.
|
||||
hash = payment_hash or hash
|
||||
assert hash, "hash must be set."
|
||||
# END: backwards compatibility < 0.12
|
||||
|
||||
promises = await ledger.mint(outputs=payload.outputs, quote_id=hash)
|
||||
blinded_signatures = PostMintResponse_deprecated(promises=promises)
|
||||
|
||||
logger.trace(f"< POST /mint: {blinded_signatures}")
|
||||
return blinded_signatures
|
||||
|
||||
|
||||
@router_deprecated.post(
|
||||
"/melt",
|
||||
name="Melt tokens",
|
||||
summary=(
|
||||
"Melt tokens for a Bitcoin payment that the mint will make for the user in"
|
||||
" exchange"
|
||||
),
|
||||
response_model=PostMeltResponse_deprecated,
|
||||
response_description=(
|
||||
"The state of the payment, a preimage as proof of payment, and a list of"
|
||||
" promises for change."
|
||||
),
|
||||
deprecated=True,
|
||||
)
|
||||
async def melt_deprecated(
|
||||
payload: PostMeltRequest_deprecated,
|
||||
) -> PostMeltResponse_deprecated:
|
||||
"""
|
||||
Requests tokens to be destroyed and sent out via Lightning.
|
||||
"""
|
||||
logger.trace(f"> POST /melt: {payload}")
|
||||
# BEGIN BACKWARDS COMPATIBILITY < 0.14: add "id" to outputs
|
||||
if payload.outputs:
|
||||
for output in payload.outputs:
|
||||
if not output.id:
|
||||
output.id = ledger.keyset.id
|
||||
# END BACKWARDS COMPATIBILITY < 0.14
|
||||
quote = await ledger.melt_quote(
|
||||
PostMeltQuoteRequest(request=payload.pr, unit="sat")
|
||||
)
|
||||
preimage, change_promises = await ledger.melt(
|
||||
proofs=payload.proofs, quote=quote.quote, outputs=payload.outputs
|
||||
)
|
||||
resp = PostMeltResponse_deprecated(
|
||||
paid=True, preimage=preimage, change=change_promises
|
||||
)
|
||||
logger.trace(f"< POST /melt: {resp}")
|
||||
return resp
|
||||
|
||||
|
||||
@router_deprecated.post(
|
||||
"/checkfees",
|
||||
name="Check fees",
|
||||
summary="Check fee reserve for a Lightning payment",
|
||||
response_model=CheckFeesResponse_deprecated,
|
||||
response_description="The fees necessary to pay a Lightning invoice.",
|
||||
deprecated=True,
|
||||
)
|
||||
async def check_fees(
|
||||
payload: CheckFeesRequest_deprecated,
|
||||
) -> CheckFeesResponse_deprecated:
|
||||
"""
|
||||
Responds with the fees necessary to pay a Lightning invoice.
|
||||
Used by wallets for figuring out the fees they need to supply together with the payment amount.
|
||||
This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu).
|
||||
"""
|
||||
logger.trace(f"> POST /checkfees: {payload}")
|
||||
quote = await ledger.melt_quote(
|
||||
PostMeltQuoteRequest(request=payload.pr, unit="sat")
|
||||
)
|
||||
fees_sat = quote.fee_reserve
|
||||
logger.trace(f"< POST /checkfees: {fees_sat}")
|
||||
return CheckFeesResponse_deprecated(fee=fees_sat)
|
||||
|
||||
|
||||
@router_deprecated.post(
|
||||
"/split",
|
||||
name="Split",
|
||||
summary="Split proofs at a specified amount",
|
||||
# response_model=Union[
|
||||
# PostSplitResponse_Very_Deprecated, PostSplitResponse_Deprecated
|
||||
# ],
|
||||
response_description=(
|
||||
"A list of blinded signatures that can be used to create proofs."
|
||||
),
|
||||
deprecated=True,
|
||||
)
|
||||
async def split_deprecated(
|
||||
payload: PostSplitRequest_Deprecated,
|
||||
# ) -> Union[PostSplitResponse_Very_Deprecated, PostSplitResponse_Deprecated]:
|
||||
):
|
||||
"""
|
||||
Requests a set of Proofs to be split into two a new set of BlindedSignatures.
|
||||
|
||||
This endpoint is used by Alice to split a set of proofs before making a payment to Carol.
|
||||
It is then used by Carol (by setting split=total) to redeem the tokens.
|
||||
"""
|
||||
logger.trace(f"> POST /split: {payload}")
|
||||
assert payload.outputs, Exception("no outputs provided.")
|
||||
# BEGIN BACKWARDS COMPATIBILITY < 0.14: add "id" to outputs
|
||||
if payload.outputs:
|
||||
for output in payload.outputs:
|
||||
if not output.id:
|
||||
output.id = ledger.keyset.id
|
||||
# END BACKWARDS COMPATIBILITY < 0.14
|
||||
promises = await ledger.split(proofs=payload.proofs, outputs=payload.outputs)
|
||||
|
||||
if payload.amount:
|
||||
# BEGIN backwards compatibility < 0.13
|
||||
# old clients expect two lists of promises where the second one's amounts
|
||||
# sum up to `amount`. The first one is the rest.
|
||||
# The returned value `promises` has the form [keep1, keep2, ..., send1, send2, ...]
|
||||
# The sum of the sendx is `amount`. We need to split this into two lists and keep the order of the elements.
|
||||
frst_promises: List[BlindedSignature] = []
|
||||
scnd_promises: List[BlindedSignature] = []
|
||||
scnd_amount = 0
|
||||
for promise in promises[::-1]: # we iterate backwards
|
||||
if scnd_amount < payload.amount:
|
||||
scnd_promises.insert(0, promise) # and insert at the beginning
|
||||
scnd_amount += promise.amount
|
||||
else:
|
||||
frst_promises.insert(0, promise) # and insert at the beginning
|
||||
logger.trace(
|
||||
f"Split into keep: {len(frst_promises)}:"
|
||||
f" {sum([p.amount for p in frst_promises])} sat and send:"
|
||||
f" {len(scnd_promises)}: {sum([p.amount for p in scnd_promises])} sat"
|
||||
)
|
||||
return PostSplitResponse_Very_Deprecated(fst=frst_promises, snd=scnd_promises)
|
||||
# END backwards compatibility < 0.13
|
||||
else:
|
||||
return PostSplitResponse_Deprecated(promises=promises)
|
||||
|
||||
|
||||
@router_deprecated.post(
|
||||
"/check",
|
||||
name="Check proof state",
|
||||
summary="Check whether a proof is spent already or is pending in a transaction",
|
||||
response_model=CheckSpendableResponse_deprecated,
|
||||
response_description=(
|
||||
"Two lists of booleans indicating whether the provided proofs "
|
||||
"are spendable or pending in a transaction respectively."
|
||||
),
|
||||
deprecated=True,
|
||||
)
|
||||
async def check_spendable(
|
||||
payload: CheckSpendableRequest_deprecated,
|
||||
) -> CheckSpendableResponse_deprecated:
|
||||
"""Check whether a secret has been spent already or not."""
|
||||
logger.trace(f"> POST /check: {payload}")
|
||||
proofs_state = await ledger.check_proofs_state([p.secret for p in payload.proofs])
|
||||
spendableList: List[bool] = []
|
||||
pendingList: List[bool] = []
|
||||
for proof_state in proofs_state:
|
||||
if proof_state.state == SpentState.unspent:
|
||||
spendableList.append(True)
|
||||
pendingList.append(False)
|
||||
elif proof_state.state == SpentState.spent:
|
||||
spendableList.append(False)
|
||||
pendingList.append(False)
|
||||
elif proof_state.state == SpentState.pending:
|
||||
spendableList.append(True)
|
||||
pendingList.append(True)
|
||||
return CheckSpendableResponse_deprecated(
|
||||
spendable=spendableList, pending=pendingList
|
||||
)
|
||||
|
||||
|
||||
@router_deprecated.post(
|
||||
"/restore",
|
||||
name="Restore",
|
||||
summary="Restores a blinded signature from a secret",
|
||||
response_model=PostRestoreResponse,
|
||||
response_description=(
|
||||
"Two lists with the first being the list of the provided outputs that "
|
||||
"have an associated blinded signature which is given in the second list."
|
||||
),
|
||||
deprecated=True,
|
||||
)
|
||||
async def restore(payload: PostMintRequest_deprecated) -> PostRestoreResponse:
|
||||
assert payload.outputs, Exception("no outputs provided.")
|
||||
outputs, promises = await ledger.restore(payload.outputs)
|
||||
return PostRestoreResponse(outputs=outputs, promises=promises)
|
||||
Reference in New Issue
Block a user