feat(@desktop/wallet): Add support for custom derivations path

Make getDerivedAddresses call async
Show pending and error states when gettting the derived addresses

fixes #5700, #5699
This commit is contained in:
Khushboo Mehta 2022-05-12 17:24:03 +02:00 committed by Khushboo-dev-cpp
parent b601c88447
commit 86cfbec8f9
17 changed files with 674 additions and 392 deletions

View File

@ -138,8 +138,8 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
)
result.collectibleService = collectible_service.newService(result.settingsService)
result.walletAccountService = wallet_account_service.newService(
statusFoundation.events, result.settingsService, result.accountsService, result.tokenService,
result.networkService,
statusFoundation.events, statusFoundation.threadpool, result.settingsService, result.accountsService,
result.tokenService, result.networkService,
)
result.messageService = message_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.contactsService, result.tokenService, result.walletAccountService, result.networkService

View File

@ -38,10 +38,13 @@ proc addWatchOnlyAccount*(self: Controller, address: string, accountName: string
proc deleteAccount*(self: Controller, address: string) =
self.walletAccountService.deleteAccount(address)
method getDerivedAddressList*(self: Controller, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) =
return self.walletAccountService.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber)
method getDerivedAddressList*(self: Controller, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int)=
self.walletAccountService.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber)
method getDerivedAddressListForMnemonic*(self: Controller, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) =
return self.walletAccountService.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
method getDerivedAddressListForMnemonic*(self: Controller, mnemonic: string, path: string, pageSize: int, pageNumber: int) =
self.walletAccountService.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
method getDerivedAddressForPrivateKey*(self: Controller, privateKey: string) =
self.walletAccountService.getDerivedAddressForPrivateKey(privateKey)

View File

@ -31,10 +31,13 @@ method deleteAccount*(self: AccessInterface, address: string) {.base.} =
method refreshWalletAccounts*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getDerivedAddressList*(self: AccessInterface, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) {.base.} =
method getDerivedAddressList*(self: AccessInterface, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int) {.base.} =
raise newException(ValueError, "No implementation available")
method getDerivedAddressListForMnemonic*(self: AccessInterface, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) {.base.} =
method getDerivedAddressListForMnemonic*(self: AccessInterface, mnemonic: string, path: string, pageSize: int, pageNumber: int) {.base.} =
raise newException(ValueError, "No implementation available")
method getDerivedAddressForPrivateKey*(self: AccessInterface, privateKey: string) {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface

View File

@ -91,6 +91,10 @@ method load*(self: Module) =
self.events.on(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED) do(e:Args):
self.refreshWalletAccounts()
self.events.on(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY) do(e:Args):
var args = DerivedAddressesArgs(e)
self.view.setDerivedAddresses(args.derivedAddresses, args.error)
self.controller.init()
self.view.load()
@ -117,11 +121,15 @@ method addWatchOnlyAccount*(self: Module, address: string, accountName: string,
method deleteAccount*(self: Module, address: string) =
self.controller.deleteAccount(address)
method getDerivedAddressList*(self: Module, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[DerivedAddressDto], string) =
return self.controller.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber)
method getDerivedAddressList*(self: Module, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int) =
self.controller.getDerivedAddressList(password, derivedFrom, path, pageSize, pageNumber)
method getDerivedAddressListForMnemonic*(self: Module, mnemonic: string, path: string, pageSize: int, pageNumber: int) =
self.controller.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
method getDerivedAddressForPrivateKey*(self: Module, privateKey: string) =
self.controller.getDerivedAddressForPrivateKey(privateKey)
method getDerivedAddressListForMnemonic*(self: Module, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[wallet_account_service.DerivedAddressDto], string) =
return self.controller.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)

View File

