parent
63b6b01b68
commit
792397987c
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
|
@ -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 = ""
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
@ -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)
|
|
@ -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")
|
|
@ -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)
|
|
@ -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)
|
|
@ -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))
|
|
@ -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)
|
|
@ -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"
|
|
@ -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
|
|
@ -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
|
|
@ -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")
|
|
@ -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"]
|
|
@ -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))
|
|
@ -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"
|
|
@ -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())
|
|
@ -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")
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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))
|
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
||||
|
|
|
@ -28,7 +28,7 @@ StatusAppThreePanelLayout {
|
|||
property var store
|
||||
|
||||
// Not Refactored
|
||||
// property var messageStore
|
||||
property var messageStore
|
||||
|
||||
// Not Refactored
|
||||
property RootStore rootStore: RootStore {
|
||||
|
|
|
@ -38,6 +38,7 @@ QtObject {
|
|||
}
|
||||
}
|
||||
property var contactsModuleInst: contactsModule
|
||||
property var stickersModuleInst: stickersModule
|
||||
|
||||
property var activeCommunity: chatsModelInst.communities.activeCommunity
|
||||
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(){}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue