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 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

View file

@ -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')
},

View file

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

View file

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

View file

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

View file

@ -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()