mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-21 19:14:19 +01:00
17
README.md
17
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).
|
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).
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Quick links:
|
||||||
|
<a href="#cashu-client-protocol">Cashu client protocol</a> ·
|
||||||
|
<a href="#easy-install">Quick Install</a> ·
|
||||||
|
<a href="#hard-install-poetry">Manual install</a> ·
|
||||||
|
<a href="#configuration">Configuration</a> ·
|
||||||
|
<a href="#using-cashu">Using Cashu</a> ·
|
||||||
|
<a href="#running-a-mint">Run a mint</a>
|
||||||
|
<br><br>
|
||||||
|
</p>
|
||||||
|
|
||||||
## Cashu client protocol
|
## 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.
|
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).
|
You can skip the entire next section about Poetry and jump right to [Using Cashu](#using-cashu).
|
||||||
|
|
||||||
### Hard install: Poetry
|
## 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).
|
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
|
#### Poetry: Prerequisites
|
||||||
|
|
||||||
@@ -168,7 +179,7 @@ Balance: 351 sat (Available: 351 sat in 7 tokens)
|
|||||||
Balance: 339 sat (Available: 339 sat in 8 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.
|
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
|
```bash
|
||||||
mint
|
mint
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from functools import partial, wraps
|
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):
|
def async_wrap(func):
|
||||||
@@ -2,7 +2,7 @@ import re
|
|||||||
|
|
||||||
from loguru import logger
|
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):
|
async def migrate_databases(db: Database, migrations_module):
|
||||||
@@ -32,3 +32,4 @@ LNBITS_ENDPOINT = env.str("LNBITS_ENDPOINT", default=None)
|
|||||||
LNBITS_KEY = env.str("LNBITS_KEY", default=None)
|
LNBITS_KEY = env.str("LNBITS_KEY", default=None)
|
||||||
|
|
||||||
MAX_ORDER = 64
|
MAX_ORDER = 64
|
||||||
|
VERSION = "0.1.10"
|
||||||
3
cashu/lightning/__init__.py
Normal file
3
cashu/lightning/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from cashu.lightning.lnbits import LNbitsWallet
|
||||||
|
|
||||||
|
WALLET = LNbitsWallet()
|
||||||
@@ -6,10 +6,15 @@ from typing import AsyncGenerator, Dict, Optional
|
|||||||
|
|
||||||
import requests
|
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,
|
from .base import (
|
||||||
StatusResponse, Wallet)
|
InvoiceResponse,
|
||||||
|
PaymentResponse,
|
||||||
|
PaymentStatus,
|
||||||
|
StatusResponse,
|
||||||
|
Wallet,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LNbitsWallet(Wallet):
|
class LNbitsWallet(Wallet):
|
||||||
4
cashu/mint/__init__.py
Normal file
4
cashu/mint/__init__.py
Normal file
@@ -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")
|
||||||
3
cashu/mint/__main__.py
Normal file
3
cashu/mint/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .main import main
|
||||||
|
|
||||||
|
main()
|
||||||
71
cashu/mint/app.py
Normal file
71
cashu/mint/app.py
Normal file
@@ -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 = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level}</level> | <level>{message}</level>\n"
|
||||||
|
if 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"
|
||||||
|
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()
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import secrets
|
import secrets
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from core.base import Invoice, Proof
|
from cashu.core.base import Invoice, Proof
|
||||||
from core.db import Connection, Database
|
from cashu.core.db import Connection, Database
|
||||||
|
|
||||||
|
|
||||||
async def store_promise(
|
async def store_promise(
|
||||||
@@ -5,15 +5,15 @@ Implementation of https://gist.github.com/phyro/935badc682057f418842c72961cf096c
|
|||||||
import hashlib
|
import hashlib
|
||||||
from typing import List, Set
|
from typing import List, Set
|
||||||
|
|
||||||
import core.b_dhke as b_dhke
|
import cashu.core.b_dhke as b_dhke
|
||||||
from core.base import BlindedMessage, BlindedSignature, Invoice, Proof
|
from cashu.core.base import BlindedMessage, BlindedSignature, Invoice, Proof
|
||||||
from core.db import Database
|
from cashu.core.db import Database
|
||||||
from core.helpers import fee_reserve
|
from cashu.core.helpers import fee_reserve
|
||||||
from core.secp import PrivateKey, PublicKey
|
from cashu.core.secp import PrivateKey, PublicKey
|
||||||
from core.settings import LIGHTNING, MAX_ORDER
|
from cashu.core.settings import LIGHTNING, MAX_ORDER
|
||||||
from core.split import amount_split
|
from cashu.core.split import amount_split
|
||||||
from lightning import WALLET
|
from cashu.lightning import WALLET
|
||||||
from mint.crud import (
|
from cashu.mint.crud import (
|
||||||
get_lightning_invoice,
|
get_lightning_invoice,
|
||||||
get_proofs_used,
|
get_proofs_used,
|
||||||
invalidate_proof,
|
invalidate_proof,
|
||||||
49
cashu/mint/main.py
Normal file
49
cashu/mint/main.py
Normal file
@@ -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()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from core.db import Database
|
from cashu.core.db import Database
|
||||||
|
|
||||||
|
|
||||||
async def m000_create_migrations_table(db):
|
async def m000_create_migrations_table(db):
|
||||||
88
cashu/mint/router.py
Normal file
88
cashu/mint/router.py
Normal file
@@ -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}
|
||||||
25
cashu/mint/startup.py
Normal file
25
cashu/mint/startup.py
Normal file
@@ -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.")
|
||||||
3
cashu/wallet/__main__.py
Normal file
3
cashu/wallet/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .cli import cli
|
||||||
|
|
||||||
|
cli()
|
||||||
@@ -12,15 +12,15 @@ from operator import itemgetter
|
|||||||
import click
|
import click
|
||||||
from bech32 import bech32_decode, bech32_encode, convertbits
|
from bech32 import bech32_decode, bech32_encode, convertbits
|
||||||
|
|
||||||
import core.bolt11 as bolt11
|
import cashu.core.bolt11 as bolt11
|
||||||
from core.base import Proof
|
from cashu.core.base import Proof
|
||||||
from core.bolt11 import Invoice
|
from cashu.core.bolt11 import Invoice
|
||||||
from core.helpers import fee_reserve
|
from cashu.core.helpers import fee_reserve
|
||||||
from core.migrations import migrate_databases
|
from cashu.core.migrations import migrate_databases
|
||||||
from core.settings import CASHU_DIR, DEBUG, LIGHTNING, MINT_URL
|
from cashu.core.settings import CASHU_DIR, DEBUG, LIGHTNING, MINT_URL, VERSION
|
||||||
from wallet import migrations
|
from cashu.wallet import migrations
|
||||||
from wallet.crud import get_reserved_proofs
|
from cashu.wallet.crud import get_reserved_proofs
|
||||||
from wallet.wallet import Wallet as Wallet
|
from cashu.wallet.wallet import Wallet as Wallet
|
||||||
|
|
||||||
|
|
||||||
async def init_wallet(wallet: Wallet):
|
async def init_wallet(wallet: Wallet):
|
||||||
@@ -203,9 +203,7 @@ async def pay(ctx, invoice: str):
|
|||||||
@click.pass_context
|
@click.pass_context
|
||||||
@coro
|
@coro
|
||||||
async def info(ctx):
|
async def info(ctx):
|
||||||
wallet: Wallet = ctx.obj["WALLET"]
|
print(f"Version: {VERSION}")
|
||||||
await init_wallet(wallet)
|
|
||||||
wallet.status()
|
|
||||||
print(f"Debug: {DEBUG}")
|
print(f"Debug: {DEBUG}")
|
||||||
print(f"Cashu dir: {CASHU_DIR}")
|
print(f"Cashu dir: {CASHU_DIR}")
|
||||||
print(f"Mint URL: {MINT_URL}")
|
print(f"Mint URL: {MINT_URL}")
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from core.base import Proof
|
from cashu.core.base import Proof
|
||||||
from core.db import Connection, Database
|
from cashu.core.db import Connection, Database
|
||||||
|
|
||||||
|
|
||||||
async def store_proof(
|
async def store_proof(
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from core.db import Database
|
from cashu.core.db import Database
|
||||||
|
|
||||||
|
|
||||||
async def m000_create_migrations_table(db):
|
async def m000_create_migrations_table(db):
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import random
|
|
||||||
import secrets as scrts
|
import secrets as scrts
|
||||||
import uuid
|
import uuid
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
import core.b_dhke as b_dhke
|
import cashu.core.b_dhke as b_dhke
|
||||||
from core.base import (
|
from cashu.core.base import (
|
||||||
BlindedMessage,
|
BlindedMessage,
|
||||||
BlindedSignature,
|
BlindedSignature,
|
||||||
CheckPayload,
|
CheckPayload,
|
||||||
@@ -17,11 +16,16 @@ from core.base import (
|
|||||||
Proof,
|
Proof,
|
||||||
SplitPayload,
|
SplitPayload,
|
||||||
)
|
)
|
||||||
from core.db import Database
|
from cashu.core.db import Database
|
||||||
from core.secp import PublicKey
|
from cashu.core.secp import PublicKey
|
||||||
from core.settings import DEBUG
|
from cashu.core.settings import DEBUG
|
||||||
from core.split import amount_split
|
from cashu.core.split import amount_split
|
||||||
from wallet.crud import get_proofs, invalidate_proof, store_proof, update_proof_reserved
|
from cashu.wallet.crud import (
|
||||||
|
get_proofs,
|
||||||
|
invalidate_proof,
|
||||||
|
store_proof,
|
||||||
|
update_proof_reserved,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class LedgerAPI:
|
class LedgerAPI:
|
||||||
@@ -97,6 +101,7 @@ class LedgerAPI:
|
|||||||
fst_outputs = amount_split(fst_amt)
|
fst_outputs = amount_split(fst_amt)
|
||||||
snd_outputs = amount_split(snd_amt)
|
snd_outputs = amount_split(snd_amt)
|
||||||
|
|
||||||
|
# TODO: Refactor together with the same procedure in self.mint()
|
||||||
secrets = []
|
secrets = []
|
||||||
payloads: MintPayloads = MintPayloads()
|
payloads: MintPayloads = MintPayloads()
|
||||||
for output_amt in fst_outputs + snd_outputs:
|
for output_amt in fst_outputs + snd_outputs:
|
||||||
BIN
cashu/wallet/wallet_live/.DS_Store
vendored
Normal file
BIN
cashu/wallet/wallet_live/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
cashu/wallet/wallet_live/wallet.sqlite3
Normal file
BIN
cashu/wallet/wallet_live/wallet.sqlite3
Normal file
Binary file not shown.
@@ -1,3 +0,0 @@
|
|||||||
from lightning.lnbits import LNbitsWallet
|
|
||||||
|
|
||||||
WALLET = LNbitsWallet()
|
|
||||||
217
mint/app.py
217
mint/app.py
@@ -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 = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level}</level> | <level>{message}</level>\n"
|
|
||||||
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"
|
|
||||||
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()
|
|
||||||
39
poetry.lock
generated
39
poetry.lock
generated
@@ -85,7 +85,7 @@ uvloop = ["uvloop (>=0.15.2)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2022.9.14"
|
version = "2022.9.24"
|
||||||
description = "Python package for providing Mozilla's CA Bundle."
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
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)"]
|
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)"]
|
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]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
version = "0.13.0"
|
version = "0.14.0"
|
||||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.7"
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
typing-extensions = {version = "*", markers = "python_version < \"3.8\""}
|
||||||
@@ -709,7 +690,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "d5ee88384ec1ec1774f9fab845d7e8ac6e8ab1859506bcce178f6783b98e535c"
|
content-hash = "4058e11929b71dee85bceb89e1fb5254411db9bde0e087f65b5cf7c0c7fbae8f"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
anyio = [
|
anyio = [
|
||||||
@@ -759,8 +740,8 @@ black = [
|
|||||||
{file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
|
{file = "black-22.8.0.tar.gz", hash = "sha256:792f7eb540ba9a17e8656538701d3eb1afcb134e3b45b71f20b25c77a8db7e6e"},
|
||||||
]
|
]
|
||||||
certifi = [
|
certifi = [
|
||||||
{file = "certifi-2022.9.14-py3-none-any.whl", hash = "sha256:e232343de1ab72c2aa521b625c80f699e356830fd0e2c620b465b304b17b0516"},
|
{file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
|
||||||
{file = "certifi-2022.9.14.tar.gz", hash = "sha256:36973885b9542e6bd01dea287b2b4b3b21236307c56324fcc3f1160f2d655ed5"},
|
{file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
|
||||||
]
|
]
|
||||||
cffi = [
|
cffi = [
|
||||||
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
|
{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-py3-none-any.whl", hash = "sha256:694a2b6c2607a61029a4be1c6613f84d74019cb9f7a41c7a475dca8e715f9368"},
|
||||||
{file = "fastapi-0.83.0.tar.gz", hash = "sha256:96eb692350fe13d7a9843c3c87a874f0d45102975257dd224903efd6c0fde3bd"},
|
{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 = [
|
h11 = [
|
||||||
{file = "h11-0.13.0-py3-none-any.whl", hash = "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442"},
|
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||||
{file = "h11-0.13.0.tar.gz", hash = "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06"},
|
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||||
]
|
]
|
||||||
idna = [
|
idna = [
|
||||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "cashu"
|
name = "cashu"
|
||||||
version = "0.1.9"
|
version = "0.1.10"
|
||||||
description = "Ecash wallet and mint."
|
description = "Ecash wallet and mint."
|
||||||
authors = ["calle <callebtc@protonmail.com>"]
|
authors = ["calle <callebtc@protonmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
@@ -13,7 +13,6 @@ SQLAlchemy = "1.3.24"
|
|||||||
sqlalchemy-aio = "0.17.0"
|
sqlalchemy-aio = "0.17.0"
|
||||||
charset-normalizer = "2.0.12"
|
charset-normalizer = "2.0.12"
|
||||||
click = "8.0.4"
|
click = "8.0.4"
|
||||||
Flask = "2.2.2"
|
|
||||||
idna = "3.3"
|
idna = "3.3"
|
||||||
itsdangerous = "2.1.1"
|
itsdangerous = "2.1.1"
|
||||||
Jinja2 = "3.0.3"
|
Jinja2 = "3.0.3"
|
||||||
@@ -46,6 +45,6 @@ requires = ["poetry-core>=1.0.0"]
|
|||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
mint = "mint.app:main"
|
mint = "cashu.mint.main:main"
|
||||||
cashu = "wallet.cashu:cli"
|
cashu = "cashu.wallet.cli:cli"
|
||||||
wallet-test = "tests.test_wallet:test"
|
wallet-test = "tests.test_wallet:test"
|
||||||
4
setup.py
4
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:
|
with open("requirements.txt") as f:
|
||||||
requirements = f.read().splitlines()
|
requirements = f.read().splitlines()
|
||||||
|
|
||||||
entry_points = {"console_scripts": ["cashu = wallet.cashu:cli"]}
|
entry_points = {"console_scripts": ["cashu = cahu.wallet.cli:cli"]}
|
||||||
|
|
||||||
setuptools.setup(
|
setuptools.setup(
|
||||||
name="cashu",
|
name="cashu",
|
||||||
version="0.1.9",
|
version="0.1.10",
|
||||||
description="Ecash wallet and mint with Bitcoin Lightning support",
|
description="Ecash wallet and mint with Bitcoin Lightning support",
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
long_description_content_type="text/markdown",
|
long_description_content_type="text/markdown",
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import asyncio
|
from cashu.core.helpers import async_unwrap
|
||||||
|
from cashu.core.migrations import migrate_databases
|
||||||
from core.helpers import async_unwrap
|
from cashu.wallet import migrations
|
||||||
from core.migrations import migrate_databases
|
from cashu.wallet.wallet import Wallet as Wallet1
|
||||||
from wallet import migrations
|
from cashu.wallet.wallet import Wallet as Wallet2
|
||||||
from wallet.wallet import Wallet as Wallet1
|
|
||||||
from wallet.wallet import Wallet as Wallet2
|
|
||||||
|
|
||||||
SERVER_ENDPOINT = "http://localhost:3338"
|
SERVER_ENDPOINT = "http://localhost:3338"
|
||||||
|
|
||||||
@@ -29,7 +27,7 @@ async def run_test():
|
|||||||
await migrate_databases(wallet1.db, migrations)
|
await migrate_databases(wallet1.db, migrations)
|
||||||
wallet1.status()
|
wallet1.status()
|
||||||
|
|
||||||
wallet2 = Wallet1(SERVER_ENDPOINT, "data/wallet2", "wallet2")
|
wallet2 = Wallet2(SERVER_ENDPOINT, "data/wallet2", "wallet2")
|
||||||
await migrate_databases(wallet2.db, migrations)
|
await migrate_databases(wallet2.db, migrations)
|
||||||
wallet2.status()
|
wallet2.status()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user