diff --git a/lnbits/extensions/events/README.md b/lnbits/extensions/events/README.md
new file mode 100644
index 00000000..11b62fec
--- /dev/null
+++ b/lnbits/extensions/events/README.md
@@ -0,0 +1,33 @@
+# Events
+
+## Sell tickets for events and use the built-in scanner for registering attendants
+
+Events alows you to make tickets for an event. Each ticket is in the form of a uniqque QR code. After registering, and paying for ticket, the user gets a QR code to present at registration/entrance.
+
+Events includes a shareable ticket scanner, which can be used to register attendees.
+
+## Usage
+
+1. Create an event\
+ 
+2. Fill out the event information:
+
+ - event name
+ - wallet (normally there's only one)
+ - event information
+ - closing date for event registration
+ - begin and end date of the event
+
+ 
+
+3. Share the event registration link\
+ 
+
+ - ticket example\
+ 
+
+ - QR code ticket, presented after invoice paid, to present at registration\
+ 
+
+4. Use the built-in ticket scanner to validate registered, and paid, attendees\
+ 
diff --git a/lnbits/extensions/events/__init__.py b/lnbits/extensions/events/__init__.py
new file mode 100644
index 00000000..da29358b
--- /dev/null
+++ b/lnbits/extensions/events/__init__.py
@@ -0,0 +1,19 @@
+from fastapi import APIRouter
+
+from lnbits.db import Database
+from lnbits.helpers import template_renderer
+
+db = Database("ext_events")
+
+
+events_ext: APIRouter = APIRouter(
+ prefix="/events",
+ tags=["Events"]
+)
+
+def events_renderer():
+ return template_renderer(["lnbits/extensions/events/templates"])
+
+from .views import * # noqa
+from .views_api import * # noqa
+
diff --git a/lnbits/extensions/events/config.json b/lnbits/extensions/events/config.json
new file mode 100644
index 00000000..6bc144ab
--- /dev/null
+++ b/lnbits/extensions/events/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Events",
+ "short_description": "Sell and register event tickets",
+ "icon": "local_activity",
+ "contributors": ["benarc"]
+}
diff --git a/lnbits/extensions/events/crud.py b/lnbits/extensions/events/crud.py
new file mode 100644
index 00000000..4a24b797
--- /dev/null
+++ b/lnbits/extensions/events/crud.py
@@ -0,0 +1,159 @@
+from typing import List, Optional, Union
+
+from lnbits.helpers import urlsafe_short_hash
+
+from . import db
+from .models import CreateEvent, Events, Tickets
+
+# TICKETS
+
+
+async def create_ticket(
+ payment_hash: str, wallet: str, event: str, name: str, email: str
+) -> Tickets:
+ await db.execute(
+ """
+ INSERT INTO events.ticket (id, wallet, event, name, email, registered, paid)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (payment_hash, wallet, event, name, email, False, False),
+ )
+
+ ticket = await get_ticket(payment_hash)
+ assert ticket, "Newly created ticket couldn't be retrieved"
+ return ticket
+
+
+async def set_ticket_paid(payment_hash: str) -> Tickets:
+ row = await db.fetchone("SELECT * FROM events.ticket WHERE id = ?", (payment_hash,))
+ if row[6] != True:
+ await db.execute(
+ """
+ UPDATE events.ticket
+ SET paid = true
+ WHERE id = ?
+ """,
+ (payment_hash,),
+ )
+
+ eventdata = await get_event(row[2])
+ assert eventdata, "Couldn't get event from ticket being paid"
+
+ sold = eventdata.sold + 1
+ amount_tickets = eventdata.amount_tickets - 1
+ await db.execute(
+ """
+ UPDATE events.events
+ SET sold = ?, amount_tickets = ?
+ WHERE id = ?
+ """,
+ (sold, amount_tickets, row[2]),
+ )
+
+ ticket = await get_ticket(payment_hash)
+ assert ticket, "Newly updated ticket couldn't be retrieved"
+ return ticket
+
+
+async def get_ticket(payment_hash: str) -> Optional[Tickets]:
+ row = await db.fetchone("SELECT * FROM events.ticket WHERE id = ?", (payment_hash,))
+ return Tickets(**row) if row else None
+
+
+async def get_tickets(wallet_ids: Union[str, List[str]]) -> List[Tickets]:
+ if isinstance(wallet_ids, str):
+ wallet_ids = [wallet_ids]
+
+ q = ",".join(["?"] * len(wallet_ids))
+ rows = await db.fetchall(
+ f"SELECT * FROM events.ticket WHERE wallet IN ({q})", (*wallet_ids,)
+ )
+ return [Tickets(**row) for row in rows]
+
+
+async def delete_ticket(payment_hash: str) -> None:
+ await db.execute("DELETE FROM events.ticket WHERE id = ?", (payment_hash,))
+
+
+# EVENTS
+
+
+async def create_event(
+ data: CreateEvent
+) -> Events:
+ event_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO events.events (id, wallet, name, info, closing_date, event_start_date, event_end_date, amount_tickets, price_per_ticket, sold)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ event_id,
+ data.wallet,
+ data.name,
+ data.info,
+ data.closing_date,
+ data.event_start_date,
+ data.event_end_date,
+ data.amount_tickets,
+ data.price_per_ticket,
+ 0,
+ ),
+ )
+
+ event = await get_event(event_id)
+ assert event, "Newly created event couldn't be retrieved"
+ return event
+
+
+async def update_event(event_id: str, **kwargs) -> Events:
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE events.events SET {q} WHERE id = ?", (*kwargs.values(), event_id)
+ )
+ event = await get_event(event_id)
+ assert event, "Newly updated event couldn't be retrieved"
+ return event
+
+
+async def get_event(event_id: str) -> Optional[Events]:
+ row = await db.fetchone("SELECT * FROM events.events WHERE id = ?", (event_id,))
+ return Events(**row) if row else None
+
+
+async def get_events(wallet_ids: Union[str, List[str]]) -> List[Events]:
+ if isinstance(wallet_ids, str):
+ wallet_ids = [wallet_ids]
+
+ q = ",".join(["?"] * len(wallet_ids))
+ rows = await db.fetchall(
+ f"SELECT * FROM events.events WHERE wallet IN ({q})", (*wallet_ids,)
+ )
+
+ return [Events(**row) for row in rows]
+
+
+async def delete_event(event_id: str) -> None:
+ await db.execute("DELETE FROM events.events WHERE id = ?", (event_id,))
+
+
+# EVENTTICKETS
+
+
+async def get_event_tickets(event_id: str, wallet_id: str) -> List[Tickets]:
+ rows = await db.fetchall(
+ "SELECT * FROM events.ticket WHERE wallet = ? AND event = ?",
+ (wallet_id, event_id),
+ )
+ return [Tickets(**row) for row in rows]
+
+
+async def reg_ticket(ticket_id: str) -> List[Tickets]:
+ await db.execute(
+ "UPDATE events.ticket SET registered = ? WHERE id = ?", (True, ticket_id)
+ )
+ ticket = await db.fetchone("SELECT * FROM events.ticket WHERE id = ?", (ticket_id,))
+ rows = await db.fetchall(
+ "SELECT * FROM events.ticket WHERE event = ?", (ticket[1],)
+ )
+ return [Tickets(**row) for row in rows]
diff --git a/lnbits/extensions/events/migrations.py b/lnbits/extensions/events/migrations.py
new file mode 100644
index 00000000..d8f3d94e
--- /dev/null
+++ b/lnbits/extensions/events/migrations.py
@@ -0,0 +1,91 @@
+async def m001_initial(db):
+
+ await db.execute(
+ """
+ CREATE TABLE events.events (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ name TEXT NOT NULL,
+ info TEXT NOT NULL,
+ closing_date TEXT NOT NULL,
+ event_start_date TEXT NOT NULL,
+ event_end_date TEXT NOT NULL,
+ amount_tickets INTEGER NOT NULL,
+ price_per_ticket INTEGER NOT NULL,
+ sold INTEGER NOT NULL,
+ time TIMESTAMP NOT NULL DEFAULT """
+ + db.timestamp_now
+ + """
+ );
+ """
+ )
+
+ await db.execute(
+ """
+ CREATE TABLE events.tickets (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ event TEXT NOT NULL,
+ name TEXT NOT NULL,
+ email TEXT NOT NULL,
+ registered BOOLEAN NOT NULL,
+ time TIMESTAMP NOT NULL DEFAULT """
+ + db.timestamp_now
+ + """
+ );
+ """
+ )
+
+
+async def m002_changed(db):
+
+ await db.execute(
+ """
+ CREATE TABLE events.ticket (
+ id TEXT PRIMARY KEY,
+ wallet TEXT NOT NULL,
+ event TEXT NOT NULL,
+ name TEXT NOT NULL,
+ email TEXT NOT NULL,
+ registered BOOLEAN NOT NULL,
+ paid BOOLEAN NOT NULL,
+ time TIMESTAMP NOT NULL DEFAULT """
+ + db.timestamp_now
+ + """
+ );
+ """
+ )
+
+ for row in [list(row) for row in await db.fetchall("SELECT * FROM events.tickets")]:
+ usescsv = ""
+
+ for i in range(row[5]):
+ if row[7]:
+ usescsv += "," + str(i + 1)
+ else:
+ usescsv += "," + str(1)
+ usescsv = usescsv[1:]
+ await db.execute(
+ """
+ INSERT INTO events.ticket (
+ id,
+ wallet,
+ event,
+ name,
+ email,
+ registered,
+ paid
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (
+ row[0],
+ row[1],
+ row[2],
+ row[3],
+ row[4],
+ row[5],
+ True,
+ ),
+ )
+ await db.execute("DROP TABLE events.tickets")
diff --git a/lnbits/extensions/events/models.py b/lnbits/extensions/events/models.py
new file mode 100644
index 00000000..c775382f
--- /dev/null
+++ b/lnbits/extensions/events/models.py
@@ -0,0 +1,41 @@
+from fastapi.param_functions import Query
+from pydantic import BaseModel
+
+
+class CreateEvent(BaseModel):
+ wallet: str
+ name: str
+ info: str
+ closing_date: str
+ event_start_date: str
+ event_end_date: str
+ amount_tickets: int = Query(..., ge=0)
+ price_per_ticket: int = Query(..., ge=0)
+
+class CreateTicket(BaseModel):
+ name: str
+ email: str
+
+class Events(BaseModel):
+ id: str
+ wallet: str
+ name: str
+ info: str
+ closing_date: str
+ event_start_date: str
+ event_end_date: str
+ amount_tickets: int
+ price_per_ticket: int
+ sold: int
+ time: int
+
+
+class Tickets(BaseModel):
+ id: str
+ wallet: str
+ event: str
+ name: str
+ email: str
+ registered: bool
+ paid: bool
+ time: int
diff --git a/lnbits/extensions/events/templates/events/_api_docs.html b/lnbits/extensions/events/templates/events/_api_docs.html
new file mode 100644
index 00000000..a5c82174
--- /dev/null
+++ b/lnbits/extensions/events/templates/events/_api_docs.html
@@ -0,0 +1,23 @@
+
+ Events alows you to make a wave of tickets for an event, each ticket is
+ in the form of a unqiue QRcode, which the user presents at registration.
+ Events comes with a shareable ticket scanner, which can be used to
+ register attendees.
+ Events: Sell and register ticket waves for an event
+
+
+
+ Created by, Ben Arc
+
+
You'll be redirected in a few moments...
+