refactor(chat): move stickers to the new architecture

Fixes #4060
This commit is contained in:
Jonathan Rainville 2021-11-24 11:49:18 -05:00 committed by Sale Djenic
parent 63b6b01b68
commit 792397987c
40 changed files with 3206 additions and 112 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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()

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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 = ""

View File

@ -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)

View File

@ -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)

View File

@ -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 =
@ -53,3 +56,19 @@ method getChatById*(self: Service, chatId: string): ChatDto =
method prettyChatName*(self: Service, chatId: string): string =
let contact = self.contactService.getContactById(chatId)
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)

View File

@ -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,7 +17,7 @@ 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.} =
@ -23,3 +25,6 @@ method getChatById*(self: ServiceInterface, chatId: string): ChatDto {.base.} =
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")

View File

@ -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)

View File

@ -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)

View File

@ -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..<node.vec.len:
var sticker: StickerDto = StickerDto()
let child = node.vec[i]
if child.kind == EdnMap:
for k, v in sticker.fieldPairs:
v = parseMap[v.type](child.map, k)
result.add(sticker)
proc parseMap[T](map: HMap, searchName: string): T =
for iBucket in 0..<map.buckets.len:
let bucket = map.buckets[iBucket]
if bucket.len > 0:
for iChild in 0..<bucket.len:
let child = bucket[iChild]
let isRoot = child.key.kind == EdnSymbol and child.key.symbol.name == "meta"
if child.key.kind != EdnKeyword and not isRoot:
continue
if isRoot or child.key.keyword.name == searchName:
if child.value.kind == EdnMap:
result = parseMap[T](child.value.map, searchName)
break
elif child.value.kind == EdnVector:
when T is seq[StickerDto]:
result = parseVector[T](child.value, searchName)
break
result = getValueFromNode[T](child.value)
break
proc parseNode[T](node: EdnNode, searchName: string): T =
if node.kind == EdnMap:
result = parseMap[T](node.map, searchName)
else:
result = getValueFromNode[T](node)
proc ednDecode*[T](node: EdnNode): T =
result = T()
for k, v in result.fieldPairs:
v = parseNode[v.type](node, k)
proc ednDecode*[T](edn: string): T =
ednDecode[T](read(edn))

View File

@ -0,0 +1,110 @@
import
strutils, options, json
import
nimcrypto, web3/[encoding, ethtypes]
import status/statusgo_backend_new/eth as status_eth
import
status/types/rpc_response,
status/statusgo_backend/eth
import
./transaction,
./coder
type MethodDto* = object
name*: string
signature*: string
const ERC20_METHODS* = @[
("name", MethodDto(signature: "name()")),
("symbol", MethodDto(signature: "symbol()")),
("decimals", MethodDto(signature: "decimals()")),
("totalSupply", MethodDto(signature: "totalSupply()")),
("balanceOf", MethodDto(signature: "balanceOf(address)")),
("transfer", MethodDto(signature: "transfer(address,uint256)")),
("allowance", MethodDto(signature: "allowance(address,address)")),
("approve", MethodDto(signature: "approve(address,uint256)")),
("transferFrom", MethodDto(signature: "approve(address,address,uint256)")),
("increaseAllowance", MethodDto(signature: "increaseAllowance(address,uint256)")),
("decreaseAllowance", MethodDto(signature: "decreaseAllowance(address,uint256)")),
("approveAndCall", MethodDto(signature: "approveAndCall(address,uint256,bytes)"))
]
const ERC721_ENUMERABLE_METHODS* = @[
("balanceOf", MethodDto(signature: "balanceOf(address)")),
("ownerOf", MethodDto(signature: "ownerOf(uint256)")),
("name", MethodDto(signature: "name()")),
("symbol", MethodDto(signature: "symbol()")),
("tokenURI", MethodDto(signature: "tokenURI(uint256)")),
("baseURI", MethodDto(signature: "baseURI()")),
("tokenOfOwnerByIndex", MethodDto(signature: "tokenOfOwnerByIndex(address,uint256)")),
("totalSupply", MethodDto(signature: "totalSupply()")),
("tokenByIndex", MethodDto(signature: "tokenByIndex(uint256)")),
("approve", MethodDto(signature: "approve(address,uint256)")),
("getApproved", MethodDto(signature: "getApproved(uint256)")),
("setApprovalForAll", MethodDto(signature: "setApprovalForAll(address,bool)")),
("isApprovedForAll", MethodDto(signature: "isApprovedForAll(address,address)")),
("transferFrom", MethodDto(signature: "transferFrom(address,address,uint256)")),
("safeTransferFrom", MethodDto(signature: "safeTransferFrom(address,address,uint256)")),
("safeTransferFromWithData", MethodDto(signature: "safeTransferFrom(address,address,uint256,bytes)"))
]
proc encodeMethod(self: MethodDto): string =
($nimcrypto.keccak256.digest(self.signature))[0..<8].toLower
proc encodeAbi*(self: MethodDto, obj: object = RootObj()): string =
result = "0x" & self.encodeMethod()
# .fields is an iterator, and there's no way to get a count of an iterator
# in nim, so we have to loop and increment a counter
var fieldCount = 0
for i in obj.fields:
fieldCount += 1
var
offset = 32*fieldCount
data = ""
for field in obj.fields:
let encoded = encode(field)
if encoded.dynamic:
result &= offset.toHex(64).toLower
data &= encoded.data
offset += encoded.data.len
else:
result &= encoded.data
result &= data
proc estimateGas*(self: MethodDto, tx: var TransactionDataDto, methodDescriptor: object, success: var bool): string =
success = true
tx.data = self.encodeAbi(methodDescriptor)
try:
let response = eth.estimateGas(%*[%tx])
result = response.result # gas estimate in hex
except RpcException as e:
success = false
result = e.msg
proc getEstimateGasData*(self: MethodDto, tx: var TransactionDataDto, methodDescriptor: object): JsonNode =
tx.data = self.encodeAbi(methodDescriptor)
return %*[%tx]
proc send*(self: MethodDto, tx: var TransactionDataDto, methodDescriptor: object, password: string, success: var bool): string =
tx.data = self.encodeAbi(methodDescriptor)
let response = status_eth.sendTransaction($(%tx), password)
return $response.result
proc call*[T](self: MethodDto, tx: var TransactionDataDto, methodDescriptor: object, success: var bool): T =
success = true
tx.data = self.encodeAbi(methodDescriptor)
let response: RpcResponse
try:
response = eth.call(tx)
except RpcException as e:
success = false
result = e.msg
result = coder.decodeContractResponse[T](response.result)

View File

