mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-25 07:07:48 +01:00
feat: handle psbt extract
This commit is contained in:
parent
3bf0bb1e63
commit
73adc4a7e8
6 changed files with 149 additions and 32 deletions
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
},
|
||||
|
|
|
@ -271,7 +271,7 @@ const tableData = {
|
|||
utxoSelectionMode: 'Manual',
|
||||
signModes: [
|
||||
{
|
||||
label: 'Serial Port',
|
||||
label: 'Serial Port Device',
|
||||
value: 'serial-port'
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const PSBT_BASE64_PREFIX = 'cHNidP8'
|
||||
|
||||
const blockTimeToDate = blockTime =>
|
||||
blockTime ? moment(blockTime * 1000).format('LLL') : ''
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue