mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 18:44:20 +01:00
TokenV3 and new Mint startup in tests (#149)
* tokenv3 send and receive * receive v2 and v1 tokens with tests
This commit is contained in:
8
.github/workflows/tests.yml
vendored
8
.github/workflows/tests.yml
vendored
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
except:
|
||||||
# if it's an lnbits https:// link with a token as an argument, speacial treatment
|
pass
|
||||||
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:
|
|
||||||
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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user