mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-24 03:54:21 +01:00
Merge pull request #5 from callebtc/deployment/dockerfile
Deployment/dockerfile
This commit is contained in:
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM python:3.9-slim
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y curl python3-dev autoconf g++
|
||||
RUN curl -sSL https://install.python-poetry.org | python3 -
|
||||
ENV PATH="/root/.local/bin:$PATH"
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
RUN poetry config virtualenvs.create false
|
||||
RUN poetry install --no-dev --no-root
|
||||
EXPOSE 3338
|
||||
CMD ["poetry", "run", "mint", "--port", "3338", "--host", "0.0.0.0"]
|
||||
21
README.md
21
README.md
@@ -49,7 +49,7 @@ To use the wallet with the [public test mint](#test-instance), you need to chang
|
||||
## Run a mint yourself
|
||||
This runs the mint on your local computer. Skip this step if you want to use the [public test mint](#test-instance) instead.
|
||||
```bash
|
||||
poetry run uvicorn mint.app:app --port 3338
|
||||
poetry run mint
|
||||
```
|
||||
|
||||
## Use wallet
|
||||
@@ -59,7 +59,7 @@ poetry run uvicorn mint.app:app --port 3338
|
||||
This command will return a Lightning invoice and a payment hash. You have to pay the invoice before you can receive the tokens. Note: Minting tokens involves two steps: requesting a mint, and actually minting tokens (see below).
|
||||
|
||||
```bash
|
||||
poetry run ./cashu --wallet=wallet --mint=420
|
||||
poetry run cashu mint 420
|
||||
```
|
||||
Returns:
|
||||
```bash
|
||||
@@ -73,7 +73,7 @@ Balance: 0
|
||||
#### Mint tokens
|
||||
After paying the invoice, copy the `hash` value from above and add it to the command
|
||||
```bash
|
||||
poetry run ./cashu --wallet=wallet --mint=420 --hash=009d6eb02da8769b37602ac6d98ecafc1d65bd2408114a5d50e60da200bc85c5
|
||||
poetry run cashu mint 420 --hash=009d6eb02da8769b37602ac6d98ecafc1d65bd2408114a5d50e60da200bc85c5
|
||||
```
|
||||
You should see your balance update accordingly:
|
||||
```bash
|
||||
@@ -81,10 +81,15 @@ Balance: 0
|
||||
Balance: 420
|
||||
```
|
||||
|
||||
#### Check balance
|
||||
```bash
|
||||
poetry run cashu balance
|
||||
```
|
||||
|
||||
#### Send tokens
|
||||
To send tokens to another user, enter
|
||||
```bash
|
||||
poetry run ./cashu --send=69
|
||||
poetry run cashu send 69
|
||||
```
|
||||
You should see the encoded token. Copy the token and send it to another user such as via email or a messenger. The token looks like this:
|
||||
```bash
|
||||
@@ -94,7 +99,7 @@ W3siYW1vdW50IjogMSwgIkMiOiB7IngiOiAzMzg0Mzg0NDYzNzAwMTY1NDA2MTQxMDY3Mzg1MDg5MjA2
|
||||
#### Receive tokens
|
||||
To receive tokens, another user enters:
|
||||
```bash
|
||||
poetry run ./cashu --receive=W3siYW1vdW50IjogMSwgIkMiOi...
|
||||
poetry run cashu receive W3siYW1vdW50IjogMSwgIkMiOi...
|
||||
```
|
||||
You should see the balance increase:
|
||||
```bash
|
||||
@@ -102,10 +107,10 @@ wallet balance: 0
|
||||
wallet balance: 69
|
||||
```
|
||||
|
||||
#### Invalidate tokens
|
||||
The sending user needs to invalidate their tokens from above, otherwise they will try to double spend them (which won't work because the server keeps a list of all spent tokens):
|
||||
#### Burn tokens
|
||||
The sending user needs to burn (invalidate) their tokens from above, otherwise they will try to double spend them (which won't work because the server keeps a list of all spent tokens):
|
||||
```bash
|
||||
poetry run ./cashu --invalidate=W3siYW1vdW50IjogMSwgIkMiOi...
|
||||
poetry run cashu burn W3siYW1vdW50IjogMSwgIkMiOi...
|
||||
```
|
||||
Returns:
|
||||
```bash
|
||||
|
||||
75
cashu
75
cashu
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
from functools import wraps
|
||||
|
||||
import click
|
||||
from bech32 import bech32_decode, bech32_encode, convertbits
|
||||
|
||||
from wallet.migrations import m001_initial
|
||||
from wallet.wallet import Wallet as Wallet
|
||||
from core.settings import MINT_URL
|
||||
|
||||
# https://github.com/pallets/click/issues/85#issuecomment-503464628
|
||||
def coro(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
return asyncio.run(f(*args, **kwargs))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@click.command("mint")
|
||||
@click.option("--host", default=MINT_URL, help="Mint hostname.")
|
||||
@click.option("--wallet", default="wallet", help="Wallet to use.")
|
||||
@click.option("--mint", default=0, help="Mint tokens.")
|
||||
@click.option("--hash", default="", help="Hash of the paid invoice.")
|
||||
@click.option("--send", default=0, help="Send tokens.")
|
||||
@click.option("--receive", default="", help="Receive tokens.")
|
||||
@click.option("--invalidate", default="", help="Invalidate tokens.")
|
||||
@coro
|
||||
async def main(host, wallet, mint, hash, send, receive, invalidate):
|
||||
wallet = Wallet(host, f"data/{wallet}", wallet)
|
||||
await m001_initial(db=wallet.db)
|
||||
await wallet.load_proofs()
|
||||
if mint and not hash:
|
||||
print(f"Balance: {wallet.balance}")
|
||||
r = await wallet.request_mint(mint)
|
||||
print(r)
|
||||
|
||||
if mint and hash:
|
||||
print(f"Balance: {wallet.balance}")
|
||||
await wallet.mint(mint, hash)
|
||||
print(f"Balance: {wallet.balance}")
|
||||
|
||||
if send:
|
||||
wallet.status()
|
||||
_, send_proofs = await wallet.split(wallet.proofs, send)
|
||||
print(base64.urlsafe_b64encode(json.dumps(send_proofs).encode()).decode())
|
||||
|
||||
if receive:
|
||||
wallet.status()
|
||||
proofs = json.loads(base64.urlsafe_b64decode(receive))
|
||||
_, _ = await wallet.redeem(proofs)
|
||||
wallet.status()
|
||||
|
||||
if invalidate:
|
||||
wallet.status()
|
||||
proofs = json.loads(base64.urlsafe_b64decode(invalidate))
|
||||
await wallet.invalidate(proofs)
|
||||
wallet.status()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@click.command("send")
|
||||
@click.option("--send", default=1, help="Mint tokens.")
|
||||
@coro
|
||||
async def send(send):
|
||||
print("asd")
|
||||
# w1_fst_proofs, w1_snd_proofs = await wallet.split(proofs, 20)
|
||||
return "asd"
|
||||
1
cashu.py
Executable file
1
cashu.py
Executable file
@@ -0,0 +1 @@
|
||||
# for mysterious reasons, this file needs to exist for `poetry run cashu` to work
|
||||
53
mint/app.py
53
mint/app.py
@@ -3,13 +3,15 @@ import logging
|
||||
import sys
|
||||
from typing import Union
|
||||
|
||||
import click
|
||||
import uvicorn
|
||||
from ecc.curve import Point, secp256k1
|
||||
from fastapi import FastAPI
|
||||
from loguru import logger
|
||||
|
||||
import core.settings as settings
|
||||
from core.base import MintPayloads, SplitPayload
|
||||
from core.settings import MINT_PRIVATE_KEY
|
||||
from core.settings import MINT_PRIVATE_KEY, MINT_SERVER_HOST, MINT_SERVER_PORT
|
||||
from lightning import WALLET
|
||||
from mint.ledger import Ledger
|
||||
from mint.migrations import m001_initial
|
||||
@@ -148,5 +150,50 @@ async def split(payload: SplitPayload):
|
||||
return {"error": str(exc)}
|
||||
|
||||
|
||||
# if __name__ == "__main__":
|
||||
# uvicorn.run(app, host="127.0.0.1", port=5049)
|
||||
@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()
|
||||
|
||||
@@ -13,14 +13,9 @@ from core.db import Database
|
||||
from core.settings import MAX_ORDER
|
||||
from core.split import amount_split
|
||||
from lightning import WALLET
|
||||
from mint.crud import (
|
||||
get_lightning_invoice,
|
||||
get_proofs_used,
|
||||
invalidate_proof,
|
||||
store_lightning_invoice,
|
||||
store_promise,
|
||||
update_lightning_invoice,
|
||||
)
|
||||
from mint.crud import (get_lightning_invoice, get_proofs_used,
|
||||
invalidate_proof, store_lightning_invoice,
|
||||
store_promise, update_lightning_invoice)
|
||||
|
||||
|
||||
class Ledger:
|
||||
|
||||
@@ -39,3 +39,7 @@ isort = "^5.10.1"
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
mint = "mint.app:main"
|
||||
cashu = "wallet.cashu:cli"
|
||||
118
wallet/cashu.py
Executable file
118
wallet/cashu.py
Executable file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
from functools import wraps
|
||||
|
||||
import click
|
||||
from bech32 import bech32_decode, bech32_encode, convertbits
|
||||
|
||||
from core.settings import MINT_URL
|
||||
from wallet.migrations import m001_initial
|
||||
from wallet.wallet import Wallet as Wallet
|
||||
|
||||
|
||||
async def init_wallet(wallet: Wallet):
|
||||
"""Performs migrations and loads proofs from db."""
|
||||
await m001_initial(db=wallet.db)
|
||||
await wallet.load_proofs()
|
||||
|
||||
|
||||
class NaturalOrderGroup(click.Group):
|
||||
"""For listing commands in help in order of definition"""
|
||||
|
||||
def list_commands(self, ctx):
|
||||
return self.commands.keys()
|
||||
|
||||
|
||||
@click.group(cls=NaturalOrderGroup)
|
||||
@click.option("--host", "-h", default=MINT_URL, help="Mint address.")
|
||||
@click.option("--wallet", "-w", "walletname", default="wallet", help="Wallet to use.")
|
||||
@click.pass_context
|
||||
def cli(
|
||||
ctx,
|
||||
host: str,
|
||||
walletname: str,
|
||||
):
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["HOST"] = host
|
||||
ctx.obj["WALLET_NAME"] = walletname
|
||||
ctx.obj["WALLET"] = Wallet(ctx.obj["HOST"], f"data/{walletname}", walletname)
|
||||
pass
|
||||
|
||||
|
||||
# https://github.com/pallets/click/issues/85#issuecomment-503464628
|
||||
def coro(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
return asyncio.run(f(*args, **kwargs))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
@cli.command("mint", help="Mint tokens.")
|
||||
@click.argument("amount", type=int)
|
||||
@click.option("--hash", default="", help="Hash of the paid invoice.", type=str)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def mint(ctx, amount: int, hash: str):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await m001_initial(db=wallet.db)
|
||||
await wallet.load_proofs()
|
||||
if amount and not hash:
|
||||
print(f"Balance: {wallet.balance}")
|
||||
r = await wallet.request_mint(amount)
|
||||
print(r)
|
||||
|
||||
if amount and hash:
|
||||
print(f"Balance: {wallet.balance}")
|
||||
await wallet.mint(amount, hash)
|
||||
print(f"Balance: {wallet.balance}")
|
||||
|
||||
|
||||
@cli.command("balance", help="See balance.")
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def receive(ctx):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await init_wallet(wallet)
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("send", help="Send tokens.")
|
||||
@click.argument("amount", type=int)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def send(ctx, amount: int):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await init_wallet(wallet)
|
||||
wallet.status()
|
||||
_, send_proofs = await wallet.split(wallet.proofs, amount)
|
||||
print(base64.urlsafe_b64encode(json.dumps(send_proofs).encode()).decode())
|
||||
|
||||
|
||||
@cli.command("receive", help="Receive tokens.")
|
||||
@click.argument("token", type=str)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def receive(ctx, token: str):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await init_wallet(wallet)
|
||||
wallet.status()
|
||||
proofs = json.loads(base64.urlsafe_b64decode(token))
|
||||
_, _ = await wallet.redeem(proofs)
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("burn", help="Burn spent tokens.")
|
||||
@click.argument("token", type=str)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def receive(ctx, token: str):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await init_wallet(wallet)
|
||||
wallet.status()
|
||||
proofs = json.loads(base64.urlsafe_b64decode(token))
|
||||
await wallet.invalidate(proofs)
|
||||
wallet.status()
|
||||
@@ -139,6 +139,7 @@ class Wallet(LedgerAPI):
|
||||
return await self.split(proofs, sum(p["amount"] for p in proofs))
|
||||
|
||||
async def split(self, proofs, amount):
|
||||
assert len(proofs) > 0, ValueError("no proofs provided.")
|
||||
fst_proofs, snd_proofs = super().split(proofs, amount)
|
||||
if len(fst_proofs) == 0 and len(snd_proofs) == 0:
|
||||
raise Exception("received no splits")
|
||||
|
||||
Reference in New Issue
Block a user