Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CI
on:
push:
branches:
- main
pull_request:

jobs:
lint:
uses: lnbits/lnbits/.github/workflows/lint.yml@dev
tests:
runs-on: ubuntu-latest
needs: [lint]
strategy:
matrix:
python-version: ['3.11']
steps:
- uses: actions/checkout@v4
- uses: lnbits/lnbits/.github/actions/prepare@dev
with:
python-version: ${{ matrix.python-version }}
- name: Run pytest
uses: pavelzw/pytest-action@v2
env:
LNBITS_BACKEND_WALLET_CLASS: FakeWallet
PYTHONUNBUFFERED: 1
DEBUG: true
with:
verbose: true
job-summary: true
emoji: false
click-to-expand: true
custom-pytest: uv run pytest
report-title: 'test (${{ matrix.python-version }})'
10 changes: 0 additions & 10 deletions .github/workflows/lint.yml

This file was deleted.

24 changes: 12 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,42 +5,42 @@ format: prettier black ruff
check: mypy pyright checkblack checkruff checkprettier

prettier:
poetry run ./node_modules/.bin/prettier --write .
uv run ./node_modules/.bin/prettier --write .
pyright:
poetry run ./node_modules/.bin/pyright
uv run ./node_modules/.bin/pyright

mypy:
poetry run mypy .
uv run mypy .

black:
poetry run black .
uv run black .

ruff:
poetry run ruff check . --fix
uv run ruff check . --fix

checkruff:
poetry run ruff check .
uv run ruff check .

checkprettier:
poetry run ./node_modules/.bin/prettier --check .
uv run ./node_modules/.bin/prettier --check .

checkblack:
poetry run black --check .
uv run black --check .

checkeditorconfig:
editorconfig-checker

test:
PYTHONUNBUFFERED=1 \
DEBUG=true \
poetry run pytest
uv run pytest
install-pre-commit-hook:
@echo "Installing pre-commit hook to git"
@echo "Uninstall the hook with poetry run pre-commit uninstall"
poetry run pre-commit install
@echo "Uninstall the hook with uv run pre-commit uninstall"
uv run pre-commit install

pre-commit:
poetry run pre-commit run --all-files
uv run pre-commit run --all-files


