diff --git a/src/app/global/feature_flags.nim b/src/app/global/feature_flags.nim index 54f2005216..3c85d47f4f 100644 --- a/src/app/global/feature_flags.nim +++ b/src/app/global/feature_flags.nim @@ -4,6 +4,7 @@ import os const DEFAULT_FLAG_DAPPS_ENABLED = false const DEFAULT_FLAG_SWAP_ENABLED = true const DEFAULT_FLAG_CONNECTOR_ENABLED = true +const DEFAULT_FLAG_MULTI_TX_ENABLED = false proc boolToEnv(defaultValue: bool): string = return if defaultValue: "1" else: "0" @@ -13,12 +14,14 @@ QtObject: dappsEnabled: bool swapEnabled: bool connectorEnabled: bool + multiTxEnabled: bool proc setup(self: FeatureFlags) = self.QObject.setup() self.dappsEnabled = getEnv("FLAG_DAPPS_ENABLED", boolToEnv(DEFAULT_FLAG_DAPPS_ENABLED)) != "0" self.swapEnabled = getEnv("FLAG_SWAP_ENABLED", boolToEnv(DEFAULT_FLAG_SWAP_ENABLED)) != "0" self.connectorEnabled = getEnv("FLAG_CONNECTOR_ENABLED", boolToEnv(DEFAULT_FLAG_CONNECTOR_ENABLED)) != "0" + self.multiTxEnabled = getEnv("FLAG_MULTI_TX_ENABLED", boolToEnv(DEFAULT_FLAG_MULTI_TX_ENABLED)) != "0" proc delete*(self: FeatureFlags) = self.QObject.delete() @@ -44,3 +47,10 @@ QtObject: QtProperty[bool] connectorEnabled: read = getConnectorEnabled + + + proc getMultiTxEnabled*(self: FeatureFlags): bool {.slot.} = + return self.multiTxEnabled + + QtProperty[bool] multiTxEnabled: + read = getMultiTxEnabled \ No newline at end of file diff --git a/src/app/global/global_singleton.nim b/src/app/global/global_singleton.nim index b7a2530d63..9f68127731 100644 --- a/src/app/global/global_singleton.nim +++ b/src/app/global/global_singleton.nim @@ -16,6 +16,7 @@ export user_profile export utils export global_events export loader_deactivator +export feature_flags type GlobalSingleton = object diff --git a/src/app/modules/main/wallet_section/send/module.nim b/src/app/modules/main/wallet_section/send/module.nim index cfe5019bd3..d8043cff70 100644 --- a/src/app/modules/main/wallet_section/send/module.nim +++ b/src/app/modules/main/wallet_section/send/module.nim @@ -21,6 +21,8 @@ import app/modules/shared_models/collectibles_model as collectibles import app/modules/shared_models/collectibles_nested_model as nested_collectibles import backend/collectibles as backend_collectibles +import constants as main_constants + export io_interface logScope: @@ -109,7 +111,7 @@ proc convertSendToNetworkToNetworkItem(self: Module, network: SendToNetwork): Ne proc convertNetworkDtoToNetworkRouteItem(self: Module, network: network_service_item.NetworkItem): NetworkRouteItem = result = initNetworkRouteItem( - network.chainId, + network.chainId, network.layer, true, false, @@ -220,7 +222,7 @@ method authenticateAndTransferWithPaths*(self: Module, fromAddr: string, toAddr: # Temporary until transaction service rework is completed let pathsV2 = rawPaths.toTransactionPathsDtoV2() let pathsV1 = pathsV2.convertToOldRoute().addFirstSimpleBridgeTxFlag() - + self.tmpSendTransactionDetails.paths = pathsV1 self.tmpSendTransactionDetails.slippagePercentage = slippagePercentage self.authenticateAndTransfer(fromAddr, toAddr, assetKey, toAssetKey, uuid, sendType, selectedTokenName, selectedTokenIsOwnerToken) @@ -310,6 +312,29 @@ method suggestedRoutes*(self: Module, disabledToChainIDs: seq[int] = @[], lockedInAmounts: Table[string, string] = initTable[string, string](), extraParamsTable: Table[string, string] = initTable[string, string]()) = + + var + finalLockedInAmounts = lockedInAmounts + finalDisbaledFromChains = disabledFromChainIDs + finalDisbaledToChains = disabledToChainIDs + + if not singletonInstance.featureFlags.getMultiTxEnabled(): + finalLockedInAmounts.clear() + + const maxDisabledChains = 2 # at any given time, only 2 chains can be disabled + + if finalDisbaledFromChains.len != maxDisabledChains: + if self.controller.areTestNetworksEnabled(): + finalDisbaledFromChains = @[main_constants.SEPOLIA_OPTIMISM_CHAIN_ID, main_constants.SEPOLIA_ARBITRUM_CHAIN_ID] + else: + finalDisbaledFromChains = @[main_constants.OPTIMISM_CHAIN_ID, main_constants.ARBITRUM_CHAIN_ID] + + if finalDisbaledToChains.len != maxDisabledChains: + if self.controller.areTestNetworksEnabled(): + finalDisbaledToChains = @[main_constants.SEPOLIA_OPTIMISM_CHAIN_ID, main_constants.SEPOLIA_ARBITRUM_CHAIN_ID] + else: + finalDisbaledToChains = @[main_constants.OPTIMISM_CHAIN_ID, main_constants.ARBITRUM_CHAIN_ID] + self.controller.suggestedRoutes( uuid, sendType, @@ -319,9 +344,9 @@ method suggestedRoutes*(self: Module, amountIn, toToken, amountOut, - disabledFromChainIDs, - disabledToChainIDs, - lockedInAmounts, + finalDisbaledFromChains, + finalDisbaledToChains, + finalLockedInAmounts, extraParamsTable ) diff --git a/src/constants.nim b/src/constants.nim index a13bd4f848..22fd3e10d5 100644 --- a/src/constants.nim +++ b/src/constants.nim @@ -76,3 +76,10 @@ proc runtimeLogLevelSet*(): bool = return existsEnv(RUN_TIME_PREFIX & "_LOG_LEVEL") or hasLogLevelOption() const MAIN_STATUS_SHARD_CLUSTER_ID* = 16 + +const MAINNET_CHAIN_ID* = 1 +const SEPOLIA_CHAIN_ID* = 11155111 +const OPTIMISM_CHAIN_ID* = 10 +const SEPOLIA_OPTIMISM_CHAIN_ID* = 11155420 +const ARBITRUM_CHAIN_ID* = 42161 +const SEPOLIA_ARBITRUM_CHAIN_ID* = 421614 \ No newline at end of file diff --git a/ui/imports/shared/popups/send/SendModal.qml b/ui/imports/shared/popups/send/SendModal.qml index ae743741b9..02af0262b1 100644 --- a/ui/imports/shared/popups/send/SendModal.qml +++ b/ui/imports/shared/popups/send/SendModal.qml @@ -158,6 +158,26 @@ StatusDialog { recalculateRoutesAndFees() } + + // This function is used in single selected chain mode, not for multitx sending. + // Providing correct chainId will make that chain selected, otherwise the mainnet chain will be selecte as default one. + function changeSelectedChain(chainId) { + if (Global.featureFlags.multiTxEnabled) { + return + } + for( var i = 0; i < fromNetworksRouteModel.rowCount(); i++ ) { + const item = SQUtils.ModelUtils.get(fromNetworksRouteModel, i) + if (item.chainId === chainId) { + store.setRouteDisabledFromChains(item.chainId, false) + store.setRouteDisabledToChains(item.chainId, false) + continue + } + store.setRouteDisabledFromChains(item.chainId, true) + store.setRouteDisabledToChains(item.chainId, true) + } + + popup.recalculateRoutesAndFees() + } } LeftJoinModel { @@ -549,6 +569,12 @@ StatusDialog { onIsLoading: popup.isLoading = true onRecalculateRoutesAndFees: popup.recalculateRoutesAndFees() onAddressTextChanged: store.setSelectedRecipient(addressText) + + fromNetworksList: fromNetworksRouteModel + + onChangeSelectedChain: { + d.changeSelectedChain(chainId) + } } } } @@ -634,6 +660,10 @@ StatusDialog { totalFeesInFiat: d.totalFeesInFiat fromNetworksList: fromNetworksRouteModel toNetworksList: toNetworksRouteModel + + onChangeSelectedChain:{ + d.changeSelectedChain(chainId) + } } } } diff --git a/ui/imports/shared/popups/send/views/NetworkCardsComponent.qml b/ui/imports/shared/popups/send/views/NetworkCardsComponent.qml index d534b14f77..6747f01fa1 100644 --- a/ui/imports/shared/popups/send/views/NetworkCardsComponent.qml +++ b/ui/imports/shared/popups/send/views/NetworkCardsComponent.qml @@ -32,6 +32,8 @@ Item { property int errorType: Constants.NoError property bool isLoading + signal changeSelectedChain(int chainId) + QtObject { id: d property double customAmountToSend: 0 @@ -114,15 +116,20 @@ Item { (root.errorMode || !advancedInput.valid) && advancedInputCurrencyAmount > 0 ? "error" : "default" cardIcon.source: Style.svg(model.iconUrl) disabledText: qsTr("Disabled") - disableText: qsTr("Disable") - enableText: qsTr("Enable") + disableText: Global.featureFlags.multiTxEnabled? qsTr("Disable") : "" + enableText: Global.featureFlags.multiTxEnabled? qsTr("Enable") : qsTr("Select") advancedMode: root.customMode disabled: !model.isRouteEnabled - clickable: root.interactive + clickable: Global.featureFlags.multiTxEnabled && root.interactive || + !Global.featureFlags.multiTxEnabled && disabled onClicked: { - store.toggleFromDisabledChains(model.chainId) - store.lockCard(model.chainId, 0, false) - root.reCalculateSuggestedRoute() + if (!Global.featureFlags.multiTxEnabled) { + root.changeSelectedChain(model.chainId) + } else { + store.toggleFromDisabledChains(model.chainId) + store.lockCard(model.chainId, 0, false) + root.reCalculateSuggestedRoute() + } } onLockCard: { let amount = lock ? (advancedInputCurrencyAmount * Math.pow(10, root.selectedAsset.decimals)).toString(16) : "" @@ -183,14 +190,19 @@ Item { opacity: preferred || store.showUnPreferredChains ? 1 : 0 cardIcon.source: Style.svg(model.iconUrl) disabledText: qsTr("Disabled") - disableText: qsTr("Disable") - enableText: qsTr("Enable") + disableText: Global.featureFlags.multiTxEnabled? qsTr("Disable") : "" + enableText: Global.featureFlags.multiTxEnabled? qsTr("Enable") : qsTr("Select") disabled: !model.isRouteEnabled - clickable: root.interactive + clickable: Global.featureFlags.multiTxEnabled && root.interactive || + !Global.featureFlags.multiTxEnabled && disabled loading: root.isLoading onClicked: { - store.toggleToDisabledChains(model.chainId) - root.reCalculateSuggestedRoute() + if (!Global.featureFlags.multiTxEnabled) { + root.changeSelectedChain(model.chainId) + } else { + store.toggleToDisabledChains(model.chainId) + root.reCalculateSuggestedRoute() + } } } } diff --git a/ui/imports/shared/popups/send/views/NetworkSelector.qml b/ui/imports/shared/popups/send/views/NetworkSelector.qml index f8adb9dcde..e551941259 100644 --- a/ui/imports/shared/popups/send/views/NetworkSelector.qml +++ b/ui/imports/shared/popups/send/views/NetworkSelector.qml @@ -37,6 +37,7 @@ Item { property double totalFeesInFiat signal reCalculateSuggestedRoute() + signal changeSelectedChain(int chainId) implicitHeight: childrenRect.height @@ -59,6 +60,7 @@ Item { } StatusSwitchTabButton { text: qsTr("Custom") + enabled: Global.featureFlags.multiTxEnabled } } @@ -133,6 +135,10 @@ Item { return parseFloat(store.getWei2Eth(wei, selectedAsset.decimals)) return 0 } + + onChangeSelectedChain: { + root.changeSelectedChain(chainId) + } } } } diff --git a/ui/imports/shared/popups/send/views/NetworksAdvancedCustomRoutingView.qml b/ui/imports/shared/popups/send/views/NetworksAdvancedCustomRoutingView.qml index 47d5e9b84d..1753c0ca22 100644 --- a/ui/imports/shared/popups/send/views/NetworksAdvancedCustomRoutingView.qml +++ b/ui/imports/shared/popups/send/views/NetworksAdvancedCustomRoutingView.qml @@ -33,6 +33,7 @@ ColumnLayout { property int errorType: Constants.NoError signal reCalculateSuggestedRoute() + signal changeSelectedChain(int chainId) RowLayout { Layout.fillWidth: true @@ -67,7 +68,7 @@ ColumnLayout { icon.height: 16 icon.width: 16 text: checked ? qsTr("Hide Unpreferred Networks"): qsTr("Show Unpreferred Networks") - visible: !isBridgeTx + visible: Global.featureFlags.multiTxEnabled && !isBridgeTx checked: root.store.showUnPreferredChains onClicked: { root.store.toggleShowUnPreferredChains() @@ -79,8 +80,13 @@ ColumnLayout { Layout.fillWidth: true font.pixelSize: 15 color: Theme.palette.baseColor1 - text: isBridgeTx ? qsTr("Routes will be automatically calculated to give you the lowest cost.") : - qsTr("The networks where the recipient will receive tokens. Amounts calculated automatically for the lowest cost.") + text: { + if (Global.featureFlags.multiTxEnabled) { + return isBridgeTx ? qsTr("Routes will be automatically calculated to give you the lowest cost.") : + qsTr("The networks where the recipient will receive tokens. Amounts calculated automatically for the lowest cost.") + } + return qsTr("Select the network to make a transaction on") + } wrapMode: Text.WordWrap } Loader { @@ -108,6 +114,10 @@ ColumnLayout { interactive: root.interactive errorType: root.errorType isLoading: root.isLoading + + onChangeSelectedChain: { + root.changeSelectedChain(chainId) + } } } } diff --git a/ui/imports/shared/popups/send/views/NetworksSimpleRoutingView.qml b/ui/imports/shared/popups/send/views/NetworksSimpleRoutingView.qml index ee0f08a110..b2b1a3ebf3 100644 --- a/ui/imports/shared/popups/send/views/NetworksSimpleRoutingView.qml +++ b/ui/imports/shared/popups/send/views/NetworksSimpleRoutingView.qml @@ -157,7 +157,7 @@ RowLayout { } } onCheckedChanged: { - store.setRouteDisabledChains(chainId, !gasRectangle.checked) + store.setRouteDisabledToChains(chainId, !gasRectangle.checked) if(checked) root.reCalculateSuggestedRoute() } @@ -167,7 +167,7 @@ RowLayout { height: card.height } Component.onCompleted: { - store.setRouteDisabledChains(chainId, !gasRectangle.checked) + store.setRouteDisabledToChains(chainId, !gasRectangle.checked) if(index === (repeater.count -1)) root.reCalculateSuggestedRoute() } diff --git a/ui/imports/shared/popups/send/views/RecipientView.qml b/ui/imports/shared/popups/send/views/RecipientView.qml index 9b252fff01..0dc810a385 100644 --- a/ui/imports/shared/popups/send/views/RecipientView.qml +++ b/ui/imports/shared/popups/send/views/RecipientView.qml @@ -29,12 +29,15 @@ Loader { property var selectedRecipient: null property int selectedRecipientType: Helpers.RecipientAddressObjectType.Address + property var fromNetworksList // needed just for simple view + readonly property bool ready: (d.isAddressValid || !!resolvedENSAddress) && !d.isPending property string addressText property string resolvedENSAddress signal recalculateRoutesAndFees() signal isLoading() + signal changeSelectedChain(int chainId) onAddressTextChanged: d.isPending = false @@ -74,10 +77,24 @@ Loader { // set preferred chains if(!isCollectiblesTransfer) { - if(root.isBridgeTx) + if (Global.featureFlags.multiTxEnabled) { + if(root.isBridgeTx) + root.store.setAllNetworksAsRoutePreferredChains() + else + root.store.updateRoutePreferredChains(preferredChainIds) + } else { + // for simple sending we always set all chains as preferred chains, but we select the first one from the prefferd chains + // as selected chain (just to obey address prefixes) + let chainIds = preferredChainIds.split(":").filter(Boolean).map(Number) root.store.setAllNetworksAsRoutePreferredChains() - else - root.store.updateRoutePreferredChains(preferredChainIds) + if (chainIds.length > 0) { + root.changeSelectedChain(chainIds[0]) + } else { + root.changeSelectedChain(-1) + } + + return + } } recalculateRoutesAndFees() diff --git a/ui/imports/shared/stores/send/TransactionStore.qml b/ui/imports/shared/stores/send/TransactionStore.qml index 69fcd5b046..647c487ad2 100644 --- a/ui/imports/shared/stores/send/TransactionStore.qml +++ b/ui/imports/shared/stores/send/TransactionStore.qml @@ -124,10 +124,14 @@ QtObject { walletSectionSendInst.toNetworksRouteModel.toggleRouteDisabledChains(chainId) } - function setRouteDisabledChains(chainId, disabled) { + function setRouteDisabledToChains(chainId, disabled) { walletSectionSendInst.toNetworksRouteModel.setRouteDisabledChains(chainId, disabled) } + function setRouteDisabledFromChains(chainId, disabled) { + walletSectionSendInst.fromNetworksRouteModel.setRouteDisabledChains(chainId, disabled) + } + function setSelectedTokenName(tokenName) { walletSectionSendInst.setSelectedTokenName(tokenName) }