mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-23 03:34:19 +01:00
keyset working
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
from sqlite3 import Row
|
||||
from typing import List, Union, Dict
|
||||
from typing import List, Union, Dict, Any
|
||||
from cashu.core.crypto import derive_keyset_id, derive_keys, derive_pubkeys
|
||||
from cashu.core.secp import PrivateKey, PublicKey
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -9,12 +13,10 @@ class CashuError(BaseModel):
|
||||
error = "CashuError"
|
||||
|
||||
|
||||
class Keyset(BaseModel):
|
||||
class KeyBase(BaseModel):
|
||||
id: str
|
||||
keys: Dict
|
||||
mint_url: Union[str, None] = None
|
||||
first_seen: Union[str, None] = None
|
||||
active: bool = True
|
||||
amount: int
|
||||
pubkey: str
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row):
|
||||
@@ -22,13 +24,55 @@ class Keyset(BaseModel):
|
||||
return cls
|
||||
return cls(
|
||||
id=row[0],
|
||||
keys=row[1],
|
||||
mint_url=row[2],
|
||||
first_seen=row[3],
|
||||
active=row[4],
|
||||
amount=int(row[1]),
|
||||
pubkey=row[2],
|
||||
)
|
||||
|
||||
|
||||
class Keyset:
|
||||
id: str
|
||||
private_keys: Dict[int, PrivateKey]
|
||||
public_keys: Dict[int, PublicKey]
|
||||
mint_url: Union[str, None] = None
|
||||
valid_from: Union[str, None] = None
|
||||
valid_to: Union[str, None] = None
|
||||
first_seen: Union[str, None] = None
|
||||
active: bool = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
seed: Union[None, str] = None,
|
||||
derivation_path: str = "0",
|
||||
pubkeys: Union[None, Dict[int, PublicKey]] = None,
|
||||
):
|
||||
if seed:
|
||||
self.private_keys = derive_keys(seed, derivation_path)
|
||||
self.public_keys = derive_pubkeys(self.private_keys)
|
||||
if pubkeys:
|
||||
self.public_keys = pubkeys
|
||||
self.id = derive_keyset_id(self.public_keys)
|
||||
logger.debug(f"Mint keyset id: {self.id}")
|
||||
|
||||
@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],
|
||||
)
|
||||
|
||||
def get_keybase(self):
|
||||
return {
|
||||
k: KeyBase(id=self.id, amount=k, pubkey=v.serialize().hex())
|
||||
for k, v in self.public_keys.items()
|
||||
}
|
||||
|
||||
|
||||
class P2SHScript(BaseModel):
|
||||
script: str
|
||||
signature: str
|
||||
@@ -65,6 +109,7 @@ class Proof(BaseModel):
|
||||
send_id=row[4] or "",
|
||||
time_created=row[5] or "",
|
||||
time_reserved=row[6] or "",
|
||||
id=row[7] or "",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -116,17 +161,20 @@ class Invoice(BaseModel):
|
||||
|
||||
|
||||
class BlindedMessage(BaseModel):
|
||||
id: str = ""
|
||||
amount: int
|
||||
B_: str
|
||||
|
||||
|
||||
class BlindedSignature(BaseModel):
|
||||
id: str = ""
|
||||
amount: int
|
||||
C_: str
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, d: dict):
|
||||
return cls(
|
||||
id=d["id"],
|
||||
amount=d["amount"],
|
||||
C_=d["C_"],
|
||||
)
|
||||
|
||||
74
cashu/core/crud.py
Normal file
74
cashu/core/crud.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from typing import Optional
|
||||
|
||||
from cashu.core.base import Keyset, KeyBase
|
||||
from cashu.core.db import Connection, Database
|
||||
|
||||
|
||||
async def store_keyset(
|
||||
keyset: Keyset,
|
||||
mint_url: str,
|
||||
db: Database,
|
||||
conn: Optional[Connection] = None,
|
||||
):
|
||||
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
INSERT INTO keysets
|
||||
(id, mint_url, valid_from, valid_to, first_seen, active)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
keyset.id,
|
||||
mint_url,
|
||||
keyset.valid_from,
|
||||
keyset.valid_to,
|
||||
keyset.first_seen,
|
||||
True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def get_keyset(
|
||||
id: str = None,
|
||||
mint_url: str = None,
|
||||
db: Database = None,
|
||||
conn: Optional[Connection] = None,
|
||||
):
|
||||
clauses = []
|
||||
values = []
|
||||
clauses.append("active = ?")
|
||||
values.append(True)
|
||||
if id:
|
||||
clauses.append("id = ?")
|
||||
values.append(id)
|
||||
if mint_url:
|
||||
clauses.append("mint_url = ?")
|
||||
values.append(mint_url)
|
||||
where = ""
|
||||
if clauses:
|
||||
where = f"WHERE {' AND '.join(clauses)}"
|
||||
|
||||
row = await (conn or db).fetchone(
|
||||
f"""
|
||||
SELECT * from keysets
|
||||
{where}
|
||||
""",
|
||||
tuple(values),
|
||||
)
|
||||
return Keyset.from_row(row)
|
||||
|
||||
|
||||
async def store_mint_pubkey(
|
||||
key: KeyBase,
|
||||
db: Database,
|
||||
conn: Optional[Connection] = None,
|
||||
):
|
||||
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
INSERT INTO mint_pubkeys
|
||||
(id, amount, pubkey)
|
||||
VALUES (?, ?, ?)
|
||||
""",
|
||||
(key.id, key.amount, key.pubkey),
|
||||
)
|
||||
31
cashu/core/crypto.py
Normal file
31
cashu/core/crypto.py
Normal file
@@ -0,0 +1,31 @@
|
||||
import hashlib
|
||||
from typing import Dict, List
|
||||
from cashu.core.secp import PrivateKey, PublicKey
|
||||
from cashu.core.settings import MAX_ORDER
|
||||
|
||||
|
||||
def derive_keys(master_key: str, derivation_path: str = ""):
|
||||
"""
|
||||
Deterministic derivation of keys for 2^n values.
|
||||
TODO: Implement BIP32.
|
||||
"""
|
||||
return {
|
||||
2
|
||||
** i: PrivateKey(
|
||||
hashlib.sha256((str(master_key) + derivation_path + str(i)).encode("utf-8"))
|
||||
.hexdigest()
|
||||
.encode("utf-8")[:32],
|
||||
raw=True,
|
||||
)
|
||||
for i in range(MAX_ORDER)
|
||||
}
|
||||
|
||||
|
||||
def derive_pubkeys(keys: Dict[int, PrivateKey]):
|
||||
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
|
||||
|
||||
|
||||
def derive_keyset_id(keys: Dict[str, PublicKey]):
|
||||
"""Deterministic derivation keyset_id from set of public keys."""
|
||||
pubkeys_concat = "".join([p.serialize().hex() for _, p in keys.items()])
|
||||
return hashlib.sha256((pubkeys_concat).encode("utf-8")).hexdigest()[:16]
|
||||
21
cashu/core/errors.py
Normal file
21
cashu/core/errors.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class CashuError(Exception, BaseModel):
|
||||
code = "000"
|
||||
error = "CashuError"
|
||||
|
||||
|
||||
class MintException(CashuError):
|
||||
code = 100
|
||||
error = "Mint"
|
||||
|
||||
|
||||
class LightningException(MintException):
|
||||
code = 200
|
||||
error = "Lightning"
|
||||
|
||||
|
||||
class InvoiceNotPaidException(LightningException):
|
||||
code = 201
|
||||
error = "invoice not paid."
|
||||
@@ -1,7 +1,5 @@
|
||||
import re
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from cashu.core.db import COCKROACH, POSTGRES, SQLITE, Database
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import secrets
|
||||
from typing import Optional
|
||||
|
||||
from cashu.core.base import Invoice, Proof
|
||||
|
||||
@@ -2,20 +2,17 @@
|
||||
Implementation of https://gist.github.com/phyro/935badc682057f418842c72961cf096c
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import math
|
||||
from inspect import signature
|
||||
from signal import signal
|
||||
from typing import List, Set
|
||||
|
||||
import cashu.core.b_dhke as b_dhke
|
||||
import cashu.core.bolt11 as bolt11
|
||||
from cashu.core.base import BlindedMessage, BlindedSignature, Invoice, Proof
|
||||
from cashu.core.crypto import derive_keyset_id
|
||||
from cashu.core.base import BlindedMessage, BlindedSignature, Invoice, Proof, Keyset
|
||||
from cashu.core.crypto import derive_keyset_id, derive_keys, derive_pubkeys
|
||||
from cashu.core.db import Database
|
||||
from cashu.core.helpers import fee_reserve
|
||||
from cashu.core.script import verify_script
|
||||
from cashu.core.secp import PrivateKey, PublicKey
|
||||
from cashu.core.secp import PublicKey
|
||||
from cashu.core.settings import LIGHTNING, MAX_ORDER
|
||||
from cashu.core.split import amount_split
|
||||
from cashu.lightning import WALLET
|
||||
@@ -32,34 +29,13 @@ from cashu.mint.crud import (
|
||||
class Ledger:
|
||||
def __init__(self, secret_key: str, db: str):
|
||||
self.proofs_used: Set[str] = set()
|
||||
|
||||
self.master_key = secret_key
|
||||
self.keys = self._derive_keys(self.master_key)
|
||||
self.keyset_id = derive_keyset_id(self.keys)
|
||||
self.pub_keys = self._derive_pubkeys(self.keys)
|
||||
self.keyset = Keyset(self.master_key)
|
||||
self.db: Database = Database("mint", db)
|
||||
|
||||
async def load_used_proofs(self):
|
||||
self.proofs_used = set(await get_proofs_used(db=self.db))
|
||||
|
||||
@staticmethod
|
||||
def _derive_keys(master_key: str, keyset_id: str = ""):
|
||||
"""Deterministic derivation of keys for 2^n values."""
|
||||
return {
|
||||
2
|
||||
** i: PrivateKey(
|
||||
hashlib.sha256((str(master_key) + str(i) + keyset_id).encode("utf-8"))
|
||||
.hexdigest()
|
||||
.encode("utf-8")[:32],
|
||||
raw=True,
|
||||
)
|
||||
for i in range(MAX_ORDER)
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _derive_pubkeys(keys: List[PrivateKey]):
|
||||
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
|
||||
|
||||
async def _generate_promises(self, amounts: List[int], B_s: List[str]):
|
||||
"""Generates promises that sum to the given amount."""
|
||||
return [
|
||||
@@ -69,7 +45,7 @@ class Ledger:
|
||||
|
||||
async def _generate_promise(self, amount: int, B_: PublicKey):
|
||||
"""Generates a promise for given amount and returns a pair (amount, C')."""
|
||||
secret_key = self.keys[amount] # Get the correct key
|
||||
secret_key = self.keyset.private_keys[amount] # Get the correct key
|
||||
C_ = b_dhke.step2_bob(B_, secret_key)
|
||||
await store_promise(
|
||||
amount, B_=B_.serialize().hex(), C_=C_.serialize().hex(), db=self.db
|
||||
@@ -94,7 +70,9 @@ class Ledger:
|
||||
"""Verifies that the proof of promise was issued by this ledger."""
|
||||
if not self._check_spendable(proof):
|
||||
raise Exception(f"tokens already spent. Secret: {proof.secret}")
|
||||
secret_key = self.keys[proof.amount] # Get the correct key to check against
|
||||
secret_key = self.keyset.private_keys[
|
||||
proof.amount
|
||||
] # Get the correct key to check against
|
||||
C = PublicKey(bytes.fromhex(proof.C), raw=True)
|
||||
return b_dhke.verify(secret_key, C, proof.secret)
|
||||
|
||||
@@ -125,7 +103,7 @@ class Ledger:
|
||||
assert len(proof.secret.split(":")) == 3, "secret format wrong."
|
||||
assert proof.secret.split(":")[1] == str(
|
||||
txin_p2sh_address
|
||||
), f"secret does not contain correct P2SH address: {proof.secret.split(':')[1]}!={txin_p2sh_address}."
|
||||
), f"secret does not contain correct P2SH address: {proof.secret.split(':')[1]} is not {txin_p2sh_address}."
|
||||
return valid
|
||||
|
||||
def _verify_outputs(self, total: int, amount: int, outputs: List[BlindedMessage]):
|
||||
@@ -223,13 +201,13 @@ class Ledger:
|
||||
for p in proofs:
|
||||
await invalidate_proof(p, db=self.db)
|
||||
|
||||
# Public methods
|
||||
def get_pubkeys(self):
|
||||
def _serialize_pubkeys(self):
|
||||
"""Returns public keys for possible amounts."""
|
||||
return {a: p.serialize().hex() for a, p in self.pub_keys.items()}
|
||||
return {a: p.serialize().hex() for a, p in self.keyset.public_keys.items()}
|
||||
|
||||
# Public methods
|
||||
def get_keyset(self):
|
||||
return {"id": self.keyset_id, "keys": self.get_pubkeys()}
|
||||
return {"id": self.keyset.id, "keys": self._serialize_pubkeys()}
|
||||
|
||||
async def request_mint(self, amount):
|
||||
"""Returns Lightning invoice and stores it in the db."""
|
||||
|
||||
@@ -85,3 +85,36 @@ async def m001_initial(db: Database):
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
async def m003_mint_keysets(db: Database):
|
||||
"""
|
||||
Stores mint keysets from different mints and epochs.
|
||||
"""
|
||||
await db.execute(
|
||||
f"""
|
||||
CREATE TABLE IF NOT EXISTS keysets (
|
||||
id TEXT NOT NULL,
|
||||
mint_url TEXT NOT NULL,
|
||||
valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
||||
valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
||||
first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
||||
active BOOL NOT NULL DEFAULT TRUE,
|
||||
|
||||
UNIQUE (id, mint_url)
|
||||
|
||||
);
|
||||
"""
|
||||
)
|
||||
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)
|
||||
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -4,13 +4,15 @@ from loguru import logger
|
||||
|
||||
from cashu.core.settings import CASHU_DIR, LIGHTNING
|
||||
from cashu.lightning import WALLET
|
||||
from cashu.mint.migrations import m001_initial
|
||||
from cashu.mint import migrations
|
||||
from cashu.core.migrations import migrate_databases
|
||||
|
||||
from . import ledger
|
||||
|
||||
|
||||
async def load_ledger():
|
||||
await asyncio.wait([m001_initial(ledger.db)])
|
||||
await migrate_databases(ledger.db, migrations)
|
||||
# await asyncio.wait([m001_initial(ledger.db)])
|
||||
await ledger.load_used_proofs()
|
||||
|
||||
if LIGHTNING:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import time
|
||||
from typing import Any, List, Optional, Dict
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from cashu.core.base import P2SHScript, Proof, Keyset
|
||||
from cashu.core.base import P2SHScript, Proof
|
||||
from cashu.core.db import Connection, Database
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@ async def store_proof(
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
INSERT INTO proofs
|
||||
(amount, C, secret, time_created)
|
||||
VALUES (?, ?, ?, ?)
|
||||
(id, amount, C, secret, time_created)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(proof.amount, str(proof.C), str(proof.secret), int(time.time())),
|
||||
(proof.id, proof.amount, str(proof.C), str(proof.secret), int(time.time())),
|
||||
)
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ async def invalidate_proof(
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
INSERT INTO proofs_used
|
||||
(amount, C, secret, time_used)
|
||||
VALUES (?, ?, ?, ?)
|
||||
(amount, C, secret, time_used, id)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(proof.amount, str(proof.C), str(proof.secret), int(time.time())),
|
||||
(proof.amount, str(proof.C), str(proof.secret), int(time.time()), proof.id),
|
||||
)
|
||||
|
||||
|
||||
@@ -180,50 +180,3 @@ async def update_p2sh_used(
|
||||
f"UPDATE proofs SET {', '.join(clauses)} WHERE address = ?",
|
||||
(*values, str(p2sh.address)),
|
||||
)
|
||||
|
||||
|
||||
async def store_keyset(
|
||||
keyset: Keyset,
|
||||
mint_url: str,
|
||||
db: Database,
|
||||
conn: Optional[Connection] = None,
|
||||
):
|
||||
|
||||
await (conn or db).execute(
|
||||
"""
|
||||
INSERT INTO keysets
|
||||
(id, mint_url, keys, first_seen, active)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(keyset.id, mint_url, keyset.keys, keyset.first_seen, True),
|
||||
)
|
||||
|
||||
|
||||
async def get_keyset(
|
||||
id: str = None,
|
||||
mint_url: str = None,
|
||||
db: Database = None,
|
||||
conn: Optional[Connection] = None,
|
||||
):
|
||||
clauses = []
|
||||
values = []
|
||||
clauses.append("active = ?")
|
||||
values.append(True)
|
||||
if id:
|
||||
clauses.append("id = ?")
|
||||
values.append(id)
|
||||
if mint_url:
|
||||
clauses.append("mint_url = ?")
|
||||
values.append(mint_url)
|
||||
where = ""
|
||||
if clauses:
|
||||
where = f"WHERE {' AND '.join(clauses)}"
|
||||
|
||||
row = await (conn or db).fetchone(
|
||||
f"""
|
||||
SELECT * from keysets
|
||||
{where}
|
||||
""",
|
||||
tuple(values),
|
||||
)
|
||||
return Keyset.from_row(row)
|
||||
|
||||
@@ -108,8 +108,9 @@ async def m005_mint_keysets(db: Database):
|
||||
f"""
|
||||
CREATE TABLE IF NOT EXISTS keysets (
|
||||
id TEXT NOT NULL,
|
||||
keys TEXT NOT NULL,
|
||||
mint_url TEXT NOT NULL,
|
||||
valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
||||
valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
||||
first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
||||
active BOOL NOT NULL DEFAULT TRUE,
|
||||
|
||||
@@ -118,3 +119,18 @@ async def m005_mint_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")
|
||||
|
||||
@@ -37,9 +37,8 @@ from cashu.wallet.crud import (
|
||||
store_p2sh,
|
||||
store_proof,
|
||||
update_proof_reserved,
|
||||
store_keyset,
|
||||
get_keyset,
|
||||
)
|
||||
from cashu.core.crud import store_keyset, get_keyset
|
||||
|
||||
|
||||
class LedgerAPI:
|
||||
@@ -49,37 +48,18 @@ class LedgerAPI:
|
||||
def __init__(self, url):
|
||||
self.url = url
|
||||
|
||||
<<<<<<< HEAD
|
||||
def _get_keys(self, url):
|
||||
resp = requests.get(url + "/keys").json()
|
||||
keyset_id = resp["id"]
|
||||
keys = resp["keys"]
|
||||
# return {
|
||||
# "id": keyset_id,
|
||||
# "keys": {
|
||||
# int(amt): PublicKey(bytes.fromhex(val), raw=True)
|
||||
# for amt, val in keys.items()
|
||||
# },
|
||||
# }
|
||||
keyset_keys = (
|
||||
{
|
||||
assert len(keys), Exception("did not receive any keys")
|
||||
keyset_keys = {
|
||||
int(amt): PublicKey(bytes.fromhex(val), raw=True)
|
||||
for amt, val in keys.items()
|
||||
},
|
||||
)
|
||||
print(resp)
|
||||
return Keyset(id=keyset_id, keys=keyset_keys, mint_url=self.url)
|
||||
=======
|
||||
@staticmethod
|
||||
def _get_keys(url):
|
||||
resp = requests.get(url + "/keys")
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return {
|
||||
int(amt): PublicKey(bytes.fromhex(val), raw=True)
|
||||
for amt, val in data.items()
|
||||
}
|
||||
>>>>>>> main
|
||||
keyset = Keyset(pubkeys=keyset_keys)
|
||||
assert keyset_id == keyset.id, Exception("mint keyset id not valid.")
|
||||
return keyset
|
||||
|
||||
@staticmethod
|
||||
def _get_output_split(amount):
|
||||
@@ -121,7 +101,7 @@ class LedgerAPI:
|
||||
keyset_local: Keyset = await get_keyset(keyset.id, self.url, db=self.db)
|
||||
if keyset_local is None:
|
||||
await store_keyset(keyset=keyset, db=self.db)
|
||||
self.keys = keyset.keys
|
||||
self.keys = keyset.public_keys
|
||||
self.keyset_id = keyset.id
|
||||
assert len(self.keys) > 0, "did not receive keys from mint."
|
||||
|
||||
@@ -215,9 +195,19 @@ class LedgerAPI:
|
||||
await self._check_used_secrets(secrets)
|
||||
payloads, rs = self._construct_outputs(amounts, secrets)
|
||||
split_payload = SplitRequest(proofs=proofs, amount=amount, outputs=payloads)
|
||||
|
||||
def _splitrequest_include_fields(proofs):
|
||||
"""strips away fields from the model that aren't necessary for the /split"""
|
||||
proofs_include = {"id", "amount", "secret", "C", "script"}
|
||||
return {
|
||||
"amount": ...,
|
||||
"outputs": ...,
|
||||
"proofs": {i: proofs_include for i in range(len(proofs))},
|
||||
}
|
||||
|
||||
resp = requests.post(
|
||||
self.url + "/split",
|
||||
json=split_payload.dict(),
|
||||
json=split_payload.dict(include=_splitrequest_include_fields(proofs)),
|
||||
)
|
||||
resp.raise_for_status()
|
||||
try:
|
||||
@@ -263,9 +253,19 @@ class LedgerAPI:
|
||||
|
||||
async def pay_lightning(self, proofs: List[Proof], invoice: str):
|
||||
payload = MeltRequest(proofs=proofs, invoice=invoice)
|
||||
|
||||
def _meltequest_include_fields(proofs):
|
||||
"""strips away fields from the model that aren't necessary for the /melt"""
|
||||
proofs_include = {"id", "amount", "secret", "C", "script"}
|
||||
return {
|
||||
"amount": ...,
|
||||
"invoice": ...,
|
||||
"proofs": {i: proofs_include for i in range(len(proofs))},
|
||||
}
|
||||
|
||||
resp = requests.post(
|
||||
self.url + "/melt",
|
||||
json=payload.dict(),
|
||||
json=payload.dict(include=_meltequest_include_fields(proofs)),
|
||||
)
|
||||
resp.raise_for_status()
|
||||
|
||||
@@ -357,14 +357,22 @@ class Wallet(LedgerAPI):
|
||||
).decode()
|
||||
return token
|
||||
|
||||
async def _get_spendable_proofs(self, proofs: List[Proof]):
|
||||
print(f"Debug: only loading proofs with id: {self.keyset_id}")
|
||||
proofs = [p for p in proofs if p.id == self.keyset_id or not p.id]
|
||||
proofs = [p for p in proofs if not p.reserved]
|
||||
return proofs
|
||||
|
||||
async def split_to_send(self, proofs: List[Proof], amount, scnd_secret: str = None):
|
||||
"""Like self.split but only considers non-reserved tokens."""
|
||||
if scnd_secret:
|
||||
logger.debug(f"Spending conditions: {scnd_secret}")
|
||||
if len([p for p in proofs if not p.reserved]) <= 0:
|
||||
spendable_proofs = await self._get_spendable_proofs(proofs)
|
||||
print(f"Balance: {sum([p.amount for p in spendable_proofs])}")
|
||||
if sum([p.amount for p in spendable_proofs]) < amount:
|
||||
raise Exception("balance too low.")
|
||||
return await self.split(
|
||||
[p for p in proofs if not p.reserved], amount, scnd_secret
|
||||
[p for p in spendable_proofs if not p.reserved], amount, scnd_secret
|
||||
)
|
||||
|
||||
async def set_reserved(self, proofs: List[Proof], reserved: bool):
|
||||
|
||||
Reference in New Issue
Block a user