From 7b75cbf59c41b269d0c59a27383a632481d8ab92 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Tue, 12 Jul 2022 15:46:30 +0300 Subject: [PATCH 001/136] feat: add `Share PSBT` button with options --- .../extensions/watchonly/static/js/index.js | 51 +++++++------ .../watchonly/templates/watchonly/index.html | 73 ++++++++++++++++--- 2 files changed, 93 insertions(+), 31 deletions(-) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index f44d30cd..70c3ae16 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -426,28 +426,6 @@ new Vue({ this.payment.txSize = Math.round(txSize(tx)) return this.payment.feeRate * this.payment.txSize }, - createPsbt: async function () { - const wallet = this.g.user.wallets[0] - try { - this.computeFee() - const tx = this.createTx() - txSize(tx) - for (const input of tx.inputs) { - input.tx_hex = await this.fetchTxHex(input.tx_id) - } - - const {data} = await LNbits.api.request( - 'POST', - '/watchonly/api/v1/psbt', - wallet.adminkey, - tx - ) - - this.payment.psbtBase64 = data - } catch (err) { - LNbits.utils.notifyApiError(err) - } - }, deletePaymentAddress: function (v) { const index = this.payment.data.indexOf(v) if (index !== -1) { @@ -499,6 +477,35 @@ new Vue({ const payedAmount = this.getTotalPaymentAmount() paymentAddress.amount = Math.max(0, inputAmount - payedAmount - fee) }, + //################### PSBT ################### + createPsbt: async function () { + const wallet = this.g.user.wallets[0] + try { + this.computeFee() + const tx = this.createTx() + txSize(tx) + for (const input of tx.inputs) { + input.tx_hex = await this.fetchTxHex(input.tx_id) + } + + const {data} = await LNbits.api.request( + 'POST', + '/watchonly/api/v1/psbt', + wallet.adminkey, + tx + ) + + this.payment.psbtBase64 = data + } catch (err) { + LNbits.utils.notifyApiError(err) + } + }, + sharePsbtOnSerialPort: async function () { + console.log('### sharePsbtOnSerialPort') + }, + sharePsbtWithAnimatedQRCode: async function () { + console.log('### sharePsbtWithAnimatedQRCode') + }, //################### UTXOs ################### scanAllAddresses: async function () { diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index ff596699..16ebdabb 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -674,8 +674,22 @@ - + +
+
+ + The payed amount is higher than the selected amount! + +
+
+ @@ -993,6 +1007,7 @@ +
- - The payed amount is higher than the selected amount! - + + + + + + + Serial Port + Send the PSBT using an USB port + + + + + + + + + + Animated QR Code + Comming Soon + + + +
Date: Tue, 12 Jul 2022 17:34:40 +0300 Subject: [PATCH 002/136] feat: add basic communication via the serial port --- .../extensions/watchonly/static/js/index.js | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index 70c3ae16..c7938f56 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -501,7 +501,43 @@ new Vue({ } }, sharePsbtOnSerialPort: async function () { - console.log('### sharePsbtOnSerialPort') + console.log('### sharePsbtOnSerialPort', navigator.serial, navigator) + if (!navigator.serial) { + this.$q.notify({ + type: 'warning', + message: 'Serial port communication not supported!', + caption: + 'Make sure your browser supports Serial Port and that you are using HTTPS.', + timeout: 10000 + }) + return + } + navigator.serial.addEventListener('connect', event => { + console.log('### navigator.serial event: connected!', event) + }) + + navigator.serial.addEventListener('disconnect', event => { + console.log('### navigator.serial event: disconnected!', event) + }) + // const ports = await navigator.serial.getPorts(); + const port = await navigator.serial.requestPort() + console.log('### port', port) + + // Wait for the serial port to open. + await port.open({baudRate: 9600}) + + const writer = port.writable.getWriter() + + const psbtByteArray = Uint8Array.from(atob(this.payment.psbtBase64), c => + c.charCodeAt(0) + ) + await writer.write(psbtByteArray) + + // Allow the serial port to be closed later. + writer.releaseLock() + + await port.close() + console.log('### sharePsbtOnSerialPort done') }, sharePsbtWithAnimatedQRCode: async function () { console.log('### sharePsbtWithAnimatedQRCode') From ae68c4dc6e3c57bb3ad2338760c4445efea8a70c Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 13 Jul 2022 12:45:38 +0300 Subject: [PATCH 003/136] chore: code format --- .../extensions/watchonly/static/js/index.js | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index c7938f56..afde985c 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -519,25 +519,36 @@ new Vue({ navigator.serial.addEventListener('disconnect', event => { console.log('### navigator.serial event: disconnected!', event) }) - // const ports = await navigator.serial.getPorts(); - const port = await navigator.serial.requestPort() - console.log('### port', port) + try { + // const ports = await navigator.serial.getPorts(); + const port = await navigator.serial.requestPort() + console.log('### port', port) - // Wait for the serial port to open. - await port.open({baudRate: 9600}) + // Wait for the serial port to open. + await port.open({baudRate: 9600}) - const writer = port.writable.getWriter() + const writer = port.writable.getWriter() - const psbtByteArray = Uint8Array.from(atob(this.payment.psbtBase64), c => - c.charCodeAt(0) - ) - await writer.write(psbtByteArray) + const psbtByteArray = Uint8Array.from( + atob(this.payment.psbtBase64), + c => c.charCodeAt(0) + ) + await writer.write(psbtByteArray) - // Allow the serial port to be closed later. - writer.releaseLock() + // Allow the serial port to be closed later. + writer.releaseLock() - await port.close() - console.log('### sharePsbtOnSerialPort done') + await port.close() + console.log('### sharePsbtOnSerialPort done') + } catch (error) { + console.log('### error', error) + this.$q.notify({ + type: 'warning', + message: 'Serial port communication failed!', + caption: `${error}`, + timeout: 10000 + }) + } }, sharePsbtWithAnimatedQRCode: async function () { console.log('### sharePsbtWithAnimatedQRCode') From 75944741897f010891f4edc933a8f0147c171598 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 13 Jul 2022 17:22:45 +0300 Subject: [PATCH 004/136] feat: send data to and from serial port --- .../extensions/watchonly/static/js/index.js | 136 ++++++++++++++---- .../extensions/watchonly/static/js/tables.js | 13 ++ .../watchonly/templates/watchonly/index.html | 114 ++++++++------- 3 files changed, 184 insertions(+), 79 deletions(-) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index afde985c..1e1b597d 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -33,6 +33,16 @@ new Vue({ show: false }, + serial: { + selectedPort: null, + writableStreamClosed: null, + writer: null, + readableStreamClosed: null, + reader: null, + showAdvancedConfig: false, + config: {} + }, + formDialog: { show: false, data: {} @@ -500,8 +510,7 @@ new Vue({ LNbits.utils.notifyApiError(err) } }, - sharePsbtOnSerialPort: async function () { - console.log('### sharePsbtOnSerialPort', navigator.serial, navigator) + checkSerialPortSupported: function () { if (!navigator.serial) { this.$q.notify({ type: 'warning', @@ -510,46 +519,119 @@ new Vue({ 'Make sure your browser supports Serial Port and that you are using HTTPS.', timeout: 10000 }) - return + return false } - navigator.serial.addEventListener('connect', event => { - console.log('### navigator.serial event: connected!', event) - }) - - navigator.serial.addEventListener('disconnect', event => { - console.log('### navigator.serial event: disconnected!', event) - }) + return true + }, + openSerialPort: async function () { + if (!this.checkSerialPortSupported()) return + console.log('### openSerialPort', this.serial.selectedPort) try { - // const ports = await navigator.serial.getPorts(); - const port = await navigator.serial.requestPort() - console.log('### port', port) + navigator.serial.addEventListener('connect', event => { + console.log('### navigator.serial event: connected!', event) + }) + navigator.serial.addEventListener('disconnect', event => { + console.log('### navigator.serial event: disconnected!', event) + }) + this.serial.selectedPort = await navigator.serial.requestPort() // Wait for the serial port to open. - await port.open({baudRate: 9600}) + await this.serial.selectedPort.open({baudRate: 9600}) + this.startSerialPortReading() - const writer = port.writable.getWriter() - - const psbtByteArray = Uint8Array.from( - atob(this.payment.psbtBase64), - c => c.charCodeAt(0) + const textEncoder = new TextEncoderStream() + this.serial.writableStreamClosed = textEncoder.readable.pipeTo( + this.serial.selectedPort.writable ) - await writer.write(psbtByteArray) - // Allow the serial port to be closed later. - writer.releaseLock() - - await port.close() - console.log('### sharePsbtOnSerialPort done') + this.serial.writer = textEncoder.writable.getWriter() } catch (error) { - console.log('### error', error) this.$q.notify({ type: 'warning', - message: 'Serial port communication failed!', + message: 'Cannot open serial port!', caption: `${error}`, timeout: 10000 }) } }, + closeSerialPort: async function () { + try { + console.log('### closeSerialPort', this.serial.selectedPort) + if (this.serial.writer) this.serial.writer.close() + if (this.serial.writableStreamClosed) + await this.serial.writableStreamClosed + if (this.serial.reader) this.reader.writer.close() + if (this.serial.readableStreamClosed) + await this.serial.readableStreamClosed + if (this.serial.selectedPort) await this.serial.selectedPort.close() + this.serial.selectedPort = null + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Cannot close serial port!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + sendPsbtToSerialPort: async function () { + try { + await this.serial.writer.write(this.payment.psbtBase64 + '\n') + this.$q.notify({ + type: 'positive', + message: 'Data sent to serial port!', + timeout: 5000 + }) + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to send data to serial port!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + + startSerialPortReading: async function () { + const port = this.serial.selectedPort + + while (port && port.readable) { + const textDecoder = new TextDecoderStream() + this.serial.readableStreamClosed = port.readable.pipeTo( + textDecoder.writable + ) + this.serial.reader = textDecoder.readable.getReader() + + try { + while (true) { + console.log('### reader.read()') + const {value, done} = await this.serial.reader.read() + if (value) { + console.log(value) + this.$q.notify({ + type: 'warning', + message: 'Received data from serial port (not psbt)', + caption: value.slice(0, 80) + '...', + timeout: 5000 + }) + } + if (done) { + this.serial.reader.close() + this.serial.readereadableStreamClosed() + return + } + } + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Serial port communication error!', + caption: `${error}`, + timeout: 10000 + }) + } + } + console.log('### startSerialPortReading DONE') + }, sharePsbtWithAnimatedQRCode: async function () { console.log('### sharePsbtWithAnimatedQRCode') }, diff --git a/lnbits/extensions/watchonly/static/js/tables.js b/lnbits/extensions/watchonly/static/js/tables.js index fdd558bd..26182912 100644 --- a/lnbits/extensions/watchonly/static/js/tables.js +++ b/lnbits/extensions/watchonly/static/js/tables.js @@ -260,6 +260,7 @@ const tableData = { fee: 0, txSize: 0, psbtBase64: '', + psbtBase64Signed: '', utxoSelectionModes: [ 'Manual', 'Random', @@ -268,6 +269,18 @@ const tableData = { 'Larger Inputs First' ], utxoSelectionMode: 'Manual', + signModes: [ + { + label: 'Serial Port', + value: 'serial-port' + }, + { + label: 'Animated QR', + value: 'animated-qr', + disable: true + } + ], + signMode: '', show: false, showAdvanced: false }, diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index 16ebdabb..f140494a 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -1009,72 +1009,82 @@
-
+
Create PSBT
-
- + - - - - - - - Serial Port - Send the PSBT using an USB port - - - - - - - - - - Animated QR Code - Comming Soon - - - - + v-model="payment.psbtBase64" + filled + readonly + />
-
+
Sign With
+
+ +
+
+ +
+
+
+ Connect + Disconnect +
+
+ Send PSBT +
+
+ +
+
+
+
+
From 3bf0bb1e63dbe6177dc585e0e263119ae1661e24 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Wed, 13 Jul 2022 17:56:12 +0300 Subject: [PATCH 005/136] fix: port disconnect --- lnbits/extensions/watchonly/static/js/index.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index 1e1b597d..b3d69299 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -560,12 +560,20 @@ new Vue({ if (this.serial.writer) this.serial.writer.close() if (this.serial.writableStreamClosed) await this.serial.writableStreamClosed - if (this.serial.reader) this.reader.writer.close() + if (this.serial.reader) this.serial.reader.cancel() if (this.serial.readableStreamClosed) - await this.serial.readableStreamClosed + await this.serial.readableStreamClosed.catch(() => { + /* Ignore the error */ + }) if (this.serial.selectedPort) await this.serial.selectedPort.close() this.serial.selectedPort = null + this.$q.notify({ + type: 'positive', + message: 'Serial port disconnected!', + timeout: 5000 + }) } catch (error) { + console.log('### error', error) this.$q.notify({ type: 'warning', message: 'Cannot close serial port!', @@ -616,8 +624,6 @@ new Vue({ }) } if (done) { - this.serial.reader.close() - this.serial.readereadableStreamClosed() return } } From 73adc4a7e825cef033a26554755487726fdadc34 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 14 Jul 2022 14:15:11 +0300 Subject: [PATCH 006/136] feat: handle psbt extract --- lnbits/extensions/watchonly/models.py | 12 ++- .../extensions/watchonly/static/js/index.js | 66 +++++++++++++-- .../extensions/watchonly/static/js/tables.js | 2 +- .../extensions/watchonly/static/js/utils.js | 2 + .../watchonly/templates/watchonly/index.html | 19 ++--- lnbits/extensions/watchonly/views_api.py | 80 ++++++++++++++++--- 6 files changed, 149 insertions(+), 32 deletions(-) diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index bc10e421..d499c504 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -1,6 +1,5 @@ from sqlite3 import Row -from typing import List - +from typing import List, Optional from fastapi.param_functions import Query from pydantic import BaseModel @@ -82,6 +81,15 @@ class CreatePsbt(BaseModel): tx_size: int +class ExtractPsbt(BaseModel): + psbtBase64 = "" + + +class SignedTransaction(BaseModel): + tx_hex: Optional[str] + tx_json: Optional[str] + + class Config(BaseModel): mempool_endpoint = "https://mempool.space" receive_gap_limit = 20 diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index b3d69299..d6b21f31 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -587,7 +587,7 @@ new Vue({ await this.serial.writer.write(this.payment.psbtBase64 + '\n') this.$q.notify({ type: 'positive', - message: 'Data sent to serial port!', + message: 'Data sent to serial port device!', timeout: 5000 }) } catch (error) { @@ -609,19 +609,42 @@ new Vue({ textDecoder.writable ) this.serial.reader = textDecoder.readable.getReader() + let psbtChunks = [] try { while (true) { console.log('### reader.read()') const {value, done} = await this.serial.reader.read() + console.log('### value', value) if (value) { - console.log(value) - this.$q.notify({ - type: 'warning', - message: 'Received data from serial port (not psbt)', - caption: value.slice(0, 80) + '...', - timeout: 5000 - }) + const data = value.split('\n') + console.log('### xxx', data) + const isPsbtStartChunk = data[0].startsWith(PSBT_BASE64_PREFIX) + if (isPsbtStartChunk) { + psbtChunks = [data[0]] + } else if (psbtChunks.length) { + psbtChunks.push(data[0]) + if (data.length > 1) { + console.log('### psbtChunks', psbtChunks) + this.$q.notify({ + type: 'positive', + message: 'PSBT received from serial port device!', + timeout: 10000 + }) + const transaction = await this.etractTxFromPsbt( + psbtChunks.join('') + ) + console.log('### transaction', transaction) + } + } else { + psbtChunks = [] + this.$q.notify({ + type: 'warning', + message: 'Received data from serial port (not psbt)', + caption: value.slice(0, 80) + '...', + timeout: 5000 + }) + } } if (done) { return @@ -638,6 +661,33 @@ new Vue({ } console.log('### startSerialPortReading DONE') }, + etractTxFromPsbt: async function (psbtBase64) { + const wallet = this.g.user.wallets[0] + try { + const {data} = await LNbits.api.request( + 'PUT', + '/watchonly/api/v1/psbt/extract', + wallet.adminkey, + { + psbtBase64 + } + ) + console.log('### data', data) + if (data.error) { + this.$q.notify({ + type: 'warning', + message: 'Cannot process received PSBT!', + caption: data.error, + timeout: 10000 + }) + } + + return data + } catch (error) { + console.log('### error', error, JSON.stringify(error)) + LNbits.utils.notifyApiError(error) + } + }, sharePsbtWithAnimatedQRCode: async function () { console.log('### sharePsbtWithAnimatedQRCode') }, diff --git a/lnbits/extensions/watchonly/static/js/tables.js b/lnbits/extensions/watchonly/static/js/tables.js index 26182912..49597bd2 100644 --- a/lnbits/extensions/watchonly/static/js/tables.js +++ b/lnbits/extensions/watchonly/static/js/tables.js @@ -271,7 +271,7 @@ const tableData = { utxoSelectionMode: 'Manual', signModes: [ { - label: 'Serial Port', + label: 'Serial Port Device', value: 'serial-port' }, { diff --git a/lnbits/extensions/watchonly/static/js/utils.js b/lnbits/extensions/watchonly/static/js/utils.js index 26bebac6..3726d867 100644 --- a/lnbits/extensions/watchonly/static/js/utils.js +++ b/lnbits/extensions/watchonly/static/js/utils.js @@ -1,3 +1,5 @@ +const PSBT_BASE64_PREFIX = 'cHNidP8' + const blockTimeToDate = blockTime => blockTime ? moment(blockTime * 1000).format('LLL') : '' diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index f140494a..c5993d2a 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -1044,7 +1044,7 @@ class="row items-center no-wrap q-mb-md q-mt-lg" >
-
+
+ +
+
Send PSBTSend to Device
-
- -
diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index ae656540..2c097022 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -1,34 +1,40 @@ from http import HTTPStatus +import json -from embit import script -from embit.descriptor import Descriptor, Key -from embit.ec import PublicKey -from embit.psbt import PSBT, DerivationPath -from embit.transaction import Transaction, TransactionInput, TransactionOutput from fastapi import Query, Request from fastapi.params import Depends from starlette.exceptions import HTTPException +from embit.descriptor import Descriptor, Key +from embit.psbt import PSBT, DerivationPath +from embit.ec import PublicKey +from embit.transaction import Transaction, TransactionInput, TransactionOutput +from embit import script, finalizer + from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from lnbits.extensions.watchonly import watchonly_ext from .crud import ( - create_config, - create_fresh_addresses, + create_mempool, create_watch_wallet, - delete_addresses_for_wallet, delete_watch_wallet, get_addresses, - get_config, get_fresh_address, + create_fresh_addresses, + update_address, + delete_addresses_for_wallet, + get_mempool, get_watch_wallet, get_watch_wallets, - update_address, - update_config, + update_mempool, update_watch_wallet, + create_config, + get_config, + update_config, ) +from .models import SignedTransaction, CreateWallet, CreatePsbt, Config, WalletAccount, ExtractPsbt from .helpers import parse_key -from .models import Config, CreatePsbt, CreateWallet, WalletAccount + ###################WALLETS############################# @@ -261,6 +267,36 @@ async def api_psbt_create( raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) +@watchonly_ext.put("/api/v1/psbt/extract") +async def api_psbt_extract_tx( + data: ExtractPsbt, w: WalletTypeInfo = Depends(require_admin_key) +): + res = SignedTransaction() + try: + psbt = PSBT.from_base64(data.psbtBase64) + final_psbt = finalizer.finalize_psbt(psbt) + if not final_psbt: + raise ValueError("PSBT cannot be finalized!") + res.tx_hex = final_psbt.to_string() + + transaction = Transaction.from_string(res.tx_hex) + tx = { + "locktime": transaction.locktime, + "version": transaction.version, + "outputs": [], + "fee": psbt.fee(), + } + + for out in transaction.vout: + tx["outputs"].append( + {"value": out.value, "address": out.script_pubkey.address()} + ) + res.tx_json = json.dumps(tx) + except Exception as e: + raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) + return res.dict() + + #############################CONFIG########################## @@ -278,3 +314,23 @@ async def api_get_config(w: WalletTypeInfo = Depends(get_key_type)): if not config: config = await create_config(user=w.wallet.user) return config.dict() + + +#############################MEMPOOL########################## + +### TODO: fix statspay dependcy and remove +@watchonly_ext.put("/api/v1/mempool") +async def api_update_mempool( + endpoint: str = Query(...), w: WalletTypeInfo = Depends(require_admin_key) +): + mempool = await update_mempool(**{"endpoint": endpoint}, user=w.wallet.user) + return mempool.dict() + + +### TODO: fix statspay dependcy and remove +@watchonly_ext.get("/api/v1/mempool") +async def api_get_mempool(w: WalletTypeInfo = Depends(require_admin_key)): + mempool = await get_mempool(w.wallet.user) + if not mempool: + mempool = await create_mempool(user=w.wallet.user) + return mempool.dict() From d80ae7de1b429f88ca05a0c094e5b05e5ec15c14 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 14 Jul 2022 17:37:36 +0300 Subject: [PATCH 007/136] feat: show signed transaction details --- .../extensions/watchonly/static/js/index.js | 18 ++----- .../extensions/watchonly/static/js/tables.js | 1 + .../watchonly/templates/watchonly/index.html | 52 +++++++++++++++---- lnbits/extensions/watchonly/views_api.py | 11 +++- 4 files changed, 57 insertions(+), 25 deletions(-) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index d6b21f31..5448159e 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -626,15 +626,16 @@ new Vue({ psbtChunks.push(data[0]) if (data.length > 1) { console.log('### psbtChunks', psbtChunks) + this.payment.psbtBase64Signed = psbtChunks.join('') this.$q.notify({ type: 'positive', message: 'PSBT received from serial port device!', timeout: 10000 }) - const transaction = await this.etractTxFromPsbt( - psbtChunks.join('') + const data = await this.etractTxFromPsbt( + this.payment.psbtBase64Signed ) - console.log('### transaction', transaction) + this.payment.signedTx = JSON.parse(data.tx_json) } } else { psbtChunks = [] @@ -672,19 +673,8 @@ new Vue({ psbtBase64 } ) - console.log('### data', data) - if (data.error) { - this.$q.notify({ - type: 'warning', - message: 'Cannot process received PSBT!', - caption: data.error, - timeout: 10000 - }) - } - return data } catch (error) { - console.log('### error', error, JSON.stringify(error)) LNbits.utils.notifyApiError(error) } }, diff --git a/lnbits/extensions/watchonly/static/js/tables.js b/lnbits/extensions/watchonly/static/js/tables.js index 49597bd2..ffb095b3 100644 --- a/lnbits/extensions/watchonly/static/js/tables.js +++ b/lnbits/extensions/watchonly/static/js/tables.js @@ -261,6 +261,7 @@ const tableData = { txSize: 0, psbtBase64: '', psbtBase64Signed: '', + signedTx: null, utxoSelectionModes: [ 'Manual', 'Random', diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index c5993d2a..ef9fb131 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -1043,13 +1043,14 @@ v-if="payment.psbtBase64 && payment.signMode === 'serial-port'" class="row items-center no-wrap q-mb-md q-mt-lg" > -
-
+ +
Connect Disconnect
-
+
@@ -1074,21 +1075,54 @@ @click="sendPsbtToSerialPort()" unelevated color="secondary float-right" - >Send to DeviceSend PSBT to Device
-
-
-
+
+
PSBT from device
+
+
+
+
+
+
Version
+
{{payment.signedTx.version}}
+
+
+
Locktime
+
{{payment.signedTx.locktime}}
+
+
+
Fee
+
+ {{satBtc(payment.signedTx.fee)}}
+
+
+
Send
+
+
+
{{satBtc(out.amount)}}
+
to
+
{{out.address}}
+
+
+
+
+
diff --git a/lnbits/extensions/watchonly/views_api.py b/lnbits/extensions/watchonly/views_api.py index 2c097022..b5dd2c3c 100644 --- a/lnbits/extensions/watchonly/views_api.py +++ b/lnbits/extensions/watchonly/views_api.py @@ -32,7 +32,14 @@ from .crud import ( get_config, update_config, ) -from .models import SignedTransaction, CreateWallet, CreatePsbt, Config, WalletAccount, ExtractPsbt +from .models import ( + SignedTransaction, + CreateWallet, + CreatePsbt, + Config, + WalletAccount, + ExtractPsbt, +) from .helpers import parse_key @@ -289,7 +296,7 @@ async def api_psbt_extract_tx( for out in transaction.vout: tx["outputs"].append( - {"value": out.value, "address": out.script_pubkey.address()} + {"amount": out.value, "address": out.script_pubkey.address()} ) res.tx_json = json.dumps(tx) except Exception as e: From 0e87e6608ffb0c91c68926a15c2825ef3f3cade3 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 14 Jul 2022 18:21:16 +0300 Subject: [PATCH 008/136] fix: handle Connect/Disconnect failure state --- lnbits/extensions/watchonly/static/js/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index 5448159e..19d375a1 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -546,6 +546,7 @@ new Vue({ this.serial.writer = textEncoder.writable.getWriter() } catch (error) { + this.serial.selectedPort = null this.$q.notify({ type: 'warning', message: 'Cannot open serial port!', @@ -573,6 +574,7 @@ new Vue({ timeout: 5000 }) } catch (error) { + this.serial.selectedPort = null console.log('### error', error) this.$q.notify({ type: 'warning', From 6098b8308ea7e19cf1fe420b276c8b4f0e2487f3 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Thu, 14 Jul 2022 18:47:52 +0300 Subject: [PATCH 009/136] feat:small UI improvements --- .../extensions/watchonly/static/js/index.js | 6 ++- .../watchonly/templates/watchonly/index.html | 47 +++++++++++-------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index 19d375a1..bbfe2ad7 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -637,7 +637,11 @@ new Vue({ const data = await this.etractTxFromPsbt( this.payment.psbtBase64Signed ) - this.payment.signedTx = JSON.parse(data.tx_json) + if (data) { + this.payment.signedTx = JSON.parse(data.tx_json) + } else { + this.payment.signedTx = null + } } } else { psbtChunks = [] diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index ef9fb131..fe7bd59e 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -173,24 +173,29 @@
-
+
Scan Blockchain - +
-
+
+ +
+
Make Payment - {{payment.changeAmount ? payment.changeAmount: + {{payment.changeAmount ? satBtc(payment.changeAmount): 'no change'}} Connect Disconnect
@@ -1066,10 +1069,11 @@
-
+
{{satBtc(payment.signedTx.fee)}}
-
-
Send
+ +
+
{{satBtc(out.amount)}}
+
-
-
{{satBtc(out.amount)}}
-
to
-
{{out.address}}
-
-
+ {{out.address}}
+
@@ -1298,7 +1300,12 @@
{% endblock %} {% block scripts %} {{ window_vars(user) }} - + From 367faf437a3d8e6fe530104f337692e6553f7d75 Mon Sep 17 00:00:00 2001 From: Vlad Stan Date: Fri, 15 Jul 2022 09:02:50 +0300 Subject: [PATCH 010/136] feat: broadcast transaction (partial solution) --- lnbits/extensions/watchonly/models.py | 6 +- .../extensions/watchonly/static/js/index.js | 23 +++++++ .../extensions/watchonly/static/js/tables.js | 2 + .../watchonly/templates/watchonly/index.html | 64 ++++++++++++++----- lnbits/extensions/watchonly/views_api.py | 23 +++++++ 5 files changed, 101 insertions(+), 17 deletions(-) diff --git a/lnbits/extensions/watchonly/models.py b/lnbits/extensions/watchonly/models.py index d499c504..cce93b48 100644 --- a/lnbits/extensions/watchonly/models.py +++ b/lnbits/extensions/watchonly/models.py @@ -82,7 +82,7 @@ class CreatePsbt(BaseModel): class ExtractPsbt(BaseModel): - psbtBase64 = "" + psbtBase64 = "" # // todo snake case class SignedTransaction(BaseModel): @@ -90,6 +90,10 @@ class SignedTransaction(BaseModel): tx_json: Optional[str] +class BroadcastTransaction(BaseModel): + tx_hex: str + + class Config(BaseModel): mempool_endpoint = "https://mempool.space" receive_gap_limit = 20 diff --git a/lnbits/extensions/watchonly/static/js/index.js b/lnbits/extensions/watchonly/static/js/index.js index bbfe2ad7..79a0e6e3 100644 --- a/lnbits/extensions/watchonly/static/js/index.js +++ b/lnbits/extensions/watchonly/static/js/index.js @@ -639,8 +639,10 @@ new Vue({ ) if (data) { this.payment.signedTx = JSON.parse(data.tx_json) + this.payment.signedTxHex = data.tx_hex } else { this.payment.signedTx = null + his.payment.signedTxHex = null } } } else { @@ -688,6 +690,27 @@ new Vue({ console.log('### sharePsbtWithAnimatedQRCode') }, + broadcastTransaction: async function () { + console.log('### broadcastTransaction', this.payment.signedTxHex) + + try { + const wallet = this.g.user.wallets[0] + await LNbits.api.request( + 'POST', + '/watchonly/api/v1/tx', + wallet.adminkey, + {tx_hex: this.payment.signedTxHex} + ) + } catch (error) { + this.$q.notify({ + type: 'warning', + message: 'Failed to broadcast!', + caption: `${error}`, + timeout: 10000 + }) + } + }, + //################### UTXOs ################### scanAllAddresses: async function () { await this.refreshAddresses() diff --git a/lnbits/extensions/watchonly/static/js/tables.js b/lnbits/extensions/watchonly/static/js/tables.js index ffb095b3..616b7060 100644 --- a/lnbits/extensions/watchonly/static/js/tables.js +++ b/lnbits/extensions/watchonly/static/js/tables.js @@ -262,6 +262,8 @@ const tableData = { psbtBase64: '', psbtBase64Signed: '', signedTx: null, + signedTxHex: null, + sentTxId: null, utxoSelectionModes: [ 'Manual', 'Random', diff --git a/lnbits/extensions/watchonly/templates/watchonly/index.html b/lnbits/extensions/watchonly/templates/watchonly/index.html index fe7bd59e..18414480 100644 --- a/lnbits/extensions/watchonly/templates/watchonly/index.html +++ b/lnbits/extensions/watchonly/templates/watchonly/index.html @@ -182,14 +182,13 @@ :disabled="scan.scanning == true" >Scan Blockchain -
+ v-if="scan.scanning == true" + color="primary" + size="2.55em" + >
- {{payment.changeAmount ? satBtc(payment.changeAmount): - 'no change'}} + {{payment.changeAmount ? + satBtc(payment.changeAmount): 'no change'}} Disconnect
-
+
-
+
Fee
- {{satBtc(payment.signedTx.fee)}}
+ {{satBtc(payment.signedTx.fee)}} + +
-
-
{{satBtc(out.amount)}}
- +
+
+ {{satBtc(out.amount)}} +
+
- {{out.address}}
+ {{out.address}} +
- +
+
+
+
+ Send Payment +
+
+
@@ -1301,7 +1334,6 @@ {% endblock %} {% block scripts %} {{ window_vars(user) }}