feat: integrate psbt response from HWW

This commit is contained in:
Vlad Stan 2022-07-19 18:48:39 +03:00
parent 367faf437a
commit b7c4a411b1
5 changed files with 110 additions and 60 deletions

View file

@ -83,6 +83,7 @@ class CreatePsbt(BaseModel):
class ExtractPsbt(BaseModel): class ExtractPsbt(BaseModel):
psbtBase64 = "" # // todo snake case psbtBase64 = "" # // todo snake case
inputs: List[TransactionInput]
class SignedTransaction(BaseModel): class SignedTransaction(BaseModel):

View file

@ -40,6 +40,7 @@ new Vue({
readableStreamClosed: null, readableStreamClosed: null,
reader: null, reader: null,
showAdvancedConfig: false, showAdvancedConfig: false,
receivedData: '',
config: {} config: {}
}, },
@ -498,6 +499,7 @@ new Vue({
input.tx_hex = await this.fetchTxHex(input.tx_id) input.tx_hex = await this.fetchTxHex(input.tx_id)
} }
this.payment.tx = tx
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(
'POST', 'POST',
'/watchonly/api/v1/psbt', '/watchonly/api/v1/psbt',
@ -611,53 +613,19 @@ new Vue({
textDecoder.writable textDecoder.writable
) )
this.serial.reader = textDecoder.readable.getReader() this.serial.reader = textDecoder.readable.getReader()
let psbtChunks = [] const readStringUntil = readFromSerialPort(this.serial)
try { try {
while (true) { while (true) {
console.log('### reader.read()') const {value, done} = await readStringUntil('\n')
const {value, done} = await this.serial.reader.read()
console.log('### value', value) console.log('### value', value)
if (value) { if (value) {
const data = value.split('\n') const isPsbt = value.startsWith(PSBT_BASE64_PREFIX)
console.log('### xxx', data) if (isPsbt) this.updateSignedPsbt(value)
const isPsbtStartChunk = data[0].startsWith(PSBT_BASE64_PREFIX) this.updateSerialPortConsole(value)
if (isPsbtStartChunk) {
psbtChunks = [data[0]]
} else if (psbtChunks.length) {
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 data = await this.etractTxFromPsbt(
this.payment.psbtBase64Signed
)
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 {
psbtChunks = []
this.$q.notify({
type: 'warning',
message: 'Received data from serial port (not psbt)',
caption: value.slice(0, 80) + '...',
timeout: 5000
})
}
}
if (done) {
return
} }
console.log('### startSerialPortReading DONE', done)
if (done) return
} }
} catch (error) { } catch (error) {
this.$q.notify({ this.$q.notify({
@ -668,9 +636,9 @@ new Vue({
}) })
} }
} }
console.log('### startSerialPortReading DONE') console.log('### startSerialPortReading port', port)
}, },
etractTxFromPsbt: async function (psbtBase64) { extractTxFromPsbt: async function (psbtBase64) {
const wallet = this.g.user.wallets[0] const wallet = this.g.user.wallets[0]
try { try {
const {data} = await LNbits.api.request( const {data} = await LNbits.api.request(
@ -678,14 +646,43 @@ new Vue({
'/watchonly/api/v1/psbt/extract', '/watchonly/api/v1/psbt/extract',
wallet.adminkey, wallet.adminkey,
{ {
psbtBase64 psbtBase64,
inputs: this.payment.tx.inputs
} }
) )
return data return data
} catch (error) { } catch (error) {
this.$q.notify({
type: 'warning',
message: 'Cannot finalize PSBT!',
timeout: 10000
})
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
} }
}, },
updateSignedPsbt: async function (value) {
this.payment.psbtBase64Signed = value
this.$q.notify({
type: 'positive',
message: 'PSBT received from serial port device!',
timeout: 10000
})
const data = await this.extractTxFromPsbt(this.payment.psbtBase64Signed)
if (data) {
this.payment.signedTx = JSON.parse(data.tx_json)
this.payment.signedTxHex = data.tx_hex
} else {
this.payment.signedTx = null
this.payment.signedTxHex = null
}
},
updateSerialPortConsole: function (value) {
this.serial.receivedData += value + '\n'
const textArea = document.getElementById(
'watchonly-serial-port-data-input'
)
if (textArea) textArea.scrollTop = textArea.scrollHeight
},
sharePsbtWithAnimatedQRCode: async function () { sharePsbtWithAnimatedQRCode: async function () {
console.log('### sharePsbtWithAnimatedQRCode') console.log('### sharePsbtWithAnimatedQRCode')
}, },
@ -695,12 +692,18 @@ new Vue({
try { try {
const wallet = this.g.user.wallets[0] const wallet = this.g.user.wallets[0]
await LNbits.api.request( const {data} = await LNbits.api.request(
'POST', 'POST',
'/watchonly/api/v1/tx', '/watchonly/api/v1/tx',
wallet.adminkey, wallet.adminkey,
{tx_hex: this.payment.signedTxHex} {tx_hex: this.payment.signedTxHex}
) )
this.$q.notify({
type: 'positive',
message: 'Transaction broadcasted!',
caption: `${data}`,
timeout: 10000
})
} catch (error) { } catch (error) {
this.$q.notify({ this.$q.notify({
type: 'warning', type: 'warning',

View file

@ -99,3 +99,36 @@ const ACCOUNT_TYPES = {
} }
const getAccountDescription = type => ACCOUNT_TYPES[type] || 'nonstandard' const getAccountDescription = type => ACCOUNT_TYPES[type] || 'nonstandard'
const readFromSerialPort = serial => {
let partialChunk
let fulliness = []
const readStringUntil = async (separator = '\n') => {
console.log('### fulliness', fulliness)
if (fulliness.length) return fulliness.shift()
const chunks = []
if (partialChunk) {
// leftovers from previous read
chunks.push(partialChunk)
partialChunk = undefined
}
while (true) {
const {value, done} = await serial.reader.read()
console.log('### value 1', value)
if (value) {
const values = value.split(separator)
// found one or more separators
if (values.length > 1) {
chunks.push(values.shift()) // first element
partialChunk = values.pop() // last element
fulliness = values // full lines
return {value: chunks.join(''), done: false}
}
chunks.push(value)
}
if (done) return {value: chunks.join(''), done: true}
}
}
return readStringUntil
}

View file

@ -1019,12 +1019,7 @@
> >
</div> </div>
<div class="col-9"> <div class="col-9">
<q-input <q-input v-model="payment.psbtBase64" filled />
v-if="payment.psbtBase64"
v-model="payment.psbtBase64"
filled
readonly
/>
</div> </div>
</div> </div>
<div <div
@ -1066,7 +1061,7 @@
</div> </div>
<div class="col-6"> <div class="col-6">
<q-toggle <q-toggle
label="Advanced Config" label="Advanced"
color="secodary float-left" color="secodary float-left"
class="q-ml-lg" class="q-ml-lg"
v-model="serial.showAdvancedConfig" v-model="serial.showAdvancedConfig"
@ -1082,6 +1077,20 @@
> >
</div> </div>
</div> </div>
<div
v-if="serial.showAdvancedConfig"
class="row items-center no-wrap q-mb-md"
>
<div class="col-3 q-pr-lg">Message from device</div>
<div class="col-9">
<q-input
for="watchonly-serial-port-data-input"
v-model="serial.receivedData"
filled
type="textarea"
/>
</div>
</div>
<div <div
v-if="payment.psbtBase64Signed" v-if="payment.psbtBase64Signed"
class="row items-center no-wrap q-mb-md" class="row items-center no-wrap q-mb-md"

View file

@ -252,6 +252,7 @@ async def api_psbt_create(
psbt = PSBT(tx) psbt = PSBT(tx)
for i, inp in enumerate(inputs_extra): for i, inp in enumerate(inputs_extra):
print("### ", psbt.inputs[i].bip32_derivations)
psbt.inputs[i].bip32_derivations = inp["bip32_derivations"] psbt.inputs[i].bip32_derivations = inp["bip32_derivations"]
psbt.inputs[i].non_witness_utxo = inp.get("non_witness_utxo", None) psbt.inputs[i].non_witness_utxo = inp.get("non_witness_utxo", None)
@ -283,6 +284,9 @@ async def api_psbt_extract_tx(
res = SignedTransaction() res = SignedTransaction()
try: try:
psbt = PSBT.from_base64(data.psbtBase64) psbt = PSBT.from_base64(data.psbtBase64)
for i, inp in enumerate(data.inputs):
psbt.inputs[i].non_witness_utxo = Transaction.from_string(inp.tx_hex)
final_psbt = finalizer.finalize_psbt(psbt) final_psbt = finalizer.finalize_psbt(psbt)
if not final_psbt: if not final_psbt:
raise ValueError("PSBT cannot be finalized!") raise ValueError("PSBT cannot be finalized!")
@ -314,19 +318,19 @@ async def api_psbt_extract_tx(
try: try:
config = await get_config(w.wallet.user) config = await get_config(w.wallet.user)
if not config: if not config:
raise ValueError("Cannot broadcast transaction. Mempool endpoint not defined!") raise ValueError(
x = bytes.fromhex(data.tx_hex) "Cannot broadcast transaction. Mempool endpoint not defined!"
print('### x', x)
async with httpx.AsyncClient() as client:
r = await client.post(
config.mempool_endpoint + "/api/tx",
data=x
) )
tx_id = r.json() x = bytes.fromhex(data.tx_hex)
print("### x", x)
async with httpx.AsyncClient() as client:
r = await client.post(config.mempool_endpoint + "/api/tx", data=data.tx_hex)
tx_id = r.text
return tx_id return tx_id
except Exception as e: except Exception as e:
raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e)) raise HTTPException(status_code=HTTPStatus.BAD_REQUEST, detail=str(e))
#############################CONFIG########################## #############################CONFIG##########################