mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-24 03:54:21 +01:00
Merge pull request #11 from callebtc/show_pending_tokens
Show pending tokens
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
DEBUG = true
|
||||
DEBUG=FALSE
|
||||
|
||||
CASHU_DIR=~/.cashu
|
||||
|
||||
# WALLET
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ The easiest way to use Cashu is to install the package it via pip:
|
||||
pip install cashu
|
||||
```
|
||||
|
||||
To update Cashu, use `pip install cashu -U`. You can skip the entire next section about Poetry and jump right to [Using Cashu](#using-cashu).
|
||||
To update Cashu, use `pip install cashu -U`. If you have problems running the command above on Ubuntu, run `sudo apt install -y pip pkg-config libpq-dev`.
|
||||
|
||||
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).
|
||||
|
||||
24
core/base.py
24
core/base.py
@@ -6,9 +6,12 @@ from pydantic import BaseModel
|
||||
|
||||
class Proof(BaseModel):
|
||||
amount: int
|
||||
C: str
|
||||
secret: str
|
||||
C: str
|
||||
reserved: bool = False # whether this proof is reserved for sending
|
||||
send_id: str = "" # unique ID of send attempt
|
||||
time_created: str = ""
|
||||
time_reserved: str = ""
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
@@ -17,17 +20,28 @@ class Proof(BaseModel):
|
||||
C=row[1],
|
||||
secret=row[2],
|
||||
reserved=row[3] or False,
|
||||
send_id=row[4] or "",
|
||||
time_created=row[5] or "",
|
||||
time_reserved=row[6] or "",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
assert "secret" in d, "no secret in proof"
|
||||
assert "amount" in d, "no amount in proof"
|
||||
return cls(
|
||||
amount=d["amount"],
|
||||
C=d["C"],
|
||||
secret=d["secret"],
|
||||
reserved=d["reserved"] or False,
|
||||
amount=d.get("amount"),
|
||||
C=d.get("C"),
|
||||
secret=d.get("secret"),
|
||||
reserved=d.get("reserved") or False,
|
||||
send_id=d.get("send_id") or "",
|
||||
time_created=d.get("time_created") or "",
|
||||
time_reserved=d.get("time_reserved") or "",
|
||||
)
|
||||
|
||||
def to_dict(self):
|
||||
return dict(amount=self.amount, secret=self.secret, C=self.C)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__getattribute__(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"
|
||||
|
||||
@@ -11,7 +11,8 @@ 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 +34,7 @@ def startup(app: FastAPI):
|
||||
)
|
||||
|
||||
logger.info(f"Lightning balance: {balance} sat")
|
||||
|
||||
logger.info(f"Data dir: {CASHU_DIR}")
|
||||
logger.info("Mint started.")
|
||||
|
||||
|
||||
|
||||
@@ -13,14 +13,9 @@ 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 (
|
||||
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:
|
||||
|
||||
46
poetry.lock
generated
46
poetry.lock
generated
@@ -323,6 +323,25 @@ docs = ["alabaster (==0.7.12)", "autodocsumm (==0.2.9)", "sphinx (==5.1.1)", "sp
|
||||
lint = ["flake8 (==5.0.4)", "flake8-bugbear (==22.9.11)", "mypy (==0.971)", "pre-commit (>=2.4,<3.0)"]
|
||||
tests = ["pytest", "pytz", "simplejson"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "0.971"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
mypy-extensions = ">=0.4.3"
|
||||
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
|
||||
typed-ast = {version = ">=1.4.0,<2", markers = "python_version < \"3.8\""}
|
||||
typing-extensions = ">=3.10"
|
||||
|
||||
[package.extras]
|
||||
dmypy = ["psutil (>=4.0)"]
|
||||
python2 = ["typed-ast (>=1.4.0,<2)"]
|
||||
reports = ["lxml"]
|
||||
|
||||
[[package]]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
@@ -690,7 +709,7 @@ testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "68f6f6e33100fdd1bd86879f8c146a96a5ee822b0a6e8b9ec27205f6eab1fcd8"
|
||||
content-hash = "d5ee88384ec1ec1774f9fab845d7e8ac6e8ab1859506bcce178f6783b98e535c"
|
||||
|
||||
[metadata.files]
|
||||
anyio = [
|
||||
@@ -915,6 +934,31 @@ marshmallow = [
|
||||
{file = "marshmallow-3.18.0-py3-none-any.whl", hash = "sha256:35e02a3a06899c9119b785c12a22f4cda361745d66a71ab691fd7610202ae104"},
|
||||
{file = "marshmallow-3.18.0.tar.gz", hash = "sha256:6804c16114f7fce1f5b4dadc31f4674af23317fcc7f075da21e35c1a35d781f7"},
|
||||
]
|
||||
mypy = [
|
||||
{file = "mypy-0.971-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f2899a3cbd394da157194f913a931edfd4be5f274a88041c9dc2d9cdcb1c315c"},
|
||||
{file = "mypy-0.971-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98e02d56ebe93981c41211c05adb630d1d26c14195d04d95e49cd97dbc046dc5"},
|
||||
{file = "mypy-0.971-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:19830b7dba7d5356d3e26e2427a2ec91c994cd92d983142cbd025ebe81d69cf3"},
|
||||
{file = "mypy-0.971-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02ef476f6dcb86e6f502ae39a16b93285fef97e7f1ff22932b657d1ef1f28655"},
|
||||
{file = "mypy-0.971-cp310-cp310-win_amd64.whl", hash = "sha256:25c5750ba5609a0c7550b73a33deb314ecfb559c350bb050b655505e8aed4103"},
|
||||
{file = "mypy-0.971-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d3348e7eb2eea2472db611486846742d5d52d1290576de99d59edeb7cd4a42ca"},
|
||||
{file = "mypy-0.971-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3fa7a477b9900be9b7dd4bab30a12759e5abe9586574ceb944bc29cddf8f0417"},
|
||||
{file = "mypy-0.971-cp36-cp36m-win_amd64.whl", hash = "sha256:2ad53cf9c3adc43cf3bea0a7d01a2f2e86db9fe7596dfecb4496a5dda63cbb09"},
|
||||
{file = "mypy-0.971-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:855048b6feb6dfe09d3353466004490b1872887150c5bb5caad7838b57328cc8"},
|
||||
{file = "mypy-0.971-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:23488a14a83bca6e54402c2e6435467a4138785df93ec85aeff64c6170077fb0"},
|
||||
{file = "mypy-0.971-cp37-cp37m-win_amd64.whl", hash = "sha256:4b21e5b1a70dfb972490035128f305c39bc4bc253f34e96a4adf9127cf943eb2"},
|
||||
{file = "mypy-0.971-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9796a2ba7b4b538649caa5cecd398d873f4022ed2333ffde58eaf604c4d2cb27"},
|
||||
{file = "mypy-0.971-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a361d92635ad4ada1b1b2d3630fc2f53f2127d51cf2def9db83cba32e47c856"},
|
||||
{file = "mypy-0.971-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b793b899f7cf563b1e7044a5c97361196b938e92f0a4343a5d27966a53d2ec71"},
|
||||
{file = "mypy-0.971-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d1ea5d12c8e2d266b5fb8c7a5d2e9c0219fedfeb493b7ed60cd350322384ac27"},
|
||||
{file = "mypy-0.971-cp38-cp38-win_amd64.whl", hash = "sha256:23c7ff43fff4b0df93a186581885c8512bc50fc4d4910e0f838e35d6bb6b5e58"},
|
||||
{file = "mypy-0.971-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f7656b69974a6933e987ee8ffb951d836272d6c0f81d727f1d0e2696074d9e6"},
|
||||
{file = "mypy-0.971-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d2022bfadb7a5c2ef410d6a7c9763188afdb7f3533f22a0a32be10d571ee4bbe"},
|
||||
{file = "mypy-0.971-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ef943c72a786b0f8d90fd76e9b39ce81fb7171172daf84bf43eaf937e9f220a9"},
|
||||
{file = "mypy-0.971-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d744f72eb39f69312bc6c2abf8ff6656973120e2eb3f3ec4f758ed47e414a4bf"},
|
||||
{file = "mypy-0.971-cp39-cp39-win_amd64.whl", hash = "sha256:77a514ea15d3007d33a9e2157b0ba9c267496acf12a7f2b9b9f8446337aac5b0"},
|
||||
{file = "mypy-0.971-py3-none-any.whl", hash = "sha256:0d054ef16b071149917085f51f89555a576e2618d5d9dd70bd6eea6410af3ac9"},
|
||||
{file = "mypy-0.971.tar.gz", hash = "sha256:40b0f21484238269ae6a57200c807d80debc6459d444c0489a102d7c6a75fa56"},
|
||||
]
|
||||
mypy-extensions = [
|
||||
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
|
||||
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "cashu"
|
||||
version = "0.0.1"
|
||||
version = "0.1.8"
|
||||
description = "Ecash wallet and mint."
|
||||
authors = ["calle <callebtc@protonmail.com>"]
|
||||
license = "MIT"
|
||||
@@ -36,6 +36,11 @@ secp256k1 = "^0.14.0"
|
||||
black = {version = "^22.8.0", allow-prereleases = true}
|
||||
isort = "^5.10.1"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
mypy = "^0.971"
|
||||
black = {version = "^22.8.0", allow-prereleases = true}
|
||||
isort = "^5.10.1"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
6
setup.py
6
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()
|
||||
@@ -13,7 +13,7 @@ entry_points = {"console_scripts": ["cashu = wallet.cashu:cli"]}
|
||||
|
||||
setuptools.setup(
|
||||
name="cashu",
|
||||
version="0.1.7",
|
||||
version="0.1.8",
|
||||
description="Ecash wallet and mint with Bitcoin Lightning support",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
|
||||
@@ -4,8 +4,10 @@ import asyncio
|
||||
import base64
|
||||
import json
|
||||
import math
|
||||
from datetime import datetime
|
||||
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 +17,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, DEBUG, LIGHTNING, MINT_URL
|
||||
from wallet import migrations
|
||||
from wallet.crud import get_reserved_proofs
|
||||
from wallet.wallet import Wallet as Wallet
|
||||
@@ -38,17 +40,11 @@ class NaturalOrderGroup(click.Group):
|
||||
@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,
|
||||
):
|
||||
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"{str(Path.home())}/.cashu/{walletname}", walletname
|
||||
)
|
||||
ctx.obj["WALLET"] = Wallet(ctx.obj["HOST"], f"{CASHU_DIR}/{walletname}", walletname)
|
||||
pass
|
||||
|
||||
|
||||
@@ -106,8 +102,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()
|
||||
|
||||
|
||||
@@ -127,17 +123,27 @@ async def receive(ctx, token: str):
|
||||
@cli.command("burn", help="Burn spent tokens.")
|
||||
@click.argument("token", required=False, type=str)
|
||||
@click.option("--all", "-a", default=False, is_flag=True, help="Burn all spent tokens.")
|
||||
@click.option(
|
||||
"--force", "-f", default=False, is_flag=True, help="Force check on all tokens."
|
||||
)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def burn(ctx, token: str, all: bool):
|
||||
async def burn(ctx, token: str, all: bool, force: bool):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await init_wallet(wallet)
|
||||
if not (all or token) or (token and all):
|
||||
print("Error: enter a token or use --all to burn all pending tokens.")
|
||||
if not (all or token or force) or (token and all):
|
||||
print(
|
||||
"Error: enter a token or use --all to burn all pending tokens or --force to check all tokens."
|
||||
)
|
||||
return
|
||||
if all:
|
||||
# check only those who are flagged as reserved
|
||||
proofs = await get_reserved_proofs(wallet.db)
|
||||
if force:
|
||||
# check all proofs in db
|
||||
proofs = wallet.proofs
|
||||
else:
|
||||
# check only the specified ones
|
||||
proofs = [
|
||||
Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(token))
|
||||
]
|
||||
@@ -146,6 +152,29 @@ 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)
|
||||
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)
|
||||
reserved_date = datetime.utcfromtimestamp(
|
||||
int(grouped_proofs[0].time_reserved)
|
||||
).strftime("%Y-%m-%d %H:%M:%S")
|
||||
print(
|
||||
f"Amount: {sum([p['amount'] for p in grouped_proofs])} sat Sent: {reserved_date} ID: {key}\n"
|
||||
)
|
||||
print(token)
|
||||
print("")
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("pay", help="Pay lightning invoice.")
|
||||
@click.argument("invoice", type=str)
|
||||
@click.pass_context
|
||||
@@ -168,3 +197,16 @@ async def pay(ctx, invoice: str):
|
||||
_, send_proofs = await wallet.split_to_send(wallet.proofs, amount)
|
||||
await wallet.pay_lightning(send_proofs, amount, invoice)
|
||||
wallet.status()
|
||||
|
||||
|
||||
@cli.command("info", help="Information about Cashu wallet.")
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def info(ctx):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await init_wallet(wallet)
|
||||
wallet.status()
|
||||
print(f"Debug: {DEBUG}")
|
||||
print(f"Cashu dir: {CASHU_DIR}")
|
||||
print(f"Mint URL: {MINT_URL}")
|
||||
return
|
||||
|
||||
@@ -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_and_timestamps(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,8 @@
|
||||
import base64
|
||||
import json
|
||||
import random
|
||||
import secrets as scrts
|
||||
import uuid
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
@@ -15,6 +19,7 @@ from core.base import (
|
||||
)
|
||||
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
|
||||
|
||||
@@ -52,6 +57,10 @@ class LedgerAPI:
|
||||
proofs.append(proof)
|
||||
return proofs
|
||||
|
||||
def _generate_secret(self, randombits=128):
|
||||
"""Returns base64 encoded random string."""
|
||||
return scrts.token_urlsafe(randombits // 8)
|
||||
|
||||
def request_mint(self, amount):
|
||||
"""Requests a mint from the server and returns Lightning invoice."""
|
||||
r = requests.get(self.url + "/mint", params={"amount": amount})
|
||||
@@ -63,7 +72,7 @@ class LedgerAPI:
|
||||
secrets = []
|
||||
rs = []
|
||||
for amount in amounts:
|
||||
secret = str(random.getrandbits(128))
|
||||
secret = self._generate_secret()
|
||||
secrets.append(secret)
|
||||
B_, r = b_dhke.step1_bob(secret)
|
||||
rs.append(r)
|
||||
@@ -91,7 +100,7 @@ class LedgerAPI:
|
||||
secrets = []
|
||||
payloads: MintPayloads = MintPayloads()
|
||||
for output_amt in fst_outputs + snd_outputs:
|
||||
secret = str(random.getrandbits(128))
|
||||
secret = self._generate_secret()
|
||||
B_, r = b_dhke.step1_bob(secret)
|
||||
secrets.append((r, secret))
|
||||
payload: BlindedMessage = BlindedMessage(
|
||||
@@ -186,6 +195,14 @@ class Wallet(LedgerAPI):
|
||||
raise Exception("could not pay invoice.")
|
||||
return status["paid"]
|
||||
|
||||
@staticmethod
|
||||
async def serialize_proofs(proofs: List[Proof]):
|
||||
proofs_serialized = [p.to_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 +211,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