feat: add accounts using the HWW

This commit is contained in:
Vlad Stan 2022-08-01 18:53:26 +03:00
parent 73265a9951
commit 2ac8ee95b0
4 changed files with 109 additions and 24 deletions

View File

@ -29,8 +29,9 @@ async function serialSigner(path) {
showSignedPsbt: false,
sendingPsbt: false,
signingPsbt: false,
psbtSentResolve: null,
loginResolve: null,
psbtSentResolve: null,
xpubResolve: null,
confirm: {
outputIndex: 0,
showFee: false
@ -105,7 +106,6 @@ async function serialSigner(path) {
})
} catch (error) {
this.selectedPort = null
console.log('### error', error)
this.$q.notify({
type: 'warning',
message: 'Cannot close serial port!',
@ -137,6 +137,12 @@ async function serialSigner(path) {
})
},
isFetchingXpub: async function () {
return new Promise(resolve => {
this.xpubResolve = resolve
})
},
checkSerialPortSupported: function () {
if (!navigator.serial) {
this.$q.notify({
@ -162,7 +168,6 @@ async function serialSigner(path) {
try {
while (true) {
const {value, done} = await readStringUntil('\n')
console.log('### value', value)
if (value) {
this.handleSerialPortResponse(value)
this.updateSerialPortConsole(value)
@ -180,15 +185,31 @@ async function serialSigner(path) {
}
},
handleSerialPortResponse: function (value) {
const msg = value.split(' ')
if (msg[0] == COMMAND_SIGN_PSBT) this.handleSignResponse(msg[1])
else if (msg[0] == COMMAND_PASSWORD) this.handleLoginResponse(msg[1])
else if (msg[0] == COMMAND_PASSWORD_CLEAR)
this.handleLogoutResponse(msg[1])
else if (msg[0] == COMMAND_SEND_PSBT)
this.handleSendPsbtResponse(msg[1])
else if (msg[0] == COMMAND_WIPE) this.handleWipeResponse(msg[1])
else console.log('### console', value)
const command = value.split(' ')[0]
const commandData = value.substring(command.length).trim()
switch (command) {
case COMMAND_SIGN_PSBT:
this.handleSignResponse(commandData)
break
case COMMAND_PASSWORD:
this.handleLoginResponse(commandData)
break
case COMMAND_PASSWORD_CLEAR:
this.handleLogoutResponse(commandData)
break
case COMMAND_SEND_PSBT:
this.handleSendPsbtResponse(commandData)
break
case COMMAND_WIPE:
this.handleWipeResponse(commandData)
break
case COMMAND_XPUB:
this.handleXpubResponse(commandData)
break
default:
console.log('### console', value)
}
},
updateSerialPortConsole: function (value) {
this.receivedData += value + '\n'
@ -304,7 +325,6 @@ async function serialSigner(path) {
}
},
handleLogoutResponse: function (res = '') {
console.log('###handleLogoutResponse ', res)
this.hww.authenticated = !(res.trim() === '1')
if (this.hww.authenticated) {
this.$q.notify({
@ -316,7 +336,6 @@ async function serialSigner(path) {
},
hwwSendPsbt: async function (psbtBase64, tx) {
try {
console.log('### hwwSendPsbt tx', tx)
this.tx = tx
this.hww.sendingPsbt = true
await this.writer.write(
@ -427,7 +446,6 @@ async function serialSigner(path) {
},
handleWipeResponse: function (res = '') {
const wiped = res.trim() === '1'
console.log('### wiped', wiped)
if (wiped) {
this.$q.notify({
type: 'positive',
@ -443,6 +461,36 @@ async function serialSigner(path) {
})
}
},
hwwXpub: async function (path) {
try {
await this.writer.write(
COMMAND_XPUB + ' ' + this.network + ' ' + path + '\n'
)
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to fetch XPub!',
caption: `${error}`,
timeout: 10000
})
}
},
handleXpubResponse: function (res = '') {
const args = res.trim().split(' ')
if (args.length < 3 || args[0].trim() !== '1') {
this.$q.notify({
type: 'warning',
message: 'Failed to fetch XPub!',
caption: `${res}`,
timeout: 10000
})
this.xpubResolve({})
return
}
const xpub = args[1].trim()
const fingerprint = args[2].trim()
this.xpubResolve({xpub, fingerprint})
},
hwwShowSeed: async function () {
try {
await this.writer.write(COMMAND_SEED + '\n')

View File

@ -205,7 +205,7 @@
v-if="formDialog.useSerialPort"
filled
type="text"
v-model="formDialog.data.accountPath"
v-model="accountPath"
height="50px"
autogrow
label="Account Path"
@ -216,7 +216,7 @@
unelevated
color="primary"
:disable="
(formDialog.data.masterpub == null && formDialog.data.accountPath == null)||
(formDialog.data.masterpub == null && accountPath == null)||
formDialog.data.title == null"
type="submit"
>Add Watch-Only Account</q-btn

View File

@ -18,13 +18,15 @@ async function walletList(path) {
address: {},
formDialog: {
show: false,
addressType: {
label: 'Pay-to-witness-pubkey-hash scripts (P2WPKH)',
value: 'wpkh'
},
addressType: 'wpkh',
useSerialPort: false,
data: {}
data: {
title: '',
masterpub: ''
}
},
accountPath: '',
filter: '',
addressTypeOptions: [
{
@ -103,6 +105,17 @@ async function walletList(path) {
},
createWalletAccount: async function (data) {
try {
if (this.formDialog.useSerialPort) {
const {xpub, fingerprint} = await this.fetchXpubFromHww()
if (!xpub) return
const path = this.accountPath.substring(2)
const outputType = this.formDialog.addressType
if (outputType === 'sh') {
data.masterpub = `${outputType}(wpkh([${fingerprint}/${path}]${xpub}/{0,1}/*))`
} else {
data.masterpub = `${outputType}([${fingerprint}/${path}]${xpub}/{0,1}/*)`
}
}
const response = await LNbits.api.request(
'POST',
'/watchonly/api/v1/wallet',
@ -117,6 +130,20 @@ async function walletList(path) {
LNbits.utils.notifyApiError(error)
}
},
fetchXpubFromHww: async function () {
const error = findAccountPathIssues(this.accountPath)
if (error) {
this.$q.notify({
type: 'warning',
message: 'Invalid derivation path.',
caption: error,
timeout: 10000
})
return
}
await this.serialSignerRef.hwwXpub(this.accountPath)
return await this.serialSignerRef.isFetchingXpub()
},
deleteWalletAccount: function (walletAccountId) {
LNbits.utils
.confirmDialog(
@ -214,7 +241,6 @@ async function walletList(path) {
this.formDialog.useSerialPort = false
},
getXpubFromDevice: async function () {
this.handleAddressTypeChanged('wpkh')
try {
if (!this.serialSignerRef.isConnected()) {
const portOpen = await this.serialSignerRef.openSerialPort()
@ -239,12 +265,13 @@ async function walletList(path) {
handleAddressTypeChanged: function (value) {
const addressType =
this.addressTypeOptions.find(t => t.value === value) || {}
this.formDialog.data.accountPath = addressType.path
this.accountPath = addressType.path
}
},
created: async function () {
if (this.inkey) {
await this.refreshWalletAccounts()
this.handleAddressTypeChanged('wpkh')
}
}
})

View File

@ -9,6 +9,7 @@ const COMMAND_SEED = '/seed'
const COMMAND_RESTORE = '/restore'
const COMMAND_CONFIRM_NEXT = '/confirm-next'
const COMMAND_CANCEL = '/cancel'
const COMMAND_XPUB = '/xpub'
const DEFAULT_RECEIVE_GAP_LIMIT = 20
@ -171,3 +172,12 @@ function loadTemplateAsync(path) {
return result
}
function findAccountPathIssues(path = '') {
const p = path.split('/')
if (p[0] !== 'm') return "Path must start with 'm/'"
for (let i = 1; i < p.length; i++) {
if (p[i].endsWith('')) p[i] = p[i].substring(0, p[i].length - 1)
if (isNaN(p[i])) return `${p[i]} is not a valid value`
}
}