mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-24 22:58:46 +01:00
lnbits migrations work
This commit is contained in:
parent
7111639446
commit
c36f7a48aa
8 changed files with 358 additions and 158 deletions
|
@ -9,7 +9,26 @@ from lnbits.tasks import catch_everything_and_restart
|
||||||
|
|
||||||
db = Database("ext_cashu")
|
db = Database("ext_cashu")
|
||||||
|
|
||||||
cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["cashu"])
|
import sys
|
||||||
|
|
||||||
|
sys.path.append("/Users/cc/git/cashu")
|
||||||
|
from cashu.mint.ledger import Ledger
|
||||||
|
from .crud import LedgerCrud
|
||||||
|
|
||||||
|
# db = Database("ext_cashu", LNBITS_DATA_FOLDER)
|
||||||
|
|
||||||
|
ledger = Ledger(
|
||||||
|
db=db,
|
||||||
|
# seed=MINT_PRIVATE_KEY,
|
||||||
|
seed="asd",
|
||||||
|
derivation_path="0/0/0/1",
|
||||||
|
crud=LedgerCrud,
|
||||||
|
)
|
||||||
|
|
||||||
|
cashu_ext: APIRouter = APIRouter(prefix="/api/v1/cashu", tags=["cashu"])
|
||||||
|
# from cashu.mint.router import router as cashu_router
|
||||||
|
|
||||||
|
# cashu_ext.include_router(router=cashu_router)
|
||||||
|
|
||||||
cashu_static_files = [
|
cashu_static_files = [
|
||||||
{
|
{
|
||||||
|
@ -24,11 +43,12 @@ def cashu_renderer():
|
||||||
return template_renderer(["lnbits/extensions/cashu/templates"])
|
return template_renderer(["lnbits/extensions/cashu/templates"])
|
||||||
|
|
||||||
|
|
||||||
from .tasks import wait_for_paid_invoices
|
from .tasks import wait_for_paid_invoices, startup_cashu_mint
|
||||||
from .views import * # noqa
|
from .views import * # noqa
|
||||||
from .views_api import * # noqa
|
from .views_api import * # noqa
|
||||||
|
|
||||||
|
|
||||||
def cashu_start():
|
def cashu_start():
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.create_task(catch_everything_and_restart(startup_cashu_mint))
|
||||||
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
|
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
{
|
{
|
||||||
"name": "Cashu Ecash",
|
"name": "Cashu Ecash",
|
||||||
"short_description": "Ecash mints with LN peg in/out",
|
"short_description": "Ecash mint and wallet",
|
||||||
"icon": "approval",
|
"icon": "approval",
|
||||||
"contributors": ["arcbtc", "calle", "vlad"],
|
"contributors": ["arcbtc", "calle", "vlad"],
|
||||||
"hidden": false
|
"hidden": false,
|
||||||
|
"migration_module": "cashu.mint.migrations",
|
||||||
|
"db_name": "cashu"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import time
|
||||||
from binascii import hexlify, unhexlify
|
from binascii import hexlify, unhexlify
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union, Any
|
||||||
|
|
||||||
from embit import bip32, bip39, ec, script
|
from embit import bip32, bip39, ec, script
|
||||||
from embit.networks import NETWORKS
|
from embit.networks import NETWORKS
|
||||||
|
@ -13,6 +14,49 @@ from . import db
|
||||||
from .core.base import Invoice
|
from .core.base import Invoice
|
||||||
from .models import Cashu, Pegs, Promises, Proof
|
from .models import Cashu, Pegs, Promises, Proof
|
||||||
|
|
||||||
|
from cashu.core.base import MintKeyset
|
||||||
|
from lnbits.db import Database, Connection
|
||||||
|
|
||||||
|
|
||||||
|
class LedgerCrud:
|
||||||
|
"""
|
||||||
|
Database interface for Cashu mint.
|
||||||
|
|
||||||
|
This class needs to be overloaded by any app that imports the Cashu mint.
|
||||||
|
"""
|
||||||
|
|
||||||
|
async def get_keyset(*args, **kwags):
|
||||||
|
|
||||||
|
return await get_keyset(*args, **kwags)
|
||||||
|
|
||||||
|
async def get_lightning_invoice(*args, **kwags):
|
||||||
|
|
||||||
|
return await get_lightning_invoice(*args, **kwags)
|
||||||
|
|
||||||
|
async def get_proofs_used(*args, **kwags):
|
||||||
|
|
||||||
|
return await get_proofs_used(*args, **kwags)
|
||||||
|
|
||||||
|
async def invalidate_proof(*args, **kwags):
|
||||||
|
|
||||||
|
return await invalidate_proof(*args, **kwags)
|
||||||
|
|
||||||
|
async def store_keyset(*args, **kwags):
|
||||||
|
|
||||||
|
return await store_keyset(*args, **kwags)
|
||||||
|
|
||||||
|
async def store_lightning_invoice(*args, **kwags):
|
||||||
|
|
||||||
|
return await store_lightning_invoice(*args, **kwags)
|
||||||
|
|
||||||
|
async def store_promise(*args, **kwags):
|
||||||
|
|
||||||
|
return await store_promise(*args, **kwags)
|
||||||
|
|
||||||
|
async def update_lightning_invoice(*args, **kwags):
|
||||||
|
|
||||||
|
return await update_lightning_invoice(*args, **kwags)
|
||||||
|
|
||||||
|
|
||||||
async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
|
async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
|
||||||
cashu_id = urlsafe_short_hash()
|
cashu_id = urlsafe_short_hash()
|
||||||
|
@ -120,9 +164,15 @@ async def get_promises(cashu_id) -> Optional[Cashu]:
|
||||||
return Promises(**row) if row else None
|
return Promises(**row) if row else None
|
||||||
|
|
||||||
|
|
||||||
async def get_proofs_used(cashu_id):
|
async def get_proofs_used(
|
||||||
rows = await db.fetchall(
|
db: Database,
|
||||||
"SELECT secret from cashu.proofs_used WHERE cashu_id = ?", (cashu_id,)
|
conn: Optional[Connection] = None,
|
||||||
|
):
|
||||||
|
|
||||||
|
rows = await (conn or db).fetchall(
|
||||||
|
"""
|
||||||
|
SELECT secret from cashu.proofs_used
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
return [row[0] for row in rows]
|
return [row[0] for row in rows]
|
||||||
|
|
||||||
|
@ -184,3 +234,62 @@ async def update_lightning_invoice(cashu_id: str, hash: str, issued: bool):
|
||||||
hash,
|
hash,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
##############################
|
||||||
|
######### KEYSETS ############
|
||||||
|
##############################
|
||||||
|
|
||||||
|
|
||||||
|
async def store_keyset(
|
||||||
|
keyset: MintKeyset,
|
||||||
|
db: Database = None,
|
||||||
|
conn: Optional[Connection] = None,
|
||||||
|
):
|
||||||
|
|
||||||
|
await (conn or db).execute( # type: ignore
|
||||||
|
"""
|
||||||
|
INSERT INTO cashu.keysets
|
||||||
|
(id, derivation_path, valid_from, valid_to, first_seen, active, version)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
keyset.id,
|
||||||
|
keyset.derivation_path,
|
||||||
|
keyset.valid_from or db.timestamp_now,
|
||||||
|
keyset.valid_to or db.timestamp_now,
|
||||||
|
keyset.first_seen or db.timestamp_now,
|
||||||
|
True,
|
||||||
|
keyset.version,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_keyset(
|
||||||
|
id: str = None,
|
||||||
|
derivation_path: str = "",
|
||||||
|
db: Database = None,
|
||||||
|
conn: Optional[Connection] = None,
|
||||||
|
):
|
||||||
|
clauses = []
|
||||||
|
values: List[Any] = []
|
||||||
|
clauses.append("active = ?")
|
||||||
|
values.append(True)
|
||||||
|
if id:
|
||||||
|
clauses.append("id = ?")
|
||||||
|
values.append(id)
|
||||||
|
if derivation_path:
|
||||||
|
clauses.append("derivation_path = ?")
|
||||||
|
values.append(derivation_path)
|
||||||
|
where = ""
|
||||||
|
if clauses:
|
||||||
|
where = f"WHERE {' AND '.join(clauses)}"
|
||||||
|
|
||||||
|
rows = await (conn or db).fetchall( # type: ignore
|
||||||
|
f"""
|
||||||
|
SELECT * from cashu.keysets
|
||||||
|
{where}
|
||||||
|
""",
|
||||||
|
tuple(values),
|
||||||
|
)
|
||||||
|
return [MintKeyset.from_row(row) for row in rows]
|
||||||
|
|
|
@ -1,79 +1 @@
|
||||||
async def m001_initial(db):
|
# this extension will use the migration_module module cashu.mint.migrations (see config.json)
|
||||||
"""
|
|
||||||
Initial cashu table.
|
|
||||||
"""
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE cashu.cashu (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
wallet TEXT NOT NULL,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
tickershort TEXT DEFAULT 'sats',
|
|
||||||
fraction BOOL,
|
|
||||||
maxsats INT,
|
|
||||||
coins INT,
|
|
||||||
prvkey TEXT NOT NULL,
|
|
||||||
pubkey TEXT NOT NULL
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Initial cashus table.
|
|
||||||
"""
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE cashu.pegs (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
wallet TEXT NOT NULL,
|
|
||||||
inout BOOL NOT NULL,
|
|
||||||
amount INT
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Initial cashus table.
|
|
||||||
"""
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE cashu.promises (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
amount INT,
|
|
||||||
B_b TEXT NOT NULL,
|
|
||||||
C_b TEXT NOT NULL,
|
|
||||||
cashu_id TEXT NOT NULL,
|
|
||||||
UNIQUE (B_b)
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
"""
|
|
||||||
Initial cashus table.
|
|
||||||
"""
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE cashu.proofs_used (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
amount INT,
|
|
||||||
C TEXT NOT NULL,
|
|
||||||
secret TEXT NOT NULL,
|
|
||||||
cashu_id TEXT NOT NULL
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE IF NOT EXISTS cashu.invoices (
|
|
||||||
cashu_id TEXT NOT NULL,
|
|
||||||
amount INTEGER NOT NULL,
|
|
||||||
pr TEXT NOT NULL,
|
|
||||||
hash TEXT NOT NULL,
|
|
||||||
issued BOOL NOT NULL,
|
|
||||||
|
|
||||||
UNIQUE (hash)
|
|
||||||
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
|
@ -9,6 +9,21 @@ from lnbits.tasks import internal_invoice_queue, register_invoice_listener
|
||||||
|
|
||||||
from .crud import get_cashu
|
from .crud import get_cashu
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.append("/Users/cc/git/cashu")
|
||||||
|
# from cashu.mint import migrations
|
||||||
|
# from cashu.core.migrations import migrate_databases
|
||||||
|
from . import db, ledger
|
||||||
|
|
||||||
|
|
||||||
|
async def startup_cashu_mint():
|
||||||
|
# await migrate_databases(db, migrations)
|
||||||
|
await ledger.load_used_proofs()
|
||||||
|
await ledger.init_keysets()
|
||||||
|
print(ledger.get_keyset())
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
async def wait_for_paid_invoices():
|
||||||
invoice_queue = asyncio.Queue()
|
invoice_queue = asyncio.Queue()
|
||||||
|
|
|
@ -120,7 +120,7 @@
|
||||||
emit-value
|
emit-value
|
||||||
v-model="formDialog.data.wallet"
|
v-model="formDialog.data.wallet"
|
||||||
:options="g.user.walletOptions"
|
:options="g.user.walletOptions"
|
||||||
label="Wallet *"
|
label="Cashu wallet *"
|
||||||
></q-select>
|
></q-select>
|
||||||
<q-toggle
|
<q-toggle
|
||||||
v-model="toggleAdvanced"
|
v-model="toggleAdvanced"
|
||||||
|
@ -156,7 +156,7 @@
|
||||||
dense
|
dense
|
||||||
v-model.trim="formDialog.data.tickershort"
|
v-model.trim="formDialog.data.tickershort"
|
||||||
label="Ticker shorthand"
|
label="Ticker shorthand"
|
||||||
placeholder="CC"
|
placeholder="sats"
|
||||||
#
|
#
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
|
@ -229,7 +229,7 @@
|
||||||
{
|
{
|
||||||
name: 'wallet',
|
name: 'wallet',
|
||||||
align: 'left',
|
align: 'left',
|
||||||
label: 'Wallet',
|
label: 'Cashu wallet',
|
||||||
field: 'wallet'
|
field: 'wallet'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Wallet
|
{% extends "public.html" %} {% block toolbar_title %} {% raw %} {{name}} Cashu
|
||||||
{% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
|
wallet {% endraw %} {% endblock %} {% block footer %}{% endblock %} {% block
|
||||||
page_container %}
|
page_container %}
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
<q-page>
|
<q-page>
|
||||||
|
@ -14,9 +14,8 @@ page_container %}
|
||||||
rounded
|
rounded
|
||||||
color="secondary"
|
color="secondary"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
@click="showBuyTokensDialog"
|
@click="showInvoicesDialog"
|
||||||
>Buy tokens
|
>Create invoice
|
||||||
<h5 class="text-caption q-ml-sm q-mb-none">(with sats)</h5>
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6">
|
<div class="col-6">
|
||||||
|
@ -34,8 +33,7 @@ page_container %}
|
||||||
rounded
|
rounded
|
||||||
color="secondary"
|
color="secondary"
|
||||||
class="full-width"
|
class="full-width"
|
||||||
>Sell tokens
|
>Pay invoice
|
||||||
<h5 class="text-caption q-ml-sm q-mb-none">(for sats)</h5>
|
|
||||||
</q-btn>
|
</q-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -115,11 +113,11 @@ page_container %}
|
||||||
<q-table
|
<q-table
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
:data="buyOrders"
|
:data="invoicesCashu"
|
||||||
:columns="buysTable.columns"
|
:columns="invoicesTable.columns"
|
||||||
:pagination.sync="buysTable.pagination"
|
:pagination.sync="invoicesTable.pagination"
|
||||||
no-data-label="No buys made yet"
|
no-data-label="No invoices made yet"
|
||||||
:filter="buysTable.filter"
|
:filter="invoicesTable.filter"
|
||||||
>
|
>
|
||||||
{% raw %}
|
{% raw %}
|
||||||
<template v-slot:body="props">
|
<template v-slot:body="props">
|
||||||
|
@ -137,7 +135,7 @@ page_container %}
|
||||||
size="lg"
|
size="lg"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
class="q-mr-md cursor-pointer"
|
class="q-mr-md cursor-pointer"
|
||||||
@click="recheckBuyOrder(props.row.hash)"
|
@click="recheckInvoice(props.row.hash)"
|
||||||
>
|
>
|
||||||
Recheck
|
Recheck
|
||||||
</q-badge>
|
</q-badge>
|
||||||
|
@ -182,11 +180,15 @@ page_container %}
|
||||||
active-class="px-0"
|
active-class="px-0"
|
||||||
indicator-color="transparent"
|
indicator-color="transparent"
|
||||||
>
|
>
|
||||||
<q-tab icon="arrow_right" label="Buy" @click="showBuyTokensDialog">
|
<q-tab
|
||||||
|
icon="arrow_right"
|
||||||
|
label="Create Invoice"
|
||||||
|
@click="showInvoicesDialog"
|
||||||
|
>
|
||||||
</q-tab>
|
</q-tab>
|
||||||
<q-tab icon="arrow_downward" label="Receive"></q-tab>
|
<q-tab icon="arrow_downward" label="Receive Tokens"></q-tab>
|
||||||
<q-tab icon="arrow_upward" label="Send"></q-tab>
|
<q-tab icon="arrow_upward" label="Send Token"></q-tab>
|
||||||
<q-tab icon="arrow_right" label="Sell"> </q-tab>
|
<q-tab icon="arrow_right" label="Pay Invoice"> </q-tab>
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
|
|
||||||
<q-dialog v-model="disclaimerDialog.show">
|
<q-dialog v-model="disclaimerDialog.show">
|
||||||
|
@ -214,7 +216,7 @@ page_container %}
|
||||||
|
|
||||||
<q-dialog v-model="showInvoiceDetails" position="top">
|
<q-dialog v-model="showInvoiceDetails" position="top">
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||||
<div v-if="!buyData.bolt11">
|
<div v-if="!invoiceData.bolt11">
|
||||||
<div class="row items-center no-wrap q-mb-sm">
|
<div class="row items-center no-wrap q-mb-sm">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<span class="text-subtitle1"
|
<span class="text-subtitle1"
|
||||||
|
@ -225,7 +227,7 @@ page_container %}
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model.number="buyData.amount"
|
v-model.number="invoiceData.amount"
|
||||||
label="Amount (sats) *"
|
label="Amount (sats) *"
|
||||||
type="number"
|
type="number"
|
||||||
class="q-mb-lg"
|
class="q-mb-lg"
|
||||||
|
@ -234,15 +236,15 @@ page_container %}
|
||||||
<q-input
|
<q-input
|
||||||
filled
|
filled
|
||||||
dense
|
dense
|
||||||
v-model.trim="buyData.memo"
|
v-model.trim="invoiceData.memo"
|
||||||
label="Memo"
|
label="Memo"
|
||||||
></q-input>
|
></q-input>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="text-center q-mb-lg">
|
<div v-else class="text-center q-mb-lg">
|
||||||
<a :href="'lightning:' + buyData.bolt11">
|
<a :href="'lightning:' + invoiceData.bolt11">
|
||||||
<q-responsive :ratio="1" class="q-mx-xl">
|
<q-responsive :ratio="1" class="q-mx-xl">
|
||||||
<qrcode
|
<qrcode
|
||||||
:value="buyData.bolt11"
|
:value="invoiceData.bolt11"
|
||||||
:options="{width: 340}"
|
:options="{width: 340}"
|
||||||
class="rounded-borders"
|
class="rounded-borders"
|
||||||
>
|
>
|
||||||
|
@ -252,8 +254,8 @@ page_container %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-mt-lg">
|
<div class="row q-mt-lg">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="buyData.bolt11"
|
v-if="invoiceData.bolt11"
|
||||||
@click="copyText(buyData.bolt11)"
|
@click="copyText(invoiceData.bolt11)"
|
||||||
outline
|
outline
|
||||||
color="grey"
|
color="grey"
|
||||||
>Copy invoice</q-btn
|
>Copy invoice</q-btn
|
||||||
|
@ -449,8 +451,8 @@ page_container %}
|
||||||
mintId: '',
|
mintId: '',
|
||||||
mintName: '',
|
mintName: '',
|
||||||
keys: '',
|
keys: '',
|
||||||
buyOrders: [],
|
invoicesCashu: [],
|
||||||
buyData: {
|
invoiceData: {
|
||||||
amount: 0,
|
amount: 0,
|
||||||
memo: '',
|
memo: '',
|
||||||
bolt11: '',
|
bolt11: '',
|
||||||
|
@ -507,7 +509,7 @@ page_container %}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
payments: [],
|
payments: [],
|
||||||
buysTable: {
|
invoicesTable: {
|
||||||
columns: [
|
columns: [
|
||||||
{
|
{
|
||||||
name: 'status',
|
name: 'status',
|
||||||
|
@ -873,17 +875,17 @@ page_container %}
|
||||||
},
|
},
|
||||||
|
|
||||||
/////////////////////////////////// WALLET ///////////////////////////////////
|
/////////////////////////////////// WALLET ///////////////////////////////////
|
||||||
showBuyTokensDialog: async function () {
|
showInvoicesDialog: async function () {
|
||||||
console.log('##### showBuyTokensDialog')
|
console.log('##### showInvoicesDialog')
|
||||||
this.buyData.amount = 0
|
this.invoiceData.amount = 0
|
||||||
this.buyData.bolt11 = ''
|
this.invoiceData.bolt11 = ''
|
||||||
this.buyData.hash = ''
|
this.invoiceData.hash = ''
|
||||||
this.buyData.memo = ''
|
this.invoiceData.memo = ''
|
||||||
this.showInvoiceDetails = true
|
this.showInvoiceDetails = true
|
||||||
},
|
},
|
||||||
|
|
||||||
showInvoiceDialog: function (data) {
|
showInvoiceDialog: function (data) {
|
||||||
this.buyData = _.clone(data)
|
this.invoiceData = _.clone(data)
|
||||||
this.showInvoiceDetails = true
|
this.showInvoiceDetails = true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -911,20 +913,20 @@ page_container %}
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'GET',
|
'GET',
|
||||||
`/cashu/api/v1/cashu/${this.mintId}/mint?amount=${this.buyData.amount}`
|
`/cashu/api/v1/cashu/${this.mintId}/mint?amount=${this.invoiceData.amount}`
|
||||||
)
|
)
|
||||||
console.log('### data', data)
|
console.log('### data', data)
|
||||||
|
|
||||||
this.buyData.bolt11 = data.pr
|
this.invoiceData.bolt11 = data.pr
|
||||||
this.buyData.hash = data.hash
|
this.invoiceData.hash = data.hash
|
||||||
this.buyOrders.push({
|
this.invoicesCashu.push({
|
||||||
..._.clone(this.buyData),
|
..._.clone(this.invoiceData),
|
||||||
date: currentDateStr(),
|
date: currentDateStr(),
|
||||||
status: 'pending'
|
status: 'pending'
|
||||||
})
|
})
|
||||||
this.storeBuyOrders()
|
this.storeinvoicesCashu()
|
||||||
const amounts = splitAmount(this.buyData.amount)
|
const amounts = splitAmount(this.invoiceData.amount)
|
||||||
await this.requestTokens(amounts, this.buyData.hash)
|
await this.requestTokens(amounts, this.invoiceData.hash)
|
||||||
this.tab = 'orders'
|
this.tab = 'orders'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -933,12 +935,12 @@ page_container %}
|
||||||
},
|
},
|
||||||
|
|
||||||
checkXXXXXX: async function () {
|
checkXXXXXX: async function () {
|
||||||
for (const tokenBuy of this.buyOrders) {
|
for (const invoice of this.invoicesCashu) {
|
||||||
if (tokenBuy.status === 'pending') {
|
if (invoice.status === 'pending') {
|
||||||
try {
|
try {
|
||||||
const {data} = await LNbits.api.request(
|
const {data} = await LNbits.api.request(
|
||||||
'POST',
|
'POST',
|
||||||
`/cashu/api/v1/cashu/${this.mintId}/mint?payment_hash=${tokenBuy.hash}`,
|
`/cashu/api/v1/cashu/${this.mintId}/mint?payment_hash=${invoice.hash}`,
|
||||||
'',
|
'',
|
||||||
{
|
{
|
||||||
blinded_messages: []
|
blinded_messages: []
|
||||||
|
@ -953,10 +955,10 @@ page_container %}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
recheckBuyOrder: async function (hash) {
|
recheckInvoice: async function (hash) {
|
||||||
console.log('### recheckBuyOrder.hash', hash)
|
console.log('### recheckInvoice.hash', hash)
|
||||||
const tokens = this.tokens.find(bt => bt.hash === hash)
|
const tokens = this.tokens.find(bt => bt.hash === hash)
|
||||||
console.log('### recheckBuyOrder.tokens', tokens)
|
console.log('### recheckInvoice.tokens', tokens)
|
||||||
if (!tokens) {
|
if (!tokens) {
|
||||||
console.error('####### no token for hash', hash)
|
console.error('####### no token for hash', hash)
|
||||||
return
|
return
|
||||||
|
@ -970,9 +972,9 @@ page_container %}
|
||||||
tokens.status = 'paid'
|
tokens.status = 'paid'
|
||||||
this.storeTokens()
|
this.storeTokens()
|
||||||
|
|
||||||
const buyOrder = this.buyOrders.find(bo => bo.hash === hash)
|
const invoice = this.invoicesCashu.find(bo => bo.hash === hash)
|
||||||
buyOrder.status = 'paid'
|
invoice.status = 'paid'
|
||||||
this.storeBuyOrders()
|
this.storeinvoicesCashu()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1261,8 +1263,11 @@ page_container %}
|
||||||
localStorage.setItem('cashu.keys', JSON.stringify(data))
|
localStorage.setItem('cashu.keys', JSON.stringify(data))
|
||||||
},
|
},
|
||||||
|
|
||||||
storeBuyOrders: function () {
|
storeinvoicesCashu: function () {
|
||||||
localStorage.setItem('cashu.buyOrders', JSON.stringify(this.buyOrders))
|
localStorage.setItem(
|
||||||
|
'cashu.invoicesCashu',
|
||||||
|
JSON.stringify(this.invoicesCashu)
|
||||||
|
)
|
||||||
},
|
},
|
||||||
storeTokens: function () {
|
storeTokens: function () {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
|
@ -1285,8 +1290,8 @@ page_container %}
|
||||||
!params.get('tsh') &&
|
!params.get('tsh') &&
|
||||||
!this.$q.localStorage.getItem('cashu.tickershort')
|
!this.$q.localStorage.getItem('cashu.tickershort')
|
||||||
) {
|
) {
|
||||||
this.$q.localStorage.set('cashu.tickershort', 'CE')
|
this.$q.localStorage.set('cashu.tickershort', 'sats')
|
||||||
this.tickershort = 'CE'
|
this.tickershort = 'sats'
|
||||||
} else if (params.get('tsh')) {
|
} else if (params.get('tsh')) {
|
||||||
this.$q.localStorage.set('cashu.tickershort', params.get('tsh'))
|
this.$q.localStorage.set('cashu.tickershort', params.get('tsh'))
|
||||||
this.tickershort = params.get('tsh')
|
this.tickershort = params.get('tsh')
|
||||||
|
@ -1322,11 +1327,11 @@ page_container %}
|
||||||
this.keys = JSON.parse(keysJson)
|
this.keys = JSON.parse(keysJson)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.buyOrders = JSON.parse(
|
this.invoicesCashu = JSON.parse(
|
||||||
localStorage.getItem('cashu.buyOrders') || '[]'
|
localStorage.getItem('cashu.invoicesCashu') || '[]'
|
||||||
)
|
)
|
||||||
this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
|
this.tokens = JSON.parse(localStorage.getItem('cashu.tokens') || '[]')
|
||||||
console.log('### buyOrders', this.buyOrders)
|
console.log('### invoicesCashu', this.invoicesCashu)
|
||||||
console.table('### tokens', this.tokens)
|
console.table('### tokens', this.tokens)
|
||||||
console.log('#### this.mintId', this.mintId)
|
console.log('#### this.mintId', this.mintId)
|
||||||
console.log('#### this.mintName', this.mintName)
|
console.log('#### this.mintName', this.mintName)
|
||||||
|
|
|
@ -28,7 +28,8 @@ from .crud import (
|
||||||
store_promise,
|
store_promise,
|
||||||
update_lightning_invoice,
|
update_lightning_invoice,
|
||||||
)
|
)
|
||||||
from .ledger import mint, request_mint
|
|
||||||
|
# from .ledger import mint, request_mint
|
||||||
from .mint import generate_promises, get_pubkeys, melt, split
|
from .mint import generate_promises, get_pubkeys, melt, split
|
||||||
from .models import (
|
from .models import (
|
||||||
Cashu,
|
Cashu,
|
||||||
|
@ -207,15 +208,15 @@ async def api_cashu_check_invoice(cashu_id: str, payment_hash: str):
|
||||||
########################################
|
########################################
|
||||||
|
|
||||||
|
|
||||||
@cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
|
# @cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
|
||||||
async def keys(cashu_id: str = Query(False)):
|
# async def keys(cashu_id: str = Query(False)):
|
||||||
"""Get the public keys of the mint"""
|
# """Get the public keys of the mint"""
|
||||||
mint = await get_cashu(cashu_id)
|
# mint = await get_cashu(cashu_id)
|
||||||
if mint is None:
|
# if mint is None:
|
||||||
raise HTTPException(
|
# 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)
|
# return get_pubkeys(mint.prvkey)
|
||||||
|
|
||||||
|
|
||||||
@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
|
@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
|
||||||
|
@ -355,3 +356,129 @@ async def split_proofs(payload: SplitRequest, cashu_id: str = Query(None)):
|
||||||
resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
|
resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
|
||||||
print("### resp", json.dumps(resp, default=vars))
|
print("### resp", json.dumps(resp, default=vars))
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
##################################################################
|
||||||
|
##################################################################
|
||||||
|
# CASHU LIB
|
||||||
|
##################################################################
|
||||||
|
|
||||||
|
from typing import Dict, List, Union
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
from secp256k1 import PublicKey
|
||||||
|
|
||||||
|
from cashu.core.base import (
|
||||||
|
BlindedSignature,
|
||||||
|
CheckFeesRequest,
|
||||||
|
CheckFeesResponse,
|
||||||
|
CheckRequest,
|
||||||
|
GetMeltResponse,
|
||||||
|
GetMintResponse,
|
||||||
|
MeltRequest,
|
||||||
|
MintRequest,
|
||||||
|
PostSplitResponse,
|
||||||
|
SplitRequest,
|
||||||
|
)
|
||||||
|
from cashu.core.errors import CashuError
|
||||||
|
|
||||||
|
from . import db, ledger
|
||||||
|
|
||||||
|
|
||||||
|
@cashu_ext.get("/keys")
|
||||||
|
async def keys() -> dict[int, str]:
|
||||||
|
"""Get the public keys of the mint"""
|
||||||
|
return ledger.get_keyset()
|
||||||
|
|
||||||
|
|
||||||
|
@cashu_ext.get("/keysets")
|
||||||
|
async def keysets() -> dict[str, list[str]]:
|
||||||
|
"""Get all active keysets of the mint"""
|
||||||
|
return {"keysets": await ledger.keysets.get_ids()}
|
||||||
|
|
||||||
|
|
||||||
|
@cashu_ext.get("/mint")
|
||||||
|
async def request_mint(amount: int = 0) -> GetMintResponse:
|
||||||
|
"""
|
||||||
|
Request minting of new tokens. The mint responds with a Lightning invoice.
|
||||||
|
This endpoint can be used for a Lightning invoice UX flow.
|
||||||
|
|
||||||
|
Call `POST /mint` after paying the invoice.
|
||||||
|
"""
|
||||||
|
payment_request, payment_hash = await ledger.request_mint(amount)
|
||||||
|
print(f"Lightning invoice: {payment_request}")
|
||||||
|
resp = GetMintResponse(pr=payment_request, hash=payment_hash)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@cashu_ext.post("/mint")
|
||||||
|
async def mint(
|
||||||
|
payloads: MintRequest,
|
||||||
|
payment_hash: Union[str, None] = None,
|
||||||
|
) -> Union[List[BlindedSignature], CashuError]:
|
||||||
|
"""
|
||||||
|
Requests the minting of tokens belonging to a paid payment request.
|
||||||
|
|
||||||
|
Call this endpoint after `GET /mint`.
|
||||||
|
"""
|
||||||
|
amounts = []
|
||||||
|
B_s = []
|
||||||
|
for payload in payloads.blinded_messages:
|
||||||
|
amounts.append(payload.amount)
|
||||||
|
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
|
||||||
|
try:
|
||||||
|
promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
|
||||||
|
return promises
|
||||||
|
except Exception as exc:
|
||||||
|
return CashuError(error=str(exc))
|
||||||
|
|
||||||
|
|
||||||
|
@cashu_ext.post("/melt")
|
||||||
|
async def melt(payload: MeltRequest) -> GetMeltResponse:
|
||||||
|
"""
|
||||||
|
Requests tokens to be destroyed and sent out via Lightning.
|
||||||
|
"""
|
||||||
|
ok, preimage = await ledger.melt(payload.proofs, payload.invoice)
|
||||||
|
resp = GetMeltResponse(paid=ok, preimage=preimage)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@cashu_ext.post("/check")
|
||||||
|
async def check_spendable(payload: CheckRequest) -> Dict[int, bool]:
|
||||||
|
"""Check whether a secret has been spent already or not."""
|
||||||
|
return await ledger.check_spendable(payload.proofs)
|
||||||
|
|
||||||
|
|
||||||
|
@cashu_ext.post("/checkfees")
|
||||||
|
async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
|
||||||
|
"""
|
||||||
|
Responds with the fees necessary to pay a Lightning invoice.
|
||||||
|
Used by wallets for figuring out the fees they need to supply.
|
||||||
|
This is can be useful for checking whether an invoice is internal (Cashu-to-Cashu).
|
||||||
|
"""
|
||||||
|
fees_msat = await ledger.check_fees(payload.pr)
|
||||||
|
return CheckFeesResponse(fee=fees_msat / 1000)
|
||||||
|
|
||||||
|
|
||||||
|
@cashu_ext.post("/split")
|
||||||
|
async def split(
|
||||||
|
payload: SplitRequest,
|
||||||
|
) -> Union[CashuError, PostSplitResponse]:
|
||||||
|
"""
|
||||||
|
Requetst a set of tokens with amount "total" to be split into two
|
||||||
|
newly minted sets with amount "split" and "total-split".
|
||||||
|
"""
|
||||||
|
proofs = payload.proofs
|
||||||
|
amount = payload.amount
|
||||||
|
outputs = payload.outputs.blinded_messages if payload.outputs else None
|
||||||
|
# backwards compatibility with clients < v0.2.2
|
||||||
|
assert outputs, Exception("no outputs provided.")
|
||||||
|
try:
|
||||||
|
split_return = await ledger.split(proofs, amount, outputs)
|
||||||
|
except Exception as exc:
|
||||||
|
return CashuError(error=str(exc))
|
||||||
|
if not split_return:
|
||||||
|
return CashuError(error="there was an error with the split")
|
||||||
|
frst_promises, scnd_promises = split_return
|
||||||
|
resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises)
|
||||||
|
return resp
|
||||||
|
|
Loading…
Add table
Reference in a new issue