feat: show address details on device

This commit is contained in:
Vlad Stan 2022-09-08 16:37:02 +03:00
parent c432671ab7
commit 8c87e84b68
12 changed files with 131 additions and 53 deletions

View File

@ -23,9 +23,10 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
type,
address_no,
balance,
network
network,
meta
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
wallet_id,
@ -37,6 +38,7 @@ async def create_watch_wallet(w: WalletAccount) -> WalletAccount:
w.address_no,
w.balance,
w.network,
w.meta,
),
)

View File

@ -93,3 +93,10 @@ async def m006_drop_mempool_table(db):
Mempool data is now part of `config`
"""
await db.execute("DROP TABLE watchonly.mempool;")
async def m007_add_wallet_meta_data(db):
"""
Add 'meta' for storing various metadata about the wallet
"""
await db.execute("ALTER TABLE watchonly.wallets ADD COLUMN meta TEXT DEFAULT '{}';")

View File

@ -9,6 +9,7 @@ class CreateWallet(BaseModel):
masterpub: str = Query("")
title: str = Query("")
network: str = "Mainnet"
meta: str = "{}"
class WalletAccount(BaseModel):
@ -21,6 +22,7 @@ class WalletAccount(BaseModel):
balance: int
type: Optional[str] = ""
network: str = "Mainnet"
meta: str = "{}"
@classmethod
def from_row(cls, row: Row) -> "WalletAccount":

View File

@ -170,6 +170,31 @@
type="password"
label="Password"
></q-input>
<q-separator></q-separator>
<q-toggle
label="Passphrase (optional)"
color="secodary"
v-model="hww.hasPassphrase"
></q-toggle>
<q-input
v-if="hww.hasPassphrase"
v-model.trim="hww.passphrase"
filled
:type="hww.showPassphrase ? 'text' : 'password'"
filled
dense
label="Passphrase"
>
<template v-slot:append>
<q-icon
:name="hww.showPassphrase ? 'visibility' : 'visibility_off'"
class="cursor-pointer"
@click="hww.showPassphrase = !hww.showPassphrase"
/>
</template>
</q-input>
<br />
<div class="row q-mt-lg">
<q-btn
@ -428,31 +453,7 @@
</template>
</q-input>
<br />
<q-toggle
label="Passphrase (optional)"
color="secodary"
v-model="hww.hasPassphrase"
></q-toggle>
<br />
<q-input
v-if="hww.hasPassphrase"
v-model.trim="hww.passphrase"
filled
:type="hww.showPassphrase ? 'text' : 'password'"
filled
dense
label="Passphrase"
>
<template v-slot:append>
<q-icon
:name="hww.showPassphrase ? 'visibility' : 'visibility_off'"
class="cursor-pointer"
@click="hww.showPassphrase = !hww.showPassphrase"
/>
</template>
</q-input>
<q-separator></q-separator>
<br />
<span>Enter new password (8 numbers/letters)</span>
<q-input
v-model.trim="hww.password"

View File

@ -403,7 +403,10 @@ async function serialSigner(path) {
},
hwwLogin: async function () {
try {
await this.sendCommandSecure(COMMAND_PASSWORD, [this.hww.password])
await this.sendCommandSecure(COMMAND_PASSWORD, [
this.hww.password,
this.hww.passphrase
])
} catch (error) {
this.$q.notify({
type: 'warning',
@ -414,7 +417,9 @@ async function serialSigner(path) {
} finally {
this.hww.showPasswordDialog = false
this.hww.password = null
this.hww.passphrase = null
this.hww.showPassword = false
this.hww.showPassphrase = false
}
},
handleLoginResponse: function (res = '') {
@ -449,6 +454,22 @@ async function serialSigner(path) {
})
}
},
hwwShowAddress: async function (path, address) {
try {
await this.sendCommandSecure(COMMAND_ADDRESS, [
this.network,
path,
address
])
} catch (error) {
this.$q.notify({
type: 'warning',
message: 'Failed to logout from Hardware Wallet!',
caption: `${error}`,
timeout: 10000
})
}
},
handleLogoutResponse: function (res = '') {
const authenticated = !(res.trim() === '1')
if (this.hww.authenticated && !authenticated) {
@ -800,17 +821,9 @@ async function serialSigner(path) {
},
hwwRestore: async function () {
try {
let mnemonicWithPassphrase = this.hww.mnemonic
if (
this.hww.hasPassphrase &&
this.hww.passphrase &&
this.hww.passphrase.length
) {
mnemonicWithPassphrase += '/' + this.hww.passphrase
}
await this.sendCommandSecure(COMMAND_RESTORE, [
this.hww.password,
mnemonicWithPassphrase
this.hww.mnemonic
])
} catch (error) {
this.$q.notify({
@ -822,7 +835,6 @@ async function serialSigner(path) {
} finally {
this.hww.showRestoreDialog = false
this.hww.mnemonic = null
this.hww.passphrase = null
this.hww.showMnemonic = false
this.hww.password = null
this.hww.confirmedPassword = null

View File

@ -125,14 +125,35 @@
<div class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg">Master Pubkey:</div>
<div class="col-8">
<q-input
v-model="props.row.masterpub"
filled
readonly
type="textarea"
/>
<q-input v-model="props.row.masterpub" filled readonly />
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
color="grey"
icon="content_copy"
@click="copyText(props.row.masterpub)"
class="q-ml-sm"
></q-btn>
</div>
</div>
<div
v-if="props.row.meta?.xpub"
class="row items-center no-wrap q-mb-md"
>
<div class="col-2 q-pr-lg">XPub:</div>
<div class="col-8">
<q-input v-model="props.row.meta.xpub" filled readonly />
</div>
<div class="col-2 q-pr-lg">
<q-btn
outline
color="grey"
icon="content_copy"
@click="copyText(props.row.meta.xpub)"
class="q-ml-sm"
></q-btn>
</div>
<div class="col-2 q-pr-lg"></div>
</div>
<div class="row items-center no-wrap q-mb-md">
<div class="col-2 q-pr-lg">Last Address Index:</div>

View File

@ -118,9 +118,11 @@ async function walletList(path) {
},
createWalletAccount: async function (data) {
try {
const meta = {accountPath: this.accountPath}
if (this.formDialog.useSerialPort) {
const {xpub, fingerprint} = await this.fetchXpubFromHww()
if (!xpub) return
meta.xpub = xpub
const path = this.accountPath.substring(2)
const outputType = this.formDialog.addressType.id
if (outputType === 'sh') {
@ -129,6 +131,7 @@ async function walletList(path) {
data.masterpub = `${outputType}([${fingerprint}/${path}]${xpub}/{0,1}/*)`
}
}
data.meta = JSON.stringify(meta)
const response = await LNbits.api.request(
'POST',
'/watchonly/api/v1/wallet',
@ -247,7 +250,7 @@ async function walletList(path) {
const wallet = this.walletAccounts.find(w => w.id === walletId) || {}
wallet.address_no = addressData.addressIndex
this.$emit('new-receive-address', addressData)
this.$emit('new-receive-address', {addressData, wallet})
},
showAddAccountDialog: function () {
this.formDialog.show = true
@ -283,6 +286,16 @@ async function walletList(path) {
const addressType =
this.addressTypeOptions.find(t => t.id === value.id) || {}
this.accountPath = addressType[`path${this.network}`]
},
// todo: bad. base.js not present in custom components
copyText: function (text, message, position) {
var notify = this.$q.notify
Quasar.utils.copyToClipboard(text).then(function () {
notify({
message: message || 'Copied to clipboard!',
position: position || 'bottom'
})
})
}
},
created: async function () {

View File

@ -172,10 +172,6 @@ const watchOnly = async () => {
this.$refs.paymentRef.updateSignedPsbt(psbtBase64)
},
//################### SERIAL PORT ###################
//################### HARDWARE WALLET ###################
//################### UTXOs ###################
scanAllAddresses: async function () {
await this.refreshAddresses()
@ -380,6 +376,26 @@ const watchOnly = async () => {
showAddressDetails: function (addressData) {
this.openQrCodeDialog(addressData)
},
showAddressDetailsWithConfirmation: function ({addressData, wallet}) {
this.showAddressDetails(addressData)
if (this.$refs.serialSigner.isConnected()) {
if (this.$refs.serialSigner.isAuthenticated()) {
if (wallet.meta?.accountPath) {
const branchIndex = addressData.isChange ? 1 : 0
const path =
wallet.meta.accountPath +
`/${branchIndex}/${addressData.addressIndex}`
this.$refs.serialSigner.hwwShowAddress(path, addressData.address)
}
} else {
this.$q.notify({
type: 'warning',
message: 'Please login in order to confirm address on device',
timeout: 10000
})
}
}
},
initUtxos: function (addresses) {
if (!this.fetchedUtxos && addresses.length) {
this.fetchedUtxos = true

View File

@ -74,6 +74,7 @@ const mapWalletAccount = function (o) {
'YYYY-MM-DD HH:mm'
)
: '',
meta: o.meta ? JSON.parse(o.meta) : null,
label: o.title,
expanded: false
})

View File

@ -3,6 +3,7 @@ const PSBT_BASE64_PREFIX = 'cHNidP8'
const COMMAND_PING = '/ping'
const COMMAND_PASSWORD = '/password'
const COMMAND_PASSWORD_CLEAR = '/password-clear'
const COMMAND_ADDRESS = '/address'
const COMMAND_SEND_PSBT = '/psbt'
const COMMAND_SIGN_PSBT = '/sign'
const COMMAND_HELP = '/help'

View File

@ -27,7 +27,7 @@
:addresses="addresses"
:serial-signer-ref="$refs.serialSigner"
@accounts-update="updateAccounts"
@new-receive-address="showAddressDetails"
@new-receive-address="showAddressDetailsWithConfirmation"
>
</wallet-list>
@ -148,7 +148,8 @@
<q-card>
<q-card-section>
<h6 class="text-subtitle1 q-my-none">
{{SITE_TITLE}} Onchain Wallet (watch-only) Extension <small>(v0.100)</small>
{{SITE_TITLE}} Onchain Wallet (watch-only) Extension
<small>(v0.100)</small>
</h6>
</q-card-section>
<q-card-section class="q-pa-none">

View File

@ -93,6 +93,7 @@ async def api_wallet_create_or_update(
address_no=-1, # so fresh address on empty wallet can get address with index 0
balance=0,
network=network["name"],
meta=data.meta,
)
wallets = await get_watch_wallets(w.wallet.user, network["name"])
@ -137,7 +138,7 @@ async def api_wallet_delete(wallet_id, w: WalletTypeInfo = Depends(require_admin
await delete_watch_wallet(wallet_id)
await delete_addresses_for_wallet(wallet_id)
raise HTTPException(status_code=HTTPStatus.NO_CONTENT)
return "", HTTPStatus.NO_CONTENT
#############################ADDRESSES##########################