mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 02:24:20 +01:00
* 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
229 lines
7.3 KiB
Python
229 lines
7.3 KiB
Python
import base64
|
|
import json
|
|
import os
|
|
|
|
from loguru import logger
|
|
|
|
from ..core.base import TokenV1, TokenV2, TokenV3, TokenV3Token
|
|
from ..core.db import Database
|
|
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_keysets
|
|
from ..wallet.wallet import Wallet
|
|
|
|
|
|
async def migrate_wallet_db(db: Database):
|
|
await migrate_databases(db, migrations)
|
|
|
|
|
|
async def init_wallet(wallet: Wallet, load_proofs: bool = True):
|
|
"""Performs migrations and loads proofs from db."""
|
|
await wallet._migrate_database()
|
|
await wallet._init_private_key()
|
|
if load_proofs:
|
|
await wallet.load_proofs(reload=True)
|
|
|
|
|
|
async def list_mints(wallet: Wallet):
|
|
await wallet.load_proofs()
|
|
balances = await wallet.balance_per_minturl()
|
|
mints = list(balances.keys())
|
|
if wallet.url not in mints:
|
|
mints.append(wallet.url)
|
|
return mints
|
|
|
|
|
|
async def redeem_TokenV3_multimint(wallet: Wallet, token: TokenV3):
|
|
"""
|
|
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 = await Wallet.with_db(
|
|
t.mint, os.path.join(settings.cashu_dir, wallet.name)
|
|
)
|
|
keyset_ids = mint_wallet._get_proofs_keysets(t.proofs)
|
|
logger.trace(f"Keysets in tokens: {keyset_ids}")
|
|
# loop over all keysets
|
|
for keyset_id in set(keyset_ids):
|
|
await mint_wallet.load_mint(keyset_id)
|
|
mint_wallet.unit = mint_wallet.keysets[keyset_id].unit
|
|
# redeem proofs of this keyset
|
|
redeem_proofs = [p for p in t.proofs if p.id == keyset_id]
|
|
_, _ = await mint_wallet.redeem(redeem_proofs)
|
|
print(f"Received {mint_wallet.unit.str(sum_proofs(redeem_proofs))}")
|
|
|
|
|
|
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
|
|
|
|
|
|
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
|
|
|
|
|
|
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 = serialize_TokenV2_to_TokenV3(tokenv2)
|
|
except Exception:
|
|
pass
|
|
|
|
# V1Tokens (<0.7) (W3siaWQ...)
|
|
if token.startswith("W3siaWQ"):
|
|
try:
|
|
tokenv1 = TokenV1.parse_obj(json.loads(base64.urlsafe_b64decode(token)))
|
|
token = serialize_TokenV1_to_TokenV3(tokenv1)
|
|
except Exception:
|
|
pass
|
|
|
|
if token.startswith("cashu"):
|
|
tokenObj = TokenV3.deserialize(token)
|
|
assert len(tokenObj.token), Exception("no proofs in token")
|
|
assert len(tokenObj.token[0].proofs), Exception("no proofs in token")
|
|
return tokenObj
|
|
|
|
raise Exception("Invalid token")
|
|
|
|
|
|
async def receive(
|
|
wallet: Wallet,
|
|
tokenObj: TokenV3,
|
|
):
|
|
logger.debug(f"receive: {tokenObj}")
|
|
proofs = [p for t in tokenObj.token for p in t.proofs]
|
|
|
|
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,
|
|
)
|
|
else:
|
|
# this is very legacy code, virtually any token should have mint information
|
|
# no mint information present, we extract the proofs and use wallet's default mint
|
|
# 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_keysets(id=keyset_in_token, db=wallet.db)
|
|
assert mint_keysets, Exception(f"we don't know this keyset: {keyset_in_token}")
|
|
mint_keyset = mint_keysets[0]
|
|
assert mint_keyset.mint_url, Exception("we don't know this mint's URL")
|
|
# now we have the URL
|
|
mint_wallet = await Wallet.with_db(
|
|
mint_keyset.mint_url,
|
|
os.path.join(settings.cashu_dir, wallet.name),
|
|
)
|
|
await mint_wallet.load_mint(keyset_in_token)
|
|
_, _ = await mint_wallet.redeem(proofs)
|
|
print(f"Received {mint_wallet.unit.str(sum_proofs(proofs))}")
|
|
|
|
# reload main wallet so the balance updates
|
|
await wallet.load_proofs(reload=True)
|
|
return wallet.available_balance
|
|
|
|
|
|
async def send(
|
|
wallet: Wallet,
|
|
*,
|
|
amount: int,
|
|
lock: str,
|
|
legacy: bool,
|
|
split: bool = True,
|
|
include_dleq: bool = False,
|
|
):
|
|
"""
|
|
Prints token to send to stdout.
|
|
"""
|
|
secret_lock = None
|
|
if lock:
|
|
assert len(lock) > 21, Exception(
|
|
"Error: lock has to be at least 22 characters long."
|
|
)
|
|
if not lock.startswith("P2PK:"):
|
|
raise Exception("Error: lock has to start with P2PK:")
|
|
# we add a time lock to the P2PK lock by appending the current unix time + 14 days
|
|
else:
|
|
logger.debug(f"Locking token to: {lock}")
|
|
logger.debug(
|
|
f"Adding a time lock of {settings.locktime_delta_seconds} seconds."
|
|
)
|
|
secret_lock = await wallet.create_p2pk_lock(
|
|
lock.split(":")[1],
|
|
locktime_seconds=settings.locktime_delta_seconds,
|
|
sig_all=True,
|
|
n_sigs=1,
|
|
)
|
|
|
|
await wallet.load_proofs()
|
|
if split:
|
|
await wallet.load_mint()
|
|
_, send_proofs = await wallet.split_to_send(
|
|
wallet.proofs, amount, secret_lock, set_reserved=True
|
|
)
|
|
else:
|
|
# get a proof with specific amount
|
|
send_proofs = []
|
|
for p in wallet.proofs:
|
|
if not p.reserved and p.amount == amount:
|
|
send_proofs = [p]
|
|
break
|
|
assert send_proofs, Exception(
|
|
"No proof with this amount found. Available amounts:"
|
|
f" {set([p.amount for p in wallet.proofs])}"
|
|
)
|
|
|
|
token = await wallet.serialize_proofs(
|
|
send_proofs,
|
|
include_mints=True,
|
|
include_dleq=include_dleq,
|
|
)
|
|
print(token)
|
|
await wallet.set_reserved(send_proofs, reserved=True)
|
|
if legacy:
|
|
print("")
|
|
print("Old token format:")
|
|
print("")
|
|
token = await wallet.serialize_proofs(
|
|
send_proofs,
|
|
legacy=True,
|
|
include_dleq=include_dleq,
|
|
)
|
|
print(token)
|
|
|
|
return wallet.available_balance, token
|