use settings module (#136)

* use settings module
This commit is contained in:
calle
2023-03-16 00:59:50 +01:00
committed by GitHub
parent 51156799b9
commit f3a31fd09e
18 changed files with 222 additions and 173 deletions

View File

@@ -24,8 +24,8 @@ MINT_PRIVATE_KEY=supersecretprivatekey
# Supported: LNbitsWallet, FakeWallet # Supported: LNbitsWallet, FakeWallet
MINT_LIGHTNING_BACKEND=LNbitsWallet MINT_LIGHTNING_BACKEND=LNbitsWallet
MINT_SERVER_HOST=127.0.0.1 MINT_LISTEN_HOST=127.0.0.1
MINT_SERVER_PORT=3338 MINT_LISTEN_PORT=3338
LIGHTNING=TRUE LIGHTNING=TRUE
# fee to reserve in percent of the amount # fee to reserve in percent of the amount

View File

@@ -31,8 +31,8 @@ jobs:
env: env:
LIGHTNING: False LIGHTNING: False
MINT_PRIVATE_KEY: "testingkey" MINT_PRIVATE_KEY: "testingkey"
MINT_SERVER_HOST: 0.0.0.0 MINT_LISTEN_HOST: 0.0.0.0
MINT_SERVER_PORT: 3337 MINT_LISTEN_PORT: 3337
run: | run: |
nohup poetry run mint & nohup poetry run mint &
- name: Run tests - name: Run tests

View File

@@ -3,7 +3,7 @@ import hashlib
from typing import Dict, List from typing import Dict, List
from cashu.core.secp import PrivateKey, PublicKey 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)]) # entropy = bytes([random.getrandbits(8) for i in range(16)])
# mnemonic = bip39.mnemonic_from_bytes(entropy) # mnemonic = bip39.mnemonic_from_bytes(entropy)
@@ -27,12 +27,14 @@ def derive_keys(master_key: str, derivation_path: str = ""):
.encode("utf-8")[:32], .encode("utf-8")[:32],
raw=True, raw=True,
) )
for i in range(MAX_ORDER) for i in range(settings.max_order)
} }
def derive_pubkeys(keys: Dict[int, PrivateKey]): 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]): def derive_keyset_id(keys: Dict[int, PublicKey]):

View File

@@ -3,7 +3,7 @@ from functools import partial, wraps
from typing import List from typing import List
from cashu.core.base import Proof 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]): def sum_proofs(proofs: List[Proof]):
@@ -39,5 +39,6 @@ def fee_reserve(amount_msat: int, internal=False) -> int:
if internal: if internal:
return 0 return 0
return max( 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),
) )

View File

@@ -1,72 +1,112 @@
import os import os
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Union from typing import List, Union
from environs import Env # type: ignore from environs import Env # type: ignore
from pydantic import BaseSettings, Extra, Field, validator
env = Env() 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" 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()

View File

