diff --git a/lnbits/extensions/copilot/README.md b/lnbits/extensions/copilot/README.md
new file mode 100644
index 00000000..68a38809
--- /dev/null
+++ b/lnbits/extensions/copilot/README.md
@@ -0,0 +1,3 @@
+# Stream Copilot
+
+Tool to help streamers accept sats for tips
diff --git a/lnbits/extensions/copilot/__init__.py b/lnbits/extensions/copilot/__init__.py
new file mode 100644
index 00000000..3b41c4c9
--- /dev/null
+++ b/lnbits/extensions/copilot/__init__.py
@@ -0,0 +1,13 @@
+from quart import Blueprint
+from lnbits.db import Database
+
+db = Database("ext_copilot")
+
+
+copilot_ext: Blueprint = Blueprint(
+ "copilot", __name__, static_folder="static", template_folder="templates"
+)
+
+
+from .views_api import * # noqa
+from .views import * # noqa
diff --git a/lnbits/extensions/copilot/config.json b/lnbits/extensions/copilot/config.json
new file mode 100644
index 00000000..02275230
--- /dev/null
+++ b/lnbits/extensions/copilot/config.json
@@ -0,0 +1,8 @@
+{
+ "name": "StreamCopilot",
+ "short_description": "Tipping and animations for streamers",
+ "icon": "face",
+ "contributors": [
+ "arcbtc"
+ ]
+}
diff --git a/lnbits/extensions/copilot/crud.py b/lnbits/extensions/copilot/crud.py
new file mode 100644
index 00000000..67f9b92e
--- /dev/null
+++ b/lnbits/extensions/copilot/crud.py
@@ -0,0 +1,71 @@
+from typing import List, Optional, Union
+
+# from lnbits.db import open_ext_db
+from . import db
+from .models import Copilots
+
+from lnbits.helpers import urlsafe_short_hash
+
+from quart import jsonify
+
+
+###############COPILOTS##########################
+
+
+async def create_copilot(
+ title: str,
+ user: str,
+ animation: str = None,
+ show_message: Optional[str] = None,
+ amount: Optional[str] = None,
+ lnurl_title: Optional[str] = None,
+) -> Copilots:
+ copilot_id = urlsafe_short_hash()
+
+ await db.execute(
+ """
+ INSERT INTO copilots (
+ id,
+ user,
+ title,
+ animation,
+ show_message,
+ amount,
+ lnurl_title
+ )
+ VALUES (?, ?, ?, ?, ?, ?)
+ """,
+ (
+ copilot_id,
+ user,
+ title,
+ animation,
+ show_message,
+ amount,
+ lnurl_title
+ ),
+ )
+ return await get_copilot(copilot_id)
+
+
+async def update_copilot(copilot_id: str, **kwargs) -> Optional[Copilots]:
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE copilots SET {q} WHERE id = ?", (*kwargs.values(), copilot_id)
+ )
+ row = await db.fetchone("SELECT * FROM copilots WHERE id = ?", (copilot_id,))
+ return Copilots.from_row(row) if row else None
+
+
+async def get_copilot(copilot_id: str) -> Copilots:
+ row = await db.fetchone("SELECT * FROM copilots WHERE id = ?", (copilot_id,))
+ return Copilots.from_row(row) if row else None
+
+
+async def get_copilots(user: str) -> List[Copilots]:
+ rows = await db.fetchall("SELECT * FROM copilots WHERE user = ?", (user,))
+ return [Copilots.from_row(row) for row in rows]
+
+
+async def delete_copilot(copilot_id: str) -> None:
+ await db.execute("DELETE FROM copilots WHERE id = ?", (copilot_id,))
diff --git a/lnbits/extensions/copilot/migrations.py b/lnbits/extensions/copilot/migrations.py
new file mode 100644
index 00000000..6f46bf21
--- /dev/null
+++ b/lnbits/extensions/copilot/migrations.py
@@ -0,0 +1,19 @@
+async def m001_initial(db):
+ """
+ Initial copilot table.
+ """
+
+ await db.execute(
+ """
+ CREATE TABLE IF NOT EXISTS copilots (
+ id TEXT NOT NULL PRIMARY KEY,
+ user TEXT,
+ title TEXT,
+ animation INTEGER,
+ show_message TEXT,
+ amount INTEGER,
+ lnurl_title TEXT,
+ timestamp TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now'))
+ );
+ """
+ )
diff --git a/lnbits/extensions/copilot/models.py b/lnbits/extensions/copilot/models.py
new file mode 100644
index 00000000..b9d3325f
--- /dev/null
+++ b/lnbits/extensions/copilot/models.py
@@ -0,0 +1,24 @@
+from sqlite3 import Row
+from typing import NamedTuple
+import time
+
+
+class Copilots(NamedTuple):
+ id: str
+ user: str
+ title: str
+ animation: str
+ show_message: bool
+ amount: int
+ lnurl_title: str
+
+ @classmethod
+ def from_row(cls, row: Row) -> "Charges":
+ return cls(**dict(row))
+
+ @property
+ def paid(self):
+ if self.balance >= self.amount:
+ return True
+ else:
+ return False
diff --git a/lnbits/extensions/copilot/templates/copilot/_api_docs.html b/lnbits/extensions/copilot/templates/copilot/_api_docs.html
new file mode 100644
index 00000000..859eefe4
--- /dev/null
+++ b/lnbits/extensions/copilot/templates/copilot/_api_docs.html
@@ -0,0 +1,169 @@
+
+ StreamCopilot: get tips and show an animation
+
+ Created by, Ben Arc
+ POST /satspay/api/v1/charge
+ Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<charge_object>, ...]
+ Curl example
+ curl -X POST {{ request.url_root }}api/v1/charge -d
+ '{"onchainwallet": <string, watchonly_wallet_id>,
+ "description": <string>, "webhook":<string>, "time":
+ <integer>, "amount": <integer>, "lnbitswallet":
+ <string, lnbits_wallet_id>}' -H "Content-type:
+ application/json" -H "X-Api-Key: {{g.user.wallets[0].adminkey }}"
+
+ PUT
+ /satspay/api/v1/charge/<charge_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<charge_object>, ...]
+ Curl example
+ curl -X POST {{ request.url_root }}api/v1/charge/<charge_id>
+ -d '{"onchainwallet": <string, watchonly_wallet_id>,
+ "description": <string>, "webhook":<string>, "time":
+ <integer>, "amount": <integer>, "lnbitswallet":
+ <string, lnbits_wallet_id>}' -H "Content-type:
+ application/json" -H "X-Api-Key: {{g.user.wallets[0].adminkey }}"
+
+ GET
+ /satspay/api/v1/charge/<charge_id>
+ Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<charge_object>, ...]
+ Curl example
+ curl -X GET {{ request.url_root }}api/v1/charge/<charge_id>
+ -H "X-Api-Key: {{ g.user.wallets[0].inkey }}"
+
+ GET /satspay/api/v1/charges
+ Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<charge_object>, ...]
+ Curl example
+ curl -X GET {{ request.url_root }}api/v1/charges -H "X-Api-Key: {{
+ g.user.wallets[0].inkey }}"
+
+ DELETE
+ /satspay/api/v1/charge/<charge_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+ Returns 204 NO CONTENT
+
+ Curl example
+ curl -X DELETE {{ request.url_root
+ }}api/v1/charge/<charge_id> -H "X-Api-Key: {{
+ g.user.wallets[0].adminkey }}"
+
+ GET
+ /satspay/api/v1/charges/balance/<charge_id>
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<charge_object>, ...]
+ Curl example
+ curl -X GET {{ request.url_root
+ }}api/v1/charges/balance/<charge_id> -H "X-Api-Key: {{
+ g.user.wallets[0].inkey }}"
+
+