prettier and black

This commit is contained in:
Ben Arc
2021-04-04 13:28:46 +01:00
parent 69ef4d7e63
commit b2841ee909
8 changed files with 394 additions and 416 deletions

View File

@@ -4,7 +4,8 @@ from lnbits.db import Database
db = Database("ext_watchonly") db = Database("ext_watchonly")
watchonly_ext: Blueprint = Blueprint("watchonly", __name__, static_folder="static", template_folder="templates") watchonly_ext: Blueprint = Blueprint(
"watchonly", __name__, static_folder="static", template_folder="templates")
from .views_api import * # noqa from .views_api import * # noqa

View File

@@ -24,7 +24,6 @@ from binascii import unhexlify, hexlify, a2b_base64, b2a_base64
import httpx import httpx
##########################WALLETS#################### ##########################WALLETS####################
async def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Wallets: async def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Wallets:
@@ -43,7 +42,7 @@ async def create_watch_wallet(*, user: str, masterpub: str, title: str) -> Walle
""", """,
(wallet_id, user, masterpub, title, 0, 0), (wallet_id, user, masterpub, title, 0, 0),
) )
# weallet_id = db.cursor.lastrowid # weallet_id = db.cursor.lastrowid
return await get_watch_wallet(wallet_id) return await get_watch_wallet(wallet_id)
@@ -57,6 +56,7 @@ async def get_watch_wallets(user: str) -> List[Wallets]:
rows = await db.fetchall("SELECT * FROM wallets WHERE user = ?", (user,)) rows = await db.fetchall("SELECT * FROM wallets WHERE user = ?", (user,))
return [Wallets(**row) for row in rows] return [Wallets(**row) for row in rows]
async def update_watch_wallet(wallet_id: str, **kwargs) -> Optional[Wallets]: async def update_watch_wallet(wallet_id: str, **kwargs) -> Optional[Wallets]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])
@@ -70,13 +70,14 @@ async def delete_watch_wallet(wallet_id: str) -> None:
########################ADDRESSES####################### ########################ADDRESSES#######################
async def get_derive_address(wallet_id: str, num: int): async def get_derive_address(wallet_id: str, num: int):
wallet = await get_watch_wallet(wallet_id) wallet = await get_watch_wallet(wallet_id)
key = wallet[2] key = wallet[2]
k = bip32.HDKey.from_base58(key) k = bip32.HDKey.from_base58(key)
child = k.derive([0, num]) child = k.derive([0, num])
if key[0:4] == "xpub": if key[0:4] == "xpub":
address = script.p2pkh(child).address() address = script.p2pkh(child).address()
elif key[0:4] == "zpub": elif key[0:4] == "zpub":
@@ -86,12 +87,13 @@ async def get_derive_address(wallet_id: str, num: int):
return address return address
async def get_fresh_address(wallet_id: str) -> Addresses: async def get_fresh_address(wallet_id: str) -> Addresses:
wallet = await get_watch_wallet(wallet_id) wallet = await get_watch_wallet(wallet_id)
address = await get_derive_address(wallet_id, wallet[4] + 1) address = await get_derive_address(wallet_id, wallet[4] + 1)
await update_watch_wallet(wallet_id = wallet_id, address_no = wallet[4] + 1) await update_watch_wallet(wallet_id=wallet_id, address_no=wallet[4] + 1)
masterpub_id = urlsafe_short_hash() masterpub_id = urlsafe_short_hash()
await db.execute( await db.execute(
""" """
@@ -113,12 +115,14 @@ async def get_address(address: str) -> Addresses:
row = await db.fetchone("SELECT * FROM addresses WHERE address = ?", (address,)) row = await db.fetchone("SELECT * FROM addresses WHERE address = ?", (address,))
return Addresses.from_row(row) if row else None return Addresses.from_row(row) if row else None
async def get_addresses(wallet_id: str) -> List[Addresses]: async def get_addresses(wallet_id: str) -> List[Addresses]:
rows = await db.fetchall("SELECT * FROM addresses WHERE wallet = ?", (wallet_id,)) rows = await db.fetchall("SELECT * FROM addresses WHERE wallet = ?", (wallet_id,))
return [Addresses(**row) for row in rows] return [Addresses(**row) for row in rows]
######################MEMPOOL####################### ######################MEMPOOL#######################
async def create_mempool(user: str) -> Mempool: async def create_mempool(user: str) -> Mempool:
await db.execute( await db.execute(
""" """
@@ -133,6 +137,7 @@ async def create_mempool(user: str) -> Mempool:
row = await db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,)) row = await db.fetchone("SELECT * FROM mempool WHERE user = ?", (user,))
return Mempool.from_row(row) if row else None return Mempool.from_row(row) if row else None
async def update_mempool(user: str, **kwargs) -> Optional[Mempool]: async def update_mempool(user: str, **kwargs) -> Optional[Mempool]:
q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()]) q = ", ".join([f"{field[0]} = ?" for field in kwargs.items()])

