diff --git a/.env.example b/.env.example index 910a718..50c86d2 100644 --- a/.env.example +++ b/.env.example @@ -24,8 +24,8 @@ MINT_PRIVATE_KEY=supersecretprivatekey # Supported: LNbitsWallet, FakeWallet MINT_LIGHTNING_BACKEND=LNbitsWallet -MINT_SERVER_HOST=127.0.0.1 -MINT_SERVER_PORT=3338 +MINT_LISTEN_HOST=127.0.0.1 +MINT_LISTEN_PORT=3338 LIGHTNING=TRUE # fee to reserve in percent of the amount diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6cd1c63..2ea696c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,8 +31,8 @@ jobs: env: LIGHTNING: False MINT_PRIVATE_KEY: "testingkey" - MINT_SERVER_HOST: 0.0.0.0 - MINT_SERVER_PORT: 3337 + MINT_LISTEN_HOST: 0.0.0.0 + MINT_LISTEN_PORT: 3337 run: | nohup poetry run mint & - name: Run tests diff --git a/cashu/core/crypto.py b/cashu/core/crypto.py index a3e0500..e19f58a 100644 --- a/cashu/core/crypto.py +++ b/cashu/core/crypto.py @@ -3,7 +3,7 @@ import hashlib from typing import Dict, List from cashu.core.secp import PrivateKey, PublicKey -from cashu.core.settings import MAX_ORDER +from cashu.core.settings import settings # entropy = bytes([random.getrandbits(8) for i in range(16)]) # mnemonic = bip39.mnemonic_from_bytes(entropy) @@ -27,12 +27,14 @@ def derive_keys(master_key: str, derivation_path: str = ""): .encode("utf-8")[:32], raw=True, ) - for i in range(MAX_ORDER) + for i in range(settings.max_order) } def derive_pubkeys(keys: Dict[int, PrivateKey]): - return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} + return { + amt: keys[amt].pubkey for amt in [2**i for i in range(settings.max_order)] + } def derive_keyset_id(keys: Dict[int, PublicKey]): diff --git a/cashu/core/helpers.py b/cashu/core/helpers.py index 9b38c71..d8079cf 100644 --- a/cashu/core/helpers.py +++ b/cashu/core/helpers.py @@ -3,7 +3,7 @@ from functools import partial, wraps from typing import List from cashu.core.base import Proof -from cashu.core.settings import LIGHTNING_FEE_PERCENT, LIGHTNING_RESERVE_FEE_MIN +from cashu.core.settings import settings def sum_proofs(proofs: List[Proof]): @@ -39,5 +39,6 @@ def fee_reserve(amount_msat: int, internal=False) -> int: if internal: return 0 return max( - int(LIGHTNING_RESERVE_FEE_MIN), int(amount_msat * LIGHTNING_FEE_PERCENT / 100.0) + int(settings.lightning_reserve_fee_min), + int(amount_msat * settings.lightning_fee_percent / 100.0), ) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 523fa87..5b6a73a 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -1,72 +1,112 @@ import os import sys from pathlib import Path -from typing import Union +from typing import List, Union from environs import Env # type: ignore +from pydantic import BaseSettings, Extra, Field, validator env = Env() -# env file: default to current dir, else home dir -ENV_FILE = os.path.join(os.getcwd(), ".env") -if not os.path.isfile(ENV_FILE): - ENV_FILE = os.path.join(str(Path.home()), ".cashu", ".env") -if os.path.isfile(ENV_FILE): - env.read_env(ENV_FILE) -else: - ENV_FILE = "" - env.read_env(recurse=False) - -DEBUG = env.bool("DEBUG", default=False) -if not DEBUG: - sys.tracebacklimit = 0 - -CASHU_DIR = env.str("CASHU_DIR", default=os.path.join(str(Path.home()), ".cashu")) -CASHU_DIR = CASHU_DIR.replace("~", str(Path.home())) -assert len(CASHU_DIR), "CASHU_DIR not defined" - -TOR = env.bool("TOR", default=True) - -SOCKS_HOST = env.str("SOCKS_HOST", default=None) -SOCKS_PORT = env.int("SOCKS_PORT", default=9050) - -LIGHTNING = env.bool("LIGHTNING", default=True) -LIGHTNING_FEE_PERCENT = env.float("LIGHTNING_FEE_PERCENT", default=1.0) -assert LIGHTNING_FEE_PERCENT >= 0, "LIGHTNING_FEE_PERCENT must be at least 0" -LIGHTNING_RESERVE_FEE_MIN = env.float("LIGHTNING_RESERVE_FEE_MIN", default=2000) - -MINT_PRIVATE_KEY = env.str("MINT_PRIVATE_KEY", default=None) - -MINT_SERVER_HOST = env.str("MINT_SERVER_HOST", default="127.0.0.1") -MINT_SERVER_PORT = env.int("MINT_SERVER_PORT", default=3338) - -MINT_URL = env.str("MINT_URL", default=None) -MINT_HOST = env.str("MINT_HOST", default="8333.space") -MINT_PORT = env.int("MINT_PORT", default=3338) - -MINT_LIGHTNING_BACKEND = env.str("MINT_LIGHTNING_BACKEND", default="FakeWallet") -MINT_DATABASE = env.str("MINT_DATABASE", default="data/mint") - -if not MINT_URL: - if MINT_HOST in ["localhost", "127.0.0.1"]: - MINT_URL = f"http://{MINT_HOST}:{MINT_PORT}" - else: - MINT_URL = f"https://{MINT_HOST}:{MINT_PORT}" - -LNBITS_ENDPOINT = env.str("LNBITS_ENDPOINT", default=None) -LNBITS_KEY = env.str("LNBITS_KEY", default=None) - -NOSTR_PRIVATE_KEY = env.str("NOSTR_PRIVATE_KEY", default=None) -NOSTR_RELAYS = env.list( - "NOSTR_RELAYS", - default=[ - "wss://nostr-pub.wellorder.net", - "wss://relay.damus.io", - "wss://nostr.zebedee.cloud", - "wss://relay.snort.social", - "wss://nostr.fmt.wiz.biz", - ], -) - -MAX_ORDER = 64 VERSION = "0.9.4" + + +def find_env_file(): + # env file: default to current dir, else home dir + ENV_FILE = os.path.join(os.getcwd(), ".env") + if not os.path.isfile(ENV_FILE): + ENV_FILE = os.path.join(str(Path.home()), ".cashu", ".env") + if os.path.isfile(ENV_FILE): + env.read_env(ENV_FILE) + else: + ENV_FILE = "" + return ENV_FILE + + +class CashuSettings(BaseSettings): + env_file: str = Field(default=None) + lightning: bool = Field(default=True) + lightning_fee_percent: float = Field(default=1.0) + lightning_reserve_fee_min: int = Field(default=2000) + max_order: int = Field(default=64) + + class Config: + env_file = find_env_file() + env_file_encoding = "utf-8" + case_sensitive = False + extra = Extra.ignore + + # def __init__(self, env_file=None): + # self.env_file = env_file or self.env_file + + +class EnvSettings(CashuSettings): + debug: bool = Field(default=False) + host: str = Field(default="127.0.0.1") + port: int = Field(default=5000) + cashu_dir: str = Field(default=os.path.join(str(Path.home()), ".cashu")) + + +class MintSettings(CashuSettings): + mint_private_key: str = Field(default=None) + mint_listen_host: str = Field(default="127.0.0.1") + mint_listen_port: int = Field(default=3338) + mint_lightning_backend: str = Field(default="LNbitsWallet") + mint_database: str = Field(default="data/mint") + + mint_lnbits_endpoint: str = Field(default=None) + mint_lnbits_key: str = Field(default=None) + + +class WalletSettings(CashuSettings): + lightning: bool = Field(default=True) + tor: bool = Field(default=True) + socks_host: str = Field(default=None) + socks_port: int = Field(default=9050) + mint_url: str = Field(default=None) + mint_host: str = Field(default="8333.space") + mint_port: int = Field(default=3338) + + nostr_private_key: str = Field(default=None) + nostr_relays: List[str] = Field( + default=[ + "wss://nostr-pub.wellorder.net", + "wss://relay.damus.io", + "wss://nostr.zebedee.cloud", + "wss://relay.snort.social", + "wss://nostr.fmt.wiz.biz", + ] + ) + + +class Settings(EnvSettings, MintSettings, WalletSettings, CashuSettings): + version: str = Field(default=VERSION) + + # def __init__(self, env_file=None): + # super().Config(env_file=env_file) + + +settings = Settings() + + +def startup_settings_tasks(): + # set env_file (this does not affect the settings module, it's just for reading) + settings.env_file = find_env_file() + + if not settings.debug: + # set traceback limit + sys.tracebacklimit = 0 + + # replace ~ with home directory in cashu_dir + settings.cashu_dir = settings.cashu_dir.replace("~", str(Path.home())) + + # set mint_url if only mint_host is set + if not settings.mint_url: + if settings.mint_host in ["localhost", "127.0.0.1"] and settings.mint_port: + # localhost without https + settings.mint_url = f"http://{settings.mint_host}:{settings.mint_port}" + else: + settings.mint_url = f"https://{settings.mint_host}:{settings.mint_port}" + + +startup_settings_tasks() diff --git a/cashu/lightning/lnbits.py b/cashu/lightning/lnbits.py index f7e2db3..a3125b6 100644 --- a/cashu/lightning/lnbits.py +++ b/cashu/lightning/lnbits.py @@ -3,7 +3,7 @@ from typing import Dict, Optional import requests -from cashu.core.settings import DEBUG, LNBITS_ENDPOINT, LNBITS_KEY +from cashu.core.settings import settings from .base import ( InvoiceResponse, @@ -18,14 +18,14 @@ class LNbitsWallet(Wallet): """https://github.com/lnbits/lnbits""" def __init__(self): - self.endpoint = LNBITS_ENDPOINT + self.endpoint = settings.mint_lnbits_endpoint - key = LNBITS_KEY + key = settings.mint_lnbits_key self.key = {"X-Api-Key": key} self.s = requests.Session() self.s.auth = ("user", "pass") self.s.headers.update({"X-Api-Key": key}) - self.s.verify = not DEBUG + self.s.verify = not settings.debug async def status(self) -> StatusResponse: try: diff --git a/cashu/mint/app.py b/cashu/mint/app.py index d4594cc..8cf1a3c 100644 --- a/cashu/mint/app.py +++ b/cashu/mint/app.py @@ -7,7 +7,7 @@ from starlette.middleware import Middleware from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.cors import CORSMiddleware -from cashu.core.settings import DEBUG, VERSION +from cashu.core.settings import settings from .router import router from .startup import start_mint_init @@ -33,7 +33,7 @@ def create_app(config_object="core.settings") -> FastAPI: def __init__(self): self.padding = 0 self.minimal_fmt: str = "{time:YYYY-MM-DD HH:mm:ss.SS} | {level} | {message}\n" - if DEBUG: + if settings.debug: self.fmt: str = "{time:YYYY-MM-DD HH:mm:ss.SS} | {level: <4} | {name}:{function}:{line} | {message}\n" else: self.fmt: str = self.minimal_fmt @@ -53,7 +53,7 @@ def create_app(config_object="core.settings") -> FastAPI: logger.log(level, record.getMessage()) logger.remove() - log_level: str = "DEBUG" if DEBUG else "INFO" + log_level: str = "DEBUG" if settings.debug else "INFO" formatter = Formatter() logger.add(sys.stderr, level=log_level, format=formatter.format) @@ -82,7 +82,7 @@ def create_app(config_object="core.settings") -> FastAPI: app = FastAPI( title="Cashu Python Mint", description="Ecash wallet and mint for Bitcoin", - version=VERSION, + version=settings.version, license_info={ "name": "MIT License", "url": "https://raw.githubusercontent.com/cashubtc/cashu/main/LICENSE", diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 5d3d4cd..a170543 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -18,7 +18,7 @@ from cashu.core.db import Database from cashu.core.helpers import fee_reserve, sum_proofs from cashu.core.script import verify_script from cashu.core.secp import PublicKey -from cashu.core.settings import LIGHTNING, MAX_ORDER, VERSION +from cashu.core.settings import settings from cashu.core.split import amount_split from cashu.lightning.base import Wallet from cashu.mint.crud import LedgerCrud @@ -51,7 +51,9 @@ class Ledger: async def load_keyset(self, derivation_path, autosave=True): """Load current keyset keyset or generate new one.""" keyset = MintKeyset( - seed=self.master_key, derivation_path=derivation_path, version=VERSION + seed=self.master_key, + derivation_path=derivation_path, + version=settings.version, ) # check if current keyset is stored in db and store if not logger.trace(f"Loading keyset {keyset.id} from db.") @@ -192,7 +194,9 @@ class Ledger: def _verify_amount(self, amount: int): """Any amount used should be a positive integer not larger than 2^MAX_ORDER.""" - valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER + valid = ( + isinstance(amount, int) and amount > 0 and amount < 2**settings.max_order + ) if not valid: raise Exception("invalid amount: " + str(amount)) return amount @@ -359,7 +363,7 @@ class Ledger: amounts = [b.amount for b in B_s] amount = sum(amounts) # check if lightning invoice was paid - if LIGHTNING: + if settings.lightning: if not payment_hash: raise Exception("no payment_hash provided.") try: @@ -368,8 +372,10 @@ class Ledger: raise e for amount in amounts: - if amount not in [2**i for i in range(MAX_ORDER)]: - raise Exception(f"Can only mint amounts with 2^n up to {2**MAX_ORDER}.") + if amount not in [2**i for i in range(settings.max_order)]: + raise Exception( + f"Can only mint amounts with 2^n up to {2**settings.max_order}." + ) promises = await self._generate_promises(B_s, keyset) return promises @@ -391,7 +397,7 @@ class Ledger: "provided proofs not enough for Lightning payment." ) - if LIGHTNING: + if settings.lightning: status, preimage = await self._pay_lightning_invoice(invoice, fees_msat) else: status, preimage = True, "preimage" @@ -413,7 +419,7 @@ class Ledger: """Returns the fees (in msat) required to pay this pr.""" # hack: check if it's internal, if it exists, it will return paid = False, # if id does not exist (not internal), it returns paid = None - if LIGHTNING: + if settings.lightning: decoded_invoice = bolt11.decode(pr) amount = math.ceil(decoded_invoice.amount_msat / 1000) paid = await self.lightning.get_invoice_status(decoded_invoice.payment_hash) diff --git a/cashu/mint/main.py b/cashu/mint/main.py index 47c86ae..bb04e5d 100644 --- a/cashu/mint/main.py +++ b/cashu/mint/main.py @@ -4,7 +4,7 @@ import click import uvicorn from click import Context -from cashu.core.settings import MINT_SERVER_HOST, MINT_SERVER_PORT +from cashu.core.settings import settings @click.command( @@ -13,15 +13,15 @@ from cashu.core.settings import MINT_SERVER_HOST, MINT_SERVER_PORT allow_extra_args=True, ) ) -@click.option("--port", default=MINT_SERVER_PORT, help="Port to listen on") -@click.option("--host", default=MINT_SERVER_HOST, help="Host to run mint on") +@click.option("--port", default=settings.mint_listen_port, help="Port to listen on") +@click.option("--host", default=settings.mint_listen_host, help="Host to run mint on") @click.option("--ssl-keyfile", default=None, help="Path to SSL keyfile") @click.option("--ssl-certfile", default=None, help="Path to SSL certificate") @click.pass_context def main( ctx: Context, - port: int = MINT_SERVER_PORT, - host: str = MINT_SERVER_HOST, + port: int = settings.mint_listen_port, + host: str = settings.mint_listen_host, ssl_keyfile: Optional[str] = None, ssl_certfile: Optional[str] = None, ): diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 53f1ca7..632a305 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -7,26 +7,24 @@ from loguru import logger from cashu.core.db import Database from cashu.core.migrations import migrate_databases -from cashu.core.settings import ( - CASHU_DIR, - LIGHTNING, - MINT_DATABASE, - MINT_LIGHTNING_BACKEND, - MINT_PRIVATE_KEY, -) +from cashu.core.settings import settings from cashu.lightning.fake import FakeWallet # type: ignore from cashu.lightning.lnbits import LNbitsWallet # type: ignore from cashu.mint import migrations from cashu.mint.ledger import Ledger +logger.debug("Enviroment Settings:") +for key, value in settings.dict().items(): + logger.debug(f"{key}: {value}") + wallets_module = importlib.import_module("cashu.lightning") -LIGHTNING_BACKEND = getattr(wallets_module, MINT_LIGHTNING_BACKEND)() +lightning_backend = getattr(wallets_module, settings.mint_lightning_backend)() ledger = Ledger( - db=Database("mint", MINT_DATABASE), - seed=MINT_PRIVATE_KEY, + db=Database("mint", settings.mint_database), + seed=settings.mint_private_key, derivation_path="0/0/0/0", - lightning=LIGHTNING_BACKEND, + lightning=lightning_backend, ) @@ -36,8 +34,8 @@ async def start_mint_init(): await ledger.load_used_proofs() await ledger.init_keysets() - if LIGHTNING: - logger.info(f"Using backend: {MINT_LIGHTNING_BACKEND}") + if settings.lightning: + logger.info(f"Using backend: {settings.mint_lightning_backend}") error_message, balance = await ledger.lightning.status() if error_message: logger.warning( @@ -46,5 +44,5 @@ async def start_mint_init(): ) logger.info(f"Lightning balance: {balance} msat") - logger.info(f"Data dir: {CASHU_DIR}") + logger.info(f"Data dir: {settings.cashu_dir}") logger.info("Mint started.") diff --git a/cashu/wallet/__init__.py b/cashu/wallet/__init__.py index 3a33a17..df2a29f 100644 --- a/cashu/wallet/__init__.py +++ b/cashu/wallet/__init__.py @@ -4,8 +4,8 @@ sys.tracebacklimit = None # type: ignore from loguru import logger -from cashu.core.settings import DEBUG +from cashu.core.settings import settings # configure logger logger.remove() -logger.add(sys.stderr, level="DEBUG" if DEBUG else "INFO") +logger.add(sys.stderr, level="DEBUG" if settings.debug else "INFO") diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 07b4b6e..fb493da 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -21,19 +21,7 @@ from loguru import logger from cashu.core.base import Proof, TokenV2 from cashu.core.helpers import sum_proofs from cashu.core.migrations import migrate_databases -from cashu.core.settings import ( - CASHU_DIR, - DEBUG, - ENV_FILE, - LIGHTNING, - MINT_URL, - NOSTR_PRIVATE_KEY, - NOSTR_RELAYS, - SOCKS_HOST, - SOCKS_PORT, - TOR, - VERSION, -) +from cashu.core.settings import settings from cashu.nostr.nostr.client.client import NostrClient from cashu.tor.tor import TorProxy from cashu.wallet import migrations @@ -69,7 +57,12 @@ class NaturalOrderGroup(click.Group): @click.group(cls=NaturalOrderGroup) -@click.option("--host", "-h", default=MINT_URL, help=f"Mint URL (default: {MINT_URL}).") +@click.option( + "--host", + "-h", + default=settings.mint_url, + help=f"Mint URL (default: {settings.mint_url}).", +) @click.option( "--wallet", "-w", @@ -79,24 +72,22 @@ class NaturalOrderGroup(click.Group): ) @click.pass_context def cli(ctx: Context, host: str, walletname: str): - if TOR and not TorProxy().check_platform(): + if settings.tor and not TorProxy().check_platform(): error_str = "Your settings say TOR=true but the built-in Tor bundle is not supported on your system. You have two options: Either install Tor manually and set TOR=FALSE and SOCKS_HOST=localhost and SOCKS_PORT=9050 in your Cashu config (recommended). Or turn off Tor by setting TOR=false (not recommended). Cashu will not work until you edit your config file accordingly." error_str += "\n\n" - if ENV_FILE: - error_str += f"Edit your Cashu config file here: {ENV_FILE}" - env_path = ENV_FILE + if settings.env_file: + error_str += f"Edit your Cashu config file here: {settings.env_file}" + env_path = settings.env_file else: - error_str += ( - f"Ceate a new Cashu config file here: {os.path.join(CASHU_DIR, '.env')}" - ) - env_path = os.path.join(CASHU_DIR, ".env") + error_str += f"Ceate a new Cashu config file here: {os.path.join(settings.cashu_dir, '.env')}" + env_path = os.path.join(settings.cashu_dir, ".env") error_str += f'\n\nYou can turn off Tor with this command: echo "TOR=FALSE" >> {env_path}' raise Exception(error_str) ctx.ensure_object(dict) ctx.obj["HOST"] = host ctx.obj["WALLET_NAME"] = walletname - wallet = Wallet(ctx.obj["HOST"], os.path.join(CASHU_DIR, walletname)) + wallet = Wallet(ctx.obj["HOST"], os.path.join(settings.cashu_dir, walletname)) ctx.obj["WALLET"] = wallet asyncio.run(init_wallet(wallet)) pass @@ -149,7 +140,7 @@ async def invoice(ctx: Context, amount: int, hash: str): wallet: Wallet = ctx.obj["WALLET"] await wallet.load_mint() wallet.status() - if not LIGHTNING: + if not settings.lightning: r = await wallet.mint(amount) elif amount and not hash: invoice = await wallet.request_mint(amount) @@ -601,13 +592,15 @@ async def invoices(ctx): @coro async def wallets(ctx): # list all directories - wallets = [d for d in listdir(CASHU_DIR) if isdir(join(CASHU_DIR, d))] + wallets = [ + d for d in listdir(settings.cashu_dir) if isdir(join(settings.cashu_dir, d)) + ] try: wallets.remove("mint") except ValueError: pass for w in wallets: - wallet = Wallet(ctx.obj["HOST"], os.path.join(CASHU_DIR, w)) + wallet = Wallet(ctx.obj["HOST"], os.path.join(settings.cashu_dir, w)) try: await init_wallet(wallet) if wallet.proofs and len(wallet.proofs): @@ -625,20 +618,20 @@ async def wallets(ctx): @click.pass_context @coro async def info(ctx: Context): - print(f"Version: {VERSION}") + print(f"Version: {settings.version}") print(f"Wallet: {ctx.obj['WALLET_NAME']}") - if DEBUG: - print(f"Debug: {DEBUG}") - print(f"Cashu dir: {CASHU_DIR}") - if ENV_FILE: - print(f"Settings: {ENV_FILE}") - if TOR: - print(f"Tor enabled: {TOR}") - if NOSTR_PRIVATE_KEY: - client = NostrClient(private_key=NOSTR_PRIVATE_KEY, connect=False) + if settings.debug: + print(f"Debug: {settings.debug}") + print(f"Cashu dir: {settings.cashu_dir}") + if settings.env_file: + print(f"Settings: {settings.env_file}") + if settings.tor: + print(f"Tor enabled: {settings.tor}") + if settings.nostr_private_key: + client = NostrClient(private_key=settings.nostr_private_key, connect=False) print(f"Nostr public key: {client.public_key.bech32()}") - print(f"Nostr relays: {NOSTR_RELAYS}") - if SOCKS_HOST: - print(f"Socks proxy: {SOCKS_HOST}:{SOCKS_PORT}") + print(f"Nostr relays: {settings.nostr_relays}") + if settings.socks_host: + print(f"Socks proxy: {settings.socks_host}:{settings.socks_port}") print(f"Mint URL: {ctx.obj['HOST']}") return diff --git a/cashu/wallet/cli/cli_helpers.py b/cashu/wallet/cli/cli_helpers.py index 3f53c23..2d884b3 100644 --- a/cashu/wallet/cli/cli_helpers.py +++ b/cashu/wallet/cli/cli_helpers.py @@ -8,7 +8,7 @@ from loguru import logger from cashu.core.base import Proof, TokenV2, TokenV2Mint, WalletKeyset from cashu.core.helpers import sum_proofs -from cashu.core.settings import CASHU_DIR, MINT_URL, NOSTR_PRIVATE_KEY, NOSTR_RELAYS +from cashu.core.settings import settings from cashu.wallet.crud import get_keyset from cashu.wallet.wallet import Wallet as Wallet @@ -32,7 +32,7 @@ async def verify_mints(ctx: Context, token: TokenV2): for keyset in set([id for id in mint.ids if id in proofs_keysets]): # init a temporary wallet object keyset_wallet = Wallet( - mint.url, os.path.join(CASHU_DIR, ctx.obj["WALLET_NAME"]) + mint.url, os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"]) ) # make sure that this mint supports this keyset mint_keysets = await keyset_wallet._get_keyset_ids(mint.url) @@ -81,7 +81,7 @@ async def redeem_multimint(ctx: Context, token: TokenV2, script, signature): logger.debug(f"Redeeming tokens from keyset {keyset}") # init a temporary wallet object keyset_wallet = Wallet( - mint.url, os.path.join(CASHU_DIR, ctx.obj["WALLET_NAME"]) + mint.url, os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"]) ) # load the keys @@ -147,7 +147,9 @@ async def get_mint_wallet(ctx: Context): mint_url = list(mint_balances.keys())[mint_nr - 1] # load this mint_url into a wallet - mint_wallet = Wallet(mint_url, os.path.join(CASHU_DIR, ctx.obj["WALLET_NAME"])) + mint_wallet = Wallet( + mint_url, os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"]) + ) mint_keysets: WalletKeyset = await get_keyset(mint_url=mint_url, db=mint_wallet.db) # type: ignore # load the keys @@ -196,7 +198,8 @@ async def proofs_to_serialized_tokenv2(wallet, proofs: List[Proof], url: str): url = ks.mint_url if ks and ks.mint_url else "" url = url or ( - input(f"Enter mint URL (press enter for default {MINT_URL}): ") or MINT_URL + input(f"Enter mint URL (press enter for default {settings.mint_url}): ") + or settings.mint_url ) token.mints.append(TokenV2Mint(url=url, ids=keysets)) diff --git a/cashu/wallet/cli/nostr.py b/cashu/wallet/cli/nostr.py index 95916cf..f4d449e 100644 --- a/cashu/wallet/cli/nostr.py +++ b/cashu/wallet/cli/nostr.py @@ -6,7 +6,7 @@ import click from click import Context from requests.exceptions import ConnectionError -from cashu.core.settings import NOSTR_PRIVATE_KEY, NOSTR_RELAYS +from cashu.core.settings import settings from cashu.nostr.nostr.client.client import NostrClient from cashu.nostr.nostr.event import Event from cashu.nostr.nostr.key import PublicKey @@ -79,8 +79,10 @@ async def send_nostr(ctx: Context, amount: int, pubkey: str, verbose: bool, yes: default=True, ) - client = NostrClient(private_key=NOSTR_PRIVATE_KEY or "", relays=NOSTR_RELAYS) - if verbose and not NOSTR_PRIVATE_KEY: + client = NostrClient( + private_key=settings.nostr_private_key or "", relays=settings.nostr_relays + ) + if verbose and not settings.nostr_private_key: # we generated a random key if none was present print(f"Your nostr private key: {client.private_key.bech32()}") @@ -91,12 +93,14 @@ async def send_nostr(ctx: Context, amount: int, pubkey: str, verbose: bool, yes: async def receive_nostr(ctx: Context, verbose: bool): - if NOSTR_PRIVATE_KEY is None: + if settings.nostr_private_key is None: print( "Warning: No nostr private key set! You don't have NOSTR_PRIVATE_KEY set in your .env file. I will create a random private key for this session but I will not remember it." ) print("") - client = NostrClient(private_key=NOSTR_PRIVATE_KEY, relays=NOSTR_RELAYS) + client = NostrClient( + private_key=settings.nostr_private_key, relays=settings.nostr_relays + ) print(f"Your nostr public key: {client.public_key.bech32()}") if verbose: print(f"Your nostr private key (do not share!): {client.private_key.bech32()}") diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index bedb19d..4ed201c 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -42,7 +42,7 @@ from cashu.core.script import ( step2_carol_sign_tx, ) from cashu.core.secp import PublicKey -from cashu.core.settings import DEBUG, MAX_ORDER, SOCKS_HOST, SOCKS_PORT, TOR, VERSION +from cashu.core.settings import settings from cashu.core.split import amount_split from cashu.tor.tor import TorProxy from cashu.wallet.crud import ( @@ -67,16 +67,16 @@ def async_set_requests(func): """ async def wrapper(self, *args, **kwargs): - self.s.headers.update({"Client-version": VERSION}) - if DEBUG: + self.s.headers.update({"Client-version": settings.version}) + if settings.debug: self.s.verify = False socks_host, socks_port = None, None - if TOR and TorProxy().check_platform(): + if settings.tor and TorProxy().check_platform(): self.tor = TorProxy(timeout=True) self.tor.run_daemon(verbose=True) socks_host, socks_port = "localhost", 9050 else: - socks_host, socks_port = SOCKS_HOST, SOCKS_PORT + socks_host, socks_port = settings.socks_host, settings.socks_port if socks_host and socks_port: proxies = { @@ -515,8 +515,10 @@ class Wallet(LedgerAPI): List[Proof]: Newly minted proofs. """ for amount in amounts: - if amount not in [2**i for i in range(MAX_ORDER)]: - raise Exception(f"Can only mint amounts with 2^n up to {2**MAX_ORDER}.") + if amount not in [2**i for i in range(settings.max_order)]: + raise Exception( + f"Can only mint amounts with 2^n up to {2**settings.max_order}." + ) proofs = await super().mint(amounts, payment_hash) if proofs == []: raise Exception("received no proofs.") diff --git a/tests/test_cli.py b/tests/test_cli.py index 79eb5d8..7c1b63f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -4,7 +4,7 @@ import pytest from click.testing import CliRunner from cashu.core.migrations import migrate_databases -from cashu.core.settings import VERSION +from cashu.core.settings import settings from cashu.wallet import migrations from cashu.wallet.cli.cli import cli from cashu.wallet.wallet import Wallet @@ -29,7 +29,7 @@ def test_info(): ) print("INFO") print(result.output) - result.output.startswith(f"Version: {VERSION}") + result.output.startswith(f"Version: {settings.version}") assert result.exit_code == 0 diff --git a/tests/test_mint.py b/tests/test_mint.py index f175ce6..e50e6be 100644 --- a/tests/test_mint.py +++ b/tests/test_mint.py @@ -11,7 +11,7 @@ SERVER_ENDPOINT = "http://localhost:3338" import os from cashu.core.db import Database -from cashu.core.settings import MAX_ORDER +from cashu.core.settings import settings from cashu.mint import migrations from cashu.mint.ledger import Ledger @@ -63,7 +63,7 @@ async def test_keysets(ledger: Ledger): async def test_get_keyset(ledger: Ledger): keyset = ledger.get_keyset() assert type(keyset) == dict - assert len(keyset) == MAX_ORDER + assert len(keyset) == settings.max_order @pytest.mark.asyncio diff --git a/tests/test_wallet.py b/tests/test_wallet.py index ea25596..b9e6d6b 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -7,7 +7,7 @@ import pytest_asyncio from cashu.core.base import Proof from cashu.core.helpers import async_unwrap, sum_proofs from cashu.core.migrations import migrate_databases -from cashu.core.settings import MAX_ORDER +from cashu.core.settings import settings from cashu.wallet import migrations from cashu.wallet.wallet import Wallet from cashu.wallet.wallet import Wallet as Wallet1 @@ -51,7 +51,7 @@ async def wallet2(mint): @pytest.mark.asyncio async def test_get_keys(wallet1: Wallet): assert wallet1.keys.public_keys - assert len(wallet1.keys.public_keys) == MAX_ORDER + assert len(wallet1.keys.public_keys) == settings.max_order keyset = await wallet1._get_keys(wallet1.url) assert keyset.id is not None assert type(keyset.id) == str @@ -61,7 +61,7 @@ async def test_get_keys(wallet1: Wallet): @pytest.mark.asyncio async def test_get_keyset(wallet1: Wallet): assert wallet1.keys.public_keys - assert len(wallet1.keys.public_keys) == MAX_ORDER + assert len(wallet1.keys.public_keys) == settings.max_order # let's get the keys first so we can get a keyset ID that we use later keys1 = await wallet1._get_keys(wallet1.url) # gets the keys of a specific keyset @@ -107,7 +107,7 @@ async def test_mint_amounts_wrong_order(wallet1: Wallet): """Mint amount that is not part in 2^n""" await assert_err( wallet1.mint_amounts([1, 2, 3]), - f"Can only mint amounts with 2^n up to {2**MAX_ORDER}.", + f"Can only mint amounts with 2^n up to {2**settings.max_order}.", )