Skip to content

Commit

Permalink
Mint: Fakewallet support for USD (#488)
Browse files Browse the repository at this point in the history
* fakewallet usd wip

* FakeWallet support for USD

* fix api return for receive

* use MINT_BACKEND_BOLT11_SAT everywhere
  • Loading branch information
callebtc committed Mar 22, 2024
1 parent f462134 commit 3ba1e81
Show file tree
Hide file tree
Showing 21 changed files with 168 additions and 93 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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> ·
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
10 changes: 9 additions & 1 deletion cashu/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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()
4 changes: 2 additions & 2 deletions cashu/lightning/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
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")
9 changes: 7 additions & 2 deletions cashu/lightning/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,17 @@ def __str__(self) -> str:


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
Expand Down
10 changes: 7 additions & 3 deletions cashu/lightning/blink.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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,
Expand Down Expand Up @@ -297,7 +301,7 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
... on SettlementViaLn {
preImage
}
}
}
}
}
}
Expand Down
7 changes: 5 additions & 2 deletions cashu/lightning/corelightningrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@


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"

Expand Down
39 changes: 33 additions & 6 deletions cashu/lightning/fake.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import asyncio
import hashlib
import math
import random
from datetime import datetime
from os import urandom
Expand Down Expand Up @@ -28,7 +29,7 @@


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()
Expand All @@ -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)

Expand Down Expand Up @@ -80,9 +88,19 @@ async def create_invoice(

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,
)
Expand Down Expand Up @@ -137,10 +155,19 @@ async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
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
)
7 changes: 5 additions & 2 deletions cashu/lightning/lnbits.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
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,
Expand Down
7 changes: 5 additions & 2 deletions cashu/lightning/lndrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
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

Expand Down
6 changes: 4 additions & 2 deletions cashu/lightning/strike.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
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
Expand Down
22 changes: 11 additions & 11 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import asyncio
import copy
import math
import time
from typing import Dict, List, Mapping, Optional, Tuple

Expand Down Expand Up @@ -493,10 +492,10 @@ async def melt_quote(
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"
Expand All @@ -506,7 +505,7 @@ async def melt_quote(
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} ->"
Expand Down Expand Up @@ -622,11 +621,12 @@ async def melt_mint_settle_internally(self, melt_quote: MeltQuote) -> MeltQuote:
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"
Expand Down
33 changes: 18 additions & 15 deletions cashu/mint/startup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

import asyncio
import importlib
from typing import Dict

from loguru import logger

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
Expand All @@ -31,23 +33,24 @@
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,
Expand Down

0 comments on commit 3ba1e81

Please sign in to comment.