updated watchonly
This commit is contained in:
parent
52956a62a2
commit
a838706090
4
lnbits/extensions/watchonly/README.md
Normal file
4
lnbits/extensions/watchonly/README.md
Normal file
|
@ -0,0 +1,4 @@
|
|||
# Watch Only wallet
|
||||
|
||||
Monitor an extended public key and generate deterministic fresh public keys with this simple watch only wallet. Invoice payments can also be generated, both through a publically shareable page and API.
|
||||
|
8
lnbits/extensions/watchonly/__init__.py
Normal file
8
lnbits/extensions/watchonly/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from quart import Blueprint
|
||||
|
||||
|
||||
watchonly_ext: Blueprint = Blueprint("watchonly", __name__, static_folder="static", template_folder="templates")
|
||||
|
||||
|
||||
from .views_api import * # noqa
|
||||
from .views import * # noqa
|
8
lnbits/extensions/watchonly/config.json
Normal file
8
lnbits/extensions/watchonly/config.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Watch Only",
|
||||
"short_description": "Onchain watch only wallets",
|
||||
"icon": "visibility",
|
||||
"contributors": [
|
||||
"arcbtc"
|
||||
]
|
||||
}
|
186
lnbits/extensions/watchonly/crud.py
Normal file
186
lnbits/extensions/watchonly/crud.py
Normal file
|
@ -0,0 +1,186 @@
|
|||
from typing import List, Optional, Union
|
||||
|
||||
from lnbits.db import open_ext_db
|
||||
|
||||
from .models import Wallets, Payments, Addresses, Mempool
|
||||
from lnbits.helpers import urlsafe_short_hash
|
||||
|
||||
from embit import bip32
|
||||
from embit import ec
|
||||
from embit.networks import NETWORKS
|
||||
from embit import base58
|
||||
from embit.util import hashlib
|
||||
import io
|
||||
from embit.util import secp256k1
|
||||
from embit import hashes
|
||||
from binascii import hexlify
|
||||
from quart import jsonify
|
||||
from embit import script
|
||||
from embit import ec
|
||||
from embit.networks import NETWORKS
|
||||
from binascii import unhexlify, hexlify, a2b_base64, b2a_base64
|
||||
|
||||
########################ADDRESSES#######################
|
||||
|
||||
def get_derive_address(wallet_id: str, num: int):
|
||||
|
||||
wallet = get_watch_wallet(wallet_id)
|
||||
k = bip32.HDKey.from_base58(str(wallet[2]))
|
||||
child = k.derive([0, num])
|
||||
address = script.p2wpkh(child).address()
|
||||
|
||||
return address
|
||||
|
||||
def get_fresh_address(wallet_id: str) -> Addresses:
|
||||
wallet = get_watch_wallet(wallet_id)
|
||||
|
||||
address = get_derive_address(wallet_id, wallet[4] + 1)
|
||||
|
||||
update_watch_wallet(wallet_id = wallet_id, address_no = wallet[4] + 1)
|
||||
with open_ext_db("watchonly") as db:
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO addresses (
|
||||
address,
|
||||
wallet,
|
||||
amount
|
||||
)
|
||||
VALUES (?, ?, ?)
|
||||
""",
|
||||
(address, wallet_id, 0),
|
||||
)
|
||||
|
||||
return get_address(address)
|
||||
|
||||
|
||||
def get_address(address: str) -> Addresses:
|
||||
with open_ext_db("watchonly") as db:
|
||||
row = db.fetchone("SELECT * FROM addresses WHERE address = ?", (address,))
|
||||
return Addresses.from_row(row) if row else None
|
||||
|
||||
|
||||
def get_addresses(wallet_id: str) -> List[Addresses]:
|
||||
with open_ext_db("watchonly") as db:
|
||||
rows = db.fetchall("SELECT * FROM addresses WHERE wallet = ?", (wallet_id,))
|
||||
return [Addresses(**row) for row in rows]
|
||||
|
||||
|
||||
##########################WALLETS####################
|
||||
|
||||
def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Wallets:
|
||||
wallet_id = urlsafe_short_hash()
|
||||
with open_ext_db("watchonly") as db:
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO wallets (
|
||||
id,
|
||||
user,
|
||||
masterpub,
|
||||
title,
|
||||
address_no,
|
||||
amount
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(wallet_id, user, masterpub, title, 0, 0),
|
||||
)
|
||||
# weallet_id = db.cursor.lastrowid
|
||||
address = get_fresh_address(wallet_id)
|
||||
return get_watch_wallet(wallet_id)
|
||||
|
||||
|
||||
def get_watch_wallet(wallet_id: str) -> Wallets:
|
||||
with open_ext_db("watchonly") as db:
|
||||
row = db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,))
|
||||
return Wallets.from_row(row) if row else None
|
||||
|
||||
def get_watch_wallets(user: str) -> List[Wallets]:
|
||||
with open_ext_db("watchonly") as db:
|
||||
rows = db.fetchall("SELECT * FROM wallets WHERE user = ?", (user,))
|
||||
return [Wallets(**row) for row in rows]
|
||||
|
||||
def update_watch_wallet(wallet_id: str, **kwargs) -> Optional[Wallets]:
|
||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||
|
||||
with open_ext_db("watchonly") as db:
|
||||
db.execute(f"UPDATE wallets SET {q} WHERE id = ?", (*kwargs.values(), wallet_id))
|
||||
row = db.fetchone("SELECT * FROM wallets WHERE id = ?", (wallet_id,))
|
||||
return Wallets.from_row(row) if row else None
|
||||
|
||||
|
||||
def delete_watch_wallet(wallet_id: str) -> None:
|
||||
with open_ext_db("watchonly") as db:
|
||||
db.execute("DELETE FROM wallets WHERE id = ?", (wallet_id,))
|
||||
|
||||
|
||||
###############PAYMENTS##########################
|
||||
|
||||
def create_payment(*, user: str, ex_key: str, description: str, amount: int) -> Payments:
|
||||
|
||||
address = get_fresh_address(ex_key)
|
||||
payment_id = urlsafe_short_hash()
|
||||
with open_ext_db("watchonly") as db:
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO payments (
|
||||
payment_id,
|
||||
user,
|
||||
ex_key,
|
||||
address,
|
||||
amount
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(payment_id, user, ex_key, address, amount),
|
||||
)
|
||||
payment_id = db.cursor.lastrowid
|
||||
return get_payment(payment_id)
|
||||
|
||||
|
||||
def get_payment(payment_id: str) -> Payments:
|
||||
with open_ext_db("watchonly") as db:
|
||||
row = db.fetchone("SELECT * FROM payments WHERE id = ?", (payment_id,))
|
||||
return Payments.from_row(row) if row else None
|
||||
|
||||
|
||||
def get_payments(user: str) -> List[Payments]:
|
||||
with open_ext_db("watchonly") as db:
|
||||
rows = db.fetchall("SELECT * FROM payments WHERE user IN ?", (user,))
|
||||
return [Payments.from_row(row) for row in rows]
|
||||
|
||||
|
||||
def delete_payment(payment_id: str) -> None:
|
||||
with open_ext_db("watchonly") as db:
|
||||
db.execute("DELETE FROM payments WHERE id = ?", (payment_id,))
|
||||
|
||||
|
||||
######################MEMPOOL#######################
|
||||
|
||||
def create_mempool(user: str) -> Mempool:
|
||||
with open_ext_db("watchonly") as db:
|
||||
db.execute(
|
||||
"""
|
||||
INSERT INTO mempool (
|
||||
user,
|
||||
endpoint
|
||||
)
|
||||
VALUES (?, ?)
|
||||
""",
|
||||
(user, 'https://mempool.space'),
|
||||
)
|
||||
row = db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,))
|
||||
return Mempool.from_row(row) if row else None
|
||||
|
||||
def update_mempool(user: str, **kwargs) -> Optional[Mempool]:
|
||||
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
|
||||
|
||||
with open_ext_db("watchonly") as db:
|
||||
db.execute(f"UPDATE mempool SET {q} WHERE user = ?", (*kwargs.values(), user))
|
||||
row = db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,))
|
||||
return Mempool.from_row(row) if row else None
|
||||
|
||||
|
||||
def get_mempool(user: str) -> Mempool:
|
||||
with open_ext_db("watchonly") as db:
|
||||
row = db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,))
|
||||
return Mempool.from_row(row) if row else None
|
48
lnbits/extensions/watchonly/migrations.py
Normal file
48
lnbits/extensions/watchonly/migrations.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
def m001_initial(db):
|
||||
"""
|
||||
Initial wallet table.
|
||||
"""
|
||||
db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS wallets (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
user TEXT,
|
||||
masterpub TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
address_no INTEGER NOT NULL DEFAULT 0,
|
||||
amount INTEGER NOT NULL
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS addresses (
|
||||
address TEXT NOT NULL PRIMARY KEY,
|
||||
wallet TEXT NOT NULL,
|
||||
amount INTEGER NOT NULL
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS payments (
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
user TEXT,
|
||||
masterpub TEXT NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
time_to_pay INTEGER NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
time TIMESTAMP NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||
);
|
||||
"""
|
||||
)
|
||||
db.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS mempool (
|
||||
user TEXT NOT NULL,
|
||||
endpoint TEXT NOT NULL
|
||||
);
|
||||
"""
|
||||
)
|
44
lnbits/extensions/watchonly/models.py
Normal file
44
lnbits/extensions/watchonly/models.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from sqlite3 import Row
|
||||
from typing import NamedTuple
|
||||
|
||||
class Wallets(NamedTuple):
|
||||
id: str
|
||||
user: str
|
||||
masterpub: str
|
||||
title: str
|
||||
address_no: int
|
||||
amount: int
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "Wallets":
|
||||
return cls(**dict(row))
|
||||
|
||||
class Payments(NamedTuple):
|
||||
id: str
|
||||
user: str
|
||||
ex_key: str
|
||||
address: str
|
||||
time_to_pay: str
|
||||
amount: int
|
||||
time: int
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "Payments":
|
||||
return cls(**dict(row))
|
||||
|
||||
class Addresses(NamedTuple):
|
||||
address: str
|
||||
wallet: str
|
||||
amount: int
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "Addresses":
|
||||
return cls(**dict(row))
|
||||
|
||||
class Mempool(NamedTuple):
|
||||
user: str
|
||||
endpoint: str
|
||||
|
||||
@classmethod
|
||||
def from_row(cls, row: Row) -> "Mempool":
|
||||
return cls(**dict(row))
|
141
lnbits/extensions/watchonly/templates/watchonly/_api_docs.html
Normal file
141
lnbits/extensions/watchonly/templates/watchonly/_api_docs.html
Normal file
|
@ -0,0 +1,141 @@
|
|||
<q-card>
|
||||
<q-card-section>
|
||||
<p>The WatchOnly extension uses https://mempool.block for blockchain data.<br />
|
||||
<small>
|
||||
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
|
||||
>
|
||||
</p>
|
||||
</q-card-section>
|
||||
|
||||
|
||||
|
||||
<q-expansion-item
|
||||
group="extras"
|
||||
icon="swap_vertical_circle"
|
||||
label="API info"
|
||||
:content-inset-level="0.5"
|
||||
>
|
||||
<q-expansion-item group="api" dense expand-separator label="List pay links">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<code><span class="text-blue">GET</span> /pay/api/v1/links</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <invoice_key>}</code><br />
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||
Returns 200 OK (application/json)
|
||||
</h5>
|
||||
<code>[<pay_link_object>, ...]</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X GET {{ request.url_root }}pay/api/v1/links -H "X-Api-Key: {{
|
||||
g.user.wallets[0].inkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item group="api" dense expand-separator label="Get a pay link">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<code
|
||||
><span class="text-blue">GET</span>
|
||||
/pay/api/v1/links/<pay_id></code
|
||||
>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <invoice_key>}</code><br />
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||
Returns 201 CREATED (application/json)
|
||||
</h5>
|
||||
<code>{"lnurl": <string>}</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X GET {{ request.url_root }}pay/api/v1/links/<pay_id> -H
|
||||
"X-Api-Key: {{ g.user.wallets[0].inkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item
|
||||
group="api"
|
||||
dense
|
||||
expand-separator
|
||||
label="Create a pay link"
|
||||
>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<code><span class="text-green">POST</span> /pay/api/v1/links</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||
<code>{"description": <string> "amount": <integer>}</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||
Returns 201 CREATED (application/json)
|
||||
</h5>
|
||||
<code>{"lnurl": <string>}</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X POST {{ request.url_root }}pay/api/v1/links -d
|
||||
'{"description": <string>, "amount": <integer>}' -H
|
||||
"Content-type: application/json" -H "X-Api-Key: {{
|
||||
g.user.wallets[0].adminkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item
|
||||
group="api"
|
||||
dense
|
||||
expand-separator
|
||||
label="Update a pay link"
|
||||
>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<code
|
||||
><span class="text-green">PUT</span>
|
||||
/pay/api/v1/links/<pay_id></code
|
||||
>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
|
||||
<code>{"description": <string>, "amount": <integer>}</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">
|
||||
Returns 200 OK (application/json)
|
||||
</h5>
|
||||
<code>{"lnurl": <string>}</code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X PUT {{ request.url_root }}pay/api/v1/links/<pay_id> -d
|
||||
'{"description": <string>, "amount": <integer>}' -H
|
||||
"Content-type: application/json" -H "X-Api-Key: {{
|
||||
g.user.wallets[0].adminkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
<q-expansion-item
|
||||
group="api"
|
||||
dense
|
||||
expand-separator
|
||||
label="Delete a pay link"
|
||||
class="q-pb-md"
|
||||
>
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<code
|
||||
><span class="text-pink">DELETE</span>
|
||||
/pay/api/v1/links/<pay_id></code
|
||||
>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
|
||||
<code>{"X-Api-Key": <admin_key>}</code><br />
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Returns 204 NO CONTENT</h5>
|
||||
<code></code>
|
||||
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
|
||||
<code
|
||||
>curl -X DELETE {{ request.url_root }}pay/api/v1/links/<pay_id>
|
||||
-H "X-Api-Key: {{ g.user.wallets[0].adminkey }}"
|
||||
</code>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item>
|
||||
</q-expansion-item>
|
54
lnbits/extensions/watchonly/templates/watchonly/display.html
Normal file
54
lnbits/extensions/watchonly/templates/watchonly/display.html
Normal file
|
@ -0,0 +1,54 @@
|
|||
{% extends "public.html" %} {% block page %}
|
||||
<div class="row q-col-gutter-md justify-center">
|
||||
<div class="col-12 col-sm-6 col-md-5 col-lg-4">
|
||||
<q-card class="q-pa-lg">
|
||||
<q-card-section class="q-pa-none">
|
||||
<div class="text-center">
|
||||
<a href="lightning:{{ link.lnurl }}">
|
||||
<q-responsive :ratio="1" class="q-mx-md">
|
||||
<qrcode
|
||||
value="{{ link.lnurl }}"
|
||||
:options="{width: 800}"
|
||||
class="rounded-borders"
|
||||
></qrcode>
|
||||
</q-responsive>
|
||||
</a>
|
||||
</div>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn outline color="grey" @click="copyText('{{ link.lnurl }}')"
|
||||
>Copy LNURL</q-btn
|
||||
>
|
||||
</div>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6 col-md-5 col-lg-4 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h6 class="text-subtitle1 q-mb-sm q-mt-none">
|
||||
LNbits LNURL-pay link
|
||||
</h6>
|
||||
<p class="q-my-none">
|
||||
Use a LNURL compatible bitcoin wallet to claim the sats.
|
||||
</p>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-pa-none">
|
||||
<q-separator></q-separator>
|
||||
<q-list>
|
||||
{% include "lnurlp/_lnurl.html" %}
|
||||
</q-list>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %} {% block scripts %}
|
||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin]
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
710
lnbits/extensions/watchonly/templates/watchonly/index.html
Normal file
710
lnbits/extensions/watchonly/templates/watchonly/index.html
Normal file
|
@ -0,0 +1,710 @@
|
|||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||
%} {% block page %}
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-7 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
{% raw %}
|
||||
<q-btn unelevated color="deep-purple" @click="formDialog.show = true"
|
||||
>New wallet</q-btn
|
||||
>
|
||||
<q-btn unelevated color="deep-purple"
|
||||
icon="edit">
|
||||
<div class="cursor-pointer">
|
||||
<q-tooltip>
|
||||
Point to another Mempool
|
||||
</q-tooltip>
|
||||
{{ this.mempool.endpoint }}
|
||||
<q-popup-edit v-model="mempool.endpoint">
|
||||
<q-input color="accent" v-model="mempool.endpoint">
|
||||
</q-input>
|
||||
<center><q-btn
|
||||
flat
|
||||
dense
|
||||
@click="updateMempool()"
|
||||
v-close-popup
|
||||
>set</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
v-close-popup
|
||||
>cancel</q-btn>
|
||||
</center>
|
||||
|
||||
</q-popup-edit>
|
||||
</div>
|
||||
|
||||
</q-btn
|
||||
>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col">
|
||||
<h5 class="text-subtitle1 q-my-none">Wallets</h5>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search"></q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<q-table
|
||||
flat
|
||||
dense
|
||||
:data="walletLinks"
|
||||
row-key="id"
|
||||
:columns="WalletsTable.columns"
|
||||
:pagination.sync="WalletsTable.pagination"
|
||||
:filter="filter"
|
||||
>
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th auto-width></q-th>
|
||||
|
||||
|
||||
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||
<div v-if="col.name == 'id'"></div>
|
||||
<div v-else>
|
||||
{{ col.label }}
|
||||
</div>
|
||||
</q-th>
|
||||
<q-th auto-width></q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
|
||||
<q-td auto-width>
|
||||
<q-btn
|
||||
unelevated
|
||||
dense
|
||||
size="xs"
|
||||
icon="toll"
|
||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||
@click="formDialogPayLink.show = true"
|
||||
>
|
||||
|
||||
<q-tooltip>
|
||||
Payment link
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
unelevated
|
||||
dense
|
||||
size="xs"
|
||||
icon="dns"
|
||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
||||
@click="openQrCodeDialog(props.row.id)"
|
||||
>
|
||||
|
||||
<q-tooltip>
|
||||
Adresses
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
@click="openUpdateDialog(props.row.id)"
|
||||
icon="edit"
|
||||
color="light-blue"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
@click="deleteWalletLink(props.row.id)"
|
||||
icon="cancel"
|
||||
color="pink"
|
||||
></q-btn>
|
||||
|
||||
|
||||
</q-td>
|
||||
</q-td>
|
||||
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||
<div v-if="col.name == 'id'"></div>
|
||||
<div v-else>
|
||||
{{ col.value }}
|
||||
</div>
|
||||
</q-td>
|
||||
|
||||
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
|
||||
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col">
|
||||
<h5 class="text-subtitle1 q-my-none">Paylinks</h5>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search"></q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
{% endraw %}
|
||||
</div>
|
||||
</div>
|
||||
<q-table
|
||||
flat
|
||||
dense
|
||||
:data="payLinks"
|
||||
row-key="id"
|
||||
:columns="PaylinksTable.columns"
|
||||
:pagination.sync="PaylinksTable.pagination"
|
||||
:filter="filter"
|
||||
>
|
||||
|
||||
{% raw %}
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th auto-width></q-th>
|
||||
|
||||
|
||||
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||
<div v-if="col.name == 'id'"></div>
|
||||
<div v-else>
|
||||
{{ col.label }}
|
||||
</div>
|
||||
</q-th>
|
||||
<q-th auto-width></q-th>
|
||||
</q-tr>
|
||||
</template>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
|
||||
<q-td auto-width>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
@click="openUpdateDialog(props.row.id)"
|
||||
icon="edit"
|
||||
color="light-blue"
|
||||
></q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="xs"
|
||||
@click="deleteWalletLink(props.row.id)"
|
||||
icon="cancel"
|
||||
color="pink"
|
||||
></q-btn>
|
||||
|
||||
|
||||
</q-td>
|
||||
</q-td>
|
||||
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||
<div v-if="col.name == 'id'"></div>
|
||||
<div v-else>
|
||||
{{ col.value }}
|
||||
</div>
|
||||
</q-td>
|
||||
|
||||
|
||||
</q-tr>
|
||||
</template>
|
||||
{% endraw %}
|
||||
|
||||
|
||||
|
||||
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-12 col-md-5 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h6 class="text-subtitle1 q-my-none">
|
||||
LNbits WatchOnly Extension
|
||||
</h6>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-pa-none">
|
||||
<q-separator></q-separator>
|
||||
<q-list>
|
||||
{% include "watchonly/_api_docs.html" %}
|
||||
</q-list>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
|
||||
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
|
||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||
<q-form @submit="sendFormData" class="q-gutter-md">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="formDialog.data.title"
|
||||
type="text"
|
||||
label="Title"
|
||||
></q-input>
|
||||
<q-input
|
||||
filled
|
||||
type="textarea"
|
||||
v-model="formDialog.data.masterpub"
|
||||
height="50px"
|
||||
autogrow
|
||||
label="Master Public Key, either xpub, ypub, zpub"
|
||||
></q-input>
|
||||
|
||||
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn
|
||||
v-if="formDialog.data.id"
|
||||
unelevated
|
||||
color="deep-purple"
|
||||
type="submit"
|
||||
>Update Watch-only Wallet</q-btn
|
||||
>
|
||||
<q-btn
|
||||
v-else
|
||||
unelevated
|
||||
color="deep-purple"
|
||||
:disable="
|
||||
formDialog.data.masterpub == null ||
|
||||
formDialog.data.title == null"
|
||||
type="submit"
|
||||
>Create Watch-only Wallet</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>Cancel</q-btn
|
||||
>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
|
||||
<q-dialog v-model="formDialogPayLink.show" position="top" @hide="closeFormDialog">
|
||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||
<q-form @submit="sendFormData" class="q-gutter-md">
|
||||
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="formDialogPayLink.data.title"
|
||||
type="text"
|
||||
label="Title"
|
||||
></q-input>
|
||||
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="formDialogPayLink.data.amount"
|
||||
type="number"
|
||||
label="Amount (sats)"
|
||||
></q-input>
|
||||
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="formDialogPayLink.data.time"
|
||||
type="number"
|
||||
label="Time (mins)"
|
||||
> </q-input>
|
||||
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn
|
||||
v-if="formDialogPayLink.data.id"
|
||||
unelevated
|
||||
color="deep-purple"
|
||||
type="submit"
|
||||
>Update Paylink</q-btn
|
||||
>
|
||||
<q-btn
|
||||
v-else
|
||||
unelevated
|
||||
color="deep-purple"
|
||||
:disable="
|
||||
formDialogPayLink.data.time == null ||
|
||||
formDialogPayLink.data.amount == null"
|
||||
type="submit"
|
||||
>Create Paylink</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
|
||||
>Cancel</q-btn
|
||||
>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
|
||||
<q-dialog v-model="Addresses.show" position="top">
|
||||
<q-card v-if="Addresses.data" class="q-pa-lg lnbits__dialog-card">
|
||||
{% raw %}
|
||||
<h5 class="text-subtitle1 q-my-none">Addresses</h5>
|
||||
<q-separator></q-separator><br/>
|
||||
<p><strong>Current:</strong>
|
||||
{{ Addresses.data[0].address }}
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="ms"
|
||||
icon="visibility"
|
||||
type="a"
|
||||
:href="mempool.endpoint + '/address/' + Addresses.data[0].address"
|
||||
target="_blank"
|
||||
></q-btn>
|
||||
|
||||
</p>
|
||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
||||
<qrcode
|
||||
:value="Addresses.data[0].address"
|
||||
:options="{width: 800}"
|
||||
class="rounded-borders"
|
||||
></qrcode>
|
||||
</q-responsive>
|
||||
<p style="word-break: break-all;">
|
||||
<br /><br />
|
||||
Table of addresses and amount will go here...
|
||||
|
||||
</p>
|
||||
{% endraw %}
|
||||
<div class="row q-mt-lg q-gutter-sm">
|
||||
<q-btn
|
||||
outline
|
||||
color="grey"
|
||||
@click="copyText(Addresses.show, 'LNURL copied to clipboard!')"
|
||||
class="q-ml-sm"
|
||||
>Get fresh address</q-btn
|
||||
>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
||||
</div>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
|
||||
</div>
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<script>
|
||||
Vue.component(VueQrcode.name, VueQrcode)
|
||||
|
||||
var locationPath = [
|
||||
window.location.protocol,
|
||||
'//',
|
||||
window.location.hostname,
|
||||
window.location.pathname
|
||||
].join('')
|
||||
|
||||
var mapWalletLink = function (obj) {
|
||||
obj._data = _.clone(obj)
|
||||
obj.date = Quasar.utils.date.formatDate(
|
||||
new Date(obj.time * 1000),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
)
|
||||
return obj
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
data: function () {
|
||||
return {
|
||||
filter: '',
|
||||
checker: null,
|
||||
walletLinks: [],
|
||||
Addresses: {
|
||||
show: false,
|
||||
data: null
|
||||
},
|
||||
mempool:{
|
||||
endpoint:""
|
||||
},
|
||||
WalletsTable: {
|
||||
columns: [
|
||||
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||
{
|
||||
name: 'title',
|
||||
align: 'left',
|
||||
label: 'Title',
|
||||
field: 'title'
|
||||
},
|
||||
{
|
||||
name: 'amount',
|
||||
align: 'left',
|
||||
label: 'Amount',
|
||||
field: 'amount'
|
||||
},
|
||||
{
|
||||
name: 'masterpub',
|
||||
align: 'left',
|
||||
label: 'MasterPub',
|
||||
field: 'masterpub'
|
||||
},
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
}
|
||||
},
|
||||
PaylinksTable: {
|
||||
columns: [
|
||||
{name: 'id', align: 'left', label: 'ID', field: 'id'},
|
||||
{
|
||||
name: 'title',
|
||||
align: 'left',
|
||||
label: 'Title',
|
||||
field: 'title'
|
||||
},
|
||||
{
|
||||
name: 'amount',
|
||||
align: 'left',
|
||||
label: 'Amount',
|
||||
field: 'amount'
|
||||
},
|
||||
{
|
||||
name: 'time',
|
||||
align: 'left',
|
||||
label: 'time',
|
||||
field: 'time'
|
||||
},
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
}
|
||||
},
|
||||
AddressTable: {
|
||||
columns: [
|
||||
{
|
||||
name: 'address',
|
||||
align: 'left',
|
||||
label: 'Address',
|
||||
field: 'address'
|
||||
},
|
||||
{
|
||||
name: 'amount',
|
||||
align: 'left',
|
||||
label: 'Amount',
|
||||
field: 'amount'
|
||||
},
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
}
|
||||
},
|
||||
formDialog: {
|
||||
show: false,
|
||||
data: {}
|
||||
},
|
||||
formDialogPayLink: {
|
||||
show: false,
|
||||
data: {}
|
||||
},
|
||||
qrCodeDialog: {
|
||||
show: false,
|
||||
data: null
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
getAddresses: function (walletID) {
|
||||
var self = this
|
||||
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/watchonly/api/v1/addresses/' + walletID,
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
.then(function (response) {
|
||||
|
||||
self.walletLinks = response.data.map(function (obj) {
|
||||
self.Addresses.data = response.data
|
||||
})
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
addressRedirect: function (address){
|
||||
window.location.href = this.mempool.endpoint + "/address/" + address;
|
||||
},
|
||||
getMempool: function () {
|
||||
var self = this
|
||||
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/watchonly/api/v1/mempool',
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
.then(function (response) {
|
||||
console.log(response.data.endpoint)
|
||||
self.mempool.endpoint = response.data.endpoint
|
||||
console.log(this.mempool.endpoint)
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
|
||||
updateMempool: function () {
|
||||
var self = this
|
||||
var wallet = this.g.user.wallets[0]
|
||||
LNbits.api
|
||||
.request(
|
||||
'PUT',
|
||||
'/watchonly/api/v1/mempool',
|
||||
wallet.inkey, self.mempool)
|
||||
.then(function (response) {
|
||||
self.mempool.endpoint = response.data.endpoint
|
||||
self.walletLinks.push(mapwalletLink(response.data))
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
getWalletLinks: function () {
|
||||
var self = this
|
||||
|
||||
LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/watchonly/api/v1/wallet',
|
||||
this.g.user.wallets[0].inkey
|
||||
)
|
||||
.then(function (response) {
|
||||
self.walletLinks = response.data.map(function (obj) {
|
||||
return mapWalletLink(obj)
|
||||
})
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
closeFormDialog: function () {
|
||||
this.formDialog.data = {
|
||||
is_unique: false
|
||||
}
|
||||
},
|
||||
openQrCodeDialog: function (linkId) {
|
||||
var getAddresses = this.getAddresses
|
||||
getAddresses(linkId)
|
||||
this.Addresses.show = true
|
||||
},
|
||||
openUpdateDialog: function (linkId) {
|
||||
var link = _.findWhere(this.walletLinks, {id: linkId})
|
||||
this.formDialog.data = _.clone(link._data)
|
||||
this.formDialog.show = true
|
||||
},
|
||||
sendFormData: function () {
|
||||
var wallet = this.g.user.wallets[0]
|
||||
var data = _.omit(this.formDialog.data, 'wallet')
|
||||
|
||||
data.wait_time =
|
||||
data.wait_time *
|
||||
{
|
||||
seconds: 1,
|
||||
minutes: 60,
|
||||
hours: 3600
|
||||
}[this.formDialog.secondMultiplier]
|
||||
|
||||
if (data.id) {
|
||||
this.updateWalletLink(wallet, data)
|
||||
} else {
|
||||
this.createWalletLink(wallet, data)
|
||||
}
|
||||
},
|
||||
updateWalletLink: function (wallet, data) {
|
||||
var self = this
|
||||
|
||||
LNbits.api
|
||||
.request(
|
||||
'PUT',
|
||||
'/watchonly/api/v1/wallet/' + data.id,
|
||||
wallet.inkey, data)
|
||||
.then(function (response) {
|
||||
self.walletLinks = _.reject(self.walletLinks, function (obj) {
|
||||
return obj.id === data.id
|
||||
})
|
||||
self.walletLinks.push(mapWalletLink(response.data))
|
||||
self.formDialog.show = false
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
createWalletLink: function (wallet, data) {
|
||||
var self = this
|
||||
|
||||
LNbits.api
|
||||
.request('POST', '/watchonly/api/v1/wallet', wallet.inkey, data)
|
||||
.then(function (response) {
|
||||
self.walletLinks.push(mapWalletLink(response.data))
|
||||
self.formDialog.show = false
|
||||
console.log(response.data[1][1])
|
||||
})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
deleteWalletLink: function (linkId) {
|
||||
var self = this
|
||||
var link = _.findWhere(this.walletLinks, {id: linkId})
|
||||
console.log(self.g.user.wallets[0].adminkey)
|
||||
LNbits.utils
|
||||
.confirmDialog('Are you sure you want to delete this pay link?')
|
||||
.onOk(function () {
|
||||
LNbits.api
|
||||
.request(
|
||||
'DELETE',
|
||||
'/watchonly/api/v1/wallet/' + linkId,
|
||||
self.g.user.wallets[0].inkey
|
||||
)
|
||||
.then(function (response) {
|
||||
self.walletLinks = _.reject(self.walletLinks, function (obj) {
|
||||
return obj.id === linkId
|
||||
})})
|
||||
.catch(function (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
})
|
||||
},
|
||||
exportCSV: function () {
|
||||
LNbits.utils.exportCSV(this.paywallsTable.columns, this.paywalls)
|
||||
},
|
||||
|
||||
},
|
||||
created: function () {
|
||||
if (this.g.user.wallets.length) {
|
||||
var getWalletLinks = this.getWalletLinks
|
||||
getWalletLinks()
|
||||
var getMempool = this.getMempool
|
||||
getMempool()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
21
lnbits/extensions/watchonly/views.py
Normal file
21
lnbits/extensions/watchonly/views.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from quart import g, abort, render_template
|
||||
from http import HTTPStatus
|
||||
|
||||
from lnbits.decorators import check_user_exists, validate_uuids
|
||||
|
||||
from lnbits.extensions.watchonly import watchonly_ext
|
||||
from .crud import get_payment
|
||||
|
||||
|
||||
@watchonly_ext.route("/")
|
||||
@validate_uuids(["usr"], required=True)
|
||||
@check_user_exists()
|
||||
async def index():
|
||||
return await render_template("watchonly/index.html", user=g.user)
|
||||
|
||||
|
||||
@watchonly_ext.route("/<payment_id>")
|
||||
async def display(payment_id):
|
||||
link = get_payment(payment_id) or abort(HTTPStatus.NOT_FOUND, "Pay link does not exist.")
|
||||
|
||||
return await render_template("watchonly/display.html", link=link)
|
194
lnbits/extensions/watchonly/views_api.py
Normal file
194
lnbits/extensions/watchonly/views_api.py
Normal file
|
@ -0,0 +1,194 @@
|
|||
import hashlib
|
||||
from quart import g, jsonify, request, url_for
|
||||
from http import HTTPStatus
|
||||
|
||||
from lnbits.core.crud import get_user
|
||||
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
||||
|
||||
from lnbits.extensions.watchonly import watchonly_ext
|
||||
from .crud import (
|
||||
create_watch_wallet,
|
||||
get_watch_wallet,
|
||||
get_watch_wallets,
|
||||
update_watch_wallet,
|
||||
delete_watch_wallet,
|
||||
create_payment,
|
||||
get_payment,
|
||||
get_payments,
|
||||
delete_payment,
|
||||
create_mempool,
|
||||
update_mempool,
|
||||
get_mempool,
|
||||
get_addresses,
|
||||
get_fresh_address,
|
||||
get_address
|
||||
)
|
||||
|
||||
###################WALLETS#############################
|
||||
|
||||
@watchonly_ext.route("/api/v1/wallet", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_wallets_retrieve():
|
||||
|
||||
try:
|
||||
return (
|
||||
jsonify([wallet._asdict() for wallet in get_watch_wallets(g.wallet.user)]), HTTPStatus.OK
|
||||
)
|
||||
except:
|
||||
return (
|
||||
jsonify({"message": "Cant fetch."}),
|
||||
HTTPStatus.UPGRADE_REQUIRED,
|
||||
)
|
||||
|
||||
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_wallet_retrieve(wallet_id):
|
||||
wallet = get_watch_wallet(wallet_id)
|
||||
|
||||
if not wallet:
|
||||
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
|
||||
|
||||
return jsonify({wallet}), HTTPStatus.OK
|
||||
|
||||
|
||||
@watchonly_ext.route("/api/v1/wallet", methods=["POST"])
|
||||
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["PUT"])
|
||||
@api_check_wallet_key("invoice")
|
||||
@api_validate_post_request(
|
||||
schema={
|
||||
"masterpub": {"type": "string", "empty": False, "required": True},
|
||||
"title": {"type": "string", "empty": False, "required": True},
|
||||
}
|
||||
)
|
||||
async def api_wallet_create_or_update(wallet_id=None):
|
||||
print("g.data")
|
||||
if not wallet_id:
|
||||
wallet = create_watch_wallet(user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"])
|
||||
mempool = get_mempool(g.wallet.user)
|
||||
if not mempool:
|
||||
create_mempool(user=g.wallet.user)
|
||||
return jsonify(wallet._asdict()), HTTPStatus.CREATED
|
||||
|
||||
else:
|
||||
wallet = update_watch_wallet(wallet_id=wallet_id, **g.data)
|
||||
return jsonify(wallet._asdict()), HTTPStatus.OK
|
||||
|
||||
|
||||
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["DELETE"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_wallet_delete(wallet_id):
|
||||
wallet = get_watch_wallet(wallet_id)
|
||||
|
||||
if not wallet:
|
||||
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
|
||||
|
||||
delete_watch_wallet(wallet_id)
|
||||
|
||||
return jsonify({"deleted": "true"}), HTTPStatus.NO_CONTENT
|
||||
|
||||
|
||||
#############################ADDRESSES##########################
|
||||
|
||||
@watchonly_ext.route("/api/v1/address/<wallet_id>", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_fresh_address(wallet_id):
|
||||
address = get_fresh_address(wallet_id)
|
||||
|
||||
if not address:
|
||||
return jsonify({"message": "something went wrong"}), HTTPStatus.NOT_FOUND
|
||||
|
||||
return jsonify({address}), HTTPStatus.OK
|
||||
|
||||
|
||||
@watchonly_ext.route("/api/v1/addresses/<wallet_id>", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_get_addresses(wallet_id):
|
||||
addresses = get_addresses(wallet_id)
|
||||
print(addresses)
|
||||
if not addresses:
|
||||
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
|
||||
|
||||
return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK
|
||||
|
||||
#############################PAYEMENTS##########################
|
||||
|
||||
@watchonly_ext.route("/api/v1/payment", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_payments_retrieve():
|
||||
|
||||
try:
|
||||
return (
|
||||
jsonify(get_payments(g.wallet.user)),
|
||||
HTTPStatus.OK,
|
||||
)
|
||||
except:
|
||||
return (
|
||||
jsonify({"message": "Cant fetch."}),
|
||||
HTTPStatus.UPGRADE_REQUIRED,
|
||||
)
|
||||
|
||||
@watchonly_ext.route("/api/v1/payment/<payment_id>", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_payment_retrieve(payment_id):
|
||||
payment = get_payment(payment_id)
|
||||
|
||||
if not payment:
|
||||
return jsonify({"message": "payment does not exist"}), HTTPStatus.NOT_FOUND
|
||||
|
||||
return jsonify({payment}), HTTPStatus.OK
|
||||
|
||||
|
||||
@watchonly_ext.route("/api/v1/payment", methods=["POST"])
|
||||
@watchonly_ext.route("/api/v1/payment/<payment_id>", methods=["PUT"])
|
||||
@api_check_wallet_key("invoice")
|
||||
@api_validate_post_request(
|
||||
schema={
|
||||
"ex_key": {"type": "string", "empty": False, "required": True},
|
||||
"pub_key": {"type": "string", "empty": False, "required": True},
|
||||
"time_to_pay": {"type": "integer", "min": 1, "required": True},
|
||||
"amount": {"type": "integer", "min": 1, "required": True},
|
||||
}
|
||||
)
|
||||
async def api_payment_create_or_update(payment_id=None):
|
||||
|
||||
if not payment_id:
|
||||
payment = create_payment(g.wallet.user, g.data.ex_key, g.data.pub_key, g.data.amount)
|
||||
return jsonify(get_payment(payment)), HTTPStatus.CREATED
|
||||
|
||||
else:
|
||||
payment = update_payment(payment_id, g.data)
|
||||
return jsonify({payment}), HTTPStatus.OK
|
||||
|
||||
|
||||
@watchonly_ext.route("/api/v1/payment/<payment_id>", methods=["DELETE"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_payment_delete(payment_id):
|
||||
payment = get_watch_wallet(payment_id)
|
||||
|
||||
if not payment:
|
||||
return jsonify({"message": "Wallet link does not exist."}), HTTPStatus.NOT_FOUND
|
||||
|
||||
delete_watch_wallet(payment_id)
|
||||
|
||||
return "", HTTPStatus.NO_CONTENT
|
||||
|
||||
#############################MEMPOOL##########################
|
||||
|
||||
@watchonly_ext.route("/api/v1/mempool", methods=["PUT"])
|
||||
@api_check_wallet_key("invoice")
|
||||
@api_validate_post_request(
|
||||
schema={
|
||||
"endpoint": {"type": "string", "empty": False, "required": True},
|
||||
}
|
||||
)
|
||||
async def api_update_mempool():
|
||||
mempool = update_mempool(user=g.wallet.user, **g.data)
|
||||
return jsonify(mempool._asdict()), HTTPStatus.OK
|
||||
|
||||
@watchonly_ext.route("/api/v1/mempool", methods=["GET"])
|
||||
@api_check_wallet_key("invoice")
|
||||
async def api_get_mempool():
|
||||
mempool = get_mempool(g.wallet.user)
|
||||
if not mempool:
|
||||
mempool = create_mempool(user=g.wallet.user)
|
||||
return jsonify(mempool._asdict()), HTTPStatus.OK
|
|
@ -46,3 +46,4 @@ trio==0.16.0
|
|||
typing-extensions==3.7.4.3
|
||||
werkzeug==1.0.1
|
||||
wsproto==1.0.0
|
||||
embit==0.1.2
|
Loading…
Reference in New Issue
Block a user