@ -1,5 +1,7 @@
import NimQml, sequtils, strutils, sugar
import ../../../../../app_service/service/wallet_account/service as wallet_account_service
import ./model
import ./item
import ./io_interface
@ -23,6 +25,8 @@ QtObject:
imported: Model
generatedAccounts: GeneratedWalletModel
derivedAddresses: DerivedAddressModel
derivedAddressesLoading: bool
derivedAddressesError: string
modelVariant: QVariant
generatedVariant: QVariant
importedVariant: QVariant
@ -61,6 +65,8 @@ QtObject:
result.generatedAccounts = newGeneratedWalletModel()
result.generatedAccountsVariant = newQVariant(result.generatedAccounts)
result.derivedAddresses = newDerivedAddressModel()
result.derivedAddressesLoading = false
result.derivedAddressesError = ""
result.derivedAddressesVariant = newQVariant(result.derivedAddresses)
proc load*(self: View) =
@ -120,6 +126,35 @@ QtObject:
read = getDerivedAddresses
notify = derivedAddressesChanged
proc derivedAddressesLoadingChanged*(self: View) {.signal.}
proc getDerivedAddressesLoading(self: View): bool {.slot.} =
return self.derivedAddressesLoading
proc setDerivedAddressesLoading*(self: View, loading: bool) {.slot.} =
if self.derivedAddressesLoading != loading:
self.derivedAddressesLoading = loading
self.derivedAddressesLoadingChanged()
QtProperty[bool] derivedAddressesLoading:
read = getDerivedAddressesLoading
write = setDerivedAddressesLoading
notify = derivedAddressesLoadingChanged
proc derivedAddressErrorChanged*(self: View) {.signal.}
proc getDerivedAddressesError(self: View): string {.slot.} =
return self.derivedAddressesError
proc setDerivedAddressesError(self: View, error: string) {.slot.} =
self.derivedAddressesError = error
self.derivedAddressErrorChanged()
QtProperty[string] derivedAddressesError:
read = getDerivedAddressesError
write = setDerivedAddressesError
notify = derivedAddressErrorChanged
proc setItems*(self: View, items: seq[Item]) =
self.model.setItems(items)
@ -184,23 +219,29 @@ QtObject:
proc getAccountAssetsByAddress*(self: View): QVariant {.slot.} =
return self.model.getAccountAssetsByAddress(self.tmpAddress)
proc getDerivedAddressList*(self: View, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): string {.slot.} =
proc setDerivedAddresses*(self: View, derivedAddresses: seq[wallet_account_service.DerivedAddressDto], error: string) =
var items: seq[DerivedAddressItem] = @[]
let (result, error) = self.delegate.getDerivedAddressList(password, derivedfrom, path, pageSize, pageNumber)
for item in result:
for item in derivedAddresses:
items.add(initDerivedAddressItem(item.address, item.path, item.hasActivity, item.alreadyCreated))
self.derivedAddresses.setItems(items)
self.setDerivedAddressesError(error)
self.setDerivedAddressesLoading(false)
self.derivedAddressesChanged()
return error
proc getDerivedAddressListForMnemonic*(self: View, mnemonic: string, path: string, pageSize: int, pageNumber: int): string {.slot.} =
var items: seq[DerivedAddressItem] = @[]
let (result, error) = self.delegate.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
for item in result:
items.add(initDerivedAddressItem(item.address, item.path, item.hasActivity, item.alreadyCreated))
self.derivedAddresses.setItems(items)
self.derivedAddressesChanged()
return error
proc getDerivedAddressList*(self: View, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int) {.slot.} =
self.setDerivedAddressesLoading(true)
self.setDerivedAddressesError("")
self.delegate.getDerivedAddressList(password, derivedfrom, path, pageSize, pageNumber)
proc getDerivedAddressListForMnemonic*(self: View, mnemonic: string, path: string, pageSize: int, pageNumber: int) {.slot.} =
self.setDerivedAddressesLoading(true)
self.setDerivedAddressesError("")
self.delegate.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
proc getDerivedAddressForPrivateKey*(self: View, privateKey: string) {.slot.} =
self.setDerivedAddressesLoading(true)
self.setDerivedAddressesError("")
self.delegate.getDerivedAddressForPrivateKey(privateKey)
proc resetDerivedAddressModel*(self: View) {.slot.} =
var items: seq[DerivedAddressItem] = @[]

View File

@ -0,0 +1,74 @@
#################################################
# Async load derivedAddreses
#################################################
type
GetDerivedAddressesTaskArg* = ref object of QObjectTaskArg
password: string
derivedFrom: string
path: string
pageSize: int
pageNumber: int
const getDerivedAddressesTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[GetDerivedAddressesTaskArg](argEncoded)
try:
let response = status_go_accounts.getDerivedAddressList(arg.password, arg.derivedFrom, arg.path, arg.pageSize, arg.pageNumber)
let output = %*{
"derivedAddresses": response.result,
"error": ""
}
arg.finish(output)
except Exception as e:
let output = %* {
"derivedAddresses": "",
"error": fmt"Error getting derived address list: {e.msg}"
}
arg.finish(output)
type
GetDerivedAddressesForMnemonicTaskArg* = ref object of QObjectTaskArg
mnemonic: string
path: string
pageSize: int
pageNumber: int
const getDerivedAddressesForMnemonicTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[GetDerivedAddressesForMnemonicTaskArg](argEncoded)
try:
let response = status_go_accounts.getDerivedAddressListForMnemonic(arg.mnemonic, arg.path, arg.pageSize, arg.pageNumber)
let output = %*{
"derivedAddresses": response.result,
"error": ""
}
arg.finish(output)
except Exception as e:
let output = %* {
"derivedAddresses": "",
"error": fmt"Error getting derived address list for mnemonic: {e.msg}"
}
arg.finish(output)
type
GetDerivedAddressForPrivateKeyTaskArg* = ref object of QObjectTaskArg
privateKey: string
const getDerivedAddressForPrivateKeyTask*: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[GetDerivedAddressForPrivateKeyTaskArg](argEncoded)
try:
let response = status_go_accounts.getDerivedAddressForPrivateKey(arg.privateKey)
let output = %*{
"derivedAddresses": response.result,
"error": ""
}
arg.finish(output)
except Exception as e:
let output = %* {
"derivedAddresses": "",
"error": fmt"Error getting derived address list for private key: {e.msg}"
}
arg.finish(output)

View File