checkbundle:
Expand Down
2 changes: 1 addition & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "Onchain Wallet",
"short_description": "Onchain watch only wallets",
"tile": "/watchonly/static/bitcoin-wallet.png",
"min_lnbits_version": "1.0.0",
"min_lnbits_version": "1.4.0",
"contributors": [
{
"name": "motorina0",
Expand Down
12 changes: 5 additions & 7 deletions crud.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Optional

from lnbits.db import Database
from lnbits.helpers import urlsafe_short_hash

Expand All @@ -14,7 +12,7 @@ async def create_watch_wallet(wallet: WalletAccount) -> WalletAccount:
return wallet


async def get_watch_wallet(wallet_id: str) -> Optional[WalletAccount]:
async def get_watch_wallet(wallet_id: str) -> WalletAccount | None:
return await db.fetchone(
"SELECT * FROM watchonly.wallets WHERE id = :id",
{"id": wallet_id},
Expand Down Expand Up @@ -45,7 +43,7 @@ async def delete_watch_wallet(wallet_id: str) -> None:
)


async def get_fresh_address(wallet_id: str) -> Optional[Address]:
async def get_fresh_address(wallet_id: str) -> Address | None:
# todo: move logic to views_api after satspay refactoring
wallet = await get_watch_wallet(wallet_id)

Expand Down Expand Up @@ -129,15 +127,15 @@ async def create_fresh_addresses(
)


async def get_address(address: str) -> Optional[Address]:
async def get_address(address: str) -> Address | None:
return await db.fetchone(
"SELECT * FROM watchonly.addresses WHERE address = :address",
{"address": address},
Address,
)


async def get_address_by_id(address_id: str) -> Optional[Address]:
async def get_address_by_id(address_id: str) -> Address | None:
return await db.fetchone(
"SELECT * FROM watchonly.addresses WHERE id = :id",
{"id": address_id},
Expand All @@ -147,7 +145,7 @@ async def get_address_by_id(address_id: str) -> Optional[Address]:

async def get_address_at_index(
wallet_id: str, branch_index: int, address_index: int
) -> Optional[Address]:
) -> Address | None:
return await db.fetchone(
"""
SELECT * FROM watchonly.addresses
Expand Down
71 changes: 71 additions & 0 deletions decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from dataclasses import dataclass

from fastapi import Depends, HTTPException, Request
from lnbits.decorators import (
check_access_token,
check_account_exists,
require_admin_key,
require_invoice_key,
)
from pydantic.types import UUID4


@dataclass(frozen=True)
class WatchOnlyAuth:
user_id: str


async def require_watchonly_read_account(
request: Request,
access_token: str | None = Depends(check_access_token),
usr: UUID4 | None = None,
) -> WatchOnlyAuth:
return await _require_watchonly_account(
request, access_token, usr, require_invoice_key
)


async def require_watchonly_admin_account(
request: Request,
access_token: str | None = Depends(check_access_token),
usr: UUID4 | None = None,
) -> WatchOnlyAuth:
return await _require_watchonly_account(
request, access_token, usr, require_admin_key
)


async def _require_watchonly_account(
request: Request,
access_token: str | None,
usr: UUID4 | None,
legacy_key_checker,
) -> WatchOnlyAuth:
api_key_header, api_key_query = _legacy_api_key(request)
account_error: HTTPException | None = None

if access_token or usr:
try:
account = await check_account_exists(request, access_token, usr)
return WatchOnlyAuth(user_id=account.id)
except HTTPException as exc:
account_error = exc
if not api_key_header and not api_key_query:
raise

try:
key_info = await legacy_key_checker(
request,
api_key_header=api_key_header,
api_key_query=api_key_query,
)
except HTTPException as exc:
if account_error:
raise account_error from exc
raise

return WatchOnlyAuth(user_id=key_info.wallet.user)


def _legacy_api_key(request: Request) -> tuple[str | None, str | None]:
return request.headers.get("X-API-KEY"), request.query_params.get("api-key")
4 changes: 1 addition & 3 deletions helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Optional, Tuple

from embit.descriptor import Descriptor, Key
from embit.descriptor.arguments import AllowedDerivation
from embit.networks import NETWORKS
Expand All @@ -14,7 +12,7 @@ def detect_network(k):
return net


def parse_key(masterpub: str) -> Tuple[Descriptor, Optional[dict]]:
def parse_key(masterpub: str) -> tuple[Descriptor, dict | None]:
"""Parses masterpub or descriptor and returns a tuple: (Descriptor, network)
To create addresses use descriptor.derive(num).address(network=network)
"""
Expand Down
16 changes: 7 additions & 9 deletions models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Optional

from fastapi import Query
from pydantic import BaseModel

Expand All @@ -19,7 +17,7 @@ class WalletAccount(BaseModel):
title: str
address_no: int
balance: int
type: Optional[str] = ""
type: str | None = ""
network: str = "Mainnet"
meta: str = "{}"

Expand All @@ -31,7 +29,7 @@ class Address(BaseModel):
amount: int = 0
branch_index: int = 0
address_index: int
note: Optional[str] = None
note: str | None = None
has_activity: bool = False


Expand All @@ -49,9 +47,9 @@ class TransactionInput(BaseModel):
class TransactionOutput(BaseModel):
amount: int
address: str
branch_index: Optional[int] = None
address_index: Optional[int] = None
wallet: Optional[str] = None
branch_index: int | None = None
address_index: int | None = None
wallet: str | None = None


class MasterPublicKey(BaseModel):
Expand Down Expand Up @@ -84,8 +82,8 @@ class ExtractTx(BaseModel):


class SignedTransaction(BaseModel):
tx_hex: Optional[str]
tx_json: Optional[str]
tx_hex: str | None
tx_json: str | None


class Config(BaseModel):
Expand Down
Loading
Loading