feat: show address details on device
This commit is contained in:
parent
c432671ab7
commit
8c87e84b68
|
@ -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,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -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 '{}';")
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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##########################
|
||||
|
|
Loading…
Reference in New Issue
Block a user