status-lib/status/wallet2.nim

213 lines
9.2 KiB
Nim

import json, strformat, options, chronicles, sugar, sequtils, strutils
import libstatus/accounts as status_accounts
import libstatus/accounts/constants as constants
import libstatus/tokens as status_tokens
import libstatus/wallet as status_wallet
import libstatus/settings as status_settings
import libstatus/eth/[contracts]
import wallet2/[balance_manager, collectibles]
import wallet2/account as wallet_account
import ./types/[account, transaction, network, setting, gas_prediction, rpc_response]
import ../eventemitter
from web3/ethtypes import Address
from web3/conversions import `$`
export wallet_account, collectibles
logScope:
topics = "status-wallet2"
type
CryptoServicesArg* = ref object of Args
services*: JsonNode # an array
type
StatusWalletController* = ref object
events: EventEmitter
accounts: seq[WalletAccount]
tokens: seq[Erc20Contract]
totalBalance*: float
# Forward declarations
proc initEvents*(self: StatusWalletController)
proc generateAccountConfiguredAssets*(self: StatusWalletController,
accountAddress: string): seq[Asset]
proc calculateTotalFiatBalance*(self: StatusWalletController)
proc setup(self: StatusWalletController, events: EventEmitter) =
self.events = events
self.accounts = @[]
self.tokens = @[]
self.totalBalance = 0.0
self.initEvents()
proc delete*(self: StatusWalletController) =
discard
proc newStatusWalletController*(events: EventEmitter):
StatusWalletController =
result = StatusWalletController()
result.setup(events)
proc initTokens(self: StatusWalletController) =
self.tokens = status_tokens.getVisibleTokens()
proc initAccounts(self: StatusWalletController) =
let accounts = status_wallet.getWalletAccounts()
for acc in accounts:
var assets: seq[Asset] = self.generateAccountConfiguredAssets(acc.address)
var walletAccount = newWalletAccount(acc.name, acc.address, acc.iconColor,
acc.path, acc.walletType, acc.publicKey, acc.wallet, acc.chat, assets)
self.accounts.add(walletAccount)
proc init*(self: StatusWalletController) =
self.initTokens()
self.initAccounts()
proc initEvents*(self: StatusWalletController) =
self.events.on("currencyChanged") do(e: Args):
self.events.emit("accountsUpdated", Args())
self.events.on("newAccountAdded") do(e: Args):
self.calculateTotalFiatBalance()
proc getAccounts*(self: StatusWalletController): seq[WalletAccount] =
self.accounts
proc getDefaultCurrency*(self: StatusWalletController): string =
# TODO: this should come from a model? It is going to be used too in the
# profile section and ideally we should not call the settings more than once
status_settings.getSetting[string](Setting.Currency, "usd")
proc generateAccountConfiguredAssets*(self: StatusWalletController,
accountAddress: string): seq[Asset] =
var assets: seq[Asset] = @[]
var asset = Asset(name:"Ethereum", symbol: "ETH", value: "0.0",
fiatBalanceDisplay: "0.0", accountAddress: accountAddress)
assets.add(asset)
for token in self.tokens:
var symbol = token.symbol
var existingToken = Asset(name: token.name, symbol: symbol,
value: fmt"0.0", fiatBalanceDisplay: "$0.0", accountAddress: accountAddress,
address: $token.address)
assets.add(existingToken)
assets
proc calculateTotalFiatBalance*(self: StatusWalletController) =
self.totalBalance = 0.0
for account in self.accounts:
if account.realFiatBalance.isSome:
self.totalBalance += account.realFiatBalance.get()
proc newAccount*(self: StatusWalletController, walletType: string, derivationPath: string,
name: string, address: string, iconColor: string, balance: string,
publicKey: string): WalletAccount =
var assets: seq[Asset] = self.generateAccountConfiguredAssets(address)
var account = WalletAccount(name: name, path: derivationPath, walletType: walletType,
address: address, iconColor: iconColor, balance: none[string](), assetList: assets,
realFiatBalance: none[float](), publicKey: publicKey)
updateBalance(account, self.getDefaultCurrency())
account
proc addNewGeneratedAccount(self: StatusWalletController, generatedAccount: GeneratedAccount,
password: string, accountName: string, color: string, accountType: string,
isADerivedAccount = true, walletIndex: int = 0) =
try:
generatedAccount.name = accountName
var derivedAccount: DerivedAccount = status_accounts.saveAccount(generatedAccount,
password, color, accountType, isADerivedAccount, walletIndex)
var account = self.newAccount(accountType, derivedAccount.derivationPath,
accountName, derivedAccount.address, color, fmt"0.00 {self.getDefaultCurrency()}",
derivedAccount.publicKey)
self.accounts.add(account)
# wallet_checkRecentHistory is required to be called when a new account is
# added before wallet_getTransfersByAddress can be called. This is because
# wallet_checkRecentHistory populates the status-go db that
# wallet_getTransfersByAddress reads from
discard status_wallet.checkRecentHistory(self.accounts.map(account => account.address))
self.events.emit("newAccountAdded", wallet_account.AccountArgs(account: account))
except Exception as e:
raise newException(StatusGoException, fmt"Error adding new account: {e.msg}")
proc generateNewAccount*(self: StatusWalletController, password: string, accountName: string, color: string) =
let
walletRootAddress = status_settings.getSetting[string](Setting.WalletRootAddress, "")
walletIndex = status_settings.getSetting[int](Setting.LatestDerivedPath) + 1
loadedAccount = status_accounts.loadAccount(walletRootAddress, password)
derivedAccount = status_accounts.deriveWallet(loadedAccount.id, walletIndex)
generatedAccount = GeneratedAccount(
id: loadedAccount.id,
publicKey: derivedAccount.publicKey,
address: derivedAccount.address
)
# if we've gotten here, the password is ok (loadAccount requires a valid password)
# so no need to check for a valid password
self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.GENERATED, true, walletIndex)
let statusGoResult = status_settings.saveSetting(Setting.LatestDerivedPath, $walletIndex)
if statusGoResult.error != "":
error "Error storing the latest wallet index", msg=statusGoResult.error
proc addAccountsFromSeed*(self: StatusWalletController, seed: string, password: string, accountName: string, color: string) =
let mnemonic = replace(seed, ',', ' ')
var generatedAccount = status_accounts.multiAccountImportMnemonic(mnemonic)
generatedAccount.derived = status_accounts.deriveAccounts(generatedAccount.id)
let
defaultAccount = status_accounts.getDefaultAccount()
isPasswordOk = status_accounts.verifyAccountPassword(defaultAccount, password)
if not isPasswordOk:
raise newException(StatusGoException, "Error generating new account: invalid password")
self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.SEED)
proc addAccountsFromPrivateKey*(self: StatusWalletController, privateKey: string, password: string, accountName: string, color: string) =
let
generatedAccount = status_accounts.MultiAccountImportPrivateKey(privateKey)
defaultAccount = status_accounts.getDefaultAccount()
isPasswordOk = status_accounts.verifyAccountPassword(defaultAccount, password)
if not isPasswordOk:
raise newException(StatusGoException, "Error generating new account: invalid password")
self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.KEY, false)
proc addWatchOnlyAccount*(self: StatusWalletController, address: string, accountName: string, color: string) =
let account = GeneratedAccount(address: address)
self.addNewGeneratedAccount(account, "", accountName, color, constants.WATCH, false)
proc changeAccountSettings*(self: StatusWalletController, address: string, accountName: string, color: string): string =
var selectedAccount: WalletAccount
for account in self.accounts:
if (account.address == address):
selectedAccount = account
break
if (isNil(selectedAccount)):
result = "No account found with that address"
error "No account found with that address", address
selectedAccount.name = accountName
selectedAccount.iconColor = color
result = status_accounts.changeAccount(selectedAccount.name, selectedAccount.address,
selectedAccount.publicKey, selectedAccount.walletType, selectedAccount.iconColor)
proc deleteAccount*(self: StatusWalletController, address: string): string =
result = status_accounts.deleteAccount(address)
self.accounts = self.accounts.filter(acc => acc.address.toLowerAscii != address.toLowerAscii)
proc getOpenseaCollections*(address: string): string =
result = status_wallet.getOpenseaCollections(address)
proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string =
result = status_wallet.getOpenseaAssets(address, collectionSlug, limit)
proc onAsyncFetchCryptoServices*(self: StatusWalletController, response: string) =
let responseArray = response.parseJson
if (responseArray.kind != JArray):
info "received crypto services is not a json array"
self.events.emit("cryptoServicesFetched", CryptoServicesArg())
return
self.events.emit("cryptoServicesFetched", CryptoServicesArg(services: responseArray))