diff --git a/lnbits/extensions/nostr/README.md b/lnbits/extensions/nostr/README.md
new file mode 100644
index 00000000..596cce9d
--- /dev/null
+++ b/lnbits/extensions/nostr/README.md
@@ -0,0 +1,3 @@
+# Nostr
+
+Opens a Nostr daemon
diff --git a/lnbits/extensions/nostr/__init__.py b/lnbits/extensions/nostr/__init__.py
new file mode 100644
index 00000000..775960e3
--- /dev/null
+++ b/lnbits/extensions/nostr/__init__.py
@@ -0,0 +1,17 @@
+from fastapi import APIRouter
+
+from lnbits.db import Database
+from lnbits.helpers import template_renderer
+
+db = Database("ext_nostr")
+
+nostr_ext: APIRouter = APIRouter(prefix="/nostr", tags=["nostr"])
+
+
+def nostr_renderer():
+ return template_renderer(["lnbits/extensions/nostr/templates"])
+
+
+from .lnurl import * # noqa
+from .views import * # noqa
+from .views_api import * # noqa
diff --git a/lnbits/extensions/nostr/config.json b/lnbits/extensions/nostr/config.json
new file mode 100644
index 00000000..a32e39a1
--- /dev/null
+++ b/lnbits/extensions/nostr/config.json
@@ -0,0 +1,6 @@
+{
+ "name": "Nostr",
+ "short_description": "Daemon for Nostr",
+ "icon": "swap_horizontal_circle",
+ "contributors": ["arcbtc"]
+}
diff --git a/lnbits/extensions/nostr/crud.py b/lnbits/extensions/nostr/crud.py
new file mode 100644
index 00000000..55e99ec6
--- /dev/null
+++ b/lnbits/extensions/nostr/crud.py
@@ -0,0 +1,134 @@
+from typing import List, Optional, Union
+
+from lnbits.helpers import urlsafe_short_hash
+
+from . import db
+from .models import nostrKeys, nostrNotes, nostrRelays, nostrConnections
+
+###############KEYS##################
+
+async def create_nostrkeys(
+ data: nostrKeys
+) -> nostrKeys:
+ nostrkey_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO nostr.keys (
+ id,
+ pubkey,
+ privkey
+ )
+ VALUES (?, ?, ?)
+ """,
+ (nostrkey_id, data.pubkey, data.privkey),
+ )
+ return await get_nostrkeys(nostrkey_id)
+
+async def get_nostrkeys(nostrkey_id: str) -> nostrKeys:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.keys WHERE id = ?",
+ (lnurldevicepayment_id,),
+ )
+ return nostrKeys(**row) if row else None
+
+
+###############NOTES##################
+
+async def create_nostrnotes(
+ data: nostrNotes
+) -> nostrNotes:
+ await db.execute(
+ """
+ INSERT INTO nostr.notes (
+ id,
+ pubkey,
+ created_at,
+ kind,
+ tags,
+ content,
+ sig
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
+ )
+ return await get_nostrnotes(data.id)
+
+async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.notes WHERE id = ?",
+ (nostrnote_id,),
+ )
+ return nostrNotes(**row) if row else None
+
+###############RELAYS##################
+
+async def create_nostrrelays(
+ relay: str
+) -> nostrRelays:
+ nostrrelay_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO nostr.relays (
+ id,
+ relay
+ )
+ VALUES (?, ?)
+ """,
+ (nostrrelay_id, relay),
+ )
+ return await get_nostrnotes(nostrrelay_id)
+
+async def get_nostrrelays(nostrrelay_id: str) -> nostrRelays:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.relays WHERE id = ?",
+ (nostrnote_id,),
+ )
+ return nostrRelays(**row) if row else None
+
+
+###############CONNECTIONS##################
+
+async def create_nostrconnections(
+ data: nostrNotes
+) -> nostrNotes:
+ nostrkey_id = urlsafe_short_hash()
+ await db.execute(
+ """
+ INSERT INTO nostr.notes (
+ id,
+ pubkey,
+ created_at,
+ kind,
+ tags,
+ content,
+ sig
+ )
+ VALUES (?, ?, ?, ?, ?, ?, ?)
+ """,
+ (data.id, data.pubkey, data.created_at, data.kind, data.tags, data.content, data.sig),
+ )
+ return await get_nostrnotes(data.id)
+
+async def get_nostrnotes(nostrnote_id: str) -> nostrNotes:
+ row = await db.fetchone(
+ "SELECT * FROM nostr.notes WHERE id = ?",
+ (nostrnote_id,),
+ )
+ return nostrNotes(**row) if row else None
+
+
+
+async def update_lnurldevicepayment(
+ lnurldevicepayment_id: str, **kwargs
+) -> Optional[lnurldevicepayment]:
+ q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
+ await db.execute(
+ f"UPDATE lnurldevice.lnurldevicepayment SET {q} WHERE id = ?",
+ (*kwargs.values(), lnurldevicepayment_id),
+ )
+ row = await db.fetchone(
+ "SELECT * FROM lnurldevice.lnurldevicepayment WHERE id = ?",
+ (lnurldevicepayment_id,),
+ )
+ return lnurldevicepayment(**row) if row else None
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/migrations.py b/lnbits/extensions/nostr/migrations.py
new file mode 100644
index 00000000..de32a578
--- /dev/null
+++ b/lnbits/extensions/nostr/migrations.py
@@ -0,0 +1,47 @@
+from lnbits.db import Database
+
+db2 = Database("ext_nostr")
+
+
+async def m001_initial(db):
+ """
+ Initial nostr table.
+ """
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.keys (
+ pubkey TEXT NOT NULL PRIMARY KEY,
+ privkey TEXT NOT NULL
+ );
+ """
+ )
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.notes (
+ id TEXT NOT NULL PRIMARY KEY,
+ pubkey TEXT NOT NULL,
+ created_at TEXT NOT NULL,
+ kind INT NOT NULL,
+ tags TEXT NOT NULL,
+ content TEXT NOT NULL,
+ sig TEXT NOT NULL,
+ );
+ """
+ )
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.relays (
+ id TEXT NOT NULL PRIMARY KEY,
+ relay TEXT NOT NULL
+ );
+ """
+ )
+ await db.execute(
+ f"""
+ CREATE TABLE nostr.connections (
+ id TEXT NOT NULL PRIMARY KEY,
+ publickey TEXT NOT NULL,
+ relayid TEXT NOT NULL
+ );
+ """
+ )
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/models.py b/lnbits/extensions/nostr/models.py
new file mode 100644
index 00000000..ba0c87a5
--- /dev/null
+++ b/lnbits/extensions/nostr/models.py
@@ -0,0 +1,34 @@
+import json
+from sqlite3 import Row
+from typing import Optional
+
+from fastapi import Request
+from lnurl import Lnurl
+from lnurl import encode as lnurl_encode # type: ignore
+from lnurl.models import LnurlPaySuccessAction, UrlAction # type: ignore
+from lnurl.types import LnurlPayMetadata # type: ignore
+from pydantic import BaseModel
+from pydantic.main import BaseModel
+
+class nostrKeys(BaseModel):
+ id: str
+ pubkey: str
+ privkey: str
+
+class nostrNotes(BaseModel):
+ id: str
+ pubkey: str
+ created_at: str
+ kind: int
+ tags: str
+ content: str
+ sig: str
+
+class nostrRelays(BaseModel):
+ id: str
+ relay: str
+
+class nostrConnections(BaseModel):
+ id: str
+ pubkey: str
+ relayid: str
\ No newline at end of file
diff --git a/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
new file mode 100644
index 00000000..af69b76e
--- /dev/null
+++ b/lnbits/extensions/nostr/templates/lnurldevice/_api_docs.html
@@ -0,0 +1,169 @@
+
+ Register LNURLDevice devices to receive payments in your LNbits wallet.
+ Build your own here
+ https://github.com/arcbtc/bitcoinpos
+
+ Created by, Ben Arc
+ /lnurldevice/api/v1/lnurlpos
+ Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X POST {{ request.base_url }}api/v1/lnurldevice -d '{"title":
+ <string>, "message":<string>, "currency":
+ <integer>}' -H "Content-type: application/json" -H "X-Api-Key:
+ {{user.wallets[0].adminkey }}"
+
+ PUT
+ /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X POST {{ request.base_url
+ }}api/v1/lnurlpos/<lnurldevice_id> -d ''{"title":
+ <string>, "message":<string>, "currency":
+ <integer>} -H "Content-type: application/json" -H "X-Api-Key:
+ {{user.wallets[0].adminkey }}"
+
+ GET
+ /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
+ Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X GET {{ request.base_url
+ }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
+ user.wallets[0].inkey }}"
+
+ GET
+ /lnurldevice/api/v1/lnurlposs
+ Headers
+ {"X-Api-Key": <invoice_key>}
+
+ Body (application/json)
+
+
+ Returns 200 OK (application/json)
+
+ [<lnurldevice_object>, ...]
+ Curl example
+ curl -X GET {{ request.base_url }}api/v1/lnurldevices -H
+ "X-Api-Key: {{ user.wallets[0].inkey }}"
+
+ DELETE
+ /lnurldevice/api/v1/lnurlpos/<lnurldevice_id>
+ Headers
+ {"X-Api-Key": <admin_key>}
+ Returns 204 NO CONTENT
+
+ Curl example
+ curl -X DELETE {{ request.base_url
+ }}api/v1/lnurlpos/<lnurldevice_id> -H "X-Api-Key: {{
+ user.wallets[0].adminkey }}"
+
+