diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index e0e538a316..2c53e7c740 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -1,6 +1,9 @@ import NimQml, os, strformat +import ../../app_service/common/utils + import ../../app_service/service/os_notification/service as os_notification_service +import ../../app_service/service/eth/service as eth_service import ../../app_service/service/keychain/service as keychain_service import ../../app_service/service/accounts/service as accounts_service import ../../app_service/service/contacts/service as contacts_service @@ -20,8 +23,10 @@ import ../../app_service/service/provider/service as provider_service import ../../app_service/service/ens/service as ens_service import ../../app_service/service/profile/service as profile_service import ../../app_service/service/settings/service as settings_service +import ../../app_service/service/stickers/service as stickers_service import ../../app_service/service/about/service as about_service import ../../app_service/service/node_configuration/service as node_configuration_service +import ../../app_service/service/network/service as network_service import ../modules/startup/module as startup_module import ../modules/main/module as main_module @@ -82,6 +87,7 @@ type # Services osNotificationService: os_notification_service.Service keychainService: keychain_service.Service + ethService: eth_service.Service accountsService: accounts_service.Service contactsService: contacts_service.Service chatService: chat_service.Service @@ -97,7 +103,9 @@ type providerService: provider_service.Service profileService: profile_service.Service settingsService: settings_service.Service + stickersService: stickers_service.Service aboutService: about_service.Service + networkService: network_service.Service languageService: language_service.Service mnemonicService: mnemonic_service.Service privacyService: privacy_service.Service @@ -169,7 +177,9 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.settingsService) result.osNotificationService = os_notification_service.newService(statusFoundation.status.events) result.keychainService = keychain_service.newService(statusFoundation.status.events) + result.ethService = eth_service.newService() result.accountsService = accounts_service.newService(statusFoundation.fleetConfiguration) + result.networkService = network_service.newService() result.contactsService = contacts_service.newService(statusFoundation.status.events, statusFoundation.threadpool) result.chatService = chat_service.newService(result.contactsService) result.communityService = community_service.newService(result.chatService) @@ -183,6 +193,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.walletAccountService) result.bookmarkService = bookmark_service.newService() result.profileService = profile_service.newService() + result.stickersService = stickers_service.newService(statusFoundation.status.events, statusFoundation.threadpool, result.ethService, result.settingsService, result.walletAccountService, result.transactionService, result.networkService, result.chatService) result.aboutService = about_service.newService() result.dappPermissionsService = dapp_permissions_service.newService() result.languageService = language_service.newService() @@ -220,6 +231,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.mnemonicService, result.privacyService, result.providerService, + result.stickersService ) ################################################# @@ -242,6 +254,7 @@ proc delete*(self: AppController) = self.bookmarkService.delete self.startupModule.delete self.mainModule.delete + self.ethService.delete ################################################# # At the end of refactoring this will be moved to appropriate place or removed: @@ -267,6 +280,7 @@ proc delete*(self: AppController) = self.collectibleService.delete self.walletAccountService.delete self.aboutService.delete + self.networkService.delete self.dappPermissionsService.delete self.providerService.delete self.ensService.delete @@ -305,6 +319,7 @@ proc start*(self: AppController) = self.keycard.init() ################################################# + self.ethService.init() self.accountsService.init() self.startupModule.load() @@ -323,6 +338,8 @@ proc load(self: AppController) = self.walletAccountService.init() self.transactionService.init() self.languageService.init() + self.stickersService.init() + self.networkService.init() let pubKey = self.settingsService.getPublicKey() singletonInstance.localAccountSensitiveSettings.setFileName(pubKey) @@ -333,7 +350,12 @@ proc load(self: AppController) = self.buildAndRegisterUserProfile() # load main module - self.mainModule.load(self.statusFoundation.status.events, self.chatService, self.communityService, self.messageService) + self.mainModule.load( + self.statusFoundation.status.events, + self.chatService, + self.communityService, + self.messageService + ) proc userLoggedIn*(self: AppController) = ################################################# @@ -366,7 +388,7 @@ proc buildAndRegisterUserProfile(self: AppController) = let meAsContact = self.contactsService.getContactById(pubKey) var ensName: string if(meAsContact.ensVerified): - ensName = prettyEnsName(meAsContact.name) + ensName = utils.prettyEnsName(meAsContact.name) singletonInstance.userProfile.setFixedData(loggedInAccount.name, loggedInAccount.keyUid, loggedInAccount.identicon, pubKey) diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index 9def7ce17a..a66ae977f8 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -2,7 +2,6 @@ import NimQml, Tables, chronicles import io_interface import ../io_interface as delegate_interface import view, controller, item, sub_item, model, sub_model -import ../../../global/global_singleton import chat_content/module as chat_content_module @@ -26,10 +25,16 @@ type chatContentModule: OrderedTable[string, chat_content_module.AccessInterface] moduleLoaded: bool -proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitter, sectionId: string, isCommunity: bool, - chatService: chat_service.Service, communityService: community_service.Service, - messageService: message_service.Service): - Module = + +proc newModule*( + delegate: delegate_interface.AccessInterface, + events: EventEmitter, + sectionId: string, + isCommunity: bool, + chatService: chat_service.Service, + communityService: community_service.Service, + messageService: message_service.Service + ): Module = result = Module() result.delegate = delegate result.view = view.newView(result) diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index b05eb7ff5c..df08c3451c 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -9,6 +9,7 @@ import wallet_section/module as wallet_section_module import browser_section/module as browser_section_module import profile_section/module as profile_section_module import app_search/module as app_search_module +import stickers/module as stickers_module import ../../../app_service/service/keychain/service as keychain_service import ../../../app_service/service/chat/service as chat_service @@ -29,6 +30,7 @@ import ../../../app_service/service/about/service as about_service import ../../../app_service/service/language/service as language_service import ../../../app_service/service/mnemonic/service as mnemonic_service import ../../../app_service/service/privacy/service as privacy_service +import ../../../app_service/service/stickers/service as stickers_service import eventemitter @@ -45,6 +47,7 @@ type walletSectionModule: wallet_section_module.AccessInterface browserSectionModule: browser_section_module.AccessInterface profileSectionModule: profile_section_module.AccessInterface + stickersModule: stickers_module.AccessInterface appSearchModule: app_search_module.AccessInterface moduleLoaded: bool @@ -69,7 +72,8 @@ proc newModule*[T]( languageService: language_service.ServiceInterface, mnemonicService: mnemonic_service.ServiceInterface, privacyService: privacy_service.ServiceInterface, - providerService: provider_service.ServiceInterface + providerService: provider_service.ServiceInterface, + stickersService: stickers_service.Service ): Module[T] = result = Module[T]() result.delegate = delegate @@ -89,12 +93,14 @@ proc newModule*[T]( dappPermissionsService, providerService) result.profileSectionModule = profile_section_module.newModule(result, events, accountsService, settingsService, profileService, contactsService, aboutService, languageService, mnemonicService, privacyService) + result.stickersModule = stickers_module.newModule(result, events, stickersService) result.appSearchModule = app_search_module.newModule(result, events, contactsService, chatService, communityService, messageService) method delete*[T](self: Module[T]) = self.chatSectionModule.delete self.profileSectionModule.delete + self.stickersModule.delete for cModule in self.communitySectionsModule.values: cModule.delete self.communitySectionsModule.clear @@ -105,17 +111,30 @@ method delete*[T](self: Module[T]) = self.viewVariant.delete self.controller.delete -method load*[T](self: Module[T], events: EventEmitter, chatService: chat_service.Service, - communityService: community_service.Service, messageService: message_service.Service) = +method load*[T]( + self: Module[T], + events: EventEmitter, + chatService: chat_service.Service, + communityService: community_service.Service, + messageService: message_service.Service + ) = singletonInstance.engine.setRootContextProperty("mainModule", self.viewVariant) self.controller.init() self.view.load() # Create community modules here, since we don't know earlier how many communities we have. let communities = self.controller.getCommunities() + for c in communities: - self.communitySectionsModule[c.id] = chat_section_module.newModule(self, events, c.id, true, chatService, - communityService, messageService) + self.communitySectionsModule[c.id] = chat_section_module.newModule( + self, + events, + c.id, + true, + chatService, + communityService, + messageService + ) var activeSection: Item var activeSectionId = singletonInstance.localAccountSensitiveSettings.getActiveSection() @@ -199,6 +218,7 @@ method load*[T](self: Module[T], events: EventEmitter, chatService: chat_service # self.timelineSectionModule.load() # self.nodeManagementSectionModule.load() self.profileSectionModule.load() + self.stickersModule.load() self.appSearchModule.load() # Set active section on app start @@ -224,6 +244,9 @@ proc checkIfModuleDidLoad [T](self: Module[T]) = if(not self.profileSectionModule.isLoaded()): return + if(not self.stickersModule.isLoaded()): + return + self.moduleLoaded = true self.delegate.mainDidLoad() diff --git a/src/app/modules/main/private_interfaces/module_access_interface.nim b/src/app/modules/main/private_interfaces/module_access_interface.nim index 7dee8d2569..12458a7ec5 100644 --- a/src/app/modules/main/private_interfaces/module_access_interface.nim +++ b/src/app/modules/main/private_interfaces/module_access_interface.nim @@ -7,8 +7,13 @@ import eventemitter method delete*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") -method load*(self: AccessInterface, events: EventEmitter, chatService: chat_service.Service, communityService: community_service.Service, - messageService: message_service.Service) +method load*( + self: AccessInterface, + events: EventEmitter, + chatService: chat_service.Service, + communityService: community_service.Service, + messageService: message_service.Service + ) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/stickers/controller.nim b/src/app/modules/main/stickers/controller.nim new file mode 100644 index 0000000000..5562878241 --- /dev/null +++ b/src/app/modules/main/stickers/controller.nim @@ -0,0 +1,90 @@ +import Tables +import eventemitter +import ./controller_interface +import ./io_interface +import ../../../../app_service/service/stickers/service as stickers_service + +export controller_interface + +type + Controller*[T: controller_interface.DelegateInterface] = ref object of controller_interface.AccessInterface + delegate: io_interface.AccessInterface + events: EventEmitter + stickerService: stickers_service.Service + +# Forward declaration +method obtainAvailableStickerPacks*[T](self: Controller[T]) +method getInstalledStickerPacks*[T](self: Controller[T]): Table[int, StickerPackDto] + +proc newController*[T]( + delegate: io_interface.AccessInterface, + events: EventEmitter, + stickerService: stickers_service.Service + ): Controller[T] = + result = Controller[T]() + result.delegate = delegate + result.events = events + result.stickerService = stickerService + +method delete*[T](self: Controller[T]) = + discard + +method init*[T](self: Controller[T]) = + let recentStickers = self.stickerService.getRecentStickers() + for sticker in recentStickers: + self.delegate.addRecentStickerToList(sticker) + + self.events.on("network:disconnected") do(e: Args): + self.delegate.clearStickerPacks() + let installedStickerPacks = self.getInstalledStickerPacks() + self.delegate.populateInstalledStickerPacks(installedStickerPacks) + + self.events.on("network:connected") do(e: Args): + self.delegate.clearStickerPacks() + self.obtainAvailableStickerPacks() + + self.events.on(SIGNAL_STICKER_PACK_LOADED) do(e: Args): + let args = StickerPackLoadedArgs(e) + self.delegate.addStickerPackToList( + args.stickerPack, + args.isInstalled, + args.isBought, + args.isPending + ) + + self.events.on(SIGNAL_ALL_STICKER_PACKS_LOADED) do(e: Args): + self.delegate.allPacksLoaded() + + self.events.on(SIGNAL_STICKER_GAS_ESTIMATED) do(e: Args): + let args = StickerGasEstimatedArgs(e) + self.delegate.gasEstimateReturned(args.estimate, args.uuid) + +method buy*[T](self: Controller[T], packId: int, address: string, price: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string): tuple[response: string, success: bool] = + self.stickerService.buy(packId, address, price, gas, gasPrice, maxPriorityFeePerGas, maxFeePerGas, password) + +method estimate*[T](self: Controller[T], packId: int, address: string, price: string, uuid: string) = + self.stickerService.estimate(packId, address, price, uuid) + +method getInstalledStickerPacks*[T](self: Controller[T]): Table[int, StickerPackDto] = + self.stickerService.getInstalledStickerPacks() + +method getPurchasedStickerPacks*[T](self: Controller[T], address: string): seq[int] = + self.stickerService.getPurchasedStickerPacks(address) + +method obtainAvailableStickerPacks*[T](self: Controller[T]) = + self.stickerService.obtainAvailableStickerPacks() + +method getNumInstalledStickerPacks*[T](self: Controller[T]): int = + self.stickerService.getNumInstalledStickerPacks() + +method installStickerPack*[T](self: Controller[T], packId: int) = + self.stickerService.installStickerPack(packId) + +method uninstallStickerPack*[T](self: Controller[T], packId: int) = + self.stickerService.uninstallStickerPack(packId) + +method removeRecentStickers*[T](self: Controller[T], packId: int) = + self.stickerService.removeRecentStickers(packId) + +method sendSticker*[T](self: Controller[T], channelId: string, replyTo: string, sticker: StickerDto) = + self.stickerService.sendSticker(channelId, replyTo, sticker) diff --git a/src/app/modules/main/stickers/controller_interface.nim b/src/app/modules/main/stickers/controller_interface.nim new file mode 100644 index 0000000000..f5614367db --- /dev/null +++ b/src/app/modules/main/stickers/controller_interface.nim @@ -0,0 +1,47 @@ +import Tables +import ../../../../app_service/service/stickers/service as stickers_service + +type + AccessInterface* {.pure inheritable.} = ref object of RootObj + ## Abstract class for any input/interaction with this module. + +method delete*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method init*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method buy*(self: AccessInterface, packId: int, address: string, price: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string): tuple[response: string, success: bool] {.base.} = + raise newException(ValueError, "No implementation available") + +method getInstalledStickerPacks*(self: AccessInterface): Table[int, StickerPackDto] {.base.} = + raise newException(ValueError, "No implementation available") + +method getPurchasedStickerPacks*(self: AccessInterface, address: string): seq[int] {.base.} = + raise newException(ValueError, "No implementation available") + +method obtainAvailableStickerPacks*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method getNumInstalledStickerPacks*(self: AccessInterface): int {.base.} = + raise newException(ValueError, "No implementation available") + +method estimate*(self: AccessInterface, packId: int, address: string, price: string, uuid: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method installStickerPack*(self: AccessInterface, packId: int) {.base.} = + raise newException(ValueError, "No implementation available") + +method uninstallStickerPack*(self: AccessInterface, packId: int) {.base.} = + raise newException(ValueError, "No implementation available") + +method removeRecentStickers*(self: AccessInterface, packId: int) {.base.} = + raise newException(ValueError, "No implementation available") + +method sendSticker*(self: AccessInterface, channelId: string, replyTo: string, sticker: StickerDto) {.base.} = + raise newException(ValueError, "No implementation available") + +type + ## Abstract class (concept) which must be implemented by object/s used in this + ## module. + DelegateInterface* = concept c diff --git a/src/app/modules/main/stickers/io_interface.nim b/src/app/modules/main/stickers/io_interface.nim new file mode 100644 index 0000000000..2d2f93f51e --- /dev/null +++ b/src/app/modules/main/stickers/io_interface.nim @@ -0,0 +1,69 @@ +import Tables +import ../../../../app_service/service/wallet_account/service as wallet_account_service +import ../../../../app_service/service/stickers/service as stickers_service + +type + AccessInterface* {.pure inheritable.} = ref object of RootObj + ## Abstract class for any input/interaction with this module. + +method delete*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method load*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method isLoaded*(self: AccessInterface): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method buy*(self: AccessInterface, packId: int, address: string, price: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string): tuple[response: string, success: bool] {.base.} = + raise newException(ValueError, "No implementation available") + +method getInstalledStickerPacks*(self: AccessInterface): Table[int, StickerPackDto] {.base.} = + raise newException(ValueError, "No implementation available") + +method getPurchasedStickerPacks*(self: AccessInterface, address: string): seq[int] {.base.} = + raise newException(ValueError, "No implementation available") + +method obtainAvailableStickerPacks*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method addRecentStickerToList*(self: AccessInterface, sticker: StickerDto) {.base.} = + raise newException(ValueError, "No implementation available") + +method clearStickerPacks*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method getNumInstalledStickerPacks*(self: AccessInterface): int {.base.} = + raise newException(ValueError, "No implementation available") + +method allPacksLoaded*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method estimate*(self: AccessInterface, packId: int, address: string, price: string, uuid: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method installStickerPack*(self: AccessInterface, packId: int) {.base.} = + raise newException(ValueError, "No implementation available") + +method uninstallStickerPack*(self: AccessInterface, packId: int) {.base.} = + raise newException(ValueError, "No implementation available") + +method removeRecentStickers*(self: AccessInterface, packId: int) {.base.} = + raise newException(ValueError, "No implementation available") + +method sendSticker*(self: AccessInterface, channelId: string, replyTo: string, sticker: StickerDto) {.base.} = + raise newException(ValueError, "No implementation available") + +method populateInstalledStickerPacks*(self: AccessInterface, stickers: Table[int, StickerPackDto]) {.base.} = + raise newException(ValueError, "No implementation available") + +method gasEstimateReturned*(self: AccessInterface, estimate: int, uuid: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method addStickerPackToList*(self: AccessInterface, stickerPack: StickerPackDto, isInstalled: bool, isBought: bool, isPending: bool) {.base.} = + raise newException(ValueError, "No implementation available") + +type + ## Abstract class (concept) which must be implemented by object/s used in this + ## module. + DelegateInterface* = concept c diff --git a/src/app/modules/main/stickers/models/sticker_list.nim b/src/app/modules/main/stickers/models/sticker_list.nim new file mode 100644 index 0000000000..3c50e394cc --- /dev/null +++ b/src/app/modules/main/stickers/models/sticker_list.nim @@ -0,0 +1,60 @@ +import NimQml, Tables, sequtils +import status/chat/stickers +import status/types/[sticker] +import ../../../../../app_service/service/stickers/dto/stickers as stickers_dto + +type + StickerRoles {.pure.} = enum + Url = UserRole + 1 + Hash = UserRole + 2 + PackId = UserRole + 3 + +QtObject: + type + StickerList* = ref object of QAbstractListModel + stickers*: seq[StickerDto] + + proc setup(self: StickerList) = self.QAbstractListModel.setup + + proc delete(self: StickerList) = self.QAbstractListModel.delete + + proc newStickerList*(stickers: seq[StickerDto] = @[]): StickerList = + new(result, delete) + result.stickers = stickers + result.setup() + + method rowCount(self: StickerList, index: QModelIndex = nil): int = self.stickers.len + + method data(self: StickerList, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.stickers.len: + return + + let sticker = self.stickers[index.row] + let stickerRole = role.StickerRoles + case stickerRole: + of StickerRoles.Url: result = newQVariant(decodeContentHash(sticker.hash)) + of StickerRoles.Hash: result = newQVariant(sticker.hash) + of StickerRoles.PackId: result = newQVariant(sticker.packId) + + method roleNames(self: StickerList): Table[int, string] = + { + StickerRoles.Url.int:"url", + StickerRoles.Hash.int:"hash", + StickerRoles.PackId.int:"packId" + }.toTable + + proc addStickerToList*(self: StickerList, sticker: StickerDto) = + if(self.stickers.any(proc(existingSticker: StickerDto): bool = return existingSticker.hash == sticker.hash)): + return + self.beginInsertRows(newQModelIndex(), 0, 0) + self.stickers.insert(sticker, 0) + self.endInsertRows() + + proc removeStickersFromList*(self: StickerList, packId: int) = + if not self.stickers.anyIt(it.packId == packId): + return + self.beginRemoveRows(newQModelIndex(), 0, 0) + self.stickers.keepItIf(it.packId != packId) + self.endRemoveRows() diff --git a/src/app/modules/main/stickers/models/sticker_pack_list.nim b/src/app/modules/main/stickers/models/sticker_pack_list.nim new file mode 100644 index 0000000000..d864681612 --- /dev/null +++ b/src/app/modules/main/stickers/models/sticker_pack_list.nim @@ -0,0 +1,141 @@ +import NimQml, Tables, sequtils, sugar +import ./sticker_list +import status/utils as status_utils +import status/types/[sticker] +import ../../../../../app_service/service/stickers/dto/stickers as stickers_dto + +type + StickerPackRoles {.pure.} = enum + Author = UserRole + 1, + Id = UserRole + 2 + Name = UserRole + 3 + Price = UserRole + 4 + Preview = UserRole + 5 + Stickers = UserRole + 6 + Thumbnail = UserRole + 7 + Installed = UserRole + 8 + Bought = UserRole + 9 + Pending = UserRole + 10 + +type + StickerPackView* = tuple[pack: StickerPackDto, stickers: StickerList, installed, bought, pending: bool] + +QtObject: + type + StickerPackList* = ref object of QAbstractListModel + packs*: seq[StickerPackView] + packIdToRetrieve*: int + + proc setup(self: StickerPackList) = self.QAbstractListModel.setup + + proc delete(self: StickerPackList) = self.QAbstractListModel.delete + + proc clear*(self: StickerPackList) = + self.beginResetModel() + self.packs = @[] + self.endResetModel() + + proc newStickerPackList*(): StickerPackList = + new(result, delete) + result.packs = @[] + result.setup() + + method rowCount(self: StickerPackList, index: QModelIndex = nil): int = self.packs.len + + method data(self: StickerPackList, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.packs.len: + return + + let packInfo = self.packs[index.row] + let stickerPack = packInfo.pack + let stickerPackRole = role.StickerPackRoles + case stickerPackRole: + of StickerPackRoles.Author: result = newQVariant(stickerPack.author) + of StickerPackRoles.Id: result = newQVariant(stickerPack.id) + of StickerPackRoles.Name: result = newQVariant(stickerPack.name) + of StickerPackRoles.Price: result = newQVariant(stickerPack.price.wei2Eth) + of StickerPackRoles.Preview: result = newQVariant(status_utils.decodeContentHash(stickerPack.preview)) + of StickerPackRoles.Stickers: result = newQVariant(packInfo.stickers) + of StickerPackRoles.Thumbnail: result = newQVariant(status_utils.decodeContentHash(stickerPack.thumbnail)) + of StickerPackRoles.Installed: result = newQVariant(packInfo.installed) + of StickerPackRoles.Bought: result = newQVariant(packInfo.bought) + of StickerPackRoles.Pending: result = newQVariant(packInfo.pending) + + method roleNames(self: StickerPackList): Table[int, string] = + { + StickerPackRoles.Author.int:"author", + StickerPackRoles.Id.int:"packId", + StickerPackRoles.Name.int: "name", + StickerPackRoles.Price.int: "price", + StickerPackRoles.Preview.int: "preview", + StickerPackRoles.Stickers.int: "stickers", + StickerPackRoles.Thumbnail.int: "thumbnail", + StickerPackRoles.Installed.int: "installed", + StickerPackRoles.Bought.int: "bought", + StickerPackRoles.Pending.int: "pending" + }.toTable + + proc findIndexById*(self: StickerPackList, packId: int, mustBeInstalled: bool = false): int {.slot.} = + result = -1 + var idx = -1 + for item in self.packs: + inc idx + let installed = if mustBeInstalled: item.installed else: true + if(item.pack.id == packId and installed): + result = idx + break + + proc hasKey*(self: StickerPackList, packId: int): bool = + result = self.packs.anyIt(it.pack.id == packId) + + proc `[]`*(self: StickerPackList, packId: int): StickerPackDto = + if not self.hasKey(packId): + raise newException(ValueError, "Sticker pack list does not have a pack with id " & $packId) + result = find(self.packs, (view: StickerPackView) => view.pack.id == packId).pack + + proc addStickerPackToList*(self: StickerPackList, pack: StickerPackDto, stickers: StickerList, installed, bought, pending: bool) = + self.beginInsertRows(newQModelIndex(), 0, 0) + self.packs.insert((pack: pack, stickers: stickers, installed: installed, bought: bought, pending: pending), 0) + self.endInsertRows() + + proc removeStickerPackFromList*(self: StickerPackList, packId: int) = + let idx = self.findIndexById(packId) + self.beginRemoveRows(newQModelIndex(), idx, idx) + self.packs.keepItIf(it.pack.id != packId) + self.endRemoveRows() + + proc updateStickerPackInList*(self: StickerPackList, packId: int, installed: bool, pending: bool) = + if not self.hasKey(packId): + return + + let topLeft = self.createIndex(0, 0, nil) + let bottomRight = self.createIndex(self.packs.len, 0, nil) + self.packs.apply(proc(it: var StickerPackView) = + if it.pack.id == packId: + it.installed = installed + it.pending = pending) + + self.dataChanged(topLeft, bottomRight, @[StickerPackRoles.Installed.int, StickerPackRoles.Pending.int]) + + proc getStickers*(self: StickerPackList): QVariant {.slot.} = + let packInfo = self.packs[self.packIdToRetrieve] + result = newQVariant(packInfo.stickers) + + proc rowData*(self: StickerPackList, row: int, data: string): string {.slot.} = + if row < 0 or (row > self.packs.len - 1): + return + self.packIdToRetrieve = row + let packInfo = self.packs[row] + let stickerPack = packInfo.pack + case data: + of "author": result = stickerPack.author + of "name": result = stickerPack.name + of "price": result = $stickerPack.price.wei2Eth + of "preview": result = status_utils.decodeContentHash(stickerPack.preview) + of "thumbnail": result = status_utils.decodeContentHash(stickerPack.thumbnail) + of "installed": result = $packInfo.installed + of "bought": result = $packInfo.bought + of "pending": result = $packInfo.pending + else: result = "" diff --git a/src/app/modules/main/stickers/module.nim b/src/app/modules/main/stickers/module.nim new file mode 100644 index 0000000000..ccda208180 --- /dev/null +++ b/src/app/modules/main/stickers/module.nim @@ -0,0 +1,88 @@ +import NimQml, Tables + +import eventemitter +import ./io_interface, ./view, ./controller +import ../../../global/global_singleton +import ../../../../app_service/service/stickers/service as stickers_service + +export io_interface + +type + Module* [T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface + delegate: T + controller: controller.AccessInterface + view: View + viewVariant: QVariant + moduleLoaded: bool + +proc newModule*[T]( + delegate: T, + events: EventEmitter, + stickersService: stickers_service.Service + ): Module[T] = + result = Module[T]() + result.delegate = delegate + result.view = newView(result) + result.viewVariant = newQVariant(result.view) + result.controller = controller.newController[Module[T]](result, events, stickersService) + result.moduleLoaded = false + + singletonInstance.engine.setRootContextProperty("stickersModule", result.viewVariant) + +method delete*[T](self: Module[T]) = + self.view.delete + +method load*[T](self: Module[T]) = + self.controller.init() + self.moduleLoaded = true + +method isLoaded*[T](self: Module[T]): bool = + return self.moduleLoaded + +method buy*[T](self: Module[T], packId: int, address: string, price: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string): tuple[response: string, success: bool] = + return self.controller.buy(packId, address, price, gas, gasPrice, maxPriorityFeePerGas, maxFeePerGas, password) + +method getInstalledStickerPacks*[T](self: Module[T]): Table[int, StickerPackDto] = + self.controller.getInstalledStickerPacks() + +method getPurchasedStickerPacks*[T](self: Module[T], address: string): seq[int] = + self.controller.getPurchasedStickerPacks(address) + +method obtainAvailableStickerPacks*[T](self: Module[T]) = + self.controller.obtainAvailableStickerPacks() + +method getNumInstalledStickerPacks*[T](self: Module[T]): int = + self.controller.getNumInstalledStickerPacks() + +method installStickerPack*[T](self: Module[T], packId: int) = + self.controller.installStickerPack(packId) + +method uninstallStickerPack*[T](self: Module[T], packId: int) = + self.controller.uninstallStickerPack(packId) + +method removeRecentStickers*[T](self: Module[T], packId: int) = + self.controller.removeRecentStickers(packId) + +method sendSticker*[T](self: Module[T], channelId: string, replyTo: string, sticker: StickerDto) = + self.controller.sendSticker(channelId, replyTo, sticker) + +method estimate*[T](self: Module[T], packId: int, address: string, price: string, uuid: string) = + self.controller.estimate(packId, address, price, uuid) + +method addRecentStickerToList*[T](self: Module[T], sticker: StickerDto) = + self.view.addRecentStickerToList(sticker) + +method clearStickerPacks*[T](self: Module[T]) = + self.view.clearStickerPacks() + +method allPacksLoaded*[T](self: Module[T]) = + self.view.allPacksLoaded() + +method populateInstalledStickerPacks*[T](self: Module[T], stickers: Table[int, StickerPackDto]) = + self.view.populateInstalledStickerPacks(stickers) + +method gasEstimateReturned*[T](self: Module[T], estimate: int, uuid: string) = + self.view.gasEstimateReturned(estimate, uuid) + +method addStickerPackToList*[T](self: Module[T], stickerPack: StickerPackDto, isInstalled: bool, isBought: bool, isPending: bool) = + self.view.addStickerPackToList(stickerPack, isInstalled, isBought, isPending) diff --git a/src/app/modules/main/stickers/view.nim b/src/app/modules/main/stickers/view.nim new file mode 100644 index 0000000000..c3a2074a8d --- /dev/null +++ b/src/app/modules/main/stickers/view.nim @@ -0,0 +1,105 @@ +import NimQml, atomics, json, sets, strutils, tables, json_serialization + +import status/types/[sticker, pending_transaction_type] +import ./models/[sticker_list, sticker_pack_list] +import ./io_interface +import ../../../../app_service/service/stickers/dto/stickers + +QtObject: + type + View* = ref object of QObject + delegate: io_interface.AccessInterface + stickerPacks*: StickerPackList + recentStickers*: StickerList + + proc delete*(self: View) = + self.QObject.delete + + proc newView*(delegate: io_interface.AccessInterface): View = + new(result, delete) + result.QObject.setup + result.delegate = delegate + result.stickerPacks = newStickerPackList() + result.recentStickers = newStickerList() + + proc addStickerPackToList*(self: View, stickerPack: StickerPackDto, isInstalled, isBought, isPending: bool) = + self.stickerPacks.addStickerPackToList(stickerPack, newStickerList(stickerPack.stickers), isInstalled, isBought, isPending) + + proc getStickerPackList(self: View): QVariant {.slot.} = + newQVariant(self.stickerPacks) + + QtProperty[QVariant] stickerPacks: + read = getStickerPackList + + proc recentStickersUpdated*(self: View) {.signal.} + proc getRecentStickerList*(self: View): QVariant {.slot.} = + result = newQVariant(self.recentStickers) + + QtProperty[QVariant] recent: + read = getRecentStickerList + notify = recentStickersUpdated + + proc transactionWasSent*(self: View, txResult: string) {.signal.} + + proc transactionCompleted*(self: View, success: bool, txHash: string, revertReason: string = "") {.signal.} + + proc estimate*(self: View, packId: int, address: string, price: string, uuid: string) {.slot.} = + self.delegate.estimate(packId, address, price, uuid) + + proc gasEstimateReturned*(self: View, estimate: int, uuid: string) {.signal.} + + proc buy*(self: View, packId: int, address: string, price: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string): string {.slot.} = + let responseTuple = self.delegate.buy(packId, address, price, gas, gasPrice, maxPriorityFeePerGas, maxFeePerGas, password) + let response = responseTuple.response + let success = responseTuple.success + if success: + self.stickerPacks.updateStickerPackInList(packId, false, true) + self.transactionWasSent(response) + + proc stickerPacksLoaded*(self: View) {.signal.} + + proc installedStickerPacksUpdated*(self: View) {.signal.} + + proc clearStickerPacks*(self: View) = + self.stickerPacks.clear() + + proc populateInstalledStickerPacks*(self: View, installedStickerPacks: Table[int, StickerPackDto]) = + for stickerPack in installedStickerPacks.values: + self.addStickerPackToList(stickerPack, isInstalled = true, isBought = true, isPending = false) + + + proc getNumInstalledStickerPacks(self: View): int {.slot.} = + self.delegate.getNumInstalledStickerPacks() + + QtProperty[int] numInstalledStickerPacks: + read = getNumInstalledStickerPacks + notify = installedStickerPacksUpdated + + proc install*(self: View, packId: int) {.slot.} = + self.delegate.installStickerPack(packId) + self.stickerPacks.updateStickerPackInList(packId, true, false) + self.installedStickerPacksUpdated() + + proc resetBuyAttempt*(self: View, packId: int) {.slot.} = + self.stickerPacks.updateStickerPackInList(packId, false, false) + + proc uninstall*(self: View, packId: int) {.slot.} = + self.delegate.uninstallStickerPack(packId) + self.delegate.removeRecentStickers(packId) + self.stickerPacks.updateStickerPackInList(packId, false, false) + self.recentStickers.removeStickersFromList(packId) + self.installedStickerPacksUpdated() + self.recentStickersUpdated() + + proc addRecentStickerToList*(self: View, sticker: StickerDto) = + self.recentStickers.addStickerToList(sticker) + + proc allPacksLoaded*(self: View) = + self.stickerPacksLoaded() + self.installedStickerPacksUpdated() + + proc send*(self: View, channelId: string, hash: string, replyTo: string, pack: int) {.slot.} = + let sticker = StickerDto(hash: hash, packId: pack) + self.addRecentStickerToList(sticker) + self.delegate.sendSticker(channelId, replyTo, sticker) + diff --git a/src/app_service/service/chat/service.nim b/src/app_service/service/chat/service.nim index 5e2e7a8ba1..8aca58bc60 100644 --- a/src/app_service/service/chat/service.nim +++ b/src/app_service/service/chat/service.nim @@ -1,8 +1,11 @@ import Tables, json, sequtils, strformat, chronicles -import service_interface, ./dto/chat +import service_interface +import ./dto/chat as chat_dto import ../contacts/service as contact_service import status/statusgo_backend_new/chat as status_go +import status/types/[message] +import status/types/chat as chat_type export service_interface @@ -29,7 +32,7 @@ method init*(self: Service) = let chats = map(response.result.getElems(), proc(x: JsonNode): ChatDto = x.toChatDto()) for chat in chats: - if chat.active and chat.chatType != ChatType.Unknown: + if chat.active and chat.chatType != chat_dto.ChatType.Unknown: self.chats[chat.id] = chat except Exception as e: @@ -40,7 +43,7 @@ method init*(self: Service) = method getAllChats*(self: Service): seq[ChatDto] = return toSeq(self.chats.values) -method getChatsOfChatTypes*(self: Service, types: seq[ChatType]): seq[ChatDto] = +method getChatsOfChatTypes*(self: Service, types: seq[chat_dto.ChatType]): seq[ChatDto] = return self.getAllChats().filterIt(it.chatType in types) method getChatById*(self: Service, chatId: string): ChatDto = @@ -52,4 +55,20 @@ method getChatById*(self: Service, chatId: string): ChatDto = method prettyChatName*(self: Service, chatId: string): string = let contact = self.contactService.getContactById(chatId) - return contact.userNameOrAlias() \ No newline at end of file + return contact.userNameOrAlias() + +# TODO refactor this to new object types +proc parseChatResponse*(self: Service, response: string): (seq[Chat], seq[Message]) = + var parsedResponse = parseJson(response) + var chats: seq[Chat] = @[] + var messages: seq[Message] = @[] + if parsedResponse{"result"}{"messages"} != nil: + for jsonMsg in parsedResponse["result"]["messages"]: + messages.add(jsonMsg.toMessage()) + if parsedResponse{"result"}{"chats"} != nil: + for jsonChat in parsedResponse["result"]["chats"]: + let chat = jsonChat.toChat + # TODO add the channel back to `chat` when it is refactored + # self.channels[chat.id] = chat + chats.add(chat) + result = (chats, messages) \ No newline at end of file diff --git a/src/app_service/service/chat/service_interface.nim b/src/app_service/service/chat/service_interface.nim index e6c10a6293..9c085802b6 100644 --- a/src/app_service/service/chat/service_interface.nim +++ b/src/app_service/service/chat/service_interface.nim @@ -1,4 +1,6 @@ import ./dto/chat as chat_dto +import status/types/[message] +import status/types/chat as chat_type export chat_dto @@ -15,11 +17,14 @@ method init*(self: ServiceInterface) {.base.} = method getAllChats*(self: ServiceInterface): seq[ChatDto] {.base.} = raise newException(ValueError, "No implementation available") -method getChatsOfChatTypes*(self: ServiceInterface, types: seq[ChatType]): seq[ChatDto] {.base.} = +method getChatsOfChatTypes*(self: ServiceInterface, types: seq[chat_dto.ChatType]): seq[ChatDto] {.base.} = raise newException(ValueError, "No implementation available") method getChatById*(self: ServiceInterface, chatId: string): ChatDto {.base.} = raise newException(ValueError, "No implementation available") method prettyChatName*(self: ServiceInterface, chatId: string): string {.base.} = + raise newException(ValueError, "No implementation available") + +method parseChatResponse*(self: ServiceInterface, response: string): (seq[Chat], seq[Message]) {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app_service/service/eth/dto/coder.nim b/src/app_service/service/eth/dto/coder.nim new file mode 100644 index 0000000000..e8eaa81197 --- /dev/null +++ b/src/app_service/service/eth/dto/coder.nim @@ -0,0 +1,82 @@ +import tables, strutils, stint, macros +import + web3/[encoding, ethtypes], stew/byteutils, nimcrypto, json_serialization, chronicles +import json, tables, json_serialization + +include ../../../common/json_utils + +type + GetPackData* = object + packId*: Stuint[256] + + PackData* = object + category*: DynamicBytes[32] # bytes4[] + owner*: Address # address + mintable*: bool # bool + timestamp*: Stuint[256] # uint256 + price*: Stuint[256] # uint256 + contentHash*: DynamicBytes[64] # bytes + + BuyToken* = object + packId*: Stuint[256] + address*: Address + price*: Stuint[256] + + Register* = object + label*: FixedBytes[32] + account*: Address + x*: FixedBytes[32] + y*: FixedBytes[32] + + SetPubkey* = object + label*: FixedBytes[32] + x*: FixedBytes[32] + y*: FixedBytes[32] + + ExpirationTime* = object + label*: FixedBytes[32] + + Release* = object + label*: FixedBytes[32] + + ApproveAndCall*[N: static[int]] = object + to*: Address + value*: Stuint[256] + data*: DynamicBytes[N] + + Transfer* = object + to*: Address + value*: Stuint[256] + + BalanceOf* = object + address*: Address + + TokenOfOwnerByIndex* = object + address*: Address + index*: Stuint[256] + + TokenPackId* = object + tokenId*: Stuint[256] + + TokenUri* = object + tokenId*: Stuint[256] + + +# TODO: Figure out a way to parse a bool as a Bool instead of bool, as it is +# done in nim-web3 +func decode*(input: string, offset: int, to: var bool): int {.inline.} = + let val = input[offset..offset+63].parse(Int256) + to = val.truncate(int) == 1 + 64 + +# TODO: This is taken directly from nim-web3 in order to be able to decode +# booleans. I could not get the type Bool, as used in nim-web3, to be decoded +# properly, and instead resorted to a standard bool type. +func decodeHere*(input: string, offset: int, obj: var object): int = + var offset = offset + for field in fields(obj): + offset += decode(input, offset, field) + +func decodeContractResponse*[T](input: string): T = + result = T() + discard decodeHere(input.strip0xPrefix, 0, result) \ No newline at end of file diff --git a/src/app_service/service/eth/dto/contract.nim b/src/app_service/service/eth/dto/contract.nim new file mode 100644 index 0000000000..ea0777fb32 --- /dev/null +++ b/src/app_service/service/eth/dto/contract.nim @@ -0,0 +1,35 @@ +import + sequtils, macros, tables, strutils + +import + web3/ethtypes, stew/byteutils, nimcrypto, json_serialization, chronicles +import json, tables, json_serialization +import ./method_dto + +include ../../../common/json_utils + +type + ContractDto* = ref object of RootObj + name*: string + chainId*: int + address*: Address + methods* {.dontSerialize.}: Table[string, MethodDto] + + Erc20ContractDto* = ref object of ContractDto + symbol*: string + decimals*: int + hasIcon* {.dontSerialize.}: bool + color*: string + + Erc721ContractDto* = ref object of ContractDto + symbol*: string + hasIcon*: bool + +proc newErc20Contract*(name: string, chainId: int, address: Address, symbol: string, decimals: int, hasIcon: bool): Erc20ContractDto = + Erc20ContractDto(name: name, chainId: chainId, address: address, methods: ERC20_METHODS.toTable, symbol: symbol, decimals: decimals, hasIcon: hasIcon) + +proc newErc20Contract*(chainId: int, address: Address): Erc20ContractDto = + Erc20ContractDto(name: "", chainId: chainId, address: address, methods: ERC20_METHODS.toTable, symbol: "", decimals: 0, hasIcon: false) + +proc newErc721Contract*(name: string, chainId: int, address: Address, symbol: string, hasIcon: bool, addlMethods: seq[tuple[name: string, meth: MethodDto]] = @[]): Erc721ContractDto = + Erc721ContractDto(name: name, chainId: chainId, address: address, symbol: symbol, hasIcon: hasIcon, methods: ERC721_ENUMERABLE_METHODS.concat(addlMethods).toTable) diff --git a/src/app_service/service/eth/dto/edn_dto.nim b/src/app_service/service/eth/dto/edn_dto.nim new file mode 100644 index 0000000000..4914a6d4a8 --- /dev/null +++ b/src/app_service/service/eth/dto/edn_dto.nim @@ -0,0 +1,83 @@ +import typetraits +import edn, chronicles +import ../../stickers/dto/stickers # FIXME: there should be no type deps + +# forward declaration: +proc parseNode[T](node: EdnNode, searchName: string): T +proc parseMap[T](map: HMap, searchName: string,): T + +proc getValueFromNode[T](node: EdnNode): T = + if node.kind == EdnSymbol: + when T is string: + result = node.symbol.name + elif node.kind == EdnKeyword: + when T is string: + result = node.keyword.name + elif node.kind == EdnString: + when T is string: + result = node.str + elif node.kind == EdnCharacter: + when T is string: + result = node.character + elif node.kind == EdnBool: + when T is bool: + result = node.boolVal + elif node.kind == EdnInt: + when T is int: + try: + result = cast[int](node.num) + except: + warn "Returned 0 value for node, when value should have been ", val = $node.num + result = 0 + else: + raise newException(ValueError, "couldn't get '" & T.type.name & "'value from node: " & repr(node)) + +proc parseVector[T: seq[StickerDto]](node: EdnNode, searchName: string): seq[StickerDto] = + # TODO: make this generic to accept any seq[T]. Issue is that instantiating result + # like `result = T()` is not allowed when T is `seq[StickerDto]` + # because seq[StickerDto] isn't an object, whereas it works when T is + # an object type (like StickerDto). IOW, StickerDto is an object type, but seq[StickerDto] + # is not + result = newSeq[StickerDto]() + + for i in 0.. 0: + for iChild in 0.. contract.address == address) + result = if found.len > 0: found[0] else: nil + +method findBySymbol*(self: Service, contracts: seq[Erc20ContractDto], symbol: string): Erc20ContractDto = + let found = contracts.filter(contract => contract.symbol.toLower == symbol.toLower) + result = if found.len > 0: found[0] else: nil + +method findContract*(self: Service, chainId: int, name: string): ContractDto = + let found = self.allContracts().filter(contract => contract.name == name and contract.chainId == chainId) + result = if found.len > 0: found[0] else: nil + +method allErc20Contracts*(self: Service): seq[Erc20ContractDto] = + result = self.allContracts().filter(contract => contract of Erc20ContractDto).map(contract => Erc20ContractDto(contract)) + +method allErc20ContractsByChainId*(self: Service, chainId: int): seq[Erc20ContractDto] = + result = self.allContracts().filter(contract => contract of Erc20ContractDto and contract.chainId == chainId).map(contract => Erc20ContractDto(contract)) + +method findErc20Contract*(self: Service, chainId: int, symbol: string): Erc20ContractDto = + return self.findBySymbol(self.allErc20ContractsByChainId(chainId), symbol) + +method findErc20Contract*(self: Service, chainId: int, address: Address): Erc20ContractDto = + return self.findByAddress(self.allErc20ContractsByChainId(chainId), address) + +method allErc721ContractsByChainId*(self: Service, chainId: int): seq[Erc721ContractDto] = + result = self.allContracts().filter(contract => contract of Erc721ContractDto and contract.chainId == chainId).map(contract => Erc721ContractDto(contract)) + +method findErc721Contract*(self: Service, chainId: int, name: string): Erc721ContractDto = + let found = self.allErc721ContractsByChainId(chainId).filter(contract => contract.name.toLower == name.toLower) + result = if found.len > 0: found[0] else: nil + +method buildTransaction*(self: Service, source: Address, value: Uint256, gas = "", gasPrice = "", isEIP1559Enabled = false, maxPriorityFeePerGas = "", maxFeePerGas = "", data = ""): TransactionDataDto = + result = TransactionDataDto( + source: source, + value: value.some, + gas: (if gas.isEmptyOrWhitespace: Quantity.none else: Quantity(cast[uint64](parseFloat(gas).toUInt64)).some), + gasPrice: (if gasPrice.isEmptyOrWhitespace: int.none else: gwei2Wei(parseFloat(gasPrice)).truncate(int).some), + data: data + ) + if isEIP1559Enabled: + result.txType = "0x02" + result.maxPriorityFeePerGas = if maxFeePerGas.isEmptyOrWhitespace: Uint256.none else: gwei2Wei(parseFloat(maxPriorityFeePerGas)).some + result.maxFeePerGas = (if maxFeePerGas.isEmptyOrWhitespace: Uint256.none else: gwei2Wei(parseFloat(maxFeePerGas)).some) + else: + result.txType = "0x00" + +method buildTokenTransaction*(self: Service, source, contractAddress: Address, gas = "", gasPrice = "", isEIP1559Enabled = false, maxPriorityFeePerGas = "", maxFeePerGas = ""): TransactionDataDto = + result = self.buildTransaction(source, 0.u256, gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas) + result.to = contractAddress.some diff --git a/src/app_service/service/eth/service_interface.nim b/src/app_service/service/eth/service_interface.nim new file mode 100644 index 0000000000..c70a8dfb57 --- /dev/null +++ b/src/app_service/service/eth/service_interface.nim @@ -0,0 +1,10 @@ + +type + ServiceInterface* {.pure inheritable.} = ref object of RootObj + ## Abstract class for this service access. + +method delete*(self: ServiceInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method init*(self: ServiceInterface) {.base.} = + raise newException(ValueError, "No implementation available") diff --git a/src/app_service/service/eth/signing_phrases.nim b/src/app_service/service/eth/signing_phrases.nim new file mode 100644 index 0000000000..e459823bf3 --- /dev/null +++ b/src/app_service/service/eth/signing_phrases.nim @@ -0,0 +1,623 @@ +const phrases*: seq[string] = @[ + "acid", + "alto", + "apse", + "arch", + "area", + "army", + "atom", + "aunt", + "babe", + "baby", + "back", + "bail", + "bait", + "bake", + "ball", + "band", + "bank", + "barn", + "base", + "bass", + "bath", + "bead", + "beak", + "beam", + "bean", + "bear", + "beat", + "beef", + "beer", + "beet", + "bell", + "belt", + "bend", + "bike", + "bill", + "bird", + "bite", + "blow", + "blue", + "boar", + "boat", + "body", + "bolt", + "bomb", + "bone", + "book", + "boot", + "bore", + "boss", + "bowl", + "brow", + "bulb", + "bull", + "burn", + "bush", + "bust", + "cafe", + "cake", + "calf", + "call", + "calm", + "camp", + "cane", + "cape", + "card", + "care", + "carp", + "cart", + "case", + "cash", + "cast", + "cave", + "cell", + "cent", + "chap", + "chef", + "chin", + "chip", + "chop", + "chub", + "chug", + "city", + "clam", + "clef", + "clip", + "club", + "clue", + "coal", + "coat", + "code", + "coil", + "coin", + "coke", + "cold", + "colt", + "comb", + "cone", + "cook", + "cope", + "copy", + "cord", + "cork", + "corn", + "cost", + "crab", + "craw", + "crew", + "crib", + "crop", + "crow", + "curl", + "cyst", + "dame", + "dare", + "dark", + "dart", + "dash", + "data", + "date", + "dead", + "deal", + "dear", + "debt", + "deck", + "deep", + "deer", + "desk", + "dhow", + "diet", + "dill", + "dime", + "dirt", + "dish", + "disk", + "dock", + "doll", + "door", + "dory", + "drag", + "draw", + "drop", + "drug", + "drum", + "duck", + "dump", + "dust", + "duty", + "ease", + "east", + "eave", + "eddy", + "edge", + "envy", + "epee", + "exam", + "exit", + "face", + "fact", + "fail", + "fall", + "fame", + "fang", + "farm", + "fawn", + "fear", + "feed", + "feel", + "feet", + "file", + "fill", + "film", + "find", + "fine", + "fire", + "fish", + "flag", + "flat", + "flax", + "flow", + "foam", + "fold", + "font", + "food", + "foot", + "fork", + "form", + "fort", + "fowl", + "frog", + "fuel", + "full", + "gain", + "gale", + "galn", + "game", + "garb", + "gate", + "gear", + "gene", + "gift", + "girl", + "give", + "glad", + "glen", + "glue", + "glut", + "goal", + "goat", + "gold", + "golf", + "gong", + "good", + "gown", + "grab", + "gram", + "gray", + "grey", + "grip", + "grit", + "gyro", + "hail", + "hair", + "half", + "hall", + "hand", + "hang", + "harm", + "harp", + "hate", + "hawk", + "head", + "heat", + "heel", + "hell", + "helo", + "help", + "hemp", + "herb", + "hide", + "high", + "hill", + "hire", + "hive", + "hold", + "hole", + "home", + "hood", + "hoof", + "hook", + "hope", + "hops", + "horn", + "hose", + "host", + "hour", + "hunt", + "hurt", + "icon", + "idea", + "inch", + "iris", + "iron", + "item", + "jail", + "jeep", + "jeff", + "joey", + "join", + "joke", + "judo", + "jump", + "junk", + "jury", + "jute", + "kale", + "keep", + "kick", + "kill", + "kilt", + "kind", + "king", + "kiss", + "kite", + "knee", + "knot", + "lace", + "lack", + "lady", + "lake", + "lamb", + "lamp", + "land", + "lark", + "lava", + "lawn", + "lead", + "leaf", + "leek", + "lier", + "life", + "lift", + "lily", + "limo", + "line", + "link", + "lion", + "lisa", + "list", + "load", + "loaf", + "loan", + "lock", + "loft", + "long", + "look", + "loss", + "lout", + "love", + "luck", + "lung", + "lute", + "lynx", + "lyre", + "maid", + "mail", + "main", + "make", + "male", + "mall", + "manx", + "many", + "mare", + "mark", + "mask", + "mass", + "mate", + "math", + "meal", + "meat", + "meet", + "menu", + "mess", + "mice", + "midi", + "mile", + "milk", + "mime", + "mind", + "mine", + "mini", + "mint", + "miss", + "mist", + "moat", + "mode", + "mole", + "mood", + "moon", + "most", + "moth", + "move", + "mule", + "mutt", + "nail", + "name", + "neat", + "neck", + "need", + "neon", + "nest", + "news", + "node", + "nose", + "note", + "oboe", + "okra", + "open", + "oval", + "oven", + "oxen", + "pace", + "pack", + "page", + "pail", + "pain", + "pair", + "palm", + "pard", + "park", + "part", + "pass", + "past", + "path", + "peak", + "pear", + "peen", + "peer", + "pelt", + "perp", + "pest", + "pick", + "pier", + "pike", + "pile", + "pimp", + "pine", + "ping", + "pink", + "pint", + "pipe", + "piss", + "pith", + "plan", + "play", + "plot", + "plow", + "poem", + "poet", + "pole", + "polo", + "pond", + "pony", + "poof", + "pool", + "port", + "post", + "prow", + "pull", + "puma", + "pump", + "pupa", + "push", + "quit", + "race", + "rack", + "raft", + "rage", + "rail", + "rain", + "rake", + "rank", + "rate", + "read", + "rear", + "reef", + "rent", + "rest", + "rice", + "rich", + "ride", + "ring", + "rise", + "risk", + "road", + "robe", + "rock", + "role", + "roll", + "roof", + "room", + "root", + "rope", + "rose", + "ruin", + "rule", + "rush", + "ruth", + "sack", + "safe", + "sage", + "sail", + "sale", + "salt", + "sand", + "sari", + "sash", + "save", + "scow", + "seal", + "seat", + "seed", + "self", + "sell", + "shed", + "shin", + "ship", + "shoe", + "shop", + "shot", + "show", + "sick", + "side", + "sign", + "silk", + "sill", + "silo", + "sing", + "sink", + "site", + "size", + "skin", + "sled", + "slip", + "smog", + "snob", + "snow", + "soap", + "sock", + "soda", + "sofa", + "soft", + "soil", + "song", + "soot", + "sort", + "soup", + "spot", + "spur", + "stag", + "star", + "stay", + "stem", + "step", + "stew", + "stop", + "stud", + "suck", + "suit", + "swan", + "swim", + "tail", + "tale", + "talk", + "tank", + "tard", + "task", + "taxi", + "team", + "tear", + "teen", + "tell", + "temp", + "tent", + "term", + "test", + "text", + "thaw", + "tile", + "till", + "time", + "tire", + "toad", + "toga", + "togs", + "tone", + "tool", + "toot", + "tote", + "tour", + "town", + "tram", + "tray", + "tree", + "trim", + "trip", + "tuba", + "tube", + "tuna", + "tune", + "turn", + "tutu", + "twig", + "type", + "unit", + "user", + "vane", + "vase", + "vast", + "veal", + "veil", + "vein", + "vest", + "vibe", + "view", + "vise", + "wait", + "wake", + "walk", + "wall", + "wash", + "wasp", + "wave", + "wear", + "weed", + "week", + "well", + "west", + "whip", + "wife", + "will", + "wind", + "wine", + "wing", + "wire", + "wish", + "wolf", + "wood", + "wool", + "word", + "work", + "worm", + "wrap", + "wren", + "yard", + "yarn", + "yawl", + "year", + "yoga", + "yoke", + "yurt", + "zinc", + "zone"] \ No newline at end of file diff --git a/src/app_service/service/eth/utils.nim b/src/app_service/service/eth/utils.nim new file mode 100644 index 0000000000..ef19dd0979 --- /dev/null +++ b/src/app_service/service/eth/utils.nim @@ -0,0 +1,239 @@ +import + atomics, json, tables, sequtils, httpclient, net +import json, random, strutils, strformat, tables, chronicles, unicode, times +import + json_serialization, chronicles, libp2p/[multihash, multicodec, cid], stint, nimcrypto +from sugar import `=>`, `->` +import stint +from times import getTime, toUnix, nanosecond +import signing_phrases +from web3 import Address, fromHex +import web3/ethhexstrings + +proc decodeContentHash*(value: string): string = + if value == "": + return "" + + # eg encoded sticker multihash cid: + # e30101701220eab9a8ef4eac6c3e5836a3768d8e04935c10c67d9a700436a0e53199e9b64d29 + # e3017012205c531b83da9dd91529a4cf8ecd01cb62c399139e6f767e397d2f038b820c139f (testnet) + # e3011220c04c617170b1f5725070428c01280b4c19ae9083b7e6d71b7a0d2a1b5ae3ce30 (testnet) + # + # The first 4 bytes (in hex) represent: + # e3 = codec identifier "ipfs-ns" for content-hash + # 01 = unused - sometimes this is NOT included (ie ropsten) + # 01 = CID version (effectively unused, as we will decode with CIDv0 regardless) + # 70 = codec identifier "dag-pb" + + # ipfs-ns + if value[0..1] != "e3": + warn "Could not decode sticker. It may still be valid, but requires a different codec to be used", hash=value + return "" + + try: + # dag-pb + let defaultCodec = parseHexInt("70") #dag-pb + var codec = defaultCodec # no codec specified + var codecStartIdx = 2 # idx of where codec would start if it was specified + # handle the case when starts with 0xe30170 instead of 0xe3010170 + if value[2..5] == "0101": + codecStartIdx = 6 + codec = parseHexInt(value[6..7]) + elif value[2..3] == "01" and value[4..5] != "12": + codecStartIdx = 4 + codec = parseHexInt(value[4..5]) + + # strip the info we no longer need + var multiHashStr = value[codecStartIdx + 2..= 0): + result = parse($stringValue, StUint[bits]) + else: + result = parse("0", StUint[bits]) + +proc toUInt256*(flt: float): UInt256 = + toStUInt(flt, StUInt[256]) + +proc toUInt64*(flt: float): StUInt[64] = + toStUInt(flt, StUInt[64]) + +proc eth2Wei*(eth: float, decimals: int = 18): UInt256 = + let weiValue = eth * parseFloat(alignLeft("1", decimals + 1, '0')) + weiValue.toUInt256 + +proc gwei2Wei*(gwei: float): UInt256 = + eth2Wei(gwei, 9) + +proc wei2Eth*(input: Stuint[256], decimals: int = 18): string = + var one_eth = u256(10).pow(decimals) # fromHex(Stuint[256], "DE0B6B3A7640000") + + var (eth, remainder) = divmod(input, one_eth) + let leading_zeros = "0".repeat(($one_eth).len - ($remainder).len - 1) + + fmt"{eth}.{leading_zeros}{remainder}" + +proc wei2Eth*(input: string, decimals: int): string = + try: + var input256: Stuint[256] + if input.contains("e+"): # we have a js string BN, ie 1e+21 + let + inputSplit = input.split("e+") + whole = inputSplit[0].u256 + remainder = u256(10).pow(inputSplit[1].parseInt) + input256 = whole * remainder + else: + input256 = input.u256 + result = wei2Eth(input256, decimals) + except Exception as e: + error "Error parsing this wei value", input, msg=e.msg + result = "0" + +proc wei2Gwei*(input: string): string = + result = wei2Eth(input, 9) + +proc first*(jArray: JsonNode, fieldName, id: string): JsonNode = + if jArray == nil: + return nil + if jArray.kind != JArray: + raise newException(ValueError, "Parameter 'jArray' is a " & $jArray.kind & ", but must be a JArray") + for child in jArray.getElems: + if child{fieldName}.getStr.toLower == id.toLower: + return child + +proc any*(jArray: JsonNode, fieldName, id: string): bool = + if jArray == nil: + return false + result = false + for child in jArray.getElems: + if child{fieldName}.getStr.toLower == id.toLower: + return true + +proc isEmpty*(a: JsonNode): bool = + case a.kind: + of JObject: return a.fields.len == 0 + of JArray: return a.elems.len == 0 + of JString: return a.str == "" + of JNull: return true + else: + return false + +proc find*[T](s: seq[T], pred: proc(x: T): bool {.closure.}): T {.inline.} = + let results = s.filter(pred) + if results.len == 0: + return default(type(T)) + result = results[0] + +proc find*[T](s: seq[T], pred: proc(x: T): bool {.closure.}, found: var bool): T {.inline.} = + let results = s.filter(pred) + if results.len == 0: + found = false + return default(type(T)) + result = results[0] + found = true + +proc parseAddress*(strAddress: string): Address = + fromHex(Address, strAddress) + +proc isAddress*(strAddress: string): bool = + try: + discard parseAddress(strAddress) + except: + return false + return true + +proc validateTransactionInput*(from_addr, to_addr, assetAddress, value, gas, gasPrice, data: string, isEIP1599Enabled: bool, maxPriorityFeePerGas, maxFeePerGas, uuid: string) = + if not isAddress(from_addr): raise newException(ValueError, "from_addr is not a valid ETH address") + if not isAddress(to_addr): raise newException(ValueError, "to_addr is not a valid ETH address") + if parseFloat(value) < 0: raise newException(ValueError, "value should be a number >= 0") + if parseInt(gas) <= 0: raise newException(ValueError, "gas should be a number > 0") + if isEIP1599Enabled: + if gasPrice != "" and (maxPriorityFeePerGas != "" or maxFeePerGas != ""): + raise newException(ValueError, "gasPrice can't be used with maxPriorityFeePerGas and maxFeePerGas") + if gasPrice == "": + if parseFloat(maxPriorityFeePerGas) <= 0: raise newException(ValueError, "maxPriorityFeePerGas should be a number > 0") + if parseFloat(maxFeePerGas) <= 0: raise newException(ValueError, "maxFeePerGas should be a number > 0") + else: + if parseFloat(gasPrice) <= 0: raise newException(ValueError, "gasPrice should be a number > 0") + if uuid.isEmptyOrWhitespace(): raise newException(ValueError, "uuid is required") + + if assetAddress != "": # If a token is being used + if not isAddress(assetAddress): raise newException(ValueError, "assetAddress is not a valid ETH address") + if assetAddress == "0x0000000000000000000000000000000000000000": raise newException(ValueError, "assetAddress requires a valid token address") + + if data != "": # If data is being used + if not validate(HexDataStr(data)): raise newException(ValueError, "data should contain a valid hex string") + +proc hex2Time*(hex: string): Time = + # represents the time since 1970-01-01T00:00:00Z + fromUnix(fromHex[int64](hex)) + +proc hex2LocalDateTime*(hex: string): DateTime = + # Convert hex time (since 1970-01-01T00:00:00Z) into a DateTime using the + # local timezone. + hex.hex2Time.local + +proc isUnique*[T](key: T, existingKeys: var seq[T]): bool = + # If the key doesn't exist in the existingKeys seq, add it and return true. + # Otherwise, the key already existed, so return false. + # Can be used to deduplicate sequences with `deduplicate[T]`. + if not existingKeys.contains(key): + existingKeys.add key + return true + return false + +proc deduplicate*[T](txs: var seq[T], key: (T) -> string) = + var existingKeys: seq[string] = @[] + txs.keepIf(tx => tx.key().isUnique(existingKeys)) diff --git a/src/app_service/service/network/dto.nim b/src/app_service/service/network/dto.nim new file mode 100644 index 0000000000..dbcd0d11ab --- /dev/null +++ b/src/app_service/service/network/dto.nim @@ -0,0 +1,29 @@ + +import strformat, json, json_serialization + +import ./types + +type NetworkDto* = ref object + chainId* {.serializedFieldName("chainId").}: int + nativeCurrencyDecimals* {.serializedFieldName("nativeCurrencyDecimals").}: int + layer* {.serializedFieldName("layer").}: int + chainName* {.serializedFieldName("chainName").}: string + rpcURL* {.serializedFieldName("rpcUrl").}: string + blockExplorerURL* {.serializedFieldName("blockExplorerUrl").}: string + iconURL* {.serializedFieldName("iconUrl").}: string + nativeCurrencyName* {.serializedFieldName("nativeCurrencyName").}: string + nativeCurrencySymbol* {.serializedFieldName("nativeCurrencySymbol").}: string + isTest* {.serializedFieldName("isTest").}: bool + enabled* {.serializedFieldName("enabled").}: bool + +proc `$`*(self: NetworkDto): string = + return fmt"Network(chainId:{self.chainId}, name:{self.chainName}, rpcURL:{self.rpcURL}, isTest:{self.isTest}, enabled:{self.enabled})" + +proc toPayload*(self: NetworkDto): JsonNode = + return %* [Json.encode(self).parseJson] + +proc sntSymbol*(self: NetworkDto): string = + if self.chainId == Mainnet: + return "SNT" + else: + return "STT" \ No newline at end of file diff --git a/src/app_service/service/network/service.nim b/src/app_service/service/network/service.nim new file mode 100644 index 0000000000..2175490e9a --- /dev/null +++ b/src/app_service/service/network/service.nim @@ -0,0 +1,59 @@ +import json, json_serialization, chronicles, atomics +import options + +import status/statusgo_backend_new/network as status_network +import ./service_interface +import ./dto +import ./types + +export service_interface +export dto, types + +logScope: + topics = "network-service" + +type + Service* = ref object of ServiceInterface + networks: seq[NetworkDto] + networksInited: bool + dirty: Atomic[bool] + +method delete*(self: Service) = + discard + +proc newService*(): Service = + result = Service() + +method init*(self: Service) = + discard + +method getNetworks*(self: Service, useCached: bool = true): seq[NetworkDto] = + let cacheIsDirty = not self.networksInited or self.dirty.load + if useCached and not cacheIsDirty: + result = self.networks + else: + let payload = %* [false] + let response = status_network.getNetworks(payload) + if not response.error.isNil: + raise newException(Exception, "Error getting networks: " & response.error.message) + result = if response.result.isNil or response.result.kind == JNull: @[] + else: Json.decode($response.result, seq[NetworkDto]) + self.dirty.store(false) + self.networks = result + self.networksInited = true + +method upsertNetwork*(self: Service, network: NetworkDto) = + discard status_network.upsertNetwork(network.toPayload()) + self.dirty.store(true) + +method deleteNetwork*(self: Service, network: NetworkDto) = + discard status_network.deleteNetwork(%* [network.chainId]) + self.dirty.store(true) + +method getNetwork*(self: Service, networkType: NetworkType): NetworkDto = + for network in self.getNetworks(): + if networkType.toChainId() == network.chainId: + return network + + # Will be removed, this is used in case of legacy chain Id + return NetworkDto(chainId: networkType.toChainId()) diff --git a/src/app_service/service/network/service_interface.nim b/src/app_service/service/network/service_interface.nim new file mode 100644 index 0000000000..75eb73e825 --- /dev/null +++ b/src/app_service/service/network/service_interface.nim @@ -0,0 +1,25 @@ +import dto, types + +export dto + +type + ServiceInterface* {.pure inheritable.} = ref object of RootObj + ## Abstract class for this service access. + +method delete*(self: ServiceInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method init*(self: ServiceInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method getNetworks*(self: ServiceInterface, useCached: bool = true): seq[NetworkDto] {.base.} = + raise newException(ValueError, "No implementation available") + +method upsertNetwork*(self: ServiceInterface, network: NetworkDto) {.base.} = + raise newException(ValueError, "No implementation available") + +method deleteNetwork*(self: ServiceInterface, network: NetworkDto) {.base.} = + raise newException(ValueError, "No implementation available") + +method getNetwork*(self: ServiceInterface, networkType: NetworkType): NetworkDto {.base.} = + raise newException(ValueError, "No implementation available") diff --git a/src/app_service/service/network/types.nim b/src/app_service/service/network/types.nim index d77fa5f99f..0ecef6427f 100644 --- a/src/app_service/service/network/types.nim +++ b/src/app_service/service/network/types.nim @@ -6,4 +6,41 @@ const Optimism = 10 const Poa = 99 const XDai = 100 -export Mainnet, Ropsten, Rinkeby, Goerli, Optimism, Poa, XDai \ No newline at end of file +export Mainnet, Ropsten, Rinkeby, Goerli, Optimism, Poa, XDai + +type + NetworkType* {.pure.} = enum + Mainnet = "mainnet_rpc", + Testnet = "testnet_rpc", + Rinkeby = "rinkeby_rpc", + Goerli = "goerli_rpc", + XDai = "xdai_rpc", + Poa = "poa_rpc", + Other = "other" + +proc toNetworkType*(networkName: string): NetworkType = + case networkName: + of "mainnet_rpc": + result = NetworkType.Mainnet + of "testnet_rpc": + result = NetworkType.Testnet + of "rinkeby_rpc": + result = NetworkType.Rinkeby + of "goerli_rpc": + result = NetworkType.Goerli + of "xdai_rpc": + result = NetworkType.XDai + of "poa_rpc": + result = NetworkType.Poa + else: + result = NetworkType.Other + +proc toChainId*(self: NetworkType): int = + case self: + of NetworkType.Mainnet: result = Mainnet + of NetworkType.Testnet: result = Ropsten + of NetworkType.Rinkeby: result = Rinkeby + of NetworkType.Goerli: result = Goerli + of NetworkType.XDai: result = XDai + of NetworkType.Poa: result = 99 + of NetworkType.Other: result = -1 diff --git a/src/app_service/service/settings/dto/settings.nim b/src/app_service/service/settings/dto/settings.nim index 86369c69d3..2fbfc1820c 100644 --- a/src/app_service/service/settings/dto/settings.nim +++ b/src/app_service/service/settings/dto/settings.nim @@ -1,7 +1,44 @@ -import json, options +import json, options, tables, strutils +import ../../stickers/dto/stickers include ../../../common/json_utils +# Setting keys: +const KEY_ADDRESS* = "address" +const KEY_CURRENCY* = "currency" +const KEY_NETWORKS_CURRENT_NETWORK* = "networks/current-network" +const KEY_NETWORKS_ALL_NETWORKS* = "networks/networks" +const KEY_DAPPS_ADDRESS* = "dapps-address" +const KEY_EIP1581_ADDRESS* = "eip1581-address" +const KEY_INSTALLATION_ID* = "installation-id" +const KEY_KEY_UID* = "key-uid" +const KEY_LATEST_DERIVED_PATH* = "latest-derived-path" +const KEY_LINK_PREVIEW_REQUEST_ENABLED* = "link-preview-request-enabled" +const KEY_MESSAGES_FROM_CONTACTS_ONLY* = "messages-from-contacts-only" +const KEY_MNEMONIC* = "mnemonic" +const KEY_NAME* = "name" +const KEY_PHOTO_PATH* = "photo-path" +const KEY_PREVIEW_PRIVACY* = "preview-privacy?" +const KEY_PUBLIC_KEY* = "public-key" +const KEY_SIGNING_PHRASE* = "signing-phrase" +const KEY_DEFAULT_SYNC_PERIOD* = "default-sync-period" +const KEY_SEND_PUSH_NOTIFICATIONS* = "send-push-notifications?" +const KEY_APPEARANCE* = "appearance" +const KEY_PROFILE_PICTURES_SHOW_TO* = "profile-pictures-show-to" +const KEY_PROFILE_PICTURES_VISIBILITY* = "profile-pictures-visibility" +const KEY_USE_MAILSERVERS* = "use-mailservers?" +const KEY_WALLET_ROOT_ADDRESS* = "wallet-root-address" +const KEY_SEND_STATUS_UPDATES* = "send-status-updates?" +const KEY_TELEMETRY_SERVER_URL* = "telemetry-server-url" +const KEY_WALLET_VISIBLE_TOKENS* = "wallet/visible-tokens" +const KEY_PINNED_MAILSERVERS* = "pinned-mailservers" +const KEY_CURRENT_USER_STATUS* = "current-user-status" +const KEY_RECENT_STICKERS* = "stickers/recent-stickers" +const KEY_INSTALLED_STICKER_PACKS* = "stickers/packs-installed" +const KEY_FLEET* = "fleet" +const KEY_NODE_CONFIG* = "node-config" +const KEY_WAKU_BLOOM_FILTER_MODE* = "waku-bloom-filter-mode" + type UpstreamConfig* = object enabled*: bool url*: string @@ -62,6 +99,8 @@ type walletVisibleTokens*: WalletVisibleTokens nodeConfig*: JsonNode wakuBloomFilterMode*: bool + recentStickers*: seq[StickerDto] + installedStickerPacks*: Table[int, StickerPackDto] proc toUpstreamConfig*(jsonObj: JsonNode): UpstreamConfig = discard jsonObj.getProp("Enabled", result.enabled) @@ -102,51 +141,65 @@ proc toWalletVisibleTokens*(jsonObj: JsonNode, networkId: string): WalletVisible proc toSettingsDto*(jsonObj: JsonNode): SettingsDto = - discard jsonObj.getProp("address", result.address) - discard jsonObj.getProp("currency", result.currency) - discard jsonObj.getProp("networks/current-network", result.currentNetwork) + discard jsonObj.getProp(KEY_ADDRESS, result.address) + discard jsonObj.getProp(KEY_CURRENCY, result.currency) + discard jsonObj.getProp(KEY_NETWORKS_CURRENT_NETWORK, result.currentNetwork) var networksArr: JsonNode - if(jsonObj.getProp("networks/networks", networksArr)): + if(jsonObj.getProp(KEY_NETWORKS_ALL_NETWORKS, networksArr)): if(networksArr.kind == JArray): for networkObj in networksArr: result.availableNetworks.add(toNetwork(networkObj)) - discard jsonObj.getProp("dapps-address", result.dappsAddress) - discard jsonObj.getProp("eip1581-address", result.eip1581Address) - discard jsonObj.getProp("installation-id", result.installationId) - discard jsonObj.getProp("key-uid", result.keyUid) - discard jsonObj.getProp("latest-derived-path", result.latestDerivedPath) - discard jsonObj.getProp("link-preview-request-enabled", result.linkPreviewRequestEnabled) - discard jsonObj.getProp("messages-from-contacts-only", result.messagesFromContactsOnly) - discard jsonObj.getProp("mnemonic", result.mnemonic) - discard jsonObj.getProp("name", result.name) - discard jsonObj.getProp("photo-path", result.photoPath) - discard jsonObj.getProp("preview-privacy?", result.previewPrivacy) - discard jsonObj.getProp("public-key", result.publicKey) - discard jsonObj.getProp("signing-phrase", result.signingPhrase) - discard jsonObj.getProp("default-sync-period", result.defaultSyncPeriod) - discard jsonObj.getProp("send-push-notifications?", result.sendPushNotifications) - discard jsonObj.getProp("appearance", result.appearance) - discard jsonObj.getProp("profile-pictures-show-to", result.profilePicturesShowTo) - discard jsonObj.getProp("profile-pictures-visibility", result.profilePicturesVisibility) - discard jsonObj.getProp("use-mailservers?", result.useMailservers) - discard jsonObj.getProp("wallet-root-address", result.walletRootAddress) - discard jsonObj.getProp("send-status-updates?", result.sendStatusUpdates) - discard jsonObj.getProp("telemetry-server-url", result.telemetryServerUrl) - discard jsonObj.getProp("fleet", result.fleet) + var recentStickersArr: JsonNode + if(jsonObj.getProp(KEY_RECENT_STICKERS, recentStickersArr)): + if(recentStickersArr.kind == JArray): + for recentStickerObj in recentStickersArr: + result.recentStickers.add(recentStickerObj.toStickerDto) + + var installedStickerPacksArr: JsonNode + if(jsonObj.getProp(KEY_INSTALLED_STICKER_PACKS, installedStickerPacksArr)): + if(installedStickerPacksArr.kind == JObject): + result.installedStickerPacks = initTable[int, StickerPackDto]() + for i in installedStickerPacksArr.keys: + let packId = parseInt(i) + result.installedStickerPacks[packId] = installedStickerPacksArr[i].toStickerPackDto + + discard jsonObj.getProp(KEY_DAPPS_ADDRESS, result.dappsAddress) + discard jsonObj.getProp(KEY_EIP1581_ADDRESS, result.eip1581Address) + discard jsonObj.getProp(KEY_INSTALLATION_ID, result.installationId) + discard jsonObj.getProp(KEY_KEY_UID, result.keyUid) + discard jsonObj.getProp(KEY_LATEST_DERIVED_PATH, result.latestDerivedPath) + discard jsonObj.getProp(KEY_LINK_PREVIEW_REQUEST_ENABLED, result.linkPreviewRequestEnabled) + discard jsonObj.getProp(KEY_MESSAGES_FROM_CONTACTS_ONLY, result.messagesFromContactsOnly) + discard jsonObj.getProp(KEY_MNEMONIC, result.mnemonic) + discard jsonObj.getProp(KEY_NAME, result.name) + discard jsonObj.getProp(KEY_PHOTO_PATH, result.photoPath) + discard jsonObj.getProp(KEY_PREVIEW_PRIVACY, result.previewPrivacy) + discard jsonObj.getProp(KEY_PUBLIC_KEY, result.publicKey) + discard jsonObj.getProp(KEY_SIGNING_PHRASE, result.signingPhrase) + discard jsonObj.getProp(KEY_DEFAULT_SYNC_PERIOD, result.defaultSyncPeriod) + discard jsonObj.getProp(KEY_SEND_PUSH_NOTIFICATIONS, result.sendPushNotifications) + discard jsonObj.getProp(KEY_APPEARANCE, result.appearance) + discard jsonObj.getProp(KEY_PROFILE_PICTURES_SHOW_TO, result.profilePicturesShowTo) + discard jsonObj.getProp(KEY_PROFILE_PICTURES_VISIBILITY, result.profilePicturesVisibility) + discard jsonObj.getProp(KEY_USE_MAILSERVERS, result.useMailservers) + discard jsonObj.getProp(KEY_WALLET_ROOT_ADDRESS, result.walletRootAddress) + discard jsonObj.getProp(KEY_SEND_STATUS_UPDATES, result.sendStatusUpdates) + discard jsonObj.getProp(KEY_TELEMETRY_SERVER_URL, result.telemetryServerUrl) + discard jsonObj.getProp(KEY_FLEET, result.fleet) var pinnedMailserversObj: JsonNode - if(jsonObj.getProp("pinned-mailservers", pinnedMailserversObj)): + if(jsonObj.getProp(KEY_PINNED_MAILSERVERS, pinnedMailserversObj)): result.pinnedMailservers = toPinnedMailservers(pinnedMailserversObj) var currentUserStatusObj: JsonNode - if(jsonObj.getProp("current-user-status", currentUserStatusObj)): + if(jsonObj.getProp(KEY_CURRENT_USER_STATUS, currentUserStatusObj)): result.currentUserStatus = toCurrentUserStatus(currentUserStatusObj) var walletVisibleTokensObj: JsonNode - if(jsonObj.getProp("wallet/visible-tokens", walletVisibleTokensObj)): + if(jsonObj.getProp(KEY_WALLET_VISIBLE_TOKENS, walletVisibleTokensObj)): result.walletVisibleTokens = toWalletVisibleTokens(walletVisibleTokensObj, result.currentNetwork) - discard jsonObj.getProp("node-config", result.nodeConfig) - discard jsonObj.getProp("waku-bloom-filter-mode", result.wakuBloomFilterMode) + discard jsonObj.getProp(KEY_NODE_CONFIG, result.nodeConfig) + discard jsonObj.getProp(KEY_WAKU_BLOOM_FILTER_MODE, result.wakuBloomFilterMode) diff --git a/src/app_service/service/settings/service.nim b/src/app_service/service/settings/service.nim index 2f6eacb589..f8214943e0 100644 --- a/src/app_service/service/settings/service.nim +++ b/src/app_service/service/settings/service.nim @@ -1,4 +1,4 @@ -import chronicles, json +import chronicles, json, sequtils, tables import service_interface, ./dto/settings import status/statusgo_backend_new/settings as status_go @@ -8,46 +8,17 @@ export service_interface logScope: topics = "settings-service" -# Setting keys: -const KEY_ADDRESS = "address" -const KEY_CURRENCY = "currency" -const KEY_NETWORKS_CURRENT_NETWORK = "networks/current-network" -const KEY_DAPPS_ADDRESS = "dapps-address" -const KEY_EIP1581_ADDRESS = "eip1581-address" -const KEY_INSTALLATION_ID = "installation-id" -const KEY_KEY_UID = "key-uid" -const KEY_LATEST_DERIVED_PATH = "latest-derived-path" -const KEY_LINK_PREVIEW_REQUEST_ENABLED = "link-preview-request-enabled" -const KEY_MESSAGES_FROM_CONTACTS_ONLY = "messages-from-contacts-only" -const KEY_MNEMONIC = "mnemonic" -const KEY_NAME = "name" -const KEY_PHOTO_PATH = "photo-path" -const KEY_PREVIEW_PRIVACY = "preview-privacy?" -const KEY_PUBLIC_KEY = "public-key" -const KEY_SIGNING_PHRASE = "signing-phrase" -const KEY_DEFAULT_SYNC_PERIOD = "default-sync-period" -const KEY_SEND_PUSH_NOTIFICATIONS = "send-push-notifications?" -const KEY_APPEARANCE = "appearance" -const KEY_PROFILE_PICTURES_SHOW_TO = "profile-pictures-show-to" -const KEY_PROFILE_PICTURES_VISIBILITY = "profile-pictures-visibility" -const KEY_USE_MAILSERVERS = "use-mailservers?" -const KEY_WALLET_ROOT_ADDRESS = "wallet-root-address" -const KEY_SEND_STATUS_UPDATES = "send-status-updates?" -const KEY_TELEMETRY_SERVER_URL = "telemetry-server-url" -const KEY_FLEET = "fleet" -const KEY_WALLET_VISIBLE_TOKENS = "wallet/visible-tokens" -const KEY_NODE_CONFIG = "node-config" -const KEY_WAKU_BLOOM_FILTER_MODE = "waku-bloom-filter-mode" - type Service* = ref object of service_interface.ServiceInterface settings: SettingsDto + eip1559Enabled*: bool method delete*(self: Service) = discard proc newService*(): Service = result = Service() + result.eip1559Enabled = false method init*(self: Service) = try: @@ -369,6 +340,46 @@ method saveWalletVisibleTokens*(self: Service, tokens: seq[string]): bool = return true return false +method isEIP1559Enabled*(self: Service, blockNumber: int): bool = + let networkId = self.getCurrentNetworkDetails().config.networkId + let activationBlock = case networkId: + of 3: 10499401 # Ropsten + of 4: 8897988 # Rinkeby + of 5: 5062605 # Goerli + of 1: 12965000 # Mainnet + else: -1 + if activationBlock > -1 and blockNumber >= activationBlock: + result = true + else: + result = false + self.eip1559Enabled = result + +method isEIP1559Enabled*(self: Service): bool = + result = self.eip1559Enabled + +method getRecentStickers*(self: Service): seq[StickerDto] = + result = self.settings.recentStickers + +method saveRecentStickers*(self: Service, recentStickers: seq[StickerDto]): bool = + if(self.saveSetting(KEY_RECENT_STICKERS, %(recentStickers.mapIt($it.hash)))): + self.settings.recentStickers = recentStickers + return true + return false + +method getInstalledStickerPacks*(self: Service): Table[int, StickerPackDto] = + result = self.settings.installedStickerPacks + +method saveRecentStickers*(self: Service, installedStickerPacks: Table[int, StickerPackDto]): bool = + let json = %* {} + for packId, pack in installedStickerPacks.pairs: + json[$packId] = %(pack) + + if(self.saveSetting(KEY_INSTALLED_STICKER_PACKS, $json)): + self.settings.installedStickerPacks = installedStickerPacks + return true + return false + + method saveNodeConfiguration*(self: Service, value: JsonNode): bool = if(self.saveSetting(KEY_NODE_CONFIG, value)): self.settings.nodeConfig = value diff --git a/src/app_service/service/settings/service_interface.nim b/src/app_service/service/settings/service_interface.nim index 1c88a089be..4aff2dbe95 100644 --- a/src/app_service/service/settings/service_interface.nim +++ b/src/app_service/service/settings/service_interface.nim @@ -1,7 +1,10 @@ import json +import tables import ./dto/settings as settings_dto +import ../stickers/dto/stickers as stickers_dto export settings_dto +export stickers_dto # Default values: const DEFAULT_CURRENT_NETWORK* = "mainnet_rpc" @@ -204,6 +207,25 @@ method getWalletVisibleTokens*(self: ServiceInterface): seq[string] {.base.} = method saveWalletVisibleTokens*(self: ServiceInterface, tokens: seq[string]): bool {.base.} = raise newException(ValueError, "No implementation available") +method isEIP1559Enabled*(self: ServiceInterface, blockNumber: int): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method isEIP1559Enabled*(self: ServiceInterface): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method getRecentStickers*(self: ServiceInterface): seq[StickerDto] {.base.} = + raise newException(ValueError, "No implementation available") + +method saveRecentStickers*(self: ServiceInterface, recentStickers: seq[StickerDto]): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method getInstalledStickerPacks*(self: ServiceInterface): Table[int, StickerPackDto] {.base.} = + raise newException(ValueError, "No implementation available") + +method saveRecentStickers*(self: ServiceInterface, installedStickerPacks: Table[int, StickerPackDto]): bool {.base.} = + raise newException(ValueError, "No implementation available") + + method saveNodeConfiguration*(self: ServiceInterface, value: JsonNode): bool {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app_service/service/stickers/async_tasks.nim b/src/app_service/service/stickers/async_tasks.nim new file mode 100644 index 0000000000..edba908656 --- /dev/null +++ b/src/app_service/service/stickers/async_tasks.nim @@ -0,0 +1,133 @@ +include ../../common/json_utils +include ../../../app/core/tasks/common + +# type +# EstimateTaskArg = ref object of QObjectTaskArg +# packId: int +# address: string +# price: string +# uuid: string +# ObtainAvailableStickerPacksTaskArg = ref object of QObjectTaskArg +# running*: ByteAddress # pointer to threadpool's `.running` Atomic[bool] +# contract*: ContractDto + +type + # EstimateTaskArg = ref object of QObjectTaskArg + # packId: int + # address: string + # price: string + # uuid: string + EstimateTaskArg = ref object of QObjectTaskArg + data: JsonNode + uuid: string + # tx: TransactionDataDto + # approveAndCall: ApproveAndCall[100] + # sntContract: Erc20ContractDto + ObtainAvailableStickerPacksTaskArg = ref object of QObjectTaskArg + running*: ByteAddress # pointer to threadpool's `.running` Atomic[bool] + contract*: ContractDto + packCountMethod*: MethodDto + getPackDataMethod*: MethodDto + + +proc getPackCount*(contract: ContractDto, packCountMethod: MethodDto): RpcResponse[JsonNode] = + status_stickers.getPackCount($contract.address, packCountMethod.encodeAbi()) + +proc getPackData*(contract: ContractDto, getPackDataMethod: MethodDto, id: Stuint[256], running: var Atomic[bool]): StickerPackDto = + let secureSSLContext = newContext() + let client = newHttpClient(sslContext = secureSSLContext) + try: + let + contractMethod = getPackDataMethod + getPackData = GetPackData(packId: id) + payload = %* [{ + "to": $contract.address, + "data": contractMethod.encodeAbi(getPackData) + }, "latest"] + let response = eth.doEthCall(payload) + if not response.error.isNil: + raise newException(RpcException, "Error getting sticker pack data: " & response.error.message) + + let packData = decodeContractResponse[PackData](response.result.getStr) + + if not running.load(): + trace "Sticker pack task interrupted, exiting sticker pack loading" + return + + # contract response includes a contenthash, which needs to be decoded to reveal + # an IPFS identifier. Once decoded, download the content from IPFS. This content + # is in EDN format, ie https://ipfs.infura.io/ipfs/QmWVVLwVKCwkVNjYJrRzQWREVvEk917PhbHYAUhA1gECTM + # and it also needs to be decoded in to a nim type + let contentHash = toHex(packData.contentHash) + let url = "https://ipfs.infura.io/ipfs/" & decodeContentHash(contentHash) + var ednMeta = client.getContent(url) + + # decode the EDN content in to a StickerPackDto + result = edn_helper.ednDecode[StickerPackDto](ednMeta) + # EDN doesn't include a packId for each sticker, so add it here + result.stickers.apply(proc(sticker: var StickerDto) = + sticker.packId = truncate(id, int)) + result.id = truncate(id, int) + result.price = packData.price + except Exception as e: + raise newException(RpcException, "Error getting sticker pack data: " & e.msg) + finally: + client.close() + +proc getAvailableStickerPacks*( + contract: ContractDto, + getPackCount: MethodDto, + getPackDataMethod: MethodDto, + running: var Atomic[bool] + ): Table[int, StickerPackDto] = + + var availableStickerPacks = initTable[int, StickerPackDto]() + try: + let numPacksReponse = getPackCount(contract, getPackCount) + + var numPacks = 0 + if numPacksReponse.result.getStr != "0x": + numPacks = parseHexInt(numPacksReponse.result.getStr) + + for i in 0.. 24: + self.recentStickers = self.recentStickers[0..23] # take top 24 most recent + if save: + discard self.settingsService.saveRecentStickers(self.recentStickers) + + proc getPackIdForSticker*(packs: Table[int, StickerPackDto], hash: string): int = + for packId, pack in packs.pairs: + if pack.stickers.any(proc(sticker: StickerDto): bool = return sticker.hash == hash): + return packId + return 0 + + proc getRecentStickers*(self: Service): seq[StickerDto] = + # TODO: this should be a custom `readValue` implementation of nim-json-serialization + let recentStickers = self.settingsService.getRecentStickers() + let installedStickers = self.getInstalledStickerPacks() + var stickers = newSeq[StickerDto]() + for hash in recentStickers: + # pack id is not returned from status-go settings, populate here + let packId = getPackIdForSticker(installedStickers, $hash) + # .insert instead of .add to effectively reverse the order stickers because + # stickers are re-reversed when added to the view due to the nature of + # inserting recent stickers at the front of the list + stickers.insert(StickerDto(hash: $hash, packId: packId), 0) + + for sticker in stickers: + self.addStickerToRecent(sticker) + + result = self.recentStickers + + proc getNumInstalledStickerPacks*(self: Service): int = + return self.settingsService.getInstalledStickerPacks().len + + proc installStickerPack*(self: Service, packId: int) = + if not self.availableStickerPacks.hasKey(packId): + return + let pack = self.availableStickerPacks[packId] + var installedStickerPacks = self.settingsService.getInstalledStickerPacks() + installedStickerPacks[packId] = pack + + discard self.settingsService.saveRecentStickers(installedStickerPacks) + + proc uninstallStickerPack*(self: Service, packId: int) = + var installedStickerPacks = self.settingsService.getInstalledStickerPacks() + if not installedStickerPacks.hasKey(packId): + return + installedStickerPacks.del(packId) + + discard self.settingsService.saveRecentStickers(installedStickerPacks) + + proc sendSticker*(self: Service, chatId: string, replyTo: string, sticker: StickerDto) = + let stickerToSend = Sticker( + hash: sticker.hash, + packId: sticker.packId + ) + # TODO change this to the new chat service call once it is available + var response = status_chat.sendStickerMessage(chatId, replyTo, stickerToSend) + self.addStickerToRecent(sticker, true) + var (chats, messages) = self.chatService.parseChatResponse(response) + # TODO change this event when the chat is refactored + self.events.emit("chatUpdate", ChatUpdateArgs(messages: messages, chats: chats)) + + proc removeRecentStickers*(self: Service, packId: int) = + self.recentStickers.keepItIf(it.packId != packId) + discard self.settingsService.saveRecentStickers(self.recentStickers) diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index 6a3c730d89..125c725e3a 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -1,5 +1,6 @@ import NimQml, chronicles, sequtils, sugar, stint, json import status/statusgo_backend_new/transactions as transactions +import status/statusgo_backend/wallet import eventemitter @@ -52,6 +53,9 @@ QtObject: error "error: ", errDesription return + proc getPendingTransactions*(self: Service): string = + wallet.getPendingTransactions() + proc getTransfersByAddress*(self: Service, address: string, toBlock: Uint256, limit: int, loadMore: bool = false): seq[TransactionDto] = try: let response = transactions.getTransfersByAddress(address, toBlock, limit, loadMore) diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index 3ef590696d..dca94979f2 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -28,7 +28,7 @@ StatusAppThreePanelLayout { property var store // Not Refactored -// property var messageStore + property var messageStore // Not Refactored property RootStore rootStore: RootStore { diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index f4749b1b89..fdbfb43daf 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -38,6 +38,7 @@ QtObject { } } property var contactsModuleInst: contactsModule + property var stickersModuleInst: stickersModule property var activeCommunity: chatsModelInst.communities.activeCommunity diff --git a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml index c08347ea2e..61dd27a37e 100644 --- a/ui/app/AppLayouts/Chat/views/ChatColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatColumnView.qml @@ -346,8 +346,8 @@ Item { //% "Type a message." qsTrId("type-a-message-") anchors.bottom: parent.bottom - recentStickers: root.rootStore.chatsModelInst.stickers.recent - stickerPackList: root.rootStore.chatsModelInst.stickers.stickerPacks + recentStickers: root.rootStore.stickersModuleInst.recent + stickerPackList: root.rootStore.stickersModuleInst.stickerPacks chatType: root.rootStore.chatsModelInst.channelView.activeChannel.chatType onSendTransactionCommandButtonClicked: { if (root.rootStore.chatsModelInst.channelView.activeChannel.ensVerified) { @@ -362,7 +362,10 @@ Item { txModalLoader.item.open() } onStickerSelected: { - root.rootStore.chatsModelInst.stickers.send(hashId, chatInput.isReply ? SelectedMessage.messageId : "", packId) + root.rootStore.stickersModuleInst.send(root.rootStore.chatsModelInst.channelView.activeChannel.id, + hashId, + chatInput.isReply ? SelectedMessage.messageId : "", + packId) } onSendMessage: { if (chatInput.fileUrls.length > 0){ diff --git a/ui/imports/shared/status/StatusStickerMarket.qml b/ui/imports/shared/status/StatusStickerMarket.qml index c57a5548fa..342676cd87 100644 --- a/ui/imports/shared/status/StatusStickerMarket.qml +++ b/ui/imports/shared/status/StatusStickerMarket.qml @@ -101,10 +101,10 @@ Item { assetPrice: price estimateGasFunction: function(selectedAccount, uuid) { if (packId < 0 || !selectedAccount || !price) return 325000 - return chatsModel.stickers.estimate(packId, selectedAccount.address, price, uuid) + return stickersModule.estimate(packId, selectedAccount.address, price, uuid) } onSendTransaction: function(selectedAddress, gasLimit, gasPrice, tipLimit, overallLimit, password) { - return chatsModel.stickers.buy(packId, + return stickersModule.buy(packId, selectedAddress, price, gasLimit, @@ -122,7 +122,7 @@ Item { } Connections { - target: chatsModel.stickers + target: stickersModule onGasEstimateReturned: { stickerPurchasePopup.setAsyncGasLimitResult(uuid, estimate) } diff --git a/ui/imports/shared/status/StatusStickerPackClickPopup.qml b/ui/imports/shared/status/StatusStickerPackClickPopup.qml index 5b0f0b18b2..bfa8391811 100644 --- a/ui/imports/shared/status/StatusStickerPackClickPopup.qml +++ b/ui/imports/shared/status/StatusStickerPackClickPopup.qml @@ -26,16 +26,16 @@ ModalPopup { property var stickers; Component.onCompleted: { - const idx = chatsModel.stickers.stickerPacks.findIndexById(packId, false); + const idx = stickersModule.stickerPacks.findIndexById(packId, false); if(idx === -1) close(); - name = chatsModel.stickers.stickerPacks.rowData(idx, "name") - author = chatsModel.stickers.stickerPacks.rowData(idx, "author") - thumbnail = chatsModel.stickers.stickerPacks.rowData(idx, "thumbnail") - price = chatsModel.stickers.stickerPacks.rowData(idx, "price") - stickers = chatsModel.stickers.stickerPacks.getStickers() - installed = chatsModel.stickers.stickerPacks.rowData(idx, "installed") === "true" - bought = chatsModel.stickers.stickerPacks.rowData(idx, "bought") === "true" - pending = chatsModel.stickers.stickerPacks.rowData(idx, "pending") === "true" + name = stickersModule.stickerPacks.rowData(idx, "name") + author = stickersModule.stickerPacks.rowData(idx, "author") + thumbnail = stickersModule.stickerPacks.rowData(idx, "thumbnail") + price = stickersModule.stickerPacks.rowData(idx, "price") + stickers = stickersModule.stickerPacks.getStickers() + installed = stickersModule.stickerPacks.rowData(idx, "installed") === "true" + bought = stickersModule.stickerPacks.rowData(idx, "bought") === "true" + pending = stickersModule.stickerPacks.rowData(idx, "pending") === "true" } height: 472 @@ -62,10 +62,10 @@ ModalPopup { assetPrice: price estimateGasFunction: function(selectedAccount, uuid) { if (packId < 0 || !selectedAccount || !price) return 325000 - return chatsModel.stickers.estimate(packId, selectedAccount.address, price, uuid) + return stickersModule.estimate(packId, selectedAccount.address, price, uuid) } onSendTransaction: function(selectedAddress, gasLimit, gasPrice, tipLimit, overallLimit, password) { - return chatsModel.stickers.buy(packId, + return stickersModule.buy(packId, selectedAddress, price, gasLimit, @@ -92,11 +92,11 @@ ModalPopup { isBought: bought isPending: pending onInstallClicked: { - chatsModel.stickers.install(packId); + stickersModule.install(packId); stickerPackDetailsPopup.close(); } onUninstallClicked: { - chatsModel.stickers.uninstall(packId); + stickersModule.uninstall(packId); stickerPackDetailsPopup.close(); } onCancelClicked: function(){} diff --git a/ui/imports/shared/status/StatusStickersPopup.qml b/ui/imports/shared/status/StatusStickersPopup.qml index fbcfa2baa1..a8abae6b90 100644 --- a/ui/imports/shared/status/StatusStickersPopup.qml +++ b/ui/imports/shared/status/StatusStickersPopup.qml @@ -16,7 +16,7 @@ Popup { property var recentStickers: StickerData {} property var stickerPackList: StickerPackData {} signal stickerSelected(string hashId, string packId) - property int installedPacksCount: chatsModel.stickers.numInstalledStickerPacks + property int installedPacksCount: stickersModule.numInstalledStickerPacks property bool stickerPacksLoaded: false width: 360 height: 440 @@ -58,12 +58,12 @@ Popup { Layout.fillHeight: true stickerPacks: stickerPackList onInstallClicked: { - chatsModel.stickers.install(packId) + stickersModule.install(packId) stickerGrid.model = stickers stickerPackListView.itemAt(index).clicked() } onUninstallClicked: { - chatsModel.stickers.uninstall(packId) + stickersModule.uninstall(packId) stickerGrid.model = recentStickers btnHistory.clicked() } @@ -127,7 +127,7 @@ Popup { StyledText { id: lblNoRecentStickers - visible: stickerPackListView.selectedPackId === -1 && chatsModel.stickers.recent.rowCount() === 0 && !lblNoStickersYet.visible + visible: stickerPackListView.selectedPackId === -1 && stickersModule.recent.rowCount() === 0 && !lblNoStickersYet.visible anchors.fill: parent font.pixelSize: 15 //% "Recently used stickers will appear here" @@ -171,7 +171,7 @@ Popup { Loader { id: loadingGrid - active: chatsModel.stickers.recent.rowCount() === 0 + active: stickersModule.recent.rowCount() === 0 sourceComponent: loadingImageComponent anchors.centerIn: parent } @@ -268,13 +268,13 @@ Popup { } } Connections { - target: chatsModel.stickers + target: stickersModule onStickerPacksLoaded: { root.stickerPacksLoaded = true stickerPackListView.visible = true loadingGrid.active = false loadingStickerPackListView.model = [] - noStickerPacks.visible = installedPacksCount === 0 || chatsModel.stickers.recent.rowCount() === 0 + noStickerPacks.visible = installedPacksCount === 0 || stickersModule.recent.rowCount() === 0 } } }