@@ -3,7 +3,7 @@ from typing import Dict, Optional
import requests import requests
from cashu.core.settings import DEBUG, LNBITS_ENDPOINT, LNBITS_KEY from cashu.core.settings import settings
from .base import ( from .base import (
InvoiceResponse, InvoiceResponse,
@@ -18,14 +18,14 @@ class LNbitsWallet(Wallet):
"""https://github.com/lnbits/lnbits""" """https://github.com/lnbits/lnbits"""
def __init__(self): 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.key = {"X-Api-Key": key}
self.s = requests.Session() self.s = requests.Session()
self.s.auth = ("user", "pass") self.s.auth = ("user", "pass")
self.s.headers.update({"X-Api-Key": key}) self.s.headers.update({"X-Api-Key": key})
self.s.verify = not DEBUG self.s.verify = not settings.debug
async def status(self) -> StatusResponse: async def status(self) -> StatusResponse:
try: try:

View File

@@ -7,7 +7,7 @@ from starlette.middleware import Middleware
from starlette.middleware.base import BaseHTTPMiddleware from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.cors import CORSMiddleware from starlette.middleware.cors import CORSMiddleware
from cashu.core.settings import DEBUG, VERSION from cashu.core.settings import settings
from .router import router from .router import router
from .startup import start_mint_init from .startup import start_mint_init
@@ -33,7 +33,7 @@ def create_app(config_object="core.settings") -> FastAPI:
def __init__(self): def __init__(self):
self.padding = 0 self.padding = 0
self.minimal_fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level}</level> | <level>{message}</level>\n" self.minimal_fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level}</level> | <level>{message}</level>\n"
if DEBUG: if settings.debug:
self.fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level: <4}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>\n" self.fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level: <4}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>\n"
else: else:
self.fmt: str = self.minimal_fmt self.fmt: str = self.minimal_fmt
@@ -53,7 +53,7 @@ def create_app(config_object="core.settings") -> FastAPI:
logger.log(level, record.getMessage()) logger.log(level, record.getMessage())
logger.remove() logger.remove()
log_level: str = "DEBUG" if DEBUG else "INFO" log_level: str = "DEBUG" if settings.debug else "INFO"
formatter = Formatter() formatter = Formatter()
logger.add(sys.stderr, level=log_level, format=formatter.format) logger.add(sys.stderr, level=log_level, format=formatter.format)
@@ -82,7 +82,7 @@ def create_app(config_object="core.settings") -> FastAPI:
app = FastAPI( app = FastAPI(
title="Cashu Python Mint", title="Cashu Python Mint",
description="Ecash wallet and mint for Bitcoin", description="Ecash wallet and mint for Bitcoin",
version=VERSION, version=settings.version,
license_info={ license_info={
"name": "MIT License", "name": "MIT License",
"url": "https://raw.githubusercontent.com/cashubtc/cashu/main/LICENSE", "url": "https://raw.githubusercontent.com/cashubtc/cashu/main/LICENSE",

View File

@@ -18,7 +18,7 @@ from cashu.core.db import Database
from cashu.core.helpers import fee_reserve, sum_proofs from cashu.core.helpers import fee_reserve, sum_proofs
from cashu.core.script import verify_script from cashu.core.script import verify_script
from cashu.core.secp import PublicKey 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.core.split import amount_split
from cashu.lightning.base import Wallet from cashu.lightning.base import Wallet
from cashu.mint.crud import LedgerCrud from cashu.mint.crud import LedgerCrud
@@ -51,7 +51,9 @@ class Ledger:
async def load_keyset(self, derivation_path, autosave=True): async def load_keyset(self, derivation_path, autosave=True):
"""Load current keyset keyset or generate new one.""" """Load current keyset keyset or generate new one."""
keyset = MintKeyset( 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 # check if current keyset is stored in db and store if not
logger.trace(f"Loading keyset {keyset.id} from db.") logger.trace(f"Loading keyset {keyset.id} from db.")
@@ -192,7 +194,9 @@ class Ledger:
def _verify_amount(self, amount: int): def _verify_amount(self, amount: int):
"""Any amount used should be a positive integer not larger than 2^MAX_ORDER.""" """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: if not valid:
raise Exception("invalid amount: " + str(amount)) raise Exception("invalid amount: " + str(amount))
return amount return amount
@@ -359,7 +363,7 @@ class Ledger:
amounts = [b.amount for b in B_s] amounts = [b.amount for b in B_s]
amount = sum(amounts) amount = sum(amounts)
# check if lightning invoice was paid # check if lightning invoice was paid
if LIGHTNING: if settings.lightning:
if not payment_hash: if not payment_hash:
raise Exception("no payment_hash provided.") raise Exception("no payment_hash provided.")
try: try:
@@ -368,8 +372,10 @@ class Ledger:
raise e raise e
for amount in amounts: for amount in amounts:
if amount not in [2**i for i in range(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**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) promises = await self._generate_promises(B_s, keyset)
return promises return promises
@@ -391,7 +397,7 @@ class Ledger:
"provided proofs not enough for Lightning payment." "provided proofs not enough for Lightning payment."
) )
if LIGHTNING: if settings.lightning:
status, preimage = await self._pay_lightning_invoice(invoice, fees_msat) status, preimage = await self._pay_lightning_invoice(invoice, fees_msat)
else: else:
status, preimage = True, "preimage" status, preimage = True, "preimage"
@@ -413,7 +419,7 @@ class Ledger:
"""Returns the fees (in msat) required to pay this pr.""" """Returns the fees (in msat) required to pay this pr."""
# hack: check if it's internal, if it exists, it will return paid = False, # 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 id does not exist (not internal), it returns paid = None
if LIGHTNING: if settings.lightning:
decoded_invoice = bolt11.decode(pr) decoded_invoice = bolt11.decode(pr)
amount = math.ceil(decoded_invoice.amount_msat / 1000) amount = math.ceil(decoded_invoice.amount_msat / 1000)
paid = await self.lightning.get_invoice_status(decoded_invoice.payment_hash) paid = await self.lightning.get_invoice_status(decoded_invoice.payment_hash)

View File

@@ -4,7 +4,7 @@ import click
import uvicorn import uvicorn
from click import Context from click import Context
from cashu.core.settings import MINT_SERVER_HOST, MINT_SERVER_PORT from cashu.core.settings import settings
@click.command( @click.command(
@@ -13,15 +13,15 @@ from cashu.core.settings import MINT_SERVER_HOST, MINT_SERVER_PORT
allow_extra_args=True, allow_extra_args=True,
) )
) )
@click.option("--port", default=MINT_SERVER_PORT, help="Port to listen on") @click.option("--port", default=settings.mint_listen_port, help="Port to listen on")
@click.option("--host", default=MINT_SERVER_HOST, help="Host to run mint 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-keyfile", default=None, help="Path to SSL keyfile")
@click.option("--ssl-certfile", default=None, help="Path to SSL certificate") @click.option("--ssl-certfile", default=None, help="Path to SSL certificate")
@click.pass_context @click.pass_context
def main( def main(
ctx: Context, ctx: Context,
port: int = MINT_SERVER_PORT, port: int = settings.mint_listen_port,
host: str = MINT_SERVER_HOST, host: str = settings.mint_listen_host,
ssl_keyfile: Optional[str] = None, ssl_keyfile: Optional[str] = None,
ssl_certfile: Optional[str] = None, ssl_certfile: Optional[str] = None,
): ):

View File

@@ -7,26 +7,24 @@ from loguru import logger
from cashu.core.db import Database from cashu.core.db import Database
from cashu.core.migrations import migrate_databases from cashu.core.migrations import migrate_databases
from cashu.core.settings import ( from cashu.core.settings import settings
CASHU_DIR,
LIGHTNING,
MINT_DATABASE,
MINT_LIGHTNING_BACKEND,
MINT_PRIVATE_KEY,
)
from cashu.lightning.fake import FakeWallet # type: ignore from cashu.lightning.fake import FakeWallet # type: ignore
from cashu.lightning.lnbits import LNbitsWallet # type: ignore from cashu.lightning.lnbits import LNbitsWallet # type: ignore
from cashu.mint import migrations from cashu.mint import migrations
from cashu.mint.ledger import Ledger 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") 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( ledger = Ledger(
db=Database("mint", MINT_DATABASE), db=Database("mint", settings.mint_database),
seed=MINT_PRIVATE_KEY, seed=settings.mint_private_key,
derivation_path="0/0/0/0", 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.load_used_proofs()
await ledger.init_keysets() await ledger.init_keysets()
if LIGHTNING: if settings.lightning:
logger.info(f"Using backend: {MINT_LIGHTNING_BACKEND}") logger.info(f"Using backend: {settings.mint_lightning_backend}")
error_message, balance = await ledger.lightning.status() error_message, balance = await ledger.lightning.status()
if error_message: if error_message:
logger.warning( logger.warning(
@@ -46,5 +44,5 @@ async def start_mint_init():
) )
logger.info(f"Lightning balance: {balance} msat") 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.") logger.info("Mint started.")

View File

@@ -4,8 +4,8 @@ sys.tracebacklimit = None # type: ignore
from loguru import logger from loguru import logger
from cashu.core.settings import DEBUG from cashu.core.settings import settings
# configure logger # configure logger
logger.remove() logger.remove()
logger.add(sys.stderr, level="DEBUG" if DEBUG else "INFO") logger.add(sys.stderr, level="DEBUG" if settings.debug else "INFO")

View File

@@ -21,19 +21,7 @@ from loguru import logger
from cashu.core.base import Proof, TokenV2 from cashu.core.base import Proof, 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 ( from cashu.core.settings import settings
CASHU_DIR,
DEBUG,
ENV_FILE,
LIGHTNING,
MINT_URL,
NOSTR_PRIVATE_KEY,
NOSTR_RELAYS,
SOCKS_HOST,
SOCKS_PORT,
TOR,
VERSION,
)
from cashu.nostr.nostr.client.client import NostrClient 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
@@ -69,7 +57,12 @@ class NaturalOrderGroup(click.Group):
@click.group(cls=NaturalOrderGroup) @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( @click.option(
"--wallet", "--wallet",
"-w", "-w",
@@ -79,24 +72,22 @@ class NaturalOrderGroup(click.Group):
) )
@click.pass_context @click.pass_context
def cli(ctx: Context, host: str, walletname: str): 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 = "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" error_str += "\n\n"
if ENV_FILE: if settings.env_file:
error_str += f"Edit your Cashu config file here: {ENV_FILE}" error_str += f"Edit your Cashu config file here: {settings.env_file}"
env_path = ENV_FILE env_path = settings.env_file
else: else:
error_str += ( error_str += f"Ceate a new Cashu config file here: {os.path.join(settings.cashu_dir, '.env')}"
f"Ceate a new Cashu config file here: {os.path.join(CASHU_DIR, '.env')}" env_path = os.path.join(settings.cashu_dir, ".env")
)
env_path = os.path.join(CASHU_DIR, ".env")
error_str += f'\n\nYou can turn off Tor with this command: echo "TOR=FALSE" >> {env_path}' error_str += f'\n\nYou can turn off Tor with this command: echo "TOR=FALSE" >> {env_path}'
raise Exception(error_str) raise Exception(error_str)
ctx.ensure_object(dict) ctx.ensure_object(dict)
ctx.obj["HOST"] = host ctx.obj["HOST"] = host
ctx.obj["WALLET_NAME"] = walletname 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 ctx.obj["WALLET"] = wallet
asyncio.run(init_wallet(wallet)) asyncio.run(init_wallet(wallet))
pass pass
@@ -149,7 +140,7 @@ async def invoice(ctx: Context, amount: int, hash: str):
wallet: Wallet = ctx.obj["WALLET"] wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint() await wallet.load_mint()
wallet.status() wallet.status()
if not LIGHTNING: if not settings.lightning:
r = await wallet.mint(amount) r = await wallet.mint(amount)
elif amount and not hash: elif amount and not hash:
invoice = await wallet.request_mint(amount) invoice = await wallet.request_mint(amount)
@@ -601,13 +592,15 @@ async def invoices(ctx):
@coro @coro
async def wallets(ctx): async def wallets(ctx):
# list all directories # 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: try:
wallets.remove("mint") wallets.remove("mint")
except ValueError: except ValueError:
pass pass
for w in wallets: 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: try:
await init_wallet(wallet) await init_wallet(wallet)
if wallet.proofs and len(wallet.proofs): if wallet.proofs and len(wallet.proofs):
@@ -625,20 +618,20 @@ async def wallets(ctx):
@click.pass_context @click.pass_context
@coro @coro
async def info(ctx: Context): async def info(ctx: Context):
print(f"Version: {VERSION}") print(f"Version: {settings.version}")
print(f"Wallet: {ctx.obj['WALLET_NAME']}") print(f"Wallet: {ctx.obj['WALLET_NAME']}")
if DEBUG: if settings.debug:
print(f"Debug: {DEBUG}") print(f"Debug: {settings.debug}")
print(f"Cashu dir: {CASHU_DIR}") print(f"Cashu dir: {settings.cashu_dir}")
if ENV_FILE: if settings.env_file:
print(f"Settings: {ENV_FILE}") print(f"Settings: {settings.env_file}")
if TOR: if settings.tor:
print(f"Tor enabled: {TOR}") print(f"Tor enabled: {settings.tor}")
if NOSTR_PRIVATE_KEY: if settings.nostr_private_key:
client = NostrClient(private_key=NOSTR_PRIVATE_KEY, connect=False) client = NostrClient(private_key=settings.nostr_private_key, connect=False)
print(f"Nostr public key: {client.public_key.bech32()}") print(f"Nostr public key: {client.public_key.bech32()}")
print(f"Nostr relays: {NOSTR_RELAYS}") print(f"Nostr relays: {settings.nostr_relays}")
if SOCKS_HOST: if settings.socks_host:
print(f"Socks proxy: {SOCKS_HOST}:{SOCKS_PORT}") print(f"Socks proxy: {settings.socks_host}:{settings.socks_port}")
print(f"Mint URL: {ctx.obj['HOST']}") print(f"Mint URL: {ctx.obj['HOST']}")
return return

View File

@@ -8,7 +8,7 @@ from loguru import logger
from cashu.core.base import Proof, TokenV2, TokenV2Mint, WalletKeyset from cashu.core.base import Proof, TokenV2, TokenV2Mint, WalletKeyset
from cashu.core.helpers import sum_proofs 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.crud import get_keyset
from cashu.wallet.wallet import Wallet as Wallet 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]): for keyset in set([id for id in mint.ids if id in proofs_keysets]):
# init a temporary wallet object # init a temporary wallet object
keyset_wallet = Wallet( 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 # make sure that this mint supports this keyset
mint_keysets = await keyset_wallet._get_keyset_ids(mint.url) 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}") logger.debug(f"Redeeming tokens from keyset {keyset}")
# init a temporary wallet object # init a temporary wallet object
keyset_wallet = Wallet( 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 # load the keys
@@ -147,7 +147,9 @@ async def get_mint_wallet(ctx: Context):
mint_url = list(mint_balances.keys())[mint_nr - 1] mint_url = list(mint_balances.keys())[mint_nr - 1]
# load this mint_url into a wallet # 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 mint_keysets: WalletKeyset = await get_keyset(mint_url=mint_url, db=mint_wallet.db) # type: ignore
# load the keys # 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 = ks.mint_url if ks and ks.mint_url else ""
url = url or ( 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)) token.mints.append(TokenV2Mint(url=url, ids=keysets))

View File

@@ -6,7 +6,7 @@ import click
from click import Context from click import Context
from requests.exceptions import ConnectionError 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.client.client import NostrClient
from cashu.nostr.nostr.event import Event from cashu.nostr.nostr.event import Event
from cashu.nostr.nostr.key import PublicKey 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, default=True,
) )
client = NostrClient(private_key=NOSTR_PRIVATE_KEY or "", relays=NOSTR_RELAYS) client = NostrClient(
if verbose and not NOSTR_PRIVATE_KEY: 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 # we generated a random key if none was present
print(f"Your nostr private key: {client.private_key.bech32()}") 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): async def receive_nostr(ctx: Context, verbose: bool):
if NOSTR_PRIVATE_KEY is None: if settings.nostr_private_key is None:
print( 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." "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("") 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()}") print(f"Your nostr public key: {client.public_key.bech32()}")
if verbose: if verbose:
print(f"Your nostr private key (do not share!): {client.private_key.bech32()}") print(f"Your nostr private key (do not share!): {client.private_key.bech32()}")

View File

@@ -42,7 +42,7 @@ from cashu.core.script import (
step2_carol_sign_tx, step2_carol_sign_tx,
) )
from cashu.core.secp import PublicKey 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.core.split import amount_split
from cashu.tor.tor import TorProxy from cashu.tor.tor import TorProxy
from cashu.wallet.crud import ( from cashu.wallet.crud import (
@@ -67,16 +67,16 @@ def async_set_requests(func):
""" """
async def wrapper(self, *args, **kwargs): async def wrapper(self, *args, **kwargs):
self.s.headers.update({"Client-version": VERSION}) self.s.headers.update({"Client-version": settings.version})
if DEBUG: if settings.debug:
self.s.verify = False self.s.verify = False
socks_host, socks_port = None, None 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 = TorProxy(timeout=True)
self.tor.run_daemon(verbose=True) self.tor.run_daemon(verbose=True)
socks_host, socks_port = "localhost", 9050 socks_host, socks_port = "localhost", 9050
else: 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: if socks_host and socks_port:
proxies = { proxies = {
@@ -515,8 +515,10 @@ class Wallet(LedgerAPI):
List[Proof]: Newly minted proofs. List[Proof]: Newly minted proofs.
""" """
for amount in amounts: for amount in amounts:
if amount not in [2**i for i in range(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**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) proofs = await super().mint(amounts, payment_hash)
if proofs == []: if proofs == []:
raise Exception("received no proofs.") raise Exception("received no proofs.")

View File

@@ -4,7 +4,7 @@ import pytest
from click.testing import CliRunner from click.testing import CliRunner
from cashu.core.migrations import migrate_databases 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 import migrations
from cashu.wallet.cli.cli import cli from cashu.wallet.cli.cli import cli
from cashu.wallet.wallet import Wallet from cashu.wallet.wallet import Wallet
@@ -29,7 +29,7 @@ def test_info():
) )
print("INFO") print("INFO")
print(result.output) print(result.output)
result.output.startswith(f"Version: {VERSION}") result.output.startswith(f"Version: {settings.version}")
assert result.exit_code == 0 assert result.exit_code == 0

View File

@@ -11,7 +11,7 @@ SERVER_ENDPOINT = "http://localhost:3338"
import os import os
from cashu.core.db import Database 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 import migrations
from cashu.mint.ledger import Ledger from cashu.mint.ledger import Ledger
@@ -63,7 +63,7 @@ async def test_keysets(ledger: Ledger):
async def test_get_keyset(ledger: Ledger): async def test_get_keyset(ledger: Ledger):
keyset = ledger.get_keyset() keyset = ledger.get_keyset()
assert type(keyset) == dict assert type(keyset) == dict
assert len(keyset) == MAX_ORDER assert len(keyset) == settings.max_order
@pytest.mark.asyncio @pytest.mark.asyncio

View File

@@ -7,7 +7,7 @@ import pytest_asyncio
from cashu.core.base import Proof from cashu.core.base import Proof
from cashu.core.helpers import async_unwrap, sum_proofs from cashu.core.helpers import async_unwrap, sum_proofs
from cashu.core.migrations import migrate_databases 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 import migrations
from cashu.wallet.wallet import Wallet from cashu.wallet.wallet import Wallet
from cashu.wallet.wallet import Wallet as Wallet1 from cashu.wallet.wallet import Wallet as Wallet1
@@ -51,7 +51,7 @@ async def wallet2(mint):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_keys(wallet1: Wallet): async def test_get_keys(wallet1: Wallet):
assert wallet1.keys.public_keys 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) keyset = await wallet1._get_keys(wallet1.url)
assert keyset.id is not None assert keyset.id is not None
assert type(keyset.id) == str assert type(keyset.id) == str
@@ -61,7 +61,7 @@ async def test_get_keys(wallet1: Wallet):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_get_keyset(wallet1: Wallet): async def test_get_keyset(wallet1: Wallet):
assert wallet1.keys.public_keys 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 # let's get the keys first so we can get a keyset ID that we use later
keys1 = await wallet1._get_keys(wallet1.url) keys1 = await wallet1._get_keys(wallet1.url)
# gets the keys of a specific keyset # 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""" """Mint amount that is not part in 2^n"""
await assert_err( await assert_err(
wallet1.mint_amounts([1, 2, 3]), 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}.",
) )