diff --git a/lnbits/extensions/cashu/README.md b/lnbits/extensions/cashu/README.md
new file mode 100644
index 000000000..8f53b474b
--- /dev/null
+++ b/lnbits/extensions/cashu/README.md
@@ -0,0 +1,11 @@
+# Cashu
+
+## Create ecash mint for pegging in/out of ecash
+
+
+
+### Usage
+
+1. Enable extension
+2. Create a Mint
+3. Share wallet
diff --git a/lnbits/extensions/cashu/__init__.py b/lnbits/extensions/cashu/__init__.py
new file mode 100644
index 000000000..fa549ad2e
--- /dev/null
+++ b/lnbits/extensions/cashu/__init__.py
@@ -0,0 +1,25 @@
+import asyncio
+
+from fastapi import APIRouter
+
+from lnbits.db import Database
+from lnbits.helpers import template_renderer
+from lnbits.tasks import catch_everything_and_restart
+
+db = Database("ext_cashu")
+
+cashu_ext: APIRouter = APIRouter(prefix="/cashu", tags=["TPoS"])
+
+
+def cashu_renderer():
+ return template_renderer(["lnbits/extensions/cashu/templates"])
+
+
+from .tasks import wait_for_paid_invoices
+from .views import * # noqa
+from .views_api import * # noqa
+
+
+def cashu_start():
+ loop = asyncio.get_event_loop()
+ loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
diff --git a/lnbits/extensions/cashu/config.json b/lnbits/extensions/cashu/config.json
new file mode 100644
index 000000000..c688b22c2
--- /dev/null
+++ b/lnbits/extensions/cashu/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Cashu Ecash",
+ "short_description": "Ecash mints with LN peg in/out",
+ "icon": "approval",
+ "contributors": ["shinobi", "arcbtc", "calle"]
+}
diff --git a/lnbits/extensions/cashu/crud.py b/lnbits/extensions/cashu/crud.py
new file mode 100644
index 000000000..ce83653fc
--- /dev/null
+++ b/lnbits/extensions/cashu/crud.py
@@ -0,0 +1,50 @@
+from typing import List, Optional, Union
+
+from lnbits.helpers import urlsafe_short_hash
+
+from . import db
+from .models import Cashu, Pegs
+
+
+async def create_cashu(wallet_id: str, data: Cashu) -> Cashu:
+ cashu_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO cashu.cashu (id, wallet, name, tickershort, fraction, maxsats, coins)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ cashu_id,
+ wallet_id,
+ data.name,
+ data.tickershort,
+ data.fraction,
+ data.maxsats,
+ data.coins
+ ),
+ )
+
+ cashu = await get_cashu(cashu_id)
+ assert cashu, "Newly created cashu couldn't be retrieved"
+ return cashu
+
+
+async def get_cashu(cashu_id: str) -> Optional[Cashu]:
+ row = await db.fetchone("SELECT * FROM cashu.cashu WHERE id = ?", (cashu_id,))
+ return Cashu(**row) if row else None
+
+
+async def get_cashus(wallet_ids: Union[str, List[str]]) -> List[Cashu]:
+ if isinstance(wallet_ids, str):
+ wallet_ids = [wallet_ids]
+
+ q = ",".join(["?"] * len(wallet_ids))
+ rows = await db.fetchall(
+ f"SELECT * FROM cashu.cashu WHERE wallet IN ({q})", (*wallet_ids,)
+ )
+
+ return [Cashu(**row) for row in rows]
+
+
+async def delete_cashu(cashu_id: str) -> None:
+ await db.execute("DELETE FROM cashu.cashu WHERE id = ?", (cashu_id,))
diff --git a/lnbits/extensions/cashu/migrations.py b/lnbits/extensions/cashu/migrations.py
new file mode 100644
index 000000000..95dc48152
--- /dev/null
+++ b/lnbits/extensions/cashu/migrations.py
@@ -0,0 +1,33 @@
+async def m001_initial(db):
+ """
+ Initial cashu table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE cashu.cashu (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ name TEXT NOT NULL,
+ tickershort TEXT NOT NULL,
+ fraction BOOL,
+ maxsats INT,
+ coins INT
+
+ );
+ """
+ )
+
+ """
+ Initial cashus table.
+ """
+ await db.execute(
+ """
+ CREATE TABLE cashu.pegs (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ inout BOOL NOT NULL,
+ amount INT
+ );
+ """
+ )
+
diff --git a/lnbits/extensions/cashu/models.py b/lnbits/extensions/cashu/models.py
new file mode 100644
index 000000000..0de153622
--- /dev/null
+++ b/lnbits/extensions/cashu/models.py
@@ -0,0 +1,34 @@
+from sqlite3 import Row
+from typing import Optional
+
+from fastapi import Query
+from pydantic import BaseModel
+
+
+class Cashu(BaseModel):
+ id: str = Query(None)
+ name: str = Query(None)
+ wallet: str = Query(None)
+ tickershort: str
+ fraction: bool = Query(None)
+ maxsats: int = Query(0)
+ coins: int = Query(0)
+
+
+ @classmethod
+ def from_row(cls, row: Row) -> "TPoS":
+ return cls(**dict(row))
+
+class Pegs(BaseModel):
+ id: str
+ wallet: str
+ inout: str
+ amount: str
+
+
+ @classmethod
+ def from_row(cls, row: Row) -> "TPoS":
+ return cls(**dict(row))
+
+class PayLnurlWData(BaseModel):
+ lnurl: str
\ No newline at end of file
diff --git a/lnbits/extensions/cashu/tasks.py b/lnbits/extensions/cashu/tasks.py
new file mode 100644
index 000000000..fe00a5918
--- /dev/null
+++ b/lnbits/extensions/cashu/tasks.py
@@ -0,0 +1,70 @@
+import asyncio
+import json
+
+from lnbits.core import db as core_db
+from lnbits.core.crud import create_payment
+from lnbits.core.models import Payment
+from lnbits.helpers import urlsafe_short_hash
+from lnbits.tasks import internal_invoice_queue, register_invoice_listener
+
+from .crud import get_cashu
+
+
+async def wait_for_paid_invoices():
+ invoice_queue = asyncio.Queue()
+ register_invoice_listener(invoice_queue)
+
+ while True:
+ payment = await invoice_queue.get()
+ await on_invoice_paid(payment)
+
+
+async def on_invoice_paid(payment: Payment) -> None:
+ if payment.extra.get("tag") == "cashu" and payment.extra.get("tipSplitted"):
+ # already splitted, ignore
+ return
+
+ # now we make some special internal transfers (from no one to the receiver)
+ cashu = await get_cashu(payment.extra.get("cashuId"))
+ tipAmount = payment.extra.get("tipAmount")
+
+ if tipAmount is None:
+ # no tip amount
+ return
+
+ tipAmount = tipAmount * 1000
+ amount = payment.amount - tipAmount
+
+ # mark the original payment with one extra key, "splitted"
+ # (this prevents us from doing this process again and it's informative)
+ # and reduce it by the amount we're going to send to the producer
+ await core_db.execute(
+ """
+ UPDATE apipayments
+ SET extra = ?, amount = ?
+ WHERE hash = ?
+ AND checking_id NOT LIKE 'internal_%'
+ """,
+ (
+ json.dumps(dict(**payment.extra, tipSplitted=True)),
+ amount,
+ payment.payment_hash,
+ ),
+ )
+
+ # perform the internal transfer using the same payment_hash
+ internal_checking_id = f"internal_{urlsafe_short_hash()}"
+ await create_payment(
+ wallet_id=cashu.tip_wallet,
+ checking_id=internal_checking_id,
+ payment_request="",
+ payment_hash=payment.payment_hash,
+ amount=tipAmount,
+ memo=f"Tip for {payment.memo}",
+ pending=False,
+ extra={"tipSplitted": True},
+ )
+
+ # manually send this for now
+ await internal_invoice_queue.put(internal_checking_id)
+ return
diff --git a/lnbits/extensions/cashu/templates/cashu/_api_docs.html b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
new file mode 100644
index 000000000..7378eb084
--- /dev/null
+++ b/lnbits/extensions/cashu/templates/cashu/_api_docs.html
@@ -0,0 +1,79 @@
+
+ Make Ecash mints with peg in/out to a wallet, that can create and manage ecash.
+ GET /cashu/api/v1/cashus
+ Headers
+ {"X-Api-Key": <invoice_key>}
+ Body (application/json)
+
+ Returns 200 OK (application/json)
+
+ [<cashu_object>, ...]
+ Curl example
+ curl -X GET {{ request.base_url }}cashu/api/v1/cashus -H "X-Api-Key:
+ <invoice_key>"
+
+ POST /cashu/api/v1/cashus
+ Headers
+ {"X-Api-Key": <invoice_key>}
+ Body (application/json)
+ {"name": <string>, "currency": <string*ie USD*>}
+
+ Returns 201 CREATED (application/json)
+
+ {"currency": <string>, "id": <string>, "name":
+ <string>, "wallet": <string>}
+ Curl example
+ curl -X POST {{ request.base_url }}cashu/api/v1/cashus -d '{"name":
+ <string>, "currency": <string>}' -H "Content-type:
+ application/json" -H "X-Api-Key: <admin_key>"
+
+ DELETE
+ /cashu/api/v1/cashus/<cashu_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+ Returns 204 NO CONTENT
+
+
Curl example
+ curl -X DELETE {{ request.base_url
+ }}cashu/api/v1/cashus/<cashu_id> -H "X-Api-Key: <admin_key>"
+
+
+ {{receive.lnurl.domain}} is requesting an invoice: +
+ {% endraw %} {% if LNBITS_DENOMINATION != 'sats' %} +
+ Description: {{ parse.invoice.description }}
+ Expire date: {{ parse.invoice.expireDate }}
+ Hash: {{ parse.invoice.hash }}
+
+ Authenticate with {{ parse.lnurlauth.domain }}? +
++ For every website and for every LNbits wallet, a new keypair + will be deterministically generated so your identity can't be + tied to your LNbits wallet or linked across websites. No other + data will be shared with {{ parse.lnurlauth.domain }}. +
+Your public key for {{ parse.lnurlauth.domain }} is:
+
+ {{ parse.lnurlauth.pubkey }}
+
+ BOOKMARK THIS PAGE! If only mobile you can also click the 3 dots + and "Save to homescreen"/"Install app"! +
++ Ecash is a bearer asset, meaning you have the funds saved on this + page, losing the page without exporting the page will mean you will + lose the funds. +
+