TokenV3 and new Mint startup in tests (#149)

* tokenv3 send and receive
* receive v2 and v1 tokens with tests
This commit is contained in:
calle
2023-03-28 22:35:22 +02:00
committed by GitHub
parent 258de87a9a
commit db27105d17
10 changed files with 402 additions and 168 deletions

View File

@@ -27,14 +27,6 @@ jobs:
run: | run: |
poetry install --with dev poetry install --with dev
shell: bash shell: bash
- name: Run mint
env:
LIGHTNING: False
MINT_PRIVATE_KEY: "testingkey"
MINT_LISTEN_HOST: 0.0.0.0
MINT_LISTEN_PORT: 3337
run: |
nohup poetry run mint &
- name: Run tests - name: Run tests
env: env:
LIGHTNING: False LIGHTNING: False

View File

@@ -331,3 +331,29 @@ class TokenV2(BaseModel):
) )
else: else:
return dict(proofs=[p.to_dict() for p in self.proofs]) return dict(proofs=[p.to_dict() for p in self.proofs])
class TokenV3Token(BaseModel):
mint: Optional[str] = None
proofs: List[Proof]
def to_dict(self):
return_dict = dict(proofs=[p.to_dict() for p in self.proofs])
if self.mint:
return_dict.update(dict(mint=self.mint)) # type: ignore
return return_dict
class TokenV3(BaseModel):
"""
A Cashu token that includes proofs and their respective mints. Can include proofs from multiple different mints and keysets.
"""
token: List[TokenV3Token] = []
memo: Optional[str] = None
def to_dict(self):
return_dict = dict(token=[t.to_dict() for t in self.token])
if self.memo:
return_dict.update(dict(memo=self.memo)) # type: ignore
return return_dict

View File

@@ -23,7 +23,7 @@ lightning_backend = getattr(wallets_module, settings.mint_lightning_backend)()
ledger = Ledger( ledger = Ledger(
db=Database("mint", settings.mint_database), db=Database("mint", settings.mint_database),
seed=settings.mint_private_key, seed=settings.mint_private_key,
derivation_path="0/0/0/0", derivation_path="0/0/0/1",
lightning=lightning_backend, lightning=lightning_backend,
) )

View File

