|
1 | 1 | from typing import Any |
2 | 2 |
|
3 | 3 | import httpx |
4 | | -from lnbits.core.crud import get_wallet |
| 4 | +from lnbits.core.crud import ( |
| 5 | + get_installed_extension, |
| 6 | + get_user_active_extensions_ids, |
| 7 | + get_wallet, |
| 8 | +) |
5 | 9 | from lnbits.core.models import User |
6 | 10 | from lnbits.helpers import create_access_token |
7 | 11 | from lnbits.settings import settings |
@@ -121,6 +125,93 @@ def inventory_available_for_user(user: User | None) -> bool: |
121 | 125 | return bool(user and "inventory" in (user.extensions or [])) |
122 | 126 |
|
123 | 127 |
|
| 128 | +async def watchonly_available_for_user(user_id: str) -> bool: |
| 129 | + installed = await get_installed_extension("watchonly") |
| 130 | + if not installed or not installed.active: |
| 131 | + return False |
| 132 | + active_extensions = await get_user_active_extensions_ids(user_id) |
| 133 | + return "watchonly" in active_extensions |
| 134 | + |
| 135 | + |
| 136 | +async def fetch_watchonly_config(api_key: str) -> dict[str, Any]: |
| 137 | + async with httpx.AsyncClient() as client: |
| 138 | + resp = await client.get( |
| 139 | + url=f"http://{settings.host}:{settings.port}/watchonly/api/v1/config", |
| 140 | + headers={"X-API-KEY": api_key}, |
| 141 | + ) |
| 142 | + resp.raise_for_status() |
| 143 | + return resp.json() |
| 144 | + |
| 145 | + |
| 146 | +async def fetch_watchonly_wallets(api_key: str, network: str) -> list[dict[str, Any]]: |
| 147 | + async with httpx.AsyncClient() as client: |
| 148 | + resp = await client.get( |
| 149 | + url=f"http://{settings.host}:{settings.port}/watchonly/api/v1/wallet", |
| 150 | + headers={"X-API-KEY": api_key}, |
| 151 | + params={"network": network}, |
| 152 | + ) |
| 153 | + resp.raise_for_status() |
| 154 | + return resp.json() |
| 155 | + |
| 156 | + |
| 157 | +async def fetch_watchonly_wallet(api_key: str, wallet_id: str) -> dict[str, Any]: |
| 158 | + async with httpx.AsyncClient() as client: |
| 159 | + resp = await client.get( |
| 160 | + url=f"http://{settings.host}:{settings.port}/watchonly/api/v1/wallet/{wallet_id}", |
| 161 | + headers={"X-API-KEY": api_key}, |
| 162 | + ) |
| 163 | + resp.raise_for_status() |
| 164 | + return resp.json() |
| 165 | + |
| 166 | + |
| 167 | +async def fetch_onchain_address(api_key: str, wallet_id: str) -> dict[str, Any]: |
| 168 | + async with httpx.AsyncClient() as client: |
| 169 | + resp = await client.get( |
| 170 | + url=f"http://{settings.host}:{settings.port}/watchonly/api/v1/address/{wallet_id}", |
| 171 | + headers={"X-API-KEY": api_key}, |
| 172 | + ) |
| 173 | + resp.raise_for_status() |
| 174 | + return resp.json() |
| 175 | + |
| 176 | + |
| 177 | +def normalize_mempool_endpoint( |
| 178 | + mempool_endpoint: str | None, onchain_address: str |
| 179 | +) -> str: |
| 180 | + endpoint = (mempool_endpoint or "https://mempool.space").rstrip("/") |
| 181 | + if "/testnet" in endpoint or "/signet" in endpoint: |
| 182 | + return endpoint |
| 183 | + if onchain_address.lower().startswith("tb1"): |
| 184 | + return f"{endpoint}/testnet" |
| 185 | + return endpoint |
| 186 | + |
| 187 | + |
| 188 | +async def fetch_onchain_balance( |
| 189 | + mempool_endpoint: str, onchain_address: str |
| 190 | +) -> dict[str, Any]: |
| 191 | + endpoint = normalize_mempool_endpoint(mempool_endpoint, onchain_address) |
| 192 | + async with httpx.AsyncClient() as client: |
| 193 | + resp = await client.get(f"{endpoint}/api/address/{onchain_address}/txs") |
| 194 | + resp.raise_for_status() |
| 195 | + data = resp.json() |
| 196 | + confirmed_txs = [tx for tx in data if tx["status"]["confirmed"]] |
| 197 | + unconfirmed_txs = [tx for tx in data if not tx["status"]["confirmed"]] |
| 198 | + return { |
| 199 | + "confirmed": sum_transactions(onchain_address, confirmed_txs), |
| 200 | + "unconfirmed": sum_transactions(onchain_address, unconfirmed_txs), |
| 201 | + "txids": [tx["txid"] for tx in data], |
| 202 | + } |
| 203 | + |
| 204 | + |
| 205 | +def sum_outputs(address: str, vouts: list[dict[str, Any]]) -> int: |
| 206 | + return sum( |
| 207 | + vout["value"] for vout in vouts if vout.get("scriptpubkey_address") == address |
| 208 | + ) |
| 209 | + |
| 210 | + |
| 211 | +def sum_transactions(address: str, txs: list[dict[str, Any]]) -> int: |
| 212 | + return sum(sum_outputs(address, tx.get("vout", [])) for tx in txs) |
| 213 | + |
| 214 | + |
124 | 215 | async def push_order_to_orders( |
125 | 216 | user_id: str, |
126 | 217 | payment, |
|
0 commit comments