diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
index cf2776648..bd7d5513b 100644
--- a/lnbits/extensions/cashu/__init__.py
+++ b/lnbits/extensions/cashu/__init__.py
@@ -10,13 +10,16 @@ db = Database("ext_cashu")
cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
+
def cashu_renderer():
return template_renderer(["lnbits/extensions/cashu/templates"])
+
from .tasks import wait_for_paid_invoices
from .views import * # noqa
from .views_api import * # noqa
+
def cashu_start():
loop = asyncio.get_event_loop()
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
index c991a8ecc..448614ac4 100644
--- a/lnbits/extensions/cashu/crud.py
+++ b/lnbits/extensions/cashu/crud.py
@@ -1,22 +1,18 @@
import os
-
+import random
+from binascii import hexlify, unhexlify
from typing import List, Optional, Union
-from .core.base import Invoice
+
+from embit import bip32, bip39, ec, script
+from embit.networks import NETWORKS
+from loguru import logger
from lnbits.helpers import urlsafe_short_hash
from . import db
-from .models import Cashu, Pegs, Proof, Promises
+from .core.base import Invoice
+from .models import Cashu, Pegs, Promises, Proof
-from embit import script
-from embit import ec
-from embit.networks import NETWORKS
-from embit import bip32
-from embit import bip39
-from binascii import unhexlify, hexlify
-import random
-
-from loguru import logger
async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
cashu_id = urlsafe_short_hash()
@@ -25,7 +21,7 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
mnemonic = bip39.mnemonic_from_bytes(entropy)
seed = bip39.mnemonic_to_seed(mnemonic)
root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
-
+
bip44_xprv = root.derive("m/44h/1h/0h")
bip44_xpub = bip44_xprv.to_public()
@@ -43,7 +39,7 @@ async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
data.maxsats,
data.coins,
bip44_xprv.to_base58(),
- bip44_xpub.to_base58()
+ bip44_xpub.to_base58(),
),
)
@@ -57,11 +53,16 @@ async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
mnemonic = bip39.mnemonic_from_bytes(entropy)
seed = bip39.mnemonic_to_seed(mnemonic)
root = bip32.HDKey.from_seed(seed, version=NETWORKS["main"]["xprv"])
-
+
bip44_xprv = root.derive("m/44h/1h/0h")
bip44_xpub = bip44_xprv.to_public()
- await db.execute("UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?", bip44_xprv.to_base58(), bip44_xpub.to_base58(), cashu_id)
+ await db.execute(
+ "UPDATE cashu.cashu SET prv = ?, pub = ? WHERE id = ?",
+ bip44_xprv.to_base58(),
+ bip44_xpub.to_base58(),
+ cashu_id,
+ )
row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
return Cashu(**row) if row else None
@@ -91,12 +92,8 @@ async def delete_cashu(cashu_id) -> None:
###############MINT STUFF#################
##########################################
-async def store_promise(
- amount: int,
- B_: str,
- C_: str,
- cashu_id
-):
+
+async def store_promise(amount: int, B_: str, C_: str, cashu_id):
promise_id = urlsafe_short_hash()
await db.execute(
@@ -105,28 +102,25 @@ async def store_promise(
(id, amount, B_b, C_b, cashu_id)
VALUES (?, ?, ?, ?, ?)
""",
- (
- promise_id,
- amount,
- str(B_),
- str(C_),
- cashu_id
- ),
+ (promise_id, amount, str(B_), str(C_), cashu_id),
)
+
async def get_promises(cashu_id) -> Optional[Cashu]:
- row = await db.fetchall("SELECT * FROM cashu.promises WHERE cashu_id = ?", (promises_id,))
+ row = await db.fetchall(
+ "SELECT * FROM cashu.promises WHERE cashu_id = ?", (promises_id,)
+ )
return Promises(**row) if row else None
+
async def get_proofs_used(cashu_id):
- rows = await db.fetchall("SELECT secret from cashu.proofs_used WHERE id = ?", (cashu_id,))
+ rows = await db.fetchall(
+ "SELECT secret from cashu.proofs_used WHERE id = ?", (cashu_id,)
+ )
return [row[0] for row in rows]
-async def invalidate_proof(
- proof: Proof,
- cashu_id
-):
+async def invalidate_proof(proof: Proof, cashu_id):
invalidate_proof_id = urlsafe_short_hash()
await (conn or db).execute(
"""
@@ -134,20 +128,10 @@ async def invalidate_proof(
(id, amount, C, secret, cashu_id)
VALUES (?, ?, ?, ?, ?)
""",
- (
- invalidate_proof_id,
- proof.amount,
- str(proof.C),
- str(proof.secret),
- cashu_id
- ),
+ (invalidate_proof_id, proof.amount, str(proof.C), str(proof.secret), cashu_id),
)
-
-
-
-
########################################
############ MINT INVOICES #############
########################################
@@ -169,18 +153,22 @@ async def store_lightning_invoice(cashu_id: str, invoice: Invoice):
),
)
+
async def get_lightning_invoice(cashu_id: str, hash: str):
row = await db.fetchone(
"""
SELECT * from invoices
WHERE cashu_id =? AND hash = ?
""",
- (cashu_id, hash,),
+ (
+ cashu_id,
+ hash,
+ ),
)
return Invoice.from_row(row)
-async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
+async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
await db.execute(
"UPDATE invoices SET issued = ? WHERE cashu_id = ? AND hash = ?",
(
diff --git a/lnbits/extensions/cashu/ledger.py b/lnbits/extensions/cashu/ledger.py
index 404f7ee8a..a28dc97aa 100644
--- a/lnbits/extensions/cashu/ledger.py
+++ b/lnbits/extensions/cashu/ledger.py
@@ -1,13 +1,15 @@
import hashlib
from typing import List, Set
-from .models import BlindedMessage, BlindedSignature, Invoice, Proof
-from secp256k1 import PublicKey, PrivateKey
-
from fastapi import Query
-from .crud import get_cashu
+from secp256k1 import PrivateKey, PublicKey
+
from lnbits.core.services import check_transaction_status, create_invoice
+from .crud import get_cashu
+from .models import BlindedMessage, BlindedSignature, Invoice, Proof
+
+
def _derive_keys(master_key: str, cashu_id: str = Query(None)):
"""Deterministic derivation of keys for 2^n values."""
return {
@@ -21,29 +23,34 @@ def _derive_keys(master_key: str, cashu_id: str = Query(None)):
for i in range(MAX_ORDER)
}
+
def _derive_pubkeys(keys: List[PrivateKey], cashu_id: str = Query(None)):
return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
-async def _generate_promises(amounts: List[int], B_s: List[str], cashu_id: str = Query(None)):
+
+async def _generate_promises(
+ amounts: List[int], B_s: List[str], cashu_id: str = Query(None)
+):
"""Generates promises that sum to the given amount."""
return [
await self._generate_promise(amount, PublicKey(bytes.fromhex(B_), raw=True))
for (amount, B_) in zip(amounts, B_s)
]
+
async def _generate_promise(amount: int, B_: PublicKey, cashu_id: str = Query(None)):
"""Generates a promise for given amount and returns a pair (amount, C')."""
secret_key = self.keys[amount] # Get the correct key
C_ = step2_bob(B_, secret_key)
- await store_promise(
- amount, B_=B_.serialize().hex(), C_=C_.serialize().hex()
- )
+ await store_promise(amount, B_=B_.serialize().hex(), C_=C_.serialize().hex())
return BlindedSignature(amount=amount, C_=C_.serialize().hex())
+
def _check_spendable(proof: Proof, cashu_id: str = Query(None)):
"""Checks whether the proof was already spent."""
return not proof.secret in self.proofs_used
+
def _verify_proof(proof: Proof, cashu_id: str = Query(None)):
"""Verifies that the proof of promise was issued by this ledger."""
if not self._check_spendable(proof):
@@ -52,7 +59,13 @@ def _verify_proof(proof: Proof, cashu_id: str = Query(None)):
C = PublicKey(bytes.fromhex(proof.C), raw=True)
return verify(secret_key, C, proof.secret)
-def _verify_outputs(total: int, amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)):
+
+def _verify_outputs(
+ total: int,
+ amount: int,
+ output_data: List[BlindedMessage],
+ cashu_id: str = Query(None),
+):
"""Verifies the expected split was correctly computed"""
fst_amt, snd_amt = total - amount, amount # we have two amounts to split to
fst_outputs = amount_split(fst_amt)
@@ -61,7 +74,10 @@ def _verify_outputs(total: int, amount: int, output_data: List[BlindedMessage],
given = [o.amount for o in output_data]
return given == expected
-def _verify_no_duplicates(proofs: List[Proof], output_data: List[BlindedMessage], cashu_id: str = Query(None)):
+
+def _verify_no_duplicates(
+ proofs: List[Proof], output_data: List[BlindedMessage], cashu_id: str = Query(None)
+):
secrets = [p.secret for p in proofs]
if len(secrets) != len(list(set(secrets))):
return False
@@ -70,6 +86,7 @@ def _verify_no_duplicates(proofs: List[Proof], output_data: List[BlindedMessage]
return False
return True
+
def _verify_split_amount(amount: int, cashu_id: str = Query(None)):
"""Split amount like output amount can't be negative or too big."""
try:
@@ -78,6 +95,7 @@ def _verify_split_amount(amount: int, cashu_id: str = Query(None)):
# For better error message
raise Exception("invalid split amount: " + str(amount))
+
def _verify_amount(amount: int, cashu_id: str = Query(None)):
"""Any amount used should be a positive integer not larger than 2^MAX_ORDER."""
valid = isinstance(amount, int) and amount > 0 and amount < 2**MAX_ORDER
@@ -85,12 +103,16 @@ def _verify_amount(amount: int, cashu_id: str = Query(None)):
raise Exception("invalid amount: " + str(amount))
return amount
-def _verify_equation_balanced(proofs: List[Proof], outs: List[BlindedMessage], cashu_id: str = Query(None)):
+
+def _verify_equation_balanced(
+ proofs: List[Proof], outs: List[BlindedMessage], cashu_id: str = Query(None)
+):
"""Verify that Σoutputs - Σinputs = 0."""
sum_inputs = sum(self._verify_amount(p.amount) for p in proofs)
sum_outputs = sum(self._verify_amount(p.amount) for p in outs)
assert sum_outputs - sum_inputs == 0
+
def _get_output_split(amount: int, cashu_id: str):
"""Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
self._verify_amount(amount)
@@ -101,6 +123,7 @@ def _get_output_split(amount: int, cashu_id: str):
rv.append(2**pos)
return rv
+
async def _invalidate_proofs(proofs: List[Proof], cashu_id: str = Query(None)):
"""Adds secrets of proofs to the list of knwon secrets and stores them in the db."""
# Mark proofs as used and prepare new promises
@@ -110,10 +133,12 @@ async def _invalidate_proofs(proofs: List[Proof], cashu_id: str = Query(None)):
for p in proofs:
await invalidate_proof(p)
+
def get_pubkeys(cashu_id: str = Query(None)):
"""Returns public keys for possible amounts."""
return {a: p.serialize().hex() for a, p in self.pub_keys.items()}
+
async def request_mint(amount, cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
@@ -125,9 +150,7 @@ async def request_mint(amount, cashu_id: str = Query(None)):
amount=amount,
memo=cashu.name,
unhashed_description=cashu.name.encode("utf-8"),
- extra={
- "tag": "Cashu"
- },
+ extra={"tag": "Cashu"},
)
invoice = Invoice(
@@ -137,15 +160,23 @@ async def request_mint(amount, cashu_id: str = Query(None)):
raise Exception(f"Could not create Lightning invoice.")
return payment_request, payment_hash
-async def mint(B_s: List[PublicKey], amounts: List[int], payment_hash: str = Query(None), cashu_id: str = Query(None)):
+
+async def mint(
+ B_s: List[PublicKey],
+ amounts: List[int],
+ payment_hash: str = Query(None),
+ cashu_id: str = Query(None),
+):
cashu = await get_cashu(cashu_id)
if not cashu:
raise Exception(f"Could not find Cashu")
"""Mints a promise for coins for B_."""
# check if lightning invoice was paid
- if payment_hash:
- if not await check_transaction_status(wallet_id=cashu.wallet, payment_hash=payment_hash):
+ if payment_hash:
+ if not await check_transaction_status(
+ wallet_id=cashu.wallet, payment_hash=payment_hash
+ ):
raise Exception("Lightning invoice not paid yet.")
for amount in amounts:
@@ -157,11 +188,14 @@ async def mint(B_s: List[PublicKey], amounts: List[int], payment_hash: str = Que
]
return promises
-async def melt(proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Query(None)):
+
+async def melt(
+ proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Query(None)
+):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
-
+ raise Exception(f"Could not find Cashu")
+
"""Invalidates proofs and pays a Lightning invoice."""
# if not LIGHTNING:
total = sum([p["amount"] for p in proofs])
@@ -181,6 +215,7 @@ async def melt(proofs: List[Proof], amount: int, invoice: str, cashu_id: str = Q
await self._invalidate_proofs(proofs)
return status, payment_hash
+
async def check_spendable(proofs: List[Proof], cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
@@ -189,7 +224,13 @@ async def check_spendable(proofs: List[Proof], cashu_id: str = Query(None)):
"""Checks if all provided proofs are valid and still spendable (i.e. have not been spent)."""
return {i: self._check_spendable(p) for i, p in enumerate(proofs)}
-async def split(proofs: List[Proof], amount: int, output_data: List[BlindedMessage], cashu_id: str = Query(None)):
+
+async def split(
+ proofs: List[Proof],
+ amount: int,
+ output_data: List[BlindedMessage],
+ cashu_id: str = Query(None),
+):
cashu = await get_cashu(cashu_id)
if not cashu:
raise Exception(f"Could not find Cashu")
@@ -226,18 +267,19 @@ async def split(proofs: List[Proof], amount: int, output_data: List[BlindedMessa
async def fee_reserve(amount_msat: int, cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
-
+ raise Exception(f"Could not find Cashu")
+
"""Function for calculating the Lightning fee reserve"""
return max(
int(LIGHTNING_RESERVE_FEE_MIN), int(amount_msat * LIGHTNING_FEE_PERCENT / 100.0)
)
+
async def amount_split(amount, cashu_id: str):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
-
+ raise Exception(f"Could not find Cashu")
+
"""Given an amount returns a list of amounts returned e.g. 13 is [1, 4, 8]."""
bits_amt = bin(amount)[::-1][:-2]
rv = []
@@ -246,11 +288,12 @@ async def amount_split(amount, cashu_id: str):
rv.append(2**pos)
return rv
+
async def hash_to_point(secret_msg, cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
-
+ raise Exception(f"Could not find Cashu")
+
"""Generates x coordinate from the message hash and checks if the point lies on the curve.
If it does not, it tries computing again a new x coordinate from the hash of the coordinate."""
point = None
@@ -280,10 +323,11 @@ async def step1_alice(secret_msg, cashu_id: str = Query(None)):
B_ = Y + r.pubkey
return B_, r
+
async def step2_bob(B_, a, cashu_id: str = Query(None)):
cashu = await get_cashu(cashu_id)
if not cashu:
- raise Exception(f"Could not find Cashu")
+ raise Exception(f"Could not find Cashu")
C_ = B_.mult(a)
return C_
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
index 3f1df660a..cb6b24f96 100644
--- a/lnbits/extensions/cashu/migrations.py
+++ b/lnbits/extensions/cashu/migrations.py
@@ -75,4 +75,4 @@ async def m001_initial(db):
);
"""
- )
\ No newline at end of file
+ )
diff --git a/lnbits/extensions/cashu/mint.py b/lnbits/extensions/cashu/mint.py
index fca096ed7..c25683137 100644
--- a/lnbits/extensions/cashu/mint.py
+++ b/lnbits/extensions/cashu/mint.py
@@ -1,16 +1,16 @@
-
-from .models import Cashu
from .mint_helper import derive_keys, derive_pubkeys
+from .models import Cashu
def get_pubkeys(xpriv: str):
"""Returns public keys for possible amounts."""
-
+
keys = derive_keys(xpriv)
pub_keys = derive_pubkeys(keys)
return {a: p.serialize().hex() for a, p in pub_keys.items()}
+
# async def mint(self, B_s: List[PublicKey], amounts: List[int], payment_hash=None):
# """Mints a promise for coins for B_."""
# # check if lightning invoice was paid
@@ -29,4 +29,4 @@ def get_pubkeys(xpriv: str):
# promises = [
# await self._generate_promise(amount, B_) for B_, amount in zip(B_s, amounts)
# ]
-# return promises
\ No newline at end of file
+# return promises
diff --git a/lnbits/extensions/cashu/mint_helper.py b/lnbits/extensions/cashu/mint_helper.py
index 30e66b033..1cf631b4a 100644
--- a/lnbits/extensions/cashu/mint_helper.py
+++ b/lnbits/extensions/cashu/mint_helper.py
@@ -1,10 +1,12 @@
import hashlib
from typing import List, Set
+
from .core.secp import PrivateKey, PublicKey
# todo: extract const
MAX_ORDER = 64
+
def derive_keys(master_key: str):
"""Deterministic derivation of keys for 2^n values."""
return {
@@ -18,5 +20,6 @@ def derive_keys(master_key: str):
for i in range(MAX_ORDER)
}
+
def derive_pubkeys(keys: List[PrivateKey]):
- return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
\ No newline at end of file
+ return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]}
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
index 094966ff5..570387a28 100644
--- a/lnbits/extensions/cashu/models.py
+++ b/lnbits/extensions/cashu/models.py
@@ -1,5 +1,5 @@
from sqlite3 import Row
-from typing import Optional, List
+from typing import List, Optional
from fastapi import Query
from pydantic import BaseModel
@@ -20,20 +20,22 @@ class Cashu(BaseModel):
def from_row(cls, row: Row) -> "TPoS":
return cls(**dict(row))
+
class Pegs(BaseModel):
id: str
wallet: str
inout: str
amount: str
-
@classmethod
def from_row(cls, row: Row) -> "TPoS":
return cls(**dict(row))
+
class PayLnurlWData(BaseModel):
lnurl: str
+
class Promises(BaseModel):
id: str
amount: int
@@ -41,6 +43,7 @@ class Promises(BaseModel):
C_b: str
cashu_id: str
+
class Proof(BaseModel):
amount: int
secret: str
@@ -142,4 +145,4 @@ class CheckPayload(BaseModel):
class MeltPayload(BaseModel):
proofs: List[Proof]
amount: int
- invoice: str
\ No newline at end of file
+ invoice: str
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
index 5fbdde8ed..fe00a5918 100644
--- a/lnbits/extensions/cashu/tasks.py
+++ b/lnbits/extensions/cashu/tasks.py
@@ -9,6 +9,7 @@ from lnbits.tasks import internal_invoice_queue, register_invoice_listener
from .crud import get_cashu
+
async def wait_for_paid_invoices():
invoice_queue = asyncio.Queue()
register_invoice_listener(invoice_queue)
diff --git a/lnbits/extensions/cashu/templates/cashu/_api_docs.html b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
index 7378eb084..3476d41aa 100644
--- a/lnbits/extensions/cashu/templates/cashu/_api_docs.html
+++ b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
@@ -71,7 +71,8 @@
Curl example
curl -X DELETE {{ request.base_url
- }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key: <admin_key>"
+ }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key:
+ <admin_key>"
diff --git a/lnbits/extensions/cashu/templates/cashu/_cashu.html b/lnbits/extensions/cashu/templates/cashu/_cashu.html
index 3c2a38f53..f5af738f1 100644
--- a/lnbits/extensions/cashu/templates/cashu/_cashu.html
+++ b/lnbits/extensions/cashu/templates/cashu/_cashu.html
@@ -2,13 +2,12 @@
- Make Ecash mints with peg in/out to a wallet, that can create and manage ecash.
+ Make Ecash mints with peg in/out to a wallet, that can create and manage
+ ecash.
Created by
- Calle.Calle.
diff --git a/lnbits/extensions/cashu/templates/cashu/index.html b/lnbits/extensions/cashu/templates/cashu/index.html
index 3cd57d45d..37dc360ef 100644
--- a/lnbits/extensions/cashu/templates/cashu/index.html
+++ b/lnbits/extensions/cashu/templates/cashu/index.html
@@ -4,7 +4,9 @@
- New Mint
+ New Mint
@@ -18,8 +20,14 @@
Export to CSV
-
+
{% raw %}
@@ -34,23 +42,43 @@
- Shareable wallet page
+ target="_blank"
+ >Shareable wallet page
- Shareable mint page
-
+ target="_blank"
+ >Shareable mint page
{{ (col.name == 'tip_options' && col.value ?
JSON.parse(col.value).join(", ") : col.value) }}
-
+
@@ -79,34 +107,90 @@
-
-
-
+
+
+
-
- Use with hedging extension to create a stablecoin!
+
+ Use with hedging extension to create a stablecoin!
-
-
+
+
-
-
+
+
- Create Mint
+ Create Mint
- Cancel
+ Cancel
@@ -130,12 +214,12 @@
data: function () {
return {
cashus: [],
- hostname: location.protocol + "//" + location.host + "/cashu/mint/",
+ hostname: location.protocol + '//' + location.host + '/cashu/mint/',
toggleAdvanced: false,
cashusTable: {
columns: [
- { name: 'id', align: 'left', label: 'ID', field: 'id' },
- { name: 'name', align: 'left', label: 'Name', field: 'name' },
+ {name: 'id', align: 'left', label: 'ID', field: 'id'},
+ {name: 'name', align: 'left', label: 'Name', field: 'name'},
{
name: 'tickershort',
align: 'left',
@@ -165,7 +249,7 @@
align: 'left',
label: 'No. of coins',
field: 'coins'
- },
+ }
],
pagination: {
rowsPerPage: 10
@@ -173,7 +257,7 @@
},
formDialog: {
show: false,
- data: { fraction: false }
+ data: {fraction: false}
}
}
},
@@ -203,7 +287,7 @@
var data = {
name: this.formDialog.data.name,
tickershort: this.formDialog.data.tickershort,
- maxliquid: this.formDialog.data.maxliquid,
+ maxliquid: this.formDialog.data.maxliquid
}
var self = this
@@ -211,7 +295,7 @@
.request(
'POST',
'/cashu/api/v1/cashus',
- _.findWhere(this.g.user.wallets, { id: this.formDialog.data.wallet })
+ _.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet})
.inkey,
data
)
@@ -225,17 +309,19 @@
},
deleteMint: function (cashuId) {
var self = this
- var cashu = _.findWhere(this.cashus, { id: cashuId })
+ var cashu = _.findWhere(this.cashus, {id: cashuId})
console.log(cashu)
LNbits.utils
- .confirmDialog('Are you sure you want to delete this Mint? It will suck for users.')
+ .confirmDialog(
+ 'Are you sure you want to delete this Mint? It will suck for users.'
+ )
.onOk(function () {
LNbits.api
.request(
'DELETE',
'/cashu/api/v1/cashus/' + cashuId,
- _.findWhere(self.g.user.wallets, { id: cashu.wallet }).adminkey
+ _.findWhere(self.g.user.wallets, {id: cashu.wallet}).adminkey
)
.then(function (response) {
self.cashus = _.reject(self.cashus, function (obj) {
@@ -258,4 +344,4 @@
}
})
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/lnbits/extensions/cashu/templates/cashu/mint.html b/lnbits/extensions/cashu/templates/cashu/mint.html
index 0f3e0e09a..3c0998b52 100644
--- a/lnbits/extensions/cashu/templates/cashu/mint.html
+++ b/lnbits/extensions/cashu/templates/cashu/mint.html
@@ -5,14 +5,17 @@
+ name="account_balance"
+ class="text-grey"
+ style="font-size: 10rem"
+ >
{{ mint_name }}
- Some data about mint here:
* whether its online
* Who to contact for support
* etc...
+
+ Some data about mint here:
* whether its online
* Who to
+ contact for support
* etc...
+
diff --git a/lnbits/extensions/cashu/templates/cashu/wallet.html b/lnbits/extensions/cashu/templates/cashu/wallet.html
index a5d5f371e..75dff22ef 100644
--- a/lnbits/extensions/cashu/templates/cashu/wallet.html
+++ b/lnbits/extensions/cashu/templates/cashu/wallet.html
@@ -1,41 +1,69 @@
-{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Wallet {% endraw %}
-
-{% endblock %} {% block footer %}{% endblock %} {% block page_container %}
+{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Wallet
+{% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
+page_container %}
-
-
-
+
- {% raw %} {{balanceAmount}}
- {{tickershort}}{% endraw %}
+
+ {% raw %} {{balanceAmount}} {{tickershort}}{%
+ endraw %}
+
-
-
-
+
- Receive
+ Receive
- Send
+ Send
- Peg in/out
+ Peg in/out
- scan
+ scan
-
-
-
+
@@ -43,42 +71,99 @@
Transactions
- {% raw %}
-
{% endraw %}
+
+ {% raw %}
+ {% endraw %}
Mint details
- Export to CSV
+ Export to CSV
-
+
Show chart
-
+
-
+
{% raw %}
- {{ col.label }}
+ {{ col.label }}
-
-
+
+
Pending
-
-
-
+
+
+
#{{ props.row.tag }}
@@ -89,7 +174,12 @@
{{ props.row.dateFrom }}
{% endraw %}
- {% raw %} {{
+ {% raw %} {{
parseFloat(String(props.row.fsat).replaceAll(",", "")) / 100
}}
@@ -108,34 +198,68 @@
Invoice waiting to be paid
-
-
+
+
- Copy invoice
- Close
+ Copy invoice
+ Close
-
+
Payment Received
-
+
-
+
Payment Sent
-
+
Outgoing payment pending
-
+
@@ -149,49 +273,101 @@
{% raw %}
-
+
{{receive.lnurl.domain}} is requesting an invoice:
{% endraw %} {% if LNBITS_DENOMINATION != 'sats' %}
-
+
{% else %}
-
-
+
+
{% endif %}
-
+
{% raw %}
-
+
Withdraw from {{receive.lnurl.domain}}
Create invoice
- Cancel
+ Cancel
-
+
- Copy invoice
- Close
+ Copy invoice
+ Close
{% endraw %}
@@ -217,11 +393,17 @@
{% endraw %}
Pay
- Cancel
+ Cancel
- Not enough funds!
- Cancel
+ Not enough funds!
+ Cancel
@@ -243,23 +425,46 @@
Login
- Cancel
+ Cancel
{% endraw %}
-
-
+
+
- Read
- Cancel
+ Read
+ Cancel
-
+
@@ -274,10 +479,15 @@
-
+
- Cancel
+ Cancel
@@ -289,12 +499,19 @@
-
+
-
+
@@ -302,8 +519,10 @@
Warning
- BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots
- and "Save to homescreen"/"Install app"!
+ BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots
+ and "Save to homescreen"/"Install app"!
Ecash is a bearer asset, meaning you have the funds saved on this
@@ -311,8 +530,15 @@
lose the funds.
- Copy wallet URL
- I understand
+ Copy wallet URL
+ I understand
@@ -357,397 +583,391 @@
mixins: [windowMixin],
data: function () {
return {
- balanceAmount:"",
- tickershort:"",
- name:"",
- receive: {
- show: false,
- status: 'pending',
- paymentReq: null,
- paymentHash: null,
- minMax: [0, 2100000000000000],
- lnurl: null,
- units: ['sat'],
- unit: 'sat',
- data: {
- amount: null,
- memo: ''
- }
- },
- parse: {
- show: false,
- invoice: null,
- lnurlpay: null,
- lnurlauth: null,
- data: {
- request: '',
- amount: 0,
- comment: ''
- },
- paymentChecker: null,
- camera: {
+ balanceAmount: '',
+ tickershort: '',
+ name: '',
+ receive: {
show: false,
- camera: 'auto'
- }
- },
- payments: [],
- paymentsTable: {
- columns: [
- {
- name: 'note',
- align: 'left',
- label: 'Note',
- field: 'note'
- },
- {
- name: 'date',
- align: 'left',
- label: 'Date',
- field: 'date',
- sortable: true
- },
- {
- name: 'amount',
- align: 'right',
- label: 'Amount',
- field: 'amount',
- sortable: true
+ status: 'pending',
+ paymentReq: null,
+ paymentHash: null,
+ minMax: [0, 2100000000000000],
+ lnurl: null,
+ units: ['sat'],
+ unit: 'sat',
+ data: {
+ amount: null,
+ memo: ''
}
- ],
- pagination: {
- rowsPerPage: 10
},
- filter: null
- },
- paymentsChart: {
- show: false
- },
- disclaimerDialog: {
- show: false,
- location: window.location
- },
- balance: 0,
- credit: 0,
- newName: ''
- }
+ parse: {
+ show: false,
+ invoice: null,
+ lnurlpay: null,
+ lnurlauth: null,
+ data: {
+ request: '',
+ amount: 0,
+ comment: ''
+ },
+ paymentChecker: null,
+ camera: {
+ show: false,
+ camera: 'auto'
+ }
+ },
+ payments: [],
+ paymentsTable: {
+ columns: [
+ {
+ name: 'note',
+ align: 'left',
+ label: 'Note',
+ field: 'note'
+ },
+ {
+ name: 'date',
+ align: 'left',
+ label: 'Date',
+ field: 'date',
+ sortable: true
+ },
+ {
+ name: 'amount',
+ align: 'right',
+ label: 'Amount',
+ field: 'amount',
+ sortable: true
+ }
+ ],
+ pagination: {
+ rowsPerPage: 10
+ },
+ filter: null
+ },
+ paymentsChart: {
+ show: false
+ },
+ disclaimerDialog: {
+ show: false,
+ location: window.location
+ },
+ balance: 0,
+ credit: 0,
+ newName: ''
+ }
},
computed: {
- formattedBalance: function () {
- return this.balance / 100
- },
- filteredPayments: function () {
- var q = this.paymentsTable.filter
- if (!q || q === '') return this.payments
+ formattedBalance: function () {
+ return this.balance / 100
+ },
+ filteredPayments: function () {
+ var q = this.paymentsTable.filter
+ if (!q || q === '') return this.payments
- return LNbits.utils.search(this.payments, q)
+ return LNbits.utils.search(this.payments, q)
+ },
+ canPay: function () {
+ if (!this.parse.invoice) return false
+ return this.parse.invoice.sat <= this.balance
+ },
+ pendingPaymentsExist: function () {
+ return this.payments.findIndex(payment => payment.pending) !== -1
+ }
},
- canPay: function () {
- if (!this.parse.invoice) return false
- return this.parse.invoice.sat <= this.balance
+ filters: {
+ msatoshiFormat: function (value) {
+ return LNbits.utils.formatSat(value / 1000)
+ }
},
- pendingPaymentsExist: function () {
- return this.payments.findIndex(payment => payment.pending) !== -1
- }
- },
- filters: {
- msatoshiFormat: function (value) {
- return LNbits.utils.formatSat(value / 1000)
- }
- },
- methods: {
- paymentTableRowKey: function (row) {
- return row.payment_hash + row.amount
- },
- closeCamera: function () {
- this.parse.camera.show = false
- },
- showCamera: function () {
- this.parse.camera.show = true
- },
- showChart: function () {
- this.paymentsChart.show = true
- this.$nextTick(() => {
- generateChart(this.$refs.canvas, this.payments)
- })
- },
- focusInput(el) {
- this.$nextTick(() => this.$refs[el].focus())
- },
- showReceiveDialog: function () {
- this.receive.show = true
- this.receive.status = 'pending'
- this.receive.paymentReq = null
- this.receive.paymentHash = null
- this.receive.data.amount = null
- this.receive.data.memo = null
- this.receive.unit = 'sat'
- this.receive.paymentChecker = null
- this.receive.minMax = [0, 2100000000000000]
- this.receive.lnurl = null
- this.focusInput('setAmount')
- },
- showParseDialog: function () {
- this.parse.show = true
- this.parse.invoice = null
- this.parse.lnurlpay = null
- this.parse.lnurlauth = null
- this.parse.data.request = ''
- this.parse.data.comment = ''
- this.parse.data.paymentChecker = null
- this.parse.camera.show = false
- },
- updateBalance: function (credit) {
- this.balance = this.balance // update balance
- },
- closeReceiveDialog: function () {
- setTimeout(() => {
- clearInterval(this.receive.paymentChecker)
- }, 10000)
- },
- closeParseDialog: function () {
- setTimeout(() => {
- clearInterval(this.parse.paymentChecker)
- }, 10000)
- },
- onPaymentReceived: function (paymentHash) {
- this.fetchPayments()
- this.fetchBalance()
-
- if (this.receive.paymentHash === paymentHash) {
- this.receive.show = false
+ methods: {
+ paymentTableRowKey: function (row) {
+ return row.payment_hash + row.amount
+ },
+ closeCamera: function () {
+ this.parse.camera.show = false
+ },
+ showCamera: function () {
+ this.parse.camera.show = true
+ },
+ showChart: function () {
+ this.paymentsChart.show = true
+ this.$nextTick(() => {
+ generateChart(this.$refs.canvas, this.payments)
+ })
+ },
+ focusInput(el) {
+ this.$nextTick(() => this.$refs[el].focus())
+ },
+ showReceiveDialog: function () {
+ this.receive.show = true
+ this.receive.status = 'pending'
+ this.receive.paymentReq = null
this.receive.paymentHash = null
- clearInterval(this.receive.paymentChecker)
- }
- },
- createInvoice: function () {
- this.receive.status = 'loading'
- if (LNBITS_DENOMINATION != 'sats') {
- this.receive.data.amount = this.receive.data.amount * 100
- }
- LNbits.api
- .createInvoice(
- this.receive.data.amount,
- this.receive.data.memo,
- this.receive.unit,
- this.receive.lnurl && this.receive.lnurl.callback
- )
- .then(response => {
- this.receive.status = 'success'
- this.receive.paymentReq = response.data.payment_request
- this.receive.paymentHash = response.data.payment_hash
+ this.receive.data.amount = null
+ this.receive.data.memo = null
+ this.receive.unit = 'sat'
+ this.receive.paymentChecker = null
+ this.receive.minMax = [0, 2100000000000000]
+ this.receive.lnurl = null
+ this.focusInput('setAmount')
+ },
+ showParseDialog: function () {
+ this.parse.show = true
+ this.parse.invoice = null
+ this.parse.lnurlpay = null
+ this.parse.lnurlauth = null
+ this.parse.data.request = ''
+ this.parse.data.comment = ''
+ this.parse.data.paymentChecker = null
+ this.parse.camera.show = false
+ },
+ updateBalance: function (credit) {
+ this.balance = this.balance // update balance
+ },
+ closeReceiveDialog: function () {
+ setTimeout(() => {
+ clearInterval(this.receive.paymentChecker)
+ }, 10000)
+ },
+ closeParseDialog: function () {
+ setTimeout(() => {
+ clearInterval(this.parse.paymentChecker)
+ }, 10000)
+ },
+ onPaymentReceived: function (paymentHash) {
+ this.fetchPayments()
+ this.fetchBalance()
- if (response.data.lnurl_response !== null) {
- if (response.data.lnurl_response === false) {
- response.data.lnurl_response = `Unable to connect`
+ if (this.receive.paymentHash === paymentHash) {
+ this.receive.show = false
+ this.receive.paymentHash = null
+ clearInterval(this.receive.paymentChecker)
+ }
+ },
+ createInvoice: function () {
+ this.receive.status = 'loading'
+ if (LNBITS_DENOMINATION != 'sats') {
+ this.receive.data.amount = this.receive.data.amount * 100
+ }
+ LNbits.api
+ .createInvoice(
+ this.receive.data.amount,
+ this.receive.data.memo,
+ this.receive.unit,
+ this.receive.lnurl && this.receive.lnurl.callback
+ )
+ .then(response => {
+ this.receive.status = 'success'
+ this.receive.paymentReq = response.data.payment_request
+ this.receive.paymentHash = response.data.payment_hash
+
+ if (response.data.lnurl_response !== null) {
+ if (response.data.lnurl_response === false) {
+ response.data.lnurl_response = `Unable to connect`
+ }
+
+ if (typeof response.data.lnurl_response === 'string') {
+ // failure
+ this.$q.notify({
+ timeout: 5000,
+ type: 'warning',
+ message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
+ caption: response.data.lnurl_response
+ })
+ return
+ } else if (response.data.lnurl_response === true) {
+ // success
+ this.$q.notify({
+ timeout: 5000,
+ message: `Invoice sent to ${this.receive.lnurl.domain}!`,
+ spinner: true
+ })
+ }
}
- if (typeof response.data.lnurl_response === 'string') {
- // failure
- this.$q.notify({
- timeout: 5000,
- type: 'warning',
- message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
- caption: response.data.lnurl_response
- })
- return
- } else if (response.data.lnurl_response === true) {
- // success
- this.$q.notify({
- timeout: 5000,
- message: `Invoice sent to ${this.receive.lnurl.domain}!`,
- spinner: true
- })
+ clearInterval(this.receive.paymentChecker)
+ setTimeout(() => {
+ clearInterval(this.receive.paymentChecker)
+ }, 40000)
+ })
+ .catch(err => {
+ LNbits.utils.notifyApiError(err)
+ this.receive.status = 'pending'
+ })
+ },
+ decodeQR: function (res) {
+ this.parse.data.request = res
+ this.decodeRequest()
+ this.parse.camera.show = false
+ },
+ decodeRequest: function () {
+ this.parse.show = true
+ let req = this.parse.data.request.toLowerCase()
+ if (this.parse.data.request.toLowerCase().startsWith('lightning:')) {
+ this.parse.data.request = this.parse.data.request.slice(10)
+ } else if (this.parse.data.request.toLowerCase().startsWith('lnurl:')) {
+ this.parse.data.request = this.parse.data.request.slice(6)
+ } else if (req.indexOf('lightning=lnurl1') !== -1) {
+ this.parse.data.request = this.parse.data.request
+ .split('lightning=')[1]
+ .split('&')[0]
+ }
+
+ if (
+ this.parse.data.request.toLowerCase().startsWith('lnurl1') ||
+ this.parse.data.request.match(/[\w.+-~_]+@[\w.+-~_]/)
+ ) {
+ return
+ }
+
+ let invoice
+ try {
+ invoice = decode(this.parse.data.request)
+ } catch (error) {
+ this.$q.notify({
+ timeout: 3000,
+ type: 'warning',
+ message: error + '.',
+ caption: '400 BAD REQUEST'
+ })
+ this.parse.show = false
+ return
+ }
+
+ let cleanInvoice = {
+ msat: invoice.human_readable_part.amount,
+ sat: invoice.human_readable_part.amount / 1000,
+ fsat: LNbits.utils.formatSat(
+ invoice.human_readable_part.amount / 1000
+ )
+ }
+
+ _.each(invoice.data.tags, tag => {
+ if (_.isObject(tag) && _.has(tag, 'description')) {
+ if (tag.description === 'payment_hash') {
+ cleanInvoice.hash = tag.value
+ } else if (tag.description === 'description') {
+ cleanInvoice.description = tag.value
+ } else if (tag.description === 'expiry') {
+ var expireDate = new Date(
+ (invoice.data.time_stamp + tag.value) * 1000
+ )
+ cleanInvoice.expireDate = Quasar.utils.date.formatDate(
+ expireDate,
+ 'YYYY-MM-DDTHH:mm:ss.SSSZ'
+ )
+ cleanInvoice.expired = false // TODO
}
}
+ })
- clearInterval(this.receive.paymentChecker)
- setTimeout(() => {
- clearInterval(this.receive.paymentChecker)
- }, 40000)
-
+ this.parse.invoice = Object.freeze(cleanInvoice)
+ },
+ payInvoice: function () {
+ let dismissPaymentMsg = this.$q.notify({
+ timeout: 0,
+ message: 'Processing payment...'
+ })
+ },
+ payLnurl: function () {
+ let dismissPaymentMsg = this.$q.notify({
+ timeout: 0,
+ message: 'Processing payment...'
+ })
+ },
+ authLnurl: function () {
+ let dismissAuthMsg = this.$q.notify({
+ timeout: 10,
+ message: 'Performing authentication...'
+ })
+ },
+
+ deleteWallet: function (walletId, user) {
+ LNbits.utils
+ .confirmDialog('Are you sure you want to delete this wallet?')
+ .onOk(() => {
+ LNbits.href.deleteWallet(walletId, user)
+ })
+ },
+ fetchPayments: function () {
+ return
+ },
+ fetchBalance: function () {},
+ exportCSV: function () {
+ // status is important for export but it is not in paymentsTable
+ // because it is manually added with payment detail link and icons
+ // and would cause duplication in the list
+ let columns = this.paymentsTable.columns
+ columns.unshift({
+ name: 'pending',
+ align: 'left',
+ label: 'Pending',
+ field: 'pending'
+ })
+ LNbits.utils.exportCSV(columns, this.payments)
+ }
+ },
+ watch: {
+ payments: function () {
+ this.fetchBalance()
+ }
+ },
+ created: function () {
+ this.fetchBalance()
+ this.fetchPayments()
+
+ LNbits.api
+ .request('GET', '/api/v1/currencies')
+ .then(response => {
+ this.receive.units = ['sat', ...response.data]
})
.catch(err => {
LNbits.utils.notifyApiError(err)
- this.receive.status = 'pending'
})
},
- decodeQR: function (res) {
- this.parse.data.request = res
- this.decodeRequest()
- this.parse.camera.show = false
- },
- decodeRequest: function () {
- this.parse.show = true
- let req = this.parse.data.request.toLowerCase()
- if (this.parse.data.request.toLowerCase().startsWith('lightning:')) {
- this.parse.data.request = this.parse.data.request.slice(10)
- } else if (this.parse.data.request.toLowerCase().startsWith('lnurl:')) {
- this.parse.data.request = this.parse.data.request.slice(6)
- } else if (req.indexOf('lightning=lnurl1') !== -1) {
- this.parse.data.request = this.parse.data.request
- .split('lightning=')[1]
- .split('&')[0]
- }
+ created: function () {
+ let params = new URL(document.location).searchParams
+ // get ticker
if (
- this.parse.data.request.toLowerCase().startsWith('lnurl1') ||
- this.parse.data.request.match(/[\w.+-~_]+@[\w.+-~_]/)
+ !params.get('tsh') &&
+ !this.$q.localStorage.getItem('cashu.tickershort')
) {
- return
- }
-
- let invoice
- try {
- invoice = decode(this.parse.data.request)
- } catch (error) {
- this.$q.notify({
- timeout: 3000,
- type: 'warning',
- message: error + '.',
- caption: '400 BAD REQUEST'
- })
- this.parse.show = false
- return
- }
-
- let cleanInvoice = {
- msat: invoice.human_readable_part.amount,
- sat: invoice.human_readable_part.amount / 1000,
- fsat: LNbits.utils.formatSat(invoice.human_readable_part.amount / 1000)
- }
-
- _.each(invoice.data.tags, tag => {
- if (_.isObject(tag) && _.has(tag, 'description')) {
- if (tag.description === 'payment_hash') {
- cleanInvoice.hash = tag.value
- } else if (tag.description === 'description') {
- cleanInvoice.description = tag.value
- } else if (tag.description === 'expiry') {
- var expireDate = new Date(
- (invoice.data.time_stamp + tag.value) * 1000
- )
- cleanInvoice.expireDate = Quasar.utils.date.formatDate(
- expireDate,
- 'YYYY-MM-DDTHH:mm:ss.SSSZ'
- )
- cleanInvoice.expired = false // TODO
- }
- }
- })
-
- this.parse.invoice = Object.freeze(cleanInvoice)
- },
- payInvoice: function () {
- let dismissPaymentMsg = this.$q.notify({
- timeout: 0,
- message: 'Processing payment...'
- })
- },
- payLnurl: function () {
- let dismissPaymentMsg = this.$q.notify({
- timeout: 0,
- message: 'Processing payment...'
- })
- },
- authLnurl: function () {
- let dismissAuthMsg = this.$q.notify({
- timeout: 10,
- message: 'Performing authentication...'
- })
- },
-
- deleteWallet: function (walletId, user) {
- LNbits.utils
- .confirmDialog('Are you sure you want to delete this wallet?')
- .onOk(() => {
- LNbits.href.deleteWallet(walletId, user)
- })
- },
- fetchPayments: function () {
- return
- },
- fetchBalance: function () {
-
- },
- exportCSV: function () {
- // status is important for export but it is not in paymentsTable
- // because it is manually added with payment detail link and icons
- // and would cause duplication in the list
- let columns = this.paymentsTable.columns
- columns.unshift({
- name: 'pending',
- align: 'left',
- label: 'Pending',
- field: 'pending'
- })
- LNbits.utils.exportCSV(columns, this.payments)
- }
- },
- watch: {
- payments: function () {
- this.fetchBalance()
- }
- },
- created: function () {
- this.fetchBalance()
- this.fetchPayments()
-
- LNbits.api
- .request('GET', '/api/v1/currencies')
- .then(response => {
- this.receive.units = ['sat', ...response.data]
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- },
- created: function () {
-
- let params = (new URL(document.location)).searchParams
-
- // get ticker
- if(!params.get('tsh') && !this.$q.localStorage.getItem('cashu.tickershort')){
- this.$q.localStorage.set('cashu.tickershort', "CE")
- this.tickershort = "CE"
- }
- else if(params.get('tsh')){
+ this.$q.localStorage.set('cashu.tickershort', 'CE')
+ this.tickershort = 'CE'
+ } else if (params.get('tsh')) {
this.$q.localStorage.set('cashu.tickershort', params.get('tsh'))
this.tickershort = params.get('tsh')
- }
- else if(this.$q.localStorage.getItem('cashu.tickershort')){
+ } else if (this.$q.localStorage.getItem('cashu.tickershort')) {
this.tickershort = this.$q.localStorage.getItem('cashu.tickershort')
- }
+ }
-
- if (!this.$q.localStorage.getItem('cashu.amount')) {
- this.balanceAmount = 0
- }
+ if (!this.$q.localStorage.getItem('cashu.amount')) {
+ this.balanceAmount = 0
+ }
- // get mint
- if (params.get('mnt')) {
- this.mint = params.get('mnt')
- this.$q.localStorage.set('cashu.mint', params.get('mnt'))
- }
- else if(this.$q.localStorage.getItem('cashu.mint')){
- this.mint = this.$q.localStorage.getItem('cashu.mint')
- }
- else{
- this.$q.notify({
- color: 'red',
- message: 'No mint set!'
- })
- }
+ // get mint
+ if (params.get('mnt')) {
+ this.mint = params.get('mnt')
+ this.$q.localStorage.set('cashu.mint', params.get('mnt'))
+ } else if (this.$q.localStorage.getItem('cashu.mint')) {
+ this.mint = this.$q.localStorage.getItem('cashu.mint')
+ } else {
+ this.$q.notify({
+ color: 'red',
+ message: 'No mint set!'
+ })
+ }
- // get name
- if (params.get('nme')) {
- this.name = params.get('nme')
- this.$q.localStorage.set('cashu.name', params.get('nme'))
+ // get name
+ if (params.get('nme')) {
+ this.name = params.get('nme')
+ this.$q.localStorage.set('cashu.name', params.get('nme'))
+ } else if (this.$q.localStorage.getItem('cashu.name')) {
+ this.name = this.$q.localStorage.getItem('cashu.name')
+ }
}
- else if(this.$q.localStorage.getItem('cashu.name')){
- this.name = this.$q.localStorage.getItem('cashu.name')
- }
-
- }
-})
+ })
{% endblock %}
diff --git a/lnbits/extensions/cashu/views.py b/lnbits/extensions/cashu/views.py
index 655ed0289..097fbd00d 100644
--- a/lnbits/extensions/cashu/views.py
+++ b/lnbits/extensions/cashu/views.py
@@ -22,14 +22,19 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
"cashu/index.html", {"request": request, "user": user.dict()}
)
+
@cashu_ext.get("/wallet")
async def cashu(request: Request):
- return cashu_renderer().TemplateResponse("cashu/wallet.html",{"request": request})
+ return cashu_renderer().TemplateResponse("cashu/wallet.html", {"request": request})
+
@cashu_ext.get("/mint/{mintID}")
async def cashu(request: Request, mintID):
cashu = await get_cashu(mintID)
- return cashu_renderer().TemplateResponse("cashu/mint.html",{"request": request, "mint_name": cashu.name})
+ return cashu_renderer().TemplateResponse(
+ "cashu/mint.html", {"request": request, "mint_name": cashu.name}
+ )
+
@cashu_ext.get("/manifest/{cashu_id}.webmanifest")
async def manifest(cashu_id: str):
diff --git a/lnbits/extensions/cashu/views_api.py b/lnbits/extensions/cashu/views_api.py
index c0bc59f39..ab96e1018 100644
--- a/lnbits/extensions/cashu/views_api.py
+++ b/lnbits/extensions/cashu/views_api.py
@@ -1,5 +1,4 @@
from http import HTTPStatus
-from secp256k1 import PublicKey
from typing import Union
import httpx
@@ -7,35 +6,36 @@ from fastapi import Query
from fastapi.params import Depends
from lnurl import decode as decode_lnurl
from loguru import logger
+from secp256k1 import PublicKey
from starlette.exceptions import HTTPException
from lnbits.core.crud import get_user
-from lnbits.core.services import create_invoice
+from lnbits.core.services import check_transaction_status, create_invoice
from lnbits.core.views.api import api_payment
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
-from .core.base import CashuError
+from lnbits.wallets.base import PaymentStatus
from . import cashu_ext
-from .ledger import request_mint, mint
-from .mint import get_pubkeys
-
+from .core.base import CashuError
from .crud import (
- create_cashu,
- delete_cashu,
- get_cashu,
+ create_cashu,
+ delete_cashu,
+ get_cashu,
get_cashus,
+ get_lightning_invoice,
store_lightning_invoice,
)
-
+from .ledger import mint, request_mint
+from .mint import get_pubkeys
from .models import (
Cashu,
- Invoice,
- Pegs,
- CheckPayload,
- MeltPayload,
- MintPayloads,
- SplitPayload,
- PayLnurlWData
+ CheckPayload,
+ Invoice,
+ MeltPayload,
+ MintPayloads,
+ PayLnurlWData,
+ Pegs,
+ SplitPayload,
)
########################################
@@ -55,13 +55,12 @@ async def api_cashus(
@cashu_ext.post("/api/v1/cashus", status_code=HTTPStatus.CREATED)
-async def api_cashu_create(
- data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
-):
+async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)):
cashu = await create_cashu(wallet_id=wallet.wallet.id, data=data)
logger.debug(cashu)
return cashu.dict()
+
@cashu_ext.post("/api/v1/cashus/upodatekeys", status_code=HTTPStatus.CREATED)
async def api_cashu_update_keys(
data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)
@@ -122,7 +121,8 @@ async def api_cashu_create_invoice(
@cashu_ext.post(
- "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay", status_code=HTTPStatus.OK
+ "/api/v1/cashus/{cashu_id}/invoices/{payment_request}/pay",
+ status_code=HTTPStatus.OK,
)
async def api_cashu_pay_invoice(
lnurl_data: PayLnurlWData, payment_request: str = None, cashu_id: str = None
@@ -203,26 +203,29 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
#################MINT###################
########################################
-@cashu_ext.get("/api/v1/mint/keys/{cashu_id}", status_code=HTTPStatus.OK)
-async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)):
+
+@cashu_ext.get("/api/v1/mint/keys/{cashu_id}", status_code=HTTPStatus.OK)
+async def keys(
+ cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
+):
"""Get the public keys of the mint"""
mint = await get_cashu(cashu_id)
if mint is None:
raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
- )
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
return get_pubkeys(mint.prvkey)
@cashu_ext.get("/api/v1/mint/{cashu_id}")
async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
"""Request minting of tokens. Server responds with a Lightning invoice."""
- print('############################ amount', amount)
+
cashu = await get_cashu(cashu_id)
if cashu is None:
raise HTTPException(
- status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
- )
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
try:
payment_hash, payment_request = await create_invoice(
@@ -237,20 +240,43 @@ async def mint_pay_request(amount: int = 0, cashu_id: str = Query(None)):
await store_lightning_invoice(cashu_id, invoice)
except Exception as e:
logger.error(e)
- raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(cashu_id))
+ raise HTTPException(
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(cashu_id)
+ )
return {"pr": payment_request, "hash": payment_hash}
@cashu_ext.post("/mint")
-async def mint_coins(payloads: MintPayloads, payment_hash: Union[str, None] = None, cashu_id: str = Query(None)):
+async def mint_coins(
+ payloads: MintPayloads,
+ payment_hash: Union[str, None] = None,
+ cashu_id: str = Query(None),
+):
"""
Requests the minting of tokens belonging to a paid payment request.
-
Call this endpoint after `GET /mint`.
"""
- amounts = []
- B_s = []
+ print("############################ amount")
+ cashu: Cashu = await get_cashu(cashu_id)
+ if cashu is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
+ )
+ invoice: Invoice = get_lightning_invoice(cashu_id, payment_hash)
+ if invoice is None:
+ raise HTTPException(
+ status_code=HTTPStatus.NOT_FOUND, detail="Mint does not have this invoice."
+ )
+
+ status: PaymentStatus = check_transaction_status(cashu.wallet, payment_hash)
+ if status.paid == False:
+ raise HTTPException(
+ status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
+ )
+
+ # amounts = []
+ # B_s = []
# for payload in payloads.blinded_messages:
# amounts.append(payload.amount)
# B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
@@ -290,4 +316,4 @@ async def spli_coinst(payload: SplitPayload, cashu_id: str = Query(None)):
"""There was a problem with the split"""
raise Exception("could not split tokens.")
fst_promises, snd_promises = split_return
- return {"fst": fst_promises, "snd": snd_promises}
\ No newline at end of file
+ return {"fst": fst_promises, "snd": snd_promises}