@ -0,0 +1,40 @@
from json import JsonNode, `%*`, parseJson
from strformat import fmt
import json_serialization
include ../../../common/json_utils
const Mainnet = 1
const Ropsten = 3
const Rinkeby = 4
const Goerli = 5
const Optimism = 10
const Poa = 99
const XDai = 100
export Mainnet, Ropsten, Rinkeby, Goerli, Optimism, Poa, XDai
type Network* = 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: Network): string =
return fmt"Network(chainId:{self.chainId}, name:{self.chainName}, rpcURL:{self.rpcURL}, isTest:{self.isTest}, enabled:{self.enabled})"
proc toPayload*(self: Network): JsonNode =
return %* [Json.encode(self).parseJson]
proc sntSymbol*(self: Network): string =
if self.chainId == Mainnet:
return "SNT"
else:
return "STT"

View File

@ -0,0 +1,75 @@
import strutils, json
import web3/ethtypes, web3/conversions, options, stint
type
TransactionDto* = ref object
id*: string
typeValue*: string
address*: string
blockNumber*: string
blockHash*: string
contract*: string
timestamp*: string
gasPrice*: string
gasLimit*: string
gasUsed*: string
nonce*: string
txStatus*: string
value*: string
fromAddress*: string
to*: string
type PendingTransactionTypeDto* {.pure.} = enum
RegisterENS = "RegisterENS",
SetPubKey = "SetPubKey",
ReleaseENS = "ReleaseENS",
BuyStickerPack = "BuyStickerPack"
WalletTransfer = "WalletTransfer"
type
TransactionDataDto* = object
source*: Address # the address the transaction is send from.
to*: Option[Address] # (optional when creating new contract) the address the transaction is directed to.
gas*: Option[Quantity] # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas.
gasPrice*: Option[int] # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas.
maxPriorityFeePerGas*: Option[Uint256]
maxFeePerGas*: Option[Uint256]
value*: Option[Uint256] # (optional) integer of the value sent with this transaction.
data*: string # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI.
nonce*: Option[Nonce] # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce
txType*: string
proc cmpTransactions*(x, y: TransactionDto): int =
# Sort proc to compare transactions from a single account.
# Compares first by block number, then by nonce
result = cmp(x.blockNumber.parseHexInt, y.blockNumber.parseHexInt)
if result == 0:
result = cmp(x.nonce, y.nonce)
# TODO: make this public in nim-web3 lib
template stripLeadingZeros*(value: string): string =
var cidx = 0
# ignore the last character so we retain '0' on zero value
while cidx < value.len - 1 and value[cidx] == '0':
cidx.inc
value[cidx .. ^1]
proc `%`*(x: TransactionDataDto): JsonNode =
result = newJobject()
result["from"] = %x.source
result["type"] = %x.txType
if x.to.isSome:
result["to"] = %x.to.unsafeGet
if x.gas.isSome:
result["gas"] = %x.gas.unsafeGet
if x.gasPrice.isSome:
result["gasPrice"] = %("0x" & x.gasPrice.unsafeGet.toHex.stripLeadingZeros)
if x.maxFeePerGas.isSome:
result["maxFeePerGas"] = %("0x" & x.maxFeePerGas.unsafeGet.toHex)
if x.maxPriorityFeePerGas.isSome:
result["maxPriorityFeePerGas"] = %("0x" & x.maxPriorityFeePerGas.unsafeGet.toHex)
if x.value.isSome:
result["value"] = %("0x" & x.value.unsafeGet.toHex)
result["data"] = %x.data
if x.nonce.isSome:
result["nonce"] = %x.nonce.unsafeGet

View File

