status-lib/status/wallet2.nim

228 lines
9.6 KiB
Nim

import json, strformat, options, chronicles, sugar, sequtils, strutils
import statusgo_backend/accounts as status_accounts
import statusgo_backend/accounts/constants as constants
import statusgo_backend/wallet as status_wallet
import statusgo_backend/network as status_network
import statusgo_backend/settings as status_settings
import statusgo_backend/eth as eth
import eth/contracts
import wallet2/balance_manager
import eth/tokens as tokens_backend
import wallet2/account as wallet_account
import ./types/[account, transaction, network, network_type, setting, gas_prediction, rpc_response]
import ../eventemitter
from web3/ethtypes import Address
from web3/conversions import `$`
export wallet_account
logScope:
topics = "status-wallet2"
type
CryptoServicesArg* = ref object of Args
services*: JsonNode # an array
type
Wallet2Model* = ref object
events: EventEmitter
accounts: seq[WalletAccount]
networks*: seq[Network]
tokens: seq[Erc20Contract]
totalBalance*: float
# Forward declarations
proc initEvents*(self: Wallet2Model)
proc generateAccountConfiguredAssets*(self: Wallet2Model,
accountAddress: string): seq[Asset]
proc calculateTotalFiatBalance*(self: Wallet2Model)
proc setup(self: Wallet2Model, events: EventEmitter) =
self.events = events
self.accounts = @[]
self.tokens = @[]
self.networks = @[]
self.totalBalance = 0.0
self.initEvents()
proc delete*(self: Wallet2Model) =
discard
proc newWallet2Model*(events: EventEmitter): Wallet2Model =
result = Wallet2Model()
result.setup(events)
proc initTokens(self: Wallet2Model) =
let network = status_settings.getCurrentNetwork().toNetwork()
self.tokens = tokens_backend.getVisibleTokens(network)
proc initNetworks(self: Wallet2Model) =
self.networks = status_network.getNetworks()
proc initAccounts(self: Wallet2Model) =
let accounts = status_accounts.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: Wallet2Model) =
self.initTokens()
self.initNetworks()
self.initAccounts()
proc initEvents*(self: Wallet2Model) =
self.events.on("wallet2_currencyChanged") do(e: Args):
self.events.emit("wallet2_accountsUpdated", Args())
self.events.on("wallet2_newAccountAdded") do(e: Args):
self.calculateTotalFiatBalance()
proc getAccounts*(self: Wallet2Model): seq[WalletAccount] =
self.accounts
proc getDefaultCurrency*(self: Wallet2Model): 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: Wallet2Model,
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: Wallet2Model) =
self.totalBalance = 0.0
for account in self.accounts:
if account.realFiatBalance.isSome:
self.totalBalance += account.realFiatBalance.get()
proc newAccount*(self: Wallet2Model, 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: Wallet2Model, 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("wallet2_newAccountAdded", wallet_account.AccountArgs(account: account))
except Exception as e:
raise newException(StatusGoException, fmt"Error adding new account: {e.msg}")
proc generateNewAccount*(self: Wallet2Model, 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: Wallet2Model, seed: string, password: string, accountName: string, color: string, keystoreDir: string) =
let mnemonic = replace(seed, ',', ' ')
var generatedAccount = status_accounts.multiAccountImportMnemonic(mnemonic)
generatedAccount.derived = status_accounts.deriveAccounts(generatedAccount.id)
let
defaultAccount = eth.getDefaultAccount()
isPasswordOk = status_accounts.verifyAccountPassword(defaultAccount, password, keystoreDir)
if not isPasswordOk:
raise newException(StatusGoException, "Error generating new account: invalid password")
self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.SEED)
proc addAccountsFromPrivateKey*(self: Wallet2Model, privateKey: string, password: string, accountName: string, color: string, keystoreDir: string) =
let
generatedAccount = status_accounts.MultiAccountImportPrivateKey(privateKey)
defaultAccount = eth.getDefaultAccount()
isPasswordOk = status_accounts.verifyAccountPassword(defaultAccount, password, keystoreDir)
if not isPasswordOk:
raise newException(StatusGoException, "Error generating new account: invalid password")
self.addNewGeneratedAccount(generatedAccount, password, accountName, color, constants.KEY, false)
proc addWatchOnlyAccount*(self: Wallet2Model, address: string, accountName: string, color: string) =
let account = GeneratedAccount(address: address)
self.addNewGeneratedAccount(account, "", accountName, color, constants.WATCH, false)
proc changeAccountSettings*(self: Wallet2Model, 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: Wallet2Model, address: string): string =
result = status_accounts.deleteAccount(address)
self.accounts = self.accounts.filter(acc => acc.address.toLowerAscii != address.toLowerAscii)
proc getOpenseaCollections*(address: string): string =
let networkId = status_settings.getCurrentNetworkDetails().config.networkId
result = status_wallet.getOpenseaCollections(networkId, address)
proc getOpenseaAssets*(address: string, collectionSlug: string, limit: int): string =
let networkId = status_settings.getCurrentNetworkDetails().config.networkId
result = status_wallet.getOpenseaAssets(networkId, address, collectionSlug, limit)
proc onAsyncFetchCryptoServices*(self: Wallet2Model, response: string) =
let responseArray = response.parseJson
if (responseArray.kind != JArray):
info "received crypto services is not a json array"
self.events.emit("wallet2_cryptoServicesFetched", CryptoServicesArg())
return
self.events.emit("wallet2_cryptoServicesFetched", CryptoServicesArg(services: responseArray))
proc toggleNetwork*(self: Wallet2Model, network: Network) =
network.enabled = not network.enabled
status_network.upsertNetwork(network)