View File

@@ -25,7 +25,7 @@ async def m001_initial(db):
); );
""" """
) )
await db.execute( await db.execute(
""" """
CREATE TABLE IF NOT EXISTS mempool ( CREATE TABLE IF NOT EXISTS mempool (
@@ -33,4 +33,4 @@ async def m001_initial(db):
endpoint TEXT NOT NULL endpoint TEXT NOT NULL
); );
""" """
) )

View File

@@ -1,6 +1,7 @@
from sqlite3 import Row from sqlite3 import Row
from typing import NamedTuple from typing import NamedTuple
class Wallets(NamedTuple): class Wallets(NamedTuple):
id: str id: str
user: str user: str
@@ -17,11 +18,12 @@ class Wallets(NamedTuple):
class Mempool(NamedTuple): class Mempool(NamedTuple):
user: str user: str
endpoint: str endpoint: str
@classmethod @classmethod
def from_row(cls, row: Row) -> "Mempool": def from_row(cls, row: Row) -> "Mempool":
return cls(**dict(row)) return cls(**dict(row))
class Addresses(NamedTuple): class Addresses(NamedTuple):
id: str id: str
address: str address: str
@@ -30,4 +32,4 @@ class Addresses(NamedTuple):
@classmethod @classmethod
def from_row(cls, row: Row) -> "Addresses": def from_row(cls, row: Row) -> "Addresses":
return cls(**dict(row)) return cls(**dict(row))

View File

