mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 10:34:20 +01:00
Mint: Fakewallet support for USD (#488)
* fakewallet usd wip * FakeWallet support for USD * fix api return for receive * use MINT_BACKEND_BOLT11_SAT everywhere
This commit is contained in:
@@ -55,9 +55,9 @@ MINT_DERIVATION_PATH="m/0'/0'/0'"
|
||||
|
||||
MINT_DATABASE=data/mint
|
||||
|
||||
# Lightning
|
||||
# Funding source backends
|
||||
# Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, BlinkWallet, LNbitsWallet, StrikeUSDWallet
|
||||
MINT_LIGHTNING_BACKEND=FakeWallet
|
||||
MINT_BACKEND_BOLT11_SAT=FakeWallet
|
||||
|
||||
# for use with LNbitsWallet
|
||||
MINT_LNBITS_ENDPOINT=https://legend.lnbits.com
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
poetry-version: ${{ inputs.poetry-version }}
|
||||
- name: Run tests
|
||||
env:
|
||||
MINT_LIGHTNING_BACKEND: FakeWallet
|
||||
MINT_BACKEND_BOLT11_SAT: FakeWallet
|
||||
WALLET_NAME: test_wallet
|
||||
MINT_HOST: localhost
|
||||
MINT_PORT: 3337
|
||||
|
||||
2
Makefile
2
Makefile
@@ -30,7 +30,7 @@ test:
|
||||
test-lndrest:
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DEBUG=true \
|
||||
MINT_LIGHTNING_BACKEND=LndRestWallet \
|
||||
MINT_BACKEND_BOLT11_SAT=LndRestWallet \
|
||||
MINT_LND_REST_ENDPOINT=https://localhost:8081/ \
|
||||
MINT_LND_REST_CERT=../cashu-regtest-enviroment/data/lnd-3/tls.cert \
|
||||
MINT_LND_REST_MACAROON=../cashu-regtest-enviroment/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon \
|
||||
|
||||
15
README.md
15
README.md
@@ -7,7 +7,7 @@
|
||||
|
||||
*Disclaimer: The author is NOT a cryptographer and this work has not been reviewed. This means that there is very likely a fatal flaw somewhere. Cashu is still experimental and not production-ready.*
|
||||
|
||||
Cashu is an Ecash implementation based on David Wagner's variant of Chaumian blinding ([protocol specs](https://github.com/cashubtc/nuts)). Token logic based on [minicash](https://github.com/phyro/minicash) ([description](https://gist.github.com/phyro/935badc682057f418842c72961cf096c)) which implements a [Blind Diffie-Hellman Key Exchange](https://cypherpunks.venona.com/date/1996/03/msg01848.html) scheme written down [here](https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406). The database mechanics in Cashu Nutshell and the Lightning backend uses parts from [LNbits](https://github.com/lnbits/lnbits-legend).
|
||||
Cashu is an Ecash implementation based on David Wagner's variant of Chaumian blinding ([protocol specs](https://github.com/cashubtc/nuts)). Token logic based on [minicash](https://github.com/phyro/minicash) ([description](https://gist.github.com/phyro/935badc682057f418842c72961cf096c)) which implements a [Blind Diffie-Hellman Key Exchange](https://cypherpunks.venona.com/date/1996/03/msg01848.html) scheme written down [here](https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406).
|
||||
|
||||
<p align="center">
|
||||
<a href="#the-cashu-protocol">Cashu protocol</a> ·
|
||||
@@ -169,12 +169,19 @@ You can find the API docs at [http://localhost:4448/docs](http://localhost:4448/
|
||||
# Running a mint
|
||||
This command runs the mint on your local computer. Skip this step if you want to use the [public test mint](#test-instance) instead.
|
||||
|
||||
Before you can run your own mint, make sure to enable a Lightning backend in `MINT_LIGHTNING_BACKEND` and set `MINT_PRIVATE_KEY` in your `.env` file.
|
||||
## Docker
|
||||
|
||||
```
|
||||
docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.2 poetry run mint
|
||||
```
|
||||
|
||||
## From this repository
|
||||
Before you can run your own mint, make sure to enable a Lightning backend in `MINT_BACKEND_BOLT11_SAT` and set `MINT_PRIVATE_KEY` in your `.env` file.
|
||||
```bash
|
||||
poetry run mint
|
||||
```
|
||||
|
||||
For testing, you can use Nutshell without a Lightning backend by setting `MINT_LIGHTNING_BACKEND=FakeWallet` in the `.env` file.
|
||||
For testing, you can use Nutshell without a Lightning backend by setting `MINT_BACKEND_BOLT11_SAT=FakeWallet` in the `.env` file.
|
||||
|
||||
|
||||
# Running tests
|
||||
@@ -185,7 +192,7 @@ poetry install --with dev
|
||||
|
||||
Then, make sure to set up your mint's `.env` file to use a fake Lightning backend and disable Tor:
|
||||
```bash
|
||||
MINT_LIGHTNING_BACKEND=FakeWallet
|
||||
MINT_BACKEND_BOLT11_SAT=FakeWallet
|
||||
TOR=FALSE
|
||||
```
|
||||
You can run the tests with
|
||||
|
||||
@@ -55,7 +55,11 @@ class MintSettings(CashuSettings):
|
||||
mint_derivation_path_list: List[str] = Field(default=[])
|
||||
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_lightning_backend: str = Field(default="") # deprecated
|
||||
mint_backend_bolt11_sat: str = Field(default="")
|
||||
mint_backend_bolt11_usd: str = Field(default="")
|
||||
|
||||
mint_database: str = Field(default="data/mint")
|
||||
mint_test_database: str = Field(default="test_data/test_mint")
|
||||
mint_peg_out_only: bool = Field(
|
||||
@@ -204,5 +208,9 @@ def startup_settings_tasks():
|
||||
if settings.socks_host and settings.socks_port:
|
||||
settings.socks_proxy = f"socks5://{settings.socks_host}:{settings.socks_port}"
|
||||
|
||||
# backwards compatibility: set mint_backend_bolt11_sat from mint_lightning_backend
|
||||
if settings.mint_lightning_backend:
|
||||
settings.mint_backend_bolt11_sat = settings.mint_lightning_backend
|
||||
|
||||
|
||||
startup_settings_tasks()
|
||||
|
||||
@@ -7,5 +7,5 @@ from .lnbits import LNbitsWallet # noqa: F401
|
||||
from .lndrest import LndRestWallet # noqa: F401
|
||||
from .strike import StrikeUSDWallet # noqa: F401
|
||||
|
||||
if settings.mint_lightning_backend is None:
|
||||
raise Exception("MINT_LIGHTNING_BACKEND not configured")
|
||||
if settings.mint_backend_bolt11_sat is None or settings.mint_backend_bolt11_usd is None:
|
||||
raise Exception("MINT_BACKEND_BOLT11_SAT or MINT_BACKEND_BOLT11_USD not set")
|
||||
|
||||
@@ -62,12 +62,17 @@ class PaymentStatus(BaseModel):
|
||||
|
||||
|
||||
class LightningBackend(ABC):
|
||||
units: set[Unit]
|
||||
supported_units: set[Unit]
|
||||
unit: Unit
|
||||
|
||||
def assert_unit_supported(self, unit: Unit):
|
||||
if unit not in self.units:
|
||||
if unit not in self.supported_units:
|
||||
raise Unsupported(f"Unit {unit} is not supported")
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, unit: Unit, **kwargs):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def status(self) -> Coroutine[None, None, StatusResponse]:
|
||||
pass
|
||||
|
||||
@@ -36,7 +36,6 @@ class BlinkWallet(LightningBackend):
|
||||
Create API Key at: https://dashboard.blink.sv/
|
||||
"""
|
||||
|
||||
units = set([Unit.sat, Unit.usd])
|
||||
wallet_ids: Dict[Unit, str] = {}
|
||||
endpoint = "https://api.blink.sv/graphql"
|
||||
invoice_statuses = {"PENDING": None, "PAID": True, "EXPIRED": False}
|
||||
@@ -47,7 +46,12 @@ class BlinkWallet(LightningBackend):
|
||||
}
|
||||
payment_statuses = {"SUCCESS": True, "PENDING": None, "FAILURE": False}
|
||||
|
||||
def __init__(self):
|
||||
supported_units = set([Unit.sat, Unit.msat])
|
||||
unit = Unit.sat
|
||||
|
||||
def __init__(self, unit: Unit = Unit.sat, **kwargs):
|
||||
self.assert_unit_supported(unit)
|
||||
self.unit = unit
|
||||
assert settings.mint_blink_key, "MINT_BLINK_KEY not set"
|
||||
self.client = httpx.AsyncClient(
|
||||
verify=not settings.debug,
|
||||
|
||||
@@ -26,9 +26,12 @@ from .macaroon import load_macaroon
|
||||
|
||||
|
||||
class CoreLightningRestWallet(LightningBackend):
|
||||
units = set([Unit.sat, Unit.msat])
|
||||
supported_units = set([Unit.sat, Unit.msat])
|
||||
unit = Unit.sat
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, unit: Unit = Unit.sat, **kwargs):
|
||||
self.assert_unit_supported(unit)
|
||||
self.unit = unit
|
||||
macaroon = settings.mint_corelightning_rest_macaroon
|
||||
assert macaroon, "missing cln-rest macaroon"
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import asyncio
|
||||
import hashlib
|
||||
import math
|
||||
import random
|
||||
from datetime import datetime
|
||||
from os import urandom
|
||||
@@ -28,7 +29,7 @@ from .base import (
|
||||
|
||||
|
||||
class FakeWallet(LightningBackend):
|
||||
units = set([Unit.sat, Unit.msat])
|
||||
fake_btc_price = 1e8 / 1337
|
||||
queue: asyncio.Queue[Bolt11] = asyncio.Queue(0)
|
||||
payment_secrets: Dict[str, str] = dict()
|
||||
paid_invoices: Set[str] = set()
|
||||
@@ -41,6 +42,13 @@ class FakeWallet(LightningBackend):
|
||||
32,
|
||||
).hex()
|
||||
|
||||
supported_units = set([Unit.sat, Unit.msat, Unit.usd])
|
||||
unit = Unit.sat
|
||||
|
||||
def __init__(self, unit: Unit = Unit.sat, **kwargs):
|
||||
self.assert_unit_supported(unit)
|
||||
self.unit = unit
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
return StatusResponse(error_message=None, balance=1337)
|
||||
|
||||
@@ -80,9 +88,19 @@ class FakeWallet(LightningBackend):
|
||||
|
||||
self.payment_secrets[payment_hash] = secret
|
||||
|
||||
amount_msat = 0
|
||||
if self.unit == Unit.sat:
|
||||
amount_msat = MilliSatoshi(amount.to(Unit.msat, round="up").amount)
|
||||
elif self.unit == Unit.usd:
|
||||
amount_msat = MilliSatoshi(
|
||||
math.ceil(amount.amount / self.fake_btc_price * 1e9)
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
bolt11 = Bolt11(
|
||||
currency="bc",
|
||||
amount_msat=MilliSatoshi(amount.to(Unit.msat, round="up").amount),
|
||||
amount_msat=amount_msat,
|
||||
date=int(datetime.now().timestamp()),
|
||||
tags=tags,
|
||||
)
|
||||
@@ -137,10 +155,19 @@ class FakeWallet(LightningBackend):
|
||||
async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse:
|
||||
invoice_obj = decode(bolt11)
|
||||
assert invoice_obj.amount_msat, "invoice has no amount."
|
||||
amount_msat = int(invoice_obj.amount_msat)
|
||||
fees_msat = fee_reserve(amount_msat)
|
||||
fees = Amount(unit=Unit.msat, amount=fees_msat)
|
||||
amount = Amount(unit=Unit.msat, amount=amount_msat)
|
||||
|
||||
if self.unit == Unit.sat:
|
||||
amount_msat = int(invoice_obj.amount_msat)
|
||||
fees_msat = fee_reserve(amount_msat)
|
||||
fees = Amount(unit=Unit.msat, amount=fees_msat)
|
||||
amount = Amount(unit=Unit.msat, amount=amount_msat)
|
||||
elif self.unit == Unit.usd:
|
||||
amount_usd = math.ceil(invoice_obj.amount_msat / 1e9 * self.fake_btc_price)
|
||||
amount = Amount(unit=Unit.usd, amount=amount_usd)
|
||||
fees = Amount(unit=Unit.usd, amount=1)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
return PaymentQuoteResponse(
|
||||
checking_id=invoice_obj.payment_hash, fee=fees, amount=amount
|
||||
)
|
||||
|
||||
@@ -22,9 +22,12 @@ from .base import (
|
||||
class LNbitsWallet(LightningBackend):
|
||||
"""https://github.com/lnbits/lnbits"""
|
||||
|
||||
units = set([Unit.sat])
|
||||
supported_units = set([Unit.sat])
|
||||
unit = Unit.sat
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, unit: Unit = Unit.sat, **kwargs):
|
||||
self.assert_unit_supported(unit)
|
||||
self.unit = unit
|
||||
self.endpoint = settings.mint_lnbits_endpoint
|
||||
self.client = httpx.AsyncClient(
|
||||
verify=not settings.debug,
|
||||
|
||||
@@ -27,9 +27,12 @@ from .macaroon import load_macaroon
|
||||
class LndRestWallet(LightningBackend):
|
||||
"""https://api.lightning.community/rest/index.html#lnd-rest-api-reference"""
|
||||
|
||||
units = set([Unit.sat, Unit.msat])
|
||||
supported_units = set([Unit.sat, Unit.msat])
|
||||
unit = Unit.sat
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, unit: Unit = Unit.sat, **kwargs):
|
||||
self.assert_unit_supported(unit)
|
||||
self.unit = unit
|
||||
endpoint = settings.mint_lnd_rest_endpoint
|
||||
cert = settings.mint_lnd_rest_cert
|
||||
|
||||
|
||||
@@ -19,9 +19,11 @@ from .base import (
|
||||
class StrikeUSDWallet(LightningBackend):
|
||||
"""https://github.com/lnbits/lnbits"""
|
||||
|
||||
units = [Unit.usd]
|
||||
supported_units = [Unit.usd]
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, unit: Unit = Unit.usd, **kwargs):
|
||||
self.assert_unit_supported(unit)
|
||||
self.unit = unit
|
||||
self.endpoint = "https://api.strike.me"
|
||||
|
||||
# bearer auth with settings.mint_strike_key
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import asyncio
|
||||
import copy
|
||||
import math
|
||||
import time
|
||||
from typing import Dict, List, Mapping, Optional, Tuple
|
||||
|
||||
@@ -493,10 +492,10 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
if mint_quote:
|
||||
# internal transaction, validate and return amount from
|
||||
# associated mint quote and demand zero fees
|
||||
assert (
|
||||
Amount(unit, mint_quote.amount).to(Unit.msat).amount
|
||||
== invoice_obj.amount_msat
|
||||
), "amounts do not match"
|
||||
# assert (
|
||||
# Amount(unit, mint_quote.amount).to(Unit.msat).amount
|
||||
# == invoice_obj.amount_msat
|
||||
# ), "amounts do not match"
|
||||
assert request == mint_quote.request, "bolt11 requests do not match"
|
||||
assert mint_quote.unit == melt_quote.unit, "units do not match"
|
||||
assert mint_quote.method == method.name, "methods do not match"
|
||||
@@ -506,7 +505,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
payment_quote = PaymentQuoteResponse(
|
||||
checking_id=mint_quote.checking_id,
|
||||
amount=Amount(unit, mint_quote.amount),
|
||||
fee=Amount(unit=Unit.msat, amount=0),
|
||||
fee=Amount(unit, amount=0),
|
||||
)
|
||||
logger.info(
|
||||
f"Issuing internal melt quote: {request} ->"
|
||||
@@ -622,11 +621,12 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
bolt11_request = melt_quote.request
|
||||
invoice_obj = bolt11.decode(bolt11_request)
|
||||
assert invoice_obj.amount_msat, "invoice has no amount."
|
||||
invoice_amount_sat = math.ceil(invoice_obj.amount_msat / 1000)
|
||||
assert (
|
||||
Amount(Unit[melt_quote.unit], mint_quote.amount).to(Unit.sat).amount
|
||||
== invoice_amount_sat
|
||||
), "amounts do not match"
|
||||
# invoice_amount_sat = math.ceil(invoice_obj.amount_msat / 1000)
|
||||
# assert (
|
||||
# Amount(Unit[melt_quote.unit], mint_quote.amount).to(Unit.sat).amount
|
||||
# == invoice_amount_sat
|
||||
# ), "amounts do not match"
|
||||
assert mint_quote.amount == melt_quote.amount, "amounts do not match"
|
||||
assert bolt11_request == mint_quote.request, "bolt11 requests do not match"
|
||||
assert mint_quote.unit == melt_quote.unit, "units do not match"
|
||||
assert mint_quote.method == melt_quote.method, "methods do not match"
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
import asyncio
|
||||
import importlib
|
||||
from typing import Dict
|
||||
|
||||
from loguru import logger
|
||||
|
||||
@@ -10,6 +11,7 @@ from ..core.base import Method, Unit
|
||||
from ..core.db import Database
|
||||
from ..core.migrations import migrate_databases
|
||||
from ..core.settings import settings
|
||||
from ..lightning.base import LightningBackend
|
||||
from ..mint import migrations
|
||||
from ..mint.crud import LedgerCrudSqlite
|
||||
from ..mint.ledger import Ledger
|
||||
@@ -31,23 +33,24 @@ for key, value in settings.dict().items():
|
||||
logger.debug(f"{key}: {value}")
|
||||
|
||||
wallets_module = importlib.import_module("cashu.lightning")
|
||||
lightning_backend = getattr(wallets_module, settings.mint_lightning_backend)()
|
||||
|
||||
assert settings.mint_private_key is not None, "No mint private key set."
|
||||
backends: Dict[Method, Dict[Unit, LightningBackend]] = {}
|
||||
if settings.mint_backend_bolt11_sat:
|
||||
backend_bolt11_sat = getattr(wallets_module, settings.mint_backend_bolt11_sat)(
|
||||
unit=Unit.sat
|
||||
)
|
||||
backends.setdefault(Method.bolt11, {})[Unit.sat] = backend_bolt11_sat
|
||||
if settings.mint_backend_bolt11_usd:
|
||||
backend_bolt11_usd = getattr(wallets_module, settings.mint_backend_bolt11_usd)(
|
||||
unit=Unit.usd
|
||||
)
|
||||
backends.setdefault(Method.bolt11, {})[Unit.usd] = backend_bolt11_usd
|
||||
if not backends:
|
||||
raise Exception("No backends are set.")
|
||||
|
||||
if not settings.mint_private_key:
|
||||
raise Exception("No mint private key is set.")
|
||||
|
||||
# strike_backend = getattr(wallets_module, "StrikeUSDWallet")()
|
||||
# backends = {
|
||||
# Method.bolt11: {Unit.sat: lightning_backend, Unit.usd: strike_backend},
|
||||
# }
|
||||
# backends = {
|
||||
# Method.bolt11: {Unit.sat: lightning_backend, Unit.msat: lightning_backend},
|
||||
# }
|
||||
# backends = {
|
||||
# Method.bolt11: {Unit.sat: lightning_backend, Unit.msat: lightning_backend,
|
||||
# }
|
||||
backends = {
|
||||
Method.bolt11: {Unit.sat: lightning_backend},
|
||||
}
|
||||
ledger = Ledger(
|
||||
db=Database("mint", settings.mint_database),
|
||||
seed=settings.mint_private_key,
|
||||
|
||||
@@ -265,10 +265,9 @@ async def receive_command(
|
||||
if token:
|
||||
tokenObj: TokenV3 = deserialize_token_from_string(token)
|
||||
await verify_mints(wallet, tokenObj)
|
||||
balance = await receive(wallet, tokenObj)
|
||||
await receive(wallet, tokenObj)
|
||||
elif nostr:
|
||||
await receive_nostr(wallet)
|
||||
balance = wallet.available_balance
|
||||
elif all:
|
||||
reserved_proofs = await get_reserved_proofs(wallet.db)
|
||||
balance = None
|
||||
@@ -278,10 +277,10 @@ async def receive_command(
|
||||
token = await wallet.serialize_proofs(proofs)
|
||||
tokenObj = deserialize_token_from_string(token)
|
||||
await verify_mints(wallet, tokenObj)
|
||||
balance = await receive(wallet, tokenObj)
|
||||
await receive(wallet, tokenObj)
|
||||
else:
|
||||
raise Exception("enter token or use either flag --nostr or --all.")
|
||||
assert balance
|
||||
balance = wallet.available_balance
|
||||
return ReceiveResponse(initial_balance=initial_balance, balance=balance)
|
||||
|
||||
|
||||
@@ -359,15 +358,17 @@ async def pending(
|
||||
reserved_date = datetime.utcfromtimestamp(
|
||||
int(grouped_proofs[0].time_reserved) # type: ignore
|
||||
).strftime("%Y-%m-%d %H:%M:%S")
|
||||
result.update({
|
||||
f"{i}": {
|
||||
"amount": sum_proofs(grouped_proofs),
|
||||
"time": reserved_date,
|
||||
"ID": key,
|
||||
"token": token,
|
||||
"mint": mint,
|
||||
result.update(
|
||||
{
|
||||
f"{i}": {
|
||||
"amount": sum_proofs(grouped_proofs),
|
||||
"time": reserved_date,
|
||||
"ID": key,
|
||||
"token": token,
|
||||
"mint": mint,
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
return PendingResponse(pending_token=result)
|
||||
|
||||
|
||||
@@ -412,14 +413,16 @@ async def wallets():
|
||||
if w == wallet.name:
|
||||
active_wallet = True
|
||||
if active_wallet:
|
||||
result.update({
|
||||
f"{w}": {
|
||||
"balance": sum_proofs(wallet.proofs),
|
||||
"available": sum_proofs([
|
||||
p for p in wallet.proofs if not p.reserved
|
||||
]),
|
||||
result.update(
|
||||
{
|
||||
f"{w}": {
|
||||
"balance": sum_proofs(wallet.proofs),
|
||||
"available": sum_proofs(
|
||||
[p for p in wallet.proofs if not p.reserved]
|
||||
),
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return WalletsResponse(wallets=result)
|
||||
|
||||
@@ -184,7 +184,7 @@ async def cli(ctx: Context, host: str, walletname: str, unit: str, tests: bool):
|
||||
async def pay(ctx: Context, invoice: str, yes: bool):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await wallet.load_mint()
|
||||
print_balance(ctx)
|
||||
await print_balance(ctx)
|
||||
quote = await wallet.get_pay_amount_with_fees(invoice)
|
||||
logger.debug(f"Quote: {quote}")
|
||||
total_amount = quote.amount + quote.fee_reserve
|
||||
@@ -219,7 +219,7 @@ async def pay(ctx: Context, invoice: str, yes: bool):
|
||||
print(f" (Preimage: {melt_response.payment_preimage}).")
|
||||
else:
|
||||
print(".")
|
||||
print_balance(ctx)
|
||||
await print_balance(ctx)
|
||||
|
||||
|
||||
@cli.command("invoice", help="Create Lighting invoice.")
|
||||
@@ -242,11 +242,12 @@ async def pay(ctx: Context, invoice: str, yes: bool):
|
||||
)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def invoice(ctx: Context, amount: int, id: str, split: int, no_check: bool):
|
||||
async def invoice(ctx: Context, amount: float, id: str, split: int, no_check: bool):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await wallet.load_mint()
|
||||
print_balance(ctx)
|
||||
await print_balance(ctx)
|
||||
amount = int(amount * 100) if wallet.unit == Unit.usd else int(amount)
|
||||
print(f"Requesting invoice for {wallet.unit.str(amount)} {wallet.unit}.")
|
||||
# in case the user wants a specific split, we create a list of amounts
|
||||
optional_split = None
|
||||
if split:
|
||||
@@ -305,7 +306,7 @@ async def invoice(ctx: Context, amount: int, id: str, split: int, no_check: bool
|
||||
elif amount and id:
|
||||
await wallet.mint(amount, split=optional_split, id=id)
|
||||
print("")
|
||||
print_balance(ctx)
|
||||
await print_balance(ctx)
|
||||
return
|
||||
|
||||
|
||||
@@ -474,7 +475,7 @@ async def send_command(
|
||||
await send_nostr(
|
||||
wallet, amount=amount, pubkey=nostr or nopt, verbose=verbose, yes=yes
|
||||
)
|
||||
print_balance(ctx)
|
||||
await print_balance(ctx)
|
||||
|
||||
|
||||
@cli.command("receive", help="Receive tokens.")
|
||||
@@ -508,8 +509,9 @@ async def receive_cli(
|
||||
mint_url, os.path.join(settings.cashu_dir, wallet.name)
|
||||
)
|
||||
await verify_mint(mint_wallet, mint_url)
|
||||
receive_wallet = await receive(wallet, tokenObj)
|
||||
ctx.obj["WALLET"] = receive_wallet
|
||||
|
||||
await receive(wallet, tokenObj)
|
||||
elif nostr:
|
||||
await receive_nostr(wallet)
|
||||
# exit on keypress
|
||||
@@ -530,11 +532,12 @@ async def receive_cli(
|
||||
mint_url, os.path.join(settings.cashu_dir, wallet.name)
|
||||
)
|
||||
await verify_mint(mint_wallet, mint_url)
|
||||
await receive(wallet, tokenObj)
|
||||
receive_wallet = await receive(wallet, tokenObj)
|
||||
ctx.obj["WALLET"] = receive_wallet
|
||||
else:
|
||||
print("Error: enter token or use either flag --nostr or --all.")
|
||||
return
|
||||
print_balance(ctx)
|
||||
await print_balance(ctx)
|
||||
|
||||
|
||||
@cli.command("burn", help="Burn spent tokens.")
|
||||
@@ -586,7 +589,7 @@ async def burn(ctx: Context, token: str, all: bool, force: bool, delete: str):
|
||||
for i in range(0, len(proofs), settings.proofs_batch_size)
|
||||
]:
|
||||
await wallet.invalidate(_proofs, check_spendable=True)
|
||||
print_balance(ctx)
|
||||
await print_balance(ctx)
|
||||
|
||||
|
||||
@cli.command("pending", help="Show pending tokens.")
|
||||
@@ -865,7 +868,7 @@ async def restore(ctx: Context, to: int, batch: int):
|
||||
|
||||
await wallet.restore_wallet_from_mnemonic(mnemonic, to=to, batch=batch)
|
||||
await wallet.load_proofs()
|
||||
print_balance(ctx)
|
||||
await print_balance(ctx)
|
||||
|
||||
|
||||
@cli.command("selfpay", help="Refresh tokens.")
|
||||
|
||||
@@ -10,8 +10,9 @@ from ...wallet.crud import get_keysets
|
||||
from ...wallet.wallet import Wallet as Wallet
|
||||
|
||||
|
||||
def print_balance(ctx: Context):
|
||||
async def print_balance(ctx: Context):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await wallet.load_proofs(reload=True, unit=wallet.unit)
|
||||
print(f"Balance: {wallet.unit.str(wallet.available_balance)}")
|
||||
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ async def list_mints(wallet: Wallet):
|
||||
return mints
|
||||
|
||||
|
||||
async def redeem_TokenV3_multimint(wallet: Wallet, token: TokenV3):
|
||||
async def redeem_TokenV3_multimint(wallet: Wallet, token: TokenV3) -> Wallet:
|
||||
"""
|
||||
Helper function to iterate thruogh a token with multiple mints and redeem them from
|
||||
these mints one keyset at a time.
|
||||
@@ -58,6 +58,9 @@ async def redeem_TokenV3_multimint(wallet: Wallet, token: TokenV3):
|
||||
_, _ = await mint_wallet.redeem(redeem_proofs)
|
||||
print(f"Received {mint_wallet.unit.str(sum_proofs(redeem_proofs))}")
|
||||
|
||||
# return the last mint_wallet
|
||||
return mint_wallet
|
||||
|
||||
|
||||
def serialize_TokenV2_to_TokenV3(tokenv2: TokenV2):
|
||||
"""Helper function to receive legacy TokenV2 tokens.
|
||||
@@ -120,7 +123,7 @@ def deserialize_token_from_string(token: str) -> TokenV3:
|
||||
async def receive(
|
||||
wallet: Wallet,
|
||||
tokenObj: TokenV3,
|
||||
):
|
||||
) -> Wallet:
|
||||
logger.debug(f"receive: {tokenObj}")
|
||||
proofs = [p for t in tokenObj.token for p in t.proofs]
|
||||
|
||||
@@ -128,7 +131,7 @@ async def receive(
|
||||
|
||||
if includes_mint_info:
|
||||
# redeem tokens with new wallet instances
|
||||
await redeem_TokenV3_multimint(
|
||||
mint_wallet = await redeem_TokenV3_multimint(
|
||||
wallet,
|
||||
tokenObj,
|
||||
)
|
||||
@@ -154,7 +157,7 @@ async def receive(
|
||||
|
||||
# reload main wallet so the balance updates
|
||||
await wallet.load_proofs(reload=True)
|
||||
return wallet.available_balance
|
||||
return mint_wallet
|
||||
|
||||
|
||||
async def send(
|
||||
|
||||
@@ -8,7 +8,7 @@ services:
|
||||
ports:
|
||||
- "3338:3338"
|
||||
environment:
|
||||
- MINT_LIGHTNING_BACKEND=FakeWallet
|
||||
- MINT_BACKEND_BOLT11_SAT=FakeWallet
|
||||
- MINT_LISTEN_HOST=0.0.0.0
|
||||
- MINT_LISTEN_PORT=3338
|
||||
- MINT_PRIVATE_KEY=TEST_PRIVATE_KEY
|
||||
|
||||
@@ -25,7 +25,7 @@ async def get_random_invoice_data():
|
||||
|
||||
|
||||
wallets_module = importlib.import_module("cashu.lightning")
|
||||
wallet_class = getattr(wallets_module, settings.mint_lightning_backend)
|
||||
wallet_class = getattr(wallets_module, settings.mint_backend_bolt11_sat)
|
||||
WALLET = wallet_class()
|
||||
is_fake: bool = WALLET.__class__.__name__ == "FakeWallet"
|
||||
is_regtest: bool = not is_fake
|
||||
|
||||
Reference in New Issue
Block a user