mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-24 14:51:05 +01:00
Merge pull request #1103 from lnbits/cashu_rewrite_wallet
Cashu rewrite wallet
This commit is contained in:
commit
9fdbaf92f6
16 changed files with 1941 additions and 703 deletions
|
@ -97,3 +97,8 @@ ECLAIR_PASS=eclairpw
|
|||
# Enter /api in LightningTipBot to get your key
|
||||
LNTIPS_API_KEY=LNTIPS_ADMIN_KEY
|
||||
LNTIPS_API_ENDPOINT=https://ln.tips
|
||||
|
||||
# Cashu Mint
|
||||
# Use a long-enough random (!) private key.
|
||||
# Once set, you cannot change this key as for now.
|
||||
CASHU_PRIVATE_KEY="SuperSecretPrivateKey"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
|
||||
from environs import Env # type: ignore
|
||||
from fastapi import APIRouter
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
|
@ -20,10 +21,12 @@ cashu_static_files = [
|
|||
]
|
||||
from cashu.mint.ledger import Ledger
|
||||
|
||||
env = Env()
|
||||
env.read_env()
|
||||
|
||||
ledger = Ledger(
|
||||
db=db,
|
||||
# seed=MINT_PRIVATE_KEY,
|
||||
seed="asd",
|
||||
seed=env.str("CASHU_PRIVATE_KEY", default="SuperSecretPrivateKey"),
|
||||
derivation_path="0/0/0/1",
|
||||
)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Cashu Ecash",
|
||||
"name": "Cashu",
|
||||
"short_description": "Ecash mint and wallet",
|
||||
"icon": "approval",
|
||||
"contributors": ["arcbtc", "calle", "vlad"],
|
||||
"icon": "account_balance",
|
||||
"contributors": ["calle", "vlad", "arcbtc"],
|
||||
"hidden": false
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "Cashu Ecash",
|
||||
"name": "Cashu",
|
||||
"short_description": "Ecash mints with LN peg in/out",
|
||||
"icon": "approval",
|
||||
"contributors": ["arcbtc", "calle"],
|
||||
"icon": "account_balance",
|
||||
"contributors": ["calle", "vlad", "arcbtc"],
|
||||
"hidden": true
|
||||
}
|
||||
|
|
|
@ -12,7 +12,8 @@ async def m001_initial(db):
|
|||
fraction BOOL,
|
||||
maxsats INT,
|
||||
coins INT,
|
||||
keyset_id TEXT NOT NULL
|
||||
keyset_id TEXT NOT NULL,
|
||||
issued_sat INT
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
|
|
@ -19,20 +19,21 @@ async function hashToCurve(secretMessage) {
|
|||
return point
|
||||
}
|
||||
|
||||
async function step1Bob(secretMessage) {
|
||||
secretMessage = nobleSecp256k1.utils.bytesToHex(secretMessage)
|
||||
secretMessage = new TextEncoder().encode(secretMessage);
|
||||
async function step1Alice(secretMessage) {
|
||||
// todo: document & validate `secretMessage` format
|
||||
secretMessage = uint8ToBase64.encode(secretMessage)
|
||||
secretMessage = new TextEncoder().encode(secretMessage)
|
||||
const Y = await hashToCurve(secretMessage)
|
||||
const randomBlindingFactor = bytesToNumber(
|
||||
nobleSecp256k1.utils.randomPrivateKey()
|
||||
)
|
||||
const P = nobleSecp256k1.Point.fromPrivateKey(randomBlindingFactor)
|
||||
const rpk = nobleSecp256k1.utils.randomPrivateKey()
|
||||
const r = bytesToNumber(rpk)
|
||||
const P = nobleSecp256k1.Point.fromPrivateKey(r)
|
||||
const B_ = Y.add(P)
|
||||
return {B_: B_.toHex(true), randomBlindingFactor}
|
||||
return {B_: B_.toHex(true), r: nobleSecp256k1.utils.bytesToHex(rpk)}
|
||||
}
|
||||
|
||||
function step3Bob(C_, r, A) {
|
||||
const rInt = BigInt(r)
|
||||
function step3Alice(C_, r, A) {
|
||||
// const rInt = BigInt(r)
|
||||
const rInt = bytesToNumber(r)
|
||||
const C = C_.subtract(A.multiply(rInt))
|
||||
return C
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ async def startup_cashu_mint():
|
|||
await migrate_databases(db, migrations)
|
||||
await ledger.load_used_proofs()
|
||||
await ledger.init_keysets()
|
||||
print(ledger.get_keyset())
|
||||
pass
|
||||
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
:content-inset-level="0.5"
|
||||
>
|
||||
<q-btn flat label="Swagger API" type="a" href="../docs#/cashu"></q-btn>
|
||||
<q-expansion-item group="api" dense expand-separator label="List TPoS">
|
||||
<!-- <q-expansion-item group="api" dense expand-separator label="List TPoS">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<code><span class="text-blue">GET</span> /cashu/api/v1/cashus</code>
|
||||
<code><span class="text-blue">GET</span> /cashu/api/v1/mints</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <invoice_key>}</code><br />
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||
|
@ -18,7 +18,7 @@
|
|||
<code>[<cashu_object>, ...]</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X GET {{ request.base_url }}cashu/api/v1/cashus -H "X-Api-Key:
|
||||
>curl -X GET {{ request.base_url }}cashu/api/v1/mints -H "X-Api-Key:
|
||||
<invoice_key>"
|
||||
</code>
|
||||
</q-card-section>
|
||||
|
@ -27,7 +27,7 @@
|
|||
<q-expansion-item group="api" dense expand-separator label="Create a TPoS">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<code><span class="text-green">POST</span> /cashu/api/v1/cashus</code>
|
||||
<code><span class="text-green">POST</span> /cashu/api/v1/mints</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <invoice_key>}</code><br />
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||
|
@ -43,7 +43,7 @@
|
|||
>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X POST {{ request.base_url }}cashu/api/v1/cashus -d '{"name":
|
||||
>curl -X POST {{ request.base_url }}cashu/api/v1/mints -d '{"name":
|
||||
<string>, "currency": <string>}' -H "Content-type:
|
||||
application/json" -H "X-Api-Key: <admin_key>"
|
||||
</code>
|
||||
|
@ -62,7 +62,7 @@
|
|||
<q-card-section>
|
||||
<code
|
||||
><span class="text-pink">DELETE</span>
|
||||
/cashu/api/v1/cashus/<cashu_id></code
|
||||
/cashu/api/v1/mints/<cashu_id></code
|
||||
>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
||||
|
@ -71,10 +71,10 @@
|
|||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X DELETE {{ request.base_url
|
||||
}}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key:
|
||||
}}cashu/api/v1/mints/<cashu_id> -H "X-Api-Key:
|
||||
<admin_key>"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
</q-expansion-item> -->
|
||||
</q-expansion-item>
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
<q-expansion-item group="extras" icon="info" label="About">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<p>
|
||||
Make Ecash mints with peg in/out to a wallet, that can create and manage
|
||||
ecash.
|
||||
</p>
|
||||
<p>Create Cashu ecash mints and wallets.</p>
|
||||
<small
|
||||
>Created by
|
||||
<a href="https://github.com/arcbtc" target="_blank">arcbtc</a>,
|
||||
|
|
|
@ -46,12 +46,12 @@
|
|||
unelevated
|
||||
dense
|
||||
size="xs"
|
||||
icon="launch"
|
||||
icon="account_balance_wallet"
|
||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||
type="a"
|
||||
:href="'wallet/?tsh=' + (props.row.tickershort || '') + '&mint_id=' + props.row.id + '&mint_name=' + props.row.name"
|
||||
:href="'wallet/?' + 'mint_id=' + props.row.id"
|
||||
target="_blank"
|
||||
><q-tooltip>Shareable wallet page</q-tooltip></q-btn
|
||||
><q-tooltip>Shareable wallet</q-tooltip></q-btn
|
||||
>
|
||||
|
||||
<q-btn
|
||||
|
@ -218,18 +218,18 @@
|
|||
toggleAdvanced: false,
|
||||
cashusTable: {
|
||||
columns: [
|
||||
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||
{name: 'id', align: 'left', label: 'Mint ID', field: 'id'},
|
||||
{name: 'name', align: 'left', label: 'Name', field: 'name'},
|
||||
{
|
||||
name: 'tickershort',
|
||||
align: 'left',
|
||||
label: 'tickershort',
|
||||
label: 'Ticker',
|
||||
field: 'tickershort'
|
||||
},
|
||||
{
|
||||
name: 'wallet',
|
||||
align: 'left',
|
||||
label: 'Cashu wallet',
|
||||
label: 'Mint wallet',
|
||||
field: 'wallet'
|
||||
},
|
||||
{
|
||||
|
@ -271,7 +271,7 @@
|
|||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/cashu/api/v1/cashus?all_wallets=true',
|
||||
'/cashu/api/v1/mints?all_wallets=true',
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
.then(function (response) {
|
||||
|
@ -294,7 +294,7 @@
|
|||
LNbits.api
|
||||
.request(
|
||||
'POST',
|
||||
'/cashu/api/v1/cashus',
|
||||
'/cashu/api/v1/mints',
|
||||
_.findWhere(this.g.user.wallets, {id: this.formDialog.data.wallet})
|
||||
.inkey,
|
||||
data
|
||||
|
@ -314,13 +314,13 @@
|
|||
|
||||
LNbits.utils
|
||||
.confirmDialog(
|
||||
'Are you sure you want to delete this Mint? It will suck for users.'
|
||||
"Are you sure you want to delete this Mint? This mint's users will not be able to redeem their tokens!"
|
||||
)
|
||||
.onOk(function () {
|
||||
LNbits.api
|
||||
.request(
|
||||
'DELETE',
|
||||
'/cashu/api/v1/cashus/' + cashuId,
|
||||
'/cashu/api/v1/mints/' + cashuId,
|
||||
_.findWhere(self.g.user.wallets, {id: cashu.wallet}).adminkey
|
||||
)
|
||||
.then(function (response) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "public.html" %} {% block page %}
|
||||
<div class="row q-col-gutter-md justify-center">
|
||||
<div class="col-12 col-md-7 col-lg-6 q-gutter-y-md">
|
||||
<q-card class="q-pa-lg">
|
||||
<q-card class="q-pa-lg q-mb-xl">
|
||||
<q-card-section class="q-pa-none">
|
||||
<center>
|
||||
<q-icon
|
||||
|
@ -9,13 +9,53 @@
|
|||
class="text-grey"
|
||||
style="font-size: 10rem"
|
||||
></q-icon>
|
||||
<h3 class="q-my-none">{{ mint_name }}</h3>
|
||||
<br />
|
||||
<h4 class="q-mt-none q-mb-md">{{ mint_name }}</h4>
|
||||
<a
|
||||
class="q-my-xl text-white"
|
||||
style="font-size: 1.5rem"
|
||||
href="../wallet?mint_id={{ mint_id }}"
|
||||
>Open wallet</a
|
||||
>
|
||||
</center>
|
||||
<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>
|
||||
<q-card class="q-pa-lg q-mb-xl">
|
||||
<q-card-section class="q-pa-none">
|
||||
<h5 class="q-my-md">Read the following carefully!</h5>
|
||||
<p>
|
||||
This is a
|
||||
<a href="https://cashu.space/" style="color: white" target="”_blank”"
|
||||
>Cashu</a
|
||||
>
|
||||
mint. Cashu is an ecash system for Bitcoin.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Open this page in your native browser</strong><br />
|
||||
Before you continue to the wallet, make sure to open this page in your
|
||||
device's native browser application (Safari for iOS, Chrome for
|
||||
Android). Do not use Cashu in an embedded browser that opens when you
|
||||
click a link in a messenger.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Add wallet to home screen</strong><br />
|
||||
You can add Cashu to your home screen as a progressive web app (PWA).
|
||||
After opening the wallet in your browser (click the link above), on
|
||||
Android (Chrome), click the menu at the upper right. On iOS (Safari),
|
||||
click the share button. Now press the Add to Home screen button.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Backup your wallet</strong><br />
|
||||
Ecash is a bearer asset. That means losing access to your wallet will
|
||||
make you lose your funds. The wallet stores ecash tokens on your
|
||||
device's database. If you lose the link or delete your your data
|
||||
without backing up, you will lose your tokens. Press the Backup button
|
||||
in the wallet to download a copy of your tokens.
|
||||
</p>
|
||||
<p>
|
||||
<strong>This service is in BETA</strong> <br />
|
||||
We hold no responsibility for people losing access to funds. Use at
|
||||
your own risk!
|
||||
</p>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -23,16 +23,28 @@ async def index(request: Request, user: User = Depends(check_user_exists)):
|
|||
)
|
||||
|
||||
|
||||
# @cashu_ext.get("/wallet")
|
||||
# async def wallet(request: Request):
|
||||
# return cashu_renderer().TemplateResponse("cashu/wallet.html", {"request": request})
|
||||
|
||||
|
||||
@cashu_ext.get("/wallet")
|
||||
async def cashu(request: Request):
|
||||
return cashu_renderer().TemplateResponse("cashu/wallet.html", {"request": request})
|
||||
async def wallet(request: Request, mint_id: str):
|
||||
return cashu_renderer().TemplateResponse(
|
||||
"cashu/wallet.html",
|
||||
{
|
||||
"request": request,
|
||||
"web_manifest": f"/cashu/manifest/{mint_id}.webmanifest",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@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}
|
||||
"cashu/mint.html",
|
||||
{"request": request, "mint_name": cashu.name, "mint_id": mintID},
|
||||
)
|
||||
|
||||
|
||||
|
@ -45,29 +57,167 @@ async def manifest(cashu_id: str):
|
|||
)
|
||||
|
||||
return {
|
||||
"short_name": LNBITS_SITE_TITLE,
|
||||
"name": cashu.name + " - " + LNBITS_SITE_TITLE,
|
||||
"short_name": "Cashu",
|
||||
"name": "Cashu" + " - " + cashu.name,
|
||||
"icons": [
|
||||
{
|
||||
"src": LNBITS_CUSTOM_LOGO
|
||||
if LNBITS_CUSTOM_LOGO
|
||||
else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png",
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-512-512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "900x900",
|
||||
}
|
||||
"sizes": "512x512",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-96-96.png",
|
||||
"type": "image/png",
|
||||
"sizes": "96x96",
|
||||
},
|
||||
],
|
||||
"start_url": "/cashu/" + cashu_id,
|
||||
"id": "/cashu/wallet?mint_id=" + cashu_id,
|
||||
"start_url": "/cashu/wallet?mint_id=" + cashu_id,
|
||||
"background_color": "#1F2234",
|
||||
"description": "Bitcoin Lightning tPOS",
|
||||
"description": "Cashu ecash wallet",
|
||||
"display": "standalone",
|
||||
"scope": "/cashu/" + cashu_id,
|
||||
"scope": "/cashu/",
|
||||
"theme_color": "#1F2234",
|
||||
"protocol_handlers": [
|
||||
{"protocol": "cashu", "url": "&recv_token=%s"},
|
||||
{"protocol": "lightning", "url": "&lightning=%s"},
|
||||
],
|
||||
"shortcuts": [
|
||||
{
|
||||
"name": cashu.name + " - " + LNBITS_SITE_TITLE,
|
||||
"short_name": cashu.name,
|
||||
"description": cashu.name + " - " + LNBITS_SITE_TITLE,
|
||||
"url": "/cashu/" + cashu_id,
|
||||
"name": "Cashu" + " - " + cashu.name,
|
||||
"short_name": "Cashu",
|
||||
"description": "Cashu" + " - " + cashu.name,
|
||||
"url": "/cashu/wallet?mint_id=" + cashu_id,
|
||||
"icons": [
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-512-512.png",
|
||||
"sizes": "512x512",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-192-192.png",
|
||||
"sizes": "192x192",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-144-144.png",
|
||||
"sizes": "144x144",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-96-96.png",
|
||||
"sizes": "96x96",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-72-72.png",
|
||||
"sizes": "72x72",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/android/android-launchericon-48-48.png",
|
||||
"sizes": "48x48",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/16.png",
|
||||
"sizes": "16x16",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/20.png",
|
||||
"sizes": "20x20",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/29.png",
|
||||
"sizes": "29x29",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/32.png",
|
||||
"sizes": "32x32",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/40.png",
|
||||
"sizes": "40x40",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/50.png",
|
||||
"sizes": "50x50",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/57.png",
|
||||
"sizes": "57x57",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/58.png",
|
||||
"sizes": "58x58",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/60.png",
|
||||
"sizes": "60x60",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/64.png",
|
||||
"sizes": "64x64",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/72.png",
|
||||
"sizes": "72x72",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/76.png",
|
||||
"sizes": "76x76",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/80.png",
|
||||
"sizes": "80x80",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/87.png",
|
||||
"sizes": "87x87",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/100.png",
|
||||
"sizes": "100x100",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/114.png",
|
||||
"sizes": "114x114",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/120.png",
|
||||
"sizes": "120x120",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/128.png",
|
||||
"sizes": "128x128",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/144.png",
|
||||
"sizes": "144x144",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/152.png",
|
||||
"sizes": "152x152",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/167.png",
|
||||
"sizes": "167x167",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/180.png",
|
||||
"sizes": "180x180",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/192.png",
|
||||
"sizes": "192x192",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/256.png",
|
||||
"sizes": "256x256",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/512.png",
|
||||
"sizes": "512x512",
|
||||
},
|
||||
{
|
||||
"src": "https://github.com/cashubtc/cashu-ui/raw/main/ui/icons/circle/ios/1024.png",
|
||||
"sizes": "1024x1024",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
|
|
@ -47,17 +47,20 @@ from .models import Cashu
|
|||
# --------- extension imports
|
||||
|
||||
|
||||
LIGHTNING = False
|
||||
LIGHTNING = True
|
||||
|
||||
########################################
|
||||
############### LNBITS MINTS ###########
|
||||
########################################
|
||||
|
||||
# todo: use /mints
|
||||
@cashu_ext.get("/api/v1/cashus", status_code=HTTPStatus.OK)
|
||||
|
||||
@cashu_ext.get("/api/v1/mints", status_code=HTTPStatus.OK)
|
||||
async def api_cashus(
|
||||
all_wallets: bool = Query(False), wallet: WalletTypeInfo = Depends(get_key_type)
|
||||
):
|
||||
"""
|
||||
Get all mints of this wallet.
|
||||
"""
|
||||
wallet_ids = [wallet.wallet.id]
|
||||
if all_wallets:
|
||||
wallet_ids = (await get_user(wallet.wallet.user)).wallet_ids
|
||||
|
@ -65,8 +68,11 @@ async def api_cashus(
|
|||
return [cashu.dict() for cashu in await get_cashus(wallet_ids)]
|
||||
|
||||
|
||||
@cashu_ext.post("/api/v1/cashus", status_code=HTTPStatus.CREATED)
|
||||
@cashu_ext.post("/api/v1/mints", status_code=HTTPStatus.CREATED)
|
||||
async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key_type)):
|
||||
"""
|
||||
Create a new mint for this wallet.
|
||||
"""
|
||||
cashu_id = urlsafe_short_hash()
|
||||
# generate a new keyset in cashu
|
||||
keyset = await ledger.load_keyset(cashu_id)
|
||||
|
@ -78,12 +84,35 @@ async def api_cashu_create(data: Cashu, wallet: WalletTypeInfo = Depends(get_key
|
|||
return cashu.dict()
|
||||
|
||||
|
||||
@cashu_ext.delete("/api/v1/mints/{cashu_id}")
|
||||
async def api_cashu_delete(
|
||||
cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||
):
|
||||
"""
|
||||
Delete an existing cashu mint.
|
||||
"""
|
||||
cashu = await get_cashu(cashu_id)
|
||||
|
||||
if not cashu:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Cashu mint does not exist."
|
||||
)
|
||||
|
||||
if cashu.wallet != wallet.wallet.id:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.FORBIDDEN, detail="Not your Cashu mint."
|
||||
)
|
||||
|
||||
await delete_cashu(cashu_id)
|
||||
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||
|
||||
|
||||
#######################################
|
||||
########### CASHU ENDPOINTS ###########
|
||||
#######################################
|
||||
|
||||
|
||||
@cashu_ext.get("/api/v1/cashu/{cashu_id}/keys", status_code=HTTPStatus.OK)
|
||||
@cashu_ext.get("/api/v1/{cashu_id}/keys", status_code=HTTPStatus.OK)
|
||||
async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
|
||||
"""Get the public keys of the mint"""
|
||||
cashu: Union[Cashu, None] = await get_cashu(cashu_id)
|
||||
|
@ -96,7 +125,20 @@ async def keys(cashu_id: str = Query(None)) -> dict[int, str]:
|
|||
return ledger.get_keyset(keyset_id=cashu.keyset_id)
|
||||
|
||||
|
||||
@cashu_ext.get("/api/v1/cashu/{cashu_id}/mint")
|
||||
@cashu_ext.get("/api/v1/{cashu_id}/keysets", status_code=HTTPStatus.OK)
|
||||
async def keysets(cashu_id: str = Query(None)) -> dict[str, list[str]]:
|
||||
"""Get the public keys of the mint"""
|
||||
cashu: Union[Cashu, None] = await get_cashu(cashu_id)
|
||||
|
||||
if not cashu:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
|
||||
)
|
||||
|
||||
return {"keysets": [cashu.keyset_id]}
|
||||
|
||||
|
||||
@cashu_ext.get("/api/v1/{cashu_id}/mint")
|
||||
async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintResponse:
|
||||
"""
|
||||
Request minting of new tokens. The mint responds with a Lightning invoice.
|
||||
|
@ -134,7 +176,7 @@ async def request_mint(cashu_id: str = Query(None), amount: int = 0) -> GetMintR
|
|||
return resp
|
||||
|
||||
|
||||
@cashu_ext.post("/api/v1/cashu/{cashu_id}/mint")
|
||||
@cashu_ext.post("/api/v1/{cashu_id}/mint")
|
||||
async def mint_coins(
|
||||
data: MintRequest,
|
||||
cashu_id: str = Query(None),
|
||||
|
@ -157,7 +199,7 @@ async def mint_coins(
|
|||
if invoice is None:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND,
|
||||
detail="Mint does not have this invoice.",
|
||||
detail="Mint does not know this invoice.",
|
||||
)
|
||||
if invoice.issued == True:
|
||||
raise HTTPException(
|
||||
|
@ -173,27 +215,31 @@ async def mint_coins(
|
|||
)
|
||||
|
||||
status: PaymentStatus = await check_transaction_status(cashu.wallet, payment_hash)
|
||||
# todo: revert to: status.paid != True:
|
||||
|
||||
if status.paid != True:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.PAYMENT_REQUIRED, detail="Invoice not paid."
|
||||
)
|
||||
try:
|
||||
await ledger.crud.update_lightning_invoice(
|
||||
db=ledger.db, hash=payment_hash, issued=True
|
||||
)
|
||||
keyset = ledger.keysets.keysets[cashu.keyset_id]
|
||||
|
||||
promises = await ledger._generate_promises(
|
||||
B_s=data.blinded_messages, keyset=keyset
|
||||
)
|
||||
assert len(promises), HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail="No promises returned."
|
||||
)
|
||||
await ledger.crud.update_lightning_invoice(
|
||||
db=ledger.db, hash=payment_hash, issued=True
|
||||
)
|
||||
|
||||
return promises
|
||||
except Exception as e:
|
||||
logger.error(e)
|
||||
raise HTTPException(status_code=HTTPStatus.INTERNAL_SERVER_ERROR, detail=str(e))
|
||||
|
||||
|
||||
@cashu_ext.post("/api/v1/cashu/{cashu_id}/melt")
|
||||
@cashu_ext.post("/api/v1/{cashu_id}/melt")
|
||||
async def melt_coins(
|
||||
payload: MeltRequest, cashu_id: str = Query(None)
|
||||
) -> GetMeltResponse:
|
||||
|
@ -211,7 +257,7 @@ async def melt_coins(
|
|||
# TOKENS
|
||||
assert all([p.id == cashu.keyset_id for p in proofs]), HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail="Proofs include tokens from other mint.",
|
||||
detail="Proofs include tokens from another mint.",
|
||||
)
|
||||
|
||||
assert all([ledger._verify_proof(p) for p in proofs]), HTTPException(
|
||||
|
@ -248,19 +294,33 @@ async def melt_coins(
|
|||
return GetMeltResponse(paid=status.paid, preimage=status.preimage)
|
||||
|
||||
|
||||
@cashu_ext.post("/api/v1/check")
|
||||
async def check_spendable(payload: CheckRequest) -> Dict[int, bool]:
|
||||
@cashu_ext.post("/api/v1/{cashu_id}/check")
|
||||
async def check_spendable(
|
||||
payload: CheckRequest, cashu_id: str = Query(None)
|
||||
) -> Dict[int, bool]:
|
||||
"""Check whether a secret has been spent already or not."""
|
||||
cashu: Union[None, Cashu] = await get_cashu(cashu_id)
|
||||
if cashu is None:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
|
||||
)
|
||||
return await ledger.check_spendable(payload.proofs)
|
||||
|
||||
|
||||
@cashu_ext.post("/api/v1/checkfees")
|
||||
async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
|
||||
@cashu_ext.post("/api/v1/{cashu_id}/checkfees")
|
||||
async def check_fees(
|
||||
payload: CheckFeesRequest, cashu_id: str = Query(None)
|
||||
) -> 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).
|
||||
"""
|
||||
cashu: Union[None, Cashu] = await get_cashu(cashu_id)
|
||||
if cashu is None:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
|
||||
)
|
||||
invoice_obj = bolt11.decode(payload.pr)
|
||||
internal_checking_id = await check_internal(invoice_obj.payment_hash)
|
||||
|
||||
|
@ -271,7 +331,7 @@ async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
|
|||
return CheckFeesResponse(fee=fees_msat / 1000)
|
||||
|
||||
|
||||
@cashu_ext.post("/api/v1/cashu/{cashu_id}/split")
|
||||
@cashu_ext.post("/api/v1/{cashu_id}/split")
|
||||
async def split(
|
||||
payload: SplitRequest, cashu_id: str = Query(None)
|
||||
) -> PostSplitResponse:
|
||||
|
@ -285,15 +345,24 @@ async def split(
|
|||
status_code=HTTPStatus.NOT_FOUND, detail="Mint does not exist."
|
||||
)
|
||||
proofs = payload.proofs
|
||||
|
||||
# !!!!!!! MAKE SURE THAT PROOFS ARE ONLY FROM THIS CASHU KEYSET ID
|
||||
# THIS IS NECESSARY BECAUSE THE CASHU BACKEND WILL ACCEPT ANY VALID
|
||||
# TOKENS
|
||||
assert all([p.id == cashu.keyset_id for p in proofs]), HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail="Proofs include tokens from another mint.",
|
||||
)
|
||||
|
||||
amount = payload.amount
|
||||
outputs = payload.outputs.blinded_messages
|
||||
# backwards compatibility with clients < v0.2.2
|
||||
assert outputs, Exception("no outputs provided.")
|
||||
split_return = None
|
||||
try:
|
||||
split_return = await ledger.split(proofs, amount, outputs, cashu.keyset_id)
|
||||
keyset = ledger.keysets.keysets[cashu.keyset_id]
|
||||
split_return = await ledger.split(proofs, amount, outputs, keyset)
|
||||
except Exception as exc:
|
||||
HTTPException(
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail=str(exc),
|
||||
)
|
||||
|
@ -318,24 +387,6 @@ async def split(
|
|||
# return cashu.dict()
|
||||
|
||||
|
||||
# @cashu_ext.delete("/api/v1s/{cashu_id}")
|
||||
# async def api_cashu_delete(
|
||||
# cashu_id: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||
# ):
|
||||
# cashu = await get_cashu(cashu_id)
|
||||
|
||||
# if not cashu:
|
||||
# raise HTTPException(
|
||||
# status_code=HTTPStatus.NOT_FOUND, detail="Cashu does not exist."
|
||||
# )
|
||||
|
||||
# if cashu.wallet != wallet.wallet.id:
|
||||
# raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your Cashu.")
|
||||
|
||||
# await delete_cashu(cashu_id)
|
||||
# raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
|
||||
|
||||
|
||||
# ########################################
|
||||
# #################????###################
|
||||
# ########################################
|
||||
|
|
9
poetry.lock
generated
9
poetry.lock
generated
|
@ -123,7 +123,7 @@ uvloop = ["uvloop (>=0.15.2)"]
|
|||
|
||||
[[package]]
|
||||
name = "cashu"
|
||||
version = "0.4.2"
|
||||
version = "0.5.1"
|
||||
description = "Ecash wallet and mint with Bitcoin Lightning support"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -155,6 +155,7 @@ py = {version = "1.11.0", markers = "python_version >= \"3.7\" and python_versio
|
|||
pycparser = {version = "2.21", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
|
||||
pydantic = {version = "1.10.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
|
||||
pyparsing = {version = "3.0.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
|
||||
pysocks = {version = "1.7.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
|
||||
pytest = {version = "7.1.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
|
||||
pytest-asyncio = {version = "0.19.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
|
||||
python-bitcoinlib = {version = "0.11.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
|
||||
|
@ -1143,7 +1144,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.10 | ^3.9 | ^3.8 | ^3.7"
|
||||
content-hash = "7de5e4d432bff49de536b1c90082a6a0821533b3d0fa9d92c22ccaa758d1a65f"
|
||||
content-hash = "c5d3b28864bf6b86385e38f63e3ba16d95804a812773e930b6ed818d4f09938a"
|
||||
|
||||
[metadata.files]
|
||||
aiofiles = [
|
||||
|
@ -1207,8 +1208,8 @@ black = [
|
|||
{file = "black-22.10.0.tar.gz", hash = "sha256:f513588da599943e0cde4e32cc9879e825d58720d6557062d1098c5ad80080e1"},
|
||||
]
|
||||
cashu = [
|
||||
{file = "cashu-0.4.2-py3-none-any.whl", hash = "sha256:6d24f5e921c33dae1b6823f5e34feab0d6d5662b56a67c29095d48241163a887"},
|
||||
{file = "cashu-0.4.2.tar.gz", hash = "sha256:97564481501cbe163e6be4d3cdd0d52d2841e15b830a0185c3c329657e4b8c36"},
|
||||
{file = "cashu-0.5.1-py3-none-any.whl", hash = "sha256:893f6bc098331e73cb6a5d0108c929dc7f2299d3d5405ae3b29e0868d9cd78c9"},
|
||||
{file = "cashu-0.5.1.tar.gz", hash = "sha256:c4533c72a09b0e1439836739653d3d79a7de00a1106e6676cb8f660f894006a7"},
|
||||
]
|
||||
Cerberus = [
|
||||
{file = "Cerberus-1.3.4.tar.gz", hash = "sha256:d1b21b3954b2498d9a79edf16b3170a3ac1021df88d197dc2ce5928ba519237c"},
|
||||
|
|
|
@ -64,7 +64,7 @@ protobuf = "^4.21.6"
|
|||
Cerberus = "^1.3.4"
|
||||
async-timeout = "^4.0.2"
|
||||
pyln-client = "0.11.1"
|
||||
cashu = "0.4.2"
|
||||
cashu = "^0.5.1"
|
||||
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
|
Loading…
Add table
Reference in a new issue