diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index a8292c81f20..3f7275f50f9 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -1646,6 +1646,8 @@ method activateStatusDeepLink*[T](self: Module[T], statusDeepLink: string) = self.onStatusUrlRequested(StatusUrlAction.DisplayUserProfile, communityId="", channelId="", url="", urlData.contact.publicKey, urlData.community.shard) return + if urlData.transaction.txType >= 0: + self.view.emitShowTransactionModal(urlData.transaction.txType, urlData.transaction.asset, urlData.transaction.amount, urlData.transaction.address, urlData.transaction.chainId, urlData.transaction.toAsset) method onDeactivateChatLoader*[T](self: Module[T], sectionId: string, chatId: string) = if (sectionId.len > 0 and self.chatSectionModules.contains(sectionId)): diff --git a/src/app/modules/main/shared_urls/controller.nim b/src/app/modules/main/shared_urls/controller.nim index 0cd48aaef76..83ef7198c01 100644 --- a/src/app/modules/main/shared_urls/controller.nim +++ b/src/app/modules/main/shared_urls/controller.nim @@ -37,3 +37,7 @@ proc parseContactSharedUrl*(self: Controller, url: string): ContactUrlDataDto = proc parseSharedUrl*(self: Controller, url: string): UrlDataDto = return self.sharedUrlsService.parseSharedUrl(url) + +proc parseTransactionSharedUrl*(self: Controller, url: string): TransactionUrlDataDto = + let data = self.sharedUrlsService.parseSharedUrl(url) + return data.transaction \ No newline at end of file diff --git a/src/app/modules/main/shared_urls/io_interface.nim b/src/app/modules/main/shared_urls/io_interface.nim index 93bb9c60133..08e4997d666 100644 --- a/src/app/modules/main/shared_urls/io_interface.nim +++ b/src/app/modules/main/shared_urls/io_interface.nim @@ -27,6 +27,9 @@ method parseContactSharedUrl*(self: AccessInterface, url: string): string {.base method parseSharedUrl*(self: AccessInterface, url: string): UrlDataDto {.base.} = raise newException(ValueError, "No implementation available") +method parseTransactionSharedUrl*(self: AccessInterface, url: string): string {.base.} = + raise newException(ValueError, "No implementation available") + # This way (using concepts) is used only for the modules managed by AppController type DelegateInterface* = concept c diff --git a/src/app/modules/main/shared_urls/module.nim b/src/app/modules/main/shared_urls/module.nim index c15a1461719..3d601b99554 100644 --- a/src/app/modules/main/shared_urls/module.nim +++ b/src/app/modules/main/shared_urls/module.nim @@ -63,3 +63,7 @@ method parseCommunityChannelSharedUrl*(self: Module, url: string): string = method parseContactSharedUrl*(self: Module, url: string): string = let contactData = self.controller.parseContactSharedUrl(url) return $contactData + +method parseTransactionSharedUrl*(self: Module, url: string): string = + let transactionData = self.controller.parseTransactionSharedUrl(url) + return $transactionData \ No newline at end of file diff --git a/src/app/modules/main/shared_urls/view.nim b/src/app/modules/main/shared_urls/view.nim index b321f8f7214..aa1edb92124 100644 --- a/src/app/modules/main/shared_urls/view.nim +++ b/src/app/modules/main/shared_urls/view.nim @@ -25,4 +25,7 @@ QtObject: return self.delegate.parseCommunityChannelSharedUrl(url) proc parseContactSharedUrl*(self: View, url: string): string {.slot.} = - return self.delegate.parseContactSharedUrl(url) \ No newline at end of file + return self.delegate.parseContactSharedUrl(url) + + proc parseTransactionSharedUrl*(self: View, url: string): string {.slot.} = + return self.delegate.parseTransactionSharedUrl(url) \ No newline at end of file diff --git a/src/app/modules/main/view.nim b/src/app/modules/main/view.nim index 25dc778791d..cd6ddf772d0 100644 --- a/src/app/modules/main/view.nim +++ b/src/app/modules/main/view.nim @@ -382,3 +382,8 @@ QtObject: proc stopTokenHoldersManagement*(self: View) {.slot.} = self.delegate.stopTokenHoldersManagement() + + proc showTransactionModal*(self: View, txType: int, asset: string, amount: string, address: string, chainId: int, toAsset: string) {.signal.} + proc emitShowTransactionModal*(self: View, txType: int, asset: string, amount: string, address: string, chainId: int, toAsset: string) = + self.showTransactionModal(txType, asset, amount, address, chainId, toAsset) + diff --git a/src/app/modules/main/wallet_section/send/controller.nim b/src/app/modules/main/wallet_section/send/controller.nim index a39345e0936..aa5c8b6a274 100644 --- a/src/app/modules/main/wallet_section/send/controller.nim +++ b/src/app/modules/main/wallet_section/send/controller.nim @@ -8,6 +8,7 @@ import app_service/service/currency/service as currency_service import app_service/service/currency/dto as currency_dto import app_service/service/keycard/service as keycard_service import app_service/service/network/network_item +import app_service/service/shared_urls/dto/url_data as shared_urls_dto import app/modules/shared_modules/keycard_popup/io_interface as keycard_shared_module import app/modules/shared/wallet_utils @@ -137,6 +138,9 @@ proc signMessage*(self: Controller, address: string, hashedPassword: string, has proc sendRouterTransactionsWithSignatures*(self: Controller, uuid: string, signatures: TransactionsSignatures): string = return self.transactionService.sendRouterTransactionsWithSignatures(uuid, signatures) +proc shareTransactionURL*(self: Controller, urlData: shared_urls_dto.TransactionURLDataDto): string = + return self.transactionService.shareTransactionURL(urlData) + proc areTestNetworksEnabled*(self: Controller): bool = return self.walletAccountService.areTestNetworksEnabled() diff --git a/src/app/modules/main/wallet_section/send/io_interface.nim b/src/app/modules/main/wallet_section/send/io_interface.nim index 7bccd6b03f1..151a0de9b18 100644 --- a/src/app/modules/main/wallet_section/send/io_interface.nim +++ b/src/app/modules/main/wallet_section/send/io_interface.nim @@ -2,6 +2,7 @@ import Tables import app/modules/shared_models/currency_amount import app_service/service/transaction/dto import app_service/service/transaction/router_transactions_dto +import app_service/service/shared_urls/dto/url_data import app_service/service/network/network_item import app/modules/shared_models/collectibles_model as collectibles from app_service/service/keycard/service import KeycardEvent @@ -90,4 +91,7 @@ method getNetworkItem*(self: AccessInterface, chainId: int): NetworkItem {.base. raise newException(ValueError, "No implementation available") method getNetworkChainId*(self: AccessInterface, shortName: string): int {.base.} = + raise newException(ValueError, "No implementation available") + +method shareTransactionURL*(self: AccessInterface, urlData: TransactionURLDataDto): string {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/send/module.nim b/src/app/modules/main/wallet_section/send/module.nim index 1f0354294f6..35306efb6ab 100644 --- a/src/app/modules/main/wallet_section/send/module.nim +++ b/src/app/modules/main/wallet_section/send/module.nim @@ -14,6 +14,7 @@ import app_service/service/transaction/service as transaction_service import app_service/service/keycard/service as keycard_service import app_service/service/keycard/constants as keycard_constants import app_service/service/transaction/dto +import app_service/service/shared_urls/dto/url_data as shared_urls_dto import app/modules/shared_models/currency_amount import app_service/service/network/network_item as network_service_item @@ -404,3 +405,6 @@ method splitAndFormatAddressPrefix*(self: Module, text : string, updateInStore: method transactionSendingComplete*(self: Module, txHash: string, status: string) = self.view.sendtransactionSendingCompleteSignal(txHash, status) + +method shareTransactionURL*(self: Module, urlData: shared_urls_dto.TransactionURLDataDto): string = + return self.controller.shareTransactionURL(urlData) \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/send/view.nim b/src/app/modules/main/wallet_section/send/view.nim index e5056241d8c..a3d42ee1c2e 100644 --- a/src/app/modules/main/wallet_section/send/view.nim +++ b/src/app/modules/main/wallet_section/send/view.nim @@ -3,6 +3,7 @@ import NimQml, Tables, json, sequtils, strutils, stint, chronicles import ./io_interface, ./network_route_model, ./network_route_item, ./suggested_route_item, ./transaction_routes import app_service/service/network/service as network_service import app_service/service/transaction/dto as transaction_dto +import app_service/service/shared_urls/dto/url_data as shared_urls_dto import app_service/common/utils as common_utils import app_service/service/eth/utils as eth_utils @@ -295,6 +296,16 @@ QtObject: parseChainIds(disabledToChainIDs), lockedInAmountsTable) + proc shareTransactionURL*(self: View, txType: int, asset: string, amount: string, address: string, chainId: int, toAsset: string): string {.slot.} = + return self.delegate.shareTransactionURL(shared_urls_dto.TransactionURLDataDto( + txType: txType, + asset: asset, + amount: amount, + address: address, + chainId: chainId, + toAsset: toAsset + )) + proc transactionSendingComplete*(self: View, txHash: string, status: string) {.signal.} proc sendtransactionSendingCompleteSignal*(self: View, txHash: string, status: string) = self.transactionSendingComplete(txHash, status) diff --git a/src/app/modules/shared_models/link_preview_model.nim b/src/app/modules/shared_models/link_preview_model.nim index 9a22c1f5cac..ef5261ced08 100644 --- a/src/app/modules/shared_models/link_preview_model.nim +++ b/src/app/modules/shared_models/link_preview_model.nim @@ -35,6 +35,8 @@ type StatusCommunityChannelCommunityPreview StatusCommunityChannelCommunityPreviewIcon StatusCommunityChannelCommunityPreviewBanner + # Status transaction + StatusTransactionPreview QtObject: type @@ -105,6 +107,8 @@ QtObject: ModelRole.StatusCommunityChannelCommunityPreview.int:"statusCommunityChannelCommunityPreview", ModelRole.StatusCommunityChannelCommunityPreviewIcon.int:"statusCommunityChannelCommunityPreviewIcon", ModelRole.StatusCommunityChannelCommunityPreviewBanner.int:"statusCommunityChannelCommunityPreviewBanner", + # Transaction + ModelRole.StatusTransactionPreview.int:"statusTransactionPreview", }.toTable method data(self: Model, index: QModelIndex, role: int): QVariant = @@ -165,6 +169,9 @@ QtObject: of ModelRole.StatusCommunityChannelCommunityPreviewBanner: if (let community = item.linkPreview.getChannelCommunity(); community) != nil: result = newQVariant(community.getBanner()) + of ModelRole.StatusTransactionPreview: + if item.linkPreview.statusTransactionPreview != nil: + result = newQVariant(item.linkPreview.statusTransactionPreview) else: result = newQVariant() diff --git a/src/app_service/service/message/dto/link_preview.nim b/src/app_service/service/message/dto/link_preview.nim index 651b6008f4f..0c9fd933b09 100644 --- a/src/app_service/service/message/dto/link_preview.nim +++ b/src/app_service/service/message/dto/link_preview.nim @@ -1,6 +1,6 @@ import json, stew/shims/strformat, tables import ./status_link_preview, ./standard_link_preview -import ./status_contact_link_preview, ./status_community_link_preview, ./status_community_channel_link_preview +import ./status_contact_link_preview, ./status_community_link_preview, ./status_community_channel_link_preview, ./status_transaction_link_preview import ../../contacts/dto/contact_details import ../../community/dto/community include ../../../common/json_utils @@ -13,6 +13,7 @@ type StatusContactPreview StatusCommunityPreview StatusCommunityChannelPreview + StatusTransactionPreview LinkPreview* = ref object url*: string @@ -21,6 +22,7 @@ type statusContactPreview*: StatusContactLinkPreview statusCommunityPreview*: StatusCommunityLinkPreview statusCommunityChannelPreview*: StatusCommunityChannelLinkPreview + statusTransactionPreview*: StatusTransactionLinkPreview proc delete*(self: LinkPreview) = if self.standardPreview != nil: @@ -31,6 +33,8 @@ proc delete*(self: LinkPreview) = self.statusCommunityPreview.delete if self.statusCommunityChannelPreview != nil: self.statusCommunityChannelPreview.delete + if self.statusTransactionPreview != nil: + self.statusTransactionPreview.delete proc initLinkPreview*(url: string): LinkPreview = result = LinkPreview() @@ -57,19 +61,24 @@ proc toLinkPreview*(jsonObj: JsonNode, standard: bool): LinkPreview = elif jsonObj.getProp("channel", node): result.previewType = PreviewType.StatusCommunityChannelPreview result.statusCommunityChannelPreview = toStatusCommunityChannelLinkPreview(node) + elif jsonObj.getProp("transaction", node): + result.previewType = PreviewType.StatusTransactionPreview + result.statusTransactionPreview = toStatusTransactionLinkPreview(node) proc `$`*(self: LinkPreview): string = let standardPreview = if self.standardPreview != nil: $self.standardPreview else: "" let contactPreview = if self.statusContactPreview != nil: $self.statusContactPreview else: "" let communityPreview = if self.statusCommunityPreview != nil: $self.statusCommunityPreview else: "" let channelPreview = if self.statusCommunityChannelPreview != nil: $self.statusCommunityChannelPreview else: "" + let transactionPreview = if self.statusTransactionPreview != nil: $self.statusTransactionPreview else: "" result = fmt"""LinkPreview( url: {self.url}, previewType: {self.previewType}, standardPreview: {standardPreview}, contactPreview: {contactPreview}, communityPreview: {communityPreview}, - channelPreview: {channelPreview} + channelPreview: {channelPreview}, + transactionPreview: {transactionPreview} )""" proc `%`*(self: LinkPreview): JsonNode = @@ -78,7 +87,8 @@ proc `%`*(self: LinkPreview): JsonNode = "standardPreview": if self.standardPreview != nil: %self.standardPreview else: newJNull(), "contactPreview": if self.statusContactPreview != nil: %self.statusContactPreview else: newJNull(), "communityPreview": if self.statusCommunityPreview != nil: %self.statusCommunityPreview else: newJNull(), - "channelPreview": if self.statusCommunityChannelPreview != nil: %self.statusCommunityChannelPreview else: newJNull() + "channelPreview": if self.statusCommunityChannelPreview != nil: %self.statusCommunityChannelPreview else: newJNull(), + "transactionPreview": if self.statusTransactionPreview != nil: %self.statusTransactionPreview else: newJNull() } proc empty*(self: LinkPreview): bool = @@ -91,6 +101,8 @@ proc empty*(self: LinkPreview): bool = return self.statusCommunityPreview == nil or self.statusCommunityPreview.empty() of PreviewType.StatusCommunityChannelPreview: return self.statusCommunityChannelPreview == nil or self.statusCommunityChannelPreview.empty() + of PreviewType.StatusTransactionPreview: + return self.statusTransactionPreview == nil or self.statusTransactionPreview.empty() else: return true @@ -119,6 +131,11 @@ proc extractLinkPreviewsLists*(input: seq[LinkPreview]): (seq[StandardLinkPrevie statusLinkPreview.url = preview.url statusLinkPreview.channel = preview.statusCommunityChannelPreview status.add(statusLinkPreview) + of PreviewType.StatusTransactionPreview: + let statusLinkPreview = StatusLinkPreview() + statusLinkPreview.url = preview.url + statusLinkPreview.transaction = preview.statusTransactionPreview + status.add(statusLinkPreview) else: discard diff --git a/src/app_service/service/message/dto/status_link_preview.nim b/src/app_service/service/message/dto/status_link_preview.nim index cabe24a8d31..e0e815a88e3 100644 --- a/src/app_service/service/message/dto/status_link_preview.nim +++ b/src/app_service/service/message/dto/status_link_preview.nim @@ -2,6 +2,7 @@ import json, chronicles import status_contact_link_preview import status_community_link_preview import status_community_channel_link_preview +import status_transaction_link_preview include ../../../common/json_utils @@ -10,6 +11,7 @@ type StatusLinkPreview* = ref object contact*: StatusContactLinkPreview community*: StatusCommunityLinkPreview channel*: StatusCommunityChannelLinkPreview + transaction*: StatusTransactionLinkPreview proc toStatusLinkPreview*(jsonObj: JsonNode): StatusLinkPreview = result = StatusLinkPreview() @@ -27,6 +29,10 @@ proc toStatusLinkPreview*(jsonObj: JsonNode): StatusLinkPreview = if jsonObj.getProp("channel", channel): result.channel = toStatusCommunityChannelLinkPreview(contact) + var transaction: JsonNode + if jsonObj.getProp("transaction", transaction): + result.transaction = toStatusTransactionLinkPreview(transaction) + proc `%`*(self: StatusLinkPreview): JsonNode = var obj = %*{ "url": self.url @@ -37,4 +43,6 @@ proc `%`*(self: StatusLinkPreview): JsonNode = obj["community"] = %*self.community if self.channel != nil: obj["channel"] = %*self.channel + if self.transaction != nil: + obj["transaction"] = %*self.transaction return obj diff --git a/src/app_service/service/message/dto/status_transaction_link_preview.nim b/src/app_service/service/message/dto/status_transaction_link_preview.nim new file mode 100644 index 00000000000..fea8a322814 --- /dev/null +++ b/src/app_service/service/message/dto/status_transaction_link_preview.nim @@ -0,0 +1,110 @@ +import json, stew/shims/strformat, NimQml, chronicles + +include ../../../common/json_utils + +QtObject: + type StatusTransactionLinkPreview* = ref object of QObject + txType: int + amount: string + asset: string + toAsset: string + address: string + chainId: int + + proc setup*(self: StatusTransactionLinkPreview) = + self.QObject.setup() + + proc delete*(self: StatusTransactionLinkPreview) = + self.QObject.delete() + + proc newStatusTransactionLinkPreview*(txType: int, amount: string, asset: string, toAsset: string, address: string, chainId: int): StatusTransactionLinkPreview = + new(result, delete) + result.setup() + result.txType = txType + result.amount = amount + result.asset = asset + result.toAsset = toAsset + result.address = address + result.chainId = chainId + + proc txTypeChanged*(self: StatusTransactionLinkPreview) {.signal.} + proc getTxType*(self: StatusTransactionLinkPreview): int {.slot.} = + result = self.txType + QtProperty[int] txType: + read = getTxType + notify = txTypeChanged + + proc amountChanged*(self: StatusTransactionLinkPreview) {.signal.} + proc getAmount*(self: StatusTransactionLinkPreview): string {.slot.} = + result = self.amount + QtProperty[string] amount: + read = getAmount + notify = amountChanged + + proc assetChanged*(self: StatusTransactionLinkPreview) {.signal.} + proc getAsset*(self: StatusTransactionLinkPreview): string {.slot.} = + result = self.asset + QtProperty[string] asset: + read = getAsset + notify = assetChanged + + proc toAssetChanged*(self: StatusTransactionLinkPreview) {.signal.} + proc getToAsset*(self: StatusTransactionLinkPreview): string {.slot.} = + result = self.toAsset + QtProperty[string] toAsset: + read = getToAsset + notify = toAssetChanged + + proc addressChanged*(self: StatusTransactionLinkPreview) {.signal.} + proc getAddress*(self: StatusTransactionLinkPreview): string {.slot.} = + result = self.address + QtProperty[string] address: + read = getAddress + notify = addressChanged + + proc chainIdChanged*(self: StatusTransactionLinkPreview) {.signal.} + proc getChainId*(self: StatusTransactionLinkPreview): int {.slot.} = + result = self.chainId + QtProperty[int] chainId: + read = getChainId + notify = chainIdChanged + + proc toStatusTransactionLinkPreview*(jsonObj: JsonNode): StatusTransactionLinkPreview = + var txType: int + var amount: string + var asset: string + var toAsset: string + var address: string + var chainId: int + + discard jsonObj.getProp("txType", txType) + discard jsonObj.getProp("amount", amount) + discard jsonObj.getProp("asset", asset) + discard jsonObj.getProp("toAsset", toAsset) + discard jsonObj.getProp("address", address) + discard jsonObj.getProp("chainId", chainId) + + result = newStatusTransactionLinkPreview(txType, amount, asset, toAsset, address, chainId) + + proc `$`*(self: StatusTransactionLinkPreview): string = + result = fmt"""StatusTransactionLinkPreview( + txType: {self.txType}, + amount: {self.amount}, + asset: {self.asset}, + toAsset: {self.toAsset}, + address: {self.address}, + chainId: {self.chainId} + )""" + + proc `%`*(self: StatusTransactionLinkPreview): JsonNode = + return %* { + "txType": self.txType, + "amount": self.amount, + "asset": self.asset, + "toAsset": self.toAsset, + "address": self.address, + "chainId": self.chainId + } + + proc empty*(self: StatusTransactionLinkPreview): bool = + return self.amount.len == 0 and self.asset.len == 0 and self.toAsset.len == 0 and self.address.len == 0 \ No newline at end of file diff --git a/src/app_service/service/shared_urls/dto/url_data.nim b/src/app_service/service/shared_urls/dto/url_data.nim index f59d2d28e50..1bc1e34183d 100644 --- a/src/app_service/service/shared_urls/dto/url_data.nim +++ b/src/app_service/service/shared_urls/dto/url_data.nim @@ -25,10 +25,19 @@ type ContactUrlDataDto* = object description*: string publicKey*: string +type TransactionURLDataDto* = object + txType*: int + asset*: string + amount*: string + address*: string + chainId*: int + toAsset*: string + type UrlDataDto* = object community*: CommunityUrlDataDto channel*: CommunityChannelUrlDataDto contact*: ContactUrlDataDto + transaction*: TransactionURLDataDto notASupportedStatusLink*: bool # If this is true, it was not a supported status link, so we should open it in a browser proc getShard*(jsonObj: JsonNode): Shard = @@ -69,8 +78,18 @@ proc toContactUrlDataDto*(jsonObj: JsonNode): ContactUrlDataDto = discard jsonObj.getProp("description", result.description) discard jsonObj.getProp("publicKey", result.publicKey) +proc toTransactionUrlDataDto*(jsonObj: JsonNode): TransactionURLDataDto = + result = TransactionURLDataDto() + discard jsonObj.getProp("txType", result.txType) + discard jsonObj.getProp("asset", result.asset) + discard jsonObj.getProp("amount", result.amount) + discard jsonObj.getProp("address", result.address) + discard jsonObj.getProp("chainId", result.chainId) + discard jsonObj.getProp("toAsset", result.toAsset) + proc toUrlDataDto*(jsonObj: JsonNode): UrlDataDto = result = UrlDataDto() + result.transaction.txType = -1 var communityObj: JsonNode if (jsonObj.getProp("community", communityObj)): @@ -84,6 +103,10 @@ proc toUrlDataDto*(jsonObj: JsonNode): UrlDataDto = if (jsonObj.getProp("contact", contactObj)): result.contact = contactObj.toContactUrlDataDto() + var txObj: JsonNode + if (jsonObj.getProp("tx", txObj)): + result.transaction = txObj.toTransactionUrlDataDto() + proc toJsonNode*(communityUrlDataDto: CommunityUrlDataDto): JsonNode = var jsonObj = newJObject() jsonObj["displayName"] = %* communityUrlDataDto.displayName @@ -113,3 +136,16 @@ proc `$`*(contactUrlDataDto: ContactUrlDataDto): string = jsonObj["description"] = %* contactUrlDataDto.description jsonObj["publicKey"] = %* contactUrlDataDto.publicKey return $jsonObj + +proc `%`*(transactionURLData: TransactionURLDataDto): JsonNode = + return %* [{ + "txType": transactionURLData.txType, + "asset": transactionURLData.asset, + "amount": transactionURLData.amount, + "address": transactionURLData.address, + "chainId": transactionURLData.chainId, + "toAsset": transactionURLData.toAsset, + }] + +proc `$`*(transactionURLData: TransactionURLDataDto): string = + return $(%transactionURLData) \ No newline at end of file diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index 416d5b862a7..fbd4ed3b6a7 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -19,6 +19,7 @@ import app_service/service/wallet_account/service as wallet_account_service import app_service/service/network/service as network_service import app_service/service/token/service as token_service import app_service/service/settings/service as settings_service +import app_service/service/shared_urls/dto/url_data as shared_urls_dto import ./dto as transaction_dto import ./dtoV2 import ./dto_conversion @@ -501,3 +502,14 @@ proc sendRouterTransactionsWithSignatures*(self: Service, uuid: string, signatur error "unexpected sending transactions response" return "unexpected sending transactions response" return "" + +proc shareTransactionURL*(self: Service, urlData: shared_urls_dto.TransactionURLDataDto): string = + try: + let response = transactions.shareTransactionURL(%urlData) + if response.error != nil: + error "Error sharing transaction url. Error: ", message = response.error + return "" + return response.result.getStr + except Exception as e: + error "Error sharing transaction url", message = e.msg + return "" \ No newline at end of file diff --git a/src/backend/transactions.nim b/src/backend/transactions.nim index a8f790f2b77..3edce781e9d 100644 --- a/src/backend/transactions.nim +++ b/src/backend/transactions.nim @@ -1,4 +1,5 @@ import Tables, json, stint, json_serialization, stew/shims/strformat, logging +import ../app_service/common/utils import ./core as core @@ -117,4 +118,7 @@ proc sendRouterTransactionsWithSignatures*(resultOut: var JsonNode, uuid: string return prepareResponse(resultOut, response) except Exception as e: warn e.msg - return e.msg \ No newline at end of file + return e.msg + +proc shareTransactionURL*(urlData: JsonNode): RpcResponse[JsonNode] = + return callPrivateRPC("shareTransactionURL".prefix, urlData) \ No newline at end of file diff --git a/ui/app/AppLayouts/Wallet/controls/ShareButton.qml b/ui/app/AppLayouts/Wallet/controls/ShareButton.qml new file mode 100644 index 00000000000..e59f4f90ff3 --- /dev/null +++ b/ui/app/AppLayouts/Wallet/controls/ShareButton.qml @@ -0,0 +1,40 @@ +import QtQuick 2.15 + +import StatusQ.Controls 0.1 + +StatusButton { + id: root + + text: qsTr("Share") + type: StatusBaseButton.Type.Normal + size: StatusBaseButton.Size.Tiny + + horizontalPadding: 8 + verticalPadding: 3 + implicitHeight: 22 + + radius: 20 + font.pixelSize: 12 + font.weight: Font.Normal + + Timer { + id: shareStateTimer + interval: 2000 + repeat: false + } + + states: State { + name: "success" + when: shareStateTimer.running + PropertyChanges { + target: shareButton + text: qsTr("Copied") + type: StatusBaseButton.Type.Success + icon.name: "tiny/checkmark" + } + } + + onClicked: { + shareStateTimer.restart() + } +} diff --git a/ui/app/AppLayouts/Wallet/controls/qmldir b/ui/app/AppLayouts/Wallet/controls/qmldir index ddae7e4e0ba..21816f17434 100644 --- a/ui/app/AppLayouts/Wallet/controls/qmldir +++ b/ui/app/AppLayouts/Wallet/controls/qmldir @@ -26,3 +26,4 @@ SwapProvidersTermsAndConditionsText 1.0 SwapProvidersTermsAndConditionsText.qml TokenSelector 1.0 TokenSelector.qml TokenSelectorButton 1.0 TokenSelectorButton.qml TokenSelectorCompactButton 1.0 TokenSelectorCompactButton.qml +ShareButton 1.0 ShareButton.qml diff --git a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml index 36df35f3219..4a1a1325973 100644 --- a/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml +++ b/ui/app/AppLayouts/Wallet/popups/swap/SwapModal.qml @@ -4,6 +4,7 @@ import QtQml.Models 2.15 import utils 1.0 +import StatusQ 0.1 import StatusQ.Controls 0.1 import StatusQ.Core 0.1 import StatusQ.Core.Backpressure 0.1 @@ -146,6 +147,19 @@ StatusDialog { Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter text: qsTr("Swap") } + ShareButton { + id: shareButton + + onClicked: { + const url = root.swapAdaptor.swapStore.getShareTransactionUrl(Constants.SendType.Swap, + root.swapInputParamsForm.fromTokensKey, + root.swapInputParamsForm.fromTokenAmount, + root.swapInputParamsForm.selectedAccountAddress, + root.swapInputParamsForm.selectedNetworkChainId, + root.swapInputParamsForm.toTokensKey) + ClipboardUtils.setText(url) + } + } StatusBaseText { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter text: qsTr("On:") diff --git a/ui/app/AppLayouts/Wallet/stores/SwapStore.qml b/ui/app/AppLayouts/Wallet/stores/SwapStore.qml index a1e3211c385..e02370833b4 100644 --- a/ui/app/AppLayouts/Wallet/stores/SwapStore.qml +++ b/ui/app/AppLayouts/Wallet/stores/SwapStore.qml @@ -52,4 +52,8 @@ QtObject { function getWei2Eth(wei, decimals) { return globalUtils.wei2Eth(wei, decimals) } + + function getShareTransactionUrl(txType, asset, amount, address, chainId, toAsset) { + return walletSectionSendInst.shareTransactionURL(txType, asset, amount, address, chainId, toAsset) + } } diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index a3912b3a4f1..99f4375c6ec 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -43,6 +43,7 @@ import AppLayouts.Chat.stores 1.0 as ChatStores import AppLayouts.Communities.stores 1.0 import AppLayouts.Wallet.stores 1.0 as WalletStores import AppLayouts.Wallet.popups 1.0 as WalletPopups +import AppLayouts.Wallet.popups.swap 1.0 as WalletSwapPopups import mainui.activitycenter.stores 1.0 import mainui.activitycenter.popups 1.0 @@ -381,11 +382,51 @@ Item { "" ) } + + function onShowTransactionModal(txType, asset, amount, address, chainId, toAsset) { + console.log("=========== onShowTransactionModal txType:", txType, "asset:", asset, "amount:", amount, "address:", address, "chainId:", chainId, "toAsset:", toAsset) + + if (txType === Constants.SendType.Swap) { + d.swapFormData.fromTokensKey = asset + d.swapFormData.toTokenKey = toAsset + d.swapFormData.fromTokenAmount = amount + d.swapFormData.selectedAccountAddress = address + d.swapFormData.selectedNetworkChainId = chainId + Global.openSwapModalRequested(d.swapFormData) + return + } + + sendModal.preSelectedSendType = txType + sendModal.preDefinedAmountToSend = amount + sendModal.preSelectedHoldingID = asset + switch(txType) { + case Constants.SendType.ERC721Transfer: + sendModal.preSelectedHoldingType = Constants.TokenType.ERC721 + break + case Constants.SendType.ERC1155Transfer: + sendModal.preSelectedHoldingType = Constants.TokenType.ERC1155 + break + case Constants.SendType.ENSRegister: + case Constants.SendType.ENSSetPubKey: + case Constants.SendType.ENSRelease: + case Constants.SendType.Transfer: + case Constants.SendType.StickersBuy: + sendModal.preSelectedHoldingType = Constants.TokenType.ERC20 + break + default: + break + } + sendModal.open(address) + } } QtObject { id: d + property WalletSwapPopups.SwapInputParamsForm swapFormData: WalletSwapPopups.SwapInputParamsForm { + selectedAccountAddress: WalletStores.RootStore.selectedAddress + } + property var activityCenterPopupObj: null function openActivityCenterPopup() { diff --git a/ui/imports/shared/controls/chat/LinkPreviewCard.qml b/ui/imports/shared/controls/chat/LinkPreviewCard.qml index 840baf772ef..b58c7f1fcdc 100644 --- a/ui/imports/shared/controls/chat/LinkPreviewCard.qml +++ b/ui/imports/shared/controls/chat/LinkPreviewCard.qml @@ -17,6 +17,7 @@ CalloutCard { readonly property LinkData linkData: LinkData { } readonly property UserData userData: UserData { } + readonly property TransactionData transactionData: TransactionData { } readonly property CommunityData communityData: CommunityData { } readonly property ChannelData channelData: ChannelData { } @@ -284,7 +285,57 @@ CalloutCard { PropertyChanges { target: footerLoader; active: false; visible: !root.userData.bio; Layout.fillHeight: true } PropertyChanges { target: title; text: root.userData.name } PropertyChanges { target: description; text: root.userData.bio; Layout.minimumHeight: 32; visible: true } + }, + State { + name: "transaction" + when: root.type === Constants.LinkPreviewType.StatusTransaction + PropertyChanges { target: root; implicitHeight: 187 } + PropertyChanges { target: bannerImageLoader; visible: false } + PropertyChanges { target: footerLoader; active: false; visible: false } + PropertyChanges { + target: title; + text: { + switch(root.transactionData.txType) { + case Constants.SendType.Bridge: + return qsTr("Bridge transaction") + case Constants.SendType.Swap: + return qsTr("Swap transaction") + default: + return qsTr("Send transaction") + } + } + } + PropertyChanges { + target: description; + text: { + let description = "" + switch(root.transactionData.txType) { + case Constants.SendType.Bridge: + description += qsTr("Bridge") + " " + break + case Constants.SendType.Swap: + description += qsTr("Swap") + " " + break + default: + description += qsTr("Send") + " " + break + } + if (root.transactionData.amount) { + description += root.transactionData.amount + " " + } + if (root.transactionData.asset) { + description += root.transactionData.asset + " " + } + if (root.transactionData.address) { + description += qsTr("to %1").arg(root.transactionData.address) + " " + } + if (root.transactionData.chainId) { + description += qsTr("on %1").arg(root.transactionData.chainId) // TODO use network name + } + } + } } + ] QtObject { diff --git a/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml b/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml index cdb0b054642..962101e9d82 100644 --- a/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml +++ b/ui/imports/shared/controls/chat/LinkPreviewMiniCard.qml @@ -25,6 +25,7 @@ CalloutCard { readonly property UserData userData: UserData { } readonly property CommunityData communityData: CommunityData { } readonly property ChannelData channelData: ChannelData { } + readonly property TransactionData transactionData: TransactionData { } required property int previewState required property int type @@ -147,7 +148,28 @@ CalloutCard { asset.charactersLen: 2 asset.color: Theme.palette.miscColor9 } + }, + State { + name: "loadedTransaction" + when: root.previewState === LinkPreviewMiniCard.State.Loaded && root.type === Constants.LinkPreviewType.StatusTransaction + extend: "loaded" + PropertyChanges { target: titleText; text: qsTr("Transaction") } + PropertyChanges { + target: subtitleText; visible: true; + text: { + switch(root.transactionData.txType) { + case Constants.SendType.Bridge: + return qsTr("Bridge") + case Constants.SendType.Swap: + return qsTr("Swap") + default: + return qsTr("Send") + } + } + } + PropertyChanges { target: favIcon; visible: false } } + ] contentItem: Item { diff --git a/ui/imports/shared/controls/chat/private/TransactionData.qml b/ui/imports/shared/controls/chat/private/TransactionData.qml new file mode 100644 index 00000000000..8ac0e88428a --- /dev/null +++ b/ui/imports/shared/controls/chat/private/TransactionData.qml @@ -0,0 +1,10 @@ +import QtQuick 2.15 + +QtObject { + property int txType + property string asset + property string toAsset + property string amount + property string address + property int chainId +} diff --git a/ui/imports/shared/controls/delegates/LinkPreviewCardDelegate.qml b/ui/imports/shared/controls/delegates/LinkPreviewCardDelegate.qml index 07f578fc242..3b04e6db365 100644 --- a/ui/imports/shared/controls/delegates/LinkPreviewCardDelegate.qml +++ b/ui/imports/shared/controls/delegates/LinkPreviewCardDelegate.qml @@ -48,6 +48,7 @@ LinkPreviewCard { required property var statusCommunityChannelCommunityPreview required property var statusCommunityChannelCommunityPreviewIcon required property var statusCommunityChannelCommunityPreviewBanner + required property var statusTransactionPreview //View properties type: root.previewType @@ -91,4 +92,12 @@ LinkPreviewCard { color: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.color : "" } } + transactionData { + txType: statusTransactionPreview ? statusTransactionPreview.txType : "" + asset: statusTransactionPreview ? statusTransactionPreview.asset : "" + toAsset: statusTransactionPreview ? statusTransactionPreview.toAsset : "" + amount: statusTransactionPreview ? statusTransactionPreview.amount : "" + chainId: statusTransactionPreview ? statusTransactionPreview.chainId : "" + address: statusTransactionPreview ? statusTransactionPreview.address : "" + } } diff --git a/ui/imports/shared/controls/delegates/LinkPreviewMiniCardDelegate.qml b/ui/imports/shared/controls/delegates/LinkPreviewMiniCardDelegate.qml index 4520508736a..1d4d13bf92b 100644 --- a/ui/imports/shared/controls/delegates/LinkPreviewMiniCardDelegate.qml +++ b/ui/imports/shared/controls/delegates/LinkPreviewMiniCardDelegate.qml @@ -42,6 +42,7 @@ LinkPreviewMiniCard { required property var statusCommunityChannelCommunityPreview required property var statusCommunityChannelCommunityPreviewIcon required property var statusCommunityChannelCommunityPreviewBanner + required property var statusTransactionPreview previewState: !root.unfurled ? LinkPreviewMiniCard.State.Loading : root.unfurled && !root.empty ? LinkPreviewMiniCard.State.Loaded : LinkPreviewMiniCard.State.LoadingFailed type: root.previewType @@ -83,4 +84,12 @@ LinkPreviewMiniCard { color: statusCommunityChannelCommunityPreview ? statusCommunityChannelCommunityPreview.color : "" } } + transactionData { + txType: statusTransactionPreview ? statusTransactionPreview.txType : "" + asset: statusTransactionPreview ? statusTransactionPreview.asset : "" + toAsset: statusTransactionPreview ? statusTransactionPreview.toAsset : "" + amount: statusTransactionPreview ? statusTransactionPreview.amount : "" + chainId: statusTransactionPreview ? statusTransactionPreview.chainId : "" + address: statusTransactionPreview ? statusTransactionPreview.address : "" + } } diff --git a/ui/imports/shared/popups/send/SendModal.qml b/ui/imports/shared/popups/send/SendModal.qml index d3433bff5de..1acbec75930 100644 --- a/ui/imports/shared/popups/send/SendModal.qml +++ b/ui/imports/shared/popups/send/SendModal.qml @@ -486,6 +486,19 @@ StatusDialog { amountToSend.forceActiveFocus() } } + + ShareButton { + id: shareButton + + onClicked: { + let asset = "" + if (!!d.selectedHolding) { + asset = d.isCollectiblesTransfer ? d.selectedHolding.symbol : d.selectedHolding.tokensKey + } + const url = popup.store.getShareTransactionUrl(store.sendType, asset, amountToSend.asNumber, popup.store.selectedSenderAccountAddress, 0) + ClipboardUtils.setText(url) + } + } } RowLayout { visible: d.isSelectedHoldingValidAsset && !d.isCollectiblesTransfer diff --git a/ui/imports/shared/stores/send/TransactionStore.qml b/ui/imports/shared/stores/send/TransactionStore.qml index 04a6fa3646d..7f6e11fee70 100644 --- a/ui/imports/shared/stores/send/TransactionStore.qml +++ b/ui/imports/shared/stores/send/TransactionStore.qml @@ -165,6 +165,10 @@ QtObject { } } + function getShareTransactionUrl(txType, asset, amount, address, chainId) { + return walletSectionSendInst.shareTransactionURL(txType, asset, amount, address, chainId, "") + } + function getShortChainIds(chainShortNames) { return walletSectionSendInst.getShortChainIds(chainShortNames) } diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index ce106b0c643..888a3f6defe 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -1410,7 +1410,8 @@ QtObject { Standard = 1, StatusContact = 2, StatusCommunity = 3, - StatusCommunityChannel = 4 + StatusCommunityChannel = 4, + StatusTransaction = 5 } enum StandardLinkPreviewType {