@ -0,0 +1,278 @@
import json, sequtils, chronicles, macros, sugar, strutils, stint
import
web3/ethtypes, json_serialization, chronicles, tables
import status/statusgo_backend_new/eth
# TODO remove those
import status/utils
import ./dto/contract
import ./dto/method_dto
import ./dto/network
import ./dto/coder
import ./dto/edn_dto
import ./dto/transaction
import ./service_interface
export service_interface
export coder
export edn_dto
export contract
export method_dto
export transaction
logScope:
topics = "eth-service"
type
Service* = ref object of service_interface.ServiceInterface
contracts: seq[ContractDto]
method delete*(self: Service) =
discard
proc newService*(): Service =
result = Service()
result.contracts = @[]
# Forward declaration
method initContracts(self: Service)
method init*(self: Service) =
self.initContracts()
method initContracts(self: Service) =
self.contracts = @[
# Mainnet contracts
newErc20Contract("Status Network Token", Mainnet, parseAddress("0x744d70fdbe2ba4cf95131626614a1763df805b9e"), "SNT", 18, true),
newErc20Contract("Dai Stablecoin", Mainnet, parseAddress("0x6b175474e89094c44da98b954eedeac495271d0f"), "DAI", 18, true),
newErc20Contract("Sai Stablecoin v1.0", Mainnet, parseAddress("0x89d24a6b4ccb1b6faa2625fe562bdd9a23260359"), "SAI", 18, true),
newErc20Contract("MKR", Mainnet, parseAddress("0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2"), "MKR", 18, true),
newErc20Contract("EOS", Mainnet, parseAddress("0x86fa049857e0209aa7d9e616f7eb3b3b78ecfdb0"), "EOS", 18, true),
newErc20Contract("OMGToken", Mainnet, parseAddress("0xd26114cd6ee289accf82350c8d8487fedb8a0c07"), "OMG", 18, true),
newErc20Contract("Populous Platform", Mainnet, parseAddress("0xd4fa1460f537bb9085d22c7bccb5dd450ef28e3a"), "PPT", 8, true),
newErc20Contract("Reputation", Mainnet, parseAddress("0x1985365e9f78359a9b6ad760e32412f4a445e862"), "REP", 18, true),
newErc20Contract("PowerLedger", Mainnet, parseAddress("0x595832f8fc6bf59c85c527fec3740a1b7a361269"), "POWR", 6, true),
newErc20Contract("TenX Pay Token", Mainnet, parseAddress("0xb97048628db6b661d4c2aa833e95dbe1a905b280"), "PAY", 18, true),
newErc20Contract("Veros", Mainnet, parseAddress("0x92e78dae1315067a8819efd6dca432de9dcde2e9"), "VRS", 6, false),
newErc20Contract("Golem Network Token", Mainnet, parseAddress("0xa74476443119a942de498590fe1f2454d7d4ac0d"), "GNT", 18, true),
newErc20Contract("Salt", Mainnet, parseAddress("0x4156d3342d5c385a87d264f90653733592000581"), "SALT", 8, true),
newErc20Contract("BNB", Mainnet, parseAddress("0xb8c77482e45f1f44de1745f52c74426c631bdd52"), "BNB", 18, true),
newErc20Contract("Basic Attention Token", Mainnet, parseAddress("0x0d8775f648430679a709e98d2b0cb6250d2887ef"), "BAT", 18, true),
newErc20Contract("Kyber Network Crystal", Mainnet, parseAddress("0xdd974d5c2e2928dea5f71b9825b8b646686bd200"), "KNC", 18, true),
newErc20Contract("BTU Protocol", Mainnet, parseAddress("0xb683D83a532e2Cb7DFa5275eED3698436371cc9f"), "BTU", 18, true),
newErc20Contract("Digix DAO", Mainnet, parseAddress("0xe0b7927c4af23765cb51314a0e0521a9645f0e2a"), "DGD", 9, true),
newErc20Contract("Aeternity", Mainnet, parseAddress("0x5ca9a71b1d01849c0a95490cc00559717fcf0d1d"), "AE", 18, true),
newErc20Contract("Tronix", Mainnet, parseAddress("0xf230b790e05390fc8295f4d3f60332c93bed42e2"), "TRX", 6, true),
newErc20Contract("Ethos", Mainnet, parseAddress("0x5af2be193a6abca9c8817001f45744777db30756"), "ETHOS", 8, true),
newErc20Contract("Raiden Token", Mainnet, parseAddress("0x255aa6df07540cb5d3d297f0d0d4d84cb52bc8e6"), "RDN", 18, true),
newErc20Contract("SingularDTV", Mainnet, parseAddress("0xaec2e87e0a235266d9c5adc9deb4b2e29b54d009"), "SNGLS", 0, true),
newErc20Contract("Gnosis Token", Mainnet, parseAddress("0x6810e776880c02933d47db1b9fc05908e5386b96"), "GNO", 18, true),
newErc20Contract("StorjToken", Mainnet, parseAddress("0xb64ef51c888972c908cfacf59b47c1afbc0ab8ac"), "STORJ", 8, true),
newErc20Contract("AdEx", Mainnet, parseAddress("0x4470bb87d77b963a013db939be332f927f2b992e"), "ADX", 4, false),
newErc20Contract("FunFair", Mainnet, parseAddress("0x419d0d8bdd9af5e606ae2232ed285aff190e711b"), "FUN", 8, true),
newErc20Contract("Civic", Mainnet, parseAddress("0x41e5560054824ea6b0732e656e3ad64e20e94e45"), "CVC", 8, true),
newErc20Contract("ICONOMI", Mainnet, parseAddress("0x888666ca69e0f178ded6d75b5726cee99a87d698"), "ICN", 18, true),
newErc20Contract("Walton Token", Mainnet, parseAddress("0xb7cb1c96db6b22b0d3d9536e0108d062bd488f74"), "WTC", 18, true),
newErc20Contract("Bytom", Mainnet, parseAddress("0xcb97e65f07da24d46bcdd078ebebd7c6e6e3d750"), "BTM", 8, true),
newErc20Contract("0x Protocol Token", Mainnet, parseAddress("0xe41d2489571d322189246dafa5ebde1f4699f498"), "ZRX", 18, true),
newErc20Contract("Bancor Network Token", Mainnet, parseAddress("0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c"), "BNT", 18, true),
newErc20Contract("Metal", Mainnet, parseAddress("0xf433089366899d83a9f26a773d59ec7ecf30355e"), "MTL", 8, false),
newErc20Contract("PayPie", Mainnet, parseAddress("0xc42209accc14029c1012fb5680d95fbd6036e2a0"), "PPP", 18, true),
newErc20Contract("ChainLink Token", Mainnet, parseAddress("0x514910771af9ca656af840dff83e8264ecf986ca"), "LINK", 18, true),
newErc20Contract("Kin", Mainnet, parseAddress("0x818fc6c2ec5986bc6e2cbf00939d90556ab12ce5"), "KIN", 18, true),
newErc20Contract("Aragon Network Token", Mainnet, parseAddress("0x960b236a07cf122663c4303350609a66a7b288c0"), "ANT", 18, true),
newErc20Contract("MobileGo Token", Mainnet, parseAddress("0x40395044ac3c0c57051906da938b54bd6557f212"), "MGO", 8, true),
newErc20Contract("Monaco", Mainnet, parseAddress("0xb63b606ac810a52cca15e44bb630fd42d8d1d83d"), "MCO", 8, true),
newErc20Contract("loopring", Mainnet, parseAddress("0xef68e7c694f40c8202821edf525de3782458639f"), "LRC", 18, true),
newErc20Contract("Zeus Shield Coin", Mainnet, parseAddress("0x7a41e0517a5eca4fdbc7fbeba4d4c47b9ff6dc63"), "ZSC", 18, true),
newErc20Contract("Streamr DATAcoin", Mainnet, parseAddress("0x0cf0ee63788a0849fe5297f3407f701e122cc023"), "DATA", 18, true),
newErc20Contract("Ripio Credit Network Token", Mainnet, parseAddress("0xf970b8e36e23f7fc3fd752eea86f8be8d83375a6"), "RCN", 18, true),
newErc20Contract("WINGS", Mainnet, parseAddress("0x667088b212ce3d06a1b553a7221e1fd19000d9af"), "WINGS", 18, true),
newErc20Contract("Edgeless", Mainnet, parseAddress("0x08711d3b02c8758f2fb3ab4e80228418a7f8e39c"), "EDG", 0, true),
newErc20Contract("Melon Token", Mainnet, parseAddress("0xbeb9ef514a379b997e0798fdcc901ee474b6d9a1"), "MLN", 18, true),
newErc20Contract("Moeda Loyalty Points", Mainnet, parseAddress("0x51db5ad35c671a87207d88fc11d593ac0c8415bd"), "MDA", 18, true),
newErc20Contract("PILLAR", Mainnet, parseAddress("0xe3818504c1b32bf1557b16c238b2e01fd3149c17"), "PLR", 18, true),
newErc20Contract("QRL", Mainnet, parseAddress("0x697beac28b09e122c4332d163985e8a73121b97f"), "QRL", 8, true),
newErc20Contract("Modum Token", Mainnet, parseAddress("0x957c30ab0426e0c93cd8241e2c60392d08c6ac8e"), "MOD", 0, true),
newErc20Contract("Token-as-a-Service", Mainnet, parseAddress("0xe7775a6e9bcf904eb39da2b68c5efb4f9360e08c"), "TAAS", 6, true),
newErc20Contract("GRID Token", Mainnet, parseAddress("0x12b19d3e2ccc14da04fae33e63652ce469b3f2fd"), "GRID", 12, true),
newErc20Contract("SANtiment network token", Mainnet, parseAddress("0x7c5a0ce9267ed19b22f8cae653f198e3e8daf098"), "SAN", 18, true),
newErc20Contract("SONM Token", Mainnet, parseAddress("0x983f6d60db79ea8ca4eb9968c6aff8cfa04b3c63"), "SNM", 18, true),
newErc20Contract("Request Token", Mainnet, parseAddress("0x8f8221afbb33998d8584a2b05749ba73c37a938a"), "REQ", 18, true),
newErc20Contract("Substratum", Mainnet, parseAddress("0x12480e24eb5bec1a9d4369cab6a80cad3c0a377a"), "SUB", 2, true),
newErc20Contract("Decentraland MANA", Mainnet, parseAddress("0x0f5d2fb29fb7d3cfee444a200298f468908cc942"), "MANA", 18, true),
newErc20Contract("AirSwap Token", Mainnet, parseAddress("0x27054b13b1b798b345b591a4d22e6562d47ea75a"), "AST", 4, true),
newErc20Contract("R token", Mainnet, parseAddress("0x48f775efbe4f5ece6e0df2f7b5932df56823b990"), "R", 0, true),
newErc20Contract("FirstBlood Token", Mainnet, parseAddress("0xaf30d2a7e90d7dc361c8c4585e9bb7d2f6f15bc7"), "1ST", 18, true),
newErc20Contract("Cofoundit", Mainnet, parseAddress("0x12fef5e57bf45873cd9b62e9dbd7bfb99e32d73e"), "CFI", 18, true),
newErc20Contract("Enigma", Mainnet, parseAddress("0xf0ee6b27b759c9893ce4f094b49ad28fd15a23e4"), "ENG", 8, true),
newErc20Contract("Amber Token", Mainnet, parseAddress("0x4dc3643dbc642b72c158e7f3d2ff232df61cb6ce"), "AMB", 18, true),
newErc20Contract("XPlay Token", Mainnet, parseAddress("0x90528aeb3a2b736b780fd1b6c478bb7e1d643170"), "XPA", 18, true),
newErc20Contract("Open Trading Network", Mainnet, parseAddress("0x881ef48211982d01e2cb7092c915e647cd40d85c"), "OTN", 18, true),
newErc20Contract("Trustcoin", Mainnet, parseAddress("0xcb94be6f13a1182e4a4b6140cb7bf2025d28e41b"), "TRST", 6, true),
newErc20Contract("Monolith TKN", Mainnet, parseAddress("0xaaaf91d9b90df800df4f55c205fd6989c977e73a"), "TKN", 8, true),
newErc20Contract("RHOC", Mainnet, parseAddress("0x168296bb09e24a88805cb9c33356536b980d3fc5"), "RHOC", 8, true),
newErc20Contract("Target Coin", Mainnet, parseAddress("0xac3da587eac229c9896d919abc235ca4fd7f72c1"), "TGT", 1, false),
newErc20Contract("Everex", Mainnet, parseAddress("0xf3db5fa2c66b7af3eb0c0b782510816cbe4813b8"), "EVX", 4, true),
newErc20Contract("ICOS", Mainnet, parseAddress("0x014b50466590340d41307cc54dcee990c8d58aa8"), "ICOS", 6, true),
newErc20Contract("district0x Network Token", Mainnet, parseAddress("0x0abdace70d3790235af448c88547603b945604ea"), "DNT", 18, true),
newErc20Contract("Dentacoin", Mainnet, parseAddress("0x08d32b0da63e2c3bcf8019c9c5d849d7a9d791e6"), "٨", 0, false),
newErc20Contract("Eidoo Token", Mainnet, parseAddress("0xced4e93198734ddaff8492d525bd258d49eb388e"), "EDO", 18, true),
newErc20Contract("BitDice", Mainnet, parseAddress("0x29d75277ac7f0335b2165d0895e8725cbf658d73"), "CSNO", 8, false),
newErc20Contract("Cobinhood Token", Mainnet, parseAddress("0xb2f7eb1f2c37645be61d73953035360e768d81e6"), "COB", 18, true),
newErc20Contract("Enjin Coin", Mainnet, parseAddress("0xf629cbd94d3791c9250152bd8dfbdf380e2a3b9c"), "ENJ", 18, false),
newErc20Contract("AVENTUS", Mainnet, parseAddress("0x0d88ed6e74bbfd96b831231638b66c05571e824f"), "AVT", 18, false),
newErc20Contract("Chronobank TIME", Mainnet, parseAddress("0x6531f133e6deebe7f2dce5a0441aa7ef330b4e53"), "TIME", 8, false),
newErc20Contract("Cindicator Token", Mainnet, parseAddress("0xd4c435f5b09f855c3317c8524cb1f586e42795fa"), "CND", 18, true),
newErc20Contract("Stox", Mainnet, parseAddress("0x006bea43baa3f7a6f765f14f10a1a1b08334ef45"), "STX", 18, true),
newErc20Contract("Xaurum", Mainnet, parseAddress("0x4df812f6064def1e5e029f1ca858777cc98d2d81"), "XAUR", 8, true),
newErc20Contract("Vibe", Mainnet, parseAddress("0x2c974b2d0ba1716e644c1fc59982a89ddd2ff724"), "VIB", 18, true),
newErc20Contract("PRG", Mainnet, parseAddress("0x7728dfef5abd468669eb7f9b48a7f70a501ed29d"), "PRG", 6, false),
newErc20Contract("Delphy Token", Mainnet, parseAddress("0x6c2adc2073994fb2ccc5032cc2906fa221e9b391"), "DPY", 18, true),
newErc20Contract("CoinDash Token", Mainnet, parseAddress("0x2fe6ab85ebbf7776fee46d191ee4cea322cecf51"), "CDT", 18, true),
newErc20Contract("Tierion Network Token", Mainnet, parseAddress("0x08f5a9235b08173b7569f83645d2c7fb55e8ccd8"), "TNT", 8, true),
newErc20Contract("DomRaiderToken", Mainnet, parseAddress("0x9af4f26941677c706cfecf6d3379ff01bb85d5ab"), "DRT", 8, true),
newErc20Contract("SPANK", Mainnet, parseAddress("0x42d6622dece394b54999fbd73d108123806f6a18"), "SPANK", 18, true),
newErc20Contract("Berlin Coin", Mainnet, parseAddress("0x80046305aaab08f6033b56a360c184391165dc2d"), "BRLN", 18, true),
newErc20Contract("USD//C", Mainnet, parseAddress("0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"), "USDC", 6, true),
newErc20Contract("Livepeer Token", Mainnet, parseAddress("0x58b6a8a3302369daec383334672404ee733ab239"), "LPT", 18, true),
newErc20Contract("Simple Token", Mainnet, parseAddress("0x2c4e8f2d746113d0696ce89b35f0d8bf88e0aeca"), "ST", 18, true),
newErc20Contract("Wrapped BTC", Mainnet, parseAddress("0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"), "WBTC", 8, true),
newErc20Contract("Bloom Token", Mainnet, parseAddress("0x107c4504cd79c5d2696ea0030a8dd4e92601b82e"), "BLT", 18, true),
newErc20Contract("Unisocks", Mainnet, parseAddress("0x23b608675a2b2fb1890d3abbd85c5775c51691d5"), "SOCKS", 18, true),
newErc20Contract("Hermez Network Token", Mainnet, parseAddress("0xEEF9f339514298C6A857EfCfC1A762aF84438dEE"), "HEZ", 18, true),
ContractDto(name: "stickers", chainId: Mainnet, address: parseAddress("0x0577215622f43a39f4bc9640806dfea9b10d2a36"),
methods: [
("packCount", MethodDto(signature: "packCount()")),
("getPackData", MethodDto(signature: "getPackData(uint256)"))
].toTable
),
ContractDto(name: "sticker-market", chainId: Mainnet, address: parseAddress("0x12824271339304d3a9f7e096e62a2a7e73b4a7e7"),
methods: [
("buyToken", MethodDto(signature: "buyToken(uint256,address,uint256)"))
].toTable
),
newErc721Contract("sticker-pack", Mainnet, parseAddress("0x110101156e8F0743948B2A61aFcf3994A8Fb172e"), "PACK", false, @[("tokenPackId", MethodDto(signature: "tokenPackId(uint256)"))]),
# Strikers seems dead. Their website doesn't work anymore
newErc721Contract("strikers", Mainnet, parseAddress("0xdcaad9fd9a74144d226dbf94ce6162ca9f09ed7e"), "STRK", true),
newErc721Contract("ethermon", Mainnet, parseAddress("0xb2c0782ae4a299f7358758b2d15da9bf29e1dd99"), "EMONA", true),
newErc721Contract("kudos", Mainnet, parseAddress("0x2aea4add166ebf38b63d09a75de1a7b94aa24163"), "KDO", true),
newErc721Contract("crypto-kitties", Mainnet, parseAddress("0x06012c8cf97bead5deae237070f9587f8e7a266d"), "CK", true),
ContractDto(name: "ens-usernames", chainId: Mainnet, address: parseAddress("0xDB5ac1a559b02E12F29fC0eC0e37Be8E046DEF49"),
methods: [
("register", MethodDto(signature: "register(bytes32,address,bytes32,bytes32)")),
("getPrice", MethodDto(signature: "getPrice()")),
("getExpirationTime", MethodDto(signature: "getExpirationTime(bytes32)")),
("release", MethodDto(signature: "release(bytes32)"))
].toTable
),
ContractDto(name: "ens-resolver", chainId: Mainnet, address: parseAddress("0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41"),
methods: [
("setPubkey", MethodDto(signature: "setPubkey(bytes32,bytes32,bytes32)"))
].toTable
),
# Testnet (Ropsten) contracts
newErc20Contract("Status Test Token", Ropsten, parseAddress("0xc55cf4b03948d7ebc8b9e8bad92643703811d162"), "STT", 18, true),
newErc20Contract("Handy Test Token", Ropsten, parseAddress("0xdee43a267e8726efd60c2e7d5b81552dcd4fa35c"), "HND", 0, false),
newErc20Contract("Lucky Test Token", Ropsten, parseAddress("0x703d7dc0bc8e314d65436adf985dda51e09ad43b"), "LXS", 2, false),
newErc20Contract("Adi Test Token", Ropsten, parseAddress("0xe639e24346d646e927f323558e6e0031bfc93581"), "ADI", 7, false),
newErc20Contract("Wagner Test Token", Ropsten, parseAddress("0x2e7cd05f437eb256f363417fd8f920e2efa77540"), "WGN", 10, false),
newErc20Contract("Modest Test Token", Ropsten, parseAddress("0x57cc9b83730e6d22b224e9dc3e370967b44a2de0"), "MDS", 18, false),
ContractDto(name: "tribute-to-talk", chainId: Ropsten, address: parseAddress("0xC61aa0287247a0398589a66fCD6146EC0F295432")),
ContractDto(name: "stickers", chainId: Ropsten, address: parseAddress("0x8cc272396be7583c65bee82cd7b743c69a87287d"),
methods: [
("packCount", MethodDto(signature: "packCount()")),
("getPackData", MethodDto(signature: "getPackData(uint256)"))
].toTable
),
ContractDto(name: "sticker-market", chainId: Ropsten, address: parseAddress("0x6CC7274aF9cE9572d22DFD8545Fb8c9C9Bcb48AD"),
methods: [
("buyToken", MethodDto(signature: "buyToken(uint256,address,uint256)"))
].toTable
),
newErc721Contract("sticker-pack", Ropsten, parseAddress("0xf852198d0385c4b871e0b91804ecd47c6ba97351"), "PACK", false, @[("tokenPackId", MethodDto(signature: "tokenPackId(uint256)"))]),
newErc721Contract("kudos", Ropsten, parseAddress("0xcd520707fc68d153283d518b29ada466f9091ea8"), "KDO", true),
ContractDto(name: "ens-usernames", chainId: Ropsten, address: parseAddress("0xdaae165beb8c06e0b7613168138ebba774aff071"),
methods: [
("register", MethodDto(signature: "register(bytes32,address,bytes32,bytes32)")),
("getPrice", MethodDto(signature: "getPrice()")),
("getExpirationTime", MethodDto(signature: "getExpirationTime(bytes32)")),
("release", MethodDto(signature: "release(bytes32)"))
].toTable
),
ContractDto(name: "ens-resolver", chainId: Ropsten, address: parseAddress("0x42D63ae25990889E35F215bC95884039Ba354115"),
methods: [
("setPubkey", MethodDto(signature: "setPubkey(bytes32,bytes32,bytes32)"))
].toTable
),
# Rinkeby contracts
newErc20Contract("Moksha Coin", Rinkeby, parseAddress("0x6ba7dc8dd10880ab83041e60c4ede52bb607864b"), "MOKSHA", 18, false),
newErc20Contract("WIBB", Rinkeby, parseAddress("0x7d4ccf6af2f0fdad48ee7958bcc28bdef7b732c7"), "WIBB", 18, false),
newErc20Contract("Status Test Token", Rinkeby, parseAddress("0x43d5adc3b49130a575ae6e4b00dfa4bc55c71621"), "STT", 18, false),
# xDai contracts
newErc20Contract("buffiDai", XDai, parseAddress("0x3e50bf6703fc132a94e4baff068db2055655f11b"), "BUFF", 18, false),
newErc20Contract("Uniswap", Mainnet, parseAddress("0x1f9840a85d5af5bf1d1762f925bdaddc4201f984"), "UNI", 18, true),
newErc20Contract("Compound", Mainnet, parseAddress("0xc00e94cb662c3520282e6f5717214004a7f26888"), "COMP", 18, true),
newErc20Contract("Balancer", Mainnet, parseAddress("0xba100000625a3754423978a60c9317c58a424e3d"), "BAL", 18, true),
newErc20Contract("Akropolis", Mainnet, parseAddress("0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7"), "AKRO", 18, true),
newErc20Contract("Orchid", Mainnet, parseAddress("0x4575f41308EC1483f3d399aa9a2826d74Da13Deb"), "OXT", 18, false),
]
method allContracts(self: Service): seq[ContractDto] =
result = self.contracts
method findByAddress*(self: Service, contracts: seq[Erc20ContractDto], address: Address): Erc20ContractDto =
let found = contracts.filter(contract => 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

View File

@ -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")

View File

@ -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"]

View File

@ -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..<value.len]
# The rest of the hash identifies the multihash algo, length, and digest
# More info: https://multiformats.io/multihash/
# 12 = identifies sha2-256 hash
# 20 = multihash length = 32
# ...rest = multihash digest
let multiHash = MultiHash.init(nimcrypto.fromHex(multiHashStr)).get()
let resultTyped = Cid.init(CIDv0, MultiCodec.codec(codec), multiHash).get()
result = $resultTyped
trace "Decoded sticker hash", cid=result
except Exception as e:
error "Error decoding sticker", hash=value, exception=e.msg
raise
proc getTimelineChatId*(pubKey: string = ""): string =
if pubKey == "":
return "@timeline70bd746ddcc12beb96b2c9d572d0784ab137ffc774f5383e50585a932080b57cca0484b259e61cecbaa33a4c98a300a"
else:
return "@" & pubKey
proc isWakuEnabled(): bool =
true # TODO:
proc prefix*(methodName: string, isExt:bool = true): string =
result = if isWakuEnabled(): "waku" else: "shh"
result = result & (if isExt: "ext_" else: "_")
result = result & methodName
proc isOneToOneChat*(chatId: string): bool =
result = chatId.startsWith("0x") # There is probably a better way to do this
proc keys*(obj: JsonNode): seq[string] =
result = newSeq[string]()
for k, _ in obj:
result.add k
proc generateSigningPhrase*(count: int): string =
let now = getTime()
var rng = initRand(now.toUnix * 1000000000 + now.nanosecond)
var phrases: seq[string] = @[]
for i in 1..count:
phrases.add(rng.sample(signing_phrases.phrases))
result = phrases.join(" ")
proc handleRPCErrors*(response: string) =
let parsedReponse = parseJson(response)
if (parsedReponse.hasKey("error")):
raise newException(ValueError, parsedReponse["error"]["message"].str)
proc toStUInt*[bits: static[int]](flt: float, T: typedesc[StUint[bits]]): T =
var stringValue = fmt"{flt:<.0f}"
stringValue.removeSuffix('.')
if (flt >= 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))

