mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-23 19:54:18 +01:00
Merge branch 'main' into Adjusted_Documentation
This commit is contained in:
161
cashu/wallet/cli.py
Executable file → Normal file
161
cashu/wallet/cli.py
Executable file → Normal file
@@ -19,7 +19,18 @@ from loguru import logger
|
||||
from cashu.core.base import Proof
|
||||
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, VERSION
|
||||
from cashu.core.settings import (
|
||||
CASHU_DIR,
|
||||
DEBUG,
|
||||
ENV_FILE,
|
||||
LIGHTNING,
|
||||
MINT_URL,
|
||||
SOCKS_HOST,
|
||||
SOCKS_PORT,
|
||||
TOR,
|
||||
VERSION,
|
||||
)
|
||||
from cashu.tor.tor import TorProxy
|
||||
from cashu.wallet import migrations
|
||||
from cashu.wallet.crud import (
|
||||
get_lightning_invoices,
|
||||
@@ -60,6 +71,18 @@ def cli(ctx, host: str, walletname: str):
|
||||
ctx.obj["HOST"] = host
|
||||
ctx.obj["WALLET_NAME"] = walletname
|
||||
wallet = Wallet(ctx.obj["HOST"], os.path.join(CASHU_DIR, walletname))
|
||||
|
||||
if 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. Please 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}"
|
||||
else:
|
||||
error_str += (
|
||||
f"Ceate a new Cashu config file here: {os.path.join(CASHU_DIR, '.env')}"
|
||||
)
|
||||
raise Exception(error_str)
|
||||
|
||||
ctx.obj["WALLET"] = wallet
|
||||
asyncio.run(init_wallet(wallet))
|
||||
pass
|
||||
@@ -74,6 +97,35 @@ def coro(f):
|
||||
return wrapper
|
||||
|
||||
|
||||
@cli.command("pay", help="Pay Lightning invoice.")
|
||||
@click.argument("invoice", type=str)
|
||||
@click.option(
|
||||
"--yes", "-y", default=False, is_flag=True, help="Skip confirmation.", type=bool
|
||||
)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def pay(ctx, invoice: str, yes: bool):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await wallet.load_mint()
|
||||
wallet.status()
|
||||
amount, fees = await wallet.get_pay_amount_with_fees(invoice)
|
||||
if not yes:
|
||||
click.confirm(
|
||||
f"Pay {amount - fees} sat ({amount} sat incl. fees)?",
|
||||
abort=True,
|
||||
default=True,
|
||||
)
|
||||
|
||||
print(f"Paying Lightning invoice ...")
|
||||
assert amount > 0, "amount is not positive"
|
||||
if wallet.available_balance < amount:
|
||||
print("Error: Balance too low.")
|
||||
return
|
||||
_, send_proofs = await wallet.split_to_send(wallet.proofs, amount)
|
||||
await wallet.pay_lightning(send_proofs, invoice)
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("invoice", help="Create Lighting invoice.")
|
||||
@click.argument("amount", type=int)
|
||||
@click.option("--hash", default="", help="Hash of the paid invoice.", type=str)
|
||||
@@ -120,39 +172,18 @@ async def invoice(ctx, amount: int, hash: str):
|
||||
return
|
||||
|
||||
|
||||
@cli.command("pay", help="Pay Lightning invoice.")
|
||||
@click.argument("invoice", type=str)
|
||||
@cli.command("balance", help="Balance.")
|
||||
@click.option(
|
||||
"--yes", "-y", default=False, is_flag=True, help="Skip confirmation.", type=bool
|
||||
"--verbose",
|
||||
"-v",
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help="Show pending tokens as well.",
|
||||
type=bool,
|
||||
)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def pay(ctx, invoice: str, yes: bool):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await wallet.load_mint()
|
||||
wallet.status()
|
||||
amount, fees = await wallet.get_pay_amount_with_fees(invoice)
|
||||
if not yes:
|
||||
click.confirm(
|
||||
f"Pay {amount - fees} sat ({amount} sat incl. fees)?",
|
||||
abort=True,
|
||||
default=True,
|
||||
)
|
||||
|
||||
print(f"Paying Lightning invoice ...")
|
||||
assert amount > 0, "amount is not positive"
|
||||
if wallet.available_balance < amount:
|
||||
print("Error: Balance too low.")
|
||||
return
|
||||
_, send_proofs = await wallet.split_to_send(wallet.proofs, amount)
|
||||
await wallet.pay_lightning(send_proofs, invoice)
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("balance", help="Balance.")
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def balance(ctx):
|
||||
async def balance(ctx, verbose):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
keyset_balances = wallet.balance_per_keyset()
|
||||
if len(keyset_balances) > 1:
|
||||
@@ -163,14 +194,17 @@ async def balance(ctx):
|
||||
f"Keyset: {k or 'undefined'} Balance: {v['balance']} sat (available: {v['available']} sat)"
|
||||
)
|
||||
print("")
|
||||
print(
|
||||
f"Balance: {wallet.balance} sat (available: {wallet.available_balance} sat in {len([p for p in wallet.proofs if not p.reserved])} tokens)"
|
||||
)
|
||||
if verbose:
|
||||
print(
|
||||
f"Balance: {wallet.balance} sat (available: {wallet.available_balance} sat in {len([p for p in wallet.proofs if not p.reserved])} tokens)"
|
||||
)
|
||||
else:
|
||||
print(f"Balance: {wallet.available_balance} sat")
|
||||
|
||||
|
||||
@cli.command("send", help="Send coins.")
|
||||
@cli.command("send", help="Send tokens.")
|
||||
@click.argument("amount", type=int)
|
||||
@click.option("--lock", "-l", default=None, help="Lock coins (P2SH).", type=str)
|
||||
@click.option("--lock", "-l", default=None, help="Lock tokens (P2SH).", type=str)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def send(ctx, amount: int, lock: str):
|
||||
@@ -186,19 +220,19 @@ async def send(ctx, amount: int, lock: str):
|
||||
_, send_proofs = await wallet.split_to_send(
|
||||
wallet.proofs, amount, lock, set_reserved=True
|
||||
)
|
||||
coin = await wallet.serialize_proofs(
|
||||
token = await wallet.serialize_proofs(
|
||||
send_proofs, hide_secrets=True if lock and not p2sh else False
|
||||
)
|
||||
print(coin)
|
||||
print(token)
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("receive", help="Receive coins.")
|
||||
@click.argument("coin", type=str)
|
||||
@click.option("--lock", "-l", default=None, help="Unlock coins.", type=str)
|
||||
@cli.command("receive", help="Receive tokens.")
|
||||
@click.argument("token", type=str)
|
||||
@click.option("--lock", "-l", default=None, help="Unlock tokens.", type=str)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def receive(ctx, coin: str, lock: str):
|
||||
async def receive(ctx, token: str, lock: str):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await wallet.load_mint()
|
||||
wallet.status()
|
||||
@@ -215,25 +249,25 @@ async def receive(ctx, coin: str, lock: str):
|
||||
signature = p2shscripts[0].signature
|
||||
else:
|
||||
script, signature = None, None
|
||||
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(coin))]
|
||||
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(token))]
|
||||
_, _ = await wallet.redeem(proofs, scnd_script=script, scnd_siganture=signature)
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("burn", help="Burn spent coins.")
|
||||
@click.argument("coin", required=False, type=str)
|
||||
@click.option("--all", "-a", default=False, is_flag=True, help="Burn all spent coins.")
|
||||
@cli.command("burn", help="Burn spent tokens.")
|
||||
@click.argument("token", required=False, type=str)
|
||||
@click.option("--all", "-a", default=False, is_flag=True, help="Burn all spent tokens.")
|
||||
@click.option(
|
||||
"--force", "-f", default=False, is_flag=True, help="Force check on all coins."
|
||||
"--force", "-f", default=False, is_flag=True, help="Force check on all tokens."
|
||||
)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def burn(ctx, coin: str, all: bool, force: bool):
|
||||
async def burn(ctx, token: str, all: bool, force: bool):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await wallet.load_mint()
|
||||
if not (all or coin or force) or (coin and all):
|
||||
if not (all or token or force) or (token and all):
|
||||
print(
|
||||
"Error: enter a coin or use --all to burn all pending coins or --force to check all coins."
|
||||
"Error: enter a token or use --all to burn all pending tokens or --force to check all tokens."
|
||||
)
|
||||
return
|
||||
if all:
|
||||
@@ -244,12 +278,14 @@ async def burn(ctx, coin: str, all: bool, force: bool):
|
||||
proofs = wallet.proofs
|
||||
else:
|
||||
# check only the specified ones
|
||||
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(coin))]
|
||||
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(token))]
|
||||
wallet.status()
|
||||
await wallet.invalidate(proofs)
|
||||
wallet.status()
|
||||
|
||||
@cli.command("pending", help="Show pending coins.")
|
||||
|
||||
|
||||
@cli.command("pending", help="Show pending tokens.")
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def pending(ctx):
|
||||
@@ -263,8 +299,8 @@ async def pending(ctx):
|
||||
groupby(sorted_proofs, key=itemgetter("send_id"))
|
||||
):
|
||||
grouped_proofs = list(value)
|
||||
coin = await wallet.serialize_proofs(grouped_proofs)
|
||||
coin_hidden_secret = await wallet.serialize_proofs(
|
||||
token = await wallet.serialize_proofs(grouped_proofs)
|
||||
token_hidden_secret = await wallet.serialize_proofs(
|
||||
grouped_proofs, hide_secrets=True
|
||||
)
|
||||
reserved_date = datetime.utcfromtimestamp(
|
||||
@@ -273,7 +309,7 @@ async def pending(ctx):
|
||||
print(
|
||||
f"#{i} Amount: {sum_proofs(grouped_proofs)} sat Time: {reserved_date} ID: {key}\n"
|
||||
)
|
||||
print(f"With secret: {coin}\n\nSecretless: {coin_hidden_secret}\n")
|
||||
print(f"With secret: {token}\n\nSecretless: {token_hidden_secret}\n")
|
||||
print(f"--------------------------\n")
|
||||
wallet.status()
|
||||
|
||||
@@ -286,16 +322,16 @@ async def lock(ctx):
|
||||
p2shscript = await wallet.create_p2sh_lock()
|
||||
txin_p2sh_address = p2shscript.address
|
||||
print("---- Pay to script hash (P2SH) ----\n")
|
||||
print("Use a lock to receive coins that only you can unlock.")
|
||||
print("Use a lock to receive tokens that only you can unlock.")
|
||||
print("")
|
||||
print(f"Public receiving lock: P2SH:{txin_p2sh_address}")
|
||||
print("")
|
||||
print(
|
||||
f"Anyone can send coins to this lock:\n\ncashu send <amount> --lock P2SH:{txin_p2sh_address}"
|
||||
f"Anyone can send tokens to this lock:\n\ncashu send <amount> --lock P2SH:{txin_p2sh_address}"
|
||||
)
|
||||
print("")
|
||||
print(
|
||||
f"Only you can receive coins from this lock:\n\ncashu receive <coin> --lock P2SH:{txin_p2sh_address}\n"
|
||||
f"Only you can receive tokens from this lock:\n\ncashu receive <token> --lock P2SH:{txin_p2sh_address}\n"
|
||||
)
|
||||
|
||||
|
||||
@@ -313,7 +349,7 @@ async def locks(ctx):
|
||||
print(f"Script: {l.script}")
|
||||
print(f"Signature: {l.signature}")
|
||||
print("")
|
||||
print(f"Receive: cashu receive <coin> --lock P2SH:{l.address}")
|
||||
print(f"Receive: cashu receive <token> --lock P2SH:{l.address}")
|
||||
print("")
|
||||
print(f"--------------------------\n")
|
||||
else:
|
||||
@@ -386,10 +422,15 @@ async def wallets(ctx):
|
||||
@coro
|
||||
async def info(ctx):
|
||||
print(f"Version: {VERSION}")
|
||||
print(f"Debug: {DEBUG}")
|
||||
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}")
|
||||
print(f"Wallet: {ctx.obj['WALLET_NAME']}")
|
||||
if TOR:
|
||||
print(f"Tor enabled: {TOR}")
|
||||
if SOCKS_HOST:
|
||||
print(f"Socks proxy: {SOCKS_HOST}:{SOCKS_PORT}")
|
||||
print(f"Mint URL: {MINT_URL}")
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user