feat(@desktop/wallet): Implementation of Sign Swap

fixes #14835
This commit is contained in:
Khushboo Mehta 2024-07-02 00:35:10 +02:00 committed by Khushboo-dev-cpp
parent c934ba74ef
commit 0e458842d2
7 changed files with 193 additions and 31 deletions

View File

@ -48,17 +48,21 @@ SplitView {
modal: false modal: false
closePolicy: Popup.CloseOnEscape closePolicy: Popup.CloseOnEscape
destroyOnClose: true destroyOnClose: true
title: qsTr("Approve spending cap")
loading: loadingCheckBox.checked loading: loadingCheckBox.checked
swapSignApproveInputForm: SwapSignApproveInputForm { swapSignApproveInputForm: SwapSignApproveInputForm {
selectedAccountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240" selectedAccountAddress: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
selectedNetworkChainId: 11155111 selectedNetworkChainId: 11155111
tokensKey: "DAI"
estimatedTime: 3 estimatedTime: 3
swapProviderName: "ParaSwap" swapProviderName: "ParaSwap"
approvalGasFees: "2.789231893824e-06" approvalGasFees: "2.789231893824e-06"
approvalAmountRequired: "10000000000000" approvalAmountRequired: "10000000000000"
approvalContractAddress: "0x216b4b4ba9f3e719726886d34a177484278bfcae" approvalContractAddress: "0x216b4b4ba9f3e719726886d34a177484278bfcae"
fromTokensKey: "DAI"
toTokensKey: "ETH"
fromTokensAmount: "0.00100000000000003"
toTokensAmount: "0.02925100"
selectedSlippage: 0.5
swapFees: 2.789231893824e-06
} }
adaptor: SwapSignApproveAdaptor { adaptor: SwapSignApproveAdaptor {
swapStore: SwapStore { swapStore: SwapStore {
@ -77,6 +81,7 @@ SplitView {
currencyStore: CurrenciesStore {} currencyStore: CurrenciesStore {}
inputFormData: modal.swapSignApproveInputForm inputFormData: modal.swapSignApproveInputForm
} }
txType: isApprovalTx.checked ? SwapSignApprovePopup.TxType.Approve : SwapSignApprovePopup.TxType.Swap
} }
} }
} }
@ -95,6 +100,12 @@ SplitView {
text: "loading" text: "loading"
checked: false checked: false
} }
CheckBox {
id: isApprovalTx
text: "approve tx"
checked: false
}
} }
} }
} }

View File

@ -20,7 +20,8 @@ ListModel {
{ chainId: 10, address: "0x0000000000000000000000000000000000000000"}, { chainId: 10, address: "0x0000000000000000000000000000000000000000"},
{ chainId: 420, address: "0x0000000000000000000000000000000000000000"}, { chainId: 420, address: "0x0000000000000000000000000000000000000000"},
{ chainId: 42161, address: "0x0000000000000000000000000000000000000000"}, { chainId: 42161, address: "0x0000000000000000000000000000000000000000"},
{ chainId: 421613, address: "0x0000000000000000000000000000000000000000"} { chainId: 421613, address: "0x0000000000000000000000000000000000000000"},
{ chainId: 11155111, address: "0x0000000000000000000000000000000000000000"},
], ],
decimals: 18, decimals: 18,
type: 1, type: 1,

View File