View File

@ -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"

View File

@ -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())

View File

@ -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")

View File

@ -7,3 +7,40 @@ const Poa = 99
const XDai = 100
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

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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..<numPacks:
if not running.load():
trace "Sticker pack task interrupted, exiting sticker pack loading"
break
try:
let stickerPack = getPackData(contract, getPackDataMethod, i.u256, running)
availableStickerPacks[stickerPack.id] = stickerPack
except:
continue
result = availableStickerPacks
except RpcException:
error "Error in getAvailableStickerPacks", message = getCurrentExceptionMsg()
result = initTable[int, StickerPackDto]()
# The pragmas `{.gcsafe, nimcall.}` in this context do not force the compiler
# to accept unsafe code, rather they work in conjunction with the proc
# signature for `type Task` in tasks/common.nim to ensure that the proc really
# is gcsafe and that a helpful error message is displayed
const estimateTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[EstimateTaskArg](argEncoded)
var success: bool
let response = eth.estimateGas(arg.data)
var estimate = 325000
if $response.result != "0x":
estimate = parseHexInt(response.result.getStr)
let tpl: tuple[estimate: int, uuid: string] = (estimate, arg.uuid)
arg.finish(tpl)
const obtainAvailableStickerPacksTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[ObtainAvailableStickerPacksTaskArg](argEncoded)
var running = cast[ptr Atomic[bool]](arg.running)
let availableStickerPacks = getAvailableStickerPacks(
arg.contract,
arg.packCountMethod,
arg.getPackDataMethod,
running[])
var packs: seq[StickerPackDto] = @[]
for packId, stickerPack in availableStickerPacks.pairs:
packs.add(stickerPack)
arg.finish(%*(packs))

