mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 18:44:20 +01:00
pending
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
DEBUG = true
|
||||
|
||||
CASHU_DIR=~/.cashu
|
||||
|
||||
# WALLET
|
||||
|
||||
MINT_HOST=127.0.0.1
|
||||
|
||||
@@ -9,6 +9,7 @@ class Proof(BaseModel):
|
||||
C: str
|
||||
secret: str
|
||||
reserved: bool = False # whether this proof is reserved for sending
|
||||
send_id: str = "" # unique ID of send attempt
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
@@ -17,6 +18,7 @@ class Proof(BaseModel):
|
||||
C=row[1],
|
||||
secret=row[2],
|
||||
reserved=row[3] or False,
|
||||
send_id=row[4] or "",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -26,6 +28,7 @@ class Proof(BaseModel):
|
||||
C=d["C"],
|
||||
secret=d["secret"],
|
||||
reserved=d["reserved"] or False,
|
||||
send_id=d["send_id"] or "",
|
||||
)
|
||||
|
||||
def __getitem__(self, key):
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
from pathlib import Path
|
||||
|
||||
from environs import Env # type: ignore
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
DEBUG = env.bool("DEBUG", default=False)
|
||||
CASHU_DIR = env.str("CASHU_DIR", default="~/.cashu")
|
||||
CASHU_DIR = CASHU_DIR.replace("~", str(Path.home()))
|
||||
assert len(CASHU_DIR), "CASHU_DIR not defined"
|
||||
|
||||
LIGHTNING = env.bool("LIGHTNING", default=True)
|
||||
LIGHTNING_FEE_PERCENT = env.float("LIGHTNING_FEE_PERCENT", default=1.0)
|
||||
assert LIGHTNING_FEE_PERCENT >= 0, "LIGHTNING_FEE_PERCENT must be at least 0"
|
||||
|
||||
@@ -8,8 +8,13 @@ import requests
|
||||
|
||||
from 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):
|
||||
|
||||
@@ -11,7 +11,12 @@ from secp256k1 import PublicKey
|
||||
|
||||
import core.settings as settings
|
||||
from core.base import CheckPayload, MeltPayload, MintPayloads, SplitPayload
|
||||
from core.settings import MINT_PRIVATE_KEY, MINT_SERVER_HOST, MINT_SERVER_PORT
|
||||
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
|
||||
@@ -33,7 +38,7 @@ def startup(app: FastAPI):
|
||||
)
|
||||
|
||||
logger.info(f"Lightning balance: {balance} sat")
|
||||
|
||||
logger.info(f"Data dir: {CASHU_DIR}")
|
||||
logger.info("Mint started.")
|
||||
|
||||
|
||||
|
||||
4
setup.py
4
setup.py
@@ -1,7 +1,7 @@
|
||||
import setuptools
|
||||
|
||||
from os import path
|
||||
|
||||
import setuptools
|
||||
|
||||
this_directory = path.abspath(path.dirname(__file__))
|
||||
with open(path.join(this_directory, "README.md"), encoding="utf-8") as f:
|
||||
long_description = f.read()
|
||||
|
||||
@@ -5,7 +5,8 @@ import base64
|
||||
import json
|
||||
import math
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
from itertools import groupby
|
||||
from operator import itemgetter
|
||||
|
||||
import click
|
||||
from bech32 import bech32_decode, bech32_encode, convertbits
|
||||
@@ -15,7 +16,7 @@ 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 LIGHTNING, MINT_URL
|
||||
from core.settings import CASHU_DIR, LIGHTNING, MINT_URL
|
||||
from wallet import migrations
|
||||
from wallet.crud import get_reserved_proofs
|
||||
from wallet.wallet import Wallet as Wallet
|
||||
@@ -46,9 +47,7 @@ def cli(
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj["HOST"] = host
|
||||
ctx.obj["WALLET_NAME"] = walletname
|
||||
ctx.obj["WALLET"] = Wallet(
|
||||
ctx.obj["HOST"], f"{str(Path.home())}/.cashu/{walletname}", walletname
|
||||
)
|
||||
ctx.obj["WALLET"] = Wallet(ctx.obj["HOST"], f"{CASHU_DIR}/{walletname}", walletname)
|
||||
pass
|
||||
|
||||
|
||||
@@ -106,8 +105,8 @@ async def send(ctx, amount: int):
|
||||
wallet.status()
|
||||
_, send_proofs = await wallet.split_to_send(wallet.proofs, amount)
|
||||
await wallet.set_reserved(send_proofs, reserved=True)
|
||||
proofs_serialized = [p.dict() for p in send_proofs]
|
||||
print(base64.urlsafe_b64encode(json.dumps(proofs_serialized).encode()).decode())
|
||||
token = await wallet.serialize_proofs(send_proofs)
|
||||
print(token)
|
||||
wallet.status()
|
||||
|
||||
|
||||
@@ -146,6 +145,25 @@ async def burn(ctx, token: str, all: bool):
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("pending", help="Show pending tokens.")
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def pending(ctx):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await init_wallet(wallet)
|
||||
wallet.status()
|
||||
reserved_proofs = await get_reserved_proofs(wallet.db)
|
||||
if len(reserved_proofs):
|
||||
sorted_proofs = sorted(reserved_proofs, key=itemgetter("send_id"))
|
||||
for key, value in groupby(sorted_proofs, key=itemgetter("send_id")):
|
||||
grouped_proofs = list(value)
|
||||
token = await wallet.serialize_proofs(grouped_proofs)
|
||||
print(
|
||||
f"Amount: {sum([p['amount'] for p in grouped_proofs])} sat. ID: {key}"
|
||||
)
|
||||
print(token)
|
||||
|
||||
|
||||
@cli.command("pay", help="Pay lightning invoice.")
|
||||
@click.argument("invoice", type=str)
|
||||
@click.pass_context
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import secrets
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from core.base import Proof
|
||||
@@ -14,14 +14,10 @@ async def store_proof(
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
INSERT INTO proofs
|
||||
(amount, C, secret)
|
||||
VALUES (?, ?, ?)
|
||||
(amount, C, secret, time_created)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
proof.amount,
|
||||
str(proof.C),
|
||||
str(proof.secret),
|
||||
),
|
||||
(proof.amount, str(proof.C), str(proof.secret), int(time.time())),
|
||||
)
|
||||
|
||||
|
||||
@@ -69,24 +65,35 @@ async def invalidate_proof(
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
INSERT INTO proofs_used
|
||||
(amount, C, secret)
|
||||
VALUES (?, ?, ?)
|
||||
(amount, C, secret, time_used)
|
||||
VALUES (?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
proof.amount,
|
||||
str(proof.C),
|
||||
str(proof.secret),
|
||||
),
|
||||
(proof.amount, str(proof.C), str(proof.secret), int(time.time())),
|
||||
)
|
||||
|
||||
|
||||
async def update_proof_reserved(
|
||||
proof: Proof,
|
||||
reserved: bool,
|
||||
db: Database,
|
||||
send_id: str = None,
|
||||
db: Database = None,
|
||||
conn: Optional[Connection] = None,
|
||||
):
|
||||
clauses = []
|
||||
values = []
|
||||
clauses.append("reserved = ?")
|
||||
values.append(reserved)
|
||||
|
||||
if send_id:
|
||||
clauses.append("send_id = ?")
|
||||
values.append(send_id)
|
||||
|
||||
if reserved:
|
||||
# set the time of reserving
|
||||
clauses.append("time_reserved = ?")
|
||||
values.append(int(time.time()))
|
||||
|
||||
await (conn or db).execute(
|
||||
"UPDATE proofs SET reserved = ? WHERE secret = ?",
|
||||
(reserved, str(proof.secret)),
|
||||
f"UPDATE proofs SET {', '.join(clauses)} WHERE secret = ?",
|
||||
(*values, str(proof.secret)),
|
||||
)
|
||||
|
||||
@@ -68,3 +68,14 @@ async def m002_add_proofs_reserved(db):
|
||||
"""
|
||||
|
||||
await db.execute("ALTER TABLE proofs ADD COLUMN reserved BOOL")
|
||||
|
||||
|
||||
async def m003_add_proofs_sendid(db):
|
||||
"""
|
||||
Column with unique ID for each initiated send attempt
|
||||
so proofs can be later grouped together for each send attempt.
|
||||
"""
|
||||
await db.execute("ALTER TABLE proofs ADD COLUMN send_id TEXT")
|
||||
await db.execute("ALTER TABLE proofs ADD COLUMN time_created TIMESTAMP")
|
||||
await db.execute("ALTER TABLE proofs ADD COLUMN time_reserved TIMESTAMP")
|
||||
await db.execute("ALTER TABLE proofs_used ADD COLUMN time_used TIMESTAMP")
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import base64
|
||||
import json
|
||||
import random
|
||||
import uuid
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
@@ -186,6 +189,14 @@ class Wallet(LedgerAPI):
|
||||
raise Exception("could not pay invoice.")
|
||||
return status["paid"]
|
||||
|
||||
@staticmethod
|
||||
async def serialize_proofs(proofs: List[Proof]):
|
||||
proofs_serialized = [p.dict() for p in proofs]
|
||||
token = base64.urlsafe_b64encode(
|
||||
json.dumps(proofs_serialized).encode()
|
||||
).decode()
|
||||
return token
|
||||
|
||||
async def split_to_send(self, proofs: List[Proof], amount):
|
||||
"""Like self.split but only considers non-reserved tokens."""
|
||||
if len([p for p in proofs if not p.reserved]) <= 0:
|
||||
@@ -194,9 +205,12 @@ class Wallet(LedgerAPI):
|
||||
|
||||
async def set_reserved(self, proofs: List[Proof], reserved: bool):
|
||||
"""Mark a proof as reserved to avoid reuse or delete marking."""
|
||||
uuid_str = str(uuid.uuid1())
|
||||
for proof in proofs:
|
||||
proof.reserved = True
|
||||
await update_proof_reserved(proof, reserved=reserved, db=self.db)
|
||||
await update_proof_reserved(
|
||||
proof, reserved=reserved, send_id=uuid_str, db=self.db
|
||||
)
|
||||
|
||||
async def check_spendable(self, proofs):
|
||||
return await super().check_spendable(proofs)
|
||||
|
||||
Reference in New Issue
Block a user