lnbits-legend/lnbits/extensions/watchonly/views_api.py

280 lines
9 KiB
Python
Raw Normal View History

2021-10-14 11:45:30 +01:00
from http import HTTPStatus
2021-10-14 22:30:47 +01:00
2022-07-28 15:09:23 +03:00
from embit import script
from embit.descriptor import Descriptor, Key
from embit.ec import PublicKey
2022-07-28 15:09:23 +03:00
from embit.psbt import PSBT, DerivationPath
from embit.transaction import Transaction, TransactionInput, TransactionOutput
2022-07-28 15:09:23 +03:00
from fastapi import Query, Request
from fastapi.params import Depends
from starlette.exceptions import HTTPException
2021-10-20 12:03:11 +01:00
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
2021-10-14 11:45:30 +01:00
from lnbits.extensions.watchonly import watchonly_ext
2021-10-18 10:58:09 +01:00
2021-10-20 12:03:11 +01:00
from .crud import (
create_watch_wallet,
2022-07-28 15:09:23 +03:00
delete_addresses_for_wallet,
2021-10-20 12:03:11 +01:00
delete_watch_wallet,
get_addresses,
2022-07-28 15:09:23 +03:00
get_config,
2021-10-20 12:03:11 +01:00
get_fresh_address,
create_fresh_addresses,
update_address,
delete_addresses_for_wallet,
2021-10-20 12:03:11 +01:00
get_watch_wallet,
get_watch_wallets,
update_watch_wallet,
2021-10-20 12:03:11 +01:00
)
from .helpers import parse_key
2022-07-28 15:09:23 +03:00
from .models import Config, CreatePsbt, CreateWallet, WalletAccount
2021-10-14 11:45:30 +01:00
###################WALLETS#############################
2021-10-14 22:30:47 +01:00
@watchonly_ext.get("/api/v1/wallet")
async def api_wallets_retrieve(wallet: WalletTypeInfo = Depends(get_key_type)):
2021-10-14 11:45:30 +01:00
try:
2021-10-14 22:30:47 +01:00
return [wallet.dict() for wallet in await get_watch_wallets(wallet.wallet.user)]
2021-10-14 11:45:30 +01:00
except:
return ""
2021-10-14 22:30:47 +01:00
@watchonly_ext.get("/api/v1/wallet/{wallet_id}")
2021-10-17 18:33:29 +01:00
async def api_wallet_retrieve(
wallet_id, wallet: WalletTypeInfo = Depends(get_key_type)
):
2021-10-14 22:30:47 +01:00
w_wallet = await get_watch_wallet(wallet_id)
2021-10-14 11:45:30 +01:00
2021-10-14 22:30:47 +01:00
if not w_wallet:
raise HTTPException(
2021-10-17 18:33:29 +01:00
status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
2021-10-14 22:30:47 +01:00
)
2021-10-14 11:45:30 +01:00
2021-10-14 22:30:47 +01:00
return w_wallet.dict()
2021-10-14 11:45:30 +01:00
2021-10-14 22:30:47 +01:00
@watchonly_ext.post("/api/v1/wallet")
2021-10-17 18:33:29 +01:00
async def api_wallet_create_or_update(
data: CreateWallet, w: WalletTypeInfo = Depends(require_admin_key)
2021-10-17 18:33:29 +01:00
):
2021-10-14 11:45:30 +01:00
try:
(descriptor, _) = parse_key(data.masterpub)
new_wallet = WalletAccount(
id="none",
user=w.wallet.user,
masterpub=data.masterpub,
fingerprint=descriptor.keys[0].fingerprint.hex(),
type=descriptor.scriptpubkey_type(),
title=data.title,
address_no=-1, # so fresh address on empty wallet can get address with index 0
balance=0,
)
wallets = await get_watch_wallets(w.wallet.user)
existing_wallet = next(
(ew for ew in wallets if ew.fingerprint == new_wallet.fingerprint), None
2021-10-14 11:45:30 +01:00
)
if existing_wallet:
raise ValueError(
"Account '{}' has the same master pulic key".format(
existing_wallet.title
)
)
wallet = await create_watch_wallet(new_wallet)
await api_get_addresses(wallet.id, w)
2021-10-14 11:45:30 +01:00
except Exception as e:
2021-10-17 18:33:29 +01:00
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
2021-10-14 22:30:47 +01:00
config = await get_config(w.wallet.user)
if not config:
await create_config(user=w.wallet.user)
2021-10-14 22:30:47 +01:00
return wallet.dict()
2021-10-14 11:45:30 +01:00
2021-10-14 22:30:47 +01:00
@watchonly_ext.delete("/api/v1/wallet/{wallet_id}")
2021-10-20 12:03:11 +01:00
async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(require_admin_key)):
2021-10-14 11:45:30 +01:00
wallet = await get_watch_wallet(wallet_id)
if not wallet:
2021-10-14 22:30:47 +01:00
raise HTTPException(
2021-10-17 18:33:29 +01:00
status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
2021-10-14 22:30:47 +01:00
)
2021-10-14 11:45:30 +01:00
await delete_watch_wallet(wallet_id)
await delete_addresses_for_wallet(wallet_id)
2021-10-14 11:45:30 +01:00
2021-10-14 22:30:47 +01:00
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
2021-10-14 11:45:30 +01:00
#############################ADDRESSES##########################
2021-10-14 22:30:47 +01:00
@watchonly_ext.get("/api/v1/address/{wallet_id}")
async def api_fresh_address(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
address = await get_fresh_address(wallet_id)
return address.dict()
2021-10-14 11:45:30 +01:00
@watchonly_ext.put("/api/v1/address/{id}")
async def api_update_address(
id: str, req: Request, w: WalletTypeInfo = Depends(require_admin_key)
):
body = await req.json()
params = {}
# amout is only updated if the address has history
if "amount" in body:
params["amount"] = int(body["amount"])
params["has_activity"] = True
if "note" in body:
params["note"] = str(body["note"])
address = await update_address(**params, id=id)
wallet = (
await get_watch_wallet(address.wallet)
if address.branch_index == 0 and address.amount != 0
else None
)
if wallet and wallet.address_no < address.address_index:
await update_watch_wallet(
address.wallet, **{"address_no": address.address_index}
)
return address
2021-10-14 11:45:30 +01:00
2021-10-14 22:30:47 +01:00
@watchonly_ext.get("/api/v1/addresses/{wallet_id}")
async def api_get_addresses(wallet_id, w: WalletTypeInfo = Depends(get_key_type)):
2021-10-14 11:45:30 +01:00
wallet = await get_watch_wallet(wallet_id)
if not wallet:
2021-10-14 22:30:47 +01:00
raise HTTPException(
2021-10-17 18:33:29 +01:00
status_code=HTTPStatus.NOT_FOUND, detail="Wallet does not exist."
2021-10-14 22:30:47 +01:00
)
2021-10-14 11:45:30 +01:00
addresses = await get_addresses(wallet_id)
config = await get_config(w.wallet.user)
2021-10-14 11:45:30 +01:00
if not addresses:
await create_fresh_addresses(wallet_id, 0, config.receive_gap_limit)
await create_fresh_addresses(wallet_id, 0, config.change_gap_limit, True)
2021-10-14 11:45:30 +01:00
addresses = await get_addresses(wallet_id)
receive_addresses = list(filter(lambda addr: addr.branch_index == 0, addresses))
change_addresses = list(filter(lambda addr: addr.branch_index == 1, addresses))
last_receive_address = list(
filter(lambda addr: addr.has_activity, receive_addresses)
)[-1:]
last_change_address = list(
filter(lambda addr: addr.has_activity, change_addresses)
)[-1:]
if last_receive_address:
current_index = receive_addresses[-1].address_index
address_index = last_receive_address[0].address_index
await create_fresh_addresses(
wallet_id, current_index + 1, address_index + config.receive_gap_limit + 1
)
if last_change_address:
current_index = change_addresses[-1].address_index
address_index = last_change_address[0].address_index
await create_fresh_addresses(
wallet_id,
current_index + 1,
address_index + config.change_gap_limit + 1,
True,
)
addresses = await get_addresses(wallet_id)
2021-10-14 22:30:47 +01:00
return [address.dict() for address in addresses]
2021-10-14 11:45:30 +01:00
#############################PSBT##########################
2021-10-14 11:45:30 +01:00
@watchonly_ext.post("/api/v1/psbt")
async def api_psbt_create(
data: CreatePsbt, w: WalletTypeInfo = Depends(require_admin_key)
):
try:
vin = [
TransactionInput(bytes.fromhex(inp.tx_id), inp.vout) for inp in data.inputs
]
vout = [
TransactionOutput(out.amount, script.address_to_scriptpubkey(out.address))
for out in data.outputs
]
descriptors = {}
for _, masterpub in enumerate(data.masterpubs):
descriptors[masterpub.fingerprint] = parse_key(masterpub.public_key)
inputs_extra = []
bip32_derivations = {}
for i, inp in enumerate(data.inputs):
descriptor = descriptors[inp.masterpub_fingerprint][0]
d = descriptor.derive(inp.address_index, inp.branch_index)
for k in d.keys:
bip32_derivations[PublicKey.parse(k.sec())] = DerivationPath(
k.origin.fingerprint, k.origin.derivation
)
inputs_extra.append(
{
"bip32_derivations": bip32_derivations,
"non_witness_utxo": Transaction.from_string(inp.tx_hex),
}
)
tx = Transaction(vin=vin, vout=vout)
psbt = PSBT(tx)
for i, inp in enumerate(inputs_extra):
psbt.inputs[i].bip32_derivations = inp["bip32_derivations"]
psbt.inputs[i].non_witness_utxo = inp.get("non_witness_utxo", None)
outputs_extra = []
bip32_derivations = {}
for i, out in enumerate(data.outputs):
if out.branch_index == 1:
descriptor = descriptors[out.masterpub_fingerprint][0]
d = descriptor.derive(out.address_index, out.branch_index)
for k in d.keys:
bip32_derivations[PublicKey.parse(k.sec())] = DerivationPath(
k.origin.fingerprint, k.origin.derivation
)
outputs_extra.append({"bip32_derivations": bip32_derivations})
for i, out in enumerate(outputs_extra):
psbt.outputs[i].bip32_derivations = out["bip32_derivations"]
return psbt.to_string()
except Exception as e:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
#############################CONFIG##########################
@watchonly_ext.put("/api/v1/config")
async def api_update_config(
data: Config, w: WalletTypeInfo = Depends(require_admin_key)
):
config = await update_config(data, user=w.wallet.user)
return config.dict()
@watchonly_ext.get("/api/v1/config")
async def api_get_config(w: WalletTypeInfo = Depends(get_key_type)):
config = await get_config(w.wallet.user)
if not config:
config = await create_config(user=w.wallet.user)
return config.dict()