Files
nutshell/cashu/wallet/helpers.py
callebtc 4088ab2876 Wallet REST API (#199)
* Add REST API for Cashu wallet

* Add simple way to start REST API server

* Add simple tests for wallet REST API

* Add TokenV3 to REST API

* Improve tests for wallet REST API

* Make format

* Remove unused import

* Rename nostr module for API

* Unify helper functions for CLI and API

* Make format

* Move error handling from helper functions to API

* Remove 'is_api' flag where possible

* Make format, cleanup, add comments

* Fix typo for burn also in API

* Improve error handling for API

* Add flag 'trust_new_mint' to receive command

To enable trusting or mistrusting unknown mints via API

* Allow selecting mint for sending from API

* Fix: set specific mint via API

* Fix: select mint with maximum balance via CLI

* Use different variables for mint_nr

* Allow selecting mint when sending via nostr via API

* Remove unnessecary 'is_api' flags from 'send_nostr'

* Remove HTTPException from nostr.py

* Allow selecting mint for sending with parameter also via CLI

* Allow trusting unknown mint for receiving also via CLI

* Make format

* Enable trusting unknown mint also when receiving via nostr

* Fix: wrong indentation of in receive function

* Use relative imports for wallet API

* Unify get_mint_wallet for CLI and API

* Unify send command for CLI and API

* Unify receive for CLI and API

* Catch errors in nostr via API

* Remove flag 'is_api' from verify_mints_tokenv2

* Remove cli_helpers left from last merge

* refactor cli selection

* load mint in nostr_send

* cleanup

* add cli_helpers.py

* legacy deserialization in cli

* make format

* clean up api response

* fix tests

* try pk

* verify mints in api

* try github tests

* Fix: verify_mints for API

* Uncomment verify_mints in receive of API

* update README

* Show mint url in pending

* clean up balance response

* fix test

* mint selection in api

* clean up API

* CLI: verify mint for receive -a

* clean up

* Rest -> REST

* Remove unused imports

---------

Co-authored-by: sihamon <sihamon@proton.me>
Co-authored-by: sihamon <126967444+sihamon@users.noreply.github.com>
2023-05-11 23:27:13 +02:00

208 lines
6.3 KiB
Python

import base64
import json
import os
import click
from loguru import logger
from ..core.base import TokenV1, TokenV2, TokenV3, TokenV3Token
from ..core.helpers import sum_proofs
from ..core.migrations import migrate_databases
from ..core.settings import settings
from ..wallet import migrations
from ..wallet.crud import get_keyset, get_unused_locks
from ..wallet.wallet import Wallet as Wallet
async def init_wallet(wallet: Wallet):
"""Performs migrations and loads proofs from db."""
await migrate_databases(wallet.db, migrations)
await wallet.load_proofs()
async def redeem_TokenV3_multimint(
wallet: Wallet,
token: TokenV3,
script,
signature,
):
"""
Helper function to iterate thruogh a token with multiple mints and redeem them from
these mints one keyset at a time.
"""
for t in token.token:
assert t.mint, Exception(
"redeem_TokenV3_multimint: multimint redeem without URL"
)
mint_wallet = Wallet(t.mint, os.path.join(settings.cashu_dir, wallet.name))
keysets = mint_wallet._get_proofs_keysets(t.proofs)
logger.debug(f"Keysets in tokens: {keysets}")
# loop over all keysets
for keyset in set(keysets):
await mint_wallet.load_mint()
# redeem proofs of this keyset
redeem_proofs = [p for p in t.proofs if p.id == keyset]
_, _ = await mint_wallet.redeem(
redeem_proofs, scnd_script=script, scnd_siganture=signature
)
print(f"Received {sum_proofs(redeem_proofs)} sats")
async def serialize_TokenV2_to_TokenV3(tokenv2: TokenV2):
"""Helper function to receive legacy TokenV2 tokens.
Takes a list of proofs and constructs a *serialized* TokenV3 to be received through
the ordinary path.
Returns:
TokenV3: TokenV3
"""
tokenv3 = TokenV3(token=[TokenV3Token(proofs=tokenv2.proofs)])
if tokenv2.mints:
tokenv3.token[0].mint = tokenv2.mints[0].url
token_serialized = tokenv3.serialize()
return token_serialized
async def serialize_TokenV1_to_TokenV3(tokenv1: TokenV1):
"""Helper function to receive legacy TokenV1 tokens.
Takes a list of proofs and constructs a *serialized* TokenV3 to be received through
the ordinary path.
Returns:
TokenV3: TokenV3
"""
tokenv3 = TokenV3(token=[TokenV3Token(proofs=tokenv1.__root__)])
token_serialized = tokenv3.serialize()
return token_serialized
async def deserialize_token_from_string(token: str) -> TokenV3:
# deserialize token
# ----- backwards compatibility -----
# V2Tokens (0.7-0.11.0) (eyJwcm9...)
if token.startswith("eyJwcm9"):
try:
tokenv2 = TokenV2.parse_obj(json.loads(base64.urlsafe_b64decode(token)))
token = await serialize_TokenV2_to_TokenV3(tokenv2)
except:
pass
# V1Tokens (<0.7) (W3siaWQ...)
if token.startswith("W3siaWQ"):
try:
tokenv1 = TokenV1.parse_obj(json.loads(base64.urlsafe_b64decode(token)))
token = await serialize_TokenV1_to_TokenV3(tokenv1)
except:
pass
# ----- receive token -----
# deserialize token
# dtoken = json.loads(base64.urlsafe_b64decode(token))
tokenObj = TokenV3.deserialize(token)
# tokenObj = TokenV2.parse_obj(dtoken)
assert len(tokenObj.token), Exception("no proofs in token")
assert len(tokenObj.token[0].proofs), Exception("no proofs in token")
return tokenObj
async def receive(
wallet: Wallet,
tokenObj: TokenV3,
lock: str,
):
# await wallet.load_mint()
# check for P2SH locks
if lock:
# load the script and signature of this address from the database
assert len(lock.split("P2SH:")) == 2, Exception(
"lock has wrong format. Expected P2SH:<address>."
)
address_split = lock.split("P2SH:")[1]
p2shscripts = await get_unused_locks(address_split, db=wallet.db)
assert len(p2shscripts) == 1, Exception("lock not found.")
script, signature = p2shscripts[0].script, p2shscripts[0].signature
else:
script, signature = None, None
includes_mint_info: bool = any([t.mint for t in tokenObj.token])
if includes_mint_info:
# redeem tokens with new wallet instances
await redeem_TokenV3_multimint(
wallet,
tokenObj,
script,
signature,
)
else:
# no mint information present, we extract the proofs and use wallet's default mint
proofs = [p for t in tokenObj.token for p in t.proofs]
# first we load the mint URL from the DB
keyset_in_token = proofs[0].id
assert keyset_in_token
# we get the keyset from the db
mint_keysets = await get_keyset(id=keyset_in_token, db=wallet.db)
assert mint_keysets, Exception("we don't know this keyset")
assert mint_keysets.mint_url, Exception("we don't know this mint's URL")
# now we have the URL
mint_wallet = Wallet(
mint_keysets.mint_url,
os.path.join(settings.cashu_dir, wallet.name),
)
await mint_wallet.load_mint(keyset_in_token)
_, _ = await mint_wallet.redeem(proofs, script, signature)
print(f"Received {sum_proofs(proofs)} sats")
# reload main wallet so the balance updates
await wallet.load_proofs()
wallet.status()
return wallet.available_balance
async def send(
wallet: Wallet,
amount: int,
lock: str,
legacy: bool,
):
"""
Prints token to send to stdout.
"""
if lock:
assert len(lock) > 21, Exception(
"Error: lock has to be at least 22 characters long."
)
p2sh = False
if lock and len(lock.split("P2SH:")) == 2:
p2sh = True
await wallet.load_mint()
await wallet.load_proofs()
_, send_proofs = await wallet.split_to_send(
wallet.proofs, amount, lock, set_reserved=True
)
token = await wallet.serialize_proofs(
send_proofs,
include_mints=True,
)
print(token)
if legacy:
print("")
print("Old token format:")
print("")
token = await wallet.serialize_proofs(
send_proofs,
legacy=True,
)
print(token)
wallet.status()
return wallet.available_balance, token