diff --git a/README.md b/README.md
index 2d49e35..bf9d857 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,17 @@
Cashu is an Ecash implementation based on David Wagner's variant of Chaumian blinding. 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 by Ruben Somsen [here](https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406). The database mechanics and the Lightning backend uses parts from [LNbits](https://github.com/lnbits/lnbits-legend).
+
+Quick links:
+Cashu client protocol ·
+Quick Install ·
+Manual install ·
+Configuration ·
+Using Cashu ·
+Run a mint
+
+
+
## Cashu client protocol
There are ongoing efforts to implement alternative Cashu clients that use the same protocol such as a [Cashu Javascript wallet](https://github.com/motorina0/cashu-js-wallet). If you are interested in helping with Cashu development, please see the [docs](docs/) for the notation and conventions used.
@@ -20,8 +31,8 @@ To update Cashu, use `pip install cashu -U`. If you have problems running the co
You can skip the entire next section about Poetry and jump right to [Using Cashu](#using-cashu).
-### Hard install: Poetry
-These steps help you install Python via pyenv and Poetry. If you already have Poetry running on your computer, you can skip this step and jump right to [Install Cashu](#install-cashu).
+## Hard install: Poetry
+These steps help you install Python via pyenv and Poetry. If you already have Poetry running on your computer, you can skip this step and jump right to [Install Cashu](#poetry-install-cashu).
#### Poetry: Prerequisites
@@ -168,7 +179,7 @@ Balance: 351 sat (Available: 351 sat in 7 tokens)
Balance: 339 sat (Available: 339 sat in 8 tokens)
```
-## Run a mint yourself
+# 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.
```bash
mint
diff --git a/core/__init__.py b/cashu/core/__init__.py
similarity index 100%
rename from core/__init__.py
rename to cashu/core/__init__.py
diff --git a/core/b_dhke.py b/cashu/core/b_dhke.py
similarity index 100%
rename from core/b_dhke.py
rename to cashu/core/b_dhke.py
diff --git a/core/base.py b/cashu/core/base.py
similarity index 100%
rename from core/base.py
rename to cashu/core/base.py
diff --git a/core/bolt11.py b/cashu/core/bolt11.py
similarity index 100%
rename from core/bolt11.py
rename to cashu/core/bolt11.py
diff --git a/core/db.py b/cashu/core/db.py
similarity index 100%
rename from core/db.py
rename to cashu/core/db.py
diff --git a/core/helpers.py b/cashu/core/helpers.py
similarity index 91%
rename from core/helpers.py
rename to cashu/core/helpers.py
index 75dab61..0f8c885 100644
--- a/core/helpers.py
+++ b/cashu/core/helpers.py
@@ -1,7 +1,7 @@
import asyncio
from functools import partial, wraps
-from core.settings import LIGHTNING_FEE_PERCENT, LIGHTNING_RESERVE_FEE_MIN
+from cashu.core.settings import LIGHTNING_FEE_PERCENT, LIGHTNING_RESERVE_FEE_MIN
def async_wrap(func):
diff --git a/core/migrations.py b/cashu/core/migrations.py
similarity index 96%
rename from core/migrations.py
rename to cashu/core/migrations.py
index 1133de6..6beaa91 100644
--- a/core/migrations.py
+++ b/cashu/core/migrations.py
@@ -2,7 +2,7 @@ import re
from loguru import logger
-from core.db import COCKROACH, POSTGRES, SQLITE, Database
+from cashu.core.db import COCKROACH, POSTGRES, SQLITE, Database
async def migrate_databases(db: Database, migrations_module):
diff --git a/core/secp.py b/cashu/core/secp.py
similarity index 100%
rename from core/secp.py
rename to cashu/core/secp.py
diff --git a/core/settings.py b/cashu/core/settings.py
similarity index 98%
rename from core/settings.py
rename to cashu/core/settings.py
index d24c0c4..36ec220 100644
--- a/core/settings.py
+++ b/cashu/core/settings.py
@@ -32,3 +32,4 @@ LNBITS_ENDPOINT = env.str("LNBITS_ENDPOINT", default=None)
LNBITS_KEY = env.str("LNBITS_KEY", default=None)
MAX_ORDER = 64
+VERSION = "0.1.10"
diff --git a/core/split.py b/cashu/core/split.py
similarity index 100%
rename from core/split.py
rename to cashu/core/split.py
diff --git a/cashu/lightning/__init__.py b/cashu/lightning/__init__.py
new file mode 100644
index 0000000..bc14007
--- /dev/null
+++ b/cashu/lightning/__init__.py
@@ -0,0 +1,3 @@
+from cashu.lightning.lnbits import LNbitsWallet
+
+WALLET = LNbitsWallet()
diff --git a/lightning/base.py b/cashu/lightning/base.py
similarity index 100%
rename from lightning/base.py
rename to cashu/lightning/base.py
diff --git a/lightning/lnbits.py b/cashu/lightning/lnbits.py
similarity index 96%
rename from lightning/lnbits.py
rename to cashu/lightning/lnbits.py
index 94a49eb..f174f2d 100644
--- a/lightning/lnbits.py
+++ b/cashu/lightning/lnbits.py
@@ -6,10 +6,15 @@ from typing import AsyncGenerator, Dict, Optional
import requests
-from core.settings import LNBITS_ENDPOINT, LNBITS_KEY
+from cashu.core.settings import LNBITS_ENDPOINT, LNBITS_KEY
-from .base import (InvoiceResponse, PaymentResponse, PaymentStatus,
- StatusResponse, Wallet)
+from .base import (
+ InvoiceResponse,
+ PaymentResponse,
+ PaymentStatus,
+ StatusResponse,
+ Wallet,
+)
class LNbitsWallet(Wallet):
diff --git a/cashu/mint/__init__.py b/cashu/mint/__init__.py
new file mode 100644
index 0000000..cfe9d4c
--- /dev/null
+++ b/cashu/mint/__init__.py
@@ -0,0 +1,4 @@
+from cashu.core.settings import MINT_PRIVATE_KEY
+from cashu.mint.ledger import Ledger
+
+ledger = Ledger(MINT_PRIVATE_KEY, "data/mint")
diff --git a/cashu/mint/__main__.py b/cashu/mint/__main__.py
new file mode 100644
index 0000000..5d6a810
--- /dev/null
+++ b/cashu/mint/__main__.py
@@ -0,0 +1,3 @@
+from .main import main
+
+main()
diff --git a/cashu/mint/app.py b/cashu/mint/app.py
new file mode 100644
index 0000000..428bc05
--- /dev/null
+++ b/cashu/mint/app.py
@@ -0,0 +1,71 @@
+import asyncio
+import logging
+import sys
+
+from fastapi import FastAPI
+from loguru import logger
+
+from cashu.core.settings import CASHU_DIR, DEBUG
+from cashu.lightning import WALLET
+from cashu.mint.migrations import m001_initial
+
+from . import ledger
+from .router import router
+from .startup import load_ledger
+
+
+def create_app(config_object="core.settings") -> FastAPI:
+ def configure_logger() -> None:
+ class Formatter:
+ def __init__(self):
+ self.padding = 0
+ self.minimal_fmt: str = "{time:YYYY-MM-DD HH:mm:ss.SS} | {level} | {message}\n"
+ if 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
+
+ def format(self, record):
+ function = "{function}".format(**record)
+ if function == "emit": # uvicorn logs
+ return self.minimal_fmt
+ return self.fmt
+
+ class InterceptHandler(logging.Handler):
+ def emit(self, record):
+ try:
+ level = logger.level(record.levelname).name
+ except ValueError:
+ level = record.levelno
+ logger.log(level, record.getMessage())
+
+ logger.remove()
+ log_level: str = "INFO"
+ formatter = Formatter()
+ logger.add(sys.stderr, level=log_level, format=formatter.format)
+
+ logging.getLogger("uvicorn").handlers = [InterceptHandler()]
+ logging.getLogger("uvicorn.access").handlers = [InterceptHandler()]
+
+ configure_logger()
+
+ app = FastAPI(
+ title="Cashu Mint",
+ description="Ecash wallet and mint.",
+ license_info={
+ "name": "MIT License",
+ "url": "https://raw.githubusercontent.com/callebtc/cashu/main/LICENSE",
+ },
+ )
+
+ return app
+
+
+app = create_app()
+
+app.include_router(router=router)
+
+
+@app.on_event("startup")
+async def startup_load_ledger():
+ await load_ledger()
diff --git a/mint/crud.py b/cashu/mint/crud.py
similarity index 95%
rename from mint/crud.py
rename to cashu/mint/crud.py
index 5b92271..18a7195 100644
--- a/mint/crud.py
+++ b/cashu/mint/crud.py
@@ -1,8 +1,8 @@
import secrets
from typing import Optional
-from core.base import Invoice, Proof
-from core.db import Connection, Database
+from cashu.core.base import Invoice, Proof
+from cashu.core.db import Connection, Database
async def store_promise(
diff --git a/mint/ledger.py b/cashu/mint/ledger.py
similarity index 93%
rename from mint/ledger.py
rename to cashu/mint/ledger.py
index f5ce49f..13f6310 100644
--- a/mint/ledger.py
+++ b/cashu/mint/ledger.py
@@ -5,15 +5,15 @@ Implementation of https://gist.github.com/phyro/935badc682057f418842c72961cf096c
import hashlib
from typing import List, Set
-import core.b_dhke as b_dhke
-from core.base import BlindedMessage, BlindedSignature, Invoice, Proof
-from core.db import Database
-from core.helpers import fee_reserve
-from core.secp import PrivateKey, PublicKey
-from core.settings import LIGHTNING, MAX_ORDER
-from core.split import amount_split
-from lightning import WALLET
-from mint.crud import (
+import cashu.core.b_dhke as b_dhke
+from cashu.core.base import BlindedMessage, BlindedSignature, Invoice, Proof
+from cashu.core.db import Database
+from cashu.core.helpers import fee_reserve
+from cashu.core.secp import PrivateKey, PublicKey
+from cashu.core.settings import LIGHTNING, MAX_ORDER
+from cashu.core.split import amount_split
+from cashu.lightning import WALLET
+from cashu.mint.crud import (
get_lightning_invoice,
get_proofs_used,
invalidate_proof,
diff --git a/cashu/mint/main.py b/cashu/mint/main.py
new file mode 100644
index 0000000..50c4a6a
--- /dev/null
+++ b/cashu/mint/main.py
@@ -0,0 +1,49 @@
+import click
+import uvicorn
+
+from cashu.core.settings import MINT_SERVER_HOST, MINT_SERVER_PORT
+
+
+@click.command(
+ context_settings=dict(
+ ignore_unknown_options=True,
+ 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("--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,
+ port: int = MINT_SERVER_PORT,
+ host: str = MINT_SERVER_HOST,
+ ssl_keyfile: str = None,
+ ssl_certfile: str = None,
+):
+ """Launched with `poetry run mint` at root level"""
+ # this beautiful beast parses all command line arguments and passes them to the uvicorn server
+ d = dict()
+ for a in ctx.args:
+ item = a.split("=")
+ if len(item) > 1: # argument like --key=value
+ print(a, item)
+ d[item[0].strip("--").replace("-", "_")] = (
+ int(item[1]) # need to convert to int if it's a number
+ if item[1].isdigit()
+ else item[1]
+ )
+ else:
+ d[a.strip("--")] = True # argument like --key
+
+ config = uvicorn.Config(
+ "cashu.mint.app:app",
+ port=port,
+ host=host,
+ ssl_keyfile=ssl_keyfile,
+ ssl_certfile=ssl_certfile,
+ **d,
+ )
+ server = uvicorn.Server(config)
+ server.run()
diff --git a/mint/migrations.py b/cashu/mint/migrations.py
similarity index 98%
rename from mint/migrations.py
rename to cashu/mint/migrations.py
index 231013c..967b9d3 100644
--- a/mint/migrations.py
+++ b/cashu/mint/migrations.py
@@ -1,4 +1,4 @@
-from core.db import Database
+from cashu.core.db import Database
async def m000_create_migrations_table(db):
diff --git a/cashu/mint/router.py b/cashu/mint/router.py
new file mode 100644
index 0000000..b7ebf05
--- /dev/null
+++ b/cashu/mint/router.py
@@ -0,0 +1,88 @@
+from typing import Union
+
+from fastapi import APIRouter
+from secp256k1 import PublicKey
+
+from cashu.core.base import CheckPayload, MeltPayload, MintPayloads, SplitPayload
+from cashu.mint import ledger
+
+router: APIRouter = APIRouter()
+
+
+@router.get("/keys")
+def keys():
+ """Get the public keys of the mint"""
+ return ledger.get_pubkeys()
+
+
+@router.get("/mint")
+async def request_mint(amount: int = 0):
+ """Request minting of tokens. Server responds with a Lightning invoice."""
+ payment_request, payment_hash = await ledger.request_mint(amount)
+ print(f"Lightning invoice: {payment_request}")
+ return {"pr": payment_request, "hash": payment_hash}
+
+
+@router.post("/mint")
+async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None):
+ """
+ Requests the minting of tokens belonging to a paid payment request.
+
+ Parameters:
+ pr: payment_request of the Lightning paid invoice.
+
+ Body (JSON):
+ payloads: contains a list of blinded messages waiting to be signed.
+
+ NOTE:
+ - This needs to be replaced by the preimage otherwise someone knowing
+ the payment_request can request the tokens instead of the rightful
+ owner.
+ - The blinded message should ideally be provided to the server *before* payment
+ in the GET /mint endpoint so that the server knows to sign only these tokens
+ when the invoice is paid.
+ """
+ amounts = []
+ B_s = []
+ for payload in payloads.blinded_messages:
+ amounts.append(payload.amount)
+ B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
+ try:
+ promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
+ return promises
+ except Exception as exc:
+ return {"error": str(exc)}
+
+
+@router.post("/melt")
+async def melt(payload: MeltPayload):
+ """
+ Requests tokens to be destroyed and sent out via Lightning.
+ """
+ ok, preimage = await ledger.melt(payload.proofs, payload.amount, payload.invoice)
+ return {"paid": ok, "preimage": preimage}
+
+
+@router.post("/check")
+async def check_spendable(payload: CheckPayload):
+ return await ledger.check_spendable(payload.proofs)
+
+
+@router.post("/split")
+async def split(payload: SplitPayload):
+ """
+ Requetst a set of tokens with amount "total" to be split into two
+ newly minted sets with amount "split" and "total-split".
+ """
+ proofs = payload.proofs
+ amount = payload.amount
+ output_data = payload.output_data.blinded_messages
+ try:
+ split_return = await ledger.split(proofs, amount, output_data)
+ except Exception as exc:
+ return {"error": str(exc)}
+ if not split_return:
+ """There was a problem with the split"""
+ raise Exception("could not split tokens.")
+ fst_promises, snd_promises = split_return
+ return {"fst": fst_promises, "snd": snd_promises}
diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py
new file mode 100644
index 0000000..a2c4653
--- /dev/null
+++ b/cashu/mint/startup.py
@@ -0,0 +1,25 @@
+import asyncio
+
+from loguru import logger
+
+from cashu.core.settings import CASHU_DIR
+from cashu.lightning import WALLET
+from cashu.mint.migrations import m001_initial
+
+from . import ledger
+
+
+async def load_ledger():
+ await asyncio.wait([m001_initial(ledger.db)])
+ await ledger.load_used_proofs()
+
+ error_message, balance = await WALLET.status()
+ if error_message:
+ logger.warning(
+ f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
+ RuntimeWarning,
+ )
+
+ logger.info(f"Lightning balance: {balance} sat")
+ logger.info(f"Data dir: {CASHU_DIR}")
+ logger.info("Mint started.")
diff --git a/wallet/__init__.py b/cashu/wallet/__init__.py
similarity index 100%
rename from wallet/__init__.py
rename to cashu/wallet/__init__.py
diff --git a/cashu/wallet/__main__.py b/cashu/wallet/__main__.py
new file mode 100644
index 0000000..4cafccb
--- /dev/null
+++ b/cashu/wallet/__main__.py
@@ -0,0 +1,3 @@
+from .cli import cli
+
+cli()
diff --git a/wallet/cashu.py b/cashu/wallet/cli.py
similarity index 93%
rename from wallet/cashu.py
rename to cashu/wallet/cli.py
index c252b46..09a3f4a 100755
--- a/wallet/cashu.py
+++ b/cashu/wallet/cli.py
@@ -12,15 +12,15 @@ from operator import itemgetter
import click
from bech32 import bech32_decode, bech32_encode, convertbits
-import core.bolt11 as bolt11
-from core.base import Proof
-from core.bolt11 import Invoice
-from core.helpers import fee_reserve
-from core.migrations import migrate_databases
-from core.settings import CASHU_DIR, DEBUG, LIGHTNING, MINT_URL
-from wallet import migrations
-from wallet.crud import get_reserved_proofs
-from wallet.wallet import Wallet as Wallet
+import cashu.core.bolt11 as bolt11
+from cashu.core.base import Proof
+from cashu.core.bolt11 import Invoice
+from cashu.core.helpers import fee_reserve
+from cashu.core.migrations import migrate_databases
+from cashu.core.settings import CASHU_DIR, DEBUG, LIGHTNING, MINT_URL, VERSION
+from cashu.wallet import migrations
+from cashu.wallet.crud import get_reserved_proofs
+from cashu.wallet.wallet import Wallet as Wallet
async def init_wallet(wallet: Wallet):
@@ -203,9 +203,7 @@ async def pay(ctx, invoice: str):
@click.pass_context
@coro
async def info(ctx):
- wallet: Wallet = ctx.obj["WALLET"]
- await init_wallet(wallet)
- wallet.status()
+ print(f"Version: {VERSION}")
print(f"Debug: {DEBUG}")
print(f"Cashu dir: {CASHU_DIR}")
print(f"Mint URL: {MINT_URL}")
diff --git a/wallet/crud.py b/cashu/wallet/crud.py
similarity index 96%
rename from wallet/crud.py
rename to cashu/wallet/crud.py
index 55bb293..c5741b6 100644
--- a/wallet/crud.py
+++ b/cashu/wallet/crud.py
@@ -1,8 +1,8 @@
import time
from typing import Optional
-from core.base import Proof
-from core.db import Connection, Database
+from cashu.core.base import Proof
+from cashu.core.db import Connection, Database
async def store_proof(
diff --git a/wallet/migrations.py b/cashu/wallet/migrations.py
similarity index 98%
rename from wallet/migrations.py
rename to cashu/wallet/migrations.py
index 8745577..c8b3ec0 100644
--- a/wallet/migrations.py
+++ b/cashu/wallet/migrations.py
@@ -1,4 +1,4 @@
-from core.db import Database
+from cashu.core.db import Database
async def m000_create_migrations_table(db):
diff --git a/wallet/wallet.py b/cashu/wallet/wallet.py
similarity index 93%
rename from wallet/wallet.py
rename to cashu/wallet/wallet.py
index b3dccfc..27d1ba6 100644
--- a/wallet/wallet.py
+++ b/cashu/wallet/wallet.py
@@ -1,14 +1,13 @@
import base64
import json
-import random
import secrets as scrts
import uuid
from typing import List
import requests
-import core.b_dhke as b_dhke
-from core.base import (
+import cashu.core.b_dhke as b_dhke
+from cashu.core.base import (
BlindedMessage,
BlindedSignature,
CheckPayload,
@@ -17,11 +16,16 @@ from core.base import (
Proof,
SplitPayload,
)
-from core.db import Database
-from core.secp import PublicKey
-from core.settings import DEBUG
-from core.split import amount_split
-from wallet.crud import get_proofs, invalidate_proof, store_proof, update_proof_reserved
+from cashu.core.db import Database
+from cashu.core.secp import PublicKey
+from cashu.core.settings import DEBUG
+from cashu.core.split import amount_split
+from cashu.wallet.crud import (
+ get_proofs,
+ invalidate_proof,
+ store_proof,
+ update_proof_reserved,
+)
class LedgerAPI:
@@ -97,6 +101,7 @@ class LedgerAPI:
fst_outputs = amount_split(fst_amt)
snd_outputs = amount_split(snd_amt)
+ # TODO: Refactor together with the same procedure in self.mint()
secrets = []
payloads: MintPayloads = MintPayloads()
for output_amt in fst_outputs + snd_outputs:
diff --git a/cashu/wallet/wallet_live/.DS_Store b/cashu/wallet/wallet_live/.DS_Store
new file mode 100644
index 0000000..5008ddf
Binary files /dev/null and b/cashu/wallet/wallet_live/.DS_Store differ
diff --git a/mint/__init__.py b/cashu/wallet/wallet_live/.placeholder
similarity index 100%
rename from mint/__init__.py
rename to cashu/wallet/wallet_live/.placeholder
diff --git a/cashu/wallet/wallet_live/wallet.sqlite3 b/cashu/wallet/wallet_live/wallet.sqlite3
new file mode 100644
index 0000000..d3e29bb
Binary files /dev/null and b/cashu/wallet/wallet_live/wallet.sqlite3 differ
diff --git a/lightning/__init__.py b/lightning/__init__.py
deleted file mode 100644
index baa53c7..0000000
--- a/lightning/__init__.py
+++ /dev/null
@@ -1,3 +0,0 @@
-from lightning.lnbits import LNbitsWallet
-
-WALLET = LNbitsWallet()
diff --git a/mint/app.py b/mint/app.py
deleted file mode 100644
index ddd5315..0000000
--- a/mint/app.py
+++ /dev/null
@@ -1,217 +0,0 @@
-import asyncio
-import logging
-import sys
-from typing import Union
-
-import click
-import uvicorn
-from fastapi import FastAPI
-from loguru import logger
-from secp256k1 import PublicKey
-
-import core.settings as settings
-from core.base import CheckPayload, MeltPayload, MintPayloads, SplitPayload
-from core.settings import (CASHU_DIR, MINT_PRIVATE_KEY, MINT_SERVER_HOST,
- MINT_SERVER_PORT)
-from lightning import WALLET
-from mint.ledger import Ledger
-from mint.migrations import m001_initial
-
-ledger = Ledger(MINT_PRIVATE_KEY, "data/mint")
-
-
-def startup(app: FastAPI):
- @app.on_event("startup")
- async def load_ledger():
- await asyncio.wait([m001_initial(ledger.db)])
- await ledger.load_used_proofs()
-
- error_message, balance = await WALLET.status()
- if error_message:
- logger.warning(
- f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
- RuntimeWarning,
- )
-
- logger.info(f"Lightning balance: {balance} sat")
- logger.info(f"Data dir: {CASHU_DIR}")
- logger.info("Mint started.")
-
-
-def create_app(config_object="core.settings") -> FastAPI:
- def configure_logger() -> None:
- class Formatter:
- def __init__(self):
- self.padding = 0
- self.minimal_fmt: str = "{time:YYYY-MM-DD HH:mm:ss.SS} | {level} | {message}\n"
- 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
-
- def format(self, record):
- function = "{function}".format(**record)
- if function == "emit": # uvicorn logs
- return self.minimal_fmt
- return self.fmt
-
- class InterceptHandler(logging.Handler):
- def emit(self, record):
- try:
- level = logger.level(record.levelname).name
- except ValueError:
- level = record.levelno
- logger.log(level, record.getMessage())
-
- logger.remove()
- log_level: str = "INFO"
- formatter = Formatter()
- logger.add(sys.stderr, level=log_level, format=formatter.format)
-
- logging.getLogger("uvicorn").handlers = [InterceptHandler()]
- logging.getLogger("uvicorn.access").handlers = [InterceptHandler()]
-
- configure_logger()
-
- app = FastAPI(
- title="Cashu Mint",
- description="Ecash wallet and mint.",
- license_info={
- "name": "MIT License",
- "url": "https://raw.githubusercontent.com/callebtc/cashu/main/LICENSE",
- },
- )
-
- startup(app)
- return app
-
-
-app = create_app()
-
-
-@app.get("/keys")
-def keys():
- """Get the public keys of the mint"""
- return ledger.get_pubkeys()
-
-
-@app.get("/mint")
-async def request_mint(amount: int = 0):
- """Request minting of tokens. Server responds with a Lightning invoice."""
- payment_request, payment_hash = await ledger.request_mint(amount)
- print(f"Lightning invoice: {payment_request}")
- return {"pr": payment_request, "hash": payment_hash}
-
-
-@app.post("/mint")
-async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None):
- """
- Requests the minting of tokens belonging to a paid payment request.
-
- Parameters:
- pr: payment_request of the Lightning paid invoice.
-
- Body (JSON):
- payloads: contains a list of blinded messages waiting to be signed.
-
- NOTE:
- - This needs to be replaced by the preimage otherwise someone knowing
- the payment_request can request the tokens instead of the rightful
- owner.
- - The blinded message should ideally be provided to the server *before* payment
- in the GET /mint endpoint so that the server knows to sign only these tokens
- when the invoice is paid.
- """
- amounts = []
- B_s = []
- for payload in payloads.blinded_messages:
- amounts.append(payload.amount)
- B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
- try:
- promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
- return promises
- except Exception as exc:
- return {"error": str(exc)}
-
-
-@app.post("/melt")
-async def melt(payload: MeltPayload):
- """
- Requests tokens to be destroyed and sent out via Lightning.
- """
- ok, preimage = await ledger.melt(payload.proofs, payload.amount, payload.invoice)
- return {"paid": ok, "preimage": preimage}
-
-
-@app.post("/check")
-async def check_spendable(payload: CheckPayload):
- return await ledger.check_spendable(payload.proofs)
-
-
-@app.post("/split")
-async def split(payload: SplitPayload):
- """
- Requetst a set of tokens with amount "total" to be split into two
- newly minted sets with amount "split" and "total-split".
- """
- proofs = payload.proofs
- amount = payload.amount
- output_data = payload.output_data.blinded_messages
- try:
- split_return = await ledger.split(proofs, amount, output_data)
- except Exception as exc:
- return {"error": str(exc)}
- if not split_return:
- """There was a problem with the split"""
- raise Exception("could not split tokens.")
- fst_promises, snd_promises = split_return
- return {"fst": fst_promises, "snd": snd_promises}
-
-
-@click.command(
- context_settings=dict(
- ignore_unknown_options=True,
- 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("--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,
- port: int = MINT_SERVER_PORT,
- host: str = MINT_SERVER_HOST,
- ssl_keyfile: str = None,
- ssl_certfile: str = None,
-):
- """Launched with `poetry run mint` at root level"""
- # this beautiful beast parses all command line arguments and passes them to the uvicorn server
- d = dict()
- for a in ctx.args:
- item = a.split("=")
- if len(item) > 1: # argument like --key=value
- print(a, item)
- d[item[0].strip("--").replace("-", "_")] = (
- int(item[1]) # need to convert to int if it's a number
- if item[1].isdigit()
- else item[1]
- )
- else:
- d[a.strip("--")] = True # argument like --key
-
- config = uvicorn.Config(
- "mint.app:app",
- port=port,
- host=host,
- ssl_keyfile=ssl_keyfile,
- ssl_certfile=ssl_certfile,
- **d,
- )
- server = uvicorn.Server(config)
- server.run()
-
-
-if __name__ == "__main__":
- main()
diff --git a/poetry.lock b/poetry.lock
index 4f84b80..f8a5ae0 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -85,7 +85,7 @@ uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
-version = "2022.9.14"
+version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
@@ -184,32 +184,13 @@ dev = ["autoflake (>=1.4.0,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "passlib[bcrypt]
doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer (>=0.4.1,<0.5.0)"]
test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==22.3.0)", "databases[sqlite] (>=0.3.2,<0.6.0)", "email_validator (>=1.1.1,<2.0.0)", "flake8 (>=3.8.3,<6.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.14.0,<0.19.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "orjson (>=3.2.1,<4.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=6.2.4,<7.0.0)", "pytest-cov (>=2.12.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.6)", "requests (>=2.24.0,<3.0.0)", "sqlalchemy (>=1.3.18,<1.5.0)", "types-dataclasses (==0.6.5)", "types-orjson (==3.6.2)", "types-ujson (==4.2.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
-[[package]]
-name = "Flask"
-version = "2.2.2"
-description = "A simple framework for building complex web applications."
-category = "main"
-optional = false
-python-versions = ">=3.7"
-
-[package.dependencies]
-click = ">=8.0"
-importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
-itsdangerous = ">=2.0"
-Jinja2 = ">=3.0"
-Werkzeug = ">=2.2.2"
-
-[package.extras]
-async = ["asgiref (>=3.2)"]
-dotenv = ["python-dotenv"]
-
[[package]]
name = "h11"
-version = "0.13.0"
+version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
category = "main"
optional = false
-python-versions = ">=3.6"
+python-versions = ">=3.7"
[package.dependencies]
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
@@ -709,7 +690,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
[metadata]
lock-version = "1.1"
python-versions = "^3.7"
-content-hash = "d5ee88384ec1ec1774f9fab845d7e8ac6e8ab1859506bcce178f6783b98e535c"
+content-hash = "4058e11929b71dee85bceb89e1fb5254411db9bde0e087f65b5cf7c0c7fbae8f"
[metadata.files]
anyio = [
@@ -759,8 +740,8 @@ black = [
{file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
]
certifi = [
- {file = "certifi-2022.9.14-py3-none-any.whl", hash = "sha256:e232343de1ab72c2aa521b625c80f699e356830fd0e2c620b465b304b17b0516"},
- {file = "certifi-2022.9.14.tar.gz", hash = "sha256:36973885b9542e6bd01dea287b2b4b3b21236307c56324fcc3f1160f2d655ed5"},
+ {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
+ {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
]
cffi = [
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
@@ -852,13 +833,9 @@ fastapi = [
{file = "fastapi-0.83.0-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"},
{file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"},
]
-Flask = [
- {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"},
- {file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"},
-]
h11 = [
- {file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"},
- {file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"},
+ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
]
idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
diff --git a/pyproject.toml b/pyproject.toml
index 4bf03ad..52c07a0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "cashu"
-version = "0.1.9"
+version = "0.1.10"
description = "Ecash wallet and mint."
authors = ["calle "]
license = "MIT"
@@ -13,7 +13,6 @@ SQLAlchemy = "1.3.24"
sqlalchemy-aio = "0.17.0"
charset-normalizer = "2.0.12"
click = "8.0.4"
-Flask = "2.2.2"
idna = "3.3"
itsdangerous = "2.1.1"
Jinja2 = "3.0.3"
@@ -46,6 +45,6 @@ requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
-mint = "mint.app:main"
-cashu = "wallet.cashu:cli"
+mint = "cashu.mint.main:main"
+cashu = "cashu.wallet.cli:cli"
wallet-test = "tests.test_wallet:test"
\ No newline at end of file
diff --git a/setup.py b/setup.py
index f80f9ca..b7176cc 100644
--- a/setup.py
+++ b/setup.py
@@ -9,11 +9,11 @@ with open(path.join(this_directory, "README.md"), encoding="utf-8") as f:
with open("requirements.txt") as f:
requirements = f.read().splitlines()
-entry_points = {"console_scripts": ["cashu = wallet.cashu:cli"]}
+entry_points = {"console_scripts": ["cashu = cahu.wallet.cli:cli"]}
setuptools.setup(
name="cashu",
- version="0.1.9",
+ version="0.1.10",
description="Ecash wallet and mint with Bitcoin Lightning support",
long_description=long_description,
long_description_content_type="text/markdown",
diff --git a/tests/test_wallet.py b/tests/test_wallet.py
index 9def85f..a77b141 100644
--- a/tests/test_wallet.py
+++ b/tests/test_wallet.py
@@ -1,10 +1,8 @@
-import asyncio
-
-from core.helpers import async_unwrap
-from core.migrations import migrate_databases
-from wallet import migrations
-from wallet.wallet import Wallet as Wallet1
-from wallet.wallet import Wallet as Wallet2
+from cashu.core.helpers import async_unwrap
+from cashu.core.migrations import migrate_databases
+from cashu.wallet import migrations
+from cashu.wallet.wallet import Wallet as Wallet1
+from cashu.wallet.wallet import Wallet as Wallet2
SERVER_ENDPOINT = "http://localhost:3338"
@@ -29,7 +27,7 @@ async def run_test():
await migrate_databases(wallet1.db, migrations)
wallet1.status()
- wallet2 = Wallet1(SERVER_ENDPOINT, "data/wallet2", "wallet2")
+ wallet2 = Wallet2(SERVER_ENDPOINT, "data/wallet2", "wallet2")
await migrate_databases(wallet2.db, migrations)
wallet2.status()