diff --git a/__init__.py b/__init__.py index 8df0f80..50f9d52 100644 --- a/__init__.py +++ b/__init__.py @@ -8,7 +8,6 @@ from .crud import db from .tasks import wait_for_paid_invoices, hourly_transaction_polling from .views import myextension_generic_router from .views_api import myextension_api_router -from .views_lnurl import myextension_lnurl_router logger.debug( "This logged message is from myextension/__init__.py, you can debug in your " @@ -16,10 +15,9 @@ logger.debug( ) -myextension_ext: APIRouter = APIRouter(prefix="/myextension", tags=["MyExtension"]) +myextension_ext: APIRouter = APIRouter(prefix="/myextension", tags=["DCA Admin"]) myextension_ext.include_router(myextension_generic_router) myextension_ext.include_router(myextension_api_router) -myextension_ext.include_router(myextension_lnurl_router) myextension_static_files = [ { diff --git a/config.json b/config.json index 6d51a5a..6be901a 100644 --- a/config.json +++ b/config.json @@ -1,38 +1,32 @@ { - "name": "MyExtension", - "short_description": "Minimal extension to build on", + "name": "DCA Admin", + "short_description": "Dollar Cost Averaging administration for Lamassu ATM integration", "tile": "/myextension/static/image/myextension.png", "min_lnbits_version": "1.0.0", "contributors": [ { - "name": "Alan Bits", - "uri": "https://github.com/alanbits", - "role": "Lead dev" + "name": "Atitlan Community", + "uri": "https://atitlan.io", + "role": "R&D Venue" }, { - "name": "Ben Arc", - "uri": "https://github.com/arcbtc", - "role": "Dev" + "name": "AtitlanIO", + "uri": "https://atitlan.io", + "role": "Developer" }, { - "name": "LNbits community", - "uri": "https://t.me/lnbits", - "role": "Emotional support" + "name": "LNbits", + "uri": "https://github.com/lnbits", + "role": "Developer" + }, + { + "name": "Claude", + "uri": "https://claude.ai", + "role": "Code Writing Agent" } ], - "images": [ - { - "uri": "https://raw.githubusercontent.com/lnbits/myextension/main/static/image/1.png", - "link": "https://www.youtube.com/embed/SkkIwO_X4i4?si=9JJh1Fc6GfHDZK6b" - }, - { - "uri": "https://raw.githubusercontent.com/lnbits/myextension/main/static/image/2.png" - }, - { - "uri": "https://raw.githubusercontent.com/lnbits/myextension/main/static/image/3.png" - } - ], - "description_md": "https://raw.githubusercontent.com/lnbits/myextension/main/description.md", - "terms_and_conditions_md": "https://raw.githubusercontent.com/lnbits/myextension/main/toc.md", + "images": [], + "description_md": "/myextension/description.md", + "terms_and_conditions_md": "/myextension/toc.md", "license": "MIT" } diff --git a/crud.py b/crud.py index d94b19b..2cf20e0 100644 --- a/crud.py +++ b/crud.py @@ -7,7 +7,6 @@ from lnbits.db import Database from lnbits.helpers import urlsafe_short_hash from .models import ( - CreateMyExtensionData, MyExtension, CreateDcaClientData, DcaClient, UpdateDcaClientData, CreateDepositData, DcaDeposit, UpdateDepositStatusData, CreateDcaPaymentData, DcaPayment, @@ -19,41 +18,6 @@ from .models import ( db = Database("ext_myextension") -async def create_myextension(data: CreateMyExtensionData) -> MyExtension: - data.id = urlsafe_short_hash() - await db.insert("myextension.maintable", data) - return MyExtension(**data.dict()) - - -async def get_myextension(myextension_id: str) -> Optional[MyExtension]: - return await db.fetchone( - "SELECT * FROM myextension.maintable WHERE id = :id", - {"id": myextension_id}, - MyExtension, - ) - - -async def get_myextensions(wallet_ids: Union[str, List[str]]) -> List[MyExtension]: - if isinstance(wallet_ids, str): - wallet_ids = [wallet_ids] - q = ",".join([f"'{w}'" for w in wallet_ids]) - return await db.fetchall( - f"SELECT * FROM myextension.maintable WHERE wallet IN ({q}) ORDER BY id", - model=MyExtension, - ) - - -async def update_myextension(data: CreateMyExtensionData) -> MyExtension: - await db.update("myextension.maintable", data) - return MyExtension(**data.dict()) - - -async def delete_myextension(myextension_id: str) -> None: - await db.execute( - "DELETE FROM myextension.maintable WHERE id = :id", {"id": myextension_id} - ) - - # DCA Client CRUD Operations async def create_dca_client(data: CreateDcaClientData) -> DcaClient: client_id = urlsafe_short_hash() diff --git a/helpers.py b/helpers.py deleted file mode 100644 index b7c8c4b..0000000 --- a/helpers.py +++ /dev/null @@ -1,17 +0,0 @@ -# Description: A place for helper functions. - -from fastapi import Request -from lnurl.core import encode as lnurl_encode - -# The lnurler function is used to generate the lnurlpay and lnurlwithdraw links -# from the lnurl api endpoints in views_lnurl.py. -# It needs the Request object to know the url of the LNbits. -# Lnurler is used in views_api.py - - -def lnurler(myex_id: str, route_name: str, req: Request) -> str: - url = req.url_for(route_name, myextension_id=myex_id) - url_str = str(url) - if url.netloc.endswith(".onion"): - url_str = url_str.replace("https://", "http://") - return str(lnurl_encode(url_str)) diff --git a/models.py b/models.py index 60656bf..964d67c 100644 --- a/models.py +++ b/models.py @@ -207,28 +207,3 @@ class UpdateLamassuConfigData(BaseModel): ssh_private_key: Optional[str] = None -# Legacy models (keep for backward compatibility during transition) -class CreateMyExtensionData(BaseModel): - id: Optional[str] = "" - name: str - lnurlpayamount: int - lnurlwithdrawamount: int - wallet: str - total: int = 0 - - -class MyExtension(BaseModel): - id: str - name: str - lnurlpayamount: int - lnurlwithdrawamount: int - wallet: str - total: int - lnurlpay: Optional[str] = "" - lnurlwithdraw: Optional[str] = "" - - -class CreatePayment(BaseModel): - myextension_id: str - amount: int - memo: str diff --git a/static/js/index.js b/static/js/index.js index 67ae246..fc9a2d5 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -119,32 +119,7 @@ window.app = Vue.createApp({ currencyOptions: [ { label: 'GTQ', value: 'GTQ' }, { label: 'USD', value: 'USD' } - ], - - // Legacy data (keep for backward compatibility) - invoiceAmount: 10, - qrValue: 'lnurlpay', - myex: [], - myexTable: { - columns: [ - { name: 'id', align: 'left', label: 'ID', field: 'id' }, - { name: 'name', align: 'left', label: 'Name', field: 'name' }, - { name: 'wallet', align: 'left', label: 'Wallet', field: 'wallet' }, - { name: 'total', align: 'left', label: 'Total sent/received', field: 'total' } - ], - pagination: { - rowsPerPage: 10 - } - }, - formDialog: { - show: false, - data: {}, - advanced: {} - }, - urlDialog: { - show: false, - data: {} - } + ] } }, @@ -666,194 +641,6 @@ window.app = Vue.createApp({ } }, - // Legacy Methods (keep for backward compatibility) - async closeFormDialog() { - this.formDialog.show = false - this.formDialog.data = {} - }, - async getMyExtensions() { - await LNbits.api - .request( - 'GET', - '/myextension/api/v1/myex', - this.g.user.wallets[0].inkey - ) - .then(response => { - this.myex = response.data - }) - .catch(err => { - LNbits.utils.notifyApiError(err) - }) - }, - async sendMyExtensionData() { - const data = { - name: this.formDialog.data.name, - lnurlwithdrawamount: this.formDialog.data.lnurlwithdrawamount, - lnurlpayamount: this.formDialog.data.lnurlpayamount - } - const wallet = _.findWhere(this.g.user.wallets, { - id: this.formDialog.data.wallet - }) - if (this.formDialog.data.id) { - data.id = this.formDialog.data.id - data.total = this.formDialog.data.total - await this.updateMyExtension(wallet, data) - } else { - await this.createMyExtension(wallet, data) - } - }, - - async updateMyExtensionForm(tempId) { - const myextension = _.findWhere(this.myex, { id: tempId }) - this.formDialog.data = { - ...myextension - } - if (this.formDialog.data.tip_wallet != '') { - this.formDialog.advanced.tips = true - } - if (this.formDialog.data.withdrawlimit >= 1) { - this.formDialog.advanced.otc = true - } - this.formDialog.show = true - }, - async createMyExtension(wallet, data) { - data.wallet = wallet.id - await LNbits.api - .request('POST', '/myextension/api/v1/myex', wallet.adminkey, data) - .then(response => { - this.myex.push(response.data) - this.closeFormDialog() - }) - .catch(error => { - LNbits.utils.notifyApiError(error) - }) - }, - - async updateMyExtension(wallet, data) { - data.wallet = wallet.id - await LNbits.api - .request( - 'PUT', - `/myextension/api/v1/myex/${data.id}`, - wallet.adminkey, - data - ) - .then(response => { - this.myex = _.reject(this.myex, obj => obj.id == data.id) - this.myex.push(response.data) - this.closeFormDialog() - }) - .catch(error => { - LNbits.utils.notifyApiError(error) - }) - }, - async deleteMyExtension(tempId) { - var myextension = _.findWhere(this.myex, { id: tempId }) - const wallet = _.findWhere(this.g.user.wallets, { - id: myextension.wallet - }) - await LNbits.utils - .confirmDialog('Are you sure you want to delete this MyExtension?') - .onOk(function () { - LNbits.api - .request( - 'DELETE', - '/myextension/api/v1/myex/' + tempId, - wallet.adminkey - ) - .then(() => { - this.myex = _.reject(this.myex, function (obj) { - return obj.id === myextension.id - }) - }) - .catch(error => { - LNbits.utils.notifyApiError(error) - }) - }) - }, - - async exportCSV() { - await LNbits.utils.exportCSV(this.myexTable.columns, this.myex) - }, - async itemsArray(tempId) { - const myextension = _.findWhere(this.myex, { id: tempId }) - return [...myextension.itemsMap.values()] - }, - async openformDialog(id) { - const [tempId, itemId] = id.split(':') - const myextension = _.findWhere(this.myex, { id: tempId }) - if (itemId) { - const item = myextension.itemsMap.get(id) - this.formDialog.data = { - ...item, - myextension: tempId - } - } else { - this.formDialog.data.myextension = tempId - } - this.formDialog.data.currency = myextension.currency - this.formDialog.show = true - }, - async openUrlDialog(tempid) { - this.urlDialog.data = _.findWhere(this.myex, { id: tempid }) - this.qrValue = this.urlDialog.data.lnurlpay - - // Connecting to our websocket fired in tasks.py - this.connectWebocket(this.urlDialog.data.id) - - this.urlDialog.show = true - }, - async closeformDialog() { - this.formDialog.show = false - this.formDialog.data = {} - }, - async createInvoice(tempid) { - /////////////////////////////////////////////////// - ///Simple call to the api to create an invoice///// - /////////////////////////////////////////////////// - myex = _.findWhere(this.myex, { id: tempid }) - const wallet = _.findWhere(this.g.user.wallets, { id: myex.wallet }) - const data = { - myextension_id: tempid, - amount: this.invoiceAmount, - memo: 'MyExtension - ' + myex.name - } - await LNbits.api - .request('POST', `/myextension/api/v1/myex/payment`, wallet.inkey, data) - .then(response => { - this.qrValue = response.data.payment_request - this.connectWebocket(wallet.inkey) - }) - .catch(error => { - LNbits.utils.notifyApiError(error) - }) - }, - connectWebocket(myextension_id) { - ////////////////////////////////////////////////// - ///wait for pay action to happen and do a thing//// - /////////////////////////////////////////////////// - if (location.protocol !== 'http:') { - localUrl = - 'wss://' + - document.domain + - ':' + - location.port + - '/api/v1/ws/' + - myextension_id - } else { - localUrl = - 'ws://' + - document.domain + - ':' + - location.port + - '/api/v1/ws/' + - myextension_id - } - this.connection = new WebSocket(localUrl) - this.connection.onmessage = () => { - this.urlDialog.show = false - } - } }, /////////////////////////////////////////////////// //////LIFECYCLE FUNCTIONS RUNNING ON PAGE LOAD///// @@ -866,9 +653,6 @@ window.app = Vue.createApp({ this.getDeposits(), this.getLamassuTransactions() ]) - - // Legacy data loading - await this.getMyExtensions() }, computed: { diff --git a/tasks.py b/tasks.py index 387aaa0..f653c3f 100644 --- a/tasks.py +++ b/tasks.py @@ -6,8 +6,6 @@ from lnbits.core.services import websocket_updater from lnbits.tasks import register_invoice_listener from loguru import logger -from .crud import get_myextension, update_myextension -from .models import CreateMyExtensionData from .transaction_processor import poll_lamassu_transactions ####################################### @@ -18,6 +16,7 @@ from .transaction_processor import poll_lamassu_transactions async def wait_for_paid_invoices(): + """Invoice listener for DCA-related payments""" invoice_queue = asyncio.Queue() register_invoice_listener(invoice_queue, "ext_myextension") while True: @@ -44,35 +43,11 @@ async def hourly_transaction_polling(): await asyncio.sleep(300) -# Do somethhing when an invoice related top this extension is paid - - async def on_invoice_paid(payment: Payment) -> None: - if payment.extra.get("tag") != "MyExtension": - return - - myextension_id = payment.extra.get("myextensionId") - assert myextension_id, "myextensionId not set in invoice" - myextension = await get_myextension(myextension_id) - assert myextension, "MyExtension does not exist" - - # update something in the db - if payment.extra.get("lnurlwithdraw"): - total = myextension.total - payment.amount - else: - total = myextension.total + payment.amount - - myextension.total = total - await update_myextension(CreateMyExtensionData(**myextension.dict())) - - # here we could send some data to a websocket on - # wss:///api/v1/ws/ and then listen to it on - - some_payment_data = { - "name": myextension.name, - "amount": payment.amount, - "fee": payment.fee, - "checking_id": payment.checking_id, - } - - await websocket_updater(myextension_id, str(some_payment_data)) + """Handle DCA-related invoice payments""" + # DCA payments are handled internally by the transaction processor + # This function can be extended if needed for additional payment processing + if payment.extra.get("tag") in ["dca_distribution", "dca_commission"]: + logger.info(f"DCA payment processed: {payment.checking_id} - {payment.amount} sats") + # Could add websocket notifications here if needed + pass diff --git a/templates/myextension/_myextension.html b/templates/myextension/_myextension.html deleted file mode 100644 index f9425a6..0000000 --- a/templates/myextension/_myextension.html +++ /dev/null @@ -1,25 +0,0 @@ - - - -

Some more info about my excellent extension.

- Created by - Ben Arc. - Repo - MyExtension. -
-
-
diff --git a/templates/myextension/index.html b/templates/myextension/index.html index fc455c2..389f60a 100644 --- a/templates/myextension/index.html +++ b/templates/myextension/index.html @@ -265,93 +265,6 @@ - - -
-
-
MyExtension
-
-
- Export to CSV -
-
- - - - - ${ col.label } - - - - - - -
-
@@ -854,55 +767,5 @@ - - - - - - - - - -
- - - -
-
- lnurlpay -
-
- lnurlwithdraw -
-
- - - - -
-
-
- Close -
-
-
{% endblock %} diff --git a/templates/myextension/myextension.html b/templates/myextension/myextension.html deleted file mode 100644 index bc52349..0000000 --- a/templates/myextension/myextension.html +++ /dev/null @@ -1,68 +0,0 @@ - - - - -{% extends "public.html" %} {% block page %} -
-
- - - -
- Copy LNURL - -
-
-
-
-
- - -
Public page
-

- Most extensions have a public page that can be shared (this page will - still be accessible even if you have restricted access to your LNbits - install). -

- In this example when a user pays the LNURLpay it triggers an event via - a websocket waiting for the payment, which you can subscribe to - somewhere using wss://{your-lnbits}/api/v1/ws/{the-id-of-this-record} -

- - - - -
-
-
-{% endblock %} {% block scripts %} - -{% endblock %} diff --git a/views.py b/views.py index 34dd721..e6cf852 100644 --- a/views.py +++ b/views.py @@ -1,16 +1,10 @@ -# Description: Add your page endpoints here. +# Description: DCA Admin page endpoints. -from http import HTTPStatus - -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, Depends, Request from fastapi.responses import HTMLResponse from lnbits.core.models import User from lnbits.decorators import check_user_exists from lnbits.helpers import template_renderer -from lnbits.settings import settings - -from .crud import get_myextension -from .helpers import lnurler myextension_generic_router = APIRouter() @@ -19,79 +13,9 @@ def myextension_renderer(): return template_renderer(["myextension/templates"]) -####################################### -##### ADD YOUR PAGE ENDPOINTS HERE #### -####################################### - - -# Backend admin page - - +# DCA Admin page @myextension_generic_router.get("/", response_class=HTMLResponse) async def index(req: Request, user: User = Depends(check_user_exists)): return myextension_renderer().TemplateResponse( "myextension/index.html", {"request": req, "user": user.json()} ) - - -# Frontend shareable page - - -@myextension_generic_router.get("/{myextension_id}") -async def myextension(req: Request, myextension_id): - myex = await get_myextension(myextension_id) - if not myex: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - return myextension_renderer().TemplateResponse( - "myextension/myextension.html", - { - "request": req, - "myextension_id": myextension_id, - "lnurlpay": lnurler(myex.id, "myextension.api_lnurl_pay", req), - "web_manifest": f"/myextension/manifest/{myextension_id}.webmanifest", - }, - ) - - -# Manifest for public page, customise or remove manifest completely - - -@myextension_generic_router.get("/manifest/{myextension_id}.webmanifest") -async def manifest(myextension_id: str): - myextension = await get_myextension(myextension_id) - if not myextension: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - - return { - "short_name": settings.lnbits_site_title, - "name": myextension.name + " - " + settings.lnbits_site_title, - "icons": [ - { - "src": ( - settings.lnbits_custom_logo - if settings.lnbits_custom_logo - else "https://cdn.jsdelivr.net/gh/lnbits/lnbits@0.3.0/docs/logos/lnbits.png" - ), - "type": "image/png", - "sizes": "900x900", - } - ], - "start_url": "/myextension/" + myextension_id, - "background_color": "#1F2234", - "description": "Minimal extension to build on", - "display": "standalone", - "scope": "/myextension/" + myextension_id, - "theme_color": "#1F2234", - "shortcuts": [ - { - "name": myextension.name + " - " + settings.lnbits_site_title, - "short_name": myextension.name, - "description": myextension.name + " - " + settings.lnbits_site_title, - "url": "/myextension/" + myextension_id, - } - ], - } diff --git a/views_api.py b/views_api.py index a8b2fd0..7924e2d 100644 --- a/views_api.py +++ b/views_api.py @@ -11,11 +11,6 @@ from lnbits.decorators import require_admin_key, require_invoice_key from starlette.exceptions import HTTPException from .crud import ( - create_myextension, - delete_myextension, - get_myextension, - get_myextensions, - update_myextension, # DCA CRUD operations create_dca_client, get_dca_clients, @@ -39,9 +34,7 @@ from .crud import ( get_all_lamassu_transactions, get_lamassu_transaction ) -from .helpers import lnurler from .models import ( - CreateMyExtensionData, CreatePayment, MyExtension, # DCA models CreateDcaClientData, DcaClient, UpdateDcaClientData, CreateDepositData, DcaDeposit, UpdateDepositStatusData, @@ -52,159 +45,6 @@ from .models import ( myextension_api_router = APIRouter() -# Note: we add the lnurl params to returns so the links -# are generated in the MyExtension model in models.py - -## Get all the records belonging to the user - - -@myextension_api_router.get("/api/v1/myex") -async def api_myextensions( - req: Request, # Withoutthe lnurl stuff this wouldnt be needed - wallet: WalletTypeInfo = Depends(require_invoice_key), -) -> list[MyExtension]: - wallet_ids = [wallet.wallet.id] - user = await get_user(wallet.wallet.user) - wallet_ids = user.wallet_ids if user else [] - myextensions = await get_myextensions(wallet_ids) - - # Populate lnurlpay and lnurlwithdraw for each instance. - # Without the lnurl stuff this wouldnt be needed. - for myex in myextensions: - myex.lnurlpay = lnurler(myex.id, "myextension.api_lnurl_pay", req) - myex.lnurlwithdraw = lnurler(myex.id, "myextension.api_lnurl_withdraw", req) - - return myextensions - - -## Get a single record - - -@myextension_api_router.get( - "/api/v1/myex/{myextension_id}", - dependencies=[Depends(require_invoice_key)], -) -async def api_myextension(myextension_id: str, req: Request) -> MyExtension: - myex = await get_myextension(myextension_id) - if not myex: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - # Populate lnurlpay and lnurlwithdraw. - # Without the lnurl stuff this wouldnt be needed. - myex.lnurlpay = lnurler(myex.id, "myextension.api_lnurl_pay", req) - myex.lnurlwithdraw = lnurler(myex.id, "myextension.api_lnurl_withdraw", req) - - return myex - - -## Create a new record - - -@myextension_api_router.post("/api/v1/myex", status_code=HTTPStatus.CREATED) -async def api_myextension_create( - req: Request, # Withoutthe lnurl stuff this wouldnt be needed - data: CreateMyExtensionData, - wallet: WalletTypeInfo = Depends(require_admin_key), -) -> MyExtension: - myex = await create_myextension(data) - - # Populate lnurlpay and lnurlwithdraw. - # Withoutthe lnurl stuff this wouldnt be needed. - myex.lnurlpay = lnurler(myex.id, "myextension.api_lnurl_pay", req) - myex.lnurlwithdraw = lnurler(myex.id, "myextension.api_lnurl_withdraw", req) - - return myex - - -## update a record - - -@myextension_api_router.put("/api/v1/myex/{myextension_id}") -async def api_myextension_update( - req: Request, # Withoutthe lnurl stuff this wouldnt be needed - data: CreateMyExtensionData, - myextension_id: str, - wallet: WalletTypeInfo = Depends(require_admin_key), -) -> MyExtension: - myex = await get_myextension(myextension_id) - if not myex: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - - if wallet.wallet.id != myex.wallet: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension." - ) - - for key, value in data.dict().items(): - setattr(myex, key, value) - - myex = await update_myextension(data) - - # Populate lnurlpay and lnurlwithdraw. - # Without the lnurl stuff this wouldnt be needed. - myex.lnurlpay = lnurler(myex.id, "myextension.api_lnurl_pay", req) - myex.lnurlwithdraw = lnurler(myex.id, "myextension.api_lnurl_withdraw", req) - - return myex - - -## Delete a record - - -@myextension_api_router.delete("/api/v1/myex/{myextension_id}") -async def api_myextension_delete( - myextension_id: str, wallet: WalletTypeInfo = Depends(require_admin_key) -): - myex = await get_myextension(myextension_id) - - if not myex: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - - if myex.wallet != wallet.wallet.id: - raise HTTPException( - status_code=HTTPStatus.FORBIDDEN, detail="Not your MyExtension." - ) - - await delete_myextension(myextension_id) - return - - -# ANY OTHER ENDPOINTS YOU NEED - -## This endpoint creates a payment - - -@myextension_api_router.post("/api/v1/myex/payment", status_code=HTTPStatus.CREATED) -async def api_myextension_create_invoice(data: CreatePayment) -> dict: - myextension = await get_myextension(data.myextension_id) - - if not myextension: - raise HTTPException( - status_code=HTTPStatus.NOT_FOUND, detail="MyExtension does not exist." - ) - - # we create a payment and add some tags, - # so tasks.py can grab the payment once its paid - - payment = await create_invoice( - wallet_id=myextension.wallet, - amount=data.amount, - memo=( - f"{data.memo} to {myextension.name}" if data.memo else f"{myextension.name}" - ), - extra={ - "tag": "myextension", - "amount": data.amount, - }, - ) - - return {"payment_hash": payment.payment_hash, "payment_request": payment.bolt11} - ################################################### ################ DCA API ENDPOINTS ################ diff --git a/views_lnurl.py b/views_lnurl.py deleted file mode 100644 index c953fe2..0000000 --- a/views_lnurl.py +++ /dev/null @@ -1,146 +0,0 @@ -# Description: Extensions that use LNURL usually have a few endpoints in views_lnurl.py. - -from http import HTTPStatus -from typing import Optional - -import shortuuid -from fastapi import APIRouter, Query, Request -from lnbits.core.services import create_invoice, pay_invoice -from loguru import logger - -from .crud import get_myextension - -################################################# -########### A very simple LNURLpay ############## -# https://github.com/lnurl/luds/blob/luds/06.md # -################################################# -################################################# - -myextension_lnurl_router = APIRouter() - - -@myextension_lnurl_router.get( - "/api/v1/lnurl/pay/{myextension_id}", - status_code=HTTPStatus.OK, - name="myextension.api_lnurl_pay", -) -async def api_lnurl_pay( - request: Request, - myextension_id: str, -): - myextension = await get_myextension(myextension_id) - if not myextension: - return {"status": "ERROR", "reason": "No myextension found"} - return { - "callback": str( - request.url_for( - "myextension.api_lnurl_pay_callback", myextension_id=myextension_id - ) - ), - "maxSendable": myextension.lnurlpayamount * 1000, - "minSendable": myextension.lnurlpayamount * 1000, - "metadata": '[["text/plain", "' + myextension.name + '"]]', - "tag": "payRequest", - } - - -@myextension_lnurl_router.get( - "/api/v1/lnurl/paycb/{myextension_id}", - status_code=HTTPStatus.OK, - name="myextension.api_lnurl_pay_callback", -) -async def api_lnurl_pay_cb( - request: Request, - myextension_id: str, - amount: int = Query(...), -): - myextension = await get_myextension(myextension_id) - logger.debug(myextension) - if not myextension: - return {"status": "ERROR", "reason": "No myextension found"} - - payment = await create_invoice( - wallet_id=myextension.wallet, - amount=int(amount / 1000), - memo=myextension.name, - unhashed_description=f'[["text/plain", "{myextension.name}"]]'.encode(), - extra={ - "tag": "MyExtension", - "myextensionId": myextension_id, - "extra": request.query_params.get("amount"), - }, - ) - return { - "pr": payment.bolt11, - "routes": [], - "successAction": {"tag": "message", "message": f"Paid {myextension.name}"}, - } - - -################################################# -######## A very simple LNURLwithdraw ############ -# https://github.com/lnurl/luds/blob/luds/03.md # -################################################# -## withdraw is unlimited, look at withdraw ext ## -## for more advanced withdraw options ## -################################################# - - -@myextension_lnurl_router.get( - "/api/v1/lnurl/withdraw/{myextension_id}", - status_code=HTTPStatus.OK, - name="myextension.api_lnurl_withdraw", -) -async def api_lnurl_withdraw( - request: Request, - myextension_id: str, -): - myextension = await get_myextension(myextension_id) - if not myextension: - return {"status": "ERROR", "reason": "No myextension found"} - k1 = shortuuid.uuid(name=myextension.id) - return { - "tag": "withdrawRequest", - "callback": str( - request.url_for( - "myextension.api_lnurl_withdraw_callback", myextension_id=myextension_id - ) - ), - "k1": k1, - "defaultDescription": myextension.name, - "maxWithdrawable": myextension.lnurlwithdrawamount * 1000, - "minWithdrawable": myextension.lnurlwithdrawamount * 1000, - } - - -@myextension_lnurl_router.get( - "/api/v1/lnurl/withdrawcb/{myextension_id}", - status_code=HTTPStatus.OK, - name="myextension.api_lnurl_withdraw_callback", -) -async def api_lnurl_withdraw_cb( - myextension_id: str, - pr: Optional[str] = None, - k1: Optional[str] = None, -): - assert k1, "k1 is required" - assert pr, "pr is required" - myextension = await get_myextension(myextension_id) - if not myextension: - return {"status": "ERROR", "reason": "No myextension found"} - - k1_check = shortuuid.uuid(name=myextension.id) - if k1_check != k1: - return {"status": "ERROR", "reason": "Wrong k1 check provided"} - - await pay_invoice( - wallet_id=myextension.wallet, - payment_request=pr, - max_sat=int(myextension.lnurlwithdrawamount * 1000), - extra={ - "tag": "MyExtension", - "myextensionId": myextension_id, - "lnurlwithdraw": True, - }, - ) - return {"status": "OK"}