View File

@ -0,0 +1,70 @@
{.used.}
import json, strformat, strutils, stint, json_serialization
include ../../../common/json_utils
include ../../../common/utils
type StickerDto* = object
hash*: string
packId*: int
type StickerPackDto* = object
id*: int
name*: string
author*: string
price*: Stuint[256]
preview*: string
stickers*: seq[StickerDto]
thumbnail*: string
proc `$`(self: StickerDto): string =
result = fmt"""StickerDto(
hash: {self.hash},
packId: {$self.packId},
]"""
proc `$`*(self: StickerPackDto): string =
result = fmt"""StickerPackDto(
id: {$self.id},
name: {self.name},
author: {self.author},
price: {$self.price},
preview: {self.preview},
stickersLen: {$self.stickers.len},
thumbnail:{self.thumbnail}
)"""
proc `%`*(stuint256: Stuint[256]): JsonNode =
newJString($stuint256)
proc readValue*(reader: var JsonReader, value: var Stuint[256])
{.raises: [IOError, SerializationError, Defect].} =
try:
let strVal = reader.readValue(string)
value = strVal.parse(Stuint[256])
except:
try:
let intVal = reader.readValue(int)
value = intVal.stuint(256)
except:
raise newException(SerializationError, "Expected string or int representation of Stuint[256]")
proc toStickerDto*(jsonObj: JsonNode): StickerDto =
result = StickerDto()
discard jsonObj.getProp("hash", result.hash)
discard jsonObj.getProp("packId", result.packId)
proc toStickerPackDto*(jsonObj: JsonNode): StickerPackDto =
result = StickerPackDto()
discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("author", result.author)
result.price = stint.fromHex(Stuint[256], jsonObj["price"].getStr)
discard jsonObj.getProp("thumbnail", result.thumbnail)
result.stickers = @[]
for sticker in jsonObj["stickers"]:
result.stickers.add(sticker.toStickerDto)

