feat(@desktop/wallet): Swap:: Added text that links to Paraswaps terms and conditions page

This commit is contained in:
Khushboo Mehta 2024-08-08 15:52:19 +02:00 committed by Khushboo-dev-cpp
parent 7025938398
commit ada348486e
11 changed files with 99 additions and 42 deletions

View File

@ -96,7 +96,8 @@ SplitView {
networkBlockExplorerUrl: priv.selectedNetwork.blockExplorerURL networkBlockExplorerUrl: priv.selectedNetwork.blockExplorerURL
serviceProviderName: Constants.swap.paraswapName serviceProviderName: Constants.swap.paraswapName
serviceProviderURL: Constants.swap.termsAndConditionParaswapUrl serviceProviderURL: Constants.swap.paraswapUrl
serviceProviderTandCUrl: Constants.swap.paraswapTermsAndConditionUrl
fiatFees: formatBigNumber(42.542567, "EUR") fiatFees: formatBigNumber(42.542567, "EUR")
cryptoFees: formatBigNumber(0.06, "ETH") cryptoFees: formatBigNumber(0.06, "ETH")

View File

@ -84,14 +84,14 @@ Item {
// title & subtitle // title & subtitle
compare(controlUnderTest.title, qsTr("Approve spending cap")) compare(controlUnderTest.title, qsTr("Approve spending cap"))
compare(controlUnderTest.subtitle, controlUnderTest.serviceProviderURL) compare(controlUnderTest.subtitle, controlUnderTest.serviceProviderHostname)
// info box // info box
const headerText = findChild(controlUnderTest.contentItem, "headerText") const headerText = findChild(controlUnderTest.contentItem, "headerText")
verify(!!headerText) verify(!!headerText)
compare(headerText.text, qsTr("Set %1 spending cap in %2 for %3 on %4") compare(headerText.text, qsTr("Set %1 spending cap in %2 for %3 on %4")
.arg(controlUnderTest.formatBigNumber(controlUnderTest.fromTokenAmount, controlUnderTest.fromTokenSymbol)) .arg(controlUnderTest.formatBigNumber(controlUnderTest.fromTokenAmount, controlUnderTest.fromTokenSymbol))
.arg(controlUnderTest.accountName).arg(controlUnderTest.serviceProviderURL).arg(controlUnderTest.networkName)) .arg(controlUnderTest.accountName).arg(controlUnderTest.serviceProviderHostname).arg(controlUnderTest.networkName))
const fromImageHidden = findChild(controlUnderTest.contentItem, "fromImageIdenticon") const fromImageHidden = findChild(controlUnderTest.contentItem, "fromImageIdenticon")
compare(fromImageHidden.visible, false) compare(fromImageHidden.visible, false)

View File

@ -42,7 +42,8 @@ Item {
networkBlockExplorerUrl: "https://etherscan.io/" networkBlockExplorerUrl: "https://etherscan.io/"
serviceProviderName: Constants.swap.paraswapName serviceProviderName: Constants.swap.paraswapName
serviceProviderURL: Constants.swap.termsAndConditionParaswapUrl serviceProviderURL: Constants.swap.paraswapUrl
serviceProviderTandCUrl: Constants.swap.paraswapTermsAndConditionUrl
fiatFees: "1.54 EUR" fiatFees: "1.54 EUR"
cryptoFees: "0.001 ETH" cryptoFees: "0.001 ETH"

View File

@ -0,0 +1,49 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
RowLayout {
id: root
required property string serviceProviderName
signal linkClicked()
signal termsAndConditionClicked()
spacing: 4
StatusIcon {
Layout.preferredWidth: 16
Layout.preferredHeight: 16
icon: "external-link"
color: Theme.palette.directColor1
}
StatusBaseText {
font.pixelSize: Style.current.additionalTextSize
text: qsTr("Powered by")
}
StatusLinkText {
Layout.topMargin: 1 // compensate for the underline
text: "%1.".arg(root.serviceProviderName)
normalColor: Theme.palette.directColor1
linkColor: Theme.palette.directColor1
font.weight: Font.Normal
onClicked: root.linkClicked()
}
StatusBaseText {
font.pixelSize: Style.current.additionalTextSize
text: qsTr("View")
}
StatusLinkText {
Layout.topMargin: 1 // compensate for the underline
text: qsTr("Terms & Conditions")
normalColor: Theme.palette.directColor1
linkColor: Theme.palette.directColor1
font.weight: Font.Normal
onClicked: root.termsAndConditionClicked()
}
}

View File

@ -20,3 +20,4 @@ StatusTxProgressBar 1.0 StatusTxProgressBar.qml
SwapExchangeButton 1.0 SwapExchangeButton.qml SwapExchangeButton 1.0 SwapExchangeButton.qml
TokenSelector 1.0 TokenSelector.qml TokenSelector 1.0 TokenSelector.qml
TokenSelectorNew 1.0 TokenSelectorNew.qml TokenSelectorNew 1.0 TokenSelectorNew.qml
SwapProvidersTermsAndConditionsText 1.0 SwapProvidersTermsAndConditionsText.qml

View File

@ -13,6 +13,7 @@ import StatusQ.Components 0.1
import AppLayouts.Wallet 1.0 import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.panels 1.0 import AppLayouts.Wallet.panels 1.0
import AppLayouts.Wallet.popups 1.0 import AppLayouts.Wallet.popups 1.0
import AppLayouts.Wallet.controls 1.0
import shared.controls 1.0 import shared.controls 1.0
import utils 1.0 import utils 1.0
@ -41,12 +42,14 @@ SignTransactionModalBase {
required property int estimatedTime // Constants.TransactionEstimatedTime.XXX enum required property int estimatedTime // Constants.TransactionEstimatedTime.XXX enum
property string serviceProviderName: Constants.swap.paraswapName property string serviceProviderName: Constants.swap.paraswapName
property string serviceProviderHostname: Constants.swap.paraswapHostname
property string serviceProviderTandCUrl: Constants.swap.paraswapTermsAndConditionUrl
property string serviceProviderURL: Constants.swap.paraswapUrl // TODO https://github.com/status-im/status-desktop/issues/15329 property string serviceProviderURL: Constants.swap.paraswapUrl // TODO https://github.com/status-im/status-desktop/issues/15329
property string serviceProviderContractAddress: Constants.swap.paraswapApproveContractAddress property string serviceProviderContractAddress: Constants.swap.paraswapApproveContractAddress
property string serviceProviderIcon: Style.png("swap/%1".arg(Constants.swap.paraswapIcon)) // FIXME svg property string serviceProviderIcon: Style.png("swap/%1".arg(Constants.swap.paraswapIcon)) // FIXME svg
title: qsTr("Approve spending cap") title: qsTr("Approve spending cap")
subtitle: serviceProviderURL subtitle: root.serviceProviderHostname
gradientColor: Utils.setColorAlpha(root.accountColor, 0.05) // 5% of wallet color gradientColor: Utils.setColorAlpha(root.accountColor, 0.05) // 5% of wallet color
fromImageSmartIdenticon.asset.name: "filled-account" fromImageSmartIdenticon.asset.name: "filled-account"
@ -57,8 +60,10 @@ SignTransactionModalBase {
//: e.g. "Set 100 DAI spending cap in <account name> for <service> on <network name>" //: e.g. "Set 100 DAI spending cap in <account name> for <service> on <network name>"
headerMainText: qsTr("Set %1 spending cap in %2 for %3 on %4").arg(formatBigNumber(root.fromTokenAmount, root.fromTokenSymbol)) headerMainText: qsTr("Set %1 spending cap in %2 for %3 on %4").arg(formatBigNumber(root.fromTokenAmount, root.fromTokenSymbol))
.arg(root.accountName).arg(root.serviceProviderURL).arg(root.networkName) .arg(root.accountName).arg(root.serviceProviderHostname).arg(root.networkName)
headerSubTextLayout: [ headerSubTextLayout: [
ColumnLayout {
spacing: 12
StatusBaseText { StatusBaseText {
Layout.fillWidth: true Layout.fillWidth: true
horizontalAlignment: Qt.AlignHCenter horizontalAlignment: Qt.AlignHCenter
@ -66,8 +71,16 @@ SignTransactionModalBase {
font.pixelSize: Style.current.additionalTextSize font.pixelSize: Style.current.additionalTextSize
text: qsTr("The smart contract specified will be able to spend up to %1 of your current or future balance.").arg(formatBigNumber(root.fromTokenAmount, root.fromTokenSymbol)) text: qsTr("The smart contract specified will be able to spend up to %1 of your current or future balance.").arg(formatBigNumber(root.fromTokenAmount, root.fromTokenSymbol))
} }
SwapProvidersTermsAndConditionsText {
serviceProviderName: root.serviceProviderName
onLinkClicked: root.openLinkWithConfirmation(root.serviceProviderURL)
onTermsAndConditionClicked: root.openLinkWithConfirmation(root.serviceProviderTandCUrl)
}
}
] ]
infoTagText: qsTr("Review all details before signing")
headerIconComponent: StatusSmartIdenticon { headerIconComponent: StatusSmartIdenticon {
asset.name: root.serviceProviderIcon asset.name: root.serviceProviderIcon
asset.isImage: true asset.isImage: true

View File

@ -461,8 +461,10 @@ StatusDialog {
serviceProviderName: root.swapAdaptor.swapOutputData.txProviderName serviceProviderName: root.swapAdaptor.swapOutputData.txProviderName
serviceProviderURL: Constants.swap.paraswapUrl // TODO https://github.com/status-im/status-desktop/issues/15329 serviceProviderURL: Constants.swap.paraswapUrl // TODO https://github.com/status-im/status-desktop/issues/15329
serviceProviderTandCUrl: Constants.swap.paraswapTermsAndConditionUrl // TODO https://github.com/status-im/status-desktop/issues/15329
serviceProviderIcon: Style.png("swap/%1".arg(Constants.swap.paraswapIcon)) // FIXME svg serviceProviderIcon: Style.png("swap/%1".arg(Constants.swap.paraswapIcon)) // FIXME svg
serviceProviderContractAddress: root.swapAdaptor.swapOutputData.approvalContractAddress serviceProviderContractAddress: root.swapAdaptor.swapOutputData.approvalContractAddress
serviceProviderHostname: Constants.swap.paraswapHostname
onAccepted: { onAccepted: {
root.swapAdaptor.sendApproveTx() root.swapAdaptor.sendApproveTx()
@ -511,7 +513,8 @@ StatusDialog {
slippage: root.swapInputParamsForm.selectedSlippage slippage: root.swapInputParamsForm.selectedSlippage
serviceProviderName: root.swapAdaptor.swapOutputData.txProviderName serviceProviderName: root.swapAdaptor.swapOutputData.txProviderName
serviceProviderURL: Constants.swap.termsAndConditionParaswapUrl // TODO https://github.com/status-im/status-desktop/issues/15329 serviceProviderURL: Constants.swap.paraswapUrl // TODO https://github.com/status-im/status-desktop/issues/15329
serviceProviderTandCUrl: Constants.swap.paraswapTermsAndConditionUrl // TODO https://github.com/status-im/status-desktop/issues/15329
onAccepted: { onAccepted: {
root.swapAdaptor.sendSwapTx() root.swapAdaptor.sendSwapTx()

View File

@ -12,6 +12,7 @@ import StatusQ.Components 0.1
import AppLayouts.Wallet.panels 1.0 import AppLayouts.Wallet.panels 1.0
import AppLayouts.Wallet.popups 1.0 import AppLayouts.Wallet.popups 1.0
import AppLayouts.Wallet.controls 1.0
import utils 1.0 import utils 1.0
@ -42,6 +43,7 @@ SignTransactionModalBase {
required property string serviceProviderName required property string serviceProviderName
required property string serviceProviderURL required property string serviceProviderURL
required property string serviceProviderTandCUrl
title: qsTr("Sign Swap") title: qsTr("Sign Swap")
//: e.g. (swap) 100 DAI to 100 USDT //: e.g. (swap) 100 DAI to 100 USDT
@ -55,24 +57,10 @@ SignTransactionModalBase {
headerMainText: qsTr("Swap %1 to %2 in %3 on %4").arg(formatBigNumber(root.fromTokenAmount, root.fromTokenSymbol)) headerMainText: qsTr("Swap %1 to %2 in %3 on %4").arg(formatBigNumber(root.fromTokenAmount, root.fromTokenSymbol))
.arg(formatBigNumber(root.toTokenAmount, root.toTokenSymbol)).arg(root.accountName).arg(root.networkName) .arg(formatBigNumber(root.toTokenAmount, root.toTokenSymbol)).arg(root.accountName).arg(root.networkName)
headerSubTextLayout: [ headerSubTextLayout: [
StatusBaseText { SwapProvidersTermsAndConditionsText {
font.pixelSize: Style.current.additionalTextSize serviceProviderName: root.serviceProviderName
text: qsTr("Powered by") onLinkClicked: root.openLinkWithConfirmation(root.serviceProviderURL)
}, onTermsAndConditionClicked: root.openLinkWithConfirmation(root.serviceProviderTandCUrl)
StatusLinkText {
Layout.topMargin: 1 // compensate for the underline
text: root.serviceProviderName
normalColor: Theme.palette.directColor1
linkColor: Theme.palette.directColor1
font.weight: Font.Normal
onClicked: root.openLinkWithConfirmation(root.serviceProviderURL)
},
StatusIcon {
Layout.leftMargin: -2
width: 16
height: 16
icon: "external-link"
color: Theme.palette.directColor1
} }
] ]
infoTagText: qsTr("Review all details before signing") infoTagText: qsTr("Review all details before signing")

View File

@ -576,7 +576,7 @@ QtObject {
case Constants.swap.paraswapSwapContractAddress: case Constants.swap.paraswapSwapContractAddress:
return { return {
"icon": Style.png("swap/%1".arg(Constants.swap.paraswapIcon)), "icon": Style.png("swap/%1".arg(Constants.swap.paraswapIcon)),
"url": Constants.swap.paraswapUrl, "url": Constants.swap.paraswapHostname,
"name": Constants.swap.paraswapName, "name": Constants.swap.paraswapName,
"approvalContractAddress": Constants.swap.paraswapContractAddress, "approvalContractAddress": Constants.swap.paraswapContractAddress,
"swapContractAddress": Constants.swap.paraswapContractAddress, "swapContractAddress": Constants.swap.paraswapContractAddress,

View File

@ -227,13 +227,13 @@ Item {
const networkName = SQUtils.ModelUtils.getByKey(WalletStore.RootStore.filteredFlatModel, "chainId", chainId, "chainName") const networkName = SQUtils.ModelUtils.getByKey(WalletStore.RootStore.filteredFlatModel, "chainId", chainId, "chainName")
if(!!fromToken && !!fromAccountName && !!networkName) { if(!!fromToken && !!fromAccountName && !!networkName) {
const approvalAmount = currencyStore.formatCurrencyAmountFromBigInt(fromAmount, fromToken.symbol, fromToken.decimals) const approvalAmount = currencyStore.formatCurrencyAmountFromBigInt(fromAmount, fromToken.symbol, fromToken.decimals)
let toastTitle = qsTr("Setting spending cap: %1 in %2 for %3 on %4").arg(approvalAmount).arg(fromAccountName).arg(Constants.swap.paraswapUrl).arg(networkName) let toastTitle = qsTr("Setting spending cap: %1 in %2 for %3 on %4").arg(approvalAmount).arg(fromAccountName).arg(Constants.swap.paraswapHostname).arg(networkName)
let toastSubtitle = qsTr("View on %1").arg(networkName) let toastSubtitle = qsTr("View on %1").arg(networkName)
let urlLink = "%1/%2".arg(appMain.rootStore.getEtherscanLink(chainId)).arg(txHash) let urlLink = "%1/%2".arg(appMain.rootStore.getEtherscanLink(chainId)).arg(txHash)
let toastType = Constants.ephemeralNotificationType.normal let toastType = Constants.ephemeralNotificationType.normal
let icon = "" let icon = ""
if(error) { if(error) {
toastTitle = qsTr("Failed to set spending cap: %1 in %2 for %3 on %4").arg(approvalAmount).arg(fromAccountName).arg(Constants.swap.paraswapUrl).arg(networkName) toastTitle = qsTr("Failed to set spending cap: %1 in %2 for %3 on %4").arg(approvalAmount).arg(fromAccountName).arg(Constants.swap.paraswapHostname).arg(networkName)
toastSubtitle = "" toastSubtitle = ""
urlLink = "" urlLink = ""
toastType = Constants.ephemeralNotificationType.danger toastType = Constants.ephemeralNotificationType.danger
@ -251,13 +251,13 @@ Item {
if(!!fromToken && !!toToken && !!fromAccountName && !!networkName) { if(!!fromToken && !!toToken && !!fromAccountName && !!networkName) {
const fromSwapAmount = currencyStore.formatCurrencyAmountFromBigInt(fromAmount, fromToken.symbol, fromToken.decimals) const fromSwapAmount = currencyStore.formatCurrencyAmountFromBigInt(fromAmount, fromToken.symbol, fromToken.decimals)
const toSwapAmount = currencyStore.formatCurrencyAmountFromBigInt(toAmount, toToken.symbol, toToken.decimals) const toSwapAmount = currencyStore.formatCurrencyAmountFromBigInt(toAmount, toToken.symbol, toToken.decimals)
let toastTitle = qsTr("Swapping %1 to %2 in %3 using %4 on %5").arg(fromSwapAmount).arg(toSwapAmount).arg(fromAccountName).arg(Constants.swap.paraswapUrl).arg(networkName) let toastTitle = qsTr("Swapping %1 to %2 in %3 using %4 on %5").arg(fromSwapAmount).arg(toSwapAmount).arg(fromAccountName).arg(Constants.swap.paraswapHostname).arg(networkName)
let toastSubtitle = qsTr("View on %1").arg(networkName) let toastSubtitle = qsTr("View on %1").arg(networkName)
let urlLink = "%1/%2".arg(appMain.rootStore.getEtherscanLink(chainId)).arg(txHash) let urlLink = "%1/%2".arg(appMain.rootStore.getEtherscanLink(chainId)).arg(txHash)
let toastType = Constants.ephemeralNotificationType.normal let toastType = Constants.ephemeralNotificationType.normal
let icon = "" let icon = ""
if(error) { if(error) {
toastTitle = qsTr("Failed to swap %1 to %2 in %3 using %4 on %5").arg(fromSwapAmount).arg(toSwapAmount).arg(fromAccountName).arg(Constants.swap.paraswapUrl).arg(networkName) toastTitle = qsTr("Failed to swap %1 to %2 in %3 using %4 on %5").arg(fromSwapAmount).arg(toSwapAmount).arg(fromAccountName).arg(Constants.swap.paraswapHostname).arg(networkName)
toastSubtitle = "" toastSubtitle = ""
urlLink = "" urlLink = ""
toastType = Constants.ephemeralNotificationType.danger toastType = Constants.ephemeralNotificationType.danger
@ -294,13 +294,13 @@ Item {
const networkName = SQUtils.ModelUtils.getByKey(WalletStore.RootStore.filteredFlatModel, "chainId", chainId, "chainName") const networkName = SQUtils.ModelUtils.getByKey(WalletStore.RootStore.filteredFlatModel, "chainId", chainId, "chainName")
if(!!fromToken && !!fromAccountName && !!networkName) { if(!!fromToken && !!fromAccountName && !!networkName) {
const approvalAmount = currencyStore.formatCurrencyAmountFromBigInt(fromAmount, fromToken.symbol, fromToken.decimals) const approvalAmount = currencyStore.formatCurrencyAmountFromBigInt(fromAmount, fromToken.symbol, fromToken.decimals)
let toastTitle = qsTr("Spending cap set: %1 in %2 for %3 on %4").arg(approvalAmount).arg(fromAccountName).arg(Constants.swap.paraswapUrl).arg(networkName) let toastTitle = qsTr("Spending cap set: %1 in %2 for %3 on %4").arg(approvalAmount).arg(fromAccountName).arg(Constants.swap.paraswapHostname).arg(networkName)
const toastSubtitle = qsTr("View on %1").arg(networkName) const toastSubtitle = qsTr("View on %1").arg(networkName)
const urlLink = "%1/%2".arg(appMain.rootStore.getEtherscanLink(chainId)).arg(txHash) const urlLink = "%1/%2".arg(appMain.rootStore.getEtherscanLink(chainId)).arg(txHash)
let toastType = Constants.ephemeralNotificationType.success let toastType = Constants.ephemeralNotificationType.success
let icon = "checkmark-circle" let icon = "checkmark-circle"
if(!success) { if(!success) {
toastTitle = qsTr("Failed to set spending cap: %1 in %2 for %3 on %4").arg(approvalAmount).arg(fromAccountName).arg(Constants.swap.paraswapUrl).arg(networkName) toastTitle = qsTr("Failed to set spending cap: %1 in %2 for %3 on %4").arg(approvalAmount).arg(fromAccountName).arg(Constants.swap.paraswapHostname).arg(networkName)
toastType = Constants.ephemeralNotificationType.danger toastType = Constants.ephemeralNotificationType.danger
icon = "warning" icon = "warning"
} }
@ -316,13 +316,13 @@ Item {
if(!!fromToken && !!toToken && !!fromAccountName && !!networkName) { if(!!fromToken && !!toToken && !!fromAccountName && !!networkName) {
const fromSwapAmount = currencyStore.formatCurrencyAmountFromBigInt(fromAmount, fromToken.symbol, fromToken.decimals) const fromSwapAmount = currencyStore.formatCurrencyAmountFromBigInt(fromAmount, fromToken.symbol, fromToken.decimals)
const toSwapAmount = currencyStore.formatCurrencyAmountFromBigInt(toAmount, toToken.symbol, toToken.decimals) const toSwapAmount = currencyStore.formatCurrencyAmountFromBigInt(toAmount, toToken.symbol, toToken.decimals)
let toastTitle = qsTr("Swapped %1 to %2 in %3 using %4 on %5").arg(fromSwapAmount).arg(toSwapAmount).arg(fromAccountName).arg(Constants.swap.paraswapUrl).arg(networkName) let toastTitle = qsTr("Swapped %1 to %2 in %3 using %4 on %5").arg(fromSwapAmount).arg(toSwapAmount).arg(fromAccountName).arg(Constants.swap.paraswapHostname).arg(networkName)
const toastSubtitle = qsTr("View on %1").arg(networkName) const toastSubtitle = qsTr("View on %1").arg(networkName)
const urlLink = "%1/%2".arg(appMain.rootStore.getEtherscanLink(chainId)).arg(txHash) const urlLink = "%1/%2".arg(appMain.rootStore.getEtherscanLink(chainId)).arg(txHash)
let toastType = Constants.ephemeralNotificationType.success let toastType = Constants.ephemeralNotificationType.success
let icon = "checkmark-circle" let icon = "checkmark-circle"
if(!success) { if(!success) {
toastTitle = qsTr("Failed to swap %1 to %2 in %3 using %4 on %5").arg(fromSwapAmount).arg(toSwapAmount).arg(fromAccountName).arg(Constants.swap.paraswapUrl).arg(networkName) toastTitle = qsTr("Failed to swap %1 to %2 in %3 using %4 on %5").arg(fromSwapAmount).arg(toSwapAmount).arg(fromAccountName).arg(Constants.swap.paraswapHostname).arg(networkName)
toastType = Constants.ephemeralNotificationType.danger toastType = Constants.ephemeralNotificationType.danger
icon = "warning" icon = "warning"
} }

View File

@ -1426,10 +1426,11 @@ QtObject {
this list dynamically */ this list dynamically */
readonly property string paraswapName: "Paraswap" readonly property string paraswapName: "Paraswap"
readonly property string paraswapIcon: "paraswap" readonly property string paraswapIcon: "paraswap"
readonly property string paraswapUrl: "app.paraswap.io" readonly property string paraswapHostname: "app.paraswap.io"
readonly property string paraswapUrl: "https://www.paraswap.io/"
readonly property string paraswapApproveContractAddress: "0x216B4B4Ba9F3e719726886d34a177484278Bfcae" readonly property string paraswapApproveContractAddress: "0x216B4B4Ba9F3e719726886d34a177484278Bfcae"
readonly property string paraswapSwapContractAddress: "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57" readonly property string paraswapSwapContractAddress: "0xDEF171Fe48CF0115B1d80b88dc8eAB59176FEe57"
readonly property string termsAndConditionParaswapUrl: "https://files.paraswap.io/tos_v4.pdf" readonly property string paraswapTermsAndConditionUrl: "https://files.paraswap.io/tos_v4.pdf"
// TOOD #15874: Unify with WalletUtils router error code handling // TOOD #15874: Unify with WalletUtils router error code handling
readonly property QtObject errorCodes: QtObject { readonly property QtObject errorCodes: QtObject {