mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 10:34:20 +01:00
save lightning invoices
This commit is contained in:
@@ -12,15 +12,6 @@ class P2SHScript(BaseModel):
|
||||
signature: str
|
||||
address: Union[str, None] = None
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
return cls(
|
||||
address=row[0],
|
||||
script=row[1],
|
||||
signature=row[2],
|
||||
used=row[3],
|
||||
)
|
||||
|
||||
|
||||
class Proof(BaseModel):
|
||||
id: str = ""
|
||||
@@ -28,36 +19,10 @@ class Proof(BaseModel):
|
||||
secret: str = ""
|
||||
C: str = ""
|
||||
script: Union[P2SHScript, None] = None
|
||||
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):
|
||||
return cls(
|
||||
amount=row[0],
|
||||
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 "",
|
||||
id=row[7] or "",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
assert "amount" in d, "no amount in proof"
|
||||
return cls(
|
||||
amount=d.get("amount"),
|
||||
C=d.get("C"),
|
||||
secret=d.get("secret") or "",
|
||||
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 "",
|
||||
)
|
||||
reserved: Union[None, bool] = False # whether this proof is reserved for sending
|
||||
send_id: Union[None, str] = "" # unique ID of send attempt
|
||||
time_created: Union[None, str] = ""
|
||||
time_reserved: Union[None, str] = ""
|
||||
|
||||
def to_dict(self):
|
||||
return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C)
|
||||
@@ -81,17 +46,12 @@ class Proofs(BaseModel):
|
||||
class Invoice(BaseModel):
|
||||
amount: int
|
||||
pr: str
|
||||
hash: str
|
||||
issued: bool = False
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
return cls(
|
||||
amount=int(row[0]),
|
||||
pr=str(row[1]),
|
||||
hash=str(row[2]),
|
||||
issued=bool(row[3]),
|
||||
)
|
||||
hash: Union[None, str] = None
|
||||
preimage: Union[str, None] = None
|
||||
issued: Union[None, bool] = False
|
||||
paid: Union[None, bool] = False
|
||||
time_created: Union[None, str, int, float] = ""
|
||||
time_paid: Union[None, str, int, float] = ""
|
||||
|
||||
|
||||
class BlindedMessage(BaseModel):
|
||||
@@ -104,14 +64,6 @@ class BlindedSignature(BaseModel):
|
||||
amount: int
|
||||
C_: str
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
return cls(
|
||||
id=d.get("id"),
|
||||
amount=d["amount"],
|
||||
C_=d["C_"],
|
||||
)
|
||||
|
||||
|
||||
class MintRequest(BaseModel):
|
||||
blinded_messages: List[BlindedMessage] = []
|
||||
@@ -173,16 +125,6 @@ class KeyBase(BaseModel):
|
||||
amount: int
|
||||
pubkey: str
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
if row is None:
|
||||
return cls
|
||||
return cls(
|
||||
id=row[0],
|
||||
amount=int(row[1]),
|
||||
pubkey=row[2],
|
||||
)
|
||||
|
||||
|
||||
class WalletKeyset:
|
||||
id: str
|
||||
@@ -213,19 +155,6 @@ class WalletKeyset:
|
||||
self.public_keys = pubkeys
|
||||
self.id = derive_keyset_id(self.public_keys)
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
if row is None:
|
||||
return cls
|
||||
return cls(
|
||||
id=row[0],
|
||||
mint_url=row[1],
|
||||
valid_from=row[2],
|
||||
valid_to=row[3],
|
||||
first_seen=row[4],
|
||||
active=row[5],
|
||||
)
|
||||
|
||||
|
||||
class MintKeyset:
|
||||
id: str
|
||||
@@ -266,20 +195,6 @@ class MintKeyset:
|
||||
self.public_keys = derive_pubkeys(self.private_keys)
|
||||
self.id = derive_keyset_id(self.public_keys)
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
if row is None:
|
||||
return cls
|
||||
return cls(
|
||||
id=row[0],
|
||||
derivation_path=row[1],
|
||||
valid_from=row[2],
|
||||
valid_to=row[3],
|
||||
first_seen=row[4],
|
||||
active=row[5],
|
||||
version=row[6],
|
||||
)
|
||||
|
||||
def get_keybase(self):
|
||||
return {
|
||||
k: KeyBase(id=self.id, amount=k, pubkey=v.serialize().hex())
|
||||
|
||||
@@ -135,7 +135,7 @@ async def get_lightning_invoice(
|
||||
""",
|
||||
(hash,),
|
||||
)
|
||||
return Invoice.from_row(row)
|
||||
return Invoice(**row)
|
||||
|
||||
|
||||
async def update_lightning_invoice(
|
||||
@@ -204,4 +204,4 @@ async def get_keyset(
|
||||
""",
|
||||
tuple(values),
|
||||
)
|
||||
return [MintKeyset.from_row(row) for row in rows]
|
||||
return [MintKeyset(**row) for row in rows]
|
||||
|
||||
@@ -85,14 +85,14 @@ async def invoice(ctx, amount: int, hash: str):
|
||||
if not LIGHTNING:
|
||||
r = await wallet.mint(amount)
|
||||
elif amount and not hash:
|
||||
r = await wallet.request_mint(amount)
|
||||
if "pr" in r:
|
||||
invoice = await wallet.request_mint(amount)
|
||||
if invoice.pr:
|
||||
print(f"Pay invoice to mint {amount} sat:")
|
||||
print("")
|
||||
print(f"Invoice: {r['pr']}")
|
||||
print(f"Invoice: {invoice.pr}")
|
||||
print("")
|
||||
print(
|
||||
f"Execute this command if you abort the check:\ncashu invoice {amount} --hash {r['hash']}"
|
||||
f"Execute this command if you abort the check:\ncashu invoice {amount} --hash {invoice.hash}"
|
||||
)
|
||||
check_until = time.time() + 5 * 60 # check for five minutes
|
||||
print("")
|
||||
@@ -105,7 +105,7 @@ async def invoice(ctx, amount: int, hash: str):
|
||||
while time.time() < check_until and not paid:
|
||||
time.sleep(3)
|
||||
try:
|
||||
await wallet.mint(amount, r["hash"])
|
||||
await wallet.mint(amount, invoice.hash)
|
||||
paid = True
|
||||
print(" Invoice paid.")
|
||||
except Exception as e:
|
||||
@@ -221,7 +221,7 @@ async def receive(ctx, coin: str, lock: str):
|
||||
signature = p2shscripts[0].signature
|
||||
else:
|
||||
script, signature = None, None
|
||||
proofs = [Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(coin))]
|
||||
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(coin))]
|
||||
_, _ = await wallet.redeem(proofs, scnd_script=script, scnd_siganture=signature)
|
||||
wallet.status()
|
||||
|
||||
@@ -250,9 +250,7 @@ async def burn(ctx, coin: str, all: bool, force: bool):
|
||||
proofs = wallet.proofs
|
||||
else:
|
||||
# check only the specified ones
|
||||
proofs = [
|
||||
Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(coin))
|
||||
]
|
||||
proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(coin))]
|
||||
wallet.status()
|
||||
await wallet.invalidate(proofs)
|
||||
wallet.status()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import time
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from cashu.core.base import KeyBase, P2SHScript, Proof, WalletKeyset
|
||||
from cashu.core.base import Invoice, KeyBase, P2SHScript, Proof, WalletKeyset
|
||||
from cashu.core.db import Connection, Database
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ async def get_proofs(
|
||||
SELECT * from proofs
|
||||
"""
|
||||
)
|
||||
return [Proof.from_row(r) for r in rows]
|
||||
return [Proof(**dict(r)) for r in rows]
|
||||
|
||||
|
||||
async def get_reserved_proofs(
|
||||
@@ -45,7 +45,7 @@ async def get_reserved_proofs(
|
||||
WHERE reserved
|
||||
"""
|
||||
)
|
||||
return [Proof.from_row(r) for r in rows]
|
||||
return [Proof(**r) for r in rows]
|
||||
|
||||
|
||||
async def invalidate_proof(
|
||||
@@ -162,7 +162,7 @@ async def get_unused_locks(
|
||||
""",
|
||||
tuple(args),
|
||||
)
|
||||
return [P2SHScript.from_row(r) for r in rows]
|
||||
return [P2SHScript(**r) for r in rows]
|
||||
|
||||
|
||||
async def update_p2sh_used(
|
||||
@@ -233,4 +233,78 @@ async def get_keyset(
|
||||
""",
|
||||
tuple(values),
|
||||
)
|
||||
return WalletKeyset.from_row(row) if row is not None else None
|
||||
return WalletKeyset(**row) if row is not None else None
|
||||
|
||||
|
||||
async def store_lightning_invoice(
|
||||
db: Database,
|
||||
invoice: Invoice,
|
||||
conn: Optional[Connection] = None,
|
||||
):
|
||||
|
||||
await (conn or db).execute(
|
||||
f"""
|
||||
INSERT INTO invoices
|
||||
(amount, pr, hash, preimage, paid, time_created, time_paid)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
invoice.amount,
|
||||
invoice.pr,
|
||||
invoice.hash,
|
||||
invoice.preimage,
|
||||
invoice.paid,
|
||||
invoice.time_created,
|
||||
invoice.time_paid,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def get_lightning_invoice(
|
||||
db: Database,
|
||||
hash: str = None,
|
||||
conn: Optional[Connection] = None,
|
||||
):
|
||||
clauses = []
|
||||
values: List[Any] = []
|
||||
if hash:
|
||||
clauses.append("hash = ?")
|
||||
values.append(hash)
|
||||
|
||||
where = ""
|
||||
if clauses:
|
||||
where = f"WHERE {' AND '.join(clauses)}"
|
||||
|
||||
row = await (conn or db).fetchone(
|
||||
f"""
|
||||
SELECT * from invoices
|
||||
{where}
|
||||
""",
|
||||
(hash,),
|
||||
)
|
||||
return Invoice(**row)
|
||||
|
||||
|
||||
async def update_lightning_invoice(
|
||||
db: Database,
|
||||
hash: str,
|
||||
paid: bool,
|
||||
time_paid: int = None,
|
||||
conn: Optional[Connection] = None,
|
||||
):
|
||||
clauses = []
|
||||
values: List[Any] = []
|
||||
clauses.append("paid = ?")
|
||||
values.append(paid)
|
||||
|
||||
if time_paid:
|
||||
clauses.append("time_paid = ?")
|
||||
values.append(time_paid)
|
||||
|
||||
await (conn or db).execute(
|
||||
f"UPDATE invoices SET {', '.join(clauses)} WHERE hash = ?",
|
||||
(
|
||||
*values,
|
||||
hash,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -119,18 +119,28 @@ async def m005_wallet_keysets(db: Database):
|
||||
);
|
||||
"""
|
||||
)
|
||||
# await db.execute(
|
||||
# f"""
|
||||
# CREATE TABLE IF NOT EXISTS mint_pubkeys (
|
||||
# id TEXT NOT NULL,
|
||||
# amount INTEGER NOT NULL,
|
||||
# pubkey TEXT NOT NULL,
|
||||
|
||||
# UNIQUE (id, pubkey)
|
||||
|
||||
# );
|
||||
# """
|
||||
# )
|
||||
|
||||
await db.execute("ALTER TABLE proofs ADD COLUMN id TEXT")
|
||||
await db.execute("ALTER TABLE proofs_used ADD COLUMN id TEXT")
|
||||
|
||||
|
||||
async def m006_invoices(db: Database):
|
||||
"""
|
||||
Stores Lightning invoices.
|
||||
"""
|
||||
await db.execute(
|
||||
f"""
|
||||
CREATE TABLE IF NOT EXISTS invoices (
|
||||
amount INTEGER NOT NULL,
|
||||
pr TEXT NOT NULL,
|
||||
hash TEXT,
|
||||
preimage TEXT,
|
||||
paid BOOL DEFAULT FALSE,
|
||||
time_created TIMESTAMP DEFAULT {db.timestamp_now},
|
||||
time_paid TIMESTAMP DEFAULT {db.timestamp_now},
|
||||
|
||||
UNIQUE (hash)
|
||||
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import base64
|
||||
import json
|
||||
import secrets as scrts
|
||||
import time
|
||||
import uuid
|
||||
from itertools import groupby
|
||||
from typing import Dict, List
|
||||
@@ -14,6 +15,7 @@ from cashu.core.base import (
|
||||
BlindedSignature,
|
||||
CheckFeesRequest,
|
||||
CheckRequest,
|
||||
Invoice,
|
||||
MeltRequest,
|
||||
MintRequest,
|
||||
P2SHScript,
|
||||
@@ -38,8 +40,10 @@ from cashu.wallet.crud import (
|
||||
invalidate_proof,
|
||||
secret_used,
|
||||
store_keyset,
|
||||
store_lightning_invoice,
|
||||
store_p2sh,
|
||||
store_proof,
|
||||
update_lightning_invoice,
|
||||
update_proof_reserved,
|
||||
)
|
||||
|
||||
@@ -175,7 +179,7 @@ class LedgerAPI:
|
||||
resp.raise_for_status()
|
||||
return_dict = resp.json()
|
||||
self.raise_on_error(return_dict)
|
||||
return return_dict
|
||||
return Invoice(amount=amount, pr=return_dict["pr"], hash=return_dict["hash"])
|
||||
|
||||
async def mint(self, amounts, payment_hash=None):
|
||||
"""Mints new coins and returns a proof of promise."""
|
||||
@@ -192,7 +196,7 @@ class LedgerAPI:
|
||||
promises_list = resp.json()
|
||||
self.raise_on_error(promises_list)
|
||||
|
||||
promises = [BlindedSignature.from_dict(p) for p in promises_list]
|
||||
promises = [BlindedSignature(**p) for p in promises_list]
|
||||
return self._construct_proofs(promises, secrets, rs)
|
||||
|
||||
async def split(self, proofs, amount, scnd_secret: str = None):
|
||||
@@ -246,8 +250,8 @@ class LedgerAPI:
|
||||
promises_dict = resp.json()
|
||||
self.raise_on_error(promises_dict)
|
||||
|
||||
promises_fst = [BlindedSignature.from_dict(p) for p in promises_dict["fst"]]
|
||||
promises_snd = [BlindedSignature.from_dict(p) for p in promises_dict["snd"]]
|
||||
promises_fst = [BlindedSignature(**p) for p in promises_dict["fst"]]
|
||||
promises_snd = [BlindedSignature(**p) for p in promises_dict["snd"]]
|
||||
# Construct proofs from promises (i.e., unblind signatures)
|
||||
frst_proofs = self._construct_proofs(
|
||||
promises_fst, secrets[: len(promises_fst)], rs[: len(promises_fst)]
|
||||
@@ -340,7 +344,10 @@ class Wallet(LedgerAPI):
|
||||
return {key: list(group) for key, group in groupby(proofs, lambda p: p.id)}
|
||||
|
||||
async def request_mint(self, amount):
|
||||
return super().request_mint(amount)
|
||||
invoice = super().request_mint(amount)
|
||||
invoice.time_created = int(time.time())
|
||||
await store_lightning_invoice(db=self.db, invoice=invoice)
|
||||
return invoice
|
||||
|
||||
async def mint(self, amount: int, payment_hash: str = None):
|
||||
split = amount_split(amount)
|
||||
@@ -348,6 +355,10 @@ class Wallet(LedgerAPI):
|
||||
if proofs == []:
|
||||
raise Exception("received no proofs.")
|
||||
await self._store_proofs(proofs)
|
||||
if payment_hash:
|
||||
await update_lightning_invoice(
|
||||
db=self.db, hash=payment_hash, paid=True, time_paid=int(time.time())
|
||||
)
|
||||
self.proofs += proofs
|
||||
return proofs
|
||||
|
||||
@@ -389,6 +400,14 @@ class Wallet(LedgerAPI):
|
||||
status = await super().pay_lightning(proofs, invoice)
|
||||
if status["paid"] == True:
|
||||
await self.invalidate(proofs)
|
||||
invoice_obj = Invoice(
|
||||
amount=-sum_proofs(proofs),
|
||||
pr=invoice,
|
||||
preimage=status.get("preimage"),
|
||||
paid=True,
|
||||
time_paid=time.time(),
|
||||
)
|
||||
await store_lightning_invoice(db=self.db, invoice=invoice_obj)
|
||||
else:
|
||||
raise Exception("could not pay invoice.")
|
||||
return status["paid"]
|
||||
|
||||
Reference in New Issue
Block a user