status-desktop/src/app/boot/app_controller.nim
Michal Iskierko b6f5c558a9 feat(@desktop:communities): Minting functionality with dummy data
Add Community Tokens testing UI with minting button, enabled by a Advanced Settings toggle.
Add minting module,view and needed models.
Add community_tokens service to call collectibles smart contract functions.

Issue #8921
2023-02-13 13:42:29 +01:00

575 lines
28 KiB
Nim
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import NimQml, sequtils, sugar, chronicles, os, uuids
import ../../app_service/service/general/service as general_service
import ../../app_service/service/keychain/service as keychain_service
import ../../app_service/service/keycard/service as keycard_service
import ../../app_service/service/accounts/service as accounts_service
import ../../app_service/service/contacts/service as contacts_service
import ../../app_service/service/language/service as language_service
import ../../app_service/service/chat/service as chat_service
import ../../app_service/service/community/service as community_service
import ../../app_service/service/message/service as message_service
import ../../app_service/service/token/service as token_service
import ../../app_service/service/currency/service as currency_service
import ../../app_service/service/transaction/service as transaction_service
import ../../app_service/service/collectible/service as collectible_service
import ../../app_service/service/wallet_account/service as wallet_account_service
import ../../app_service/service/bookmarks/service as bookmark_service
import ../../app_service/service/dapp_permissions/service as dapp_permissions_service
import ../../app_service/service/privacy/service as privacy_service
import ../../app_service/service/provider/service as provider_service
import ../../app_service/service/node/service as node_service
import ../../app_service/service/profile/service as profile_service
import ../../app_service/service/settings/service as settings_service
import ../../app_service/service/stickers/service as stickers_service
import ../../app_service/service/about/service as about_service
import ../../app_service/service/node_configuration/service as node_configuration_service
import ../../app_service/service/network/service as network_service
import ../../app_service/service/activity_center/service as activity_center_service
import ../../app_service/service/saved_address/service as saved_address_service
import ../../app_service/service/devices/service as devices_service
import ../../app_service/service/mailservers/service as mailservers_service
import ../../app_service/service/gif/service as gif_service
import ../../app_service/service/ens/service as ens_service
import ../../app_service/service/community_tokens/service as tokens_service
import ../../app_service/common/account_constants
import ../modules/startup/module as startup_module
import ../modules/main/module as main_module
import ../core/notifications/notifications_manager
import ../../constants as main_constants
import ../global/global_singleton
import ../core/[main]
logScope:
topics = "app-controller"
type
AppController* = ref object of RootObj
storeDefaultKeyPair: bool
syncKeycardBasedOnAppWalletState: bool
changedKeycardUids: seq[tuple[oldKcUid: string, newKcUid: string]] # used in case user unlocked keycard during onboarding using seed phrase
statusFoundation: StatusFoundation
notificationsManager*: NotificationsManager
keychainConnectionIds: seq[UUID]
# Global
appSettingsVariant: QVariant
localAppSettingsVariant: QVariant
localAccountSettingsVariant: QVariant
localAccountSensitiveSettingsVariant: QVariant
userProfileVariant: QVariant
globalUtilsVariant: QVariant
# Services
generalService: general_service.Service
keycardService*: keycard_service.Service
keychainService: keychain_service.Service
accountsService: accounts_service.Service
contactsService: contacts_service.Service
chatService: chat_service.Service
communityService: community_service.Service
messageService: message_service.Service
tokenService: token_service.Service
currencyService: currency_service.Service
transactionService: transaction_service.Service
collectibleService: collectible_service.Service
walletAccountService: wallet_account_service.Service
bookmarkService: bookmark_service.Service
dappPermissionsService: dapp_permissions_service.Service
providerService: provider_service.Service
profileService: profile_service.Service
settingsService: settings_service.Service
stickersService: stickers_service.Service
aboutService: about_service.Service
networkService: network_service.Service
activityCenterService: activity_center_service.Service
languageService: language_service.Service
# mnemonicService: mnemonic_service.Service
privacyService: privacy_service.Service
nodeConfigurationService: node_configuration_service.Service
savedAddressService: saved_address_service.Service
devicesService: devices_service.Service
mailserversService: mailservers_service.Service
nodeService: node_service.Service
gifService: gif_service.Service
ensService: ens_service.Service
tokensService: tokens_service.Service
# Modules
startupModule: startup_module.AccessInterface
mainModule: main_module.AccessInterface
#################################################
# Forward declaration section
proc load(self: AppController)
proc buildAndRegisterLocalAccountSensitiveSettings(self: AppController)
proc buildAndRegisterUserProfile(self: AppController)
# Startup Module Delegate Interface
proc startupDidLoad*(self: AppController)
proc userLoggedIn*(self: AppController): string
proc logout*(self: AppController)
proc finishAppLoading*(self: AppController)
proc storeDefaultKeyPairForNewKeycardUser*(self: AppController)
proc syncKeycardBasedOnAppWalletStateAfterLogin*(self: AppController)
proc addToKeycardUidPairsToCheckForAChangeAfterLogin*(self: AppController, oldKeycardUid: string, newKeycardUid: string)
proc removeAllKeycardUidPairsForCheckingForAChangeAfterLogin*(self: AppController)
# Main Module Delegate Interface
proc mainDidLoad*(self: AppController)
#################################################
proc connect(self: AppController) =
self.statusFoundation.events.once("nodeStopped") do(a: Args):
# not sure, but maybe we should take some actions when node stops
discard
# Handle runtime log level settings changes
if not existsEnv("LOG_LEVEL"):
self.statusFoundation.events.on(node_configuration_service.SIGNAL_NODE_LOG_LEVEL_UPDATE) do(a: Args):
let args = NodeLogLevelUpdatedArgs(a)
if args.logLevel == LogLevel.DEBUG:
setLogLevel(LogLevel.DEBUG)
elif defined(production):
setLogLevel(LogLevel.INFO)
proc newAppController*(statusFoundation: StatusFoundation): AppController =
result = AppController()
result.storeDefaultKeyPair = false
result.syncKeycardBasedOnAppWalletState = false
result.statusFoundation = statusFoundation
# Preparing settings service to be exposed later as global QObject
result.settingsService = settings_service.newService(statusFoundation.events)
result.appSettingsVariant = newQVariant(result.settingsService)
result.notificationsManager = newNotificationsManager(statusFoundation.events, result.settingsService)
# Global
result.localAppSettingsVariant = newQVariant(singletonInstance.localAppSettings)
result.localAccountSettingsVariant = newQVariant(singletonInstance.localAccountSettings)
result.localAccountSensitiveSettingsVariant = newQVariant(singletonInstance.localAccountSensitiveSettings)
result.userProfileVariant = newQVariant(singletonInstance.userProfile)
result.globalUtilsVariant = newQVariant(singletonInstance.utils)
# Services
result.generalService = general_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.activityCenterService = activity_center_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.keycardService = keycard_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration,
result.settingsService, statusFoundation.events)
result.keychainService = keychain_service.newService(statusFoundation.events)
result.accountsService = accounts_service.newService(statusFoundation.events, statusFoundation.threadpool,
statusFoundation.fleetConfiguration)
result.networkService = network_service.newService(statusFoundation.events, result.settingsService)
result.contactsService = contacts_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.networkService, result.settingsService,
result.activityCenterService
)
result.chatService = chat_service.newService(statusFoundation.events, statusFoundation.threadpool, result.contactsService)
result.tokenService = token_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.networkService
)
result.currencyService = currency_service.newService(result.tokenService, result.settingsService)
result.collectibleService = collectible_service.newService(statusFoundation.events, statusFoundation.threadpool, result.networkService)
result.walletAccountService = wallet_account_service.newService(
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
)
result.communityService = community_service.newService(statusFoundation.events,
statusFoundation.threadpool, result.chatService, result.activityCenterService, result.messageService)
result.transactionService = transaction_service.newService(statusFoundation.events, statusFoundation.threadpool, result.networkService, result.settingsService, result.tokenService)
result.bookmarkService = bookmark_service.newService(statusFoundation.events)
result.profileService = profile_service.newService(statusFoundation.events, result.settingsService)
result.stickersService = stickers_service.newService(
statusFoundation.events,
statusFoundation.threadpool,
result.settingsService,
result.walletAccountService,
result.transactionService,
result.networkService,
result.chatService,
result.tokenService
)
result.aboutService = about_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.dappPermissionsService = dapp_permissions_service.newService()
result.languageService = language_service.newService(statusFoundation.events)
# result.mnemonicService = mnemonic_service.newService()
result.privacyService = privacy_service.newService(statusFoundation.events, result.settingsService,
result.accountsService)
result.savedAddressService = saved_address_service.newService(statusFoundation.events, result.networkService)
result.devicesService = devices_service.newService(statusFoundation.events, result.settingsService)
result.mailserversService = mailservers_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.settingsService, result.nodeConfigurationService, statusFoundation.fleetConfiguration)
result.nodeService = node_service.newService(statusFoundation.events, result.settingsService, result.nodeConfigurationService)
result.gifService = gif_service.newService(result.settingsService)
result.ensService = ens_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.settingsService, result.walletAccountService, result.transactionService,
result.networkService, result.tokenService)
result.tokensService = tokens_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.networkService, result.transactionService)
result.providerService = provider_service.newService(statusFoundation.events, statusFoundation.threadpool, result.ensService)
# Modules
result.startupModule = startup_module.newModule[AppController](
result,
statusFoundation.events,
result.keychainService,
result.accountsService,
result.generalService,
result.profileService,
result.keycardService
)
result.mainModule = main_module.newModule[AppController](
result,
statusFoundation.events,
statusFoundation.urlsManager,
result.keychainService,
result.accountsService,
result.chatService,
result.communityService,
result.messageService,
result.tokenService,
result.currencyService,
result.transactionService,
result.collectibleService,
result.walletAccountService,
result.bookmarkService,
result.profileService,
result.settingsService,
result.contactsService,
result.aboutService,
result.dappPermissionsService,
result.languageService,
# result.mnemonicService,
result.privacyService,
result.providerService,
result.stickersService,
result.activityCenterService,
result.savedAddressService,
result.nodeConfigurationService,
result.devicesService,
result.mailserversService,
result.nodeService,
result.gifService,
result.ensService,
result.tokensService,
result.networkService,
result.generalService,
result.keycardService
)
# Do connections
result.connect()
proc delete*(self: AppController) =
info "logging out..."
self.generalService.logout()
singletonInstance.delete
self.notificationsManager.delete
self.keychainService.delete
self.contactsService.delete
self.bookmarkService.delete
self.gifService.delete
if not self.startupModule.isNil:
self.startupModule.delete
self.mainModule.delete
self.languageService.delete
self.appSettingsVariant.delete
self.localAppSettingsVariant.delete
self.localAccountSettingsVariant.delete
self.localAccountSensitiveSettingsVariant.delete
self.userProfileVariant.delete
self.globalUtilsVariant.delete
self.accountsService.delete
self.chatService.delete
self.communityService.delete
self.currencyService.delete
self.tokenService.delete
self.transactionService.delete
self.collectibleService.delete
self.walletAccountService.delete
self.aboutService.delete
self.networkService.delete
self.activityCenterService.delete
self.dappPermissionsService.delete
self.providerService.delete
self.nodeConfigurationService.delete
self.nodeService.delete
self.settingsService.delete
self.stickersService.delete
self.savedAddressService.delete
self.devicesService.delete
self.mailserversService.delete
self.messageService.delete
self.privacyService.delete
self.profileService.delete
self.generalService.delete
self.ensService.delete
self.tokensService.delete
self.gifService.delete
self.keycardService.delete
proc disconnectKeychain(self: AppController) =
for id in self.keychainConnectionIds:
self.statusFoundation.events.disconnect(id)
self.keychainConnectionIds = @[]
proc connectKeychain(self: AppController) =
var handlerId = self.statusFoundation.events.onWithUUID(SIGNAL_KEYCHAIN_SERVICE_SUCCESS) do(e:Args):
let args = KeyChainServiceArg(e)
self.disconnectKeychain()
## we need to set local `storeToKeychain` prop to `store` value since in this context means pass/pin is stored well
singletonInstance.localAccountSettings.setStoreToKeychainValue(LS_VALUE_STORE)
self.keychainConnectionIds.add(handlerId)
handlerId = self.statusFoundation.events.onWithUUID(SIGNAL_KEYCHAIN_SERVICE_ERROR) do(e:Args):
let args = KeyChainServiceArg(e)
self.disconnectKeychain()
## no need for any other activity in this context, local `storeToKeychain` prop remains as it was
## maybe in some point in future we add a popup letting user know about this
info "unable to store the data to keychain", errCode=args.errCode, errType=args.errType, errDesc=args.errDescription
self.keychainConnectionIds.add(handlerId)
proc checkForStoringPasswordToKeychain(self: AppController) =
## This proc is used to store pass/pin depends on user's selection during onboarding flow.
let account = self.accountsService.getLoggedInAccount()
let value = singletonInstance.localAccountSettings.getStoreToKeychainValue()
if not main_constants.IS_MACOS or # This is MacOS only feature
value == LS_VALUE_STORE or # means pass is already stored, no need to store it again
value == LS_VALUE_NEVER or # means pass doesn't need to be stored at all
account.name.len == 0:
return
# We are here if stored "storeToKeychain" property for the logged in user is either empty or set to "NotNow".
#TODO: we should store PubKey of this account instead of display name (display name is not unique)
# and we may run into a problem if 2 accounts with the same display name are generated.
self.connectKeychain()
let pass = self.startupModule.getPassword()
if pass.len > 0:
self.keychainService.storeData(account.name, pass)
else:
self.keychainService.storeData(account.name, self.startupModule.getPin())
proc startupDidLoad*(self: AppController) =
singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant)
singletonInstance.engine.setRootContextProperty("localAccountSettings", self.localAccountSettingsVariant)
singletonInstance.engine.setRootContextProperty("globalUtils", self.globalUtilsVariant)
singletonInstance.engine.load(newQUrl("qrc:///main.qml"))
# We need to init a language service once qml is loaded
self.languageService.init()
# We need this to set app width/height appropriatelly on the app start.
self.startupModule.startUpUIRaised()
proc mainDidLoad*(self: AppController) =
self.startupModule.moveToAppState()
self.checkForStoringPasswordToKeychain()
proc start*(self: AppController) =
self.keycardService.init()
self.keychainService.init()
self.generalService.init()
self.accountsService.init()
self.startupModule.load()
proc load(self: AppController) =
self.notificationsManager.init()
self.settingsService.init()
self.profileService.init()
self.nodeConfigurationService.init()
self.mailserversService.init()
self.contactsService.init()
self.chatService.init()
self.messageService.init()
self.communityService.init()
self.bookmarkService.init()
self.dappPermissionsService.init()
self.providerService.init()
self.transactionService.init()
self.stickersService.init()
self.activityCenterService.init()
self.savedAddressService.init()
self.aboutService.init()
self.devicesService.init()
self.ensService.init()
self.tokensService.init()
self.gifService.init()
# Accessible after user login
singletonInstance.engine.setRootContextProperty("appSettings", self.appSettingsVariant)
singletonInstance.engine.setRootContextProperty("globalUtils", self.globalUtilsVariant)
self.buildAndRegisterLocalAccountSensitiveSettings()
self.buildAndRegisterUserProfile()
self.networkService.init()
self.tokenService.init()
self.currencyService.init()
self.walletAccountService.init()
# Apply runtime log level settings
if not existsEnv("LOG_LEVEL"):
if self.nodeConfigurationService.isDebugEnabled():
setLogLevel(LogLevel.DEBUG)
# load main module
self.mainModule.load(
self.statusFoundation.events,
self.settingsService,
self.nodeConfigurationService,
self.contactsService,
self.chatService,
self.communityService,
self.messageService,
self.gifService,
self.mailserversService,
)
proc userLoggedIn*(self: AppController): string =
try:
self.generalService.startMessenger()
self.statusFoundation.userLoggedIn()
return ""
except Exception as e:
let errDescription = e.msg
error "error: ", errDescription
return errDescription
proc finishAppLoading*(self: AppController) =
self.load()
# Once user is logged in and main module is loaded we need to check if it gets here importing mnemonic or not
# and delete mnemonic in the first case.
let importedAccount = self.accountsService.getImportedAccount()
if(importedAccount.isValid()):
self.privacyService.removeMnemonic()
if not self.startupModule.isNil:
self.startupModule.delete
proc logout*(self: AppController) =
self.generalService.logout()
proc buildAndRegisterLocalAccountSensitiveSettings(self: AppController) =
var pubKey = self.settingsService.getPublicKey()
singletonInstance.localAccountSensitiveSettings.setFileName(pubKey)
singletonInstance.engine.setRootContextProperty("localAccountSensitiveSettings", self.localAccountSensitiveSettingsVariant)
proc buildAndRegisterUserProfile(self: AppController) =
let pubKey = self.settingsService.getPublicKey()
let alias = self.settingsService.getName()
var preferredName = self.settingsService.getPreferredName()
let displayName = self.settingsService.getDisplayName()
let currentUserStatus = self.settingsService.getCurrentUserStatus()
let loggedInAccount = self.accountsService.getLoggedInAccount()
var thumbnail, large: string
for img in loggedInAccount.images:
if(img.imgType == "large"):
large = img.uri
elif(img.imgType == "thumbnail"):
thumbnail = img.uri
singletonInstance.userProfile.setFixedData(alias, loggedInAccount.keyUid, pubKey, loggedInAccount.keycardPairing.len > 0)
singletonInstance.userProfile.setDisplayName(displayName)
singletonInstance.userProfile.setPreferredName(preferredName)
singletonInstance.userProfile.setThumbnailImage(thumbnail)
singletonInstance.userProfile.setLargeImage(large)
singletonInstance.userProfile.setCurrentUserStatus(currentUserStatus.statusType.int)
singletonInstance.engine.setRootContextProperty("userProfile", self.userProfileVariant)
############################################################################## store def kc sync with app kc uid
## Onboarding flows sync keycard state after login keypair | (inc. kp store) | update
## `Im new to Status` -> `Generate new keys` -> na | na | na
## `Im new to Status` -> `Generate keys for a new Keycard` -> yes | no | no
## `Im new to Status` -> `Import a seed phrase` -> `Import a seed phrase` -> na | na | na
## `Im new to Status` -> `Import a seed phrase` -> `Import a seed phrase into a new Keycard` -> yes | no | no
##
## `I already use Status` -> `Scan sync code` -> flow not developed yet
## `I already use Status` -> `I dont have other device` -> `Login with Keycard` (fetched) -> no | yes | no
## `I already use Status` -> `I dont have other device` -> `Login with Keycard` (unlock via puk, fetched) -> no | yes | no
## `I already use Status` -> `I dont have other device` -> `Login with Keycard` (unlock via seed phrase, fetched) -> no | yes | yes (kc details should be fetched and set to db while recovering, that's the reason why)
## `I already use Status` -> `I dont have other device` -> `Login with Keycard` (not fetched) -> no | yes | no
## `I already use Status` -> `I dont have other device` -> `Login with Keycard` (unlock via puk, not fetched) -> no | yes | no
## `I already use Status` -> `I dont have other device` -> `Login with Keycard` (unlock via seed phrase, not fetched) -> no | yes | no
## `I already use Status` -> `I dont have other device` -> `Enter a seed phrase` -> na | na | na
##
## `Login` -> na | na | na
## `Login` -> if card was unlocked via puk -> na | na | na
## `Login` -> if card was unlocked via seed phrase -> no | no | yes
## `Login` -> `Create replacement Keycard with seed phrase` -> no | yes | no (we don't know which kc is replaced if user has more kc for the same kp)
##############################################################################
if singletonInstance.userProfile.getIsKeycardUser():
if self.storeDefaultKeyPair:
let allAccounts = self.walletAccountService.fetchAccounts()
let defaultWalletAccounts = allAccounts.filter(a =>
a.walletType == WalletTypeDefaultStatusAccount and
a.path == account_constants.PATH_DEFAULT_WALLET and
not a.isChat and
a.isWallet
)
if defaultWalletAccounts.len == 0:
error "default wallet account was not generated"
return
let defaultWalletAddress = defaultWalletAccounts[0].address
let keyPair = KeyPairDto(keycardUid: self.keycardService.getLastReceivedKeycardData().flowEvent.instanceUID,
keycardName: displayName,
keycardLocked: false,
accountsAddresses: @[defaultWalletAddress],
keyUid: loggedInAccount.keyUid)
discard self.walletAccountService.addMigratedKeyPair(keyPair)
if self.syncKeycardBasedOnAppWalletState:
let allAccounts = self.walletAccountService.fetchAccounts()
let accountsForLoggedInUser = allAccounts.filter(a => a.keyUid == loggedInAccount.keyUid)
var keyPair = KeyPairDto(keycardUid: "",
keycardName: displayName,
keycardLocked: false,
accountsAddresses: @[],
keyUid: loggedInAccount.keyUid)
var activeValidPathsToStoreToAKeycard: seq[string]
for acc in accountsForLoggedInUser:
activeValidPathsToStoreToAKeycard.add(acc.path)
keyPair.accountsAddresses.add(acc.address)
self.keycardService.startStoreMetadataFlow(displayName, self.startupModule.getPin(), activeValidPathsToStoreToAKeycard)
## sleep for 3 seconds, since that is more than enough to store metadata to a keycard, if the reader is still plugged in
## and the card is still inserted, otherwise we just skip that.
## At the moment we're not able to sync later keycard without metadata, cause such card doesn't return instance uid for
## loaded seed phrase, that's in the notes I am taking for discussion with keycard team. If they are able to provide
## instance uid for GetMetadata flow we will be able to use SIGNAL_SHARED_KEYCARD_MODULE_TRY_KEYCARD_SYNC signal for syncing
## otherwise we need to handle that way separatelly in `handleKeycardSyncing` of shared module
sleep(3000)
self.keycardService.cancelCurrentFlow()
let (_, kcEvent) = self.keycardService.getLastReceivedKeycardData()
if kcEvent.instanceUID.len > 0:
keyPair.keycardUid = kcEvent.instanceUID
discard self.walletAccountService.addMigratedKeyPair(keyPair)
if self.changedKeycardUids.len > 0:
let oldUid = self.changedKeycardUids[0].oldKcUid
let newUid = self.changedKeycardUids[^1].newKcUid
discard self.walletAccountService.updateKeycardUid(oldUid, newUid)
discard self.walletAccountService.setKeycardUnlocked(loggedInAccount.keyUid, newUid)
proc storeDefaultKeyPairForNewKeycardUser*(self: AppController) =
self.storeDefaultKeyPair = true
proc syncKeycardBasedOnAppWalletStateAfterLogin*(self: AppController) =
self.syncKeycardBasedOnAppWalletState = true
proc addToKeycardUidPairsToCheckForAChangeAfterLogin*(self: AppController, oldKeycardUid: string, newKeycardUid: string) =
self.changedKeycardUids.add((oldKcUid: oldKeycardUid, newKcUid: newKeycardUid))
proc removeAllKeycardUidPairsForCheckingForAChangeAfterLogin*(self: AppController) =
self.changedKeycardUids = @[]