@ -1,4 +1,4 @@
import Tables, json, sequtils, sugar, chronicles, strformat, stint, httpclient, net, strutils
import Tables, NimQml, json, sequtils, sugar, chronicles, strformat, stint, httpclient, net, strutils
import web3/[ethtypes, conversions]
import ../settings/service as settings_service
@ -11,6 +11,7 @@ import dto
import derived_address
import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool]
import ../../../backend/accounts as status_go_accounts
import ../../../backend/backend as backend
import ../../../backend/eth as status_go_eth
@ -22,12 +23,15 @@ export derived_address
logScope:
topics = "wallet-account-service"
include async_tasks
const SIGNAL_WALLET_ACCOUNT_SAVED* = "walletAccount/accountSaved"
const SIGNAL_WALLET_ACCOUNT_DELETED* = "walletAccount/accountDeleted"
const SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED* = "walletAccount/currencyUpdated"
const SIGNAL_WALLET_ACCOUNT_TOKEN_VISIBILITY_UPDATED* = "walletAccount/tokenVisibilityUpdated"
const SIGNAL_WALLET_ACCOUNT_UPDATED* = "walletAccount/walletAccountUpdated"
const SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED* = "walletAccount/networkEnabledUpdated"
const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY* = "walletAccount/derivedAddressesReady"
var
balanceCache {.threadvar.}: Table[string, float64]
@ -80,298 +84,332 @@ type NetwordkEnabledToggled = ref object of Args
type WalletAccountUpdated = ref object of Args
account: WalletAccountDto
type
Service* = ref object of RootObj
events: EventEmitter
settingsService: settings_service.Service
accountsService: accounts_service.Service
tokenService: token_service.Service
networkService: network_service.Service
accounts: OrderedTable[string, WalletAccountDto]
type DerivedAddressesArgs* = ref object of Args
derivedAddresses*: seq[DerivedAddressDto]
error*: string
priceCache: TimedCache
QtObject:
type Service* = ref object of QObject
events: EventEmitter
threadpool: ThreadPool
settingsService: settings_service.Service
accountsService: accounts_service.Service
tokenService: token_service.Service
networkService: network_service.Service
accounts: OrderedTable[string, WalletAccountDto]
priceCache: TimedCache
proc delete*(self: Service) =
discard
proc delete*(self: Service) =
self.QObject.delete
proc newService*(
events: EventEmitter,
settingsService: settings_service.Service,
accountsService: accounts_service.Service,
tokenService: token_service.Service,
networkService: network_service.Service,
): Service =
result = Service()
result.events = events
result.settingsService = settingsService
result.accountsService = accountsService
result.tokenService = tokenService
result.networkService = networkService
result.accounts = initOrderedTable[string, WalletAccountDto]()
result.priceCache = newTimedCache()
proc newService*(
events: EventEmitter,
threadpool: ThreadPool,
settingsService: settings_service.Service,
accountsService: accounts_service.Service,
tokenService: token_service.Service,
networkService: network_service.Service,
): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.settingsService = settingsService
result.accountsService = accountsService
result.tokenService = tokenService
result.networkService = networkService
result.accounts = initOrderedTable[string, WalletAccountDto]()
result.priceCache = newTimedCache()
proc buildTokens(
self: Service,
account: WalletAccountDto,
prices: Table[string, float],
balances: JsonNode
): seq[WalletTokenDto] =
for network in self.networkService.getEnabledNetworks():
let balance = fetchNativeChainBalance(network, account.address)
result.add(WalletTokenDto(
name: network.chainName,
address: "0x0000000000000000000000000000000000000000",
symbol: network.nativeCurrencySymbol,
decimals: network.nativeCurrencyDecimals,
hasIcon: true,
color: "blue",
isCustom: false,
balance: balance,
currencyBalance: balance * prices[network.nativeCurrencySymbol]
))
for token in self.tokenService.getVisibleTokens():
let balance = parsefloat(hex2Balance(balances{token.addressAsString()}.getStr, token.decimals))
result.add(
WalletTokenDto(
name: token.name,
address: $token.address,
symbol: token.symbol,
decimals: token.decimals,
hasIcon: token.hasIcon,
color: token.color,
isCustom: token.isCustom,
proc buildTokens(
self: Service,
account: WalletAccountDto,
prices: Table[string, float],
balances: JsonNode
): seq[WalletTokenDto] =
for network in self.networkService.getEnabledNetworks():
let balance = fetchNativeChainBalance(network, account.address)
result.add(WalletTokenDto(
name: network.chainName,
address: "0x0000000000000000000000000000000000000000",
symbol: network.nativeCurrencySymbol,
decimals: network.nativeCurrencyDecimals,
hasIcon: true,
color: "blue",
isCustom: false,
balance: balance,
currencyBalance: balance * prices[token.symbol]
currencyBalance: balance * prices[network.nativeCurrencySymbol]
))
for token in self.tokenService.getVisibleTokens():
let balance = parsefloat(hex2Balance(balances{token.addressAsString()}.getStr, token.decimals))
result.add(
WalletTokenDto(
name: token.name,
address: $token.address,
symbol: token.symbol,
decimals: token.decimals,
hasIcon: token.hasIcon,
color: token.color,
isCustom: token.isCustom,
balance: balance,
currencyBalance: balance * prices[token.symbol]
)
)
)
proc getPrice*(self: Service, crypto: string, fiat: string): float64 =
let cacheKey = crypto & fiat
if self.priceCache.isCached(cacheKey):
return parseFloat(self.priceCache.get(cacheKey))
var prices = initTable[string, float]()
proc getPrice*(self: Service, crypto: string, fiat: string): float64 =
let cacheKey = crypto & fiat
if self.priceCache.isCached(cacheKey):
return parseFloat(self.priceCache.get(cacheKey))
var prices = initTable[string, float]()
try:
let response = backend.fetchPrices(@[crypto], fiat)
for (symbol, value) in response.result.pairs:
prices[symbol] = value.getFloat
self.priceCache.set(cacheKey, $value.getFloat)
try:
let response = backend.fetchPrices(@[crypto], fiat)
for (symbol, value) in response.result.pairs:
prices[symbol] = value.getFloat
self.priceCache.set(cacheKey, $value.getFloat)
return prices[crypto]
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
return 0.0
return prices[crypto]
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
return 0.0
proc fetchPrices(self: Service): Table[string, float] =
let currency = self.settingsService.getCurrency()
var symbols: seq[string] = @[]
proc fetchPrices(self: Service): Table[string, float] =
let currency = self.settingsService.getCurrency()
for network in self.networkService.getEnabledNetworks():
symbols.add(network.nativeCurrencySymbol)
var symbols: seq[string] = @[]
for token in self.tokenService.getVisibleTokens():
symbols.add(token.symbol)
for network in self.networkService.getEnabledNetworks():
symbols.add(network.nativeCurrencySymbol)
var prices = initTable[string, float]()
try:
let response = backend.fetchPrices(symbols, currency)
for (symbol, value) in response.result.pairs:
prices[symbol] = value.getFloat
for token in self.tokenService.getVisibleTokens():
symbols.add(token.symbol)
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
return prices
var prices = initTable[string, float]()
try:
let response = backend.fetchPrices(symbols, currency)
for (symbol, value) in response.result.pairs:
prices[symbol] = value.getFloat
proc fetchBalances(self: Service, accounts: seq[string]): JsonNode =
let visibleTokens = self.tokenService.getVisibleTokens()
let tokens = visibleTokens.map(t => t.addressAsString())
let chainIds = visibleTokens.map(t => t.chainId)
return backend.getTokensBalancesForChainIDs(chainIds, accounts, tokens).result
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
proc refreshBalances(self: Service) =
let prices = self.fetchPrices()
let accounts = toSeq(self.accounts.keys)
let balances = self.fetchBalances(accounts)
return prices
for account in toSeq(self.accounts.values):
account.tokens = self.buildTokens(account, prices, balances{account.address})
proc fetchBalances(self: Service, accounts: seq[string]): JsonNode =
let visibleTokens = self.tokenService.getVisibleTokens()
let tokens = visibleTokens.map(t => t.addressAsString())
let chainIds = visibleTokens.map(t => t.chainId)
return backend.getTokensBalancesForChainIDs(chainIds, accounts, tokens).result
proc init*(self: Service) =
try:
proc refreshBalances(self: Service) =
let prices = self.fetchPrices()
let accounts = toSeq(self.accounts.keys)
let balances = self.fetchBalances(accounts)
for account in toSeq(self.accounts.values):
account.tokens = self.buildTokens(account, prices, balances{account.address})
proc init*(self: Service) =
try:
let accounts = fetchAccounts()
for account in accounts:
self.accounts[account.address] = account
self.refreshBalances()
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
return
proc getAccountByAddress*(self: Service, address: string): WalletAccountDto =
if not self.accounts.hasKey(address):
return
return self.accounts[address]
proc getWalletAccounts*(self: Service): seq[WalletAccountDto] =
return toSeq(self.accounts.values)
proc getWalletAccount*(self: Service, accountIndex: int): WalletAccountDto =
if(accountIndex < 0 or accountIndex >= self.getWalletAccounts().len):
return
return self.getWalletAccounts()[accountIndex]
proc getIndex*(self: Service, address: string): int =
let accounts = self.getWalletAccounts()
for i in 0..accounts.len:
if(accounts[i].address == address):
return i
proc getCurrencyBalance*(self: Service): float64 =
return self.getWalletAccounts().map(a => a.getCurrencyBalance()).foldl(a + b, 0.0)
proc addNewAccountToLocalStore(self: Service) =
let accounts = fetchAccounts()
let prices = self.fetchPrices()
var newAccount = accounts[0]
for account in accounts:
self.accounts[account.address] = account
if not self.accounts.haskey(account.address):
newAccount = account
break
let balances = self.fetchBalances(@[newAccount.address])
newAccount.tokens = self.buildTokens(newAccount, prices, balances{newAccount.address})
self.accounts[newAccount.address] = newAccount
self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountSaved(account: newAccount))
proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
try:
discard backend.generateAccountWithDerivedPath(
hashPassword(password),
accountName,
color,
emoji,
path,
derivedFrom)
except Exception as e:
return fmt"Error generating new account: {e.msg}"
self.addNewAccountToLocalStore()
proc addAccountsFromPrivateKey*(self: Service, privateKey: string, password: string, accountName: string, color: string, emoji: string): string =
try:
discard backend.addAccountWithPrivateKey(
privateKey,
hashPassword(password),
accountName,
color,
emoji)
except Exception as e:
return fmt"Error adding account with private key: {e.msg}"
self.addNewAccountToLocalStore()
proc addAccountsFromSeed*(self: Service, mnemonic: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
try:
discard backend.addAccountWithMnemonicAndPath(
mnemonic,
hashPassword(password),
accountName,
color,
emoji,
path
)
except Exception as e:
return fmt"Error adding account with mnemonic: {e.msg}"
self.addNewAccountToLocalStore()
proc addWatchOnlyAccount*(self: Service, address: string, accountName: string, color: string, emoji: string): string =
try:
discard backend.addAccountWatch(
address,
accountName,
color,
emoji
)
except Exception as e:
return fmt"Error adding account with mnemonic: {e.msg}"
self.addNewAccountToLocalStore()
proc deleteAccount*(self: Service, address: string) =
discard status_go_accounts.deleteAccount(address)
let accountDeleted = self.accounts[address]
self.accounts.del(address)
self.events.emit(SIGNAL_WALLET_ACCOUNT_DELETED, AccountDeleted(account: accountDeleted))
proc updateCurrency*(self: Service, newCurrency: string) =
discard self.settingsService.saveCurrency(newCurrency)
self.refreshBalances()
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
return
self.events.emit(SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED, CurrencyUpdated())
proc getAccountByAddress*(self: Service, address: string): WalletAccountDto =
if not self.accounts.hasKey(address):
return
proc toggleTokenVisible*(self: Service, chainId: int, address: string) =
self.tokenService.toggleVisible(chainId, address)
self.refreshBalances()
self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKEN_VISIBILITY_UPDATED, TokenVisibilityToggled())
return self.accounts[address]
proc toggleNetworkEnabled*(self: Service, chainId: int) =
self.networkService.toggleNetwork(chainId)
self.tokenService.init()
self.refreshBalances()
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, NetwordkEnabledToggled())
proc getWalletAccounts*(self: Service): seq[WalletAccountDto] =
return toSeq(self.accounts.values)
method toggleTestNetworksEnabled*(self: Service) =
discard self.settings_service.toggleTestNetworksEnabled()
self.tokenService.init()
self.refreshBalances()
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, NetwordkEnabledToggled())
proc getWalletAccount*(self: Service, accountIndex: int): WalletAccountDto =
if(accountIndex < 0 or accountIndex >= self.getWalletAccounts().len):
return
return self.getWalletAccounts()[accountIndex]
proc getIndex*(self: Service, address: string): int =
let accounts = self.getWalletAccounts()
for i in 0..accounts.len:
if(accounts[i].address == address):
return i
proc getCurrencyBalance*(self: Service): float64 =
return self.getWalletAccounts().map(a => a.getCurrencyBalance()).foldl(a + b, 0.0)
proc addNewAccountToLocalStore(self: Service) =
let accounts = fetchAccounts()
let prices = self.fetchPrices()
var newAccount = accounts[0]
for account in accounts:
if not self.accounts.haskey(account.address):
newAccount = account
break
let balances = self.fetchBalances(@[newAccount.address])
newAccount.tokens = self.buildTokens(newAccount, prices, balances{newAccount.address})
self.accounts[newAccount.address] = newAccount
self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountSaved(account: newAccount))
proc generateNewAccount*(self: Service, password: string, accountName: string, color: string, emoji: string, path: string, derivedFrom: string): string =
try:
discard backend.generateAccountWithDerivedPath(
hashPassword(password),
accountName,
color,
emoji,
path,
derivedFrom)
except Exception as e:
return fmt"Error generating new account: {e.msg}"
self.addNewAccountToLocalStore()
proc addAccountsFromPrivateKey*(self: Service, privateKey: string, password: string, accountName: string, color: string, emoji: string): string =
try:
discard backend.addAccountWithPrivateKey(
privateKey,
hashPassword(password),
accountName,
color,
emoji)
except Exception as e:
return fmt"Error adding account with private key: {e.msg}"
self.addNewAccountToLocalStore()
proc addAccountsFromSeed*(self: Service, mnemonic: string, password: string, accountName: string, color: string, emoji: string, path: string): string =
try:
discard backend.addAccountWithMnemonicAndPath(
mnemonic,
hashPassword(password),
accountName,
color,
emoji,
path
)
except Exception as e:
return fmt"Error adding account with mnemonic: {e.msg}"
self.addNewAccountToLocalStore()
proc addWatchOnlyAccount*(self: Service, address: string, accountName: string, color: string, emoji: string): string =
try:
discard backend.addAccountWatch(
address,
proc updateWalletAccount*(self: Service, address: string, accountName: string, color: string, emoji: string) =
let account = self.accounts[address]
status_go_accounts.updateAccount(
accountName,
account.address,
account.publicKey,
account.walletType,
color,
emoji
)
except Exception as e:
return fmt"Error adding account with mnemonic: {e.msg}"
account.name = accountName
account.color = color
account.emoji = emoji
self.addNewAccountToLocalStore()
self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, WalletAccountUpdated(account: account))
proc deleteAccount*(self: Service, address: string) =
discard status_go_accounts.deleteAccount(address)
let accountDeleted = self.accounts[address]
self.accounts.del(address)
proc getDerivedAddressList*(self: Service, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int)=
let arg = GetDerivedAddressesTaskArg(
password: hashPassword(password),
derivedFrom: derivedFrom,
path: path,
pageSize: pageSize,
pageNumber: pageNumber,
tptr: cast[ByteAddress](getDerivedAddressesTask),
vptr: cast[ByteAddress](self.vptr),
slot: "setDerivedAddresses",
)
self.threadpool.start(arg)
self.events.emit(SIGNAL_WALLET_ACCOUNT_DELETED, AccountDeleted(account: accountDeleted))
proc getDerivedAddressListForMnemonic*(self: Service, mnemonic: string, path: string, pageSize: int, pageNumber: int) =
let arg = GetDerivedAddressesForMnemonicTaskArg(
mnemonic: mnemonic,
path: path,
pageSize: pageSize,
pageNumber: pageNumber,
tptr: cast[ByteAddress](getDerivedAddressesForMnemonicTask),
vptr: cast[ByteAddress](self.vptr),
slot: "setDerivedAddresses",
)
self.threadpool.start(arg)
proc updateCurrency*(self: Service, newCurrency: string) =
discard self.settingsService.saveCurrency(newCurrency)
self.refreshBalances()
self.events.emit(SIGNAL_WALLET_ACCOUNT_CURRENCY_UPDATED, CurrencyUpdated())
proc getDerivedAddressForPrivateKey*(self: Service, privateKey: string) =
let arg = GetDerivedAddressForPrivateKeyTaskArg(
privateKey: privateKey,
tptr: cast[ByteAddress](getDerivedAddressForPrivateKeyTask),
vptr: cast[ByteAddress](self.vptr),
slot: "setDerivedAddresses",
)
self.threadpool.start(arg)
proc toggleTokenVisible*(self: Service, chainId: int, address: string) =
self.tokenService.toggleVisible(chainId, address)
self.refreshBalances()
self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKEN_VISIBILITY_UPDATED, TokenVisibilityToggled())
proc setDerivedAddresses*(self: Service, derivedAddressesJson: string) {.slot.} =
let response = parseJson(derivedAddressesJson)
var derivedAddress: seq[DerivedAddressDto] = @[]
derivedAddress = response["derivedAddresses"].getElems().map(x => x.toDerivedAddressDto())
let error = response["error"].getStr()
proc toggleNetworkEnabled*(self: Service, chainId: int) =
self.networkService.toggleNetwork(chainId)
self.tokenService.init()
self.refreshBalances()
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, NetwordkEnabledToggled())
method toggleTestNetworksEnabled*(self: Service) =
discard self.settings_service.toggleTestNetworksEnabled()
self.tokenService.init()
self.refreshBalances()
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, NetwordkEnabledToggled())
proc updateWalletAccount*(self: Service, address: string, accountName: string, color: string, emoji: string) =
let account = self.accounts[address]
status_go_accounts.updateAccount(
accountName,
account.address,
account.publicKey,
account.walletType,
color,
emoji
)
account.name = accountName
account.color = color
account.emoji = emoji
self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, WalletAccountUpdated(account: account))
proc getDerivedAddressList*(self: Service, password: string, derivedFrom: string, path: string, pageSize: int, pageNumber: int): (seq[DerivedAddressDto], string) =
var derivedAddress: seq[DerivedAddressDto] = @[]
var error: string = ""
try:
let response = status_go_accounts.getDerivedAddressList(hashPassword(password), derivedFrom, path, pageSize, pageNumber)
derivedAddress = response.result.getElems().map(x => x.toDerivedAddressDto())
except Exception as e:
error = fmt"Error getting derived address list: {e.msg}"
return (derivedAddress, error)
proc getDerivedAddressListForMnemonic*(self: Service, mnemonic: string, path: string, pageSize: int, pageNumber: int): (seq[DerivedAddressDto], string) =
var derivedAddress: seq[DerivedAddressDto] = @[]
var error: string = ""
try:
let response = status_go_accounts.getDerivedAddressListForMnemonic(mnemonic, path, pageSize, pageNumber)
derivedAddress = response.result.getElems().map(x => x.toDerivedAddressDto())
except Exception as e:
error = fmt"Error getting derived address list for mnemonic: {e.msg}"
return (derivedAddress, error)
# emit event
self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESS_READY, DerivedAddressesArgs(
derivedAddresses: derivedAddress,
error: error
))

