fix(savedaddresses): preferred chains maintained in two places

This commit prevents the user from adding an address to the saved addresses list,
if it was already added to the Wallet section. Also when the user is about to add
an address to the Wallet section, which is already added to the saved addresses
list, the app will ask whether to proceed with that action by removing the related
saved address or cancel the action.

Closes: #13109
This commit is contained in:
Sale Djenic 2024-01-23 09:15:07 +01:00 committed by saledjenic
parent 18303ef49c
commit 654da3e246
8 changed files with 140 additions and 34 deletions

View File

@ -78,6 +78,7 @@ type
keycardService: keycard_service.Service keycardService: keycard_service.Service
accountsService: accounts_service.Service accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service walletAccountService: wallet_account_service.Service
savedAddressService: saved_address_service.Service
devicesService: devices_service.Service devicesService: devices_service.Service
activityController: activityc.Controller activityController: activityc.Controller
@ -114,6 +115,7 @@ proc newModule*(
result.keycardService = keycardService result.keycardService = keycardService
result.accountsService = accountsService result.accountsService = accountsService
result.walletAccountService = walletAccountService result.walletAccountService = walletAccountService
result.savedAddressService = savedAddressService
result.devicesService = devicesService result.devicesService = devicesService
result.moduleLoaded = false result.moduleLoaded = false
result.controller = newController(result, settingsService, walletAccountService, currencyService, networkService) result.controller = newController(result, settingsService, walletAccountService, currencyService, networkService)
@ -375,13 +377,13 @@ method destroyAddAccountPopup*(self: Module) =
method runAddAccountPopup*(self: Module, addingWatchOnlyAccount: bool) = method runAddAccountPopup*(self: Module, addingWatchOnlyAccount: bool) =
self.destroyAddAccountPopup() self.destroyAddAccountPopup()
self.addAccountModule = add_account_module.newModule(self, self.events, self.keycardService, self.accountsService, self.addAccountModule = add_account_module.newModule(self, self.events, self.keycardService, self.accountsService,
self.walletAccountService) self.walletAccountService, self.savedAddressService)
self.addAccountModule.loadForAddingAccount(addingWatchOnlyAccount) self.addAccountModule.loadForAddingAccount(addingWatchOnlyAccount)
method runEditAccountPopup*(self: Module, address: string) = method runEditAccountPopup*(self: Module, address: string) =
self.destroyAddAccountPopup() self.destroyAddAccountPopup()
self.addAccountModule = add_account_module.newModule(self, self.events, self.keycardService, self.accountsService, self.addAccountModule = add_account_module.newModule(self, self.events, self.keycardService, self.accountsService,
self.walletAccountService) self.walletAccountService, self.savedAddressService)
self.addAccountModule.loadForEditingAccount(address) self.addAccountModule.loadForEditingAccount(address)
method getAddAccountModule*(self: Module): QVariant = method getAddAccountModule*(self: Module): QVariant =

View File

@ -2,13 +2,14 @@ import times, chronicles
import uuids import uuids
import io_interface import io_interface
import ../../../../app_service/service/accounts/service as accounts_service import app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/wallet_account/service as wallet_account_service import app_service/service/wallet_account/service as wallet_account_service
import ../../../../app_service/service/keycard/service as keycard_service import app_service/service/saved_address/service as saved_address_service
import app_service/service/keycard/service as keycard_service
import ../keycard_popup/io_interface as keycard_shared_module import ../keycard_popup/io_interface as keycard_shared_module
import ../../../core/eventemitter import app/core/eventemitter
logScope: logScope:
topics = "wallet-add-account-controller" topics = "wallet-add-account-controller"
@ -21,6 +22,7 @@ type
events: EventEmitter events: EventEmitter
accountsService: accounts_service.Service accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service walletAccountService: wallet_account_service.Service
savedAddressService: saved_address_service.Service
keycardService: keycard_service.Service keycardService: keycard_service.Service
connectionIds: seq[UUID] connectionIds: seq[UUID]
connectionKeycardResponse: UUID connectionKeycardResponse: UUID
@ -40,6 +42,7 @@ proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter, events: EventEmitter,
accountsService: accounts_service.Service, accountsService: accounts_service.Service,
walletAccountService: wallet_account_service.Service, walletAccountService: wallet_account_service.Service,
savedAddressService: saved_address_service.Service,
keycardService: keycard_service.Service): keycardService: keycard_service.Service):
Controller = Controller =
result = Controller() result = Controller()
@ -47,6 +50,7 @@ proc newController*(delegate: io_interface.AccessInterface,
result.events = events result.events = events
result.accountsService = accountsService result.accountsService = accountsService
result.walletAccountService = walletAccountService result.walletAccountService = walletAccountService
result.savedAddressService = savedAddressService
result.keycardService = keycardService result.keycardService = keycardService
proc disconnectAll*(self: Controller) = proc disconnectAll*(self: Controller) =
@ -66,27 +70,32 @@ proc init*(self: Controller) =
self.connectionIds.add(handlerId) self.connectionIds.add(handlerId)
handlerId = self.events.onWithUUID(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FETCHED) do(e:Args): handlerId = self.events.onWithUUID(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FETCHED) do(e:Args):
var args = DerivedAddressesArgs(e) let args = DerivedAddressesArgs(e)
self.delegate.onDerivedAddressesFetched(args.derivedAddresses, args.error) self.delegate.onDerivedAddressesFetched(args.derivedAddresses, args.error)
self.connectionIds.add(handlerId) self.connectionIds.add(handlerId)
handlerId = self.events.onWithUUID(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FROM_MNEMONIC_FETCHED) do(e:Args): handlerId = self.events.onWithUUID(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FROM_MNEMONIC_FETCHED) do(e:Args):
var args = DerivedAddressesArgs(e) let args = DerivedAddressesArgs(e)
self.delegate.onDerivedAddressesFromMnemonicFetched(args.derivedAddresses, args.error) self.delegate.onDerivedAddressesFromMnemonicFetched(args.derivedAddresses, args.error)
self.connectionIds.add(handlerId) self.connectionIds.add(handlerId)
handlerId = self.events.onWithUUID(SIGNAL_DERIVED_ADDRESSES_FROM_NOT_IMPORTED_MNEMONIC_FETCHED) do(e:Args): handlerId = self.events.onWithUUID(SIGNAL_DERIVED_ADDRESSES_FROM_NOT_IMPORTED_MNEMONIC_FETCHED) do(e:Args):
var args = DerivedAddressesFromNotImportedMnemonicArgs(e) let args = DerivedAddressesFromNotImportedMnemonicArgs(e)
self.delegate.onAddressesFromNotImportedMnemonicFetched(args.derivations, args.error) self.delegate.onAddressesFromNotImportedMnemonicFetched(args.derivations, args.error)
self.connectionIds.add(handlerId) self.connectionIds.add(handlerId)
handlerId = self.events.onWithUUID(SIGNAL_WALLET_ACCOUNT_ADDRESS_DETAILS_FETCHED) do(e:Args): handlerId = self.events.onWithUUID(SIGNAL_WALLET_ACCOUNT_ADDRESS_DETAILS_FETCHED) do(e:Args):
var args = DerivedAddressesArgs(e) let args = DerivedAddressesArgs(e)
if args.uniqueId != self.uniqueFetchingDetailsId: if args.uniqueId != self.uniqueFetchingDetailsId:
return return
self.delegate.onAddressDetailsFetched(args.derivedAddresses, args.error) self.delegate.onAddressDetailsFetched(args.derivedAddresses, args.error)
self.connectionIds.add(handlerId) self.connectionIds.add(handlerId)
handlerId = self.events.onWithUUID(SIGNAL_SAVED_ADDRESS_DELETED) do(e:Args):
let args = SavedAddressArgs(e)
self.delegate.savedAddressDeleted(args.address, args.errorMsg)
self.connectionIds.add(handlerId)
proc setAuthenticatedKeyUid*(self: Controller, value: string) = proc setAuthenticatedKeyUid*(self: Controller, value: string) =
self.tmpAuthenticatedKeyUid = value self.tmpAuthenticatedKeyUid = value
@ -123,6 +132,12 @@ proc getKeypairs*(self: Controller): seq[KeypairDto] =
proc getKeypairByKeyUid*(self: Controller, keyUid: string): KeypairDto = proc getKeypairByKeyUid*(self: Controller, keyUid: string): KeypairDto =
return self.walletAccountService.getKeypairByKeyUid(keyUid) return self.walletAccountService.getKeypairByKeyUid(keyUid)
proc getSavedAddress*(self: Controller, address: string): SavedAddressDto =
return self.savedAddressService.getSavedAddress(address)
proc deleteSavedAddress*(self: Controller, address: string) =
self.savedAddressService.deleteSavedAddress(address)
proc finalizeAction*(self: Controller) = proc finalizeAction*(self: Controller) =
self.delegate.finalizeAction() self.delegate.finalizeAction()

View File

@ -101,6 +101,11 @@ method buildNewPrivateKeyKeypairAndAddItToOrigin*(self: AccessInterface) {.base.
method buildNewSeedPhraseKeypairAndAddItToOrigin*(self: AccessInterface) {.base.} = method buildNewSeedPhraseKeypairAndAddItToOrigin*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method removingSavedAddressConfirmed*(self: AccessInterface, address: string) {.base.} =
raise newException(ValueError, "No implementation available")
method savedAddressDeleted*(self: AccessInterface, address: string, errorMsg: string) {.base.} =
raise newException(ValueError, "No implementation available")
type type
DelegateInterface* = concept c DelegateInterface* = concept c

View File

@ -4,18 +4,19 @@ import io_interface
import view, controller import view, controller
import internal/[state, state_factory] import internal/[state, state_factory]
import ../../../core/eventemitter import app/core/eventemitter
import ../../../global/global_singleton import app/global/global_singleton
import ../../shared/keypairs import app/modules/shared/keypairs
import ../../shared_models/[keypair_model, derived_address_model] import app/modules/shared_models/[keypair_model, derived_address_model]
import ../../shared_modules/keycard_popup/module as keycard_shared_module import app/modules/shared_modules/keycard_popup/module as keycard_shared_module
import ../../../../app_service/common/account_constants import app_service/common/account_constants
import ../../../../app_service/service/accounts/service as accounts_service import app_service/service/accounts/service as accounts_service
import ../../../../app_service/service/wallet_account/service as wallet_account_service import app_service/service/wallet_account/service as wallet_account_service
import ../../../../app_service/service/keycard/service as keycard_service import app_service/service/saved_address/service as saved_address_service
import app_service/service/keycard/service as keycard_service
export io_interface export io_interface
@ -46,8 +47,6 @@ type
view: View view: View
viewVariant: QVariant viewVariant: QVariant
controller: Controller controller: Controller
accountsService: accounts_service.Service
walletAccountService: wallet_account_service.Service
authenticationReason: AuthenticationReason authenticationReason: AuthenticationReason
fetchingAddressesIsInProgress: bool fetchingAddressesIsInProgress: bool
@ -58,15 +57,16 @@ proc newModule*[T](delegate: T,
events: EventEmitter, events: EventEmitter,
keycardService: keycard_service.Service, keycardService: keycard_service.Service,
accountsService: accounts_service.Service, accountsService: accounts_service.Service,
walletAccountService: wallet_account_service.Service): walletAccountService: wallet_account_service.Service,
savedAddressService: saved_address_service.Service):
Module[T] = Module[T] =
result = Module[T]() result = Module[T]()
result.delegate = delegate result.delegate = delegate
result.events = events result.events = events
result.walletAccountService = walletAccountService
result.view = newView(result) result.view = newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, accountsService, walletAccountService, keycardService) result.controller = controller.newController(result, events, accountsService, walletAccountService,
savedAddressService, keycardService)
result.authenticationReason = AuthenticationReason.AddingAccount result.authenticationReason = AuthenticationReason.AddingAccount
result.fetchingAddressesIsInProgress = false result.fetchingAddressesIsInProgress = false
@ -629,6 +629,11 @@ proc doAddAccount[T](self: Module[T]) =
publicKey = "" publicKey = ""
keyUid = "" keyUid = ""
let savedAddressDto = self.controller.getSavedAddress(address)
if not savedAddressDto.isNil:
self.view.sendConfirmSavedAddressRemovalSignal(savedAddressDto.name, savedAddressDto.address)
return
var success = false var success = false
if addingNewKeyPair: if addingNewKeyPair:
if selectedOrigin.getPairType() == KeyPairType.PrivateKeyImport.int: if selectedOrigin.getPairType() == KeyPairType.PrivateKeyImport.int:
@ -689,6 +694,15 @@ proc doAddAccount[T](self: Module[T]) =
self.closeAddAccountPopup() self.closeAddAccountPopup()
method removingSavedAddressConfirmed[T](self: Module[T], address: string) =
self.controller.deleteSavedAddress(address)
method savedAddressDeleted*[T](self: Module[T], address: string, errorMsg: string) =
if errorMsg.len > 0:
error "failed to delete saved address", address=address, err=errorMsg
return
self.doAddAccount()
proc doEditAccount[T](self: Module[T]) = proc doEditAccount[T](self: Module[T]) =
self.view.setDisablePopup(true) self.view.setDisablePopup(true)
let selectedOrigin = self.view.getSelectedOrigin() let selectedOrigin = self.view.getSelectedOrigin()