View File

@ -0,0 +1,393 @@
import NimQml, Tables, json, sequtils, chronicles, strutils, atomics, sets, strutils, tables, stint
import status/types/[sticker, network_type, setting, transaction, network]
import httpclient
import eventemitter
import ../../../app/core/[main]
import ../../../app/core/tasks/[qt, threadpool]
import
web3/ethtypes, web3/conversions, stew/byteutils, nimcrypto, json_serialization, chronicles
import json, tables, json_serialization
import status/statusgo_backend_new/stickers as status_stickers
import status/statusgo_backend_new/transactions as transactions
import status/statusgo_backend_new/response_type
import status/statusgo_backend_new/eth
import ./dto/stickers
import ../eth/service as eth_service
import ../settings/service as settings_service
import ../wallet_account/service as wallet_account_service
import ../transaction/service as transaction_service
import ../network/service as network_service
import ../chat/service as chat_service
import ../eth/utils as status_utils
import ../eth/dto/edn_dto as edn_helper
# TODO Remove those imports once chat is refactored
import status/statusgo_backend/chat as status_chat
export StickerDto
export StickerPackDto
include async_tasks
logScope:
topics = "stickers-service"
type
StickerPackLoadedArgs* = ref object of Args
stickerPack*: StickerPackDto
isInstalled*: bool
isBought*: bool
isPending*: bool
StickerGasEstimatedArgs* = ref object of Args
estimate*: int
uuid*: string
# Signals which may be emitted by this service:
const SIGNAL_STICKER_PACK_LOADED* = "SIGNAL_STICKER_PACK_LOADED"
const SIGNAL_ALL_STICKER_PACKS_LOADED* = "SIGNAL_ALL_STICKER_PACKS_LOADED"
const SIGNAL_STICKER_GAS_ESTIMATED* = "SIGNAL_STICKER_GAS_ESTIMATED"
QtObject:
type Service* = ref object of QObject
threadpool: ThreadPool
availableStickerPacks*: Table[int, StickerPackDto]
purchasedStickerPacks*: seq[int]
recentStickers*: seq[StickerDto]
events: EventEmitter
ethService: eth_service.Service
settingsService: settings_service.Service
walletAccountService: wallet_account_service.Service
transactionService: transaction_service.Service
networkService: network_service.Service
chatService: chat_service.Service
# Forward declaration
proc obtainAvailableStickerPacks*(self: Service)
proc delete*(self: Service) =
self.QObject.delete
proc newService*(
events: EventEmitter,
threadpool: ThreadPool,
ethService: eth_service.Service,
settingsService: settings_service.Service,
walletAccountService: wallet_account_service.Service,
transactionService: transaction_service.Service,
networkService: network_service.Service,
chatService: chat_service.Service
): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.ethService = ethService
result.settingsService = settingsService
result.walletAccountService = walletAccountService
result.transactionService = transactionService
result.networkService = networkService
result.chatService = chatService
result.availableStickerPacks = initTable[int, StickerPackDto]()
result.purchasedStickerPacks = @[]
result.recentStickers = @[]
proc init*(self: Service) =
self.obtainAvailableStickerPacks()
# TODO redo the connect check when the network is refactored
# if self.status.network.isConnected:
# self.obtainAvailableStickerPacks()
# else:
# let installedStickerPacks = self.getInstalledStickerPacks()
# self.delegate.populateInstalledStickerPacks(installedStickerPacks) # use emit instead
proc getInstalledStickerPacks*(self: Service): Table[int, StickerPackDto] =
self.settingsService.getInstalledStickerPacks()
proc buildTransaction(
self: Service,
packId: Uint256,
address: Address,
price: Uint256,
approveAndCall: var ApproveAndCall[100],
sntContract: var Erc20ContractDto,
gas = "",
gasPrice = "",
isEIP1559Enabled = false,
maxPriorityFeePerGas = "",
maxFeePerGas = ""
): TransactionDataDto =
let networkType = self.settingsService.getCurrentNetwork().toNetworkType()
let network = self.networkService.getNetwork(networkType)
sntContract = self.eth_service.findErc20Contract(network.chainId, network.sntSymbol())
let
stickerMktContract = self.eth_service.findContract(network.chainId, "sticker-market")
buyToken = BuyToken(packId: packId, address: address, price: price)
buyTxAbiEncoded = stickerMktContract.methods["buyToken"].encodeAbi(buyToken)
approveAndCall = ApproveAndCall[100](to: stickerMktContract.address, value: price, data: DynamicBytes[100].fromHex(buyTxAbiEncoded))
self.eth_service.buildTokenTransaction(address, sntContract.address, gas, gasPrice, isEIP1559Enabled, maxPriorityFeePerGas, maxFeePerGas)
proc buyPack*(self: Service, packId: int, address, price, gas, gasPrice: string, isEIP1559Enabled: bool, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string, success: var bool): string =
var
sntContract: Erc20ContractDto
approveAndCall: ApproveAndCall[100]
tx = self.buildTransaction(
packId.u256,
parseAddress(address),
status_utils.eth2Wei(parseFloat(price), 18), # SNT
approveAndCall,
sntContract,
gas,
gasPrice,
isEIP1559Enabled,
maxPriorityFeePerGas,
maxFeePerGas
)
result = sntContract.methods["approveAndCall"].send(tx, approveAndCall, password, success)
if success:
discard transactions.trackPendingTransaction(
result,
address,
$sntContract.address,
transaction.PendingTransactionType.BuyStickerPack,
$packId
)
proc buy*(self: Service, packId: int, address: string, price: string, gas: string, gasPrice: string, maxPriorityFeePerGas: string, maxFeePerGas: string, password: string): tuple[response: string, success: bool] =
let eip1559Enabled = self.settingsService.isEIP1559Enabled()
try:
status_utils.validateTransactionInput(address, address, "", price, gas, gasPrice, "", eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, "ok")
except Exception as e:
error "Error buying sticker pack", msg = e.msg
return (response: "", success: false)
var success: bool
let response = self.buyPack(packId, address, price, gas, gasPrice, eip1559Enabled, maxPriorityFeePerGas, maxFeePerGas, password, success)
result = (response: $response, success: success)
proc getPackIdFromTokenId*(self: Service, chainId: int, tokenId: Stuint[256]): RpcResponse[JsonNode] =
let
contract = self.eth_service.findContract(chainId, "sticker-pack")
tokenPackId = TokenPackId(tokenId: tokenId)
if contract == nil:
return
let abi = contract.methods["tokenPackId"].encodeAbi(tokenPackId)
return status_stickers.getPackIdFromTokenId($contract.address, abi)
proc tokenOfOwnerByIndex*(self: Service, chainId: int, address: Address, idx: Stuint[256]): RpcResponse[JsonNode] =
let
contract = self.eth_service.findContract(chainId, "sticker-pack")
if contract == nil:
return
let
tokenOfOwnerByIndex = TokenOfOwnerByIndex(address: address, index: idx)
data = contract.methods["tokenOfOwnerByIndex"].encodeAbi(tokenOfOwnerByIndex)
status_stickers.tokenOfOwnerByIndex($contract.address, data)
proc getBalance*(self: Service, chainId: int, address: Address): RpcResponse[JsonNode] =
let contract = self.eth_service.findContract(chainId, "sticker-pack")
if contract == nil: return
let balanceOf = BalanceOf(address: address)
let data = contract.methods["balanceOf"].encodeAbi(balanceOf)
return status_stickers.getBalance($contract.address, data)
proc getPurchasedStickerPacks*(self: Service, address: string): seq[int] =
try:
let addressObj = parseAddress(address)
let networkType = self.settingsService.getCurrentNetwork().toNetworkType()
let network = self.networkService.getNetwork(networkType)
let balanceRpcResponse = self.getBalance(network.chainId, addressObj)
var balance = 0
if $balanceRpcResponse.result != "0x":
balance = parseHexInt(balanceRpcResponse.result.getStr)
var tokenIds: seq[int] = @[]
for it in toSeq[0..<balance]:
let response = self.tokenOfOwnerByIndex(network.chainId, addressObj, it.u256)
var tokenId = 0
if $response.result != "0x":
tokenId = parseHexInt(response.result.getStr)
tokenIds.add(tokenId)
var purchasedPackIds: seq[int] = @[]
for tokenId in tokenIds:
let response = self.getPackIdFromTokenId(network.chainId, tokenId.u256)
var packId = 0
if $response.result != "0x":
packId = parseHexInt(response.result.getStr)
purchasedPackIds.add(packId)
self.purchasedStickerPacks = self.purchasedStickerPacks.concat(purchasedPackIds)
self.purchasedStickerPacks = self.purchasedStickerPacks.deduplicate()
result = self.purchasedStickerPacks
except RpcException:
error "Error getting purchased sticker packs", message = getCurrentExceptionMsg()
result = @[]
proc setAvailableStickerPacks*(self: Service, availableStickersJSON: string) {.slot.} =
let
accounts = self.walletAccountService.getWalletAccounts() # TODO: make generic
installedStickerPacks = self.getInstalledStickerPacks()
var
purchasedStickerPacks: seq[int]
for account in accounts:
purchasedStickerPacks = self.getPurchasedStickerPacks(account.address)
let availableStickers = JSON.decode($availableStickersJSON, seq[StickerPackDto])
let pendingTransactions = self.transactionService.getPendingTransactions()
var pendingStickerPacks = initHashSet[int]()
if (pendingTransactions != ""):
for trx in pendingTransactions.parseJson{"result"}.getElems():
if trx["type"].getStr == $PendingTransactionType.BuyStickerPack:
pendingStickerPacks.incl(trx["additionalData"].getStr.parseInt)
for stickerPack in availableStickers:
let isInstalled = installedStickerPacks.hasKey(stickerPack.id)
let isBought = purchasedStickerPacks.contains(stickerPack.id)
let isPending = pendingStickerPacks.contains(stickerPack.id) and not isBought
self.availableStickerPacks[stickerPack.id] = stickerPack
self.events.emit(SIGNAL_STICKER_PACK_LOADED, StickerPackLoadedArgs(
stickerPack: stickerPack,
isInstalled: isInstalled,
isBought: isBought,
isPending: isPending
))
self.events.emit(SIGNAL_ALL_STICKER_PACKS_LOADED, Args())
proc obtainAvailableStickerPacks*(self: Service) =
let networkType = self.settingsService.getCurrentNetwork().toNetworkType()
let network = self.networkService.getNetwork(networkType)
let contract = self.eth_service.findContract(network.chainId, "stickers")
if (contract == nil):
return
let arg = ObtainAvailableStickerPacksTaskArg(
tptr: cast[ByteAddress](obtainAvailableStickerPacksTask),
vptr: cast[ByteAddress](self.vptr),
slot: "setAvailableStickerPacks",
contract: contract,
packCountMethod: contract.methods["packCount"],
getPackDataMethod: contract.methods["getPackData"],
running: cast[ByteAddress](addr self.threadpool.running)
)
self.threadpool.start(arg)
proc setGasEstimate*(self: Service, estimateJson: string) {.slot.} =
let estimateResult = Json.decode(estimateJson, tuple[estimate: int, uuid: string])
self.events.emit(SIGNAL_STICKER_GAS_ESTIMATED, StickerGasEstimatedArgs(estimate: estimateResult.estimate, uuid: estimateResult.uuid))
# the [T] here is annoying but the QtObject template only allows for one type
# definition so we'll need to setup the type, task, and helper outside of body
# passed to `QtObject:`
proc estimate*(self: Service, packId: int, address: string, price: string, uuid: string) =
var
approveAndCall: ApproveAndCall[100]
networkType = self.settingsService.getCurrentNetwork().toNetworkType()
network = self.networkService.getNetwork(networkType)
sntContract = self.eth_service.findErc20Contract(network.chainId, network.sntSymbol())
tx = self.buildTransaction(
packId.u256,
status_utils.parseAddress(address),
status_utils.eth2Wei(parseFloat(price), sntContract.decimals),
approveAndCall,
sntContract
)
var estimateData = sntContract.methods["approveAndCall"]
.getEstimateGasData(tx, approveAndCall)
let arg = EstimateTaskArg(
tptr: cast[ByteAddress](estimateTask),
vptr: cast[ByteAddress](self.vptr),
slot: "setGasEstimate",
uuid: uuid,
data: estimateData
)
self.threadpool.start(arg)
proc addStickerToRecent*(self: Service, sticker: StickerDto, save: bool = false) =
self.recentStickers.insert(sticker, 0)
self.recentStickers = self.recentStickers.deduplicate()
if self.recentStickers.len > 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)

View File

@ -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)

View File

@ -28,7 +28,7 @@ StatusAppThreePanelLayout {
property var store
// Not Refactored
// property var messageStore
property var messageStore
// Not Refactored
property RootStore rootStore: RootStore {

View File

@ -38,6 +38,7 @@ QtObject {
}
}
property var contactsModuleInst: contactsModule
property var stickersModuleInst: stickersModule
property var activeCommunity: chatsModelInst.communities.activeCommunity

View File

@ -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){

View File

@ -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)
}

View File

@ -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(){}

View File

@ -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
}
}
}