This commit is contained in:
callebtc 2022-10-24 09:26:32 +02:00
parent 7e38d899af
commit e8e4c7f9c3
7 changed files with 125 additions and 106 deletions

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

@ -8,7 +8,7 @@
<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,7 +71,7 @@
<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>

View File

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

@ -913,7 +913,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'GET',
`/cashu/api/v1/cashu/${this.mintId}/mint?amount=${this.invoiceData.amount}`
`/cashu/api/v1/${this.mintId}/mint?amount=${this.invoiceData.amount}`
)
console.log('### data', data)
@ -940,7 +940,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'POST',
`/cashu/api/v1/cashu/${this.mintId}/mint?payment_hash=${invoice.hash}`,
`/cashu/api/v1/${this.mintId}/mint?payment_hash=${invoice.hash}`,
'',
{
blinded_messages: []
@ -991,7 +991,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'POST',
`/cashu/api/v1/cashu/${this.mintId}/mint?payment_hash=${hash}`,
`/cashu/api/v1/${this.mintId}/mint?payment_hash=${hash}`,
'',
{
blinded_messages: blindedMessages
@ -1086,7 +1086,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'POST',
`/cashu/api/v1/cashu/${this.mintId}/split`,
`/cashu/api/v1/${this.mintId}/split`,
'',
payload
)
@ -1221,7 +1221,7 @@ page_container %}
try {
const {data} = await LNbits.api.request(
'POST',
`/cashu/api/v1/cashu/${this.mintId}/melt`,
`/cashu/api/v1/${this.mintId}/melt`,
'',
payload
)
@ -1257,7 +1257,7 @@ page_container %}
fetchMintKeys: async function () {
const {data} = await LNbits.api.request(
'GET',
`/cashu/api/v1/cashu/${this.mintId}/keys`
`/cashu/api/v1/${this.mintId}/keys`
)
this.keys = data
localStorage.setItem('cashu.keys', JSON.stringify(data))

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:
@ -291,7 +351,8 @@ async def split(
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(
status_code=HTTPStatus.BAD_REQUEST,
@ -318,24 +379,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)
# ########################################
# #################????###################
# ########################################

75
poetry.lock generated
View File

@ -124,55 +124,33 @@ uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "cashu"
version = "0.4.2"
description = "Ecash wallet and mint with Bitcoin Lightning support"
description = "Ecash wallet and mint."
category = "main"
optional = false
python-versions = ">=3.7"
python-versions = "^3.7"
develop = false
[package.dependencies]
anyio = {version = "3.6.2", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
attrs = {version = "22.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
bech32 = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
bitstring = {version = "3.1.9", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
certifi = {version = "2022.9.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
cffi = {version = "1.15.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
charset-normalizer = {version = "2.0.12", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
click = {version = "8.0.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
colorama = {version = "0.4.5", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and platform_system == \"Windows\" or python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
ecdsa = {version = "0.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
environs = {version = "9.5.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
fastapi = {version = "0.83.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
h11 = {version = "0.12.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
idna = {version = "3.4", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
importlib-metadata = {version = "5.0.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
iniconfig = {version = "1.1.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
loguru = {version = "0.6.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
marshmallow = {version = "3.18.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
outcome = {version = "1.2.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
packaging = {version = "21.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
pluggy = {version = "1.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
py = {version = "1.11.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
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\""}
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\""}
python-dotenv = {version = "0.21.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
represent = {version = "1.6.0.post0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
requests = {version = "2.27.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
secp256k1 = {version = "0.14.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
six = {version = "1.16.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
sniffio = {version = "1.3.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
sqlalchemy = {version = "1.3.24", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
sqlalchemy-aio = {version = "0.17.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
starlette = {version = "0.19.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
tomli = {version = "2.0.1", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
typing-extensions = {version = "4.4.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
urllib3 = {version = "1.26.12", markers = "python_version >= \"3.7\" and python_version < \"4\""}
uvicorn = {version = "0.18.3", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
win32-setctime = {version = "1.1.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\" and sys_platform == \"win32\""}
zipp = {version = "3.9.0", markers = "python_version >= \"3.7\" and python_version < \"3.8\""}
bech32 = "^1.2.0"
bitstring = "^3.1.9"
click = "8.0.4"
ecdsa = "^0.18.0"
environs = "^9.5.0"
fastapi = "^0.83.0"
h11 = "0.12.0"
loguru = "^0.6.0"
pydantic = "^1.10.2"
pytest-asyncio = "0.19.0"
python-bitcoinlib = "^0.11.2"
requests = "2.27.1"
secp256k1 = "^0.14.0"
SQLAlchemy = "1.3.24"
sqlalchemy-aio = "^0.17.0"
uvicorn = "^0.18.3"
[package.source]
type = "directory"
url = "../cashu"
[[package]]
name = "Cerberus"
@ -1143,7 +1121,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 = "ded9ab80b3bec75bf904c3f598afbdff5f1577aaedbec25045c3b42c332f8ccc"
[metadata.files]
aiofiles = [
@ -1206,10 +1184,7 @@ black = [
{file = "black-22.10.0-py3-none-any.whl", hash = "sha256:c957b2b4ea88587b46cf49d1dc17681c1e672864fd7af32fc1e9664d572b3458"},
{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"},
]
cashu = []
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 = {path = "../cashu"}
[tool.poetry.dev-dependencies]