Cleaned up watchonly

This commit is contained in:
Ben Arc
2021-04-04 13:17:24 +01:00
parent ae5d45a73e
commit 69ef4d7e63
6 changed files with 130 additions and 272 deletions

View File

@@ -92,16 +92,18 @@ async def get_fresh_address(wallet_id: str) -> Addresses:
address = await get_derive_address(wallet_id, wallet[4] + 1)
await update_watch_wallet(wallet_id = wallet_id, address_no = wallet[4] + 1)
masterpub_id = urlsafe_short_hash()
await db.execute(
"""
INSERT INTO addresses (
id,
address,
wallet,
amount
)
VALUES (?, ?, ?)
VALUES (?, ?, ?, ?)
""",
(address, wallet_id, 0),
(masterpub_id, address, wallet_id, 0),
)
return await get_address(address)

View File

@@ -18,7 +18,8 @@ async def m001_initial(db):
await db.execute(
"""
CREATE TABLE IF NOT EXISTS addresses (
address TEXT NOT NULL PRIMARY KEY,
id TEXT NOT NULL PRIMARY KEY,
address TEXT NOT NULL,
wallet TEXT NOT NULL,
amount INTEGER NOT NULL
);

View File

@@ -23,6 +23,7 @@ class Mempool(NamedTuple):
return cls(**dict(row))
class Addresses(NamedTuple):
id: str
address: str
wallet: str
amount: int

View File

@@ -1,9 +1,10 @@
<q-card>
<q-card-section>
<p>The WatchOnly extension uses mempool.space for blockchain data.<br />
<p>Watch Only extension uses mempool.space<br />
For use with "account Extended Public Key" <a href="https://iancoleman.io/bip39/">https://iancoleman.io/bip39/</a>
<small>
Created by, <a href="https://github.com/benarc">Ben Arc</a></small
>
<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
>)
</p>
</q-card-section>
@@ -15,31 +16,31 @@
label="API info"
:content-inset-level="0.5"
>
<q-expansion-item group="api" dense expand-separator label="List pay links">
<q-expansion-item group="api" dense expand-separator label="List wallets">
<q-card>
<q-card-section>
<code><span class="text-blue">GET</span> /pay/api/v1/links</code>
<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;pay_link_object&gt;, ...]</code>
<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 }}pay/api/v1/links -H "X-Api-Key: {{
>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 a pay link">
<q-expansion-item group="api" dense expand-separator label="Get wallet details">
<q-card>
<q-card-section>
<code
><span class="text-blue">GET</span>
/pay/api/v1/links/&lt;pay_id&gt;</code
/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 />
@@ -47,10 +48,10 @@
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<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 }}pay/api/v1/links/&lt;pay_id&gt; -H
>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>
@@ -60,24 +61,22 @@
group="api"
dense
expand-separator
label="Create a charge link"
label="Create wallet"
>
<q-card>
<q-card-section>
<code><span class="text-green">POST</span> /pay/api/v1/links</code>
<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>
<code>{"description": &lt;string&gt; "amount": &lt;integer&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 201 CREATED (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</code>
<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 }}pay/api/v1/links -d
'{"description": &lt;string&gt;, "amount": &lt;integer&gt;}' -H
>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>
@@ -88,44 +87,14 @@
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/&lt;pay_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">Body (application/json)</h5>
<code>{"description": &lt;string&gt;, "amount": &lt;integer&gt;}</code>
<h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json)
</h5>
<code>{"lnurl": &lt;string&gt;}</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/&lt;pay_id&gt; -d
'{"description": &lt;string&gt;, "amount": &lt;integer&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 a pay link"
label="Delete wallet"
class="q-pb-md"
>
<q-card>
<q-card-section>
<code
><span class="text-pink">DELETE</span>
/pay/api/v1/links/&lt;pay_id&gt;</code
/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 />
@@ -133,10 +102,95 @@
<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/&lt;pay_id&gt;
>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

@@ -66,14 +66,8 @@
<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>
@@ -96,14 +90,6 @@
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
@@ -117,10 +103,7 @@
</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>
@@ -137,7 +120,7 @@
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">
LNbits WatchOnly Extension
LNbits Watch Only Extension
</h6>
</q-card-section>
<q-card-section class="q-pa-none">
@@ -165,20 +148,11 @@
v-model="formDialog.data.masterpub"
height="50px"
autogrow
label="Master Public Key, either xpub, ypub, zpub"
label="Account Extended Public Key; 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="
@@ -293,23 +267,6 @@
)
return obj
}
var mapTxs = function (obj) {
obj._data = _.clone(obj)
obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm'
)
return obj
}
var mapCharge = 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',
@@ -321,8 +278,6 @@
checker: null,
walletLinks: [],
AddressesLinks: [],
txsLinks: [],
ChargeLinks: [],
currentaddress: "",
Addresses: {
show: false,
@@ -351,50 +306,6 @@
rowsPerPage: 10
}
},
ChargesTable: {
columns: [
{name: 'id', align: 'left', label: 'ID', field: 'id'},
{
name: 'title',
align: 'left',
label: 'Title',
field: 'title'
},
{
name: 'amount',
align: 'left',
label: 'Amount to pay',
field: 'amount'
},
{
name: 'balance',
align: 'left',
label: 'Balance',
field: 'amount_paid'
},
{
name: 'address',
align: 'left',
label: 'Address',
field: 'address'
},
{
name: 'time to pay',
align: 'left',
label: 'Time to Pay',
field: 'time_to_pay'
},
{
name: 'timeleft',
align: 'left',
label: 'Time left',
field: 'timeleft'
},
],
pagination: {
rowsPerPage: 10
}
},
formDialog: {
show: false,
data: {}
@@ -424,26 +335,6 @@
LNbits.utils.notifyApiError(error)
})
},
getAddressTxs: function (address){
LNbits.api
.request(
'GET',
'/watchonly/api/v1/mempool/txs/' + address,
this.g.user.wallets[0].inkey
)
.then(function (response) {
self.txsLinks = response.data.map(function (obj) {
console.log(obj)
return mapTxs(obj)
})
})
.catch(function (error) {
LNbits.utils.notifyApiError(error)
})
},
getAddresses: function (walletID) {
var self = this
LNbits.api
@@ -488,7 +379,7 @@
.request(
'GET',
'/watchonly/api/v1/mempool',
this.g.user.wallets[0].inkey
this.g.user.wallets[0].adminkey
)
.then(function (response) {
self.mempool.endpoint = response.data.endpoint
@@ -505,7 +396,7 @@
.request(
'PUT',
'/watchonly/api/v1/mempool',
wallet.inkey, self.mempool)
wallet.adminkey, self.mempool)
.then(function (response) {
self.mempool.endpoint = response.data.endpoint
self.walletLinks.push(mapwalletLink(response.data))
@@ -549,47 +440,17 @@
self.current = linkId
self.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')
this.createWalletLink(wallet, data)
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)
.request('POST', '/watchonly/api/v1/wallet', wallet.adminkey, data)
.then(function (response) {
self.walletLinks.push(mapWalletLink(response.data))
self.formDialog.show = false
@@ -608,7 +469,7 @@
.request(
'DELETE',
'/watchonly/api/v1/wallet/' + linkId,
self.g.user.wallets[0].inkey
self.g.user.wallets[0].adminkey
)
.then(function (response) {
self.walletLinks = _.reject(self.walletLinks, function (obj) {

View File

@@ -36,18 +36,16 @@ async def api_wallets_retrieve():
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_wallet_retrieve(wallet_id):
wallet = await get_watch_wallet(wallet_id)
addresses = await api_get_addresses(wallet_id)
wallet = await get_watch_wallet(wallet_id)
if not wallet:
return jsonify({"message": "wallet does not exist"}), HTTPStatus.NOT_FOUND
return jsonify({wallet}), HTTPStatus.OK
return jsonify(wallet._asdict()), 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_check_wallet_key("admin")
@api_validate_post_request(
schema={
"masterpub": {"type": "string", "empty": False, "required": True},
@@ -55,20 +53,15 @@ async def api_wallet_retrieve(wallet_id):
}
)
async def api_wallet_create_or_update(wallet_id=None):
print("g.data")
if not wallet_id:
wallet = await create_watch_wallet(user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"])
mempool = await get_mempool(g.wallet.user)
if not mempool:
create_mempool(user=g.wallet.user)
return jsonify(wallet._asdict()), HTTPStatus.CREATED
else:
wallet = await update_watch_wallet(wallet_id=wallet_id, **g.data)
return jsonify(wallet._asdict()), HTTPStatus.OK
wallet = await create_watch_wallet(user=g.wallet.user, masterpub=g.data["masterpub"], title=g.data["title"])
mempool = await get_mempool(g.wallet.user)
if not mempool:
create_mempool(user=g.wallet.user)
return jsonify(wallet._asdict()), HTTPStatus.CREATED
@watchonly_ext.route("/api/v1/wallet/<wallet_id>", methods=["DELETE"])
@api_check_wallet_key("invoice")
@api_check_wallet_key("admin")
async def api_wallet_delete(wallet_id):
wallet = await get_watch_wallet(wallet_id)
@@ -114,7 +107,7 @@ async def api_get_addresses(wallet_id):
#############################MEMPOOL##########################
@watchonly_ext.route("/api/v1/mempool", methods=["PUT"])
@api_check_wallet_key("invoice")
@api_check_wallet_key("admin")
@api_validate_post_request(
schema={
"endpoint": {"type": "string", "empty": False, "required": True},
@@ -125,67 +118,13 @@ async def api_update_mempool():
return jsonify(mempool._asdict()), HTTPStatus.OK
@watchonly_ext.route("/api/v1/mempool", methods=["GET"])
@api_check_wallet_key("invoice")
@api_check_wallet_key("admin")
async def api_get_mempool():
mempool = await get_mempool(g.wallet.user)
if not mempool:
mempool = await create_mempool(user=g.wallet.user)
return jsonify(mempool._asdict()), HTTPStatus.OK
@watchonly_ext.route("/api/v1/mempool/<address>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_get_mempool_wallet_balance(address):
mempool = await get_mempool(g.wallet.user)
if not mempool:
mempool = await create_mempool(user=g.wallet.user)
url = (
mempool.endpoint
+ "/api/address/"
+ address
)
header = {
"Content-Type": "application/json",
}
async with httpx.AsyncClient() as client:
try:
r = await client.get(
url,
headers=header,
timeout=40,
)
mp_response = json.loads(r.text)
print(mp_response)
except AssertionError:
mp_response = "Error occured"
return jsonify(mp_response), HTTPStatus.OK
@watchonly_ext.route("/api/v1/mempool/txs/<address>", methods=["GET"])
@api_check_wallet_key("invoice")
async def api_get_mempool_wallet_txs(address):
mempool = await get_mempool(g.wallet.user)
if not mempool:
mempool = await create_mempool(user=g.wallet.user)
url = (
mempool.endpoint
+ "/api/address/"
+ address
+ "/txs"
)
header = {
"Content-Type": "application/json",
}
async with httpx.AsyncClient() as client:
try:
r = await client.get(
url,
headers=header,
timeout=40,
)
mp_response = json.loads(r.text)
print(mp_response)
except AssertionError:
mp_response = "Error occured"
return jsonify(mp_response), HTTPStatus.OK