@@ -1,196 +1,244 @@
<q-card> <q-card>
<q-card-section> <q-card-section>
<p>Watch Only extension uses mempool.space<br /> <p>
For use with "account Extended Public Key" <a href="https://iancoleman.io/bip39/">https://iancoleman.io/bip39/</a> Watch Only extension uses mempool.space<br />
<small> For use with "account Extended Public Key"
<br />Created by, <a target="_blank" href="https://github.com/arcbtc">Ben Arc</a> (using, <a target="_blank" href="https://github.com/diybitcoinhardware/embit">Embit</a></small <a href="https://iancoleman.io/bip39/">https://iancoleman.io/bip39/</a>
>) <small>
</p> <br />Created by,
</q-card-section> <a target="_blank" href="https://github.com/arcbtc">Ben Arc</a> (using,
<a target="_blank" href="https://github.com/diybitcoinhardware/embit"
>Embit</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 wallets">
<q-card>
<q-card-section>
<code><span class="text-blue">GET</span> /watchonly/api/v1/wallet</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</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>[&lt;wallets_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/wallet -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 wallet details">
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/watchonly/api/v1/wallet/&lt;wallet_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</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>[&lt;wallet_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/wallet/&lt;wallet_id&gt; -H
"X-Api-Key: {{ g.user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item <q-expansion-item
group="api" group="extras"
dense icon="swap_vertical_circle"
expand-separator label="API info"
label="Create wallet" :content-inset-level="0.5"
> >
<q-card> <q-expansion-item group="api" dense expand-separator label="List wallets">
<q-card-section> <q-card>
<code><span class="text-green">POST</span> /watchonly/api/v1/wallet</code> <q-card-section>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5> <code
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br /> ><span class="text-blue">GET</span> /watchonly/api/v1/wallet</code
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5> >
<h5 class="text-caption q-mt-sm q-mb-none"> <h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
Returns 201 CREATED (application/json) <code>{"X-Api-Key": &lt;invoice_key&gt;}</code><br />
</h5> <h5 class="text-caption q-mt-sm q-mb-none">
<code>[&lt;wallet_object&gt;, ...]</code> Body (application/json)
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> </h5>
<code <h5 class="text-caption q-mt-sm q-mb-none">
>curl -X POST {{ request.url_root }}api/v1/wallet -d Returns 200 OK (application/json)
'{"title": &lt;string&gt;, "masterpub": &lt;string&gt;}' -H </h5>
"Content-type: application/json" -H "X-Api-Key: {{ <code>[&lt;wallets_object&gt;, ...]</code>
g.user.wallets[0].adminkey }}" <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
</code> <code
</q-card-section> >curl -X GET {{ request.url_root }}api/v1/wallet -H "X-Api-Key: {{
</q-card> g.user.wallets[0].inkey }}"
</code>
</q-card-section>
</q-card>
</q-expansion-item>
<q-expansion-item
group="api"
dense
expand-separator
label="Get wallet details"
>
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/watchonly/api/v1/wallet/&lt;wallet_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</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>[&lt;wallet_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/wallet/&lt;wallet_id&gt;
-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 wallet">
<q-card>
<q-card-section>
<code
><span class="text-green">POST</span> /watchonly/api/v1/wallet</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</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>[&lt;wallet_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X POST {{ request.url_root }}api/v1/wallet -d '{"title":
&lt;string&gt;, "masterpub": &lt;string&gt;}' -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 wallet"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code
><span class="text-pink">DELETE</span>
/watchonly/api/v1/wallet/&lt;wallet_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</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
}}api/v1/wallet/&lt;wallet_id&gt; -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="List addresses">
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/watchonly/api/v1/addresses/&lt;wallet_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</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>[&lt;address_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root
}}api/v1/addresses/&lt;wallet_id&gt; -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 fresh address"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/watchonly/api/v1/address/&lt;wallet_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</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>[&lt;address_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/address/&lt;wallet_id&gt;
-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 mempool.space details"
>
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span> /watchonly/api/v1/mempool</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</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>[&lt;mempool_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/mempool -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 mempool.space"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code
><span class="text-green">POST</span>
/watchonly/api/v1/mempool</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</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>[&lt;mempool_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X PUT {{ request.url_root }}api/v1/mempool -d '{"endpoint":
&lt;string&gt;}' -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> </q-expansion-item>
<q-expansion-item </q-card>
group="api"
dense
expand-separator
label="Delete wallet"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code
><span class="text-pink">DELETE</span>
/watchonly/api/v1/wallet/&lt;wallet_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</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 }}api/v1/wallet/&lt;wallet_id&gt;
-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="List addresses">
<q-card>
<q-card-section>
<code><span class="text-blue">GET</span> /watchonly/api/v1/addresses/&lt;wallet_id&gt;</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</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>[&lt;address_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/addresses/&lt;wallet_id&gt; -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 fresh address" class="q-pb-md">
<q-card>
<q-card-section>
<code><span class="text-blue">GET</span> /watchonly/api/v1/address/&lt;wallet_id&gt;</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;invoice_key&gt;}</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>[&lt;address_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/address/&lt;wallet_id&gt; -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 mempool.space details">
<q-card>
<q-card-section>
<code><span class="text-blue">GET</span> /watchonly/api/v1/mempool</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</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>[&lt;mempool_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X GET {{ request.url_root }}api/v1/mempool -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 mempool.space" class="q-pb-md">
<q-card>
<q-card-section>
<code><span class="text-green">POST</span> /watchonly/api/v1/mempool</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</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>[&lt;mempool_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code
>curl -X PUT {{ request.url_root }}api/v1/mempool -d
'{"endpoint": &lt;string&gt;}' -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>

View File

@@ -4,38 +4,26 @@
<div class="col-12 col-md-7 q-gutter-y-md"> <div class="col-12 col-md-7 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
{% raw %} {% raw %}
<q-btn unelevated color="deep-purple" @click="formDialog.show = true" <q-btn unelevated color="deep-purple" @click="formDialog.show = true">New wallet </q-btn>
>New wallet </q-btn <q-btn unelevated color="deep-purple" icon="edit">
> <div class="cursor-pointer">
<q-btn unelevated color="deep-purple" <q-tooltip>
icon="edit"> Point to another Mempool
<div class="cursor-pointer"> </q-tooltip>
<q-tooltip> {{ this.mempool.endpoint }}
Point to another Mempool <q-popup-edit v-model="mempool.endpoint">
</q-tooltip> <q-input color="accent" v-model="mempool.endpoint">
{{ this.mempool.endpoint }} </q-input>
<q-popup-edit v-model="mempool.endpoint"> <center>
<q-input color="accent" v-model="mempool.endpoint"> <q-btn flat dense @click="updateMempool()" v-close-popup>set</q-btn>
</q-input> <q-btn flat dense v-close-popup>cancel</q-btn>
<center><q-btn
flat
dense
@click="updateMempool()"
v-close-popup
>set</q-btn>
<q-btn
flat
dense
v-close-popup
>cancel</q-btn>
</center> </center>
</q-popup-edit> </q-popup-edit>
</div> </div>
</q-btn </q-btn>
>
</q-card-section> </q-card-section>
</q-card> </q-card>
@@ -46,23 +34,16 @@
<h5 class="text-subtitle1 q-my-none">Wallets</h5> <h5 class="text-subtitle1 q-my-none">Wallets</h5>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search"> <q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
<template v-slot:append> <template v-slot:append>
<q-icon name="search"></q-icon> <q-icon name="search"></q-icon>
</template> </template>
</q-input> </q-input>
</div> </div>
</div> </div>
<q-table <q-table flat dense :data="walletLinks" row-key="id" :columns="WalletsTable.columns"
flat :pagination.sync="WalletsTable.pagination" :filter="filter">
dense
:data="walletLinks"
row-key="id"
:columns="WalletsTable.columns"
:pagination.sync="WalletsTable.pagination"
:filter="filter"
>
<template v-slot:header="props"> <template v-slot:header="props">
<q-tr :props="props"> <q-tr :props="props">
<q-th auto-width></q-th> <q-th auto-width></q-th>
@@ -75,32 +56,19 @@
<template v-slot:body="props"> <template v-slot:body="props">
<q-tr :props="props"> <q-tr :props="props">
<q-td auto-width> <q-td auto-width>
<q-btn <q-btn unelevated dense size="xs" icon="dns" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
unelevated @click="openQrCodeDialog(props.row.id)">
dense
size="xs" <q-tooltip>
icon="dns" Adresses
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" </q-tooltip>
@click="openQrCodeDialog(props.row.id)" </q-btn>
> <q-btn flat dense size="xs" @click="deleteWalletLink(props.row.id)" icon="cancel" color="pink"></q-btn>
<q-tooltip>
Adresses
</q-tooltip>
</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> </q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width> <q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width>
{{ col.value }} {{ col.value }}
@@ -126,7 +94,7 @@
<q-card-section class="q-pa-none"> <q-card-section class="q-pa-none">
<q-separator></q-separator> <q-separator></q-separator>
<q-list> <q-list>
{% include "watchonly/_api_docs.html" %} {% include "watchonly/_api_docs.html" %}
</q-list> </q-list>
</q-card-section> </q-card-section>
</q-card> </q-card>
@@ -135,35 +103,16 @@
<q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog"> <q-dialog v-model="formDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card"> <q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
<q-form @submit="sendFormData" class="q-gutter-md"> <q-form @submit="sendFormData" class="q-gutter-md">
<q-input <q-input filled dense v-model.trim="formDialog.data.title" type="text" label="Title"></q-input>
filled
dense <q-input filled type="textarea" v-model="formDialog.data.masterpub" height="50px" autogrow
v-model.trim="formDialog.data.title" label="Account Extended Public Key; xpub, ypub, zpub"></q-input>
type="text"
label="Title"
></q-input>
<q-input
filled
type="textarea"
v-model="formDialog.data.masterpub"
height="50px"
autogrow
label="Account Extended Public Key; xpub, ypub, zpub"
></q-input>
<div class="row q-mt-lg"> <div class="row q-mt-lg">
<q-btn <q-btn unelevated color="deep-purple" :disable="
unelevated
color="deep-purple"
:disable="
formDialog.data.masterpub == null || formDialog.data.masterpub == null ||
formDialog.data.title == null" formDialog.data.title == null" type="submit">Create Watch-only Wallet</q-btn>
type="submit" <q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
>Create Watch-only Wallet</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto"
>Cancel</q-btn
>
</div> </div>
</q-form> </q-form>
</q-card> </q-card>
@@ -172,78 +121,52 @@
<q-dialog v-model="Addresses.show" position="top"> <q-dialog v-model="Addresses.show" position="top">
<q-card v-if="Addresses.data" class="q-pa-lg lnbits__dialog-card"> <q-card v-if="Addresses.data" class="q-pa-lg lnbits__dialog-card">
{% raw %} {% raw %}
<h5 class="text-subtitle1 q-my-none">Addresses</h5> <h5 class="text-subtitle1 q-my-none">Addresses</h5>
<q-separator></q-separator><br/> <q-separator></q-separator><br />
<p><strong>Current:</strong> <p><strong>Current:</strong>
{{ currentaddress }} {{ currentaddress }}
<q-btn <q-btn flat dense size="ms" icon="visibility" type="a" :href="mempool.endpoint + '/address/' + currentaddress"
flat target="_blank"></q-btn>
dense
size="ms" </p>
icon="visibility"
type="a"
:href="mempool.endpoint + '/address/' + currentaddress"
target="_blank"
></q-btn>
</p>
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> <q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode <qrcode :value="currentaddress" :options="{width: 800}" class="rounded-borders"></qrcode>
:value="currentaddress"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive> </q-responsive>
<p style="word-break: break-all;"> <p style="word-break: break-all;">
<q-scroll-area style="height: 200px; max-width: 100%;"> <q-scroll-area style="height: 200px; max-width: 100%;">
<q-list bordered v-for="data in Addresses.data.slice().reverse()"> <q-list bordered v-for="data in Addresses.data.slice().reverse()">
<q-item> <q-item>
<q-item-section>{{ data.address }}</q-item-section> <q-item-section>{{ data.address }}</q-item-section>
<q-btn <q-btn flat dense size="ms" icon="visibility" type="a"
flat :href="mempool.endpoint + '/address/' + data.address" target="_blank"></q-btn>
dense </q-item>
size="ms" </q-list>
icon="visibility" </q-scroll-area>
type="a"
:href="mempool.endpoint + '/address/' + data.address"
target="_blank"
></q-btn>
</q-item>
</q-list>
</q-scroll-area>
</p> </p>
<div class="row q-mt-lg q-gutter-sm"> <div class="row q-mt-lg q-gutter-sm">
<q-btn <q-btn outline color="grey" @click="getFreshAddress(current)" class="q-ml-sm">Get fresh address</q-btn>
outline
color="grey"
@click="getFreshAddress(current)"
class="q-ml-sm"
>Get fresh address</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn> <q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div> </div>
</q-card> </q-card>
</q-dialog> </q-dialog>
{% endraw %} {% endraw %}
</div> </div>
{% endblock %} {% block scripts %} {{ window_vars(user) }} {% endblock %} {% block scripts %} {{ window_vars(user) }}
<script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script> <script src="{{ url_for('static', filename='vendor/vue-qrcode@1.0.2/vue-qrcode.min.js') }}"></script>
<style> <style>
</style> </style>
<script> <script>
Vue.component(VueQrcode.name, VueQrcode) Vue.component(VueQrcode.name, VueQrcode)
Vue.filter('reverse', function(value) { Vue.filter('reverse', function (value) {
// slice to make a copy of array, then reverse the copy // slice to make a copy of array, then reverse the copy
return value.slice().reverse(); return value.slice().reverse();
}); });
var locationPath = [ var locationPath = [
window.location.protocol, window.location.protocol,
'//', '//',
@@ -283,12 +206,12 @@
show: false, show: false,
data: null data: null
}, },
mempool:{ mempool: {
endpoint:"" endpoint: ""
}, },
WalletsTable: { WalletsTable: {
columns: [ columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'}, { name: 'id', align: 'left', label: 'ID', field: 'id' },
{ {
name: 'title', name: 'title',
align: 'left', align: 'left',
@@ -319,46 +242,46 @@
}, },
methods: { methods: {
getAddressDetails: function (address){ getAddressDetails: function (address) {
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/watchonly/api/v1/mempool/' + address, '/watchonly/api/v1/mempool/' + address,
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
.then(function (response) { .then(function (response) {
return reponse.data return reponse.data
}) })
.catch(function (error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
getAddresses: function (walletID) { getAddresses: function (walletID) {
var self = this var self = this
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/watchonly/api/v1/addresses/' + walletID, '/watchonly/api/v1/addresses/' + walletID,
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
.then(function (response) { .then(function (response) {
self.Addresses.data = response.data self.Addresses.data = response.data
self.currentaddress = self.Addresses.data[self.Addresses.data.length - 1].address self.currentaddress = self.Addresses.data[self.Addresses.data.length - 1].address
self.AddressesLinks = response.data.map(function (obj) { self.AddressesLinks = response.data.map(function (obj) {
console.log(obj) console.log(obj)
return mapAddresses(obj)
}) return mapAddresses(obj)
})
.catch(function (error) { })
})
LNbits.utils.notifyApiError(error) .catch(function (error) {
})
LNbits.utils.notifyApiError(error)
})
}, },
getFreshAddress: function (walletID) { getFreshAddress: function (walletID) {
var self = this var self = this
LNbits.api LNbits.api
@@ -368,8 +291,8 @@
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
.then(function (response) { .then(function (response) {
self.Addresses.data = response.data self.Addresses.data = response.data
self.currentaddress = self.Addresses.data[self.Addresses.data.length - 1].address self.currentaddress = self.Addresses.data[self.Addresses.data.length - 1].address
}) })
}, },
getMempool: function () { getMempool: function () {
@@ -415,11 +338,11 @@
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
.then(function (response) { .then(function (response) {
console.log(response) console.log(response)
self.walletLinks = response.data.map(function (obj) { self.walletLinks = response.data.map(function (obj) {
self.getAddresses(obj.id) self.getAddresses(obj.id)
return mapWalletLink(obj) return mapWalletLink(obj)
}) })
}) })
.catch(function (error) { .catch(function (error) {
@@ -427,7 +350,7 @@
}) })
}, },
closeFormDialog: function () { closeFormDialog: function () {
this.formDialog.data = { this.formDialog.data = {
is_unique: false is_unique: false
@@ -461,7 +384,7 @@
}, },
deleteWalletLink: function (linkId) { deleteWalletLink: function (linkId) {
var self = this var self = this
var link = _.findWhere(this.walletLinks, {id: linkId}) var link = _.findWhere(this.walletLinks, { id: linkId })
LNbits.utils LNbits.utils
.confirmDialog('Are you sure you want to delete this pay link?') .confirmDialog('Are you sure you want to delete this pay link?')
.onOk(function () { .onOk(function () {
@@ -471,10 +394,11 @@
'/watchonly/api/v1/wallet/' + linkId, '/watchonly/api/v1/wallet/' + linkId,
self.g.user.wallets[0].adminkey self.g.user.wallets[0].adminkey
) )
.then(function (response) { .then(function (response) {
self.walletLinks = _.reject(self.walletLinks, function (obj) { self.walletLinks = _.reject(self.walletLinks, function (obj) {
return obj.id === linkId return obj.id === linkId
})}) })
})
.catch(function (error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
@@ -491,10 +415,8 @@
getMempool() getMempool()
var getWalletLinks = this.getWalletLinks var getWalletLinks = this.getWalletLinks
getWalletLinks() getWalletLinks()
} }
} }
}) })
</script> </script>
{% endblock %} {% endblock %}

View File

@@ -15,6 +15,7 @@ async def index():
@watchonly_ext.route("/<charge_id>") @watchonly_ext.route("/<charge_id>")
async def display(charge_id): async def display(charge_id):
link = get_payment(charge_id) or abort(HTTPStatus.NOT_FOUND, "Charge link does not exist.") link = get_payment(charge_id) or abort(
HTTPStatus.NOT_FOUND, "Charge link does not exist.")
return await render_template("watchonly/display.html", link=link) return await render_template("watchonly/display.html", link=link)

View File

@@ -1,7 +1,8 @@
import hashlib import hashlib
from quart import g, jsonify, url_for, request from quart import g, jsonify, url_for, request
from http import HTTPStatus from http import HTTPStatus
import httpx, json import httpx
import json
from lnbits.core.crud import get_user from lnbits.core.crud import get_user
from lnbits.decorators import api_check_wallet_key, api_validate_post_request from lnbits.decorators import api_check_wallet_key, api_validate_post_request
@@ -22,6 +23,7 @@ from .crud import (
###################WALLETS############################# ###################WALLETS#############################
@watchonly_ext.route("/api/v1/wallet", methods=["GET"]) @watchonly_ext.route("/api/v1/wallet", methods=["GET"])
@api_check_wallet_key("invoice") @api_check_wallet_key("invoice")
async def api_wallets_retrieve(): async def api_wallets_retrieve():
@@ -33,11 +35,12 @@ async def api_wallets_retrieve():
except: except:
return "" return ""
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["GET"]) @watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["GET"])
@api_check_wallet_key("invoice") @api_check_wallet_key("invoice")
async def api_wallet_retrieve(wallet_id): async def api_wallet_retrieve(wallet_id):
wallet = await get_watch_wallet(wallet_id) wallet = await get_watch_wallet(wallet_id)
if not wallet: if not wallet:
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
@@ -54,7 +57,7 @@ async def api_wallet_retrieve(wallet_id):
) )
async def api_wallet_create_or_update(wallet_id=None): async def api_wallet_create_or_update(wallet_id=None):
wallet = await create_watch_wallet(user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"]) wallet = await create_watch_wallet(user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"])
mempool = await get_mempool(g.wallet.user) mempool = await get_mempool(g.wallet.user)
if not mempool: if not mempool:
create_mempool(user=g.wallet.user) create_mempool(user=g.wallet.user)
return jsonify(wallet._asdict()), HTTPStatus.CREATED return jsonify(wallet._asdict()), HTTPStatus.CREATED
@@ -78,9 +81,9 @@ async def api_wallet_delete(wallet_id):
@watchonly_ext.route("/api/v1/address/<wallet_id>", methods=["GET"]) @watchonly_ext.route("/api/v1/address/<wallet_id>", methods=["GET"])
@api_check_wallet_key("invoice") @api_check_wallet_key("invoice")
async def api_fresh_address(wallet_id): async def api_fresh_address(wallet_id):
await get_fresh_address(wallet_id) await get_fresh_address(wallet_id)
addresses = await get_addresses(wallet_id) addresses = await get_addresses(wallet_id)
return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK
@@ -90,16 +93,16 @@ async def api_fresh_address(wallet_id):
async def api_get_addresses(wallet_id): async def api_get_addresses(wallet_id):
print(wallet_id) print(wallet_id)
wallet = await get_watch_wallet(wallet_id) wallet = await get_watch_wallet(wallet_id)
if not wallet: if not wallet:
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
addresses = await get_addresses(wallet_id) addresses = await get_addresses(wallet_id)
if not addresses: if not addresses:
await get_fresh_address(wallet_id) await get_fresh_address(wallet_id)
addresses = await get_addresses(wallet_id) addresses = await get_addresses(wallet_id)
return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK return jsonify([address._asdict() for address in addresses]), HTTPStatus.OK
@@ -115,17 +118,13 @@ async def api_get_addresses(wallet_id):
) )
async def api_update_mempool(): async def api_update_mempool():
mempool = await update_mempool(user=g.wallet.user, **g.data) mempool = await update_mempool(user=g.wallet.user, **g.data)
return jsonify(mempool._asdict()), HTTPStatus.OK return jsonify(mempool._asdict()), HTTPStatus.OK
@watchonly_ext.route("/api/v1/mempool", methods=["GET"]) @watchonly_ext.route("/api/v1/mempool", methods=["GET"])
@api_check_wallet_key("admin") @api_check_wallet_key("admin")
async def api_get_mempool(): async def api_get_mempool():
mempool = await get_mempool(g.wallet.user) mempool = await get_mempool(g.wallet.user)
if not mempool: if not mempool:
mempool = await create_mempool(user=g.wallet.user) mempool = await create_mempool(user=g.wallet.user)
return jsonify(mempool._asdict()), HTTPStatus.OK return jsonify(mempool._asdict()), HTTPStatus.OK