diff --git a/src/app/modules/main/wallet_section/accounts/model.nim b/src/app/modules/main/wallet_section/accounts/model.nim index 925d97aa30..11edd92ac8 100644 --- a/src/app/modules/main/wallet_section/accounts/model.nim +++ b/src/app/modules/main/wallet_section/accounts/model.nim @@ -139,3 +139,9 @@ QtObject: if(cmpIgnoreCase(item.address(), address) == 0): return item.colorId() return "" + + proc isOwnedAccount*(self: Model, address: string): bool = + for item in self.items: + if cmpIgnoreCase(item.address(), address) == 0 and item.walletType != "watch": + return true + return false \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/accounts/view.nim b/src/app/modules/main/wallet_section/accounts/view.nim index a146a7955d..94bbf674d2 100644 --- a/src/app/modules/main/wallet_section/accounts/view.nim +++ b/src/app/modules/main/wallet_section/accounts/view.nim @@ -55,9 +55,11 @@ QtObject: proc getColorByAddress(self: View, address: string): string {.slot.}= return self.accounts.getColorByAddress(address) + proc isOwnedAccount(self: View, address: string): bool {.slot.} = + return self.accounts.isOwnedAccount(address) + proc updateWalletAccountProdPreferredChains*(self: View, address: string, preferredChainIds: string) {.slot.} = self.delegate.updateWalletAccountProdPreferredChains(address, preferredChainIds) proc updateWalletAccountTestPreferredChains*(self: View, address: string, preferredChainIds: string) {.slot.} = self.delegate.updateWalletAccountTestPreferredChains(address, preferredChainIds) - diff --git a/src/app/modules/main/wallet_section/activity/entry.nim b/src/app/modules/main/wallet_section/activity/entry.nim index b9ed65e05c..c610c54e32 100644 --- a/src/app/modules/main/wallet_section/activity/entry.nim +++ b/src/app/modules/main/wallet_section/activity/entry.nim @@ -233,15 +233,46 @@ QtObject: QtProperty[int] txType: read = getTxType - proc getType*(self: ActivityEntry): string {.slot.} = - if self.transaction == nil: - error "getType: ActivityEntry is not an transaction entry" - return "" - return self.transaction[].typeValue + proc getTokenType*(self: ActivityEntry): string {.slot.} = + if self.transaction != nil: + return self.transaction[].typeValue + if self.metadata.activityType == backend.ActivityType.Receive and self.metadata.tokenOut.isSome: + return $self.metadata.tokenOut.unsafeGet().tokenType + if self.metadata.tokenIn.isSome: + return $self.metadata.tokenIn.unsafeGet().tokenType + return "" # TODO: used only in details, move it to a entry_details.nim. See #11598 - QtProperty[string] type: - read = getType + QtProperty[string] tokenType: + read = getTokenType + + proc getTokenInAddress*(self: ActivityEntry): string {.slot.} = + if self.metadata.tokenIn.isSome: + let address = self.metadata.tokenIn.unsafeGet().address + if address.isSome: + return toHex(address.unsafeGet()) + return "" + + QtProperty[string] tokenInAddress: + read = getTokenInAddress + + proc getTokenOutAddress*(self: ActivityEntry): string {.slot.} = + if self.metadata.tokenOut.isSome: + let address = self.metadata.tokenOut.unsafeGet().address + if address.isSome: + return toHex(address.unsafeGet()) + return "" + + QtProperty[string] tokenOutAddress: + read = getTokenOutAddress + + proc getTokenAddress*(self: ActivityEntry): string {.slot.} = + if self.metadata.activityType == backend.ActivityType.Receive: + return self.getTokenInAddress() + return self.getTokenOutAddress() + + QtProperty[string] tokenAddress: + read = getTokenAddress proc getContract*(self: ActivityEntry): string {.slot.} = return if self.metadata.contractAddress.isSome(): "0x" & self.metadata.contractAddress.unsafeGet().toHex() else: "" @@ -250,10 +281,11 @@ QtObject: read = getContract proc getTxHash*(self: ActivityEntry): string {.slot.} = - if self.transaction == nil: - error "getTxHash: ActivityEntry is not an transaction entry" - return "" - return self.transaction[].txHash + if self.transaction != nil and len(self.transaction[].txHash) > 0: + return self.transaction[].txHash + if self.metadata.transaction.isSome: + return self.metadata.transaction.unsafeGet().hash + return "" # TODO: used only in details, move it to a entry_details.nim. See #11598 QtProperty[string] txHash: diff --git a/src/app/modules/main/wallet_section/view.nim b/src/app/modules/main/wallet_section/view.nim index 25441ae944..0aeddd446b 100644 --- a/src/app/modules/main/wallet_section/view.nim +++ b/src/app/modules/main/wallet_section/view.nim @@ -150,7 +150,7 @@ QtObject: proc getLatestBlockNumber*(self: View, chainId: int): string {.slot.} = return self.delegate.getLatestBlockNumber(chainId) - proc fetchDecodedTxData*(self: View, txHash: string, data: string) = + proc fetchDecodedTxData*(self: View, txHash: string, data: string) {.slot.} = self.delegate.fetchDecodedTxData(txHash, data) proc getIsNonArchivalNode(self: View): bool {.slot.} = diff --git a/src/backend/activity.nim b/src/backend/activity.nim index d8c2df8de1..e5e40a1183 100644 --- a/src/backend/activity.nim +++ b/src/backend/activity.nim @@ -89,6 +89,19 @@ proc fromJson*(jn: JsonNode, T: typedesc[ActivityStatus]): ActivityStatus {.inli proc `%`*(tt: TokenType): JsonNode {.inline.} = return newJInt(ord(tt)) +proc `$`*(tt: TokenType): string {.inline.} = + case tt: + of Native: + return "ETH" + of Erc20: + return "ERC-20" + of Erc721: + return "ERC-721" + of Erc1155: + return "ERC-1155" + else: + return "" + proc fromJson*(jn: JsonNode, T: typedesc[TokenType]): TokenType {.inline.} = return cast[TokenType](jn.getInt()) diff --git a/ui/app/AppLayouts/Wallet/controls/StatusTxProgressBar.qml b/ui/app/AppLayouts/Wallet/controls/StatusTxProgressBar.qml index 7e0305b1c6..4212e70b6f 100644 --- a/ui/app/AppLayouts/Wallet/controls/StatusTxProgressBar.qml +++ b/ui/app/AppLayouts/Wallet/controls/StatusTxProgressBar.qml @@ -10,6 +10,7 @@ ColumnLayout { property bool isLayer1: true property bool error: false + property bool pending: false property int steps: isLayer1 ? 64 : 1 property int confirmations: 0 property int confirmationBlocks: isLayer1 ? 4 : 1 @@ -43,14 +44,18 @@ ColumnLayout { color: Theme.palette.directColor1 lineHeight: 22 lineHeightMode: Text.FixedHeight - text: error ? qsTr("Failed on %1").arg(root.chainName) : - d.finalized ? - qsTr("Finalised on %1").arg(root.chainName) : - d.confirmed ? - qsTr("Confirmed on %1, finalisation in progress...").arg(root.chainName): - confirmations > 0 ? - qsTr("Confirmation in progress on %1...").arg(root.chainName) : - qsTr("Pending on %1...").arg(root.chainName) + text: { + if (error) { + return qsTr("Failed on %1").arg(root.chainName) + } else if (pending) { + return qsTr("Confirmation in progress on %1...").arg(root.chainName) + } else if (d.finalized) { + return qsTr("Finalised on %1").arg(root.chainName) + } else if (d.confirmed) { + return qsTr("Confirmed on %1, finalisation in progress...").arg(root.chainName) + } + return qsTr("Pending on %1...").arg(root.chainName) + } } RowLayout { @@ -99,8 +104,13 @@ ColumnLayout { color: Theme.palette.baseColor1 lineHeight: 18 lineHeightMode: Text.FixedHeight - text: d.finalized && !root.error ? qsTr("In epoch %1").arg(root.confirmations) : d.confirmed && !root.isLayer1 ? - qsTr("%n day(s) until finality", "", Math.ceil((root.duration - root.progress)/d.hoursInADay)): - qsTr("%1 / %2 confirmations").arg(root.confirmations).arg(root.steps) + text: { + if (d.finalized && !root.error) { + return qsTr("In epoch %1").arg(root.confirmations) + } else if (d.confirmed && !root.isLayer1) { + return qsTr("%n day(s) until finality", "", Math.ceil((root.duration - root.progress)/d.hoursInADay)) + } + return qsTr("%1 / %2 confirmations").arg(root.confirmations).arg(root.steps) + } } } diff --git a/ui/app/AppLayouts/Wallet/panels/WalletTxProgressBlock.qml b/ui/app/AppLayouts/Wallet/panels/WalletTxProgressBlock.qml index 7e7dc0ef4d..2cbf339f0b 100644 --- a/ui/app/AppLayouts/Wallet/panels/WalletTxProgressBlock.qml +++ b/ui/app/AppLayouts/Wallet/panels/WalletTxProgressBlock.qml @@ -15,6 +15,7 @@ ColumnLayout { // To-do adapt this for multi-tx, not sure how the data will look for that yet property bool isLayer1: true property bool error: false + property bool pending: false property int confirmations: 0 property string chainName property double timeStamp @@ -23,8 +24,8 @@ ColumnLayout { QtObject { id: d - readonly property bool finalized: (isLayer1 ? confirmations >= progressBar.steps : progress >= duration) && !error - readonly property bool confirmed: confirmations >= progressBar.confirmationBlocks && !error + readonly property bool finalized: (isLayer1 ? confirmations >= progressBar.steps : progress >= duration) && !error && !pending + readonly property bool confirmed: confirmations >= progressBar.confirmationBlocks && !error && !pending readonly property double confirmationTimeStamp: { if (root.isLayer1) { return root.timeStamp + 12 * 4 // A block on layer1 is every 12s @@ -42,7 +43,7 @@ ColumnLayout { } readonly property int duration: 168 // 7 days in hours - readonly property int progress: (Math.floor(Date.now() / 1000) - root.timeStamp) / 3600 + readonly property int progress: pending || error ? 0 : (Math.floor(Date.now() / 1000) - root.timeStamp) / 3600 } StatusTxProgressBar { diff --git a/ui/app/AppLayouts/Wallet/stores/RootStore.qml b/ui/app/AppLayouts/Wallet/stores/RootStore.qml index 39ee992e59..32d9961b04 100644 --- a/ui/app/AppLayouts/Wallet/stores/RootStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/RootStore.qml @@ -190,6 +190,10 @@ QtObject { return name } + function isOwnedAccount(address) { + return walletSectionAccounts.isOwnedAccount(address) + } + function getEmojiForWalletAddress(address) { return walletSectionAccounts.getEmojiByAddress(address) } diff --git a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml index 873bdac65b..7738bb5926 100644 --- a/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml +++ b/ui/app/AppLayouts/Wallet/views/TransactionDetailView.qml @@ -36,7 +36,7 @@ Item { if (!transaction || !transaction.input) return d.loadingInputDate = true - walletSection.fetchDecodedTxData(transaction.txHash, transaction.input) + RootStore.fetchDecodedTxData(transaction.txHash, transaction.input) } QtObject { @@ -61,7 +61,7 @@ Item { if (!root.isTransactionValid || transactionHeader.isMultiTransaction) return "" const formatted = RootStore.formatCurrencyAmount(transaction.amount, transaction.symbol) - return symbol || !transaction.contract ? formatted : "%1 (%2)".arg(formatted).arg(Utils.compactAddress(transaction.contract, 4)) + return symbol || !transaction.contract ? formatted : "%1 (%2)".arg(formatted).arg(Utils.compactAddress(transaction.tokenAddress, 4)) } readonly property string outFiatValueFormatted: { if (!root.isTransactionValid || !transactionHeader.isMultiTransaction || !outSymbol) @@ -72,7 +72,7 @@ Item { if (!root.isTransactionValid || !transactionHeader.isMultiTransaction) return "" const formatted = RootStore.formatCurrencyAmount(transaction.outAmount, transaction.outSymbol) - return outSymbol || !transaction.contract ? formatted : "%1 (%2)".arg(formatted).arg(Utils.compactAddress(transaction.contract, 4)) + return outSymbol || !transaction.tokenOutAddress ? formatted : "%1 (%2)".arg(formatted).arg(Utils.compactAddress(transaction.tokenOutAddress, 4)) } readonly property real feeEthValue: root.isTransactionValid ? RootStore.getGasEthValue(transaction.totalFees.amount, 1) : 0 // TODO use directly? readonly property real feeFiatValue: root.isTransactionValid ? RootStore.getFiatValue(d.feeEthValue, Constants.ethToken, RootStore.currentCurrency) : 0 // TODO use directly? @@ -82,10 +82,6 @@ Item { property string decodedInputData: "" property bool loadingInputDate: false - function getNameForSavedWalletAddress(address) { - return RootStore.getNameForSavedWalletAddress(address) - } - function retryTransaction() { // TODO handle failed transaction retry } @@ -155,8 +151,9 @@ Item { id: progressBlock width: Math.min(513, root.width) error: transactionHeader.transactionStatus === Constants.TransactionStatus.Failed + pending: transactionHeader.transactionStatus === Constants.TransactionStatus.Pending isLayer1: root.isTransactionValid && RootStore.getNetworkLayer(root.transaction.chainId) == 1 - confirmations: root.isTransactionValid ? Math.abs(WalletStores.RootStore.getLatestBlockNumber(root.transaction.chainId) - d.blockNumber): 0 + confirmations: root.isTransactionValid && !pending && !error ? Math.abs(WalletStores.RootStore.getLatestBlockNumber(root.transaction.chainId) - d.blockNumber): 0 chainName: transactionHeader.networkName timeStamp: root.isTransactionValid ? transaction.timestamp: "" } @@ -205,7 +202,7 @@ Item { subTitle: { switch(d.transactionType) { case Constants.TransactionType.Swap: - return d.outSymbol + return !!d.outSymbol ? d.outSymbol : " " case Constants.TransactionType.Bridge: return transactionHeader.networkName default: @@ -215,7 +212,7 @@ Item { asset.name: { switch(d.transactionType) { case Constants.TransactionType.Swap: - return !!d.outSymbol ? Constants.tokenIcon(d.outSymbol) : "" + return Constants.tokenIcon(d.outSymbol) case Constants.TransactionType.Bridge: return !!d.networkIcon ? Style.svg(d.networkIcon) : "" default: @@ -232,7 +229,7 @@ Item { subTitle: { switch(d.transactionType) { case Constants.TransactionType.Swap: - return d.inSymbol + return !!d.inSymbol ? d.inSymbol : " " case Constants.TransactionType.Bridge: return d.toNetworkName default: @@ -242,7 +239,7 @@ Item { asset.name: { switch(d.transactionType) { case Constants.TransactionType.Swap: - return !!d.inSymbol ? Constants.tokenIcon(d.inSymbol) : "" + return Constants.tokenIcon(d.inSymbol) case Constants.TransactionType.Bridge: return !!d.toNetworkIcon ? Style.svg(d.toNetworkIcon) : "" default: @@ -346,7 +343,7 @@ Item { symbol: { if (!root.isTransactionValid) return "" - return d.symbol ? d.symbol : "(%1)".arg(Utils.compactAddress(transaction.contract, 4)) + return d.symbol ? d.symbol : "(%1)".arg(Utils.compactAddress(transaction.tokenAddress, 4)) } networkName: transactionHeader.networkName shortNetworkName: d.networkShortName @@ -440,13 +437,13 @@ Item { asset.name: !!d.networkIcon ? Style.svg(d.networkIcon) : "" subTitleBadgeLoaderAlignment: Qt.AlignTop smallIcon: true - visible: d.transactionType !== Constants.TransactionType.Bridge + visible: !!subTitle && d.transactionType !== Constants.TransactionType.Bridge } TransactionDataTile { Layout.fillHeight: true Layout.fillWidth: true title: qsTr("Token format") - subTitle: root.isTransactionValid ? transaction.type.toUpperCase() : "" + subTitle: root.isTransactionValid ? transaction.tokenType.toUpperCase() : "" visible: !!subTitle } TransactionDataTile { diff --git a/ui/imports/shared/controls/TransactionDelegate.qml b/ui/imports/shared/controls/TransactionDelegate.qml index 835a2331d3..8e87710b84 100644 --- a/ui/imports/shared/controls/TransactionDelegate.qml +++ b/ui/imports/shared/controls/TransactionDelegate.qml @@ -69,15 +69,28 @@ StatusListItem { return qsTr("N/A") } else if (root.isNFT) { return modelData.nftName ? modelData.nftName : "#" + modelData.tokenID - } else if (!modelData.symbol) { - return "%1 (%2)".arg(root.rootStore.formatCurrencyAmount(cryptoValue, "")).arg(Utils.compactAddress(modelData.contract, 4)) + } else if (!modelData.symbol && !!modelData.tokenAddress) { + return "%1 (%2)".arg(root.rootStore.formatCurrencyAmount(cryptoValue, "")).arg(Utils.compactAddress(modelData.tokenAddress, 4)) } - return root.rootStore.formatCurrencyAmount(cryptoValue, modelData.symbol) } - readonly property string inTransactionValue: isModelDataValid && isMultiTransaction ? rootStore.formatCurrencyAmount(inCryptoValue, modelData.inSymbol) : qsTr("N/A") - readonly property string outTransactionValue: isModelDataValid && isMultiTransaction ? rootStore.formatCurrencyAmount(outCryptoValue, modelData.outSymbol) : qsTr("N/A") + readonly property string inTransactionValue: { + if (!isModelDataValid || !isMultiTransaction) { + return qsTr("N/A") + } else if (!modelData.inSymbol && !!modelData.tokenInAddress) { + return "%1 (%2)".arg(root.rootStore.formatCurrencyAmount(inCryptoValue, "")).arg(Utils.compactAddress(modelData.tokenInAddress, 4)) + } + return rootStore.formatCurrencyAmount(inCryptoValue, modelData.inSymbol) + } + readonly property string outTransactionValue: { + if (!isModelDataValid || !isMultiTransaction) { + return qsTr("N/A") + } else if (!modelData.outSymbol && !!modelData.tokenOutAddress) { + return "%1 (%2)".arg(root.rootStore.formatCurrencyAmount(outCryptoValue, "")).arg(Utils.compactAddress(modelData.tokenOutAddress, 4)) + } + return rootStore.formatCurrencyAmount(outCryptoValue, modelData.outSymbol) + } readonly property string tokenImage: { if (!isModelDataValid || modelData.txType === Constants.TransactionType.ContractDeployment) @@ -146,6 +159,7 @@ StatusListItem { property int datePixelSize: 12 property int titlePixelSize: 15 property int subtitlePixelSize: 13 + property bool showRetryButton: false } function getDetailsString() { @@ -293,7 +307,7 @@ StatusListItem { details += protocolFromContractAddress + endl2 } if (!!modelData.contract && type !== Constants.TransactionType.ContractDeployment && !/0x0+$/.test(modelData.contract)) { - let symbol = !!modelData.symbol || !modelData.contract ? modelData.symbol : "(%1)".arg(Utils.compactAddress(modelData.contract, 4)) + let symbol = !!modelData.symbol || !modelData.tokenAddress ? modelData.symbol : "(%1)".arg(Utils.compactAddress(modelData.tokenAddress, 4)) details += qsTr("%1 %2 contract address").arg(root.networkName).arg(symbol) + endl details += modelData.contract + endl2 } @@ -325,7 +339,7 @@ StatusListItem { if (type !== Constants.TransactionType.Bridge) { details += qsTr("Network") + endl + networkName + endl2 } - details += qsTr("Token format") + endl + modelData.type.toUpperCase() + endl2 + details += qsTr("Token format") + endl + modelData.tokenType.toUpperCase() + endl2 details += qsTr("Nonce") + endl + rootStore.hex2Dec(modelData.nonce) + endl2 if (type === Constants.TransactionType.Bridge) { details += qsTr("Included in Block on %1").arg(networkName) + endl @@ -716,7 +730,7 @@ StatusListItem { text: qsTr("Retry") size: StatusButton.Small type: StatusButton.Primary - visible: !root.loading && root.transactionStatus === Constants.TransactionStatus.Failed + visible: d.showRetryButton onClicked: root.retryClicked() } @@ -730,6 +744,7 @@ StatusListItem { loading: root.loading name: root.title } + StatusRoundIcon { visible: !root.loading anchors { @@ -759,7 +774,7 @@ StatusListItem { } PropertyChanges { target: root.asset - bgBorderWidth: root.transactionStatus === Constants.TransactionStatus.Failed ? 0 : 1 + bgBorderWidth: d.showRetryButton ? 0 : 1 width: 34 height: 34 bgWidth: 56 @@ -781,6 +796,7 @@ StatusListItem { datePixelSize: 13 subtitlePixelSize: 15 loadingPixelSize: 14 + showRetryButton: (!root.loading && root.transactionStatus === Constants.TransactionStatus.Failed && walletRootStore.isOwnedAccount(modelData.sender)) } } ] diff --git a/ui/imports/shared/stores/RootStore.qml b/ui/imports/shared/stores/RootStore.qml index 6a3d7cdd02..f513b68d22 100644 --- a/ui/imports/shared/stores/RootStore.qml +++ b/ui/imports/shared/stores/RootStore.qml @@ -239,6 +239,10 @@ QtObject { walletSectionAllTokens.getHistoricalDataForToken(symbol,currency) } + function fetchDecodedTxData(txHash, input) { + walletSectionInst.fetchDecodedTxData(txHash, input) + } + property bool marketHistoryIsLoading: Global.appIsReady? walletSectionAllTokens.marketHistoryIsLoading : false function fetchHistoricalBalanceForTokenAsJson(address, tokenSymbol, currencySymbol, timeIntervalEnum) {