View File

@ -354,3 +354,12 @@ QtObject:
proc startScanningForActivity*(self: View) {.slot.} = proc startScanningForActivity*(self: View) {.slot.} =
self.delegate.startScanningForActivity() self.delegate.startScanningForActivity()
proc confirmSavedAddressRemoval*(self: View, name: string, address: string) {.signal.}
proc sendConfirmSavedAddressRemovalSignal*(self: View, name: string, address: string) =
self.confirmSavedAddressRemoval(name, address)
proc removingSavedAddressConfirmed*(self: View, address: string) {.slot.} =
self.delegate.removingSavedAddressConfirmed(address)
proc removingSavedAddressRejected*(self: View) {.slot.} =
self.setDisablePopup(false)

View File

@ -1,4 +1,4 @@
import NimQml, chronicles, sequtils, json import NimQml, chronicles, strutils, sequtils, json
import dto import dto
@ -73,6 +73,11 @@ QtObject:
proc getSavedAddresses*(self: Service): seq[SavedAddressDto] = proc getSavedAddresses*(self: Service): seq[SavedAddressDto] =
return self.savedAddresses return self.savedAddresses
proc getSavedAddress*(self: Service, address: string): SavedAddressDto =
for sa in self.savedAddresses:
if cmpIgnoreCase(sa.address, address) == 0:
return sa
proc updateAddresses(self: Service, signal: string, arg: Args) = proc updateAddresses(self: Service, signal: string, arg: Args) =
self.savedAddresses = self.getAddresses() self.savedAddresses = self.getAddresses()
self.events.emit(signal, arg) self.events.emit(signal, arg)

