chore: format

This commit is contained in:
Vlad Stan
2022-10-07 11:17:02 +03:00
committed by dni ⚡
parent 2c54c240ba
commit 104aca33ff
15 changed files with 992 additions and 610 deletions

View File

@ -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))

View File

@ -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()
@ -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(),
),
)
@ -61,7 +57,12 @@ async def update_cashu_keys(cashu_id, wif: str = None) -> Optional[Cashu]:
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,13 +153,17 @@ 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)

View File

@ -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,7 +160,13 @@ 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")
@ -145,7 +174,9 @@ async def mint(B_s: List[PublicKey], amounts: List[int], payment_hash: str = Que
"""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 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,7 +188,10 @@ 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")
@ -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")
@ -233,6 +274,7 @@ async def fee_reserve(amount_msat: int, cashu_id: str = Query(None)):
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:
@ -246,6 +288,7 @@ 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:
@ -280,6 +323,7 @@ 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:

View File

@ -1,6 +1,5 @@
from .models import Cashu
from .mint_helper import derive_keys, derive_pubkeys
from .models import Cashu
def get_pubkeys(xpriv: str):
@ -11,6 +10,7 @@ def get_pubkeys(xpriv: str):
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

View File

@ -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)]}

View File

@ -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

View File

@ -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)

View File

@ -71,7 +71,8 @@
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X DELETE {{ request.base_url
}}cashu/api/v1/cashus/&lt;cashu_id&gt; -H "X-Api-Key: &lt;admin_key&gt;"
}}cashu/api/v1/cashus/&lt;cashu_id&gt; -H "X-Api-Key:
&lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>

View File

@ -2,13 +2,12 @@
<q-card>
<q-card-section>
<p>
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.
</p>
<small
>Created by
<a href="https://github.com/calle" target="_blank"
>Calle</a
>.</small
<a href="https://github.com/calle" target="_blank">Calle</a>.</small
>
</q-card-section>
</q-card>

View File

@ -4,7 +4,9 @@
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
<q-card>
<q-card-section>
<q-btn unelevated color="primary" @click="formDialog.show = true">New Mint</q-btn>
<q-btn unelevated color="primary" @click="formDialog.show = true"
>New Mint</q-btn
>
</q-card-section>
</q-card>
@ -18,8 +20,14 @@
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
</div>
</div>
<q-table dense flat :data="cashus" row-key="id" :columns="cashusTable.columns"
:pagination.sync="cashusTable.pagination">
<q-table
dense
flat
:data="cashus"
row-key="id"
:columns="cashusTable.columns"
:pagination.sync="cashusTable.pagination"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
@ -34,23 +42,43 @@
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width>
<q-btn unelevated dense size="xs" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
<q-btn
unelevated
dense
size="xs"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="'wallet/?tsh=' + props.row.tickershort + '&mnt=' + hostname + props.row.id + '&nme=' + props.row.name"
target="_blank"><q-tooltip>Shareable wallet page</q-tooltip></q-btn>
target="_blank"
><q-tooltip>Shareable wallet page</q-tooltip></q-btn
>
<q-btn unelevated dense size="xs" icon="account_balance" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
<q-btn
unelevated
dense
size="xs"
icon="account_balance"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
type="a"
:href="'mint/' + props.row.id"
target="_blank"><q-tooltip>Shareable mint page</q-tooltip></q-btn>
target="_blank"
><q-tooltip>Shareable mint page</q-tooltip></q-btn
>
</q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props">
{{ (col.name == 'tip_options' && col.value ?
JSON.parse(col.value).join(", ") : col.value) }}
</q-td>
<q-td auto-width>
<q-btn flat dense size="xs" @click="deleteMint(props.row.id)" icon="cancel" color="pink"></q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteMint(props.row.id)"
icon="cancel"
color="pink"
></q-btn>
</q-td>
</q-tr>
</template>
@ -79,34 +107,90 @@
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
<q-form @submit="createMint" class="q-gutter-md">
<q-input filled dense v-model.trim="formDialog.data.name" label="Mint Name" placeholder="Cashu Mint"></q-input>
<q-select filled dense emit-value v-model="formDialog.data.wallet" :options="g.user.walletOptions"
label="Wallet *" ></q-select>
<q-toggle v-model="toggleAdvanced" label="Show advanced options"></q-toggle>
<q-input
filled
dense
v-model.trim="formDialog.data.name"
label="Mint Name"
placeholder="Cashu Mint"
></q-input>
<q-select
filled
dense
emit-value
v-model="formDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet *"
></q-select>
<q-toggle
v-model="toggleAdvanced"
label="Show advanced options"
></q-toggle>
<div v-show="toggleAdvanced">
<div class="row">
<div class="col-5">
<q-checkbox v-model="formDialog.data.fraction" color="primary" label="sats/coins?">
<q-tooltip>Use with hedging extension to create a stablecoin!</q-tooltip>
<q-checkbox
v-model="formDialog.data.fraction"
color="primary"
label="sats/coins?"
>
<q-tooltip
>Use with hedging extension to create a stablecoin!</q-tooltip
>
</q-checkbox>
</div>
<div class="col-7">
<q-input v-if="!formDialog.data.fraction" filled dense type="number" v-model.trim="formDialog.data.cost" label="Sat coin cost (optional)"
value="1" type="number"></q-input>
<q-input v-if="!formDialog.data.fraction" filled dense v-model.trim="formDialog.data.tickershort" label="Ticker shorthand" placeholder="CC"
#></q-input>
<q-input
v-if="!formDialog.data.fraction"
filled
dense
type="number"
v-model.trim="formDialog.data.cost"
label="Sat coin cost (optional)"
value="1"
type="number"
></q-input>
<q-input
v-if="!formDialog.data.fraction"
filled
dense
v-model.trim="formDialog.data.tickershort"
label="Ticker shorthand"
placeholder="CC"
#
></q-input>
</div>
</div>
<q-input class="q-mt-md" filled dense type="number" v-model.trim="formDialog.data.maxsats"
label="Maximum mint liquidity (optional)" placeholder="∞"></q-input>
<q-input class="q-mt-md" filled dense type="number" v-model.trim="formDialog.data.coins"
label="Coins that 'exist' in mint (optional)" placeholder="∞"></q-input>
<q-input
class="q-mt-md"
filled
dense
type="number"
v-model.trim="formDialog.data.maxsats"
label="Maximum mint liquidity (optional)"
placeholder="∞"
></q-input>
<q-input
class="q-mt-md"
filled
dense
type="number"
v-model.trim="formDialog.data.coins"
label="Coins that 'exist' in mint (optional)"
placeholder="∞"
></q-input>
</div>
<div class="row q-mt-md">
<q-btn unelevated color="primary"
:disable="formDialog.data.wallet == null || formDialog.data.name == null" type="submit">Create Mint
<q-btn
unelevated
color="primary"
:disable="formDialog.data.wallet == null || formDialog.data.name == null"
type="submit"
>Create Mint
</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
</q-card>
@ -130,7 +214,7 @@
data: function () {
return {
cashus: [],
hostname: location.protocol + "//" + location.host + "/cashu/mint/",
hostname: location.protocol + '//' + location.host + '/cashu/mint/',
toggleAdvanced: false,
cashusTable: {
columns: [
@ -165,7 +249,7 @@
align: 'left',
label: 'No. of coins',
field: 'coins'
},
}
],
pagination: {
rowsPerPage: 10
@ -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
@ -229,7 +313,9 @@
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(

View File

@ -12,7 +12,10 @@
<h3 class="q-my-none">{{ mint_name }}</h3>
<br />
</center>
<h5 class="q-my-none">Some data about mint here: <br/>* whether its online <br/>* Who to contact for support <br/>* etc...</h5>
<h5 class="q-my-none">
Some data about mint here: <br />* whether its online <br />* Who to
contact for support <br />* etc...
</h5>
</q-card-section>
</q-card>
</div>

View File

@ -1,84 +1,169 @@
{% 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 %}
<q-page-container>
<q-page>
<div class="row q-col-gutter-md justify-center q-pt-lg">
<div class="col-12 col-sm-8 col-md-9 col-lg-7 text-center q-gutter-y-md">
<q-card>
<q-card-section>
<h3 class="q-my-none">
<center><strong>{% raw %} {{balanceAmount}}
</strong> {{tickershort}}{% endraw %}</center>
<center>
<strong>{% raw %} {{balanceAmount}} </strong> {{tickershort}}{%
endraw %}
</center>
</h3>
</q-card-section>
</q-card>
<div class="row q-pb-md q-px-md justify-center q-col-gutter-sm gt-sm q-pt-lg">
<div
class="row q-pb-md q-px-md justify-center q-col-gutter-sm gt-sm q-pt-lg"
>
<div class="col">
<q-btn size="18px" icon="arrow_downward" rounded color="primary" class="full-width" @click="showParseDialog">Receive</q-btn>
<q-btn
size="18px"
icon="arrow_downward"
rounded
color="primary"
class="full-width"
@click="showParseDialog"
>Receive</q-btn
>
</div>
<div class="col">
<q-btn size="18px" icon="arrow_upward" rounded color="primary" class="full-width" @click="showSendDialog">Send</q-btn>
<q-btn
size="18px"
icon="arrow_upward"
rounded
color="primary"
class="full-width"
@click="showSendDialog"
>Send</q-btn
>
</div>
<div class="col">
<q-btn size="18px" rounded color="secondary" icon="sync_alt" class="full-width" @click="showCamera">Peg in/out
<q-btn
size="18px"
rounded
color="secondary"
icon="sync_alt"
class="full-width"
@click="showCamera"
>Peg in/out
</q-btn>
</div>
<div class="col">
<q-btn size="18px" rounded color="secondary" icon="photo_camera" class="full-width" @click="showCamera">scan
<q-btn
size="18px"
rounded
color="secondary"
icon="photo_camera"
class="full-width"
@click="showCamera"
>scan
</q-btn>
</div>
</div>
<q-card>
<q-card-section>
<div class="row items-center no-wrap q-mb-sm">
<div class="col">
<h5 class="text-subtitle1 q-my-none">Transactions</h5>
</div>
<div class="col-auto">{% raw %}
<q-btn dense flat round icon="approval" color="grey" type="a" :href="mint" target="_blank">{% endraw %}
<div class="col-auto">
{% raw %}
<q-btn
dense
flat
round
icon="approval"
color="grey"
type="a"
:href="mint"
target="_blank"
>{% endraw %}
<q-tooltip>Mint details</q-tooltip>
</q-btn>
<q-btn flat color="grey" @click="exportCSV">Export to CSV</q-btn>
<q-btn flat color="grey" @click="exportCSV"
>Export to CSV</q-btn
>
<q-btn dense flat round icon="show_chart" color="grey" @click="showChart">
<q-btn
dense
flat
round
icon="show_chart"
color="grey"
@click="showChart"
>
<q-tooltip>Show chart</q-tooltip>
</q-btn>
</div>
</div>
<q-input v-if="payments.length > 10" filled dense clearable v-model="paymentsTable.filter" debounce="300"
placeholder="Search by tag, memo, amount" class="q-mb-md">
<q-input
v-if="payments.length > 10"
filled
dense
clearable
v-model="paymentsTable.filter"
debounce="300"
placeholder="Search by tag, memo, amount"
class="q-mb-md"
>
</q-input>
<q-table dense flat :data="filteredPayments" :row-key="paymentTableRowKey" :columns="paymentsTable.columns"
:pagination.sync="paymentsTable.pagination" no-data-label="No transactions made yet"
:filter="paymentsTable.filter">
<q-table
dense
flat
:data="filteredPayments"
:row-key="paymentTableRowKey"
:columns="paymentsTable.columns"
:pagination.sync="paymentsTable.pagination"
no-data-label="No transactions made yet"
:filter="paymentsTable.filter"
>
{% raw %}
<template v-slot:header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props">{{ col.label }}</q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props"
>{{ col.label }}</q-th
>
</q-tr>
</template>
<template v-slot:body="props">
<q-tr :props="props">
<q-td auto-width class="text-center">
<q-icon v-if="props.row.isPaid" size="14px" :name="props.row.isOut ? 'call_made' : 'call_received'"
:color="props.row.isOut ? 'pink' : 'green'" @click="props.expand = !props.expand"></q-icon>
<q-icon v-else name="settings_ethernet" color="grey" @click="props.expand = !props.expand">
<q-icon
v-if="props.row.isPaid"
size="14px"
:name="props.row.isOut ? 'call_made' : 'call_received'"
:color="props.row.isOut ? 'pink' : 'green'"
@click="props.expand = !props.expand"
></q-icon>
<q-icon
v-else
name="settings_ethernet"
color="grey"
@click="props.expand = !props.expand"
>
<q-tooltip>Pending</q-tooltip>
</q-icon>
</q-td>
<q-td key="memo" :props="props" style="white-space: normal; word-break: break-all">
<q-badge v-if="props.row.tag" color="yellow" text-color="black">
<a class="inherit" :href="['/', props.row.tag, '/?usr=', user.id].join('')">
<q-td
key="memo"
:props="props"
style="white-space: normal; word-break: break-all"
>
<q-badge
v-if="props.row.tag"
color="yellow"
text-color="black"
>
<a
class="inherit"
:href="['/', props.row.tag, '/?usr=', user.id].join('')"
>
#{{ props.row.tag }}
</a>
</q-badge>
@ -89,7 +174,12 @@
{{ props.row.dateFrom }}
</q-td>
{% endraw %}
<q-td auto-width key="sat" v-if="'{{LNBITS_DENOMINATION}}' != 'sats'" :props="props">{% raw %} {{
<q-td
auto-width
key="sat"
v-if="'{{LNBITS_DENOMINATION}}' != 'sats'"
:props="props"
>{% raw %} {{
parseFloat(String(props.row.fsat).replaceAll(",", "")) / 100
}}
</q-td>
@ -108,34 +198,68 @@
<div v-if="props.row.isIn && props.row.pending">
<q-icon name="settings_ethernet" color="grey"></q-icon>
Invoice waiting to be paid
<lnbits-payment-details :payment="props.row"></lnbits-payment-details>
<div v-if="props.row.bolt11" class="text-center q-mb-lg">
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
<div
v-if="props.row.bolt11"
class="text-center q-mb-lg"
>
<a :href="'lightning:' + props.row.bolt11">
<q-responsive :ratio="1" class="q-mx-xl">
<qrcode :value="props.row.bolt11" :options="{width: 340}" class="rounded-borders">
<qrcode
:value="props.row.bolt11"
:options="{width: 340}"
class="rounded-borders"
>
</qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText(props.row.bolt11)">Copy invoice</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
<q-btn
outline
color="grey"
@click="copyText(props.row.bolt11)"
>Copy invoice</q-btn
>
<q-btn
v-close-popup
flat
color="grey"
class="q-ml-auto"
>Close</q-btn
>
</div>
</div>
<div v-else-if="props.row.isPaid && props.row.isIn">
<q-icon size="18px" :name="'call_received'" :color="'green'"></q-icon>
<q-icon
size="18px"
:name="'call_received'"
:color="'green'"
></q-icon>
Payment Received
<lnbits-payment-details :payment="props.row"></lnbits-payment-details>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
<div v-else-if="props.row.isPaid && props.row.isOut">
<q-icon size="18px" :name="'call_made'" :color="'pink'"></q-icon>
<q-icon
size="18px"
:name="'call_made'"
:color="'pink'"
></q-icon>
Payment Sent
<lnbits-payment-details :payment="props.row"></lnbits-payment-details>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
<div v-else-if="props.row.isOut && props.row.pending">
<q-icon name="settings_ethernet" color="grey"></q-icon>
Outgoing payment pending
<lnbits-payment-details :payment="props.row"></lnbits-payment-details>
<lnbits-payment-details
:payment="props.row"
></lnbits-payment-details>
</div>
</div>
</q-card>
@ -149,49 +273,101 @@
<q-dialog v-model="receive.show" @hide="closeReceiveDialog">
{% raw %}
<q-card v-if="!receive.paymentReq" class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-card
v-if="!receive.paymentReq"
class="q-pa-lg q-pt-xl lnbits__dialog-card"
>
<q-form @submit="createInvoice" class="q-gutter-md">
<p v-if="receive.lnurl" class="text-h6 text-center q-my-none">
<b>{{receive.lnurl.domain}}</b> is requesting an invoice:
</p>
{% endraw %} {% if LNBITS_DENOMINATION != 'sats' %}
<q-input filled dense v-model.number="receive.data.amount" label="Amount ({{LNBITS_DENOMINATION}}) *"
mask="#.##" fill-mask="0" reverse-fill-mask :min="receive.minMax[0]" :max="receive.minMax[1]"
:readonly="receive.lnurl && receive.lnurl.fixed"></q-input>
<q-input
filled
dense
v-model.number="receive.data.amount"
label="Amount ({{LNBITS_DENOMINATION}}) *"
mask="#.##"
fill-mask="0"
reverse-fill-mask
:min="receive.minMax[0]"
:max="receive.minMax[1]"
:readonly="receive.lnurl && receive.lnurl.fixed"
></q-input>
{% else %}
<q-select filled dense v-model="receive.unit" type="text" label="Unit" :options="receive.units"></q-select>
<q-input ref="setAmount" filled dense v-model.number="receive.data.amount"
:label="'Amount (' + receive.unit + ') *'" :mask="receive.unit != 'sat' ? '#.##' : '#'" fill-mask="0"
reverse-fill-mask :step="receive.unit != 'sat' ? '0.01' : '1'" :min="receive.minMax[0]"
:max="receive.minMax[1]" :readonly="receive.lnurl && receive.lnurl.fixed"></q-input>
<q-select
filled
dense
v-model="receive.unit"
type="text"
label="Unit"
:options="receive.units"
></q-select>
<q-input
ref="setAmount"
filled
dense
v-model.number="receive.data.amount"
:label="'Amount (' + receive.unit + ') *'"
:mask="receive.unit != 'sat' ? '#.##' : '#'"
fill-mask="0"
reverse-fill-mask
:step="receive.unit != 'sat' ? '0.01' : '1'"
:min="receive.minMax[0]"
:max="receive.minMax[1]"
:readonly="receive.lnurl && receive.lnurl.fixed"
></q-input>
{% endif %}
<q-input filled dense v-model.trim="receive.data.memo" label="Memo"></q-input>
<q-input
filled
dense
v-model.trim="receive.data.memo"
label="Memo"
></q-input>
{% raw %}
<div v-if="receive.status == 'pending'" class="row q-mt-lg">
<q-btn unelevated color="primary" :disable="receive.data.amount == null || receive.data.amount <= 0"
type="submit">
<q-btn
unelevated
color="primary"
:disable="receive.data.amount == null || receive.data.amount <= 0"
type="submit"
>
<span v-if="receive.lnurl">
Withdraw from {{receive.lnurl.domain}}
</span>
<span v-else> Create invoice </span>
</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
<q-spinner v-if="receive.status == 'loading'" color="primary" size="2.55em"></q-spinner>
<q-spinner
v-if="receive.status == 'loading'"
color="primary"
size="2.55em"
></q-spinner>
</q-form>
</q-card>
<q-card v-else class="q-pa-lg q-pt-xl lnbits__dialog-card">
<div class="text-center q-mb-lg">
<a :href="'lightning:' + receive.paymentReq">
<q-responsive :ratio="1" class="q-mx-xl">
<qrcode :value="receive.paymentReq" :options="{width: 340}" class="rounded-borders"></qrcode>
<qrcode
:value="receive.paymentReq"
:options="{width: 340}"
class="rounded-borders"
></qrcode>
</q-responsive>
</a>
</div>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText(receive.paymentReq)">Copy invoice</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
<q-btn outline color="grey" @click="copyText(receive.paymentReq)"
>Copy invoice</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Close</q-btn
>
</div>
</q-card>
{% endraw %}
@ -217,11 +393,17 @@
{% endraw %}
<div v-if="canPay" class="row q-mt-lg">
<q-btn unelevated color="primary" @click="payInvoice">Pay</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
<div v-else class="row q-mt-lg">
<q-btn unelevated disabled color="yellow" text-color="black">Not enough funds!</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
<q-btn unelevated disabled color="yellow" text-color="black"
>Not enough funds!</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</div>
<div v-else-if="parse.lnurlauth">
@ -243,23 +425,46 @@
</p>
<div class="row q-mt-lg">
<q-btn unelevated color="primary" type="submit">Login</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
{% endraw %}
</div>
<div v-else>
<q-form v-if="!parse.camera.show" @submit="decodeRequest" class="q-gutter-md">
<q-input filled dense v-model.trim="parse.data.request" type="textarea" label="Paste coins">
<q-form
v-if="!parse.camera.show"
@submit="decodeRequest"
class="q-gutter-md"
>
<q-input
filled
dense
v-model.trim="parse.data.request"
type="textarea"
label="Paste coins"
>
</q-input>
<div class="row q-mt-lg">
<q-btn unelevated color="primary" :disable="parse.data.request == ''" type="submit">Read</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
<q-btn
unelevated
color="primary"
:disable="parse.data.request == ''"
type="submit"
>Read</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-form>
<div v-else>
<q-responsive :ratio="1">
<qrcode-stream @decode="decodeQR" class="rounded-borders"></qrcode-stream>
<qrcode-stream
@decode="decodeQR"
class="rounded-borders"
></qrcode-stream>
</q-responsive>
<div class="row q-mt-lg">
<q-btn @click="closeCamera" flat color="grey" class="q-ml-auto">
@ -274,10 +479,15 @@
<q-dialog v-model="parse.camera.show">
<q-card class="q-pa-lg q-pt-xl">
<div class="text-center q-mb-lg">
<qrcode-stream @decode="decodeQR" class="rounded-borders"></qrcode-stream>
<qrcode-stream
@decode="decodeQR"
class="rounded-borders"
></qrcode-stream>
</div>
<div class="row q-mt-lg">
<q-btn @click="closeCamera" flat color="grey" class="q-ml-auto">Cancel</q-btn>
<q-btn @click="closeCamera" flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div>
</q-card>
</q-dialog>
@ -289,12 +499,19 @@
</q-card-section>
</q-card>
</q-dialog>
<q-tabs class="lt-md fixed-bottom left-0 right-0 bg-primary text-white shadow-2 z-top" active-class="px-0"
indicator-color="transparent">
<q-tabs
class="lt-md fixed-bottom left-0 right-0 bg-primary text-white shadow-2 z-top"
active-class="px-0"
indicator-color="transparent"
>
<q-tab icon="arrow_downward" label="receive" @click="showParseDialog">
</q-tab>
<q-tab icon="arrow_upward" label="Send" @click="showSendDialog"></q-tab>
<q-tab icon="sync_alt" label="Peg in/out" @click="showSendDialog"></q-tab>
<q-tab
icon="sync_alt"
label="Peg in/out"
@click="showSendDialog"
></q-tab>
<q-tab icon="photo_camera" label="Scan" @click="showCamera"> </q-tab>
</q-tabs>
@ -302,8 +519,10 @@
<q-card class="q-pa-lg">
<h6 class="q-my-md text-primary">Warning</h6>
<p>
<strong>BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots
and "Save to homescreen"/"Install app"</strong>!
<strong
>BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots
and "Save to homescreen"/"Install app"</strong
>!
</p>
<p>
Ecash is a bearer asset, meaning you have the funds saved on this
@ -311,8 +530,15 @@
lose the funds.
</p>
<div class="row q-mt-lg">
<q-btn outline color="grey" @click="copyText(disclaimerDialog.location.href)">Copy wallet URL</q-btn>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">I understand</q-btn>
<q-btn
outline
color="grey"
@click="copyText(disclaimerDialog.location.href)"
>Copy wallet URL</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>I understand</q-btn
>
</div>
</q-card>
</q-dialog>
@ -357,9 +583,9 @@
mixins: [windowMixin],
data: function () {
return {
balanceAmount:"",
tickershort:"",
name:"",
balanceAmount: '',
tickershort: '',
name: '',
receive: {
show: false,
status: 'pending',
@ -564,7 +790,6 @@
setTimeout(() => {
clearInterval(this.receive.paymentChecker)
}, 40000)
})
.catch(err => {
LNbits.utils.notifyApiError(err)
@ -613,7 +838,9 @@
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)
fsat: LNbits.utils.formatSat(
invoice.human_readable_part.amount / 1000
)
}
_.each(invoice.data.tags, tag => {
@ -666,9 +893,7 @@
fetchPayments: function () {
return
},
fetchBalance: function () {
},
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
@ -702,23 +927,22 @@
})
},
created: function () {
let params = (new URL(document.location)).searchParams
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')){
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', 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
}
@ -727,11 +951,9 @@
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')){
} else if (this.$q.localStorage.getItem('cashu.mint')) {
this.mint = this.$q.localStorage.getItem('cashu.mint')
}
else{
} else {
this.$q.notify({
color: 'red',
message: 'No mint set!'
@ -742,11 +964,9 @@
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')){
} else if (this.$q.localStorage.getItem('cashu.name')) {
this.name = this.$q.localStorage.getItem('cashu.name')
}
}
})
</script>

View File

@ -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})
@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):

View File

@ -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,
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,
Invoice,
MeltPayload,
MintPayloads,
PayLnurlWData,
Pegs,
SplitPayload,
PayLnurlWData
)
########################################
@ -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,8 +203,11 @@ 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)):
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:
@ -217,7 +220,7 @@ async def keys(cashu_id: str = Query(False), wallet: WalletTypeInfo = Depends(ge
@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(
@ -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))