feat: handle psbt extract

This commit is contained in:
Vlad Stan 2022-07-14 14:15:11 +03:00
parent 3bf0bb1e63
commit 73adc4a7e8
6 changed files with 149 additions and 32 deletions

View file

@ -1,6 +1,5 @@
from sqlite3 import Row from sqlite3 import Row
from typing import List from typing import List, Optional
from fastapi.param_functions import Query from fastapi.param_functions import Query
from pydantic import BaseModel from pydantic import BaseModel
@ -82,6 +81,15 @@ class CreatePsbt(BaseModel):
tx_size: int tx_size: int
class ExtractPsbt(BaseModel):
psbtBase64 = ""
class SignedTransaction(BaseModel):
tx_hex: Optional[str]
tx_json: Optional[str]
class Config(BaseModel): class Config(BaseModel):
mempool_endpoint = "https://mempool.space" mempool_endpoint = "https://mempool.space"
receive_gap_limit = 20 receive_gap_limit = 20

View file

@ -587,7 +587,7 @@ new Vue({
await this.serial.writer.write(this.payment.psbtBase64 + '\n') await this.serial.writer.write(this.payment.psbtBase64 + '\n')
this.$q.notify({ this.$q.notify({
type: 'positive', type: 'positive',
message: 'Data sent to serial port!', message: 'Data sent to serial port device!',
timeout: 5000 timeout: 5000
}) })
} catch (error) { } catch (error) {
@ -609,19 +609,42 @@ new Vue({
textDecoder.writable textDecoder.writable
) )
this.serial.reader = textDecoder.readable.getReader() this.serial.reader = textDecoder.readable.getReader()
let psbtChunks = []
try { try {
while (true) { while (true) {
console.log('### reader.read()') console.log('### reader.read()')
const {value, done} = await this.serial.reader.read() const {value, done} = await this.serial.reader.read()
console.log('### value', value)
if (value) { if (value) {
console.log(value) const data = value.split('\n')
this.$q.notify({ console.log('### xxx', data)
type: 'warning', const isPsbtStartChunk = data[0].startsWith(PSBT_BASE64_PREFIX)
message: 'Received data from serial port (not psbt)', if (isPsbtStartChunk) {
caption: value.slice(0, 80) + '...', psbtChunks = [data[0]]
timeout: 5000 } 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) { if (done) {
return return
@ -638,6 +661,33 @@ new Vue({
} }
console.log('### startSerialPortReading DONE') 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 () { sharePsbtWithAnimatedQRCode: async function () {
console.log('### sharePsbtWithAnimatedQRCode') console.log('### sharePsbtWithAnimatedQRCode')
}, },

View file

@ -271,7 +271,7 @@ const tableData = {
utxoSelectionMode: 'Manual', utxoSelectionMode: 'Manual',
signModes: [ signModes: [
{ {
label: 'Serial Port', label: 'Serial Port Device',
value: 'serial-port' value: 'serial-port'
}, },
{ {

View file

@ -1,3 +1,5 @@
const PSBT_BASE64_PREFIX = 'cHNidP8'
const blockTimeToDate = blockTime => const blockTimeToDate = blockTime =>
blockTime ? moment(blockTime * 1000).format('LLL') : '' blockTime ? moment(blockTime * 1000).format('LLL') : ''

View file

@ -1044,7 +1044,7 @@
class="row items-center no-wrap q-mb-md q-mt-lg" class="row items-center no-wrap q-mb-md q-mt-lg"
> >
<div class="col-3"></div> <div class="col-3"></div>
<div class="col-3"> <div class="col-2">
<q-btn <q-btn
v-if="!serial.selectedPort" v-if="!serial.selectedPort"
@click="openSerialPort()" @click="openSerialPort()"
@ -1061,21 +1061,22 @@
> >
</div> </div>
<div class="col-3"> <div class="col-3">
<q-toggle
label="Advanced Config"
color="secodary float-left"
class="q-pl-lg"
v-model="serial.showAdvancedConfig"
></q-toggle>
</div>
<div class="col-4">
<q-btn <q-btn
v-if="serial.selectedPort" v-if="serial.selectedPort"
@click="sendPsbtToSerialPort()" @click="sendPsbtToSerialPort()"
unelevated unelevated
color="secondary float-right" color="secondary float-right"
>Send PSBT</q-btn >Send to Device</q-btn
> >
</div> </div>
<div class="col-3">
<q-toggle
label="Advanced Config"
color="secodary float-right"
v-model="serial.showAdvancedConfig"
></q-toggle>
</div>
</div> </div>
<div class="row items-center no-wrap q-mb-md"> <div class="row items-center no-wrap q-mb-md">
<div class="col-4 q-pr-lg"></div> <div class="col-4 q-pr-lg"></div>

View file

@ -1,34 +1,40 @@
from http import HTTPStatus 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 import Query, Request
from fastapi.params import Depends from fastapi.params import Depends
from starlette.exceptions import HTTPException 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.decorators import WalletTypeInfo, get_key_type, require_admin_key
from lnbits.extensions.watchonly import watchonly_ext from lnbits.extensions.watchonly import watchonly_ext
from .crud import ( from .crud import (
create_config, create_mempool,
create_fresh_addresses,
create_watch_wallet, create_watch_wallet,
delete_addresses_for_wallet,
delete_watch_wallet, delete_watch_wallet,
get_addresses, get_addresses,
get_config,
get_fresh_address, get_fresh_address,
create_fresh_addresses,
update_address,
delete_addresses_for_wallet,
get_mempool,
get_watch_wallet, get_watch_wallet,
get_watch_wallets, get_watch_wallets,
update_address, update_mempool,
update_config,
update_watch_wallet, update_watch_wallet,
create_config,
get_config,
update_config,
) )
from .models import SignedTransaction, CreateWallet, CreatePsbt, Config, WalletAccount, ExtractPsbt
from .helpers import parse_key from .helpers import parse_key
from .models import Config, CreatePsbt, CreateWallet, WalletAccount
###################WALLETS############################# ###################WALLETS#############################
@ -261,6 +267,36 @@ async def api_psbt_create(
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) 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########################## #############################CONFIG##########################
@ -278,3 +314,23 @@ async def api_get_config(w: WalletTypeInfo = Depends(get_key_type)):
if not config: if not config:
config = await create_config(user=w.wallet.user) config = await create_config(user=w.wallet.user)
return config.dict() 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()