View File

@ -99,11 +99,16 @@ StatusModal {
readonly property var chainPrefixRegexPattern: /[^:]+\:?|:/g readonly property var chainPrefixRegexPattern: /[^:]+\:?|:/g
readonly property bool addressInputIsENS: !!d.ens readonly property bool addressInputIsENS: !!d.ens
property bool addressAlreadyAdded: false property bool addressAlreadyAddedToWallet: false
function checkIfAddressIsAlreadyAddded(address) { function checkIfAddressIsAlreadyAdddedToWallet(address) {
let name = RootStore.getNameForWalletAddress(address)
d.addressAlreadyAddedToWallet = !!name
}
property bool addressAlreadyAddedToSavedAddresses: false
function checkIfAddressIsAlreadyAdddedToSavedAddresses(address) {
let details = RootStore.getSavedAddress(address) let details = RootStore.getSavedAddress(address)
d.addressAlreadyAdded = !!details.address d.addressAlreadyAddedToSavedAddresses = !!details.address
return !d.addressAlreadyAdded
} }
/// Ensures that the \c root.address and \c root.chainShortNames are not reset when the initial text is set /// Ensures that the \c root.address and \c root.chainShortNames are not reset when the initial text is set
@ -220,7 +225,8 @@ StatusModal {
errorMessage: qsTr("Please enter an ethereum address") errorMessage: qsTr("Please enter an ethereum address")
}, },
StatusValidator { StatusValidator {
errorMessage: d.addressAlreadyAdded? qsTr("This address is already saved") : qsTr("Ethereum address invalid") errorMessage: d.addressAlreadyAddedToWallet? qsTr("This address is already added to Wallet") :
d.addressAlreadyAddedToSavedAddresses? qsTr("This address is already saved") : qsTr("Ethereum address invalid")
validate: function (value) { validate: function (value) {
if (value !== Constants.zeroAddress) { if (value !== Constants.zeroAddress) {
if (Utils.isValidEns(value)) { if (Utils.isValidEns(value)) {
@ -231,7 +237,12 @@ StatusModal {
return true return true
} }
const prefixAndAddress = Utils.splitToChainPrefixAndAddress(value) const prefixAndAddress = Utils.splitToChainPrefixAndAddress(value)
return d.checkIfAddressIsAlreadyAddded(prefixAndAddress.address) d.checkIfAddressIsAlreadyAdddedToWallet(prefixAndAddress.address)
if (d.addressAlreadyAddedToWallet) {
return false
}
d.checkIfAddressIsAlreadyAdddedToSavedAddresses(prefixAndAddress.address)
return !d.addressAlreadyAddedToSavedAddresses
} }
} }
@ -243,7 +254,8 @@ StatusModal {
StatusAsyncValidator { StatusAsyncValidator {
id: resolvingEnsName id: resolvingEnsName
name: "resolving-ens-name" name: "resolving-ens-name"
errorMessage: d.addressAlreadyAdded? qsTr("This address is already saved") : qsTr("Ethereum address invalid") errorMessage: d.addressAlreadyAddedToWallet? qsTr("This address is already added to Wallet") :
d.addressAlreadyAddedToSavedAddresses? qsTr("This address is already saved") : qsTr("Ethereum address invalid")
asyncOperation: (value) => { asyncOperation: (value) => {
if (!Utils.isValidEns(value)) { if (!Utils.isValidEns(value)) {
resolvingEnsName.asyncComplete("not-ens") resolvingEnsName.asyncComplete("not-ens")
@ -257,7 +269,12 @@ StatusModal {
return true return true
} }
if (!!value) { if (!!value) {
return d.checkIfAddressIsAlreadyAddded(value) d.checkIfAddressIsAlreadyAdddedToWallet(prefixAndAddress.address)
if (d.addressAlreadyAddedToWallet) {
return false
}
d.checkIfAddressIsAlreadyAdddedToSavedAddresses(value)
return !d.addressAlreadyAddedToSavedAddresses
} }
return false return false
} }
@ -290,7 +307,7 @@ StatusModal {
if (skipTextUpdate || !d.initialized) if (skipTextUpdate || !d.initialized)
return return
d.addressAlreadyAdded = false d.addressAlreadyAddedToSavedAddresses = false
plainText = input.edit.getText(0, text.length) plainText = input.edit.getText(0, text.length)
if (input.edit.previousText != plainText) { if (input.edit.previousText != plainText) {

View File

@ -6,6 +6,7 @@ import StatusQ.Popups 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import utils 1.0 import utils 1.0
import shared.popups 1.0
import "./stores" import "./stores"
import "./states" import "./states"
@ -31,6 +32,13 @@ StatusModal {
root.store.currentState.doCancelAction() root.store.currentState.doCancelAction()
} }
Connections {
target: root.store.addAccountModule
function onConfirmSavedAddressRemoval(name, address) {
Global.openPopup(confirmSavedAddressRemoval, {address: address, name: name})
}
}
StatusScrollView { StatusScrollView {
id: scrollView id: scrollView
@ -154,6 +162,37 @@ StatusModal {
} }
} }
} }
Component {
id: confirmSavedAddressRemoval
ConfirmationDialog {
property string name
property string address
closePolicy: Popup.NoAutoClose
hasCloseButton: false
headerSettings.title: qsTr("Removing saved address")
confirmationText: qsTr("The account you're trying to add <b>%1</b> is already saved under the name <b>%2</b>.<br/><br/>Do you want to remove it from saved addresses in favour of adding it to the Wallet?")
.arg(address)
.arg(name)
showCancelButton: true
cancelBtnType: ""
confirmButtonLabel: qsTr("Yes")
cancelButtonLabel: qsTr("No")
onConfirmButtonClicked: {
root.store.addAccountModule.removingSavedAddressConfirmed(address)
close()
}
onCancelButtonClicked: {
root.store.addAccountModule.removingSavedAddressRejected()
close()
}
}
}
} }
leftButtons: [ leftButtons: [