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:
parent
2c809a56e6
commit
a12e599be2
|
@ -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)
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue