feat: integrate psbt response from HWW
This commit is contained in:
parent
367faf437a
commit
b7c4a411b1
5 changed files with 110 additions and 60 deletions
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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##########################
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue