feat(@desktop/wallet): adding wallet derivation path updates

Check box added to warn user if they want to add a path out of the
default Status' wallet derivation path. In case they do that, they are
not able to migrate such keypair to a Keycard.

Closes: #9118
This commit is contained in:
Sale Djenic 2023-01-31 13:23:35 +01:00 committed by Anthony Laibe
parent 2c809a56e6
commit a12e599be2
16 changed files with 255 additions and 96 deletions

View File

@ -96,6 +96,9 @@ proc deleteAccount*(self: Controller, address: string, password = "") =
proc fetchDerivedAddressDetails*(self: Controller, address: string) =
self.walletAccountService.fetchDerivedAddressDetails(address)
method getDerivedAddress*(self: Controller, password: string, derivedFrom: string, path: string, hashPassword: bool)=
self.walletAccountService.getDerivedAddress(password, derivedFrom, path, hashPassword)
method getDerivedAddressList*(self: Controller, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int, hashPassword: bool)=
self.walletAccountService.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber, hashPassword)

View File

@ -38,6 +38,9 @@ method deleteAccount*(self: AccessInterface, keyUid: string, address: string) {.
method refreshWalletAccounts*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getDerivedAddress*(self: AccessInterface, password: string, derivedFrom: string, path: string, hashPassword: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method getDerivedAddressList*(self: AccessInterface, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int, hashPassword: bool) {.base.} =
raise newException(ValueError, "No implementation available")
@ -62,7 +65,7 @@ method authenticateUser*(self: AccessInterface) {.base.} =
method onUserAuthenticated*(self: AccessInterface, pin: string, password: string, keyUid: string) {.base.} =
raise newException(ValueError, "No implementation available")
method authenticateUserAndDeriveAddressOnKeycardForPath*(self: AccessInterface, keyUid: string, derivationPath: string) {.base.} =
method authenticateUserAndDeriveAddressOnKeycardForPath*(self: AccessInterface, keyUid: string, derivationPath: string, searchForFirstAvailableAddress: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method createSharedKeycardModule*(self: AccessInterface) {.base.} =
@ -79,4 +82,4 @@ method addressDetailsFetched*(self: AccessInterface, derivedAddress: DerivedAddr
raise newException(ValueError, "No implementation available")
method onSharedKeycarModuleFlowTerminated*(self: AccessInterface, lastStepInTheCurrentFlow: bool) {.base.} =
raise newException(ValueError, "No implementation available")
raise newException(ValueError, "No implementation available")

View File

@ -199,6 +199,9 @@ method deleteAccount*(self: Module, keyUid: string, address: string) =
self.processingWalletAccount = WalletAccountDetails(keyUid: keyUid, address: address)
self.authenticateActivityForKeyUid(keyUid, AuthenticationReason.DeleteAccountAuthentication)
method getDerivedAddress*(self: Module, password: string, derivedFrom: string, path: string, hashPassword: bool) =
self.controller.getDerivedAddress(password, derivedFrom, path, hashPassword)
method getDerivedAddressList*(self: Module, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int, hashPassword: bool) =
self.controller.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber, hashPassword)
@ -266,11 +269,12 @@ proc findFirstAvaliablePathForWallet(self: Module, keyUid: string): string =
return path
error "we couldn't find available wallet account path"
method authenticateUserAndDeriveAddressOnKeycardForPath*(self: Module, keyUid: string, derivationPath: string) =
method authenticateUserAndDeriveAddressOnKeycardForPath*(self: Module, keyUid: string, derivationPath: string, searchForFirstAvailableAddress: bool) =
self.authentiactionReason = AuthenticationReason.DeriveAccountForKeyPairAuthentication
var finalPath = derivationPath
if self.checkIfWalletAccountIsAlreadyCreated(keyUid, finalPath):
finalPath = self.findFirstAvaliablePathForWallet(keyUid)
if searchForFirstAvailableAddress and
self.checkIfWalletAccountIsAlreadyCreated(keyUid, finalPath):
finalPath = self.findFirstAvaliablePathForWallet(keyUid)
if self.keycardSharedModule.isNil:
self.createSharedKeycardModule()
self.processingWalletAccount = WalletAccountDetails(keyUid: keyUid, path: finalPath)

View File

@ -246,6 +246,11 @@ QtObject:
self.setDerivedAddressesLoading(false)
self.derivedAddressesChanged()
proc getDerivedAddress*(self: View, password: string, derivedFrom: string, path: string, hashPassword: bool) {.slot.} =
self.setDerivedAddressesLoading(true)
self.setDerivedAddressesError("")
self.delegate.getDerivedAddress(password, derivedfrom, path, hashPassword)
proc getDerivedAddressList*(self: View, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int, hashPassword: bool) {.slot.} =
self.setDerivedAddressesLoading(true)
self.setDerivedAddressesError("")
@ -296,5 +301,5 @@ QtObject:
proc destroySharedKeycarModule*(self: View) {.slot.} =
self.delegate.destroySharedKeycarModule()
proc authenticateUserAndDeriveAddressOnKeycardForPath*(self: View, keyUid: string, derivationPath: string) {.slot.} =
self.delegate.authenticateUserAndDeriveAddressOnKeycardForPath(keyUid, derivationPath)
proc authenticateUserAndDeriveAddressOnKeycardForPath*(self: View, keyUid: string, derivationPath: string, searchForFirstAvailableAddress: bool) {.slot.} =
self.delegate.authenticateUserAndDeriveAddressOnKeycardForPath(keyUid, derivationPath, searchForFirstAvailableAddress)

View File

@ -1,6 +1,8 @@
import NimQml, Tables, strformat, strutils
import key_pair_account_item
import ../../../../../app_service/common/account_constants
export key_pair_account_item
type
@ -78,6 +80,14 @@ QtObject:
return true
return false
proc containsPathOutOfTheDefaultStatusDerivationTree*(self: KeyPairAccountModel): bool =
for it in self.items:
if not it.getPath().startsWith(account_constants.PATH_WALLET_ROOT&"/") or
it.getPath().count("'") != 3 or
it.getPath().count("/") != 5:
return true
return false
proc getItemAtIndex*(self: KeyPairAccountModel, index: int): KeyPairAccountItem =
if index < 0 or index >= self.items.len:
return newKeyPairAccountItem()
@ -108,4 +118,5 @@ QtObject:
self.items[i].setColor(color)
if emoji.len > 0:
self.items[i].setEmoji(emoji)
return
return

View File

@ -192,6 +192,8 @@ QtObject:
self.setLastAccountAsObservedAccount()
proc containsAccountAddress*(self: KeyPairItem, address: string): bool =
return self.accounts.containsAccountAddress(address)
proc containsPathOutOfTheDefaultStatusDerivationTree*(self: KeyPairItem): bool {.slot.} =
return self.accounts.containsPathOutOfTheDefaultStatusDerivationTree()
proc updateDetailsForAccountWithAddressIfTheyAreSet*(self: KeyPairItem, address, name, color, emoji: string) =
self.accounts.updateDetailsForAddressIfTheyAreSet(address, name, color, emoji)

View File

@ -1,12 +1,27 @@
#################################################
# Async load derivedAddreses
#################################################
type
GetDerivedAddressesTaskArg* = ref object of QObjectTaskArg
GetDerivedAddressTaskArg* = ref object of QObjectTaskArg
password: string
derivedFrom: string
path: string
const getDerivedAddressTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[GetDerivedAddressTaskArg](argEncoded)
var output = %*{
"derivedAddress": "",
"error": ""
}
try:
let response = status_go_accounts.getDerivedAddress(arg.password, arg.derivedFrom, arg.path)
output["derivedAddresses"] = response.result
except Exception as e:
output["error"] = %* fmt"Error getting derived address list: {e.msg}"
arg.finish(output)
type
GetDerivedAddressesTaskArg* = ref object of GetDerivedAddressTaskArg
pageSize: int
pageNumber: int

View File

@ -433,6 +433,17 @@ QtObject:
account.emoji = emoji
self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, WalletAccountUpdated(account: account))
proc getDerivedAddress*(self: Service, password: string, derivedFrom: string, path: string, hashPassword: bool)=
let arg = GetDerivedAddressTaskArg(
password: if hashPassword: hashPassword(password) else: password,
derivedFrom: derivedFrom,
path: path,
tptr: cast[ByteAddress](getDerivedAddressTask),
vptr: cast[ByteAddress](self.vptr),
slot: "setDerivedAddress",
)
self.threadpool.start(arg)
proc getDerivedAddressList*(self: Service, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int, hashPassword: bool)=
let arg = GetDerivedAddressesTaskArg(
password: if hashPassword: hashPassword(password) else: password,
@ -479,6 +490,16 @@ QtObject:
error: error
))
proc setDerivedAddress*(self: Service, derivedAddressesJson: string) {.slot.} =
let response = parseJson(derivedAddressesJson)
let derivedAddress = response["derivedAddresses"].toDerivedAddressDto()
let error = response["error"].getStr()
# emit event
self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY, DerivedAddressesArgs(
derivedAddresses: @[derivedAddress],
error: error
))
proc fetchDerivedAddressDetails*(self: Service, address: string) =
let arg = FetchDerivedAddressDetailsTaskArg(
address: address,

View File

@ -313,6 +313,10 @@ proc setDisplayName*(displayName: string): RpcResponse[JsonNode] {.raises: [Exce
let payload = %* [displayName]
result = core.callPrivateRPC("setDisplayName".prefix, payload)
proc getDerivedAddress*(password: string, derivedFrom: string, path: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [password, derivedFrom, path]
result = core.callPrivateRPC("wallet_getDerivedAddressForPath", payload)
proc getDerivedAddressList*(password: string, derivedFrom: string, path: string, pageSize: int = 0, pageNumber: int = 6,): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [password, derivedFrom, path, pageSize, pageNumber ]
result = core.callPrivateRPC("wallet_getDerivedAddressesForPath", payload)

View File

@ -4,6 +4,7 @@ import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Components 0.1
import utils 1.0
@ -13,24 +14,69 @@ ColumnLayout {
id: derivationPathSelect
property string path: ""
property bool useFullyCustomPath: true
function reset() {
derivationPathInput.text = _internal.defaultDerivationPath
if (derivationPathSelect.useFullyCustomPath) {
derivationPathFullyCustomInput.text = _internal.customDerivationRootPath
}
else {
derivationPathStatusDefaultInput.text = _internal.defaultDerivationIndex
}
if (!_internal.userInputTimer.running) {
_internal.userInputTimer.start()
}
}
QtObject {
id: _internal
readonly property string defaultDerivationIndex: "1"
property var userInputTimer: Timer {
// 1 second wait after each key press
interval: 1000
running: false
onTriggered: {
derivationPathSelect.path = derivationPathInput.text
if (derivationPathSelect.useFullyCustomPath) {
derivationPathSelect.path = derivationPathFullyCustomInput.text
}
else {
if (derivationPathStatusDefaultInput.text === "") {
return
}
derivationPathSelect.path = _internal.defaultDerivationRootPath + derivationPathStatusDefaultInput.text
}
}
}
property bool pathError: Utils.isInvalidPath(RootStore.derivedAddressesError)
property bool derivationAddressLoading: RootStore.derivedAddressesLoading
property string defaultDerivationPath: "m/44'/60'/0'/0"
property string customDerivationRootPath: "m/44'/60'/0'/0"
property string defaultDerivationRootPath: "m/44'/60'/0'/0/"
}
Component {
id: loadedIcon
StatusIcon {
icon: _internal.pathError ? "cancel" : "checkmark"
height: 14
width: 14
color: _internal.pathError ? Theme.palette.dangerColor1 : Theme.palette.primaryColor1
}
}
Component {
id: loadingIcon
StatusLoadingIndicator {
color: Theme.palette.directColor4
}
}
Component {
id: fixedLeftPart
StatusBaseText {
rightPadding: 0
text: _internal.defaultDerivationRootPath
color: Theme.palette.baseColor1
font: derivationPathStatusDefaultInput.font
}
}
spacing: 7
@ -55,31 +101,34 @@ ColumnLayout {
}
}
StatusInput {
id: derivationPathInput
id: derivationPathFullyCustomInput
Layout.preferredHeight: 64
Layout.preferredWidth: parent.width
visible: derivationPathSelect.useFullyCustomPath
maximumHeight: 64
text: _internal.defaultDerivationPath
text: _internal.customDerivationRootPath
input.color: _internal.pathError ? Theme.palette.dangerColor1 : Theme.palette.directColor1
input.rightComponent: _internal.derivationAddressLoading ? loadingIcon : loadedIcon
onTextChanged: _internal.userInputTimer.start()
Component {
id: loadedIcon
StatusIcon {
icon: _internal.pathError ? "cancel" : "checkmark"
height: 14
width: 14
color: _internal.pathError ? Theme.palette.dangerColor1 : Theme.palette.primaryColor1
}
StatusInput {
id: derivationPathStatusDefaultInput
Layout.preferredHeight: 64
Layout.preferredWidth: parent.width
visible: !derivationPathSelect.useFullyCustomPath
maximumHeight: 64
text: _internal.defaultDerivationIndex
input.color: _internal.pathError ? Theme.palette.dangerColor1 : Theme.palette.directColor1
input.rightComponent: _internal.derivationAddressLoading ? loadingIcon : loadedIcon
input.leftComponent: fixedLeftPart
onTextChanged: _internal.userInputTimer.start()
validationMode: StatusInput.ValidationMode.IgnoreInvalidInput
validators: [
StatusRegularExpressionValidator {
regularExpression: /^[0-9]{0,9}$/
errorMessage: ""
}
}
Component {
id: loadingIcon
StatusLoadingIndicator {
color: Theme.palette.directColor4
}
}
]
}
}

View File

@ -32,6 +32,7 @@ Item {
selectedDerivedAddress.pathSubFix = 0
selectedDerivedAddress.title = "---"
selectedDerivedAddress.subTitle = qsTr("No activity")
selectedDerivedAddress.enabled = false
}
onIsLoadingChanged: {
@ -77,7 +78,7 @@ Item {
function runAction() {
if (derivedAddresses.selectedKeyUidMigratedToKeycard)
RootStore.authenticateUserAndDeriveAddressOnKeycardForPath(derivedAddresses.selectedKeyUid, derivedAddresses.selectedPath)
RootStore.authenticateUserAndDeriveAddressOnKeycardForPath(derivedAddresses.selectedKeyUid, derivedAddresses.selectedPath, false)
else
RootStore.authenticateUser()
}

View File

@ -76,7 +76,8 @@ StatusModal {
property bool selectedKeyUidMigratedToKeycard: RootStore.defaultSelectedKeyUidMigratedToKeycard
property string selectedPath: ""
property string selectedAddress: ""
property bool selectedAddressAvailable: true
property bool selectedAddressAvailable: false
property bool useFullyCustomPath: false
readonly property bool authenticationNeeded: d.selectedAccountType !== Constants.AddAccountType.WatchOnly &&
d.password === ""
@ -90,16 +91,22 @@ StatusModal {
}
function getDerivedAddressList() {
if(d.selectedAccountType === Constants.AddAccountType.ImportSeedPhrase
&& !!advancedSelection.expandableItem.path
&& !!advancedSelection.expandableItem.mnemonicText) {
RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText,
advancedSelection.expandableItem.path, numOfItems, pageNumber)
if (d.useFullyCustomPath) {
if(d.selectedAccountType === Constants.AddAccountType.ImportSeedPhrase
&& !!advancedSelection.expandableItem.path
&& !!advancedSelection.expandableItem.mnemonicText) {
RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText,
advancedSelection.expandableItem.path, numOfItems, pageNumber)
} else if(!!d.selectedPath && !!d.selectedAccountDerivedFromAddress
&& (d.password.length > 0)) {
RootStore.getDerivedAddressList(d.password, d.selectedAccountDerivedFromAddress,
d.selectedPath, numOfItems, pageNumber,
!(d.selectedKeyUidMigratedToKeycard || userProfile.isKeycardUser))
}
} else if(!!d.selectedPath && !!d.selectedAccountDerivedFromAddress
&& (d.password.length > 0)) {
RootStore.getDerivedAddressList(d.password, d.selectedAccountDerivedFromAddress,
d.selectedPath, numOfItems, pageNumber,
!(d.selectedKeyUidMigratedToKeycard || userProfile.isKeycardUser))
RootStore.getDerivedAddress(d.password, d.selectedAccountDerivedFromAddress, d.selectedPath,
!(d.selectedKeyUidMigratedToKeycard || userProfile.isKeycardUser))
}
}
@ -121,9 +128,13 @@ StatusModal {
accountNameInput.input.asset.emoji)
}
else {
errMessage = RootStore.generateNewAccount(d.password, accountNameInput.text, colorSelectionGrid.selectedColor,
accountNameInput.input.asset.emoji, advancedSelection.expandableItem.completePath,
advancedSelection.expandableItem.derivedFromAddress)
let finalFullPath = advancedSelection.expandableItem.completePath
if (!d.useFullyCustomPath) {
finalFullPath = d.selectedPath
}
errMessage = RootStore.generateNewAccount(d.password, accountNameInput.text, colorSelectionGrid.selectedColor,
accountNameInput.input.asset.emoji, finalFullPath,
advancedSelection.expandableItem.derivedFromAddress)
}
break
case Constants.AddAccountType.ImportSeedPhrase:
@ -157,7 +168,7 @@ StatusModal {
d.password = ""
if (d.selectedKeyUidMigratedToKeycard &&
d.selectedAccountType === Constants.AddAccountType.GenerateNew) {
RootStore.authenticateUserAndDeriveAddressOnKeycardForPath(d.selectedKeyUid, d.selectedPath)
RootStore.authenticateUserAndDeriveAddressOnKeycardForPath(d.selectedKeyUid, d.selectedPath, true)
}
else {
RootStore.authenticateUser()
@ -278,6 +289,8 @@ StatusModal {
expandableComponent: AdvancedAddAccountView {
width: parent.width
onCalculateDerivedPath: {
d.selectedAccountDerivedFromAddress = derivedFromAddress
d.selectedPath = path
if (d.selectedKeyUidMigratedToKeycard) {
d.password = ""
validationError.text = ""
@ -302,6 +315,7 @@ StatusModal {
d.selectedPath = Qt.binding(() => path)
d.selectedAddress = Qt.binding(() => selectedAddress)
d.selectedAddressAvailable = Qt.binding(() => selectedAddressAvailable)
d.useFullyCustomPath = Qt.binding(() => useFullyCustomPath)
advancedSelection.isValid = Qt.binding(() => isValid)
}
}
@ -322,10 +336,9 @@ StatusModal {
enabled: {
if (!accountNameInput.valid) {
return false
}
if (loading) {
if (!accountNameInput.valid ||
loading ||
(!d.useFullyCustomPath && !d.selectedAddressAvailable)) {
return false
}
return advancedSelection.isValid

View File

@ -186,6 +186,10 @@ QtObject {
globalUtils.copyToClipboard(text)
}
function getDerivedAddress(password, derivedFrom, path, hashPassword) {
walletSectionAccounts.getDerivedAddress(password, derivedFrom, path, hashPassword)
}
function getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber, hashPassword) {
walletSectionAccounts.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber, hashPassword)
}
@ -246,7 +250,7 @@ QtObject {
walletSectionAccounts.destroySharedKeycarModule()
}
function authenticateUserAndDeriveAddressOnKeycardForPath(keyUid, derivationPath) {
walletSectionAccounts.authenticateUserAndDeriveAddressOnKeycardForPath(keyUid, derivationPath)
function authenticateUserAndDeriveAddressOnKeycardForPath(keyUid, derivationPath, searchForFirstAvailableAddress) {
walletSectionAccounts.authenticateUserAndDeriveAddressOnKeycardForPath(keyUid, derivationPath, searchForFirstAvailableAddress)
}
}

View File

@ -16,11 +16,12 @@ import "../panels"
ColumnLayout {
id: advancedSection
readonly property alias useFullyCustomPath: fullyCustomPathCheckBox.checked
property int addAccountType: Constants.AddAccountType.GenerateNew
property string selectedKeyUid: RootStore.defaultSelectedKeyUid
property bool selectedKeyUidMigratedToKeycard: RootStore.defaultSelectedKeyUidMigratedToKeycard
property string selectedAddress: ""
property bool selectedAddressAvailable: true
property bool selectedAddressAvailable: false
property string enterPasswordIcon: ""
property string derivedFromAddress: ""
property string mnemonicText: ""
@ -76,11 +77,11 @@ ColumnLayout {
onPathChanged: {
if(addAccountType === Constants.AddAccountType.ImportSeedPhrase) {
if(importSeedPhrasePanel.isValid) {
calculateDerivedPath()
advancedSection.calculateDerivedPath()
}
}
else {
calculateDerivedPath()
advancedSection.calculateDerivedPath()
}
}
@ -90,11 +91,11 @@ ColumnLayout {
if(addAccountType === Constants.AddAccountType.ImportSeedPhrase) {
if(importSeedPhrasePanel.isValid) {
calculateDerivedPath()
advancedSection.calculateDerivedPath()
}
}
else {
calculateDerivedPath()
advancedSection.calculateDerivedPath()
}
}
@ -146,35 +147,50 @@ ColumnLayout {
]
}
RowLayout {
ColumnLayout {
Layout.preferredWidth: parent.width
Layout.rightMargin: 2
spacing: Style.current.bigPadding
visible: advancedSection.addAccountType !== Constants.AddAccountType.ImportPrivateKey &&
advancedSection.addAccountType !== Constants.AddAccountType.WatchOnly
spacing: 0
readonly property int itemWidth: (advancedSection.width - Style.current.bigPadding) * 0.5
RowLayout {
Layout.preferredWidth: advancedSection.width
Layout.rightMargin: 2
spacing: Style.current.bigPadding
visible: advancedSection.addAccountType !== Constants.AddAccountType.ImportPrivateKey &&
advancedSection.addAccountType !== Constants.AddAccountType.WatchOnly
DerivationPathsPanel {
id: derivationPathsPanel
Layout.preferredWidth: parent.itemWidth
Layout.alignment: Qt.AlignTop
Component.onCompleted: advancedSection.path = Qt.binding(function() { return derivationPathsPanel.path})
readonly property int itemWidth: (advancedSection.width - Style.current.bigPadding) * 0.5
DerivationPathsPanel {
id: derivationPathsPanel
useFullyCustomPath: fullyCustomPathCheckBox.checked
Layout.preferredWidth: parent.itemWidth
Layout.alignment: Qt.AlignTop
Component.onCompleted: advancedSection.path = Qt.binding(function() { return derivationPathsPanel.path})
}
DerivedAddressesPanel {
id: derivedAddressesPanel
Layout.preferredWidth: parent.itemWidth
Layout.alignment: Qt.AlignTop
selectedAccountType: advancedSection.addAccountType
selectedKeyUid: advancedSection.selectedKeyUid
selectedKeyUidMigratedToKeycard: advancedSection.selectedKeyUidMigratedToKeycard
selectedPath: advancedSection.path
Component.onCompleted: {
advancedSection.selectedAddress = Qt.binding(function() { return derivedAddressesPanel.selectedAddress})
advancedSection.selectedAddressAvailable = Qt.binding(function() { return derivedAddressesPanel.selectedAddressAvailable})
advancedSection.pathSubFix = Qt.binding(function() { return derivedAddressesPanel.pathSubFix})
}
}
}
DerivedAddressesPanel {
id: derivedAddressesPanel
Layout.preferredWidth: parent.itemWidth
Layout.alignment: Qt.AlignTop
selectedAccountType: advancedSection.addAccountType
selectedKeyUid: advancedSection.selectedKeyUid
selectedKeyUidMigratedToKeycard: advancedSection.selectedKeyUidMigratedToKeycard
selectedPath: advancedSection.path
Component.onCompleted: {
advancedSection.selectedAddress = Qt.binding(function() { return derivedAddressesPanel.selectedAddress})
advancedSection.selectedAddressAvailable = Qt.binding(function() { return derivedAddressesPanel.selectedAddressAvailable})
advancedSection.pathSubFix = Qt.binding(function() { return derivedAddressesPanel.pathSubFix})
StatusCheckBox {
id: fullyCustomPathCheckBox
Layout.preferredWidth: advancedSection.width
text: qsTr("I acknowledge that by adding an account out of the default Status derivation path I will not be able to migrate a keypair to a Keycard")
onToggled: {
advancedSection.reset()
}
}
}

View File

@ -17,6 +17,7 @@ StatusListItem {
property bool usedAsSelectOption: false
property bool tagClickable: false
property bool tagDisplayRemoveAccountButton: false
property bool canBeSelected: true
property int keyPairType: Constants.keycard.keyPairType.unknown
property string keyPairPubKey: ""
@ -46,6 +47,10 @@ StatusListItem {
}
return t
}
tertiaryTitle: !root.canBeSelected?
qsTr("This Keypair contains an account which is created out of the Status wallet derivation tree") :
""
statusListItemTertiaryTitle.color: Theme.palette.dangerColor1
asset {
width: root.keyPairIcon? 24 : 40
@ -98,29 +103,31 @@ StatusListItem {
}
}
components: [
StatusRadioButton {
id: radioButton
visible: root.usedAsSelectOption
ButtonGroup.group: root.buttonGroup
onCheckedChanged: {
if (!root.usedAsSelectOption)
return
if (checked) {
root.sharedKeycardModule.setSelectedKeyPair(root.keyPairPubKey)
root.keyPairSelected()
}
}
}
]
components: root.canBeSelected? d.components : []
QtObject {
id: d
property string myPublicKey: userProfile.pubKey
property list<Item> components: [
StatusRadioButton {
id: radioButton
visible: root.usedAsSelectOption
ButtonGroup.group: root.buttonGroup
onCheckedChanged: {
if (!root.usedAsSelectOption || !root.canBeSelected)
return
if (checked) {
root.sharedKeycardModule.setSelectedKeyPair(root.keyPairPubKey)
root.keyPairSelected()
}
}
}
]
}
onClicked: {
if (!root.usedAsSelectOption)
if (!root.usedAsSelectOption || !root.canBeSelected)
return
radioButton.checked = true
}

View File

@ -40,6 +40,7 @@ Item {
sharedKeycardModule: root.sharedKeycardModule
buttonGroup: root.buttonGroup
usedAsSelectOption: true
canBeSelected: !model.keyPair.containsPathOutOfTheDefaultStatusDerivationTree()
keyPairType: model.keyPair.pairType
keyPairPubKey: model.keyPair.pubKey