pragma Singleton import QtQuick 2.13 // Aliasing not to conflict with the shared.stores.RootStore import shared.stores 1.0 as Stores import utils 1.0 import StatusQ 0.1 import SortFilterProxyModel 0.2 import StatusQ.Core.Theme 0.1 import StatusQ.Core.Utils 0.1 as SQUtils QtObject { id: root property bool showSavedAddresses: false property string selectedAddress: "" readonly property bool showAllAccounts: !root.showSavedAddresses && !root.selectedAddress property var lastCreatedSavedAddress property bool addingSavedAddress: false property bool deletingSavedAddress: false readonly property TokensStore tokensStore: TokensStore {} readonly property WalletAssetsStore walletAssetsStore: WalletAssetsStore { walletTokensStore: tokensStore } /* This property holds address of currently selected account in Wallet Main layout */ readonly property var addressFilters: walletSectionInst.addressFilters readonly property var keypairImportModule: walletSectionInst.keypairImportModule readonly property string signingPhrase: walletSectionInst.signingPhrase readonly property string mnemonicBackedUp: walletSectionInst.isMnemonicBackedUp /* This property holds networks currently selected in the Wallet Main layout */ readonly property var networkFilters: networksModule.enabledChainIds readonly property string defaultSelectedKeyUid: userProfile.keyUid readonly property bool defaultSelectedKeyUidMigratedToKeycard: userProfile.isKeycardUser property string backButtonName: "" property var overview: walletSectionOverview property bool balanceLoading: overview.balanceLoading readonly property var accounts: walletSectionAccounts.accounts property var appSettings: localAppSettings property var accountSensitiveSettings: localAccountSensitiveSettings property bool hideSignPhraseModal: accountSensitiveSettings.hideSignPhraseModal property CollectiblesStore collectiblesStore: CollectiblesStore {} readonly property bool areTestNetworksEnabled: networksModule.areTestNetworksEnabled readonly property bool isGoerliEnabled: profileSectionModule.walletModule.networksModule.isGoerliEnabled property var savedAddresses: SortFilterProxyModel { sourceModel: walletSectionSavedAddresses.model filters: [ ValueFilter { roleName: "isTest" value: networksModule.areTestNetworksEnabled } ] } property var nonWatchAccounts: SortFilterProxyModel { sourceModel: accounts proxyRoles: [ FastExpressionRole { name: "color" function getColor(colorId) { return Utils.getColorForId(colorId) } // Direct call for singleton function is not handled properly by // SortFilterProxyModel that's why helper function is used instead. expression: { return getColor(model.colorId) } expectedRoles: ["colorId"] } ] filters: ValueFilter { roleName: "canSend" value: true } } readonly property var currentActivityFiltersStore: { const address = root.overview.mixedcaseAddress if (address in d.activityFiltersStoreDictionary) { return d.activityFiltersStoreDictionary[address] } let store = d.activityFilterStoreComponent.createObject(root) d.activityFiltersStoreDictionary[address] = store return store } // "walletSection" is a context property slow to lookup, so we cache it here readonly property var walletSectionInst: walletSection readonly property var totalCurrencyBalance: walletSectionInst.totalCurrencyBalance readonly property var activityController: walletSectionInst.activityController readonly property var tmpActivityController0: walletSectionInst.tmpActivityController0 readonly property var tmpActivityController1: walletSectionInst.tmpActivityController1 readonly property var activityDetailsController: walletSectionInst.activityDetailsController readonly property var walletConnectController: walletSectionInst.walletConnectController readonly property bool isAccountTokensReloading: walletSectionInst.isAccountTokensReloading readonly property double lastReloadTimestamp: walletSectionInst.lastReloadTimestamp signal savedAddressAddedOrUpdated(added: bool, name: string, address: string, errorMsg: string) signal savedAddressDeleted(name: string, address: string, errorMsg: string) property QtObject _d: QtObject { id: d readonly property var mainModuleInst: mainModule readonly property var walletSectionSavedAddressesInst: walletSectionSavedAddresses readonly property Connections walletSectionSavedAddressesConnections: Connections{ target: walletSectionSavedAddresses function onSavedAddressAddedOrUpdated(added: bool, name: string, address: string, errorMsg: string) { root.savedAddressAddedOrUpdated(added, name, address , errorMsg); } function onSavedAddressDeleted(name: string, address: string, errorMsg: string) { root.savedAddressDeleted(name, address, errorMsg) } } property var activityFiltersStoreDictionary: ({}) readonly property Component activityFilterStoreComponent: ActivityFiltersStore{ tokensList: walletAssetsStore.groupedAccountAssetsModel } property var chainColors: ({}) function initChainColors(model) { for (let i = 0; i < model.count; i++) { const item = SQUtils.ModelUtils.get(model, i) chainColors[item.shortName] = item.chainColor } } readonly property Connections walletSectionConnections: Connections { target: root.walletSectionInst function onWalletAccountRemoved(address) { address = address.toLowerCase(); for (var addressKey in d.activityFiltersStoreDictionary){ if (address === addressKey.toLowerCase()){ delete d.activityFiltersStoreDictionary[addressKey] return } } } } } function colorForChainShortName(chainShortName) { return d.chainColors[chainShortName] } readonly property var flatNetworks: networksModule.flatNetworks readonly property SortFilterProxyModel filteredFlatModel: SortFilterProxyModel { sourceModel: root.flatNetworks filters: ValueFilter { roleName: "isTest"; value: root.areTestNetworksEnabled } } onFlatNetworksChanged: { d.initChainColors(flatNetworks) } property var cryptoRampServicesModel: walletSectionBuySellCrypto.model function resetCurrentViewedHolding(type) { currentViewedHoldingTokensKey = "" currentViewedHoldingID = "" currentViewedHoldingType = type } function setCurrentViewedHoldingType(type) { currentViewedHoldingTokensKey = "" currentViewedHoldingID = "" currentViewedHoldingType = type } function setCurrentViewedHolding(id, tokensKey, type) { currentViewedHoldingTokensKey = tokensKey currentViewedHoldingID = id currentViewedHoldingType = type } property string currentViewedHoldingTokensKey: "" /* TODO: should get rid if this eventually, we shouldnt be using token symbols everywhere. Adding a new one currentViewedHoldingTokensKey aboce to not impact send/bridge flows */ property string currentViewedHoldingID: "" property int currentViewedHoldingType readonly property var currentViewedCollectible: collectiblesStore.detailedCollectible function canProfileProveOwnershipOfProvidedAddresses(addresses) { return walletSection.canProfileProveOwnershipOfProvidedAddresses(JSON.stringify(addresses)) } function setHideSignPhraseModal(value) { localAccountSensitiveSettings.hideSignPhraseModal = value; } function getLatestBlockNumber(chainId) { // NOTE returns hex return walletSection.getLatestBlockNumber(chainId) } function getEstimatedLatestBlockNumber(chainId) { // NOTE returns decimal return walletSection.getEstimatedLatestBlockNumber(chainId) } function setFilterAddress(address) { walletSection.setFilterAddress(address) } function setFilterAllAddresses() { walletSectionInst.setFilterAllAddresses() } function deleteAccount(address) { return walletSectionAccounts.deleteAccount(address) } function updateCurrency(newCurrency) { walletSection.updateCurrency(newCurrency) } function getQrCode(address) { return globalUtils.qrCode(address) } function getNameForWalletAddress(address) { return walletSectionAccounts.getNameByAddress(address) } function getWalletAccount(address) { const defaultValue = { name: "", address: "", mixedcaseAddress: "", keyUid: "", path: "", colorId: Constants.walletAccountColors.primary, publicKey: "", walletType: "", isWallet: false, isChat: false, emoji: "", ens: "", assetsLoading: false, removed: "", operable: "", createdAt: -1, position: -1, prodPreferredChainIds: "", testPreferredChainIds: "", hideFromTotalBalance: false } const jsonObj = walletSectionAccounts.getWalletAccountAsJson(address) try { if (jsonObj === "null" || jsonObj === undefined) { return defaultValue } return JSON.parse(jsonObj) } catch (e) { console.warn("error parsing wallet account for address: ", address, " error: ", e.message) return defaultValue } } function getSavedAddress(address) { const defaultValue = { name: "", address: "", ens: "", colorId: Constants.walletAccountColors.primary, chainShortNames: "", isTest: false, } const jsonObj = d.walletSectionSavedAddressesInst.getSavedAddressAsJson(address) try { return JSON.parse(jsonObj) } catch (e) { console.warn("error parsing saved address for address: ", address, " error: ", e.message) return defaultValue } } function getNameForAddress(address) { var name = getNameForWalletAddress(address) if (name.length === 0) { let savedAddress = getSavedAddress(address) name = savedAddress.name } return name } function getAssetForSendTx(tx) { if (tx.isNFT) { return { uid: tx.tokenID, chainId: tx.chainId, name: tx.nftName, imageUrl: tx.nftImageUrl, collectionUid: "", collectionName: "" } } else { return tx.symbol } } function isTxRepeatable(tx) { if (!tx || tx.txType !== Constants.TransactionType.Send) return false let res = SQUtils.ModelUtils.getByKey(root.accounts, "address", tx.sender) if (!res || res.object.walletType === Constants.watchWalletType) return false if (tx.isNFT) { // TODO #12275: check if account owns enough NFT } else { // TODO #12275: Check if account owns enough tokens } return true } function isOwnedAccount(address) { return walletSectionAccounts.isOwnedAccount(address) } function getEmojiForWalletAddress(address) { return walletSectionAccounts.getEmojiByAddress(address) } function getColorForWalletAddress(address) { return walletSectionAccounts.getColorByAddress(address) } function createOrUpdateSavedAddress(name, address, ens, colorId, chainShortNames) { root.addingSavedAddress = true walletSectionSavedAddresses.createOrUpdateSavedAddress(name, address, ens, colorId, chainShortNames) } function updatePreferredChains(address, chainShortNames) { walletSectionSavedAddresses.updatePreferredChains(address, chainShortNames) } function deleteSavedAddress(address) { root.deletingSavedAddress = true walletSectionSavedAddresses.deleteSavedAddress(address) } function savedAddressNameExists(name) { return walletSectionSavedAddresses.savedAddressNameExists(name) } function toggleNetwork(chainId) { networksModule.toggleNetwork(chainId) } function copyToClipboard(text) { globalUtils.copyToClipboard(text) } function runAddAccountPopup() { walletSection.runAddAccountPopup(false) } function runAddWatchOnlyAccountPopup() { walletSection.runAddAccountPopup(true) } function runEditAccountPopup(address) { walletSection.runEditAccountPopup(address) } function toggleWatchOnlyAccounts() { walletSection.toggleWatchOnlyAccounts() } function getAllNetworksChainIds() { let result = [] let chainIdsArray = SQUtils.ModelUtils.modelToFlatArray(root.filteredFlatModel, "chainId") for(let i = 0; i< chainIdsArray.length; i++) { result.push(chainIdsArray[i].toString()) } return result } function getNetworkShortNames(chainIds) { return networksModule.getNetworkShortNames(chainIds) } function getNetworkIds(shortNames) { return networksModule.getNetworkIds(shortNames) } function updateWalletAccountPreferredChains(address, preferredChainIds) { if(areTestNetworksEnabled) { walletSectionAccounts.updateWalletAccountTestPreferredChains(address, preferredChainIds) } else { walletSectionAccounts.updateWalletAccountProdPreferredChains(address, preferredChainIds) } } function updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance) { walletSectionAccounts.updateWatchAccountHiddenFromTotalBalance(address, hideFromTotalBalance) } property Stores.CurrenciesStore currencyStore: Stores.CurrenciesStore {} function addressWasShown(address) { return d.mainModuleInst.addressWasShown(address) } function getExplorerDomain(networkShortName) { let link = Constants.networkExplorerLinks.etherscan if (networkShortName === Constants.networkShortChainNames.mainnet) { if (root.areTestNetworksEnabled) { if (!root.isGoerliEnabled) { link = Constants.networkExplorerLinks.sepoliaEtherscan } else { link = Constants.networkExplorerLinks.goerliEtherscan } } } if (networkShortName === Constants.networkShortChainNames.arbitrum) { link = Constants.networkExplorerLinks.arbiscan if (root.areTestNetworksEnabled) { if (!root.isGoerliEnabled) { link = Constants.networkExplorerLinks.sepoliaArbiscan } else { link = Constants.networkExplorerLinks.goerliArbiscan } } } else if (networkShortName === Constants.networkShortChainNames.optimism) { link = Constants.networkExplorerLinks.optimism if (root.areTestNetworksEnabled) { if (!root.isGoerliEnabled) { link = Constants.networkExplorerLinks.sepoliaOptimism } else { link = Constants.networkExplorerLinks.goerliOptimism } } } return link } function getExplorerUrl(networkShortName, contractAddress, tokenId) { let link = getExplorerDomain(networkShortName) if (networkShortName === Constants.networkShortChainNames.mainnet) { return "%1/nft/%2/%3".arg(link).arg(contractAddress).arg(tokenId) } else { return "%1/token/%2?a=%3".arg(link).arg(contractAddress).arg(tokenId) } } function getExplorerNameForNetwork(networkShortName) { if (networkShortName === Constants.networkShortChainNames.arbitrum) { return qsTr("Arbiscan Explorer") } if (networkShortName === Constants.networkShortChainNames.optimism) { return qsTr("Optimism Explorer") } return qsTr("Etherscan Explorer") } function getOpenSeaNetworkName(networkShortName) { let networkName = Constants.openseaExplorerLinks.ethereum if (networkShortName === Constants.networkShortChainNames.mainnet) { if (root.areTestNetworksEnabled) { if (!root.isGoerliEnabled) { networkName = Constants.openseaExplorerLinks.sepoliaEthereum } else { networkName = Constants.openseaExplorerLinks.goerliEthereum } } } if (networkShortName === Constants.networkShortChainNames.arbitrum) { networkName = Constants.openseaExplorerLinks.arbitrum if (root.areTestNetworksEnabled) { if (!root.isGoerliEnabled) { networkName = Constants.openseaExplorerLinks.sepoliaArbitrum } else { networkName = Constants.openseaExplorerLinks.goerliArbitrum } } } else if (networkShortName === Constants.networkShortChainNames.optimism) { networkName = Constants.openseaExplorerLinks.optimism if (root.areTestNetworksEnabled) { if (!root.isGoerliEnabled) { networkName = Constants.openseaExplorerLinks.sepoliaOptimism } else { networkName = Constants.openseaExplorerLinks.goerliOptimism } } } return networkName } function getOpenseaDomainName() { return root.areTestNetworksEnabled ? Constants.openseaExplorerLinks.testnetLink : Constants.openseaExplorerLinks.mainnetLink } function getOpenSeaCollectionUrl(networkShortName, contractAddress) { let networkName = getOpenSeaNetworkName(networkShortName) let baseLink = root.areTestNetworksEnabled ? Constants.openseaExplorerLinks.testnetLink : Constants.openseaExplorerLinks.mainnetLink return "%1/assets/%2/%3".arg(baseLink).arg(networkName).arg(contractAddress) } function getOpenSeaCollectibleUrl(networkShortName, contractAddress, tokenId) { let networkName = getOpenSeaNetworkName(networkShortName) let baseLink = root.areTestNetworksEnabled ? Constants.openseaExplorerLinks.testnetLink : Constants.openseaExplorerLinks.mainnetLink return "%1/assets/%2/%3/%4".arg(baseLink).arg(networkName).arg(contractAddress).arg(tokenId) } function getTwitterLink(twitterHandle) { const prefix = Constants.socialLinkPrefixesByType[Constants.socialLinkType.twitter] return prefix + twitterHandle } function transactionType(transaction) { // Cross chain Send to another recipient is not a bridge, though involves bridging if (transaction.txType == Constants.TransactionType.Bridge && transaction.sender !== transaction.recipient) { if (root.showAllAccounts) { const addresses = root.addressFilters if (addresses.indexOf(transaction.sender) > -1) return Constants.TransactionType.Send return Constants.TransactionType.Receive } return addressesEqual(root.selectedAddress, transaction.sender) ? Constants.TransactionType.Send : Constants.TransactionType.Receive } return transaction.txType } }