diff --git a/lnbits/extensions/scrub/README.md b/lnbits/extensions/scrub/README.md
deleted file mode 100644
index 3b8d0b2d7..000000000
--- a/lnbits/extensions/scrub/README.md
+++ /dev/null
@@ -1,30 +0,0 @@
-# Scrub
-
-## Automatically forward funds (Scrub) that get paid to the wallet to an LNURLpay or Lightning Address
-
-SCRUB is a small but handy extension that allows a user to take advantage of all the functionalities inside **LNbits** and upon a payment received to your LNbits wallet, automatically forward it to your desired wallet via LNURL or LNAddress!
-
-Only whole values, integers, are Scrubbed, amounts will be rounded down (example: 6.3 will be 6)! The decimals, if existing, will be kept in your wallet!
-
-[**Wallets supporting LNURL**](https://github.com/fiatjaf/awesome-lnurl#wallets)
-
-## Usage
-
-1. Create an scrub (New Scrub link)\
- 
-
- - select the wallet to be _scrubbed_
- - make a small description
- - enter either an LNURL pay or a lightning address
-
- Make sure your LNURL or LNaddress is correct!
-
-2. A new scrub will show on the _Scrub links_ section\
- 
-
- - only one scrub can be created for each wallet!
- - You can _edit_ or _delete_ the Scrub at any time\
- 
-
-3. On your wallet, you'll see a transaction of a payment received and another right after it as apayment sent, marked with **#scrubed**\
- 
diff --git a/lnbits/extensions/scrub/__init__.py b/lnbits/extensions/scrub/__init__.py
deleted file mode 100644
index 29428af96..000000000
--- a/lnbits/extensions/scrub/__init__.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import asyncio
-
-from fastapi import APIRouter
-from fastapi.staticfiles import StaticFiles
-
-from lnbits.db import Database
-from lnbits.helpers import template_renderer
-from lnbits.tasks import catch_everything_and_restart
-
-db = Database("ext_scrub")
-
-scrub_static_files = [
- {
- "path": "/scrub/static",
- "app": StaticFiles(directory="lnbits/extensions/scrub/static"),
- "name": "scrub_static",
- }
-]
-
-scrub_ext: APIRouter = APIRouter(prefix="/scrub", tags=["scrub"])
-
-
-def scrub_renderer():
- return template_renderer(["lnbits/extensions/scrub/templates"])
-
-
-from .tasks import wait_for_paid_invoices
-from .views import * # noqa: F401,F403
-from .views_api import * # noqa: F401,F403
-
-
-def scrub_start():
- loop = asyncio.get_event_loop()
- loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
diff --git a/lnbits/extensions/scrub/config.json b/lnbits/extensions/scrub/config.json
deleted file mode 100644
index 93eb871ae..000000000
--- a/lnbits/extensions/scrub/config.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "name": "Scrub",
- "short_description": "Pass payments to LNURLp/LNaddress",
- "tile": "/scrub/static/image/scrub.png",
- "contributors": ["arcbtc", "talvasconcelos"]
-}
diff --git a/lnbits/extensions/scrub/crud.py b/lnbits/extensions/scrub/crud.py
deleted file mode 100644
index 1772a8c5b..000000000
--- a/lnbits/extensions/scrub/crud.py
+++ /dev/null
@@ -1,80 +0,0 @@
-from typing import List, Optional, Union
-
-from lnbits.helpers import urlsafe_short_hash
-
-from . import db
-from .models import CreateScrubLink, ScrubLink
-
-
-async def create_scrub_link(data: CreateScrubLink) -> ScrubLink:
- scrub_id = urlsafe_short_hash()
- await db.execute(
- """
- INSERT INTO scrub.scrub_links (
- id,
- wallet,
- description,
- payoraddress
- )
- VALUES (?, ?, ?, ?)
- """,
- (
- scrub_id,
- data.wallet,
- data.description,
- data.payoraddress,
- ),
- )
- link = await get_scrub_link(scrub_id)
- assert link, "Newly created link couldn't be retrieved"
- return link
-
-
-async def get_scrub_link(link_id: str) -> Optional[ScrubLink]:
- row = await db.fetchone("SELECT * FROM scrub.scrub_links WHERE id = ?", (link_id,))
- return ScrubLink(**row) if row else None
-
-
-async def get_scrub_links(wallet_ids: Union[str, List[str]]) -> List[ScrubLink]:
- if isinstance(wallet_ids, str):
- wallet_ids = [wallet_ids]
-
- q = ",".join(["?"] * len(wallet_ids))
- rows = await db.fetchall(
- f"""
- SELECT * FROM scrub.scrub_links WHERE wallet IN ({q})
- ORDER BY id
- """,
- (*wallet_ids,),
- )
- return [ScrubLink(**row) for row in rows]
-
-
-async def update_scrub_link(link_id: int, **kwargs) -> Optional[ScrubLink]:
- q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
- await db.execute(
- f"UPDATE scrub.scrub_links SET {q} WHERE id = ?",
- (*kwargs.values(), link_id),
- )
- row = await db.fetchone("SELECT * FROM scrub.scrub_links WHERE id = ?", (link_id,))
- return ScrubLink(**row) if row else None
-
-
-async def delete_scrub_link(link_id: int) -> None:
- await db.execute("DELETE FROM scrub.scrub_links WHERE id = ?", (link_id,))
-
-
-async def get_scrub_by_wallet(wallet_id) -> Optional[ScrubLink]:
- row = await db.fetchone(
- "SELECT * from scrub.scrub_links WHERE wallet = ?",
- (wallet_id,),
- )
- return ScrubLink(**row) if row else None
-
-
-async def unique_scrubed_wallet(wallet_id):
- (row,) = await db.fetchone(
- "SELECT COUNT(wallet) FROM scrub.scrub_links WHERE wallet = ?",
- (wallet_id,),
- )
- return row
diff --git a/lnbits/extensions/scrub/migrations.py b/lnbits/extensions/scrub/migrations.py
deleted file mode 100644
index f1f4badef..000000000
--- a/lnbits/extensions/scrub/migrations.py
+++ /dev/null
@@ -1,14 +0,0 @@
-async def m001_initial(db):
- """
- Initial scrub table.
- """
- await db.execute(
- """
- CREATE TABLE scrub.scrub_links (
- id TEXT PRIMARY KEY,
- wallet TEXT NOT NULL,
- description TEXT NOT NULL,
- payoraddress TEXT NOT NULL
- );
- """
- )
diff --git a/lnbits/extensions/scrub/models.py b/lnbits/extensions/scrub/models.py
deleted file mode 100644
index 8079f358a..000000000
--- a/lnbits/extensions/scrub/models.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from sqlite3 import Row
-
-from pydantic import BaseModel
-from starlette.requests import Request
-
-from lnbits.lnurl import encode as lnurl_encode
-
-
-class CreateScrubLink(BaseModel):
- wallet: str
- description: str
- payoraddress: str
-
-
-class ScrubLink(BaseModel):
- id: str
- wallet: str
- description: str
- payoraddress: str
-
- @classmethod
- def from_row(cls, row: Row) -> "ScrubLink":
- data = dict(row)
- return cls(**data)
-
- def lnurl(self, req: Request) -> str:
- url = req.url_for("scrub.api_lnurl_response", link_id=self.id)
- return lnurl_encode(url)
diff --git a/lnbits/extensions/scrub/static/image/scrub.png b/lnbits/extensions/scrub/static/image/scrub.png
deleted file mode 100644
index b3d4d24f2..000000000
Binary files a/lnbits/extensions/scrub/static/image/scrub.png and /dev/null differ
diff --git a/lnbits/extensions/scrub/static/js/index.js b/lnbits/extensions/scrub/static/js/index.js
deleted file mode 100644
index 439907921..000000000
--- a/lnbits/extensions/scrub/static/js/index.js
+++ /dev/null
@@ -1,143 +0,0 @@
-/* globals Quasar, Vue, _, VueQrcode, windowMixin, LNbits, LOCALE */
-
-Vue.component(VueQrcode.name, VueQrcode)
-
-var locationPath = [
- window.location.protocol,
- '//',
- window.location.host,
- window.location.pathname
-].join('')
-
-var mapScrubLink = obj => {
- obj._data = _.clone(obj)
- obj.date = Quasar.utils.date.formatDate(
- new Date(obj.time * 1000),
- 'YYYY-MM-DD HH:mm'
- )
- obj.amount = new Intl.NumberFormat(LOCALE).format(obj.amount)
- obj.print_url = [locationPath, 'print/', obj.id].join('')
- obj.pay_url = [locationPath, obj.id].join('')
- return obj
-}
-
-new Vue({
- el: '#vue',
- mixins: [windowMixin],
- data() {
- return {
- checker: null,
- payLinks: [],
- payLinksTable: {
- pagination: {
- rowsPerPage: 10
- }
- },
- formDialog: {
- show: false,
- data: {}
- },
- qrCodeDialog: {
- show: false,
- data: null
- }
- }
- },
- methods: {
- getScrubLinks() {
- LNbits.api
- .request(
- 'GET',
- '/scrub/api/v1/links?all_wallets=true',
- this.g.user.wallets[0].inkey
- )
- .then(response => {
- this.payLinks = response.data.map(mapScrubLink)
- })
- .catch(err => {
- clearInterval(this.checker)
- LNbits.utils.notifyApiError(err)
- })
- },
- closeFormDialog() {
- this.resetFormData()
- },
- openUpdateDialog(linkId) {
- const link = _.findWhere(this.payLinks, {id: linkId})
-
- this.formDialog.data = _.clone(link._data)
- this.formDialog.show = true
- },
- sendFormData() {
- const wallet = _.findWhere(this.g.user.wallets, {
- id: this.formDialog.data.wallet
- })
- let data = Object.freeze(this.formDialog.data)
- console.log(wallet, data)
-
- if (data.id) {
- this.updateScrubLink(wallet, data)
- } else {
- this.createScrubLink(wallet, data)
- }
- },
- resetFormData() {
- this.formDialog = {
- show: false,
- data: {}
- }
- },
- updateScrubLink(wallet, data) {
- LNbits.api
- .request('PUT', '/scrub/api/v1/links/' + data.id, wallet.adminkey, data)
- .then(response => {
- this.payLinks = _.reject(this.payLinks, obj => obj.id === data.id)
- this.payLinks.push(mapScrubLink(response.data))
- this.formDialog.show = false
- this.resetFormData()
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- },
- createScrubLink(wallet, data) {
- LNbits.api
- .request('POST', '/scrub/api/v1/links', wallet.adminkey, data)
- .then(response => {
- console.log('RES', response)
- this.getScrubLinks()
- this.formDialog.show = false
- this.resetFormData()
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- },
- deleteScrubLink(linkId) {
- var link = _.findWhere(this.payLinks, {id: linkId})
-
- LNbits.utils
- .confirmDialog('Are you sure you want to delete this pay link?')
- .onOk(() => {
- LNbits.api
- .request(
- 'DELETE',
- '/scrub/api/v1/links/' + linkId,
- _.findWhere(this.g.user.wallets, {id: link.wallet}).adminkey
- )
- .then(response => {
- this.payLinks = _.reject(this.payLinks, obj => obj.id === linkId)
- })
- .catch(err => {
- LNbits.utils.notifyApiError(err)
- })
- })
- }
- },
- created() {
- if (this.g.user.wallets.length) {
- var getScrubLinks = this.getScrubLinks
- getScrubLinks()
- }
- }
-})
diff --git a/lnbits/extensions/scrub/tasks.py b/lnbits/extensions/scrub/tasks.py
deleted file mode 100644
index 26249bb1b..000000000
--- a/lnbits/extensions/scrub/tasks.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import asyncio
-import json
-from http import HTTPStatus
-from math import floor
-from urllib.parse import urlparse
-
-import httpx
-from fastapi import HTTPException
-
-from lnbits import bolt11
-from lnbits.core.models import Payment
-from lnbits.core.services import pay_invoice
-from lnbits.helpers import get_current_extension_name
-from lnbits.tasks import register_invoice_listener
-
-from .crud import get_scrub_by_wallet
-
-
-async def wait_for_paid_invoices():
- invoice_queue = asyncio.Queue()
- register_invoice_listener(invoice_queue, get_current_extension_name())
-
- while True:
- payment = await invoice_queue.get()
- await on_invoice_paid(payment)
-
-
-async def on_invoice_paid(payment: Payment):
- # (avoid loops)
- if payment.extra.get("tag") == "scrubed":
- # already scrubbed
- return
-
- scrub_link = await get_scrub_by_wallet(payment.wallet_id)
-
- if not scrub_link:
- return
-
- from lnbits.core.views.api import api_lnurlscan
-
- # DECODE LNURLP OR LNADDRESS
- data = await api_lnurlscan(scrub_link.payoraddress)
-
- # I REALLY HATE THIS DUPLICATION OF CODE!! CORE/VIEWS/API.PY, LINE 267
- domain = urlparse(data["callback"]).netloc
- rounded_amount = floor(payment.amount / 1000) * 1000
-
- async with httpx.AsyncClient() as client:
- try:
- r = await client.get(
- data["callback"],
- params={"amount": rounded_amount},
- timeout=40,
- )
- if r.is_error:
- raise httpx.ConnectError("issue with scrub callback")
- except (httpx.ConnectError, httpx.RequestError):
- raise HTTPException(
- status_code=HTTPStatus.BAD_REQUEST,
- detail=f"Failed to connect to {domain}.",
- )
-
- params = json.loads(r.text)
- if params.get("status") == "ERROR":
- raise HTTPException(
- status_code=HTTPStatus.BAD_REQUEST,
- detail=f"{domain} said: '{params.get('reason', '')}'",
- )
-
- invoice = bolt11.decode(params["pr"])
-
- if invoice.amount_msat != rounded_amount:
- raise HTTPException(
- status_code=HTTPStatus.BAD_REQUEST,
- detail=f"{domain} returned an invalid invoice. Expected {payment.amount} msat, got {invoice.amount_msat}.",
- )
-
- payment_hash = await pay_invoice(
- wallet_id=payment.wallet_id,
- payment_request=params["pr"],
- description=data["description"],
- extra={"tag": "scrubed"},
- )
-
- return {
- "payment_hash": payment_hash,
- # maintain backwards compatibility with API clients:
- "checking_id": payment_hash,
- }
diff --git a/lnbits/extensions/scrub/templates/scrub/_api_docs.html b/lnbits/extensions/scrub/templates/scrub/_api_docs.html
deleted file mode 100644
index ae3f44d88..000000000
--- a/lnbits/extensions/scrub/templates/scrub/_api_docs.html
+++ /dev/null
@@ -1,136 +0,0 @@
-
- WARNING: LNURL must be used over https or TOR
- Exploring LNURL and finding use cases, is really helping inform
- lightning protocol development, rather than the protocol dictating how
- lightning-network should be engaged with.
- GET /scrub/api/v1/links
- Headers
- {"X-Api-Key": <invoice_key>}
- Body (application/json)
-
- Returns 200 OK (application/json)
-
- [<pay_link_object>, ...]
- Curl example
- curl -X GET {{ request.base_url }}scrub/api/v1/links?all_wallets=true
- -H "X-Api-Key: {{ user.wallets[0].inkey }}"
-
- GET
- /scrub/api/v1/links/<scrub_id>
- Headers
- {"X-Api-Key": <invoice_key>}
- Body (application/json)
-
- Returns 200 OK (application/json)
-
- {"id": <string>, "wallet": <string>, "description":
- <string>, "payoraddress": <string>}
- Curl example
- curl -X GET {{ request.base_url }}scrub/api/v1/links/<pay_id>
- -H "X-Api-Key: {{ user.wallets[0].inkey }}"
-
- POST /scrub/api/v1/links
- Headers
- {"X-Api-Key": <admin_key>}
- Body (application/json)
- {"wallet": <string>, "description": <string>,
- "payoraddress": <string>}
-
- Returns 201 CREATED (application/json)
-
- {"id": <string>, "wallet": <string>, "description":
- <string>, "payoraddress": <string>}
- Curl example
- curl -X POST {{ request.base_url }}scrub/api/v1/links -d '{"wallet":
- <string>, "description": <string>, "payoraddress":
- <string>}' -H "Content-type: application/json" -H "X-Api-Key: {{
- user.wallets[0].adminkey }}"
-
- PUT
- /scrub/api/v1/links/<pay_id>
- Headers
- {"X-Api-Key": <admin_key>}
- Body (application/json)
- {"wallet": <string>, "description": <string>,
- "payoraddress": <string>}
-
- Returns 200 OK (application/json)
-
- {"id": <string>, "wallet": <string>, "description":
- <string>, "payoraddress": <string>}
- Curl example
- curl -X PUT {{ request.base_url }}scrub/api/v1/links/<pay_id>
- -d '{"wallet": <string>, "description": <string>,
- "payoraddress": <string>}' -H "Content-type: application/json"
- -H "X-Api-Key: {{ user.wallets[0].adminkey }}"
-
- DELETE
- /scrub/api/v1/links/<pay_id>
- Headers
- {"X-Api-Key": <admin_key>}
- Returns 204 NO CONTENT
-
-
Curl example
- curl -X DELETE {{ request.base_url
- }}scrub/api/v1/links/<pay_id> -H "X-Api-Key: {{
- user.wallets[0].adminkey }}"
-
-
- LNURL is a range of lightning-network standards that allow us to use
- lightning-network differently. An LNURL-pay is a link that wallets use
- to fetch an invoice from a server on-demand. The link or QR code is
- fixed, but each time it is read by a compatible wallet a new QR code is
- issued by the service. It can be used to activate machines without them
- having to maintain an electronic screen to generate and show invoices
- locally, or to sell any predefined good or service automatically.
-
- Automatically forward funds (Scrub) that get paid to the LNbits
- wallet, to an LNURLpay or Lightning Address.
-
- More info in Scrub's
- readme.
-
- Important: wallet will need a float to account for - any fees, before being able to push a payment -
-