View File

@ -296,3 +296,8 @@ proc getDerivedAddressList*(password: string, derivedFrom: string, path: string,
proc getDerivedAddressListForMnemonic*(mnemonic: string, path: string, pageSize: int = 0, pageNumber: int = 6,): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [mnemonic, path, pageSize, pageNumber ]
result = core.callPrivateRPC("wallet_getDerivedAddressesForMenominicWithPath", payload)
proc getDerivedAddressForPrivateKey*(privateKey: string,): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [privateKey]
result = core.callPrivateRPC("wallet_getDerivedAddressForPrivateKey", payload)

View File

@ -1,5 +1,7 @@
import QtQuick 2.12
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
@ -7,52 +9,79 @@ import StatusQ.Components 0.1
import utils 1.0
import "../stores"
StatusSelect {
ColumnLayout {
id: derivationPathSelect
property string path: ""
function reset() {
derivationPathSelectedItem.title = DerivationPathModel.derivationPaths.get(0).name
derivationPathSelectedItem.subTitle = DerivationPathModel.derivationPaths.get(0).path
derivationPathInput.text = _internal.defaultDerivationPath
}
label: qsTr("Derivation Path")
selectMenu.width: 351
menuAlignment: StatusSelect.MenuAlignment.Left
model: DerivationPathModel.derivationPaths
selectedItemComponent: StatusListItem {
id: derivationPathSelectedItem
implicitWidth: parent.width
statusListItemTitle.wrapMode: Text.NoWrap
statusListItemTitle.width: parent.width - Style.current.padding
statusListItemTitle.elide: Qt.ElideMiddle
statusListItemTitle.anchors.left: undefined
statusListItemTitle.anchors.right: undefined
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
title: DerivationPathModel.derivationPaths.get(0).name
subTitle: DerivationPathModel.derivationPaths.get(0).path
Component.onCompleted: {
derivationPathSelect.path = Qt.binding(function() { return derivationPathSelectedItem.subTitle})
QtObject {
id: _internal
property var userInputTimer: Timer {
// 1 second wait after each key press
interval: 1000
running: false
onTriggered: {
derivationPathSelect.path = derivationPathInput.text
}
}
property bool pathError: Utils.isInvalidPath(RootStore.derivedAddressesError)
property bool derivationAddressLoading: RootStore.derivedAddressesLoading
property string defaultDerivationPath: "m/44'/60'/0'/0"
}
spacing: 7
RowLayout {
StatusBaseText {
id: inputLabel
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
text: qsTr("Derivation Path")
font.pixelSize: 15
}
StatusButton {
id: resetButton
Layout.alignment: Qt.AlignTop
size: StatusBaseButton.Size.Tiny
text: qsTr("Reset")
font.pixelSize: 15
defaultLeftPadding: 0
defaultRightPadding: 0
defaultTopPadding: 0
defaultBottomPadding: 0
color: "transparent"
onClicked: derivationPathSelect.reset()
}
}
selectMenu.delegate: StatusListItem {
implicitWidth: parent.width
statusListItemTitle.wrapMode: Text.NoWrap
statusListItemTitle.width: parent.width - Style.current.padding
statusListItemTitle.elide: Qt.ElideMiddle
statusListItemTitle.anchors.left: undefined
statusListItemTitle.anchors.right: undefined
title: model.name
subTitle: model.path
onClicked: {
derivationPathSelectedItem.title = title
derivationPathSelectedItem.subTitle = subTitle
derivationPathSelect.selectMenu.close()
StatusBaseInput {
id: derivationPathInput
Layout.preferredHeight: 64
Layout.preferredWidth: parent.width
text: _internal.defaultDerivationPath
color: _internal.pathError ? Theme.palette.dangerColor1 : Theme.palette.directColor1
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
}
}
Component {
id: loadingIcon
StatusLoadingIndicator {
color: Theme.palette.directColor4
}
}
}
}

View File

@ -15,6 +15,9 @@ Item {
id: derivedAddresses
property string pathSubFix: ""
property bool isLoading: RootStore.derivedAddressesLoading
property bool pathError: Utils.isInvalidPath(RootStore.derivedAddressesError)
function reset() {
RootStore.resetDerivedAddressModel()
_internal.nextSelectableAddressIndex = 0
@ -23,6 +26,20 @@ Item {
selectedDerivedAddress.subTitle = qsTr("No activity")
}
onIsLoadingChanged: {
if(isLoading) {
selectedDerivedAddress.title = qsTr("Pending")
selectedDerivedAddress.subTitle = ""
}
}
onPathErrorChanged: {
if(pathError) {
selectedDerivedAddress.title = qsTr("Invalid path")
selectedDerivedAddress.subTitle = ""
}
}
QtObject {
id: _internal
property int pageSize: 6
@ -90,9 +107,13 @@ Item {
height: 24
icon: "chevron-down"
color: Theme.palette.baseColor1
visible: RootStore.derivedAddressesList.count > 1
}
]
onClicked: derivedAddressPopup.popup(derivedAddresses.x - layout.width - Style.current.bigPadding , derivedAddresses.y + layout.height + 8)
onClicked: {
if(RootStore.derivedAddressesList.count > 0 && RootStore.derivedAddressesList.count !== 1)
derivedAddressPopup.popup(derivedAddresses.x - layout.width - Style.current.bigPadding , derivedAddresses.y + layout.height + 8)
}
enabled: RootStore.derivedAddressesList.count > 0
Component.onCompleted: derivedAddresses.pathSubFix = Qt.binding(function() { return pathSubFix})
}
@ -118,17 +139,32 @@ Item {
delegate: StatusListItem {
id: element
property int actualIndex: index + (stackLayout.currentIndex* _internal.pageSize)
property bool hasActivity: RootStore.getDerivedAddressHasActivityData(actualIndex)
property bool hasActivity: {
if(actualIndex >= 0 && actualIndex < RootStore.derivedAddressesList.count) {
return RootStore.getDerivedAddressHasActivityData(actualIndex)
}
return false
}
implicitWidth: derivedAddressPopup.width
statusListItemTitle.wrapMode: Text.NoWrap
statusListItemTitle.width: _internal.maxAddressWidth
statusListItemTitle.elide: Qt.ElideMiddle
statusListItemTitle.anchors.left: undefined
statusListItemTitle.anchors.right: undefined
title: RootStore.getDerivedAddressData(actualIndex)
title: {
if(actualIndex >= 0 && actualIndex < RootStore.derivedAddressesList.count) {
return RootStore.getDerivedAddressData(actualIndex)
}
return ""
}
subTitle: element.hasActivity ? qsTr("Has Activity"): qsTr("No Activity")
statusListItemSubTitle.color: element.hasActivity ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
enabled: !RootStore.getDerivedAddressAlreadyCreatedData(actualIndex)
enabled: {
if(actualIndex >= 0 && actualIndex < RootStore.derivedAddressesList.count) {
return !RootStore.getDerivedAddressAlreadyCreatedData(actualIndex)
}
return true
}
components: [
StatusBaseText {
text: element.actualIndex
@ -150,6 +186,15 @@ Item {
selectedDerivedAddress.pathSubFix = actualIndex
selectedDerivedAddress.hasActivity = element.hasActivity
derivedAddressPopup.close()
}
Component.onCompleted: {
if(RootStore.derivedAddressesList.count === 1 && index === 0) {
selectedDerivedAddress.title = title
selectedDerivedAddress.subTitle = subTitle
selectedDerivedAddress.pathSubFix = actualIndex
selectedDerivedAddress.hasActivity = element.hasActivity
selectedDerivedAddress.enabled = !RootStore.getDerivedAddressAlreadyCreatedData(index)
}
}
}
}

View File

@ -1,21 +1,25 @@
import QtQuick 2.12
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls.Validators 0.1
import utils 1.0
import "../stores"
StatusInput {
id: privateKey
ColumnLayout {
property string text: privateKey.text
property bool valid: privateKey.valid
function resetMe() {
_internal.errorString = ""
privateKey.text = ""
privateKey.reset()
reset()
}
function validateMe() {
@ -35,34 +39,75 @@ StatusInput {
id: _internal
property int privateKeyCharLimit: 66
property string errorString: ""
property bool accountAreadyAddedError: Utils.accountAlreadyExistsError(RootStore.derivedAddressesError)
}
//% "Private key"
label: qsTrId("private-key")
charLimit: _internal.privateKeyCharLimit
input.multiline: true
input.minimumHeight: 80
input.maximumHeight: 108
//% "Paste the contents of your private key"
input.placeholderText: qsTrId("paste-the-contents-of-your-private-key")
errorMessage: _internal.errorString
validators: [
StatusMinLengthValidator {
minLength: 1
//% "You need to enter a private key"
errorMessage: qsTrId("you-need-to-enter-a-private-key")
},
StatusValidator {
property var validate: function (value) {
return Utils.isPrivateKey(value)
spacing: 24
StatusInput {
id: privateKey
//% "Private key"
label: qsTrId("private-key")
charLimit: _internal.privateKeyCharLimit
input.multiline: true
input.minimumHeight: 80
input.maximumHeight: 108
//% "Paste the contents of your private key"
input.placeholderText: qsTrId("paste-the-contents-of-your-private-key")
errorMessage: _internal.errorString
validators: [
StatusMinLengthValidator {
minLength: 1
//% "You need to enter a private key"
errorMessage: qsTrId("you-need-to-enter-a-private-key")
},
StatusValidator {
property var validate: function (value) {
return Utils.isPrivateKey(value)
}
//% "Enter a valid private key (64 characters hexadecimal string)"
errorMessage: qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
}
]
onTextChanged: {
if(valid) {
RootStore.getDerivedAddressForPrivateKey(text)
}
//% "Enter a valid private key (64 characters hexadecimal string)"
errorMessage: qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
}
]
onVisibleChanged: {
if(visible)
privateKey.input.edit.forceActiveFocus();
onVisibleChanged: {
if(visible)
privateKey.input.edit.forceActiveFocus();
}
}
ColumnLayout {
spacing: 8
StatusBaseText {
id: inputLabel
Layout.alignment: Qt.AlignLeft
Layout.leftMargin: 16
Layout.fillWidth: true
text: qsTr("Public address")
font.pixelSize: 15
}
StatusListItem {
id: derivedAddress
property string address: RootStore.derivedAddressesList.count > 0 ? RootStore.getDerivedAddressData(0) : "---"
property bool hasActivity: RootStore.derivedAddressesList.count > 0 ? RootStore.getDerivedAddressHasActivityData(0) : false
Layout.alignment: Qt.AlignHCenter
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
type: _internal.accountAreadyAddedError ? StatusListItem.Type.Danger : StatusListItem.Type.Primary
statusListItemSubTitle.color: derivedAddress.hasActivity ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
title: _internal.accountAreadyAddedError ? qsTr("Account already added") : RootStore.derivedAddressesLoading ? qsTr("Pending") : derivedAddress.address
subTitle: RootStore.derivedAddressesLoading || _internal.accountAreadyAddedError ? "" : derivedAddress.hasActivity ? qsTr("Has Activity"): qsTr("No Activity")
sensor.enabled: false
}
}
}

View File

@ -38,7 +38,7 @@ StatusSelect {
QtObject {
id: _internal
property string importSeedPhraseString : qsTr("Import new Seed Phrase")
property string importPrivateKeyString : qsTr("Import new Private Key")
property string importPrivateKeyString : qsTr("Generate from Private key")
//% "Add a watch-only address"
property string addWatchOnlyAccountString : qsTrId("add-a-watch-account")

View File

@ -45,16 +45,22 @@ StatusModal {
if(advancedSelection.expandableItem.addAccountType === SelectGeneratedAccount.AddAccountType.ImportSeedPhrase &&
!!advancedSelection.expandableItem.path &&
!!advancedSelection.expandableItem.mnemonicText) {
errMessage = RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText, advancedSelection.expandableItem.path, numOfItems, pageNumber)
_internal.showPasswordError(errMessage)
RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText, advancedSelection.expandableItem.path, numOfItems, pageNumber)
}
else if(!!advancedSelection.expandableItem.path && !!advancedSelection.expandableItem.derivedFromAddress && (passwordInput.text.length >= 6)) {
errMessage = RootStore.getDerivedAddressList(passwordInput.text, advancedSelection.expandableItem.derivedFromAddress, advancedSelection.expandableItem.path, numOfItems, pageNumber)
_internal.showPasswordError(errMessage)
RootStore.getDerivedAddressList(passwordInput.text, advancedSelection.expandableItem.derivedFromAddress, advancedSelection.expandableItem.path, numOfItems, pageNumber)
}
}
}
property string derivedPathError: RootStore.derivedAddressesError
onDerivedPathErrorChanged: {
if(Utils.isInvalidPasswordMessage(derivedPathError))
//% "Wrong password"
popup.passwordValidationError = qsTrId("wrong-password")
}
function showPasswordError(errMessage) {
if (errMessage) {
if (Utils.isInvalidPasswordMessage(errMessage)) {
@ -66,6 +72,14 @@ StatusModal {
}
}
}
property var waitTimer: Timer {
interval: 1000
running: false
onTriggered: {
_internal.getDerivedAddressList()
}
}
}
function validate() {
@ -141,7 +155,7 @@ StatusModal {
inputLabel.font.weight: Font.Normal
onTextChanged: {
popup.passwordValidationError = ""
_internal.getDerivedAddressList()
_internal.waitTimer.restart()
}
}
}

View File

@ -1,37 +0,0 @@
pragma Singleton
import QtQuick 2.13
QtObject {
id: root
property ListModel derivationPaths: ListModel {
ListElement {
name: "Default"
path: "m/44'/60'/0'/0"
}
ListElement {
name: "Ethereum Classic"
path: "m/44'/61'/0'/0"
}
ListElement {
name: "Ethereum (Ledger)"
path: "m/44'/60'/0'"
}
ListElement {
name: "Ethereum Classic (Ledger)"
path: "m/44'/60'/160720'/0"
}
ListElement {
name: "Ethereum Classic (Ledger, Vintage MEW)"
path: "m/44'/60'/160720'/0'"
}
ListElement {
name: "Ethereum (KeepKey)"
path: "m/44'/60'"
}
ListElement {
name: "Ethereum Classic (KeepKey)"
path: "m/44'/61'"
}
}
}

View File

@ -81,6 +81,9 @@ QtObject {
}
}
property bool derivedAddressesLoading: walletSectionAccounts.derivedAddressesLoading
property string derivedAddressesError: walletSectionAccounts.derivedAddressesError
function setHideSignPhraseModal(value) {
localAccountSensitiveSettings.hideSignPhraseModal = value;
}
@ -177,7 +180,7 @@ QtObject {
}
function getDerivedAddressList(password, derivedFrom, path, pageSize , pageNumber) {
return walletSectionAccounts.getDerivedAddressList(password, derivedFrom, path, pageSize , pageNumber)
walletSectionAccounts.getDerivedAddressList(password, derivedFrom, path, pageSize , pageNumber)
}
function getDerivedAddressData(index) {
@ -197,7 +200,11 @@ QtObject {
}
function getDerivedAddressListForMnemonic(mnemonic, path, pageSize , pageNumber) {
return walletSectionAccounts.getDerivedAddressListForMnemonic(mnemonic, path, pageSize , pageNumber)
walletSectionAccounts.getDerivedAddressListForMnemonic(mnemonic, path, pageSize , pageNumber)
}
function getDerivedAddressForPrivateKey(privateKey) {
walletSectionAccounts.getDerivedAddressForPrivateKey(privateKey)
}
function resetDerivedAddressModel() {

View File

@ -1,2 +1 @@
singleton RootStore 1.0 RootStore.qml
singleton DerivationPathModel 1.0 DerivationPathModel.qml

View File

@ -778,6 +778,14 @@ QtObject {
);
}
function isInvalidPath(msg) {
return msg.includes("error parsing derivation path")
}
function accountAlreadyExistsError(msg) {
return msg.includes("account already exists")
}
// Leave this function at the bottom of the file as QT Creator messes up the code color after this
function isPunct(c) {
return /(!|\@|#|\$|%|\^|&|\*|\(|\)|_|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)