mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-22 04:21:44 +00:00
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:
parent
b601c88447
commit
86cfbec8f9
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
@ -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] = @[]
|
||||
|
74
src/app_service/service/wallet_account/async_tasks.nim
Normal file
74
src/app_service/service/wallet_account/async_tasks.nim
Normal 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)
|
||||
|
@ -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
|
||||
))
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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'"
|
||||
}
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
@ -1,2 +1 @@
|
||||
singleton RootStore 1.0 RootStore.qml
|
||||
singleton DerivationPathModel 1.0 DerivationPathModel.qml
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user