commit
bf100be209
10 changed files with 259 additions and 195 deletions
13
__init__.py
13
__init__.py
|
|
@ -4,14 +4,11 @@ from fastapi import APIRouter
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from lnbits.tasks import create_permanent_unique_task
|
from lnbits.tasks import create_permanent_unique_task
|
||||||
|
|
||||||
from .crud import db
|
from .crud import db
|
||||||
from .tasks import handle_execution_queue, handle_nwc
|
from .tasks import handle_execution_queue, handle_nwc
|
||||||
from .views import nwcprovider_router
|
from .views import nwcprovider_router
|
||||||
from .views_api import nwcprovider_api_router
|
from .views_api import nwcprovider_api_router
|
||||||
|
|
||||||
scheduled_tasks: list[asyncio.Task] = []
|
|
||||||
|
|
||||||
nwcprovider_ext: APIRouter = APIRouter(
|
nwcprovider_ext: APIRouter = APIRouter(
|
||||||
prefix="/nwcprovider", tags=["NWC Service Provider"]
|
prefix="/nwcprovider", tags=["NWC Service Provider"]
|
||||||
)
|
)
|
||||||
|
|
@ -25,6 +22,7 @@ nwcprovider_static_files = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
scheduled_tasks: list[asyncio.Task] = []
|
||||||
|
|
||||||
def nwcprovider_stop():
|
def nwcprovider_stop():
|
||||||
for task in scheduled_tasks:
|
for task in scheduled_tasks:
|
||||||
|
|
@ -42,5 +40,10 @@ def nwcprovider_start():
|
||||||
)
|
)
|
||||||
scheduled_tasks.append(task)
|
scheduled_tasks.append(task)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
__all__ = ["db"]
|
"db",
|
||||||
|
"nwcprovider_ext",
|
||||||
|
"nwcprovider_static_files",
|
||||||
|
"nwcprovider_start",
|
||||||
|
"nwcprovider_stop",
|
||||||
|
]
|
||||||
|
|
|
||||||
20
config.json
20
config.json
|
|
@ -2,13 +2,31 @@
|
||||||
"name": "NWC Service Provider",
|
"name": "NWC Service Provider",
|
||||||
"short_description": "A NWC service provider for LNbits",
|
"short_description": "A NWC service provider for LNbits",
|
||||||
"tile": "/nwcprovider/static/image/nwcprovider.png",
|
"tile": "/nwcprovider/static/image/nwcprovider.png",
|
||||||
"min_lnbits_version": "0.12.5",
|
"min_lnbits_version": "1.0.0",
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
{
|
||||||
"name": "Riccardo Balbo",
|
"name": "Riccardo Balbo",
|
||||||
"uri": "https://github.com/riccardobl",
|
"uri": "https://github.com/riccardobl",
|
||||||
|
"role": "Lead Dev"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ben Arc",
|
||||||
|
"uri": "https://github.com/arcbtc",
|
||||||
"role": "Dev"
|
"role": "Dev"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"uri": "https://raw.githubusercontent.com/riccardobl/nwcprovider/main/static/image/1.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "https://raw.githubusercontent.com/riccardobl/nwcprovider/main/static/image/2.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "https://raw.githubusercontent.com/riccardobl/nwcprovider/main/static/image/3.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description_md": "https://raw.githubusercontent.com/riccardobl/nwcprovider/main/description.md",
|
||||||
|
"terms_and_conditions_md": "https://raw.githubusercontent.com/riccardobl/nwcprovider/main/toc.md",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
195
crud.py
195
crud.py
|
|
@ -4,113 +4,70 @@ from typing import List, Optional
|
||||||
from lnbits.db import Database
|
from lnbits.db import Database
|
||||||
|
|
||||||
from .execution_queue import enqueue
|
from .execution_queue import enqueue
|
||||||
from .models import NWCBudget, NWCKey, NWCNewBudget
|
from .models import (
|
||||||
|
NWCBudget,
|
||||||
|
NWCKey,
|
||||||
|
CreateNWCKey,
|
||||||
|
GetNWCKey,
|
||||||
|
GetWalletNWC,
|
||||||
|
GetBudgetsNWC,
|
||||||
|
TrackedSpendNWC,
|
||||||
|
DeleteNWC
|
||||||
|
)
|
||||||
|
|
||||||
db = Database("ext_nwcprovider")
|
db = Database("ext_nwcprovider")
|
||||||
|
|
||||||
|
async def create_nwc(data: CreateNWCKey) -> NWCKey:
|
||||||
async def create_nwc(
|
nwckey_entry = NWCKey(
|
||||||
pubkey: str,
|
pubkey=data.pubkey,
|
||||||
wallet_id: str,
|
wallet=data.wallet_id,
|
||||||
description: str,
|
description=data.description,
|
||||||
expires_at: int,
|
expires_at=int(data.expires_at) if data.expires_at else 0,
|
||||||
permissions: List[str],
|
permissions=" ".join(data.permissions),
|
||||||
budgets: Optional[List[NWCNewBudget]] = None,
|
created_at=int(time.time()),
|
||||||
) -> NWCKey:
|
last_used=int(time.time()),
|
||||||
# Check if the key already exists
|
|
||||||
if await get_nwc(pubkey, None, True):
|
|
||||||
raise Exception("Public key already used")
|
|
||||||
# If not, create it
|
|
||||||
now = int(time.time())
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO nwcprovider.keys (
|
|
||||||
pubkey,
|
|
||||||
wallet,
|
|
||||||
description,
|
|
||||||
permissions,
|
|
||||||
created_at,
|
|
||||||
expires_at,
|
|
||||||
last_used
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
pubkey,
|
|
||||||
wallet_id,
|
|
||||||
description,
|
|
||||||
" ".join(permissions),
|
|
||||||
now,
|
|
||||||
int(expires_at) if expires_at else 0,
|
|
||||||
now,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
# Add budgets
|
await db.insert("nwcprovider.keys", nwckey_entry)
|
||||||
if budgets:
|
if data.budgets:
|
||||||
for budget in budgets:
|
for budget in data.budgets:
|
||||||
await db.execute(
|
budget_entry = NWCKey(
|
||||||
"""
|
pubkey=data.pubkey,
|
||||||
INSERT INTO nwcprovider.budgets (
|
budget_msats=budget.budget_msats,
|
||||||
pubkey,
|
refresh_window=budget.refresh_window,
|
||||||
budget_msats,
|
created_at=budget.created_at
|
||||||
refresh_window,
|
|
||||||
created_at
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(pubkey, budget.budget_msats, budget.refresh_window, budget.created_at),
|
|
||||||
)
|
)
|
||||||
# Return the created key
|
await db.insert("nwcprovider.budgets", budget_entry)
|
||||||
return NWCKey(
|
return NWCKey(**data.dict())
|
||||||
pubkey=pubkey,
|
|
||||||
wallet=wallet_id,
|
|
||||||
description=description,
|
|
||||||
expires_at=expires_at,
|
|
||||||
permissions=" ".join(permissions),
|
|
||||||
created_at=now,
|
|
||||||
last_used=now,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
async def delete_nwc(data:DeleteNWC) -> None:
|
||||||
async def delete_nwc(pubkey: str, wallet_id: str):
|
|
||||||
nwc = await get_nwc(pubkey, wallet_id)
|
|
||||||
if not nwc:
|
|
||||||
raise Exception("Public key does not exist")
|
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"DELETE FROM nwcprovider.keys WHERE pubkey = :pubkey AND wallet = :wallet", {"pubkey": data.pubkey, "wallet": data.wallet_id}
|
||||||
DELETE FROM nwcprovider.keys WHERE pubkey = ? AND wallet = ?
|
|
||||||
""",
|
|
||||||
(pubkey, wallet_id),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def get_wallet_nwcs(data: GetWalletNWC) -> List[NWCKey]:
|
||||||
async def get_wallet_nwcs(
|
return await db.fetchall(
|
||||||
wallet_id: str, include_expired: Optional[bool] = False
|
|
||||||
) -> List[NWCKey]:
|
|
||||||
rows = await db.fetchall(
|
|
||||||
"""
|
"""
|
||||||
SELECT * FROM nwcprovider.keys
|
SELECT * FROM nwcprovider.keys
|
||||||
WHERE wallet = ? AND (expires_at = 0 OR expires_at > ?)
|
WHERE wallet = :wallet AND (expires_at = 0 OR expires_at > :expires)
|
||||||
""",
|
""",
|
||||||
(wallet_id, int(time.time()) if not include_expired else -1),
|
{"wallet": data.wallet_id, "expires": int(time.time()) if not data.include_expired else -1},
|
||||||
|
model=NWCKey,
|
||||||
)
|
)
|
||||||
return [NWCKey(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_nwc(
|
async def get_nwc(
|
||||||
pubkey: str,
|
data: GetNWCKey
|
||||||
wallet_id: Optional[str] = None,
|
|
||||||
include_expired: Optional[bool] = False,
|
|
||||||
refresh_last_used: Optional[bool] = False,
|
|
||||||
) -> Optional[NWCKey]:
|
) -> Optional[NWCKey]:
|
||||||
# expires_at = 0 means it never expires
|
# expires_at = 0 means it never expires
|
||||||
if wallet_id:
|
if data.wallet_id:
|
||||||
row = await db.fetchone(
|
row = await db.fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT * FROM nwcprovider.keys
|
SELECT * FROM nwcprovider.keys
|
||||||
WHERE pubkey = ? AND wallet = ? AND (expires_at = 0 OR expires_at > ?)
|
WHERE pubkey = :pubkey AND wallet = :wallet
|
||||||
|
AND (expires_at = 0 OR expires_at > :expires)
|
||||||
""",
|
""",
|
||||||
(pubkey, wallet_id, int(time.time()) if not include_expired else -1),
|
{"pubkey": data.pubkey, "wallet": data.wallet_id,
|
||||||
|
"expires": int(time.time()) if not data.include_expired else -1},
|
||||||
|
NWCKey,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
row = await db.fetchone(
|
row = await db.fetchone(
|
||||||
|
|
@ -118,44 +75,52 @@ async def get_nwc(
|
||||||
SELECT * FROM nwcprovider.keys
|
SELECT * FROM nwcprovider.keys
|
||||||
WHERE pubkey = ? AND (expires_at = 0 OR expires_at > ?)
|
WHERE pubkey = ? AND (expires_at = 0 OR expires_at > ?)
|
||||||
""",
|
""",
|
||||||
(pubkey, int(time.time()) if not include_expired else -1),
|
(data.pubkey, int(time.time()) if not data.include_expired else -1),
|
||||||
|
)
|
||||||
|
row = await db.fetchone(
|
||||||
|
"""
|
||||||
|
SELECT * FROM nwcprovider.keys
|
||||||
|
WHERE pubkey = :pubkey AND (expires_at = 0 OR expires_at > :expires)
|
||||||
|
""",
|
||||||
|
{"pubkey": data.pubkey, "expires": int(time.time()) if not data.include_expired else -1},
|
||||||
|
NWCKey,
|
||||||
)
|
)
|
||||||
if not row:
|
if not row:
|
||||||
return None
|
return None
|
||||||
if refresh_last_used:
|
if data.refresh_last_used:
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
UPDATE nwcprovider.keys SET last_used = ? WHERE pubkey = ?
|
UPDATE nwcprovider.keys SET last_used = :last_used WHERE pubkey = :pubkey
|
||||||
""",
|
""",
|
||||||
(int(time.time()), pubkey),
|
{"last_used":int(time.time()), "pubkey":data.pubkey},
|
||||||
)
|
)
|
||||||
return NWCKey(**row)
|
return NWCKey(**row)
|
||||||
|
|
||||||
|
|
||||||
async def get_budgets_nwc(pubkey, calculate_spent=False):
|
async def get_budgets_nwc(data: GetBudgetsNWC) -> Optional[NWCBudget]:
|
||||||
rows = await db.fetchall(
|
rows = await db.fetchall(
|
||||||
"SELECT * FROM nwcprovider.budgets WHERE pubkey = ?", (pubkey)
|
"SELECT * FROM nwcprovider.budgets WHERE pubkey = :pubkey", {"pubkey":data.pubkey}
|
||||||
)
|
)
|
||||||
budgets = [NWCBudget(**row) for row in rows]
|
budgets = [NWCBudget(**row) for row in rows]
|
||||||
if calculate_spent:
|
if data.calculate_spent:
|
||||||
for budget in budgets:
|
for budget in budgets:
|
||||||
last_cycle, next_cycle = budget.get_timestamp_range()
|
last_cycle, next_cycle = budget.get_timestamp_range()
|
||||||
tot_spent_in_range_msats = await db.fetchone(
|
tot_spent_in_range_msats = await db.fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT SUM(amount_msats) FROM nwcprovider.spent
|
SELECT SUM(amount_msats) FROM nwcprovider.spent
|
||||||
WHERE pubkey = ? AND created_at >= ? AND created_at < ?
|
WHERE pubkey = :pubkey AND created_at >= :last_cycle AND created_at < next_cycle
|
||||||
""",
|
""",
|
||||||
(pubkey, last_cycle, next_cycle),
|
{"pubkey":data.pubkey, "last_cycle":last_cycle, "next_cycle":next_cycle},
|
||||||
)
|
)
|
||||||
tot_spent_in_range_msats = tot_spent_in_range_msats[0] or 0
|
tot_spent_in_range_msats = tot_spent_in_range_msats[0] or 0
|
||||||
budget.used_budget_msats = tot_spent_in_range_msats
|
budget.used_budget_msats = tot_spent_in_range_msats
|
||||||
return budgets
|
return budgets
|
||||||
|
|
||||||
|
|
||||||
async def tracked_spend_nwc(pubkey: str, amount_msats: int, action):
|
async def tracked_spend_nwc(data:TrackedSpendNWC, action):
|
||||||
async def r():
|
async def r():
|
||||||
created_at = int(time.time())
|
created_at = int(time.time())
|
||||||
budgets = await get_budgets_nwc(pubkey)
|
budgets = await get_budgets_nwc(data.pubkey)
|
||||||
in_budget = True
|
in_budget = True
|
||||||
for budget in budgets:
|
for budget in budgets:
|
||||||
last_cycle, next_cycle = budget.get_timestamp_range()
|
last_cycle, next_cycle = budget.get_timestamp_range()
|
||||||
|
|
@ -164,14 +129,14 @@ async def tracked_spend_nwc(pubkey: str, amount_msats: int, action):
|
||||||
await db.fetchone(
|
await db.fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT SUM(amount_msats) FROM nwcprovider.spent
|
SELECT SUM(amount_msats) FROM nwcprovider.spent
|
||||||
WHERE pubkey = ? AND created_at >= ? AND created_at < ?
|
WHERE pubkey = :pubkey AND created_at >= :last_cycle AND created_at < :next_cycle
|
||||||
""",
|
""",
|
||||||
(pubkey, last_cycle, next_cycle),
|
{"pubkey":data.pubkey, "last_cycle":last_cycle, "next_cycle":next_cycle},
|
||||||
)
|
)
|
||||||
)[0]
|
)[0]
|
||||||
or 0
|
or 0
|
||||||
)
|
)
|
||||||
if tot_spent_in_range_msats + amount_msats > budget.budget_msats:
|
if tot_spent_in_range_msats + data.amount_msats > budget.budget_msats:
|
||||||
in_budget = False
|
in_budget = False
|
||||||
break
|
break
|
||||||
if not in_budget:
|
if not in_budget:
|
||||||
|
|
@ -180,9 +145,9 @@ async def tracked_spend_nwc(pubkey: str, amount_msats: int, action):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO nwcprovider.spent (pubkey, amount_msats, created_at)
|
INSERT INTO nwcprovider.spent (pubkey, amount_msats, created_at)
|
||||||
VALUES (?, ?, ?)
|
VALUES (:pubkey, :amount_msats, :created_at)
|
||||||
""",
|
""",
|
||||||
(pubkey, amount_msats, created_at),
|
{"pubkey":data.pubkey, "amount_msats":data.amount_msats, "created_at":created_at},
|
||||||
)
|
)
|
||||||
return True, out
|
return True, out
|
||||||
|
|
||||||
|
|
@ -190,29 +155,27 @@ async def tracked_spend_nwc(pubkey: str, amount_msats: int, action):
|
||||||
|
|
||||||
|
|
||||||
async def get_config_nwc(key: str):
|
async def get_config_nwc(key: str):
|
||||||
row = await db.fetchone("SELECT * FROM nwcprovider.config WHERE key = ?", (key,))
|
row = await db.fetchone("SELECT * FROM nwcprovider.config WHERE key = :key", {"key":key})
|
||||||
if not row:
|
if not row:
|
||||||
return None
|
return None
|
||||||
return row["value"]
|
return row["value"]
|
||||||
|
|
||||||
|
|
||||||
async def get_all_config_nwc():
|
|
||||||
rows = await db.fetchall("SELECT * FROM nwcprovider.config")
|
|
||||||
return {row["key"]: row["value"] for row in rows}
|
|
||||||
|
|
||||||
|
|
||||||
async def set_config_nwc(key: str, value: str):
|
async def set_config_nwc(key: str, value: str):
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
DELETE FROM nwcprovider.config
|
DELETE FROM nwcprovider.config
|
||||||
WHERE key = ?
|
WHERE key = :key
|
||||||
""",
|
""",
|
||||||
(key,),
|
{"key":key},
|
||||||
)
|
)
|
||||||
await db.execute(
|
await db.execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO nwcprovider.config (key, value)
|
INSERT INTO nwcprovider.config (key, value)
|
||||||
VALUES (?, ?)
|
VALUES (:key, :value)
|
||||||
""",
|
""",
|
||||||
(key, value),
|
{"key":key, "value":value},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def get_all_config_nwc():
|
||||||
|
rows = await db.fetchall("SELECT * FROM nwcprovider.config")
|
||||||
|
return {row["key"]: row["value"] for row in rows}
|
||||||
3
description.md
Normal file
3
description.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
NWC Service Provider Extension for https://github.com/lnbits/lnbits
|
||||||
|
|
||||||
|
Easily connect your LNbits wallets via https://nwc.dev/
|
||||||
45
models.py
45
models.py
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from sqlite3 import Row
|
from sqlite3 import Row
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from .nwcp import NWCServiceProvider
|
||||||
|
|
||||||
class NWCKey(BaseModel):
|
class NWCKey(BaseModel):
|
||||||
pubkey: str
|
pubkey: str
|
||||||
|
|
@ -26,6 +26,36 @@ class NWCKey(BaseModel):
|
||||||
def from_row(cls, row: Dict[str, Any]) -> "NWCKey":
|
def from_row(cls, row: Dict[str, Any]) -> "NWCKey":
|
||||||
return cls(**row)
|
return cls(**row)
|
||||||
|
|
||||||
|
class GetNWCKey(BaseModel):
|
||||||
|
pubkey: str
|
||||||
|
wallet_id: Optional[str] = None
|
||||||
|
include_expired: Optional[bool] = False
|
||||||
|
refresh_last_used: Optional[bool] = False
|
||||||
|
|
||||||
|
class GetNWCs(BaseModel):
|
||||||
|
include_expired: bool = False
|
||||||
|
calculate_spent_budget: bool = False
|
||||||
|
|
||||||
|
class GetWalletNWC(BaseModel):
|
||||||
|
wallet_id: Optional[str] = None
|
||||||
|
include_expired: Optional[bool] = False
|
||||||
|
|
||||||
|
class GetBudgetsNWC(BaseModel):
|
||||||
|
pubkey: str
|
||||||
|
calculate_spent: Optional[bool] = False
|
||||||
|
|
||||||
|
class TrackedSpendNWC(BaseModel):
|
||||||
|
pubkey: str
|
||||||
|
amount_msats: int
|
||||||
|
|
||||||
|
class DeleteNWC(BaseModel):
|
||||||
|
pubkey: str
|
||||||
|
wallet_id: Optional[str] = None
|
||||||
|
|
||||||
|
class OnInvoicePaid(BaseModel):
|
||||||
|
sp: NWCServiceProvider
|
||||||
|
pubkey: str
|
||||||
|
payload: Dict
|
||||||
|
|
||||||
class NWCBudget(BaseModel):
|
class NWCBudget(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
|
|
@ -75,7 +105,18 @@ class NWCRegistrationRequest(BaseModel):
|
||||||
expires_at: int
|
expires_at: int
|
||||||
budgets: List[NWCNewBudget]
|
budgets: List[NWCNewBudget]
|
||||||
|
|
||||||
|
class RegisterNWC(BaseModel):
|
||||||
|
pubkey: str
|
||||||
|
registration_data: NWCRegistrationRequest
|
||||||
|
|
||||||
class NWCGetResponse(BaseModel):
|
class NWCGetResponse(BaseModel):
|
||||||
data: NWCKey
|
data: NWCKey
|
||||||
budgets: List[NWCBudget]
|
budgets: List[NWCBudget]
|
||||||
|
|
||||||
|
class CreateNWCKey(BaseModel):
|
||||||
|
pubkey: str
|
||||||
|
wallet_id: str
|
||||||
|
description: str
|
||||||
|
expires_at: int
|
||||||
|
permissions: List[str]
|
||||||
|
budgets: Optional[List[NWCNewBudget]] = None
|
||||||
70
tasks.py
70
tasks.py
|
|
@ -19,12 +19,12 @@ from loguru import logger
|
||||||
|
|
||||||
from .crud import get_config_nwc, get_nwc, tracked_spend_nwc
|
from .crud import get_config_nwc, get_nwc, tracked_spend_nwc
|
||||||
from .execution_queue import execution_queue
|
from .execution_queue import execution_queue
|
||||||
from .models import NWCKey
|
from .models import NWCKey, OnInvoicePaid
|
||||||
from .nwcp import NWCServiceProvider
|
from .nwcp import NWCServiceProvider
|
||||||
from .permission import nwc_permissions
|
from .permission import nwc_permissions
|
||||||
|
|
||||||
|
|
||||||
async def _check(nwc: Optional[NWCKey], method: str, payload: Dict) -> Optional[Dict]:
|
async def _check(nwc: Optional[NWCKey], method: str) -> Optional[Dict]:
|
||||||
# check
|
# check
|
||||||
if not nwc:
|
if not nwc:
|
||||||
return {
|
return {
|
||||||
|
|
@ -106,15 +106,15 @@ async def _process_invoice(
|
||||||
|
|
||||||
|
|
||||||
async def _on_pay_invoice(
|
async def _on_pay_invoice(
|
||||||
sp: NWCServiceProvider, pubkey: str, payload: Dict
|
data:OnInvoicePaid
|
||||||
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
||||||
nwc = await get_nwc(pubkey, None, False, True)
|
nwc = await get_nwc(data.pubkey, None, False, True)
|
||||||
error = await _check(nwc, "pay_invoice", payload)
|
error = await _check(nwc, "pay_invoice", data.payload)
|
||||||
if error:
|
if error:
|
||||||
return [(None, error, [])]
|
return [(None, error, [])]
|
||||||
if not nwc:
|
if not nwc:
|
||||||
raise Exception("Pubkey has no associated wallet")
|
raise Exception("Pubkey has no associated wallet")
|
||||||
params = payload.get("params", {})
|
params = data.payload.get("params", {})
|
||||||
invoice = params.get("invoice", None)
|
invoice = params.get("invoice", None)
|
||||||
# Ensures invoice is provided
|
# Ensures invoice is provided
|
||||||
if not invoice:
|
if not invoice:
|
||||||
|
|
@ -122,7 +122,7 @@ async def _on_pay_invoice(
|
||||||
invoice_data = bolt11_decode(invoice)
|
invoice_data = bolt11_decode(invoice)
|
||||||
amount_msats = int(invoice_data.amount_msat or 0)
|
amount_msats = int(invoice_data.amount_msat or 0)
|
||||||
res = await _process_invoice(
|
res = await _process_invoice(
|
||||||
nwc.wallet, pubkey, invoice, amount_msats, invoice_data.description
|
nwc.wallet, data.pubkey, invoice, amount_msats, invoice_data.description
|
||||||
)
|
)
|
||||||
error = res.get("error")
|
error = res.get("error")
|
||||||
if error:
|
if error:
|
||||||
|
|
@ -136,15 +136,15 @@ async def _on_pay_invoice(
|
||||||
|
|
||||||
|
|
||||||
async def _on_multi_pay_invoice(
|
async def _on_multi_pay_invoice(
|
||||||
sp: NWCServiceProvider, pubkey: str, payload: Dict
|
data:OnInvoicePaid
|
||||||
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
||||||
nwc = await get_nwc(pubkey, None, False, True)
|
nwc = await get_nwc(data.pubkey, None, False, True)
|
||||||
error = await _check(nwc, "multi_pay_invoice", payload)
|
error = await _check(nwc, "multi_pay_invoice", data.payload)
|
||||||
if error:
|
if error:
|
||||||
return [(None, error, [])]
|
return [(None, error, [])]
|
||||||
if not nwc:
|
if not nwc:
|
||||||
raise Exception("Pubkey has no associated wallet")
|
raise Exception("Pubkey has no associated wallet")
|
||||||
params = payload.get("params", {})
|
params = data.payload.get("params", {})
|
||||||
invoices = params.get("invoices", [])
|
invoices = params.get("invoices", [])
|
||||||
results: List[Tuple[Optional[Dict], Optional[Dict], List]] = []
|
results: List[Tuple[Optional[Dict], Optional[Dict], List]] = []
|
||||||
|
|
||||||
|
|
@ -182,15 +182,15 @@ async def _on_multi_pay_invoice(
|
||||||
|
|
||||||
|
|
||||||
async def _on_make_invoice(
|
async def _on_make_invoice(
|
||||||
sp: NWCServiceProvider, pubkey: str, payload: Dict
|
data:OnInvoicePaid
|
||||||
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
||||||
nwc = await get_nwc(pubkey, None, False, True)
|
nwc = await get_nwc(data.pubkey, None, False, True)
|
||||||
error = await _check(nwc, "make_invoice", payload)
|
error = await _check(nwc, "make_invoice", data.payload)
|
||||||
if error:
|
if error:
|
||||||
return [(None, error, [])]
|
return [(None, error, [])]
|
||||||
if not nwc:
|
if not nwc:
|
||||||
raise Exception("Pubkey has no associated wallet")
|
raise Exception("Pubkey has no associated wallet")
|
||||||
params = payload.get("params", {})
|
params = data.payload.get("params", {})
|
||||||
amount_msats = params.get("amount", None)
|
amount_msats = params.get("amount", None)
|
||||||
# Ensures amount is provided
|
# Ensures amount is provided
|
||||||
if not amount_msats:
|
if not amount_msats:
|
||||||
|
|
@ -234,15 +234,15 @@ async def _on_make_invoice(
|
||||||
|
|
||||||
|
|
||||||
async def _on_lookup_invoice(
|
async def _on_lookup_invoice(
|
||||||
sp: NWCServiceProvider, pubkey: str, payload: Dict
|
data:OnInvoicePaid
|
||||||
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
||||||
nwc = await get_nwc(pubkey, None, False, True)
|
nwc = await get_nwc(data.pubkey, None, False, True)
|
||||||
error = await _check(nwc, "lookup_invoice", payload)
|
error = await _check(nwc, "lookup_invoice", data.payload)
|
||||||
if error:
|
if error:
|
||||||
return [(None, error, [])]
|
return [(None, error, [])]
|
||||||
if not nwc:
|
if not nwc:
|
||||||
raise Exception("Pubkey has no associated wallet")
|
raise Exception("Pubkey has no associated wallet")
|
||||||
params = payload.get("params", {})
|
params = data.payload.get("params", {})
|
||||||
payment_hash = params.get("payment_hash", None)
|
payment_hash = params.get("payment_hash", None)
|
||||||
invoice = params.get("invoice", None)
|
invoice = params.get("invoice", None)
|
||||||
# Ensure payment_hash or invoice are provided
|
# Ensure payment_hash or invoice are provided
|
||||||
|
|
@ -281,20 +281,20 @@ async def _on_lookup_invoice(
|
||||||
|
|
||||||
|
|
||||||
async def _on_list_transactions(
|
async def _on_list_transactions(
|
||||||
sp: NWCServiceProvider, pubkey: str, payload: Dict
|
data:OnInvoicePaid
|
||||||
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
||||||
nwc = await get_nwc(pubkey, None, False, True)
|
nwc = await get_nwc(data.pubkey, None, False, True)
|
||||||
error = await _check(nwc, "list_transactions", payload)
|
error = await _check(nwc, "list_transactions", data.payload)
|
||||||
if error:
|
if error:
|
||||||
return [(None, error, [])]
|
return [(None, error, [])]
|
||||||
if not nwc:
|
if not nwc:
|
||||||
raise Exception("Pubkey has no associated wallet")
|
raise Exception("Pubkey has no associated wallet")
|
||||||
tfrom = payload.get("from", 0)
|
tfrom = data.payload.get("from", 0)
|
||||||
tto = payload.get("to", int(time.time()))
|
tto = data.payload.get("to", int(time.time()))
|
||||||
limit = payload.get("limit", 10)
|
limit = data.payload.get("limit", 10)
|
||||||
offset = payload.get("offset", 0)
|
offset = data.payload.get("offset", 0)
|
||||||
unpaid = payload.get("unpaid", False)
|
unpaid = data.payload.get("unpaid", False)
|
||||||
tx_type = payload.get("type", None)
|
tx_type = data.payload.get("type", None)
|
||||||
values = []
|
values = []
|
||||||
filters: Filters = Filters()
|
filters: Filters = Filters()
|
||||||
filters.where(["time <= ?"])
|
filters.where(["time <= ?"])
|
||||||
|
|
@ -337,10 +337,10 @@ async def _on_list_transactions(
|
||||||
|
|
||||||
|
|
||||||
async def _on_get_balance(
|
async def _on_get_balance(
|
||||||
sp: NWCServiceProvider, pubkey: str, payload: Dict
|
data:OnInvoicePaid
|
||||||
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
||||||
nwc = await get_nwc(pubkey, None, False, True)
|
nwc = await get_nwc(data.pubkey, None, False, True)
|
||||||
error = await _check(nwc, "get_balance", payload)
|
error = await _check(nwc, "get_balance", data.payload)
|
||||||
if error:
|
if error:
|
||||||
return [(None, error, [])]
|
return [(None, error, [])]
|
||||||
if not nwc:
|
if not nwc:
|
||||||
|
|
@ -355,10 +355,10 @@ async def _on_get_balance(
|
||||||
|
|
||||||
|
|
||||||
async def _on_get_info(
|
async def _on_get_info(
|
||||||
sp: NWCServiceProvider, pubkey: str, payload: Dict
|
data:OnInvoicePaid
|
||||||
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
) -> List[Tuple[Optional[Dict], Optional[Dict], List]]:
|
||||||
nwc = await get_nwc(pubkey, None, False, True)
|
nwc = await get_nwc(data.pubkey, None, False, True)
|
||||||
error = await _check(nwc, "get_info", payload)
|
error = await _check(nwc, "get_info", data.payload)
|
||||||
if error:
|
if error:
|
||||||
return [(None, error, [])]
|
return [(None, error, [])]
|
||||||
if not nwc:
|
if not nwc:
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,8 @@
|
||||||
</div>
|
</div>
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<script>
|
<script>
|
||||||
new Vue({
|
window.app = Vue.createApp({
|
||||||
el: "#vue",
|
el: '#vue',
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
delimiters: ["${", "}"],
|
delimiters: ["${", "}"],
|
||||||
data: function () {
|
data: function () {
|
||||||
|
|
|
||||||
|
|
@ -46,10 +46,10 @@
|
||||||
<q-table
|
<q-table
|
||||||
dense
|
dense
|
||||||
flat
|
flat
|
||||||
:data="nwcEntries"
|
:rows="nwcEntries"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
:columns="nwcsTable.columns"
|
:columns="nwcsTable.columns"
|
||||||
:pagination.sync="nwcsTable.pagination"
|
v-model:pagination="nwcsTable.pagination"
|
||||||
>
|
>
|
||||||
<nwcprovider v-slot:header="props">
|
<nwcprovider v-slot:header="props">
|
||||||
<q-tr :props="props">
|
<q-tr :props="props">
|
||||||
|
|
@ -407,8 +407,8 @@
|
||||||
window.NobleSecp256k1 = NobleSecp256k1;
|
window.NobleSecp256k1 = NobleSecp256k1;
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
new Vue({
|
window.app = Vue.createApp({
|
||||||
el: "#vue",
|
el: '#vue',
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
delimiters: ["${", "}"],
|
delimiters: ["${", "}"],
|
||||||
data: function () {
|
data: function () {
|
||||||
|
|
|
||||||
29
toc.md
Normal file
29
toc.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Terms and Conditions for LNbits Extension
|
||||||
|
|
||||||
|
## 1. Acceptance of Terms
|
||||||
|
|
||||||
|
By installing and using the LNbits extension ("Extension"), you agree to be bound by these terms and conditions ("Terms"). If you do not agree to these Terms, do not use the Extension.
|
||||||
|
|
||||||
|
## 2. License
|
||||||
|
|
||||||
|
The Extension is free and open-source software, released under [specify the FOSS license here, e.g., GPL-3.0, MIT, etc.]. You are permitted to use, copy, modify, and distribute the Extension under the terms of that license.
|
||||||
|
|
||||||
|
## 3. No Warranty
|
||||||
|
|
||||||
|
The Extension is provided "as is" and with all faults, and the developer expressly disclaims all warranties of any kind, whether express, implied, statutory, or otherwise, including but not limited to warranties of merchantability, fitness for a particular purpose, non-infringement, and any warranties arising out of course of dealing or usage of trade. No advice or information, whether oral or written, obtained from the developer or elsewhere will create any warranty not expressly stated in this Terms.
|
||||||
|
|
||||||
|
## 4. Limitation of Liability
|
||||||
|
|
||||||
|
In no event will the developer be liable to you or any third party for any direct, indirect, incidental, special, consequential, or punitive damages, including lost profit, lost revenue, loss of data, or other damages arising out of or in connection with your use of the Extension, even if the developer has been advised of the possibility of such damages. The foregoing limitation of liability shall apply to the fullest extent permitted by law in the applicable jurisdiction.
|
||||||
|
|
||||||
|
## 5. Modification of Terms
|
||||||
|
|
||||||
|
The developer reserves the right to modify these Terms at any time. You are advised to review these Terms periodically for any changes. Changes to these Terms are effective when they are posted on the appropriate location within or associated with the Extension.
|
||||||
|
|
||||||
|
## 6. General Provisions
|
||||||
|
|
||||||
|
If any provision of these Terms is held to be invalid or unenforceable, that provision will be enforced to the maximum extent permissible, and the other provisions of these Terms will remain in full force and effect. These Terms constitute the entire agreement between you and the developer regarding the use of the Extension.
|
||||||
|
|
||||||
|
## 7. Contact Information
|
||||||
|
|
||||||
|
If you have any questions about these Terms, please contact the developer at [developer's contact information].
|
||||||
67
views_api.py
67
views_api.py
|
|
@ -17,7 +17,15 @@ from .crud import (
|
||||||
get_wallet_nwcs,
|
get_wallet_nwcs,
|
||||||
set_config_nwc,
|
set_config_nwc,
|
||||||
)
|
)
|
||||||
from .models import NWCGetResponse, NWCRegistrationRequest
|
from .models import (
|
||||||
|
NWCGetResponse,
|
||||||
|
RegisterNWC,
|
||||||
|
GetWalletNWC,
|
||||||
|
GetBudgetsNWC,
|
||||||
|
GetNWCKey,
|
||||||
|
GetNWCs,
|
||||||
|
DeleteNWC
|
||||||
|
)
|
||||||
from .permission import nwc_permissions
|
from .permission import nwc_permissions
|
||||||
|
|
||||||
nwcprovider_api_router = APIRouter()
|
nwcprovider_api_router = APIRouter()
|
||||||
|
|
@ -26,7 +34,6 @@ nwcprovider_api_router = APIRouter()
|
||||||
# Get supported permissions
|
# Get supported permissions
|
||||||
@nwcprovider_api_router.get("/api/v1/permissions", status_code=HTTPStatus.OK)
|
@nwcprovider_api_router.get("/api/v1/permissions", status_code=HTTPStatus.OK)
|
||||||
async def api_get_permissions(
|
async def api_get_permissions(
|
||||||
req: Request,
|
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
return nwc_permissions
|
return nwc_permissions
|
||||||
|
|
@ -37,17 +44,23 @@ async def api_get_permissions(
|
||||||
"/api/v1/nwc", status_code=HTTPStatus.OK, response_model=List[NWCGetResponse]
|
"/api/v1/nwc", status_code=HTTPStatus.OK, response_model=List[NWCGetResponse]
|
||||||
)
|
)
|
||||||
async def api_get_nwcs(
|
async def api_get_nwcs(
|
||||||
req: Request,
|
data: GetNWCs,
|
||||||
include_expired: bool = False,
|
|
||||||
calculate_spent_budget: bool = False,
|
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
):
|
):
|
||||||
|
|
||||||
wallet_id = wallet.wallet.id
|
wallet_id = wallet.wallet.id
|
||||||
nwcs = await get_wallet_nwcs(wallet_id, include_expired)
|
wallet_nwcs = GetWalletNWC(
|
||||||
|
wallet_id=wallet_id,
|
||||||
|
include_expired=data.include_expired
|
||||||
|
)
|
||||||
|
nwcs = await get_wallet_nwcs(wallet_nwcs)
|
||||||
out = []
|
out = []
|
||||||
for nwc in nwcs:
|
for nwc in nwcs:
|
||||||
budgets = await get_budgets_nwc(nwc.pubkey, calculate_spent_budget)
|
budgets_nwc = GetBudgetsNWC(
|
||||||
|
pubkey=nwc.pubkey,
|
||||||
|
calculate_spent=data.calculate_spent_budget
|
||||||
|
)
|
||||||
|
budgets = await get_budgets_nwc(budgets_nwc)
|
||||||
res = NWCGetResponse(data=nwc, budgets=budgets)
|
res = NWCGetResponse(data=nwc, budgets=budgets)
|
||||||
out.append(res)
|
out.append(res)
|
||||||
return out
|
return out
|
||||||
|
|
@ -58,16 +71,14 @@ async def api_get_nwcs(
|
||||||
"/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK, response_model=NWCGetResponse
|
"/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK, response_model=NWCGetResponse
|
||||||
)
|
)
|
||||||
async def api_get_nwc(
|
async def api_get_nwc(
|
||||||
req: Request,
|
data:GetNWCKey,
|
||||||
pubkey: str,
|
wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
include_expired: Optional[bool] = False,
|
) -> NWCGetResponse:
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
|
||||||
) -> NWCGetResponse:
|
|
||||||
wallet_id = wallet.wallet.id
|
wallet_id = wallet.wallet.id
|
||||||
nwc = await get_nwc(pubkey, wallet_id, include_expired)
|
nwc = await get_nwc(data.pubkey, wallet_id, data.include_expired)
|
||||||
if not nwc:
|
if not nwc:
|
||||||
raise Exception("Pubkey has no associated wallet")
|
raise Exception("Pubkey has no associated wallet")
|
||||||
res = NWCGetResponse(data=nwc, budgets=await get_budgets_nwc(pubkey))
|
res = NWCGetResponse(data=nwc, budgets=await get_budgets_nwc(data.pubkey))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -115,21 +126,19 @@ async def api_get_pairing_url(req: Request, secret: str) -> str:
|
||||||
response_model=NWCGetResponse,
|
response_model=NWCGetResponse,
|
||||||
)
|
)
|
||||||
async def api_register_nwc(
|
async def api_register_nwc(
|
||||||
req: Request,
|
data:RegisterNWC, # Use the Pydantic model here
|
||||||
pubkey: str,
|
|
||||||
registration_data: NWCRegistrationRequest, # Use the Pydantic model here
|
|
||||||
wallet: WalletTypeInfo = Depends(require_admin_key),
|
wallet: WalletTypeInfo = Depends(require_admin_key),
|
||||||
):
|
):
|
||||||
wallet_id = wallet.wallet.id
|
wallet_id = wallet.wallet.id
|
||||||
nwc = await create_nwc(
|
nwc = await create_nwc(
|
||||||
pubkey,
|
data.pubkey,
|
||||||
wallet_id,
|
wallet_id,
|
||||||
registration_data.description,
|
data.registration_data.description,
|
||||||
registration_data.expires_at,
|
data.registration_data.expires_at,
|
||||||
registration_data.permissions,
|
data.registration_data.permissions,
|
||||||
registration_data.budgets,
|
data.registration_data.budgets,
|
||||||
)
|
)
|
||||||
budgets = await get_budgets_nwc(pubkey)
|
budgets = await get_budgets_nwc(data.pubkey)
|
||||||
res = NWCGetResponse(data=nwc, budgets=budgets)
|
res = NWCGetResponse(data=nwc, budgets=budgets)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
@ -137,20 +146,18 @@ async def api_register_nwc(
|
||||||
# Delete a nwc key
|
# Delete a nwc key
|
||||||
@nwcprovider_api_router.delete("/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK)
|
@nwcprovider_api_router.delete("/api/v1/nwc/{pubkey}", status_code=HTTPStatus.OK)
|
||||||
async def api_delete_nwc(
|
async def api_delete_nwc(
|
||||||
req: Request, pubkey: str, wallet: WalletTypeInfo = Depends(require_admin_key)
|
data:DeleteNWC, wallet: WalletTypeInfo = Depends(require_admin_key)
|
||||||
):
|
):
|
||||||
wallet_id = wallet.wallet.id
|
wallet_id = wallet.wallet.id
|
||||||
await delete_nwc(pubkey, wallet_id)
|
await delete_nwc(data.pubkey, wallet_id)
|
||||||
return JSONResponse(content={"message": f"NWC key {pubkey} deleted successfully."})
|
return JSONResponse(content={"message": f"NWC key {data.pubkey} deleted successfully."})
|
||||||
|
|
||||||
|
|
||||||
# Get config
|
# Get config
|
||||||
@nwcprovider_api_router.get(
|
@nwcprovider_api_router.get(
|
||||||
"/api/v1/config", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
|
"/api/v1/config", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)]
|
||||||
)
|
)
|
||||||
async def api_get_all_config_nwc(
|
async def api_get_all_config_nwc():
|
||||||
req: Request,
|
|
||||||
):
|
|
||||||
config = await get_all_config_nwc()
|
config = await get_all_config_nwc()
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
@ -161,7 +168,7 @@ async def api_get_all_config_nwc(
|
||||||
status_code=HTTPStatus.OK,
|
status_code=HTTPStatus.OK,
|
||||||
dependencies=[Depends(check_admin)],
|
dependencies=[Depends(check_admin)],
|
||||||
)
|
)
|
||||||
async def api_get_config_nwc(req: Request, key: str):
|
async def api_get_config_nwc(key: str):
|
||||||
config = await get_config_nwc(key)
|
config = await get_config_nwc(key)
|
||||||
out = {}
|
out = {}
|
||||||
out[key] = config
|
out[key] = config
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue