diff --git a/storybook/pages/SwapSignApprovePopupPage.qml b/storybook/pages/SwapSignApprovePopupPage.qml index 3219c8ffa1..96dcbedf2a 100644 --- a/storybook/pages/SwapSignApprovePopupPage.qml +++ b/storybook/pages/SwapSignApprovePopupPage.qml @@ -48,17 +48,21 @@ SplitView { modal: false closePolicy: Popup.CloseOnEscape destroyOnClose: true - title: qsTr("Approve spending cap") loading: loadingCheckBox.checked swapSignApproveInputForm: SwapSignApproveInputForm { selectedAccountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240" selectedNetworkChainId: 11155111 - tokensKey: "DAI" estimatedTime: 3 swapProviderName: "ParaSwap" approvalGasFees: "2.789231893824e-06" approvalAmountRequired: "10000000000000" approvalContractAddress: "0x216b4b4ba9f3e719726886d34a177484278bfcae" + fromTokensKey: "DAI" + toTokensKey: "ETH" + fromTokensAmount: "0.00100000000000003" + toTokensAmount: "0.02925100" + selectedSlippage: 0.5 + swapFees: 2.789231893824e-06 } adaptor: SwapSignApproveAdaptor { swapStore: SwapStore { @@ -77,6 +81,7 @@ SplitView { currencyStore: CurrenciesStore {} inputFormData: modal.swapSignApproveInputForm } + txType: isApprovalTx.checked ? SwapSignApprovePopup.TxType.Approve : SwapSignApprovePopup.TxType.Swap } } } @@ -95,6 +100,12 @@ SplitView { text: "loading" checked: false } + + CheckBox { + id: isApprovalTx + text: "approve tx" + checked: false + } } } } diff --git a/storybook/src/Models/TokensBySymbolModel.qml b/storybook/src/Models/TokensBySymbolModel.qml index 1c7e36a65d..bf86c1ec69 100644 --- a/storybook/src/Models/TokensBySymbolModel.qml +++ b/storybook/src/Models/TokensBySymbolModel.qml @@ -20,7 +20,8 @@ ListModel { { chainId: 10, address: "0x0000000000000000000000000000000000000000"}, { chainId: 420, address: "0x0000000000000000000000000000000000000000"}, { chainId: 42161, address: "0x0000000000000000000000000000000000000000"}, - { chainId: 421613, address: "0x0000000000000000000000000000000000000000"} + { chainId: 421613, address: "0x0000000000000000000000000000000000000000"}, + { chainId: 11155111, address: "0x0000000000000000000000000000000000000000"}, ], decimals: 18, type: 1, diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index 4c482eb7f1..aa2cc06dd8 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -331,10 +331,10 @@ StatusDialog { if(root.swapAdaptor.validSwapProposalReceived) { if(root.swapAdaptor.swapOutputData.approvalNeeded) { + let approvalGasFeesFiat = root.swapAdaptor.currencyStore.getFiatValue(root.swapAdaptor.swapOutputData.approvalGasFees, Constants.ethToken) return root.swapAdaptor.currencyStore.formatCurrencyAmount( - root.swapAdaptor.swapOutputData.approvalGasFees, + approvalGasFeesFiat, root.swapAdaptor.currencyStore.currentCurrency) - } else { return root.swapAdaptor.currencyStore.formatCurrencyAmount( root.swapAdaptor.swapOutputData.totalFees, @@ -375,14 +375,9 @@ StatusDialog { !payPanel.amountEnteredGreaterThanBalance && !root.swapAdaptor.approvalPending onClicked: { - if (root.swapAdaptor.validSwapProposalReceived ){ - if(root.swapAdaptor.swapOutputData.approvalNeeded) { - Global.openPopup(swapSignApprovePopup) - } - else { - swapAdaptor.sendSwapTx() - close() - } + if (root.swapAdaptor.validSwapProposalReceived) { + let txType = root.swapAdaptor.swapOutputData.approvalNeeded ? SwapSignApprovePopup.TxType.Approve : SwapSignApprovePopup.TxType.Swap + Global.openPopup(swapSignApprovePopup, {"txType": txType}) } } } @@ -401,12 +396,17 @@ StatusDialog { swapSignApproveInputForm: SwapSignApproveInputForm { selectedAccountAddress: root.swapInputParamsForm.selectedAccountAddress selectedNetworkChainId: root.swapInputParamsForm.selectedNetworkChainId - tokensKey: root.swapInputParamsForm.fromTokensKey estimatedTime: root.swapAdaptor.swapOutputData.estimatedTime swapProviderName: root.swapAdaptor.swapOutputData.txProviderName approvalGasFees: root.swapAdaptor.swapOutputData.approvalGasFees approvalAmountRequired: root.swapAdaptor.swapOutputData.approvalAmountRequired approvalContractAddress: root.swapAdaptor.swapOutputData.approvalContractAddress + fromTokensKey: root.swapInputParamsForm.fromTokensKey + fromTokensAmount: root.swapInputParamsForm.fromTokenAmount + toTokensKey: root.swapInputParamsForm.toTokenKey + toTokensAmount: root.swapAdaptor.swapOutputData.toTokenAmount + swapFees: root.swapAdaptor.swapOutputData.totalFees + selectedSlippage: root.swapInputParamsForm.selectedSlippage } adaptor: SwapSignApproveAdaptor { swapStore: root.swapAdaptor.swapStore @@ -415,8 +415,14 @@ StatusDialog { inputFormData: approvePopup.swapSignApproveInputForm } onSign: { - root.swapAdaptor.sendApproveTx() - close() + if(txType === SwapSignApprovePopup.TxType.Approve) { + root.swapAdaptor.sendApproveTx() + close() + } else { + root.swapAdaptor.sendSwapTx() + close() + root.close() + } } onReject: close() } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapOutputData.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapOutputData.qml index e4c5cad9a5..01f64f6403 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapOutputData.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapOutputData.qml @@ -9,6 +9,7 @@ QtObject { property string fromTokenAmount: "" property string toTokenAmount: "" + // TODO: this should be string but backend gas_estimate_item.nim passes this as float property real totalFees: 0 property var bestRoutes: [] property bool hasError diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApproveAdaptor.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApproveAdaptor.qml index 73ca374dc4..adc42fda80 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApproveAdaptor.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApproveAdaptor.qml @@ -16,14 +16,17 @@ QObject { // To expose the selected from and to Token from the SwapModal readonly property var fromToken: fromTokenEntry.item + readonly property var toToken: toTokenEntry.item readonly property var selectedAccount: selectedAccountEntry.item readonly property var selectedNetwork: selectedNetworkEntry.item + readonly property var fromTokenContractAddress: fromTokenContractAddress.item + readonly property var toTokenContractAddress: toTokenContractAddress.item ModelEntry { id: fromTokenEntry sourceModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel key: "key" - value: root.inputFormData.tokensKey + value: root.inputFormData.fromTokensKey } ModelEntry { @@ -39,4 +42,27 @@ QObject { key: "chainId" value: root.inputFormData.selectedNetworkChainId } + + ModelEntry { + id: toTokenEntry + sourceModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel + key: "key" + value: root.inputFormData.toTokensKey + } + + ModelEntry { + id: fromTokenContractAddress + sourceModel: !!root.fromToken ? + root.fromToken.addressPerChain ?? null : null + key: "chainId" + value: root.inputFormData.selectedNetworkChainId + } + + ModelEntry { + id: toTokenContractAddress + sourceModel: !!root.toToken ? + root.toToken.addressPerChain ?? null : null + key: "chainId" + value: root.inputFormData.selectedNetworkChainId + } } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApproveInputForm.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApproveInputForm.qml index a43609e0ba..06397cc52c 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApproveInputForm.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApproveInputForm.qml @@ -7,11 +7,20 @@ QtObject { required property string selectedAccountAddress required property int selectedNetworkChainId - required property string tokensKey + required property string fromTokensKey + required property string fromTokensAmount + required property string toTokensKey + required property string toTokensAmount + required property double selectedSlippage + // TODO: this should be string but backend gas_estimate_item.nim passes this as float + required property double swapFees + // need to check how this is done in new router, right now it is Enum type required property int estimatedTime required property string swapProviderName required property string approvalGasFees required property string approvalAmountRequired required property string approvalContractAddress + + } diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApprovePopup.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApprovePopup.qml index 8d05768092..5fd371347b 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApprovePopup.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapSignApprovePopup.qml @@ -24,18 +24,30 @@ StatusDialog { required property bool loading required property SwapSignApproveInputForm swapSignApproveInputForm required property SwapSignApproveAdaptor adaptor + property int txType: SwapSignApprovePopup.TxType.Swap signal sign() signal reject() + enum TxType { + Swap, + Approve + } + + QtObject { + id: d + readonly property bool isApproveTx: root.txType === SwapSignApprovePopup.TxType.Approve + readonly property int defaultDecmials: 18 + } + objectName: "swapSignApproveModal" implicitWidth: 480 padding: 20 - title: qsTr("Approve spending cap") + title: d.isApproveTx ? qsTr("Approve spending cap"): qsTr("Sign Swap") /* TODO: https://github.com/status-im/status-desktop/issues/15329 This is only added temporarily until we have an api from the backend in order to get this list dynamically */ - subtitle: Constants.swap.paraswapUrl + subtitle: d.isApproveTx ? Constants.swap.paraswapUrl : qsTr("%1 to %2").arg(payToken.title).arg(receiveToken.title) contentItem: StatusScrollView { id: scrollView @@ -55,9 +67,12 @@ StatusDialog { } StatusListItem { width: parent.width - title: SQUtils.AmountsArithmetic.div( - SQUtils.AmountsArithmetic.fromString(swapSignApproveInputForm.approvalAmountRequired), - SQUtils.AmountsArithmetic.fromNumber(1, !!root.adaptor.fromToken ? root.adaptor.fromToken.decimals: 18)).toString() + title: { + let bigAmount = SQUtils.AmountsArithmetic.div( + SQUtils.AmountsArithmetic.fromString(swapSignApproveInputForm.approvalAmountRequired), + SQUtils.AmountsArithmetic.fromNumber(1, !!root.adaptor.fromToken ? root.adaptor.fromToken.decimals: d.defaultDecmials)).toFixed() + return bigAmount.replace('.', LocaleUtils.userInputLocale.decimalPoint) + } border.width: 1 border.color: Theme.palette.baseColor2 components: [ @@ -74,6 +89,7 @@ StatusDialog { } ] } + visible: d.isApproveTx } Column { @@ -81,7 +97,73 @@ StatusDialog { spacing: Style.current.padding StatusBaseText { width: parent.width - text: qsTr("Account") + text: qsTr("Pay") + } + StatusListItem { + id: payToken + width: parent.width + height: 76 + border.width: 1 + border.color: Theme.palette.baseColor2 + asset.name: !!root.adaptor.fromToken ? + Constants.tokenIcon(root.adaptor.fromToken.symbol): "" + asset.isImage: true + title: qsTr("%1 %2").arg( + SQUtils.AmountsArithmetic.fromString(swapSignApproveInputForm.fromTokensAmount).toFixed().replace('.', LocaleUtils.userInputLocale.decimalPoint)).arg( + !!root.adaptor.fromToken ? root.adaptor.fromToken.symbol: "") + subTitle: SQUtils.Utils.elideText(root.adaptor.fromTokenContractAddress.address, 6, 4) + components: [ + StatusRoundButton { + type: StatusRoundButton.Type.Quinary + radius: 8 + icon.name: "more" + icon.color: Theme.palette.directColor5 + onClicked: {} + } + ] + } + visible: !d.isApproveTx + } + + Column { + width: scrollView.availableWidth + spacing: Style.current.padding + StatusBaseText { + width: parent.width + text: qsTr("Receive") + } + StatusListItem { + id: receiveToken + width: parent.width + height: 76 + border.width: 1 + border.color: Theme.palette.baseColor2 + asset.name: !!root.adaptor.toToken ? + Constants.tokenIcon(root.adaptor.toToken.symbol): "" + asset.isImage: true + title: qsTr("%1 %2").arg( + SQUtils.AmountsArithmetic.fromString(swapSignApproveInputForm.toTokensAmount).toFixed().replace('.', LocaleUtils.userInputLocale.decimalPoint)).arg( + !!root.adaptor.toToken ? root.adaptor.toToken.symbol: "") + subTitle: SQUtils.Utils.elideText(root.adaptor.toTokenContractAddress.address, 6, 4) + components: [ + StatusRoundButton { + type: StatusRoundButton.Type.Quinary + radius: 8 + icon.name: "more" + icon.color: Theme.palette.directColor5 + onClicked: {} + } + ] + } + visible: !d.isApproveTx + } + + Column { + width: scrollView.availableWidth + spacing: Style.current.padding + StatusBaseText { + width: parent.width + text: d.isApproveTx ? qsTr("Account") : qsTr("In account") } WalletAccountListItem { width: parent.width @@ -117,9 +199,9 @@ StatusDialog { asset.isImage: true title: !!root.adaptor.fromToken ? root.adaptor.fromToken.symbol ?? "" : "" - subTitle: SQUtils.Utils.elideText(contractAddressOnSelectedNetwork.item.address, 6, 4) + subTitle: SQUtils.Utils.elideText(payContractAddressOnSelectedNetwork.item.address, 6, 4) ModelEntry { - id: contractAddressOnSelectedNetwork + id: payContractAddressOnSelectedNetwork sourceModel: !!root.adaptor.fromToken ? root.adaptor.fromToken.addressPerChain ?? null : null key: "chainId" @@ -135,6 +217,7 @@ StatusDialog { } ] } + visible: d.isApproveTx } Column { @@ -166,6 +249,7 @@ StatusDialog { } ] } + visible: d.isApproveTx } Column { @@ -213,14 +297,26 @@ StatusDialog { anchors.right: parent.right loading: root.loading text: { - let feesInFoat = root.adaptor.currencyStore.getFiatValue(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken) - return root.adaptor.currencyStore.formatCurrencyAmount(feesInFoat, root.adaptor.currencyStore.currentCurrency) + if(d.isApproveTx) { + let feesInFoat = root.adaptor.currencyStore.getFiatValue(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken) + return root.adaptor.currencyStore.formatCurrencyAmount(feesInFoat, root.adaptor.currencyStore.currentCurrency) + } else { + return root.adaptor.currencyStore.formatCurrencyAmount(root.swapSignApproveInputForm.swapFees, root.adaptor.currencyStore.currentCurrency) + } } } StatusTextWithLoadingState { anchors.right: parent.right loading: root.loading - text: root.adaptor.currencyStore.formatCurrencyAmount(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken) + text: { + if(d.isApproveTx) { + return root.adaptor.currencyStore.formatCurrencyAmount(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken) + } + else { + let cryptoValue = root.adaptor.currencyStore.getCryptoValue(root.swapSignApproveInputForm.swapFees, Constants.ethToken) + return root.adaptor.currencyStore.formatCurrencyAmount(cryptoValue, Constants.ethToken) + } + } } } ] @@ -235,8 +331,12 @@ StatusDialog { SwapModalFooterInfoComponent { titleText: qsTr("Max fees:") infoText: { - let feesInFoat = root.adaptor.currencyStore.getFiatValue(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken) - return root.adaptor.currencyStore.formatCurrencyAmount(feesInFoat, root.adaptor.currencyStore.currentCurrency) + if(d.isApproveTx) { + let feesInFoat = root.adaptor.currencyStore.getFiatValue(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken) + return root.adaptor.currencyStore.formatCurrencyAmount(feesInFoat, root.adaptor.currencyStore.currentCurrency) + } else { + return root.adaptor.currencyStore.formatCurrencyAmount(root.swapSignApproveInputForm.swapFees, root.adaptor.currencyStore.currentCurrency) + } } loading: root.loading } @@ -245,6 +345,14 @@ StatusDialog { titleText: qsTr("Est. time:") infoText: WalletUtils.getLabelForEstimatedTxTime(root.swapSignApproveInputForm.estimatedTime) loading: root.loading + visible: d.isApproveTx + } + SwapModalFooterInfoComponent { + Layout.maximumWidth: 60 + titleText: qsTr("Max slippage:") + infoText: "%1%".arg(LocaleUtils.numberToLocaleString(root.swapSignApproveInputForm.selectedSlippage)) + loading: root.loading + visible: !d.isApproveTx } }