Merge pull request #1103 from lnbits/cashu_rewrite_wallet

Cashu rewrite wallet
This commit is contained in:
calle 2022-11-23 13:48:28 +01:00 committed by GitHub
commit 9fdbaf92f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1941 additions and 703 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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": &lt;invoice_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
@ -18,7 +18,7 @@
<code>[&lt;cashu_object&gt;, ...]</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:
&lt;invoice_key&gt;"
</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": &lt;invoice_key&gt;}</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":
&lt;string&gt;, "currency": &lt;string&gt;}' -H "Content-type:
application/json" -H "X-Api-Key: &lt;admin_key&gt;"
</code>
@ -62,7 +62,7 @@
<q-card-section>
<code
><span class="text-pink">DELETE</span>
/cashu/api/v1/cashus/&lt;cashu_id&gt;</code
/cashu/api/v1/mints/&lt;cashu_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</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/&lt;cashu_id&gt; -H "X-Api-Key:
}}cashu/api/v1/mints/&lt;cashu_id&gt; -H "X-Api-Key:
&lt;admin_key&gt;"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
</q-expansion-item> -->
</q-expansion-item>

View file

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

View file

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

View file

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

View file

@ -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",
},
],
}
],
}

View file

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

@ -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"},

View file

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