@@ -18,7 +18,7 @@ import click
from click import Context from click import Context
from loguru import logger from loguru import logger
from cashu.core.base import Proof, TokenV2 from cashu.core.base import Proof, TokenV1, TokenV2
from cashu.core.helpers import sum_proofs from cashu.core.helpers import sum_proofs
from cashu.core.migrations import migrate_databases from cashu.core.migrations import migrate_databases
from cashu.core.settings import settings from cashu.core.settings import settings
@@ -26,6 +26,7 @@ from cashu.nostr.nostr.client.client import NostrClient
from cashu.tor.tor import TorProxy from cashu.tor.tor import TorProxy
from cashu.wallet import migrations from cashu.wallet import migrations
from cashu.wallet.crud import ( from cashu.wallet.crud import (
get_keyset,
get_lightning_invoices, get_lightning_invoices,
get_reserved_proofs, get_reserved_proofs,
get_unused_locks, get_unused_locks,
@@ -35,10 +36,9 @@ from cashu.wallet.wallet import Wallet as Wallet
from .cli_helpers import ( from .cli_helpers import (
get_mint_wallet, get_mint_wallet,
print_mint_balances, print_mint_balances,
proofs_to_serialized_tokenv2, redeem_TokenV3_multimint,
redeem_multimint, serialize_TokenV1_to_TokenV3,
token_from_lnbits_link, serialize_TokenV2_to_TokenV3,
verify_mints,
) )
from .nostr import receive_nostr, send_nostr from .nostr import receive_nostr, send_nostr
@@ -243,9 +243,7 @@ async def send(ctx: Context, amount: int, lock: str, legacy: bool):
if legacy: if legacy:
print("") print("")
print( print("Old token format:")
"Legacy token without mint information for older clients. This token can only be be received by wallets who use the mint the token is issued from:"
)
print("") print("")
token = await wallet.serialize_proofs( token = await wallet.serialize_proofs(
send_proofs, send_proofs,
@@ -306,7 +304,7 @@ async def send_command(
async def receive(ctx: Context, token: str, lock: str): async def receive(ctx: Context, token: str, lock: str):
wallet: Wallet = ctx.obj["WALLET"] wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint() # await wallet.load_mint()
# check for P2SH locks # check for P2SH locks
if lock: if lock:
@@ -325,60 +323,63 @@ async def receive(ctx: Context, token: str, lock: str):
# ----- backwards compatibility ----- # ----- backwards compatibility -----
# we support old tokens (< 0.7) without mint information and (W3siaWQ...) # V2Tokens (0.7-0.11.0) (eyJwcm9...)
# new tokens (>= 0.7) with multiple mint support (eyJ0b2...) if token.startswith("eyJwcm9"):
try: try:
# backwards compatibility: tokens without mint information tokenv2 = TokenV2.parse_obj(json.loads(base64.urlsafe_b64decode(token)))
# supports tokens of the form W3siaWQiOiJH token = await serialize_TokenV2_to_TokenV3(wallet, tokenv2)
# if it's an lnbits https:// link with a token as an argument, speacial treatment
token, url = token_from_lnbits_link(token)
# assume W3siaWQiOiJH.. token
# next line trows an error if the desirialization with the old format doesn't
# work and we can assume it's the new format
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(token))]
# we take the proofs parsed from the old format token and produce a new format token with it
token = await proofs_to_serialized_tokenv2(wallet, proofs, url)
except: except:
pass 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(wallet, tokenv1)
print(token)
except:
pass
# ----- receive token ----- # ----- receive token -----
# deserialize token # deserialize token
dtoken = json.loads(base64.urlsafe_b64decode(token)) # dtoken = json.loads(base64.urlsafe_b64decode(token))
tokenObj = wallet._deserialize_token_V3(token)
# backwards compatibility wallet to wallet < 0.8.0: V2 tokens renamed "tokens" field to "proofs" # tokenObj = TokenV2.parse_obj(dtoken)
if "tokens" in dtoken: assert len(tokenObj.token), Exception("no proofs in token")
dtoken["proofs"] = dtoken.pop("tokens") assert len(tokenObj.token[0].proofs), Exception("no proofs in token")
includes_mint_info: bool = any([t.mint for t in tokenObj.token])
# backwards compatibility wallet to wallet < 0.8.3: V2 tokens got rid of the "MINT_NAME" key in "mints" and renamed "ks" to "ids"
if "mints" in dtoken and isinstance(dtoken["mints"], dict):
dtoken["mints"] = list(dtoken["mints"].values())
for m in dtoken["mints"]:
m["ids"] = m.pop("ks")
tokenObj = TokenV2.parse_obj(dtoken)
assert len(tokenObj.proofs), Exception("no proofs in token")
includes_mint_info: bool = tokenObj.mints is not None and len(tokenObj.mints) > 0
# if there is a `mints` field in the token # if there is a `mints` field in the token
# we check whether the token has mints that we don't know yet # we check whether the token has mints that we don't know yet
# and ask the user if they want to trust the new mitns # and ask the user if they want to trust the new mitns
if includes_mint_info: if includes_mint_info:
# we ask the user to confirm any new mints the tokens may include # we ask the user to confirm any new mints the tokens may include
await verify_mints(ctx, tokenObj) # await verify_mints(ctx, tokenObj)
# redeem tokens with new wallet instances # redeem tokens with new wallet instances
await redeem_multimint(ctx, tokenObj, script, signature) await redeem_TokenV3_multimint(ctx, tokenObj, script, signature)
# reload main wallet so the balance updates
await wallet.load_proofs()
else: else:
# no mint information present, we extract the proofs and use wallet's default mint # no mint information present, we extract the proofs and use wallet's default mint
proofs = [Proof(**p) for p in dtoken["proofs"]]
_, _ = await wallet.redeem(proofs, script, signature) 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, ctx.obj["WALLET_NAME"]),
)
await mint_wallet.load_mint(keyset_in_token)
_, _ = await mint_wallet.redeem(proofs, script, signature)
print(f"Received {sum_proofs(proofs)} sats") print(f"Received {sum_proofs(proofs)} sats")
# reload main wallet so the balance updates
await wallet.load_proofs()
wallet.status() wallet.status()
@@ -413,7 +414,7 @@ async def receive_cli(
elif all: elif all:
reserved_proofs = await get_reserved_proofs(wallet.db) reserved_proofs = await get_reserved_proofs(wallet.db)
if len(reserved_proofs): if len(reserved_proofs):
for (key, value) in groupby(reserved_proofs, key=itemgetter("send_id")): for (key, value) in groupby(reserved_proofs, key=itemgetter("send_id")): # type: ignore
proofs = list(value) proofs = list(value)
token = await wallet.serialize_proofs(proofs) token = await wallet.serialize_proofs(proofs)
await receive(ctx, token, lock) await receive(ctx, token, lock)

View File

@@ -6,14 +6,49 @@ import click
from click import Context from click import Context
from loguru import logger from loguru import logger
from cashu.core.base import Proof, TokenV2, TokenV2Mint, WalletKeyset from cashu.core.base import (
Proof,
TokenV1,
TokenV2,
TokenV2Mint,
TokenV3,
TokenV3Token,
WalletKeyset,
)
from cashu.core.helpers import sum_proofs from cashu.core.helpers import sum_proofs
from cashu.core.settings import settings from cashu.core.settings import settings
from cashu.wallet.crud import get_keyset from cashu.wallet.crud import get_keyset
from cashu.wallet.wallet import Wallet as Wallet from cashu.wallet.wallet import Wallet as Wallet
async def verify_mints(ctx: Context, token: TokenV2): async def verify_mint(mint_wallet: Wallet, url: str):
"""A helper function that asks the user if they trust the mint if the user
has not encountered the mint before (there is no entry in the database).
Throws an Exception if the user chooses to not trust the mint.
"""
logger.debug(f"Verifying mint {url}")
# dummy Wallet to check the database later
# mint_wallet = Wallet(url, os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"]))
# we check the db whether we know this mint already and ask the user if not
mint_keysets = await get_keyset(mint_url=url, db=mint_wallet.db)
if mint_keysets is None:
# we encountered a new mint and ask for a user confirmation
print("")
print("Warning: Tokens are from a mint you don't know yet.")
print("\n")
print(f"Mint URL: {url}")
print("\n")
click.confirm(
f"Do you trust this mint and want to receive the tokens?",
abort=True,
default=True,
)
else:
logger.debug(f"We know keyset {mint_keysets.id} already")
async def verify_mints_tokenv2(ctx: Context, token: TokenV2):
""" """
A helper function that iterates through all mints in the token and if it has A helper function that iterates through all mints in the token and if it has
not been encountered before, asks the user to confirm. not been encountered before, asks the user to confirm.
@@ -64,7 +99,7 @@ async def verify_mints(ctx: Context, token: TokenV2):
assert trust_token_mints, Exception("Aborted!") assert trust_token_mints, Exception("Aborted!")
async def redeem_multimint(ctx: Context, token: TokenV2, script, signature): async def redeem_TokenV2_multimint(ctx: Context, token: TokenV2, script, signature):
""" """
Helper function to iterate thruogh a token with multiple mints and redeem them from Helper function to iterate thruogh a token with multiple mints and redeem them from
these mints one keyset at a time. these mints one keyset at a time.
@@ -95,6 +130,30 @@ async def redeem_multimint(ctx: Context, token: TokenV2, script, signature):
print(f"Received {sum_proofs(redeem_proofs)} sats") print(f"Received {sum_proofs(redeem_proofs)} sats")
async def redeem_TokenV3_multimint(ctx: Context, 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("Multimint redeem without URL")
mint_wallet = Wallet(
t.mint, os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"])
)
await verify_mint(mint_wallet, t.mint)
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(keyset_id=keyset)
# 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 print_mint_balances(ctx: Context, wallet, show_mints=False): async def print_mint_balances(ctx: Context, wallet, show_mints=False):
""" """
Helper function that prints the balances for each mint URL that we have tokens from. Helper function that prints the balances for each mint URL that we have tokens from.
@@ -165,49 +224,29 @@ async def get_mint_wallet(ctx: Context):
return mint_wallet return mint_wallet
# LNbits token link parsing async def serialize_TokenV2_to_TokenV3(wallet: Wallet, tokenv2: TokenV2):
# can extract minut URL from LNbits token links like: """Helper function for the CLI to receive legacy TokenV2 tokens.
# https://lnbits.server/cashu/wallet?mint_id=aMintId&recv_token=W3siaWQiOiJHY2... Takes a list of proofs and constructs a *serialized* TokenV3 to be received through
def token_from_lnbits_link(link): the ordinary path.
url, token = "", ""
if len(link.split("&recv_token=")) == 2:
# extract URL params
params = urllib.parse.parse_qs(link.split("?")[1])
# extract URL
if "mint_id" in params:
url = (
link.split("?")[0].split("/wallet")[0]
+ "/api/v1/"
+ params["mint_id"][0]
)
# extract token
token = params["recv_token"][0]
return token, url
else:
return link, ""
Returns:
async def proofs_to_serialized_tokenv2(wallet, proofs: List[Proof], url: str): TokenV3: TokenV3
""" """
Ingests list of proofs and produces a serialized TokenV2 tokenv3 = TokenV3(token=[TokenV3Token(proofs=tokenv2.proofs)])
""" if tokenv2.mints:
# and add url and keyset id to token tokenv3.token[0].mint = tokenv2.mints[0].url
token: TokenV2 = await wallet._make_token(proofs, include_mints=False) token_serialized = await wallet._serialize_token_V3(tokenv3)
token.mints = [] return token_serialized
# get keysets of proofs
keysets = list(set([p.id for p in proofs if p.id is not None])) async def serialize_TokenV1_to_TokenV3(wallet: Wallet, tokenv1: TokenV1):
"""Helper function for the CLI to receive legacy TokenV1 tokens.
# check whether we know the mint urls for these proofs Takes a list of proofs and constructs a *serialized* TokenV3 to be received through
for k in keysets: the ordinary path.
ks = await get_keyset(id=k, db=wallet.db)
url = ks.mint_url if ks and ks.mint_url else "" Returns:
TokenV3: TokenV3
url = url or ( """
input(f"Enter mint URL (press enter for default {settings.mint_url}): ") tokenv3 = TokenV3(token=[TokenV3Token(proofs=tokenv1.__root__)])
or settings.mint_url token_serialized = await wallet._serialize_token_V3(tokenv3)
)
token.mints.append(TokenV2Mint(url=url, ids=keysets))
token_serialized = await wallet._serialize_token_base64(token)
return token_serialized return token_serialized

View File

@@ -31,6 +31,8 @@ from cashu.core.base import (
Proof, Proof,
TokenV2, TokenV2,
TokenV2Mint, TokenV2Mint,
TokenV3,
TokenV3Token,
WalletKeyset, WalletKeyset,
) )
from cashu.core.bolt11 import Invoice as InvoiceBolt11 from cashu.core.bolt11 import Invoice as InvoiceBolt11
@@ -630,7 +632,105 @@ class Wallet(LedgerAPI):
ret[keyset.mint_url].extend([p for p in proofs if p.id == id]) ret[keyset.mint_url].extend([p for p in proofs if p.id == id])
return ret return ret
def _get_proofs_keysets(self, proofs: List[Proof]):
"""Extracts all keyset ids from a list of proofs.
Args:
proofs (List[Proof]): List of proofs to get the keyset id's of
"""
keysets: List[str] = [proof.id for proof in proofs if proof.id]
return keysets
async def _get_keyset_urls(self, keysets: List[str]):
"""Retrieves the mint URLs for a list of keyset id's from the wallet's database.
Returns a dictionary from URL to keyset ID
Args:
keysets (List[str]): List of keysets.
"""
mint_urls: Dict[str, List[str]] = {}
for ks in set(keysets):
keyset_db = await get_keyset(id=ks, db=self.db)
if keyset_db and keyset_db.mint_url:
mint_urls[keyset_db.mint_url] = (
mint_urls[keyset_db.mint_url] + [ks]
if mint_urls.get(keyset_db.mint_url)
else [ks]
)
return mint_urls
async def _make_token(self, proofs: List[Proof], include_mints=True): async def _make_token(self, proofs: List[Proof], include_mints=True):
"""
Takes list of proofs and produces a TokenV3 by looking up
the mint URLs by the keyset id from the database.
"""
token = TokenV3()
if include_mints:
# we create a map from mint url to keyset id and then group
# all proofs with their mint url to build a tokenv3
# extract all keysets from proofs
keysets = self._get_proofs_keysets(proofs)
# get all mint URLs for all unique keysets from db
mint_urls = await self._get_keyset_urls(keysets)
# append all url-grouped proofs to token
for url, ids in mint_urls.items():
mint_proofs = [p for p in proofs if p.id in ids]
token.token.append(TokenV3Token(mint=url, proofs=mint_proofs))
else:
token_proofs = TokenV3Token(proofs=proofs)
token.token.append(token_proofs)
return token
async def _serialize_token_V3(self, token: TokenV3):
"""
Takes a TokenV3 and serializes it as "cashuA<json_urlsafe_base64>.
"""
prefix = "cashuA"
tokenv3_serialized = prefix
# encode the token as a base64 string
tokenv3_serialized += base64.urlsafe_b64encode(
json.dumps(token.to_dict()).encode()
).decode()
return tokenv3_serialized
def _deserialize_token_V3(self, tokenv3_serialized: str) -> TokenV3:
"""
Takes a TokenV3 and serializes it as "cashuA<json_urlsafe_base64>.
"""
prefix = "cashuA"
assert tokenv3_serialized.startswith(prefix), Exception(
f"Token prefix not valid. Expected {prefix}."
)
token_base64 = tokenv3_serialized[len(prefix) :]
token = json.loads(base64.urlsafe_b64decode(token_base64))
return TokenV3.parse_obj(token)
async def serialize_proofs(
self, proofs: List[Proof], include_mints=True, legacy=False
):
"""
Produces sharable token with proofs and mint information.
"""
if legacy:
# V2 tokens
token = await self._make_token_v2(proofs, include_mints)
return await self._serialize_token_base64_tokenv2(token)
# # deprecated code for V1 tokens
# proofs_serialized = [p.to_dict() for p in proofs]
# return base64.urlsafe_b64encode(
# json.dumps(proofs_serialized).encode()
# ).decode()
# V3 tokens
token = await self._make_token(proofs, include_mints)
return await self._serialize_token_V3(token)
async def _make_token_v2(self, proofs: List[Proof], include_mints=True):
""" """
Takes list of proofs and produces a TokenV2 by looking up Takes list of proofs and produces a TokenV2 by looking up
the keyset id and mint URLs from the database. the keyset id and mint URLs from the database.
@@ -642,31 +742,27 @@ class Wallet(LedgerAPI):
# dummy object to hold information about the mint # dummy object to hold information about the mint
mints: Dict[str, TokenV2Mint] = {} mints: Dict[str, TokenV2Mint] = {}
# dummy object to hold all keyset id's we need to fetch from the db later # dummy object to hold all keyset id's we need to fetch from the db later
keysets: List[str] = [] keysets: List[str] = [proof.id for proof in proofs if proof.id]
# iterate through all proofs and remember their keyset ids for the next step
for proof in proofs:
if proof.id:
keysets.append(proof.id)
# iterate through unique keyset ids # iterate through unique keyset ids
for id in set(keysets): for id in set(keysets):
# load the keyset from the db # load the keyset from the db
keyset = await get_keyset(id=id, db=self.db) keyset_db = await get_keyset(id=id, db=self.db)
if keyset and keyset.mint_url and keyset.id: if keyset_db and keyset_db.mint_url and keyset_db.id:
# we group all mints according to URL # we group all mints according to URL
if keyset.mint_url not in mints: if keyset_db.mint_url not in mints:
mints[keyset.mint_url] = TokenV2Mint( mints[keyset_db.mint_url] = TokenV2Mint(
url=keyset.mint_url, url=keyset_db.mint_url,
ids=[keyset.id], ids=[keyset_db.id],
) )
else: else:
# if a mint URL has multiple keysets, append to the already existing list # if a mint URL has multiple keysets, append to the already existing list
mints[keyset.mint_url].ids.append(keyset.id) mints[keyset_db.mint_url].ids.append(keyset_db.id)
if len(mints) > 0: if len(mints) > 0:
# add mints grouped by url to the token # add mints grouped by url to the token
token.mints = list(mints.values()) token.mints = list(mints.values())
return token return token
async def _serialize_token_base64(self, token: TokenV2): async def _serialize_token_base64_tokenv2(self, token: TokenV2):
""" """
Takes a TokenV2 and serializes it in urlsafe_base64. Takes a TokenV2 and serializes it in urlsafe_base64.
""" """
@@ -676,22 +772,6 @@ class Wallet(LedgerAPI):
).decode() ).decode()
return token_base64 return token_base64
async def serialize_proofs(
self, proofs: List[Proof], include_mints=True, legacy=False
):
"""
Produces sharable token with proofs and mint information.
"""
if legacy:
proofs_serialized = [p.to_dict() for p in proofs]
return base64.urlsafe_b64encode(
json.dumps(proofs_serialized).encode()
).decode()
token = await self._make_token(proofs, include_mints)
return await self._serialize_token_base64(token)
async def _select_proofs_to_send(self, proofs: List[Proof], amount_to_send: int): async def _select_proofs_to_send(self, proofs: List[Proof], amount_to_send: int):
""" """
Selects proofs that can be used with the current mint. Selects proofs that can be used with the current mint.

View File

View File

@@ -1,5 +1,7 @@
import multiprocessing import multiprocessing
import shutil
import time import time
from pathlib import Path
import pytest import pytest
import pytest_asyncio import pytest_asyncio
@@ -7,6 +9,7 @@ import uvicorn
from uvicorn import Config, Server from uvicorn import Config, Server
from cashu.core.migrations import migrate_databases from cashu.core.migrations import migrate_databases
from cashu.core.settings import settings
from cashu.wallet import migrations from cashu.wallet import migrations
from cashu.wallet.wallet import Wallet from cashu.wallet.wallet import Wallet
@@ -23,15 +26,32 @@ class UvicornServer(multiprocessing.Process):
self.terminate() self.terminate()
def run(self, *args, **kwargs): def run(self, *args, **kwargs):
settings.lightning = False
settings.mint_lightning_backend = "FakeWallet"
settings.mint_listen_port = 3337
settings.mint_database = "data/test_mint"
settings.mint_private_key = "privatekeyofthemint"
dirpath = Path(settings.mint_database)
if dirpath.exists() and dirpath.is_dir():
shutil.rmtree(dirpath)
dirpath = Path("data/test_wallet")
if dirpath.exists() and dirpath.is_dir():
shutil.rmtree(dirpath)
self.server.run() self.server.run()
@pytest.fixture(autouse=True, scope="session") @pytest.fixture(autouse=True, scope="session")
def mint(): def mint():
settings.mint_listen_port = 3337
settings.port = 3337
settings.mint_url = "http://localhost:3337"
settings.port = settings.mint_listen_port
config = uvicorn.Config( config = uvicorn.Config(
"cashu.mint.app:app", "cashu.mint.app:app",
port=3337, port=settings.mint_listen_port,
host="127.0.0.1", host="127.0.0.1",
) )

View File

@@ -10,23 +10,35 @@ from cashu.wallet.cli.cli import cli
from cashu.wallet.wallet import Wallet from cashu.wallet.wallet import Wallet
from tests.conftest import SERVER_ENDPOINT, mint from tests.conftest import SERVER_ENDPOINT, mint
cli_prefix = ["--wallet", "test_wallet", "--host", SERVER_ENDPOINT]
@pytest.fixture(autouse=True, scope="session")
def cli_prefix():
yield ["--wallet", "test_wallet", "--host", settings.mint_url]
@pytest.fixture(scope="session")
def wallet():
wallet = Wallet(settings.mint_host, "data/test_wallet", "wallet")
asyncio.run(migrate_databases(wallet.db, migrations))
asyncio.run(wallet.load_proofs())
yield wallet
async def init_wallet(): async def init_wallet():
wallet = Wallet(SERVER_ENDPOINT, "data/test_wallet", "wallet") wallet = Wallet(settings.mint_host, "data/test_wallet", "wallet")
await migrate_databases(wallet.db, migrations) await migrate_databases(wallet.db, migrations)
await wallet.load_proofs() await wallet.load_proofs()
return wallet return wallet
@pytest.mark.asyncio @pytest.mark.asyncio
def test_info(): def test_info(cli_prefix):
runner = CliRunner() runner = CliRunner()
result = runner.invoke( result = runner.invoke(
cli, cli,
[*cli_prefix, "info"], [*cli_prefix, "info"],
) )
assert result.exception is None
print("INFO") print("INFO")
print(result.output) print(result.output)
result.output.startswith(f"Version: {settings.version}") result.output.startswith(f"Version: {settings.version}")
@@ -34,26 +46,43 @@ def test_info():
@pytest.mark.asyncio @pytest.mark.asyncio
def test_balance(): def test_balance(cli_prefix):
runner = CliRunner() runner = CliRunner()
result = runner.invoke( result = runner.invoke(
cli, cli,
[*cli_prefix, "balance"], [*cli_prefix, "balance"],
) )
assert result.exception is None
print("------ BALANCE ------") print("------ BALANCE ------")
print(result.output) print(result.output)
wallet = asyncio.run(init_wallet()) w = asyncio.run(init_wallet())
assert f"Balance: {wallet.available_balance} sat" in result.output assert f"Balance: {w.available_balance} sat" in result.output
assert result.exit_code == 0 assert result.exit_code == 0
@pytest.mark.asyncio @pytest.mark.asyncio
def test_wallets(): def test_invoice(mint, cli_prefix):
runner = CliRunner()
result = runner.invoke(
cli,
[*cli_prefix, "invoice", "1000"],
)
assert result.exception is None
print("INVOICE")
print(result.output)
# wallet = asyncio.run(init_wallet())
# assert f"Balance: {wallet.available_balance} sat" in result.output
assert result.exit_code == 0
@pytest.mark.asyncio
def test_wallets(cli_prefix):
runner = CliRunner() runner = CliRunner()
result = runner.invoke( result = runner.invoke(
cli, cli,
[*cli_prefix, "wallets"], [*cli_prefix, "wallets"],
) )
assert result.exception is None
print("WALLETS") print("WALLETS")
# on github this is empty # on github this is empty
if len(result.output): if len(result.output):
@@ -62,62 +91,109 @@ def test_wallets():
@pytest.mark.asyncio @pytest.mark.asyncio
def test_invoice(): def test_send(mint, cli_prefix):
runner = CliRunner()
result = runner.invoke(
cli,
[*cli_prefix, "invoice", "1000"],
)
print("INVOICE")
print(result.output)
wallet = asyncio.run(init_wallet())
assert f"Balance: {wallet.available_balance} sat" in result.output
assert result.exit_code == 0
@pytest.mark.asyncio
def test_send(mint):
runner = CliRunner() runner = CliRunner()
result = runner.invoke( result = runner.invoke(
cli, cli,
[*cli_prefix, "send", "10"], [*cli_prefix, "send", "10"],
) )
assert result.exception is None
print("SEND") print("SEND")
print(result.output) print(result.output)
assert "cashuA" in result.output, "output does not have a token"
token = [l for l in result.output.split("\n") if l.startswith("ey")][0]
print("TOKEN")
print(token)
@pytest.mark.asyncio @pytest.mark.asyncio
def test_receive_tokenv2(mint): def test_receive_tokenv3(mint, cli_prefix):
runner = CliRunner() runner = CliRunner()
token = "eyJwcm9vZnMiOiBbeyJpZCI6ICJEU0FsOW52dnlmdmEiLCAiYW1vdW50IjogMiwgInNlY3JldCI6ICJ3MEs4dE9OcFJOdVFvUzQ1Y2g1NkJ3IiwgIkMiOiAiMDI3NzcxODY4NWQ0MDgxNmQ0MTdmZGE1NWUzN2YxOTFkN2E5ODA0N2QyYWE2YzFlNDRhMWZjNTM1ZmViZDdjZDQ5In0sIHsiaWQiOiAiRFNBbDludnZ5ZnZhIiwgImFtb3VudCI6IDgsICJzZWNyZXQiOiAiX2J4cDVHeG1JQUVaRFB5Sm5qaFUxdyIsICJDIjogIjAzZTY2M2UzOWYyNTZlZTAzOTBiNGFiMThkZDA2OTc0NjRjZjIzYTM4OTc1MDlmZDFlYzQ1MzMxMTRlMTcwMDQ2NCJ9XSwgIm1pbnRzIjogW3sidXJsIjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzNyIsICJpZHMiOiBbIkRTQWw5bnZ2eWZ2YSJdfV19" token = "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogImF5TVViZTk4NVVzeiIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImdQNDlRdl9EZkhxck5zMjVxY1E4a0EiLCAiQyI6ICIwMzZiNjY1MzIxYzBlNGRkYTIwMTI1YTYwOWU4Y2FlMmEzMzRkODRhZDhjZWU4NjY2NTQxYjYyZjk1YjA0Y2FhNmUifSwgeyJpZCI6ICJheU1VYmU5ODVVc3oiLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICJzTWJ4WGtVTlZKVTh0MTd5cVFJMnFBIiwgIkMiOiAiMDM5ZmIzMTQxN2IyNmY2YWUwMjE1NmYxNzgyZWExYTQ4NTAwMzU2OTVlMTUxODZkNmMwM2MxMzI3ZWU3YWQwZjhlIn1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzcifV19"
result = runner.invoke( result = runner.invoke(
cli, cli,
[*cli_prefix, "receive", token], [
*cli_prefix,
"receive",
token,
],
) )
assert result.exception is None
print("RECEIVE") print("RECEIVE")
print(result.output) print(result.output)
@pytest.mark.asyncio @pytest.mark.asyncio
def test_receive_tokenv1(mint): def test_receive_tokenv3_no_mint(mint, cli_prefix):
# this test works only if the previous test succeeds because we simulate the case where the mint URL is not in the token
# therefore, we need to know the mint keyset already and have the mint URL in the db
runner = CliRunner() runner = CliRunner()
token = "3siaWQiOiAiRFNBbDludnZ5ZnZhIiwgImFtb3VudCI6IDIsICJzZWNyZXQiOiAiX3VOV1ZNeDRhQndieWszRDZoLWREZyIsICJDIjogIjAyMmEzMzRmZTIzYTA1OTJhZmM3OTk3OWQyZDJmMmUwOTgxMGNkZTRlNDY5ZGYwYzZhMGE4ZDg0ZmY1MmIxOTZhNyJ9LCB7ImlkIjogIkRTQWw5bnZ2eWZ2YSIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogIk9VUUxnRE90WXhHOXJUMzZKdHFwbWciLCAiQyI6ICIwMzVmMGM2NTNhNTEzMGY4ZmQwNjY5NDg5YzEwMDY3N2Q5NGU0MGFlZjhkYWE0OWZiZDIyZTgzZjhjNThkZjczMTUifV0" token = "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogImF5TVViZTk4NVVzeiIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIkw4XzlBc3d0Rzh1UENmZ29xWnRVRFEiLCAiQyI6ICIwMmE1ZWMzYmY0Nzk2ZTg1NjJhNGRjYjM2YWRkOWYwNDhmZTU3ZGU0ZjEyMjgxMzA3N2FlZjBlM2Y2ZGIwY2U3ZGQifSwgeyJpZCI6ICJheU1VYmU5ODVVc3oiLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICJ2WWJKZXNhS3BMTnNwaXl3cXd3ejFRIiwgIkMiOiAiMDJjNWVkNDc4YjZjOWU0MTExYjhlOGU1MjBlNThhMTVhYzQzMjUwMGM1MTU2ZmFjNDkyN2Q0ODVhNzM3ZTdlYzA4In1dfV19"
result = runner.invoke(
cli,
[
*cli_prefix,
"receive",
token,
],
)
assert result.exception is None
print("RECEIVE")
print(result.output)
# @pytest.mark.asyncio
# def test_receive_tokenv3(mint):
# wallet = asyncio.run(init_wallet())
# runner = CliRunner()
# token = "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjVXRWJoUzJiOXZrTyIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogInpINEM3OXpwZWJYaDIxTDBEWk1qb1EiLCAiQyI6ICIwMmI4ZDZjYzA3NjliMWNiZmQyNzkwN2U2YTQ5YmY2MGMyYzUwYmUwNzhmOGNjMWU1YWE1NTY2NjE1Y2QwOGZmM2YifSwgeyJpZCI6ICI1V0ViaFMyYjl2a08iLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICJSYW1aZEJ4a01ybWtmdXh6SjFIOU9RIiwgIkMiOiAiMDI2ZGU2ZDNjZDlmNDY4MDYzMTJkYTczZDE2YzQ2ZDc3NGNkODlhZTk2NzUwMWI3MzA1MmQwNTVmODZkNmJmMmMwIn1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzcifV19"
# result = runner.invoke(
# cli,
# [*cli_prefix, "receive", token],
# )
# assert result.exception is None
# print("RECEIVE")
# print(result.output)
# @pytest.mark.asyncio
# def test_receive_tokenv3_no_mint(mint):
# runner = CliRunner()
# token = "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjVXRWJoUzJiOXZrTyIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogInpINEM3OXpwZWJYaDIxTDBEWk1qb1EiLCAiQyI6ICIwMmI4ZDZjYzA3NjliMWNiZmQyNzkwN2U2YTQ5YmY2MGMyYzUwYmUwNzhmOGNjMWU1YWE1NTY2NjE1Y2QwOGZmM2YifSwgeyJpZCI6ICI1V0ViaFMyYjl2a08iLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICJSYW1aZEJ4a01ybWtmdXh6SjFIOU9RIiwgIkMiOiAiMDI2ZGU2ZDNjZDlmNDY4MDYzMTJkYTczZDE2YzQ2ZDc3NGNkODlhZTk2NzUwMWI3MzA1MmQwNTVmODZkNmJmMmMwIn1dfV19"
# result = runner.invoke(
# cli,
# [*cli_prefix, "receive", token],
# )
# assert result.exception is None
# print("RECEIVE")
# print(result.output)
@pytest.mark.asyncio
def test_receive_tokenv2(mint, cli_prefix):
runner = CliRunner()
token = "eyJwcm9vZnMiOiBbeyJpZCI6ICJheU1VYmU5ODVVc3oiLCAiYW1vdW50IjogMiwgInNlY3JldCI6ICJ5WWxWR2lmSmJQbGRJZmp5YUxYSnNBIiwgIkMiOiAiMDJlNDE5ZjExNGFlNTFiMzI1MGVkYjE5YTI4NzQ0MjgwMjAwMGE3NTFhZmEwZGZmZDM2N2QxYTI0NTI3NjY2NmIwIn0sIHsiaWQiOiAiYXlNVWJlOTg1VXN6IiwgImFtb3VudCI6IDgsICJzZWNyZXQiOiAiVlZraDZGTW5sUVZ2WlZOR2Z6emUwQSIsICJDIjogIjAyZGMxZDhjZmFiNDA2NGI4MWFhZThiZWEzNTBjNjIzNWM1NDIzOGNiN2E5ZmYxNTJjNjMxMTAwN2FlNDEzZmFlNyJ9XSwgIm1pbnRzIjogW3sidXJsIjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzNyIsICJpZHMiOiBbImF5TVViZTk4NVVzeiJdfV19"
result = runner.invoke( result = runner.invoke(
cli, cli,
[*cli_prefix, "receive", token], [*cli_prefix, "receive", token],
) )
assert result.exception is None
print("RECEIVE")
print(result.output)
@pytest.mark.asyncio
def test_receive_tokenv1(mint, cli_prefix):
runner = CliRunner()
token = "W3siaWQiOiAiYXlNVWJlOTg1VXN6IiwgImFtb3VudCI6IDIsICJzZWNyZXQiOiAicTR6WFdzYl84cGlBRHRQSzB1MFAwdyIsICJDIjogIjAyNDVlYjFmY2E1ODhlYWM0Y2M3OGJkZTJiYmMzOGQwMmY4YTIyZTEyMjcyMjQ2M2RiNDk5ZjA0ZWQ2ZDMzNjZkZCJ9LCB7ImlkIjogImF5TVViZTk4NVVzeiIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogInBsaTNKX0QwNkxQZ3RmaW5EZkFWckEiLCAiQyI6ICIwMmU0MDFlMTBhYjI3ODJlYzQzYjMxZmZmMGMxZjc4N2FlYjgyODViNjkxMTAyMzlmYTJiN2VkNzA2MzdhMTliNzUifV0="
result = runner.invoke(
cli,
[*cli_prefix, "receive", token],
)
assert result.exception is None
print("RECEIVE") print("RECEIVE")
print(result.output) print(result.output)
@pytest.mark.asyncio() @pytest.mark.asyncio()
def test_nostr_send(mint): def test_nostr_send(mint, cli_prefix):
runner = CliRunner() runner = CliRunner()
result = runner.invoke( result = runner.invoke(
cli, cli,
@@ -130,6 +206,6 @@ def test_nostr_send(mint):
"-y", "-y",
], ],
) )
assert result.exception is None
print("NOSTR_SEND") print("NOSTR_SEND")
print(result.output) print(result.output)