parent
23426f184b
commit
570f312617
|
@ -54,9 +54,6 @@ proc getCurrencyAmount*(self: Controller, amount: float64, symbol: string): Curr
|
||||||
proc updateCurrency*(self: Controller, currency: string) =
|
proc updateCurrency*(self: Controller, currency: string) =
|
||||||
self.walletAccountService.updateCurrency(currency)
|
self.walletAccountService.updateCurrency(currency)
|
||||||
|
|
||||||
# proc getIndex*(self: Controller, address: string): int =
|
|
||||||
# return self.walletAccountService.getIndex(address)
|
|
||||||
|
|
||||||
proc getNetworks*(self: Controller): seq[NetworkDto] =
|
proc getNetworks*(self: Controller): seq[NetworkDto] =
|
||||||
return self.networkService.getNetworks()
|
return self.networkService.getNetworks()
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,588 @@
|
||||||
|
proc storeWatchOnlyAccount(self: Service, account: WalletAccountDto) =
|
||||||
|
if self.watchOnlyAccounts.hasKey(account.address):
|
||||||
|
error "trying to store an already existing watch only account"
|
||||||
|
return
|
||||||
|
self.watchOnlyAccounts[account.address] = account
|
||||||
|
|
||||||
|
proc storeKeypair(self: Service, keypair: KeypairDto) =
|
||||||
|
if keypair.keyUid.len == 0:
|
||||||
|
error "trying to store a keypair with empty keyUid"
|
||||||
|
return
|
||||||
|
if self.keypairs.hasKey(keypair.keyUid):
|
||||||
|
error "trying to store an already existing keypair"
|
||||||
|
return
|
||||||
|
self.keypairs[keypair.keyUid] = keypair
|
||||||
|
|
||||||
|
proc storeAccountToKeypair(self: Service, account: WalletAccountDto) =
|
||||||
|
if account.keyUid.len == 0:
|
||||||
|
error "trying to store a keypair related account with empty keyUid"
|
||||||
|
return
|
||||||
|
if self.keypairs[account.keyUid].accounts.filter(acc => cmpIgnoreCase(acc.address, account.address) == 0).len != 0:
|
||||||
|
error "trying to store an already existing keytpair related account"
|
||||||
|
return
|
||||||
|
self.keypairs[account.keyUid].accounts.add(account)
|
||||||
|
|
||||||
|
proc getKeypairs*(self: Service): seq[KeypairDto] =
|
||||||
|
return toSeq(self.keypairs.values)
|
||||||
|
|
||||||
|
proc getKeypairByKeyUid*(self: Service, keyUid: string): KeypairDto =
|
||||||
|
if not self.keypairs.hasKey(keyUid):
|
||||||
|
return
|
||||||
|
return self.keypairs[keyUid]
|
||||||
|
|
||||||
|
proc getWatchOnlyAccounts*(self: Service): seq[WalletAccountDto] =
|
||||||
|
return toSeq(self.watchOnlyAccounts.values)
|
||||||
|
|
||||||
|
proc getWatchOnlyAccountByAddress*(self: Service, address: string): WalletAccountDto =
|
||||||
|
if not self.watchOnlyAccounts.hasKey(address):
|
||||||
|
return
|
||||||
|
return self.watchOnlyAccounts[address]
|
||||||
|
|
||||||
|
proc getAccountByAddress*(self: Service, address: string): WalletAccountDto =
|
||||||
|
result = self.getWatchOnlyAccountByAddress(address)
|
||||||
|
if not result.isNil:
|
||||||
|
return
|
||||||
|
for _, kp in self.keypairs:
|
||||||
|
let accounts = kp.accounts.filter(acc => cmpIgnoreCase(acc.address, address) == 0)
|
||||||
|
if accounts.len == 1:
|
||||||
|
return accounts[0]
|
||||||
|
|
||||||
|
proc getAccountsByAddresses*(self: Service, addresses: seq[string]): seq[WalletAccountDto] =
|
||||||
|
for address in addresses:
|
||||||
|
let acc = self.getAccountByAddress(address)
|
||||||
|
if acc.isNil:
|
||||||
|
continue
|
||||||
|
result.add(acc)
|
||||||
|
|
||||||
|
proc getWalletAccounts*(self: Service): seq[WalletAccountDto] =
|
||||||
|
for _, kp in self.keypairs:
|
||||||
|
if kp.keypairType == KeypairTypeProfile:
|
||||||
|
for acc in kp.accounts:
|
||||||
|
if acc.isChat:
|
||||||
|
continue
|
||||||
|
result.add(acc)
|
||||||
|
continue
|
||||||
|
result.add(kp.accounts)
|
||||||
|
result.add(toSeq(self.watchOnlyAccounts.values))
|
||||||
|
result.sort(walletAccountsCmp)
|
||||||
|
|
||||||
|
proc getWalletAddresses*(self: Service): seq[string] =
|
||||||
|
return self.getWalletAccounts().map(a => a.address)
|
||||||
|
|
||||||
|
proc updateAssetsLoadingState(self: Service, address: string, loading: bool) =
|
||||||
|
var acc = self.getAccountByAddress(address)
|
||||||
|
if acc.isNil:
|
||||||
|
return
|
||||||
|
acc.assetsLoading = loading
|
||||||
|
|
||||||
|
#################################################
|
||||||
|
# TODO: remove functions below
|
||||||
|
#
|
||||||
|
# The only need for a function `getIndex` below is for switching selected account.
|
||||||
|
# Switching an account in UI by the index on which an account is stored in wallet settings service cache
|
||||||
|
# is completely wrong approach we need to handle that properly, at least by using position.
|
||||||
|
proc getIndex*(self: Service, address: string): int =
|
||||||
|
let accounts = self.getWalletAccounts()
|
||||||
|
for i in 0 ..< accounts.len:
|
||||||
|
if cmpIgnoreCase(accounts[i].address, address) == 0:
|
||||||
|
return i
|
||||||
|
# The same for `getWalletAccount`, both of them need to be removed. Parts of the app which are using them
|
||||||
|
# need refactor for sure.
|
||||||
|
proc getWalletAccount*(self: Service, accountIndex: int): WalletAccountDto =
|
||||||
|
let accounts = self.getWalletAccounts()
|
||||||
|
if accountIndex < 0 or accountIndex >= accounts.len:
|
||||||
|
return
|
||||||
|
return accounts[accountIndex]
|
||||||
|
#################################################
|
||||||
|
|
||||||
|
proc startWallet(self: Service) =
|
||||||
|
if(not main_constants.WALLET_ENABLED):
|
||||||
|
return
|
||||||
|
discard backend.startWallet()
|
||||||
|
|
||||||
|
proc init*(self: Service) =
|
||||||
|
try:
|
||||||
|
let chainId = self.networkService.getNetworkForEns().chainId
|
||||||
|
let woAccounts = getWatchOnlyAccountsFromDb()
|
||||||
|
for acc in woAccounts:
|
||||||
|
acc.ens = getEnsName(acc.address, chainId)
|
||||||
|
self.storeWatchOnlyAccount(acc)
|
||||||
|
let keypairs = getKeypairsFromDb()
|
||||||
|
for kp in keypairs:
|
||||||
|
for acc in kp.accounts:
|
||||||
|
acc.ens = getEnsName(acc.address, chainId)
|
||||||
|
self.storeKeypair(kp)
|
||||||
|
|
||||||
|
let addresses = self.getWalletAddresses()
|
||||||
|
self.buildAllTokens(addresses, store = true)
|
||||||
|
self.checkRecentHistory(addresses)
|
||||||
|
self.startWallet()
|
||||||
|
except Exception as e:
|
||||||
|
let errDesription = e.msg
|
||||||
|
error "error: ", errDesription
|
||||||
|
return
|
||||||
|
|
||||||
|
self.events.on(SignalType.Message.event) do(e: Args):
|
||||||
|
var receivedData = MessageSignal(e)
|
||||||
|
if receivedData.watchOnlyAccounts.len > 0:
|
||||||
|
for acc in receivedData.watchOnlyAccounts:
|
||||||
|
self.handleWalletAccount(acc)
|
||||||
|
if receivedData.keypairs.len > 0:
|
||||||
|
for kp in receivedData.keypairs:
|
||||||
|
self.handleKeypair(kp)
|
||||||
|
if receivedData.accountsPositions.len > 0:
|
||||||
|
self.updateAccountsPositions()
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED, Args())
|
||||||
|
|
||||||
|
self.events.on(SignalType.Wallet.event) do(e:Args):
|
||||||
|
var data = WalletSignal(e)
|
||||||
|
case data.eventType:
|
||||||
|
of "wallet-tick-reload":
|
||||||
|
let addresses = self.getWalletAddresses()
|
||||||
|
self.buildAllTokens(addresses, store = true)
|
||||||
|
self.checkRecentHistory(addresses)
|
||||||
|
|
||||||
|
self.events.on(SIGNAL_CURRENCY_UPDATED) do(e:Args):
|
||||||
|
self.buildAllTokens(self.getWalletAddresses(), store = true)
|
||||||
|
|
||||||
|
proc addNewKeypairsAccountsToLocalStoreAndNotify(self: Service, notify: bool = true) =
|
||||||
|
let chainId = self.networkService.getNetworkForEns().chainId
|
||||||
|
let allLocalAaccounts = self.getWalletAccounts()
|
||||||
|
# check if there is new watch only account
|
||||||
|
let woAccountsDb = getWatchOnlyAccountsFromDb()
|
||||||
|
for woAccDb in woAccountsDb:
|
||||||
|
var found = false
|
||||||
|
for localAcc in allLocalAaccounts:
|
||||||
|
if cmpIgnoreCase(localAcc.address, woAccDb.address) == 0:
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
continue
|
||||||
|
woAccDb.ens = getEnsName(woAccDb.address, chainId)
|
||||||
|
self.storeWatchOnlyAccount(woAccDb)
|
||||||
|
self.buildAllTokens(@[woAccDb.address], store = true)
|
||||||
|
if notify:
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountArgs(account: woAccDb))
|
||||||
|
# check if there is new keypair or any account added to an existing keypair
|
||||||
|
let keypairsDb = getKeypairsFromDb()
|
||||||
|
for kpDb in keypairsDb:
|
||||||
|
var localKp = self.getKeypairByKeyUid(kpDb.keyUid)
|
||||||
|
if localKp.isNil:
|
||||||
|
self.storeKeypair(kpDb)
|
||||||
|
let addresses = kpDb.accounts.map(a => a.address)
|
||||||
|
self.buildAllTokens(addresses, store = true)
|
||||||
|
for acc in kpDb.accounts:
|
||||||
|
acc.ens = getEnsName(acc.address, chainId)
|
||||||
|
if notify:
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountArgs(account: acc))
|
||||||
|
else:
|
||||||
|
for accDb in kpDb.accounts:
|
||||||
|
var found = false
|
||||||
|
for localAcc in allLocalAaccounts:
|
||||||
|
if cmpIgnoreCase(localAcc.address, accDb.address) == 0:
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
if found:
|
||||||
|
continue
|
||||||
|
accDb.ens = getEnsName(accDb.address, chainId)
|
||||||
|
self.storeAccountToKeypair(accDb)
|
||||||
|
self.buildAllTokens(@[accDb.address], store = true)
|
||||||
|
if notify:
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_SAVED, AccountArgs(account: accDb))
|
||||||
|
|
||||||
|
proc removeAccountFromLocalStoreAndNotify(self: Service, address: string, notify: bool = true) =
|
||||||
|
var acc = self.getAccountByAddress(address)
|
||||||
|
if acc.isNil:
|
||||||
|
return
|
||||||
|
if acc.keyUid.len == 0:
|
||||||
|
self.watchOnlyAccounts.del(acc.address)
|
||||||
|
else:
|
||||||
|
var index = -1
|
||||||
|
for i in 0 ..< self.keypairs[acc.keyUid].accounts.len:
|
||||||
|
if cmpIgnoreCase(self.keypairs[acc.keyUid].accounts[i].address, acc.address) == 0:
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
if index == -1:
|
||||||
|
error "cannot find account with the address to remove", address=address
|
||||||
|
return
|
||||||
|
self.keypairs[acc.keyUid].accounts.del(index)
|
||||||
|
if self.keypairs[acc.keyUid].accounts.len == 0:
|
||||||
|
self.keypairs.del(acc.keyUid)
|
||||||
|
if notify:
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_DELETED, AccountArgs(account: acc))
|
||||||
|
|
||||||
|
proc updateAccountsPositions(self: Service) =
|
||||||
|
let dbAccounts = getAccountsFromDb()
|
||||||
|
for dbAcc in dbAccounts:
|
||||||
|
var localAcc = self.getAccountByAddress(dbAcc.address)
|
||||||
|
if localAcc.isNil:
|
||||||
|
continue
|
||||||
|
localAcc.position = dbAcc.position
|
||||||
|
|
||||||
|
proc updateAccountInLocalStoreAndNotify(self: Service, address, name, colorId, emoji: string,
|
||||||
|
positionUpdated: Option[bool] = none(bool), notify: bool = true) =
|
||||||
|
if address.len > 0:
|
||||||
|
var account = self.getAccountByAddress(address)
|
||||||
|
if account.isNil:
|
||||||
|
return
|
||||||
|
if name.len > 0 or colorId.len > 0 or emoji.len > 0:
|
||||||
|
if name.len > 0 and name != account.name:
|
||||||
|
account.name = name
|
||||||
|
if colorId.len > 0 and colorId != account.colorId:
|
||||||
|
account.colorId = colorId
|
||||||
|
if emoji.len > 0 and emoji != account.emoji:
|
||||||
|
account.emoji = emoji
|
||||||
|
if notify:
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_UPDATED, AccountArgs(account: account))
|
||||||
|
else:
|
||||||
|
if not positionUpdated.isSome:
|
||||||
|
return
|
||||||
|
if positionUpdated.get:
|
||||||
|
## if reordering was successfully stored, we need to update local storage
|
||||||
|
self.updateAccountsPositions()
|
||||||
|
if notify:
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED, Args())
|
||||||
|
|
||||||
|
proc updatePreferredSharingChainsAndNotify(self: Service, address, prodPreferredChains, testPreferredChains: string) =
|
||||||
|
var account = self.getAccountByAddress(address)
|
||||||
|
if account.isNil:
|
||||||
|
error "account's address is not among known addresses: ", address=address, procName="updatePreferredSharingChainsAndNotify"
|
||||||
|
return
|
||||||
|
if testPreferredChains.len > 0 and testPreferredChains != account.testPreferredChainIds:
|
||||||
|
account.testPreferredChainIds = testPreferredChains
|
||||||
|
if prodPreferredChains.len > 0 and prodPreferredChains != account.prodPreferredChainIds:
|
||||||
|
account.prodPreferredChainIds = prodPreferredChains
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_PREFERRED_SHARING_CHAINS_UPDATED, AccountArgs(account: account))
|
||||||
|
|
||||||
|
## if password is not provided local keystore file won't be created
|
||||||
|
proc addWalletAccount*(self: Service, password: string, doPasswordHashing: bool, name, address, path, publicKey,
|
||||||
|
keyUid, accountType, colorId, emoji: string): string =
|
||||||
|
try:
|
||||||
|
var response: RpcResponse[JsonNode]
|
||||||
|
if password.len == 0:
|
||||||
|
response = status_go_accounts.addAccountWithoutKeystoreFileCreation(name, address, path, publicKey, keyUid,
|
||||||
|
accountType, colorId, emoji)
|
||||||
|
else:
|
||||||
|
var finalPassword = password
|
||||||
|
if doPasswordHashing:
|
||||||
|
finalPassword = utils.hashPassword(password)
|
||||||
|
response = status_go_accounts.addAccount(finalPassword, name, address, path, publicKey, keyUid, accountType,
|
||||||
|
colorId, emoji)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error", procName="addWalletAccount", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return response.error.message
|
||||||
|
self.addNewKeypairsAccountsToLocalStoreAndNotify()
|
||||||
|
return ""
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="addWalletAccount", errName=e.name, errDesription=e.msg
|
||||||
|
return e.msg
|
||||||
|
|
||||||
|
## Mandatory fields for account: `address`, `keyUid`, `walletType`, `path`, `publicKey`, `name`, `emoji`, `colorId`
|
||||||
|
proc addNewPrivateKeyKeypair*(self: Service, privateKey, password: string, doPasswordHashing: bool,
|
||||||
|
keyUid, keypairName, rootWalletMasterKey: string, account: WalletAccountDto): string =
|
||||||
|
if password.len == 0:
|
||||||
|
error "for adding new private key account, password must be provided"
|
||||||
|
return
|
||||||
|
var finalPassword = password
|
||||||
|
if doPasswordHashing:
|
||||||
|
finalPassword = utils.hashPassword(password)
|
||||||
|
try:
|
||||||
|
var response = status_go_accounts.importPrivateKey(privateKey, finalPassword)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error importing private key", procName="addNewPrivateKeyKeypair", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return response.error.message
|
||||||
|
response = status_go_accounts.addKeypair(finalPassword, keyUid, keypairName, KeypairTypeKey, rootWalletMasterKey, @[account])
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error adding keypair", procName="addNewPrivateKeyKeypair", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return response.error.message
|
||||||
|
self.addNewKeypairsAccountsToLocalStoreAndNotify()
|
||||||
|
return ""
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="addNewPrivateKeyKeypair", errName=e.name, errDesription=e.msg
|
||||||
|
return e.msg
|
||||||
|
|
||||||
|
## Mandatory fields for all accounts: `address`, `keyUid`, `walletType`, `path`, `publicKey`, `name`, `emoji`, `colorId`
|
||||||
|
proc addNewSeedPhraseKeypair*(self: Service, seedPhrase, password: string, doPasswordHashing: bool,
|
||||||
|
keyUid, keypairName, rootWalletMasterKey: string, accounts: seq[WalletAccountDto]): string =
|
||||||
|
var finalPassword = password
|
||||||
|
if password.len > 0 and doPasswordHashing:
|
||||||
|
finalPassword = utils.hashPassword(password)
|
||||||
|
try:
|
||||||
|
if seedPhrase.len > 0 and password.len > 0:
|
||||||
|
let response = status_go_accounts.importMnemonic(seedPhrase, finalPassword)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error importing private key", procName="addNewSeedPhraseKeypair", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return response.error.message
|
||||||
|
let response = status_go_accounts.addKeypair(finalPassword, keyUid, keypairName, KeypairTypeSeed, rootWalletMasterKey, accounts)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error adding keypair", procName="addNewSeedPhraseKeypair", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return response.error.message
|
||||||
|
for i in 0 ..< accounts.len:
|
||||||
|
self.addNewKeypairsAccountsToLocalStoreAndNotify()
|
||||||
|
return ""
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="addNewSeedPhraseKeypair", errName=e.name, errDesription=e.msg
|
||||||
|
return e.msg
|
||||||
|
|
||||||
|
proc getRandomMnemonic*(self: Service): string =
|
||||||
|
try:
|
||||||
|
let response = status_go_accounts.getRandomMnemonic()
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error", procName="getRandomMnemonic", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return ""
|
||||||
|
return response.result.getStr
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="getRandomMnemonic", errName=e.name, errDesription=e.msg
|
||||||
|
return ""
|
||||||
|
|
||||||
|
proc deleteAccount*(self: Service, address: string) =
|
||||||
|
try:
|
||||||
|
let response = status_go_accounts.deleteAccount(address)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error", procName="deleteAccount", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return
|
||||||
|
self.removeAccountFromLocalStoreAndNotify(address)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="deleteAccount", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc deleteKeypair*(self: Service, keyUid: string) =
|
||||||
|
try:
|
||||||
|
let kp = self.getKeypairByKeyUid(keyUid)
|
||||||
|
if kp.isNil:
|
||||||
|
error "there is no known keypair", keyUid=keyUid, procName="deleteKeypair"
|
||||||
|
return
|
||||||
|
let response = status_go_accounts.deleteKeypair(keyUid)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error", procName="deleteKeypair", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return
|
||||||
|
self.updateAccountsPositions()
|
||||||
|
for acc in kp.accounts:
|
||||||
|
self.removeAccountFromLocalStoreAndNotify(acc.address)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="deleteKeypair", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc updateCurrency*(self: Service, newCurrency: string) =
|
||||||
|
discard self.settingsService.saveCurrency(newCurrency)
|
||||||
|
|
||||||
|
proc setNetworksState*(self: Service, chainIds: seq[int], enabled: bool) =
|
||||||
|
self.networkService.setNetworksState(chainIds, enabled)
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, Args())
|
||||||
|
|
||||||
|
proc toggleTestNetworksEnabled*(self: Service) =
|
||||||
|
discard self.settingsService.toggleTestNetworksEnabled()
|
||||||
|
let addresses = self.getWalletAddresses()
|
||||||
|
self.buildAllTokens(addresses, store = true)
|
||||||
|
self.tokenService.loadData()
|
||||||
|
self.checkRecentHistory(addresses)
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED, Args())
|
||||||
|
|
||||||
|
proc updateWalletAccount*(self: Service, address: string, accountName: string, colorId: string, emoji: string): bool =
|
||||||
|
try:
|
||||||
|
var account = self.getAccountByAddress(address)
|
||||||
|
if account.isNil:
|
||||||
|
error "account's address is not among known addresses: ", address=address, procName="updateWalletAccount"
|
||||||
|
return false
|
||||||
|
let response = status_go_accounts.updateAccount(accountName, account.address, account.path, account.publicKey,
|
||||||
|
account.keyUid, account.walletType, colorId, emoji, account.isWallet, account.isChat, account.prodPreferredChainIds, account.testPreferredChainIds)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error", procName="updateWalletAccount", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return false
|
||||||
|
self.updateAccountInLocalStoreAndNotify(address, accountName, colorId, emoji)
|
||||||
|
return true
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="updateWalletAccount", errName=e.name, errDesription=e.msg
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc updateWalletAccountProdPreferredChains*(self: Service, address, preferredChainIds: string): bool =
|
||||||
|
try:
|
||||||
|
var account = self.getAccountByAddress(address)
|
||||||
|
if account.isNil:
|
||||||
|
error "account's address is not among known addresses: ", address=address, procName="updateWalletAccountProdPreferredChains"
|
||||||
|
return false
|
||||||
|
let response = status_go_accounts.updateAccount(account.name, account.address, account.path, account.publicKey,
|
||||||
|
account.keyUid, account.walletType, account.colorId, account.emoji, account.isWallet, account.isChat, preferredChainIds, account.testPreferredChainIds)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error", procName="updateWalletAccountProdPreferredChains", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return false
|
||||||
|
self.updatePreferredSharingChainsAndNotify(address, prodPreferredChains = preferredChainIds, testPreferredChains = "")
|
||||||
|
return true
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="updateWalletAccountProdPreferredChains", errName=e.name, errDesription=e.msg
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc updateWalletAccountTestPreferredChains*(self: Service, address, preferredChainIds: string): bool =
|
||||||
|
try:
|
||||||
|
var account = self.getAccountByAddress(address)
|
||||||
|
if account.isNil:
|
||||||
|
error "account's address is not among known addresses: ", address=address, procName="updateWalletAccountTestPreferredChains"
|
||||||
|
return false
|
||||||
|
let response = status_go_accounts.updateAccount(account.name, account.address, account.path, account.publicKey,
|
||||||
|
account.keyUid, account.walletType, account.colorId, account.emoji, account.isWallet, account.isChat, account.prodPreferredChainIds, preferredChainIds)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error", procName="updateWalletAccountTestPreferredChains", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return false
|
||||||
|
self.updatePreferredSharingChainsAndNotify(address, prodPreferredChains = "", testPreferredChains = preferredChainIds)
|
||||||
|
return true
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="updateWalletAccountTestPreferredChains", errName=e.name, errDesription=e.msg
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc moveAccountFinally*(self: Service, fromPosition: int, toPosition: int) =
|
||||||
|
var updated = false
|
||||||
|
try:
|
||||||
|
let response = backend.moveWalletAccount(fromPosition, toPosition)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error", procName="moveAccountFinally", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
updated = true
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="moveAccountFinally", errName=e.name, errDesription=e.msg
|
||||||
|
self.updateAccountInLocalStoreAndNotify(address = "", name = "", colorId = "", emoji = "", some(updated))
|
||||||
|
|
||||||
|
proc updateKeypairName*(self: Service, keyUid: string, name: string) =
|
||||||
|
try:
|
||||||
|
let kp = self.getKeypairByKeyUid(keyUid)
|
||||||
|
if kp.isNil:
|
||||||
|
error "there is no known keypair", keyUid=keyUid, procName="updateKeypairName"
|
||||||
|
return
|
||||||
|
let response = backend.updateKeypairName(keyUid, name)
|
||||||
|
if not response.error.isNil:
|
||||||
|
error "status-go error", procName="updateKeypairName", errCode=response.error.code, errDesription=response.error.message
|
||||||
|
return
|
||||||
|
var data = KeypairArgs(
|
||||||
|
keypair: KeypairDto(
|
||||||
|
keyUid: keyUid,
|
||||||
|
name: name
|
||||||
|
),
|
||||||
|
oldKeypairName: kp.name
|
||||||
|
)
|
||||||
|
kp.name = name
|
||||||
|
self.events.emit(SIGNAL_KEYPAIR_NAME_CHANGED, data)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="updateKeypairName", errName=e.name, errDesription=e.msg
|
||||||
|
|
||||||
|
proc fetchDerivedAddresses*(self: Service, password: string, derivedFrom: string, paths: seq[string], hashPassword: bool) =
|
||||||
|
let arg = FetchDerivedAddressesTaskArg(
|
||||||
|
password: if hashPassword: utils.hashPassword(password) else: password,
|
||||||
|
derivedFrom: derivedFrom,
|
||||||
|
paths: paths,
|
||||||
|
tptr: cast[ByteAddress](fetchDerivedAddressesTask),
|
||||||
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
|
slot: "onDerivedAddressesFetched",
|
||||||
|
)
|
||||||
|
self.threadpool.start(arg)
|
||||||
|
|
||||||
|
proc onDerivedAddressesFetched*(self: Service, jsonString: string) {.slot.} =
|
||||||
|
let response = parseJson(jsonString)
|
||||||
|
var derivedAddress: seq[DerivedAddressDto] = @[]
|
||||||
|
derivedAddress = response["derivedAddresses"].getElems().map(x => x.toDerivedAddressDto())
|
||||||
|
let error = response["error"].getStr()
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FETCHED, DerivedAddressesArgs(
|
||||||
|
derivedAddresses: derivedAddress,
|
||||||
|
error: error
|
||||||
|
))
|
||||||
|
|
||||||
|
proc fetchDerivedAddressesForMnemonic*(self: Service, mnemonic: string, paths: seq[string])=
|
||||||
|
let arg = FetchDerivedAddressesForMnemonicTaskArg(
|
||||||
|
mnemonic: mnemonic,
|
||||||
|
paths: paths,
|
||||||
|
tptr: cast[ByteAddress](fetchDerivedAddressesForMnemonicTask),
|
||||||
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
|
slot: "onDerivedAddressesForMnemonicFetched",
|
||||||
|
)
|
||||||
|
self.threadpool.start(arg)
|
||||||
|
|
||||||
|
proc onDerivedAddressesForMnemonicFetched*(self: Service, jsonString: string) {.slot.} =
|
||||||
|
let response = parseJson(jsonString)
|
||||||
|
var derivedAddress: seq[DerivedAddressDto] = @[]
|
||||||
|
derivedAddress = response["derivedAddresses"].getElems().map(x => x.toDerivedAddressDto())
|
||||||
|
let error = response["error"].getStr()
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FROM_MNEMONIC_FETCHED, DerivedAddressesArgs(
|
||||||
|
derivedAddresses: derivedAddress,
|
||||||
|
error: error
|
||||||
|
))
|
||||||
|
|
||||||
|
proc fetchDetailsForAddresses*(self: Service, uniqueId: string, addresses: seq[string]) =
|
||||||
|
let network = self.networkService.getNetworkForActivityCheck()
|
||||||
|
let arg = FetchDetailsForAddressesTaskArg(
|
||||||
|
uniqueId: uniqueId,
|
||||||
|
chainId: network.chainId,
|
||||||
|
addresses: addresses,
|
||||||
|
tptr: cast[ByteAddress](fetchDetailsForAddressesTask),
|
||||||
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
|
slot: "onAddressDetailsFetched",
|
||||||
|
)
|
||||||
|
self.threadpool.start(arg)
|
||||||
|
|
||||||
|
proc onAddressDetailsFetched*(self: Service, jsonString: string) {.slot.} =
|
||||||
|
var data = DerivedAddressesArgs()
|
||||||
|
try:
|
||||||
|
let response = parseJson(jsonString)
|
||||||
|
data.uniqueId = response["uniqueId"].getStr()
|
||||||
|
let addrDto = response{"details"}.toDerivedAddressDto()
|
||||||
|
data.derivedAddresses.add(addrDto)
|
||||||
|
data.error = response["error"].getStr()
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="fetchAddressDetails", errName = e.name, errDesription = e.msg
|
||||||
|
data.error = e.msg
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_ADDRESS_DETAILS_FETCHED, data)
|
||||||
|
|
||||||
|
proc handleWalletAccount(self: Service, account: WalletAccountDto, notify: bool = true) =
|
||||||
|
if account.removed:
|
||||||
|
self.updateAccountsPositions()
|
||||||
|
self.removeAccountFromLocalStoreAndNotify(account.address, notify)
|
||||||
|
else:
|
||||||
|
var localAcc = self.getAccountByAddress(account.address)
|
||||||
|
if not localAcc.isNil:
|
||||||
|
self.updateAccountInLocalStoreAndNotify(account.address, account.name, account.colorId, account.emoji,
|
||||||
|
none(bool), notify)
|
||||||
|
else:
|
||||||
|
self.addNewKeypairsAccountsToLocalStoreAndNotify(notify)
|
||||||
|
|
||||||
|
proc handleKeypair(self: Service, keypair: KeypairDto) =
|
||||||
|
let localKp = self.getKeypairByKeyUid(keypair.keyUid)
|
||||||
|
if not localKp.isNil:
|
||||||
|
# - first remove removed accounts from the UI
|
||||||
|
for localAcc in localKp.accounts:
|
||||||
|
let accAddress = localAcc.address
|
||||||
|
if keypair.accounts.filter(a => cmpIgnoreCase(a.address, accAddress) == 0).len == 0:
|
||||||
|
self.handleWalletAccount(WalletAccountDto(address: accAddress, removed: true), notify = false)
|
||||||
|
# - second add/update new/existing accounts
|
||||||
|
for acc in keypair.accounts:
|
||||||
|
self.handleWalletAccount(acc, notify = false)
|
||||||
|
else:
|
||||||
|
self.addNewKeypairsAccountsToLocalStoreAndNotify(notify = false)
|
||||||
|
|
||||||
|
# notify all interested parts about the keypair change
|
||||||
|
self.events.emit(SIGNAL_KEYPAIR_SYNCED, KeypairArgs(keypair: keypair))
|
||||||
|
|
||||||
|
proc isIncludeWatchOnlyAccount*(self: Service): bool =
|
||||||
|
return self.settingsService.isIncludeWatchOnlyAccount()
|
||||||
|
|
||||||
|
proc toggleIncludeWatchOnlyAccount*(self: Service) =
|
||||||
|
self.settingsService.toggleIncludeWatchOnlyAccount()
|
||||||
|
|
||||||
|
proc onFetchChainIdForUrl*(self: Service, jsonString: string) {.slot.} =
|
||||||
|
let response = parseJson(jsonString)
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_CHAIN_ID_FOR_URL_FETCHED, ChainIdForUrlArgs(
|
||||||
|
chainId: response{"chainId"}.getInt,
|
||||||
|
success: response{"success"}.getBool,
|
||||||
|
url: response{"url"}.getStr,
|
||||||
|
))
|
||||||
|
|
||||||
|
proc fetchChainIdForUrl*(self: Service, url: string) =
|
||||||
|
let arg = FetchChainIdForUrlTaskArg(
|
||||||
|
tptr: cast[ByteAddress](fetchChainIdForUrlTask),
|
||||||
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
|
slot: "onFetchChainIdForUrl",
|
||||||
|
url: url
|
||||||
|
)
|
||||||
|
self.threadpool.start(arg)
|
||||||
|
|
||||||
|
proc getEnabledChainIds*(self: Service): seq[int] =
|
||||||
|
return self.networkService.getNetworks().filter(n => n.enabled).map(n => n.chainId)
|
||||||
|
|
||||||
|
proc getCurrencyFormat*(self: Service, symbol: string): CurrencyFormatDto =
|
||||||
|
return self.currencyService.getCurrencyFormat(symbol)
|
||||||
|
|
||||||
|
proc areTestNetworksEnabled*(self: Service): bool =
|
||||||
|
return self.settingsService.areTestNetworksEnabled()
|
|
@ -0,0 +1,184 @@
|
||||||
|
proc addKeycardOrAccountsAsync*(self: Service, keycard: KeycardDto, accountsComingFromKeycard: bool = false) =
|
||||||
|
let arg = SaveOrUpdateKeycardTaskArg(
|
||||||
|
tptr: cast[ByteAddress](saveOrUpdateKeycardTask),
|
||||||
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
|
slot: "onKeycardAdded",
|
||||||
|
keycard: keycard,
|
||||||
|
accountsComingFromKeycard: accountsComingFromKeycard
|
||||||
|
)
|
||||||
|
self.threadpool.start(arg)
|
||||||
|
|
||||||
|
proc emitAddKeycardAddAccountsChange(self: Service, success: bool, keycard: KeycardDto) =
|
||||||
|
let data = KeycardArgs(
|
||||||
|
success: success,
|
||||||
|
keycard: keycard
|
||||||
|
)
|
||||||
|
self.events.emit(SIGNAL_NEW_KEYCARD_SET, data)
|
||||||
|
|
||||||
|
proc onKeycardAdded*(self: Service, response: string) {.slot.} =
|
||||||
|
var keycard = KeycardDto()
|
||||||
|
var success = false
|
||||||
|
try:
|
||||||
|
let responseObj = response.parseJson
|
||||||
|
discard responseObj.getProp("success", success)
|
||||||
|
var kpJson: JsonNode
|
||||||
|
if responseObj.getProp("keycard", kpJson):
|
||||||
|
keycard = kpJson.toKeycardDto()
|
||||||
|
except Exception as e:
|
||||||
|
error "error handilng migrated keycard response", errDesription=e.msg
|
||||||
|
self.emitAddKeycardAddAccountsChange(success, keycard)
|
||||||
|
|
||||||
|
proc addKeycardOrAccounts*(self: Service, keycard: KeycardDto, accountsComingFromKeycard: bool = false): bool =
|
||||||
|
var success = false
|
||||||
|
try:
|
||||||
|
let response = backend.saveOrUpdateKeycard(
|
||||||
|
%* {
|
||||||
|
"keycard-uid": keycard.keycardUid,
|
||||||
|
"keycard-name": keycard.keycardName,
|
||||||
|
# "keycard-locked" - no need to set it here, cause it will be set to false by the status-go
|
||||||
|
"key-uid": keycard.keyUid,
|
||||||
|
"accounts-addresses": keycard.accountsAddresses,
|
||||||
|
# "position": - no need to set it here, cause it is fully maintained by the status-go
|
||||||
|
},
|
||||||
|
accountsComingFromKeycard
|
||||||
|
)
|
||||||
|
success = responseHasNoErrors("addKeycardOrAccounts", response)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="addKeycardOrAccounts", errName = e.name, errDesription = e.msg
|
||||||
|
self.emitAddKeycardAddAccountsChange(success = success, keycard)
|
||||||
|
return success
|
||||||
|
|
||||||
|
proc removeMigratedAccountsForKeycard*(self: Service, keyUid: string, keycardUid: string, accountsToRemove: seq[string]) =
|
||||||
|
let arg = DeleteKeycardAccountsTaskArg(
|
||||||
|
tptr: cast[ByteAddress](deleteKeycardAccountsTask),
|
||||||
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
|
slot: "onMigratedAccountsForKeycardRemoved",
|
||||||
|
keycard: KeycardDto(keyUid: keyUid, keycardUid: keycardUid, accountsAddresses: accountsToRemove)
|
||||||
|
)
|
||||||
|
self.threadpool.start(arg)
|
||||||
|
|
||||||
|
proc onMigratedAccountsForKeycardRemoved*(self: Service, response: string) {.slot.} =
|
||||||
|
var data = KeycardArgs(
|
||||||
|
success: false,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
let responseObj = response.parseJson
|
||||||
|
discard responseObj.getProp("success", data.success)
|
||||||
|
var kpJson: JsonNode
|
||||||
|
if responseObj.getProp("keycard", kpJson):
|
||||||
|
data.keycard = kpJson.toKeycardDto()
|
||||||
|
except Exception as e:
|
||||||
|
error "error handilng migrated keycard response", errDesription=e.msg
|
||||||
|
self.events.emit(SIGNAL_KEYCARD_ACCOUNTS_REMOVED, data)
|
||||||
|
|
||||||
|
proc getAllKnownKeycards*(self: Service): seq[KeycardDto] =
|
||||||
|
try:
|
||||||
|
let response = backend.getAllKnownKeycards()
|
||||||
|
if responseHasNoErrors("getAllKnownKeycards", response):
|
||||||
|
return map(response.result.getElems(), proc(x: JsonNode): KeycardDto = toKeycardDto(x))
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="getAllKnownKeycards", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc getKeycardByKeycardUid*(self: Service, keycardUid: string): KeycardDto =
|
||||||
|
try:
|
||||||
|
let response = backend.getKeycardByKeycardUID(keycardUid)
|
||||||
|
if responseHasNoErrors("getKeycardByKeycardUid", response):
|
||||||
|
return response.result.toKeycardDto()
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="getKeycardByKeycardUid", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc getKeycardsWithSameKeyUid*(self: Service, keyUid: string): seq[KeycardDto] =
|
||||||
|
try:
|
||||||
|
let response = backend.getKeycardsWithSameKeyUID(keyUid)
|
||||||
|
if responseHasNoErrors("getKeycardsWithSameKeyUid", response):
|
||||||
|
return map(response.result.getElems(), proc(x: JsonNode): KeycardDto = toKeycardDto(x))
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="getKeycardsWithSameKeyUid", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc isKeycardAccount*(self: Service, account: WalletAccountDto): bool =
|
||||||
|
if account.isNil or
|
||||||
|
account.keyUid.len == 0 or
|
||||||
|
account.path.len == 0 or
|
||||||
|
utils.isPathOutOfTheDefaultStatusDerivationTree(account.path):
|
||||||
|
return false
|
||||||
|
let keycards = self.getKeycardsWithSameKeyUid(account.keyUid)
|
||||||
|
return keycards.len > 0
|
||||||
|
|
||||||
|
proc updateKeycardName*(self: Service, keycardUid: string, name: string): bool =
|
||||||
|
var data = KeycardArgs(
|
||||||
|
success: false,
|
||||||
|
keycard: KeycardDto(keycardUid: keycardUid, keycardName: name)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
let response = backend.setKeycardName(keycardUid, name)
|
||||||
|
data.success = responseHasNoErrors("updateKeycardName", response)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="updateKeycardName", errName = e.name, errDesription = e.msg
|
||||||
|
self.events.emit(SIGNAL_KEYCARD_NAME_CHANGED, data)
|
||||||
|
return data.success
|
||||||
|
|
||||||
|
proc setKeycardLocked*(self: Service, keyUid: string, keycardUid: string): bool =
|
||||||
|
var data = KeycardArgs(
|
||||||
|
success: false,
|
||||||
|
keycard: KeycardDto(keyUid: keyUid, keycardUid: keycardUid)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
let response = backend.keycardLocked(keycardUid)
|
||||||
|
data.success = responseHasNoErrors("setKeycardLocked", response)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="setKeycardLocked", errName = e.name, errDesription = e.msg
|
||||||
|
self.events.emit(SIGNAL_KEYCARD_LOCKED, data)
|
||||||
|
return data.success
|
||||||
|
|
||||||
|
proc setKeycardUnlocked*(self: Service, keyUid: string, keycardUid: string): bool =
|
||||||
|
var data = KeycardArgs(
|
||||||
|
success: false,
|
||||||
|
keycard: KeycardDto(keyUid: keyUid, keycardUid: keycardUid)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
let response = backend.keycardUnlocked(keycardUid)
|
||||||
|
data.success = responseHasNoErrors("setKeycardUnlocked", response)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="setKeycardUnlocked", errName = e.name, errDesription = e.msg
|
||||||
|
self.events.emit(SIGNAL_KEYCARD_UNLOCKED, data)
|
||||||
|
return data.success
|
||||||
|
|
||||||
|
proc updateKeycardUid*(self: Service, oldKeycardUid: string, newKeycardUid: string): bool =
|
||||||
|
var data = KeycardArgs(
|
||||||
|
success: false,
|
||||||
|
oldKeycardUid: oldKeycardUid,
|
||||||
|
keycard: KeycardDto(keycardUid: newKeycardUid)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
let response = backend.updateKeycardUID(oldKeycardUid, newKeycardUid)
|
||||||
|
data.success = responseHasNoErrors("updateKeycardUid", response)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="updateKeycardUid", errName = e.name, errDesription = e.msg
|
||||||
|
self.events.emit(SIGNAL_KEYCARD_UID_UPDATED, data)
|
||||||
|
return data.success
|
||||||
|
|
||||||
|
proc deleteKeycard*(self: Service, keycardUid: string): bool =
|
||||||
|
var data = KeycardArgs(
|
||||||
|
success: false,
|
||||||
|
keycard: KeycardDto(keycardUid: keycardUid)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
let response = backend.deleteKeycard(keycardUid)
|
||||||
|
data.success = responseHasNoErrors("deleteKeycard", response)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="deleteKeycard", errName = e.name, errDesription = e.msg
|
||||||
|
self.events.emit(SIGNAL_KEYCARD_DELETED, data)
|
||||||
|
return data.success
|
||||||
|
|
||||||
|
proc deleteAllKeycardsWithKeyUid*(self: Service, keyUid: string): bool =
|
||||||
|
var data = KeycardArgs(
|
||||||
|
success: false,
|
||||||
|
keycard: KeycardDto(keyUid: keyUid)
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
let response = backend.deleteAllKeycardsWithKeyUID(keyUid)
|
||||||
|
data.success = responseHasNoErrors("deleteAllKeycardsWithKeyUid", response)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="deleteAllKeycardsWithKeyUid", errName = e.name, errDesription = e.msg
|
||||||
|
self.events.emit(SIGNAL_ALL_KEYCARDS_DELETED, data)
|
||||||
|
return data.success
|
|
@ -0,0 +1,178 @@
|
||||||
|
proc storeTokensForAccount*(self: Service, address: string, tokens: seq[WalletTokenDto], areBalancesCached: bool, areMarketValuesCached: bool) =
|
||||||
|
let acc = self.getAccountByAddress(address)
|
||||||
|
if acc.isNil:
|
||||||
|
return
|
||||||
|
self.accountsTokens[address] = tokens
|
||||||
|
acc.hasBalanceCache = areBalancesCached
|
||||||
|
acc.hasMarketValuesCache = areMarketValuesCached
|
||||||
|
|
||||||
|
proc updateReceivedTokens*(self: Service, address: string, tokens: var seq[WalletTokenDto]) =
|
||||||
|
let acc = self.getAccountByAddress(address)
|
||||||
|
if acc.isNil or not self.accountsTokens.hasKey(address):
|
||||||
|
return
|
||||||
|
let allBalancesForAllTokensHaveError = allBalancesForAllTokensHaveError(tokens)
|
||||||
|
let allMarketValuesForAllTokensHaveError = allMarketValuesForAllTokensHaveError(tokens)
|
||||||
|
|
||||||
|
for storedToken in self.accountsTokens[address]:
|
||||||
|
for token in tokens.mitems:
|
||||||
|
if storedToken.name == token.name:
|
||||||
|
if allBalancesForAllTokensHaveError:
|
||||||
|
token.balancesPerChain = storedToken.balancesPerChain
|
||||||
|
if allMarketValuesForAllTokensHaveError:
|
||||||
|
token.marketValuesPerCurrency = storedToken.marketValuesPerCurrency
|
||||||
|
|
||||||
|
proc getTokensByAddress*(self: Service, address: string): seq[WalletTokenDto] =
|
||||||
|
if not self.accountsTokens.hasKey(address):
|
||||||
|
return
|
||||||
|
return self.accountsTokens[address]
|
||||||
|
|
||||||
|
proc getTokensByAddresses*(self: Service, addresses: seq[string]): seq[WalletTokenDto] =
|
||||||
|
var tokens = initTable[string, WalletTokenDto]()
|
||||||
|
for address in addresses:
|
||||||
|
if not self.accountsTokens.hasKey(address):
|
||||||
|
continue
|
||||||
|
for token in self.accountsTokens[address]:
|
||||||
|
if not tokens.hasKey(token.symbol):
|
||||||
|
let newToken = token.copyToken()
|
||||||
|
tokens[token.symbol] = newToken
|
||||||
|
continue
|
||||||
|
|
||||||
|
for chainId, balanceDto in token.balancesPerChain:
|
||||||
|
if not tokens[token.symbol].balancesPerChain.hasKey(chainId):
|
||||||
|
tokens[token.symbol].balancesPerChain[chainId] = balanceDto
|
||||||
|
continue
|
||||||
|
|
||||||
|
tokens[token.symbol].balancesPerChain[chainId].balance += balanceDto.balance
|
||||||
|
|
||||||
|
result = toSeq(tokens.values)
|
||||||
|
result.sort(priorityTokenCmp)
|
||||||
|
|
||||||
|
proc onAllTokensBuilt*(self: Service, response: string) {.slot.} =
|
||||||
|
try:
|
||||||
|
var visibleSymbols: seq[string]
|
||||||
|
let chainIds = self.networkService.getNetworks().map(n => n.chainId)
|
||||||
|
|
||||||
|
let responseObj = response.parseJson
|
||||||
|
var storeResult: bool
|
||||||
|
var resultObj: JsonNode
|
||||||
|
discard responseObj.getProp("storeResult", storeResult)
|
||||||
|
discard responseObj.getProp("result", resultObj)
|
||||||
|
|
||||||
|
var data = TokensPerAccountArgs()
|
||||||
|
data.accountsTokens = initOrderedTable[string, seq[WalletTokenDto]]()
|
||||||
|
data.hasBalanceCache = false
|
||||||
|
data.hasMarketValuesCache = false
|
||||||
|
if resultObj.kind == JObject:
|
||||||
|
for wAddress, tokensDetailsObj in resultObj:
|
||||||
|
if tokensDetailsObj.kind == JArray:
|
||||||
|
var tokens: seq[WalletTokenDto]
|
||||||
|
tokens = map(tokensDetailsObj.getElems(), proc(x: JsonNode): WalletTokenDto = x.toWalletTokenDto())
|
||||||
|
tokens.sort(priorityTokenCmp)
|
||||||
|
self.updateReceivedTokens(wAddress, tokens)
|
||||||
|
let hasBalanceCache = anyTokenHasBalanceForAnyChain(tokens)
|
||||||
|
let hasMarketValuesCache = anyTokenHasMarketValuesForAnyChain(tokens)
|
||||||
|
data.accountsTokens[wAddress] = @[]
|
||||||
|
deepCopy(data.accountsTokens[wAddress], tokens)
|
||||||
|
data.hasBalanceCache = data.hasBalanceCache or hasBalanceCache
|
||||||
|
data.hasMarketValuesCache = data.hasMarketValuesCache or hasMarketValuesCache
|
||||||
|
|
||||||
|
# set assetsLoading to false once the tokens are loaded
|
||||||
|
self.updateAssetsLoadingState(wAddress, false)
|
||||||
|
|
||||||
|
if storeResult:
|
||||||
|
self.storeTokensForAccount(wAddress, tokens, hasBalanceCache, hasMarketValuesCache)
|
||||||
|
self.tokenService.updateTokenPrices(tokens) # For efficiency. Will be removed when token info fetching gets moved to the tokenService
|
||||||
|
# Gather symbol for visible tokens
|
||||||
|
for token in tokens:
|
||||||
|
if token.getVisibleForNetworkWithPositiveBalance(chainIds) and find(visibleSymbols, token.symbol) == -1:
|
||||||
|
visibleSymbols.add(token.symbol)
|
||||||
|
self.events.emit(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT, data)
|
||||||
|
if visibleSymbols.len > 0:
|
||||||
|
discard backend.updateVisibleTokens(visibleSymbols)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="onAllTokensBuilt", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc buildAllTokens(self: Service, accounts: seq[string], store: bool) =
|
||||||
|
if not main_constants.WALLET_ENABLED or
|
||||||
|
accounts.len == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# set assetsLoading to true as the tokens are being loaded
|
||||||
|
for waddress in accounts:
|
||||||
|
self.updateAssetsLoadingState(waddress, true)
|
||||||
|
|
||||||
|
let arg = BuildTokensTaskArg(
|
||||||
|
tptr: cast[ByteAddress](prepareTokensTask),
|
||||||
|
vptr: cast[ByteAddress](self.vptr),
|
||||||
|
slot: "onAllTokensBuilt",
|
||||||
|
accounts: accounts,
|
||||||
|
storeResult: store
|
||||||
|
)
|
||||||
|
self.threadpool.start(arg)
|
||||||
|
|
||||||
|
proc checkRecentHistory*(self: Service, addresses: seq[string]) =
|
||||||
|
if(not main_constants.WALLET_ENABLED):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
let chainIds = self.networkService.getNetworks().map(a => a.chainId)
|
||||||
|
status_go_transactions.checkRecentHistory(chainIds, addresses)
|
||||||
|
except Exception as e:
|
||||||
|
let errDescription = e.msg
|
||||||
|
error "error: ", errDescription
|
||||||
|
|
||||||
|
proc reloadAccountTokens*(self: Service) =
|
||||||
|
let addresses = self.getWalletAddresses()
|
||||||
|
self.buildAllTokens(addresses, store = true)
|
||||||
|
self.checkRecentHistory(addresses)
|
||||||
|
|
||||||
|
proc getCurrency*(self: Service): string =
|
||||||
|
return self.settingsService.getCurrency()
|
||||||
|
|
||||||
|
proc getCurrentCurrencyIfEmpty(self: Service, currency = ""): string =
|
||||||
|
if currency != "":
|
||||||
|
return currency
|
||||||
|
else:
|
||||||
|
return self.getCurrency()
|
||||||
|
|
||||||
|
proc getCurrencyBalance*(self: Service, address: string, chainIds: seq[int], currency: string): float64 =
|
||||||
|
if not self.accountsTokens.hasKey(address):
|
||||||
|
return
|
||||||
|
return self.accountsTokens[address].map(t => t.getCurrencyBalance(chainIds, currency)).foldl(a + b, 0.0)
|
||||||
|
|
||||||
|
proc getTotalCurrencyBalance*(self: Service, addresses: seq[string], currency: string = ""): float64 =
|
||||||
|
let chainIds = self.networkService.getNetworks().filter(a => a.enabled).map(a => a.chainId)
|
||||||
|
let accounts = self.getWalletAccounts().filter(w => addresses.contains(w.address))
|
||||||
|
return accounts.map(a => self.getCurrencyBalance(a.address, chainIds, self.getCurrentCurrencyIfEmpty(currency))).foldl(a + b, 0.0)
|
||||||
|
|
||||||
|
proc findTokenSymbolByAddress*(self: Service, address: string): string =
|
||||||
|
return self.tokenService.findTokenSymbolByAddress(address)
|
||||||
|
|
||||||
|
proc getOrFetchBalanceForAddressInPreferredCurrency*(self: Service, address: string): tuple[balance: float64, fetched: bool] =
|
||||||
|
let acc = self.getAccountByAddress(address)
|
||||||
|
if acc.isNil:
|
||||||
|
self.buildAllTokens(@[address], store = false)
|
||||||
|
result.balance = 0.0
|
||||||
|
result.fetched = false
|
||||||
|
return
|
||||||
|
let chainIds = self.networkService.getNetworks().map(n => n.chainId)
|
||||||
|
result.balance = self.getCurrencyBalance(acc.address, chainIds, self.getCurrentCurrencyIfEmpty())
|
||||||
|
result.fetched = true
|
||||||
|
|
||||||
|
proc getTokenBalanceOnChain*(self: Service, address: string, chainId: int, symbol: string): float64 =
|
||||||
|
if not self.accountsTokens.hasKey(address):
|
||||||
|
return 0.0
|
||||||
|
for token in self.accountsTokens[address]:
|
||||||
|
if token.symbol == symbol and token.balancesPerChain.hasKey(chainId):
|
||||||
|
return token.balancesPerChain[chainId].balance
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
proc allAccountsTokenBalance*(self: Service, symbol: string): float64 =
|
||||||
|
var totalTokenBalance = 0.0
|
||||||
|
for walletAccount in self.getWalletAccounts():
|
||||||
|
if walletAccount.walletType == WalletTypeWatch or
|
||||||
|
not self.accountsTokens.hasKey(walletAccount.address):
|
||||||
|
continue
|
||||||
|
for token in self.accountsTokens[walletAccount.address]:
|
||||||
|
if token.symbol == symbol:
|
||||||
|
totalTokenBalance += token.getTotalBalanceOfSupportedChains()
|
||||||
|
return totalTokenBalance
|
|
@ -0,0 +1,66 @@
|
||||||
|
#################################################
|
||||||
|
# Signals emitted by wallet account service
|
||||||
|
#################################################
|
||||||
|
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_SAVED* = "walletAccount/accountSaved"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_DELETED* = "walletAccount/accountDeleted"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_UPDATED* = "walletAccount/walletAccountUpdated"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_NETWORK_ENABLED_UPDATED* = "walletAccount/networkEnabledUpdated"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT* = "walletAccount/tokensRebuilt"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_TOKENS_BEING_FETCHED* = "walletAccount/tokenFetching"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FETCHED* = "walletAccount/derivedAddressesFetched"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_DERIVED_ADDRESSES_FROM_MNEMONIC_FETCHED* = "walletAccount/derivedAddressesFromMnemonicFetched"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_ADDRESS_DETAILS_FETCHED* = "walletAccount/addressDetailsFetched"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_POSITION_UPDATED* = "walletAccount/positionUpdated"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_OPERABILITY_UPDATED* = "walletAccount/operabilityUpdated"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_CHAIN_ID_FOR_URL_FETCHED* = "walletAccount/chainIdForUrlFetched"
|
||||||
|
const SIGNAL_WALLET_ACCOUNT_PREFERRED_SHARING_CHAINS_UPDATED* = "walletAccount/preferredSharingChainsUpdated"
|
||||||
|
|
||||||
|
const SIGNAL_KEYPAIR_SYNCED* = "keypairSynced"
|
||||||
|
const SIGNAL_KEYPAIR_NAME_CHANGED* = "keypairNameChanged"
|
||||||
|
|
||||||
|
const SIGNAL_NEW_KEYCARD_SET* = "newKeycardSet"
|
||||||
|
const SIGNAL_KEYCARD_REBUILD* = "keycardRebuild"
|
||||||
|
const SIGNAL_KEYCARD_DELETED* = "keycardDeleted"
|
||||||
|
const SIGNAL_ALL_KEYCARDS_DELETED* = "allKeycardsDeleted"
|
||||||
|
const SIGNAL_KEYCARD_ACCOUNTS_REMOVED* = "keycardAccountsRemoved"
|
||||||
|
const SIGNAL_KEYCARD_LOCKED* = "keycardLocked"
|
||||||
|
const SIGNAL_KEYCARD_UNLOCKED* = "keycardUnlocked"
|
||||||
|
const SIGNAL_KEYCARD_UID_UPDATED* = "keycardUidUpdated"
|
||||||
|
const SIGNAL_KEYCARD_NAME_CHANGED* = "keycardNameChanged"
|
||||||
|
|
||||||
|
#################################################
|
||||||
|
# Payload sent via above defined signals
|
||||||
|
#################################################
|
||||||
|
|
||||||
|
type AccountArgs* = ref object of Args
|
||||||
|
account*: WalletAccountDto
|
||||||
|
|
||||||
|
type KeypairArgs* = ref object of Args
|
||||||
|
keypair*: KeypairDto
|
||||||
|
oldKeypairName*: string
|
||||||
|
|
||||||
|
type KeycardArgs* = ref object of Args
|
||||||
|
success*: bool
|
||||||
|
oldKeycardUid*: string
|
||||||
|
keycard*: KeycardDto
|
||||||
|
|
||||||
|
type DerivedAddressesArgs* = ref object of Args
|
||||||
|
uniqueId*: string
|
||||||
|
derivedAddresses*: seq[DerivedAddressDto]
|
||||||
|
error*: string
|
||||||
|
|
||||||
|
type TokensPerAccountArgs* = ref object of Args
|
||||||
|
accountsTokens*: OrderedTable[string, seq[WalletTokenDto]] # [wallet address, list of tokens]
|
||||||
|
hasBalanceCache*: bool
|
||||||
|
hasMarketValuesCache*: bool
|
||||||
|
|
||||||
|
type KeycardActivityArgs* = ref object of Args
|
||||||
|
success*: bool
|
||||||
|
oldKeycardUid*: string
|
||||||
|
keycard*: KeycardDto
|
||||||
|
|
||||||
|
type ChainIdForUrlArgs* = ref object of Args
|
||||||
|
chainId*: int
|
||||||
|
success*: bool
|
||||||
|
url*: string
|
|
@ -0,0 +1,109 @@
|
||||||
|
#################################################
|
||||||
|
# Common functions
|
||||||
|
#################################################
|
||||||
|
|
||||||
|
proc priorityTokenCmp(a, b: WalletTokenDto): int =
|
||||||
|
for symbol in @["ETH", "SNT", "DAI", "STT"]:
|
||||||
|
if a.symbol == symbol:
|
||||||
|
return -1
|
||||||
|
if b.symbol == symbol:
|
||||||
|
return 1
|
||||||
|
cmp(a.name, b.name)
|
||||||
|
|
||||||
|
proc walletAccountsCmp(x, y: WalletAccountDto): int =
|
||||||
|
cmp(x.position, y.position)
|
||||||
|
|
||||||
|
proc hex2Balance*(input: string, decimals: int): string =
|
||||||
|
var value = fromHex(Stuint[256], input)
|
||||||
|
if decimals == 0:
|
||||||
|
return fmt"{value}"
|
||||||
|
var p = u256(10).pow(decimals)
|
||||||
|
var i = value.div(p)
|
||||||
|
var r = value.mod(p)
|
||||||
|
var leading_zeros = "0".repeat(decimals - ($r).len)
|
||||||
|
var d = fmt"{leading_zeros}{$r}"
|
||||||
|
result = $i
|
||||||
|
if(r > 0): result = fmt"{result}.{d}"
|
||||||
|
|
||||||
|
proc responseHasNoErrors(procName: string, response: RpcResponse[JsonNode]): bool =
|
||||||
|
var errMsg = ""
|
||||||
|
if not response.error.isNil:
|
||||||
|
errMsg = "(" & $response.error.code & ") " & response.error.message
|
||||||
|
elif response.result.kind == JObject and response.result.contains("error"):
|
||||||
|
errMsg = response.result["error"].getStr
|
||||||
|
if(errMsg.len == 0):
|
||||||
|
return true
|
||||||
|
error "error: ", procName=procName, errDesription = errMsg
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc allBalancesForAllTokensHaveError(tokens: seq[WalletTokenDto]): bool =
|
||||||
|
for token in tokens:
|
||||||
|
for chainId, balanceDto in token.balancesPerChain:
|
||||||
|
if not balanceDto.hasError:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc anyTokenHasBalanceForAnyChain(tokens: seq[WalletTokenDto]): bool =
|
||||||
|
for token in tokens:
|
||||||
|
if len(token.balancesPerChain) > 0:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
proc allMarketValuesForAllTokensHaveError(tokens: seq[WalletTokenDto]): bool =
|
||||||
|
for token in tokens:
|
||||||
|
for currency, marketDto in token.marketValuesPerCurrency:
|
||||||
|
if not marketDto.hasError:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
proc anyTokenHasMarketValuesForAnyChain(tokens: seq[WalletTokenDto]): bool =
|
||||||
|
for token in tokens:
|
||||||
|
if len(token.marketValuesPerCurrency) > 0:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
#################################################
|
||||||
|
# Remote functions
|
||||||
|
#################################################
|
||||||
|
|
||||||
|
proc getAccountsFromDb(): seq[WalletAccountDto] =
|
||||||
|
try:
|
||||||
|
let response = status_go_accounts.getAccounts()
|
||||||
|
return response.result.getElems().map(
|
||||||
|
x => x.toWalletAccountDto()
|
||||||
|
).filter(a => not a.isChat)
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="getAccounts", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc getWatchOnlyAccountsFromDb(): seq[WalletAccountDto] =
|
||||||
|
try:
|
||||||
|
let response = status_go_accounts.getWatchOnlyAccounts()
|
||||||
|
return response.result.getElems().map(x => x.toWalletAccountDto())
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="getWatchOnlyAccounts", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc getKeypairsFromDb(): seq[KeypairDto] =
|
||||||
|
try:
|
||||||
|
let response = status_go_accounts.getKeypairs()
|
||||||
|
return response.result.getElems().map(x => x.toKeypairDto())
|
||||||
|
except Exception as e:
|
||||||
|
error "error: ", procName="getKeypairs", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc getKeypairByKeyUidFromDb(keyUid: string): KeypairDto =
|
||||||
|
if keyUid.len == 0:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
let response = status_go_accounts.getKeypairByKeyUid(keyUid)
|
||||||
|
if not response.error.isNil:
|
||||||
|
return
|
||||||
|
return response.result.toKeypairDto()
|
||||||
|
except Exception as e:
|
||||||
|
info "no known keypair", keyUid=keyUid, procName="getKeypairByKeyUid", errName = e.name, errDesription = e.msg
|
||||||
|
|
||||||
|
proc getEnsName(address: string, chainId: int): string =
|
||||||
|
try:
|
||||||
|
let response = backend.getName(chainId, address)
|
||||||
|
return response.result.getStr
|
||||||
|
except Exception as e:
|
||||||
|
let errDesription = e.msg
|
||||||
|
error "error: ", errDesription
|
Loading…
Reference in New Issue