@ -331,10 +331,10 @@ StatusDialog {
if(root.swapAdaptor.validSwapProposalReceived) { if(root.swapAdaptor.validSwapProposalReceived) {
if(root.swapAdaptor.swapOutputData.approvalNeeded) { if(root.swapAdaptor.swapOutputData.approvalNeeded) {
let approvalGasFeesFiat = root.swapAdaptor.currencyStore.getFiatValue(root.swapAdaptor.swapOutputData.approvalGasFees, Constants.ethToken)
return root.swapAdaptor.currencyStore.formatCurrencyAmount( return root.swapAdaptor.currencyStore.formatCurrencyAmount(
root.swapAdaptor.swapOutputData.approvalGasFees, approvalGasFeesFiat,
root.swapAdaptor.currencyStore.currentCurrency) root.swapAdaptor.currencyStore.currentCurrency)
} else { } else {
return root.swapAdaptor.currencyStore.formatCurrencyAmount( return root.swapAdaptor.currencyStore.formatCurrencyAmount(
root.swapAdaptor.swapOutputData.totalFees, root.swapAdaptor.swapOutputData.totalFees,
@ -376,13 +376,8 @@ StatusDialog {
!root.swapAdaptor.approvalPending !root.swapAdaptor.approvalPending
onClicked: { onClicked: {
if (root.swapAdaptor.validSwapProposalReceived) { if (root.swapAdaptor.validSwapProposalReceived) {
if(root.swapAdaptor.swapOutputData.approvalNeeded) { let txType = root.swapAdaptor.swapOutputData.approvalNeeded ? SwapSignApprovePopup.TxType.Approve : SwapSignApprovePopup.TxType.Swap
Global.openPopup(swapSignApprovePopup) Global.openPopup(swapSignApprovePopup, {"txType": txType})
}
else {
swapAdaptor.sendSwapTx()
close()
}
} }
} }
} }
@ -401,12 +396,17 @@ StatusDialog {
swapSignApproveInputForm: SwapSignApproveInputForm { swapSignApproveInputForm: SwapSignApproveInputForm {
selectedAccountAddress: root.swapInputParamsForm.selectedAccountAddress selectedAccountAddress: root.swapInputParamsForm.selectedAccountAddress
selectedNetworkChainId: root.swapInputParamsForm.selectedNetworkChainId selectedNetworkChainId: root.swapInputParamsForm.selectedNetworkChainId
tokensKey: root.swapInputParamsForm.fromTokensKey
estimatedTime: root.swapAdaptor.swapOutputData.estimatedTime estimatedTime: root.swapAdaptor.swapOutputData.estimatedTime
swapProviderName: root.swapAdaptor.swapOutputData.txProviderName swapProviderName: root.swapAdaptor.swapOutputData.txProviderName
approvalGasFees: root.swapAdaptor.swapOutputData.approvalGasFees approvalGasFees: root.swapAdaptor.swapOutputData.approvalGasFees
approvalAmountRequired: root.swapAdaptor.swapOutputData.approvalAmountRequired approvalAmountRequired: root.swapAdaptor.swapOutputData.approvalAmountRequired
approvalContractAddress: root.swapAdaptor.swapOutputData.approvalContractAddress 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 { adaptor: SwapSignApproveAdaptor {
swapStore: root.swapAdaptor.swapStore swapStore: root.swapAdaptor.swapStore
@ -415,8 +415,14 @@ StatusDialog {
inputFormData: approvePopup.swapSignApproveInputForm inputFormData: approvePopup.swapSignApproveInputForm
} }
onSign: { onSign: {
if(txType === SwapSignApprovePopup.TxType.Approve) {
root.swapAdaptor.sendApproveTx() root.swapAdaptor.sendApproveTx()
close() close()
} else {
root.swapAdaptor.sendSwapTx()
close()
root.close()
}
} }
onReject: close() onReject: close()
} }

View File

@ -9,6 +9,7 @@ QtObject {
property string fromTokenAmount: "" property string fromTokenAmount: ""
property string toTokenAmount: "" property string toTokenAmount: ""
// TODO: this should be string but backend gas_estimate_item.nim passes this as float
property real totalFees: 0 property real totalFees: 0
property var bestRoutes: [] property var bestRoutes: []
property bool hasError property bool hasError

View File

@ -16,14 +16,17 @@ QObject {
// To expose the selected from and to Token from the SwapModal // To expose the selected from and to Token from the SwapModal
readonly property var fromToken: fromTokenEntry.item readonly property var fromToken: fromTokenEntry.item
readonly property var toToken: toTokenEntry.item
readonly property var selectedAccount: selectedAccountEntry.item readonly property var selectedAccount: selectedAccountEntry.item
readonly property var selectedNetwork: selectedNetworkEntry.item readonly property var selectedNetwork: selectedNetworkEntry.item
readonly property var fromTokenContractAddress: fromTokenContractAddress.item
readonly property var toTokenContractAddress: toTokenContractAddress.item
ModelEntry { ModelEntry {
id: fromTokenEntry id: fromTokenEntry
sourceModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel sourceModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel
key: "key" key: "key"
value: root.inputFormData.tokensKey value: root.inputFormData.fromTokensKey
} }
ModelEntry { ModelEntry {
@ -39,4 +42,27 @@ QObject {
key: "chainId" key: "chainId"
value: root.inputFormData.selectedNetworkChainId 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
}
} }

View File

@ -7,11 +7,20 @@ QtObject {
required property string selectedAccountAddress required property string selectedAccountAddress
required property int selectedNetworkChainId 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 // need to check how this is done in new router, right now it is Enum type
required property int estimatedTime required property int estimatedTime
required property string swapProviderName required property string swapProviderName
required property string approvalGasFees required property string approvalGasFees
required property string approvalAmountRequired required property string approvalAmountRequired
required property string approvalContractAddress required property string approvalContractAddress
} }

View File

@ -24,18 +24,30 @@ StatusDialog {
required property bool loading required property bool loading
required property SwapSignApproveInputForm swapSignApproveInputForm required property SwapSignApproveInputForm swapSignApproveInputForm
required property SwapSignApproveAdaptor adaptor required property SwapSignApproveAdaptor adaptor
property int txType: SwapSignApprovePopup.TxType.Swap
signal sign() signal sign()
signal reject() 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" objectName: "swapSignApproveModal"
implicitWidth: 480 implicitWidth: 480
padding: 20 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 /* 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 is only added temporarily until we have an api from the backend in order to get
this list dynamically */ 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 { contentItem: StatusScrollView {
id: scrollView id: scrollView
@ -55,9 +67,12 @@ StatusDialog {
} }
StatusListItem { StatusListItem {
width: parent.width width: parent.width
title: SQUtils.AmountsArithmetic.div( title: {
let bigAmount = SQUtils.AmountsArithmetic.div(
SQUtils.AmountsArithmetic.fromString(swapSignApproveInputForm.approvalAmountRequired), SQUtils.AmountsArithmetic.fromString(swapSignApproveInputForm.approvalAmountRequired),
SQUtils.AmountsArithmetic.fromNumber(1, !!root.adaptor.fromToken ? root.adaptor.fromToken.decimals: 18)).toString() SQUtils.AmountsArithmetic.fromNumber(1, !!root.adaptor.fromToken ? root.adaptor.fromToken.decimals: d.defaultDecmials)).toFixed()
return bigAmount.replace('.', LocaleUtils.userInputLocale.decimalPoint)
}
border.width: 1 border.width: 1
border.color: Theme.palette.baseColor2 border.color: Theme.palette.baseColor2
components: [ components: [
@ -74,6 +89,7 @@ StatusDialog {
} }
] ]
} }
visible: d.isApproveTx
} }
Column { Column {
@ -81,7 +97,73 @@ StatusDialog {
spacing: Style.current.padding spacing: Style.current.padding
StatusBaseText { StatusBaseText {
width: parent.width 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 { WalletAccountListItem {
width: parent.width width: parent.width
@ -117,9 +199,9 @@ StatusDialog {
asset.isImage: true asset.isImage: true
title: !!root.adaptor.fromToken ? title: !!root.adaptor.fromToken ?
root.adaptor.fromToken.symbol ?? "" : "" root.adaptor.fromToken.symbol ?? "" : ""
subTitle: SQUtils.Utils.elideText(contractAddressOnSelectedNetwork.item.address, 6, 4) subTitle: SQUtils.Utils.elideText(payContractAddressOnSelectedNetwork.item.address, 6, 4)
ModelEntry { ModelEntry {
id: contractAddressOnSelectedNetwork id: payContractAddressOnSelectedNetwork
sourceModel: !!root.adaptor.fromToken ? sourceModel: !!root.adaptor.fromToken ?
root.adaptor.fromToken.addressPerChain ?? null : null root.adaptor.fromToken.addressPerChain ?? null : null
key: "chainId" key: "chainId"
@ -135,6 +217,7 @@ StatusDialog {
} }
] ]
} }
visible: d.isApproveTx
} }
Column { Column {
@ -166,6 +249,7 @@ StatusDialog {
} }
] ]
} }
visible: d.isApproveTx
} }
Column { Column {
@ -213,14 +297,26 @@ StatusDialog {
anchors.right: parent.right anchors.right: parent.right
loading: root.loading loading: root.loading
text: { text: {
if(d.isApproveTx) {
let feesInFoat = root.adaptor.currencyStore.getFiatValue(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken) let feesInFoat = root.adaptor.currencyStore.getFiatValue(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken)
return root.adaptor.currencyStore.formatCurrencyAmount(feesInFoat, root.adaptor.currencyStore.currentCurrency) return root.adaptor.currencyStore.formatCurrencyAmount(feesInFoat, root.adaptor.currencyStore.currentCurrency)
} else {
return root.adaptor.currencyStore.formatCurrencyAmount(root.swapSignApproveInputForm.swapFees, root.adaptor.currencyStore.currentCurrency)
}
} }
} }
StatusTextWithLoadingState { StatusTextWithLoadingState {
anchors.right: parent.right anchors.right: parent.right
loading: root.loading 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 { SwapModalFooterInfoComponent {
titleText: qsTr("Max fees:") titleText: qsTr("Max fees:")
infoText: { infoText: {
if(d.isApproveTx) {
let feesInFoat = root.adaptor.currencyStore.getFiatValue(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken) let feesInFoat = root.adaptor.currencyStore.getFiatValue(root.swapSignApproveInputForm.approvalGasFees, Constants.ethToken)
return root.adaptor.currencyStore.formatCurrencyAmount(feesInFoat, root.adaptor.currencyStore.currentCurrency) 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 loading: root.loading
} }
@ -245,6 +345,14 @@ StatusDialog {
titleText: qsTr("Est. time:") titleText: qsTr("Est. time:")
infoText: WalletUtils.getLabelForEstimatedTxTime(root.swapSignApproveInputForm.estimatedTime) infoText: WalletUtils.getLabelForEstimatedTxTime(root.swapSignApproveInputForm.estimatedTime)
loading: root.loading 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
} }
} }