diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index 1fc28eaf33..e753973acd 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -12,6 +12,7 @@ import app_service/service/message/service as message_service import app_service/service/token/service as token_service import app_service/service/collectible/service as collectible_service import app_service/service/currency/service as currency_service +import app_service/service/ramp/service as ramp_service import app_service/service/transaction/service as transaction_service import app_service/service/wallet_account/service as wallet_account_service import app_service/service/privacy/service as privacy_service @@ -77,6 +78,7 @@ type tokenService: token_service.Service collectibleService: collectible_service.Service currencyService: currency_service.Service + rampService: ramp_service.Service transactionService: transaction_service.Service walletAccountService: wallet_account_service.Service providerService: provider_service.Service @@ -199,6 +201,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = ) result.communityService = community_service.newService(statusFoundation.events, statusFoundation.threadpool, result.chatService, result.activityCenterService, result.messageService) + result.rampService = ramp_service.newService(statusFoundation.events, statusFoundation.threadpool) result.transactionService = transaction_service.newService(statusFoundation.events, statusFoundation.threadpool, result.networkService, result.settingsService, result.tokenService) result.profileService = profile_service.newService(statusFoundation.events, statusFoundation.threadpool, result.settingsService) result.stickersService = stickers_service.newService( @@ -257,6 +260,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.tokenService, result.collectibleService, result.currencyService, + result.rampService, result.transactionService, result.walletAccountService, result.profileService, @@ -317,6 +321,7 @@ proc delete*(self: AppController) = self.currencyService.delete self.collectibleService.delete self.tokenService.delete + self.rampService.delete self.transactionService.delete self.walletAccountService.delete self.aboutService.delete @@ -422,6 +427,7 @@ proc load(self: AppController) = self.messageService.init() self.communityService.init() self.providerService.init() + self.rampService.init() self.transactionService.init() self.stickersService.init() self.activityCenterService.init() diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 5a58a764d2..e85cdb6228 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -38,6 +38,7 @@ import ../../../app_service/service/message/service as message_service import ../../../app_service/service/token/service as token_service import ../../../app_service/service/collectible/service as collectible_service import ../../../app_service/service/currency/service as currency_service +import ../../../app_service/service/ramp/service as ramp_service import ../../../app_service/service/transaction/service as transaction_service import ../../../app_service/service/wallet_account/service as wallet_account_service import ../../../app_service/service/provider/service as provider_service @@ -140,6 +141,7 @@ proc newModule*[T]( tokenService: token_service.Service, collectibleService: collectible_service.Service, currencyService: currency_service.Service, + rampService: ramp_service.Service, transactionService: transaction_service.Service, walletAccountService: wallet_account_service.Service, profileService: profile_service.Service, @@ -208,7 +210,7 @@ proc newModule*[T]( result.chatSectionModules = initOrderedTable[string, chat_section_module.AccessInterface]() result.walletSectionModule = wallet_section_module.newModule( result, events, tokenService, collectibleService, currencyService, - transactionService, walletAccountService, + rampService, transactionService, walletAccountService, settingsService, savedAddressService, networkService, accountsService, keycardService, nodeService, networkConnectionService, devicesService, communityTokensService, threadpool diff --git a/src/app/modules/main/wallet_section/buy_sell_crypto/controller.nim b/src/app/modules/main/wallet_section/buy_sell_crypto/controller.nim index 04c1424524..76e9563e2d 100644 --- a/src/app/modules/main/wallet_section/buy_sell_crypto/controller.nim +++ b/src/app/modules/main/wallet_section/buy_sell_crypto/controller.nim @@ -1,31 +1,39 @@ import io_interface -import ../../../../../app_service/service/transaction/service as transaction_service +import app_service/service/ramp/service as ramp_service +import app_service/service/ramp/dto import ../../../../core/eventemitter type Controller* = ref object of RootObj delegate: io_interface.AccessInterface - transactionService: transaction_service.Service + rampService: ramp_service.Service events: EventEmitter proc newController*( delegate: io_interface.AccessInterface, events: EventEmitter, - transactionService: transaction_service.Service + rampService: ramp_service.Service ): Controller = result = Controller() result.delegate = delegate result.events = events - result.transactionService = transactionService + result.rampService = rampService proc delete*(self: Controller) = discard proc init*(self: Controller) = - self.events.on(SIGNAL_CRYPTO_SERVICES_READY) do(e:Args): - let args = CryptoServicesArgs(e) - self.delegate.updateCryptoServices(args.data) + self.events.on(SIGNAL_CRYPTO_RAMP_PROVIDERS_READY) do(e:Args): + let args = CryptoRampProvidersArgs(e) + self.delegate.updateRampProviders(args.data) -proc fetchCryptoServices*(self: Controller) = - self.transactionService.fetchCryptoServices() + self.events.on(SIGNAL_CRYPTO_RAMP_URL_READY) do(e:Args): + let args = CryptoRampUrlArgs(e) + self.delegate.onRampProviderUrlReady(args.uuid, args.url) + +proc fetchCryptoRampProviders*(self: Controller) = + self.rampService.fetchCryptoRampProviders() + +proc fetchCryptoRampUrl*(self: Controller, uuid: string, providerID: string, parameters: CryptoRampParametersDto) = + self.rampService.fetchCryptoRampUrl(uuid, providerID, parameters) diff --git a/src/app/modules/main/wallet_section/buy_sell_crypto/io_interface.nim b/src/app/modules/main/wallet_section/buy_sell_crypto/io_interface.nim index 782838b676..faa7e99bba 100644 --- a/src/app/modules/main/wallet_section/buy_sell_crypto/io_interface.nim +++ b/src/app/modules/main/wallet_section/buy_sell_crypto/io_interface.nim @@ -1,4 +1,4 @@ -import ../../../../../app_service/service/transaction/cryptoRampDto +import app_service/service/ramp/dto type AccessInterface* {.pure inheritable.} = ref object of RootObj @@ -13,7 +13,16 @@ method load*(self: AccessInterface) {.base.} = method isLoaded*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") -method updateCryptoServices*(self: AccessInterface, cryptoServices: seq[CryptoRampDto]) {.base.} = +method fetchProviders*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + +method fetchProviderUrl*(self: AccessInterface, uuid: string, providerID: string, parameters: CryptoRampParametersDto) {.base.} = + raise newException(ValueError, "No implementation available") + +method updateRampProviders*(self: AccessInterface, cryptoServices: seq[CryptoRampDto]) {.base.} = + raise newException(ValueError, "No implementation available") + +method onRampProviderUrlReady*(self: AccessInterface, uuid: string, url: string) {.base.} = raise newException(ValueError, "No implementation available") # View Delegate Interface diff --git a/src/app/modules/main/wallet_section/buy_sell_crypto/item.nim b/src/app/modules/main/wallet_section/buy_sell_crypto/item.nim index b4db2cc3d2..9ef774b6a3 100644 --- a/src/app/modules/main/wallet_section/buy_sell_crypto/item.nim +++ b/src/app/modules/main/wallet_section/buy_sell_crypto/item.nim @@ -1,35 +1,60 @@ import stew/shims/strformat + +import app/modules/shared_models/contract_model as contract_model +import app/modules/shared_models/contract_item as contract_item type Item* = object + id: string name: string description: string fees: string - logoUrl: string - siteUrl: string + logoUrl: string hostname: string - recurrentSiteUrl: string + supportsSinglePurchase: bool + supportsRecurrentPurchase: bool + supportedAssets: contract_model.Model + urlsNeedParameters: bool -proc initItem*(name, description, fees, logoUrl, siteUrl, - hostname, recurrentSiteUrl,: string): Item = +proc initItem*( + id: string, + name: string, + description: string, + fees: string, + logoUrl: string, + hostname: string, + supportsSinglePurchase: bool, + supportsRecurrentPurchase: bool, + supportedAssets: seq[contract_item.Item], + urlsNeedParameters: bool): Item = + result.id = id result.name = name result.description = description result.fees = fees result.logoUrl = logoUrl - result.siteUrl = siteUrl result.hostname = hostname - result.recurrentSiteUrl = recurrentSiteUrl + result.supportsSinglePurchase = supportsSinglePurchase + result.supportsRecurrentPurchase = supportsRecurrentPurchase + result.supportedAssets = contract_model.newModel() + result.supportedAssets.setItems(supportedAssets) + result.urlsNeedParameters = urlsNeedParameters proc `$`*(self: Item): string = result = "Item(" + result &= fmt"id:{self.id}, " result &= fmt"name:{self.name}, " result &= fmt"description:{self.description}, " result &= fmt"fees:{self.fees}, " result &= fmt"logoUrl:{self.logoUrl}, " - result &= fmt"siteUrl:{self.siteUrl}" - result &= fmt"hostname:{self.hostname}" - result &= fmt"recurrentSiteUrl:{self.recurrentSiteUrl}" + result &= fmt"hostname:{self.hostname}, " + result &= fmt"supportsSinglePurchase:{self.supportsSinglePurchase}, " + result &= fmt"supportsRecurrentPurchase:{self.supportsRecurrentPurchase}, " + result &= fmt"supportedAssets:{self.supportedAssets}, " + result &= fmt"urlsNeedParameters:{self.urlsNeedParameters}, " result &= ")" +method getId*(self: Item): string {.base.} = + return self.id + method getName*(self: Item): string {.base.} = return self.name @@ -42,11 +67,17 @@ method getFees*(self: Item): string {.base.} = method getLogoUrl*(self: Item): string {.base.} = return self.logoUrl -method getSiteUrl*(self: Item): string {.base.} = - return self.siteUrl - method getHostname*(self: Item): string {.base.} = return self.hostname -method getRecurrentSiteUrl*(self: Item): string {.base.} = - return self.recurrentSiteUrl +method getSupportsSinglePurchase*(self: Item): bool {.base.} = + return self.supportsSinglePurchase + +method getSupportsRecurrentPurchase*(self: Item): bool {.base.} = + return self.supportsRecurrentPurchase + +method getSupportedAssets*(self: Item): contract_model.Model {.base.} = + return self.supportedAssets + +method getUrlsNeedParameters*(self: Item): bool {.base.} = + return self.urlsNeedParameters \ No newline at end of file diff --git a/src/app/modules/main/wallet_section/buy_sell_crypto/model.nim b/src/app/modules/main/wallet_section/buy_sell_crypto/model.nim index eab88511d3..bca3c36ecf 100644 --- a/src/app/modules/main/wallet_section/buy_sell_crypto/model.nim +++ b/src/app/modules/main/wallet_section/buy_sell_crypto/model.nim @@ -4,13 +4,16 @@ import item type ModelRole {.pure.} = enum - Name = UserRole + 1 + Id = UserRole + 1 + Name Description Fees LogoUrl - SiteUrl Hostname - RecurrentSiteUrl + SupportsSinglePurchase + SupportsRecurrentPurchase + SupportedAssets + UrlsNeedParameters QtObject: type @@ -32,13 +35,16 @@ QtObject: method roleNames(self: Model): Table[int, string] = { + ModelRole.Id.int:"id", ModelRole.Name.int:"name", ModelRole.Description.int:"description", ModelRole.Fees.int:"fees", ModelRole.LogoUrl.int:"logoUrl", - ModelRole.SiteUrl.int:"siteUrl", ModelRole.Hostname.int:"hostname", - ModelRole.RecurrentSiteUrl.int:"recurrentSiteUrl" + ModelRole.SupportsSinglePurchase.int:"supportsSinglePurchase", + ModelRole.SupportsRecurrentPurchase.int:"supportsRecurrentPurchase", + ModelRole.SupportedAssets.int:"supportedAssets", + ModelRole.UrlsNeedParameters.int:"urlsNeedParameters" }.toTable method data(self: Model, index: QModelIndex, role: int): QVariant = @@ -52,6 +58,8 @@ QtObject: let enumRole = role.ModelRole case enumRole: + of ModelRole.Id: + result = newQVariant(item.getId) of ModelRole.Name: result = newQVariant(item.getName) of ModelRole.Description: @@ -60,12 +68,16 @@ QtObject: result = newQVariant(item.getFees) of ModelRole.LogoUrl: result = newQVariant(item.getLogoUrl) - of ModelRole.SiteUrl: - result = newQVariant(item.getSiteUrl) of ModelRole.Hostname: result = newQVariant(item.getHostname) - of ModelRole.RecurrentSiteUrl: - result = newQVariant(item.getRecurrentSiteUrl) + of ModelRole.SupportsSinglePurchase: + result = newQVariant(item.getSupportsSinglePurchase) + of ModelRole.SupportsRecurrentPurchase: + result = newQVariant(item.getSupportsRecurrentPurchase) + of ModelRole.SupportedAssets: + result = newQVariant(item.getSupportedAssets) + of ModelRole.UrlsNeedParameters: + result = newQVariant(item.getUrlsNeedParameters) proc setItems*(self: Model, items: seq[Item]) = self.beginResetModel() diff --git a/src/app/modules/main/wallet_section/buy_sell_crypto/module.nim b/src/app/modules/main/wallet_section/buy_sell_crypto/module.nim index 3069edfa33..bc5edba9b5 100644 --- a/src/app/modules/main/wallet_section/buy_sell_crypto/module.nim +++ b/src/app/modules/main/wallet_section/buy_sell_crypto/module.nim @@ -1,11 +1,11 @@ -import NimQml, sequtils +import NimQml, sequtils, sugar -import ./io_interface, ./view, ./item, ./controller +import ./io_interface, ./view, ./controller, ./utils import ../io_interface as delegate_interface import ../../../../global/global_singleton import ../../../../core/eventemitter -import ../../../../../app_service/service/transaction/service as transaction_service -import ../../../../../app_service/service/transaction/cryptoRampDto +import app_service/service/ramp/service as ramp_service +import app_service/service/ramp/dto export io_interface @@ -21,14 +21,14 @@ type proc newModule*( delegate: delegate_interface.AccessInterface, events: EventEmitter, - transactionService: transaction_service.Service, + rampService: ramp_service.Service, ): Module = result = Module() result.delegate = delegate result.events = events result.view = newView(result) result.viewVariant = newQVariant(result.view) - result.controller = controller.newController(result, events, transactionService) + result.controller = controller.newController(result, events, rampService) result.moduleLoaded = false method delete*(self: Module) = @@ -36,21 +36,23 @@ method delete*(self: Module) = self.view.delete self.controller.delete -method updateCryptoServices*(self: Module, cryptoServices: seq[CryptoRampDto]) = - let items = cryptoServices.map(proc (w: CryptoRampDto): item.Item = result = initItem( - w.name, - w.description, - w.fees, - w.logoUrl, - w.siteUrl, - w.hostname, - w.recurrentSiteUrl, - )) +method fetchProviders*(self: Module) = + self.controller.fetchCryptoRampProviders() + self.view.setIsFetching(true) + +method fetchProviderUrl*(self: Module, uuid: string, providerID: string, parameters: CryptoRampParametersDto) = + self.controller.fetchCryptoRampUrl(uuid, providerID, parameters) + +method updateRampProviders*(self: Module, cryptoServices: seq[CryptoRampDto]) = + let items = cryptoServices.map(i => i.dtoToItem()) self.view.setItems(items) + self.view.setIsFetching(false) + +method onRampProviderUrlReady*(self: Module, uuid: string, url: string) = + self.view.onProviderUrlReady(uuid, url) method load*(self: Module) = singletonInstance.engine.setRootContextProperty("walletSectionBuySellCrypto", self.viewVariant) - self.controller.fetchCryptoServices() self.controller.init() self.view.load() diff --git a/src/app/modules/main/wallet_section/buy_sell_crypto/utils.nim b/src/app/modules/main/wallet_section/buy_sell_crypto/utils.nim new file mode 100644 index 0000000000..042d7617d0 --- /dev/null +++ b/src/app/modules/main/wallet_section/buy_sell_crypto/utils.nim @@ -0,0 +1,21 @@ +import sugar, sequtils + +import ./item +import app_service/service/ramp/dto +import app/modules/shared_models/contract_item as contract_item + +proc dtoToItem*(dto: CryptoRampDto): item.Item = + let supportedAssets = dto.supportedTokens.map(t => contract_item.initItem(t.chainID, t.address)) + + return initItem( + dto.id, + dto.name, + dto.description, + dto.fees, + dto.logoUrl, + dto.hostname, + dto.supportsSinglePurchase, + dto.supportsRecurrentPurchase, + supportedAssets, + dto.urlsNeedParameters + ) diff --git a/src/app/modules/main/wallet_section/buy_sell_crypto/view.nim b/src/app/modules/main/wallet_section/buy_sell_crypto/view.nim index 62798b86d7..ec2ae1a8ab 100644 --- a/src/app/modules/main/wallet_section/buy_sell_crypto/view.nim +++ b/src/app/modules/main/wallet_section/buy_sell_crypto/view.nim @@ -1,15 +1,19 @@ import NimQml +import options import ./model import ./item import ./io_interface +import app_service/service/ramp/dto + QtObject: type View* = ref object of QObject delegate: io_interface.AccessInterface model: Model modelVariant: QVariant + isFetching: bool proc delete*(self: View) = self.model.delete @@ -22,6 +26,7 @@ QtObject: result.delegate = delegate result.model = newModel() result.modelVariant = newQVariant(result.model) + result.isFetching = false proc load*(self: View) = self.delegate.viewDidLoad() @@ -35,6 +40,49 @@ QtObject: read = getModel notify = modelChanged + proc isFetchingChanged*(self: View) {.signal.} + + proc getIsFetching*(self: View): bool {.slot.} = + return self.isFetching + + QtProperty[bool] isFetching: + read = getIsFetching + notify = isFetchingChanged + + proc setIsFetching*(self: View, value: bool) = + if self.isFetching == value: + return + self.isFetching = value + self.isFetchingChanged() + proc setItems*(self: View, items: seq[Item]) = self.model.setItems(items) self.modelChanged() + + proc fetchProviders*(self: View) {.slot.} = + self.delegate.fetchProviders() + + proc fetchProviderUrl*(self: View, + uuid: string, + providerID: string, + isRecurrent: bool, + destinationAccountAddress: string, + chainID: int, + symbol: string) {.slot.} = + let parameters = CryptoRampParametersDto( + isRecurrent: isRecurrent, + ) + + if destinationAccountAddress.len > 0: + parameters.destinationAddress = some(destinationAccountAddress) + if chainID > 0: + parameters.chainID = some(chainID) + if symbol.len > 0: + parameters.symbol = some(symbol) + + self.delegate.fetchProviderUrl(uuid, providerID, parameters) + + proc providerUrlReady*(self: View, uuid: string, url: string) {.signal.} + + proc onProviderUrlReady*(self: View, uuid: string, url: string) = + self.providerUrlReady(uuid, url) diff --git a/src/app/modules/main/wallet_section/module.nim b/src/app/modules/main/wallet_section/module.nim index f9ff177678..e1a5625813 100644 --- a/src/app/modules/main/wallet_section/module.nim +++ b/src/app/modules/main/wallet_section/module.nim @@ -29,6 +29,7 @@ import app_service/service/keycard/service as keycard_service import app_service/service/token/service as token_service import app_service/service/collectible/service as collectible_service import app_service/service/currency/service as currency_service +import app_service/service/ramp/service as ramp_service import app_service/service/transaction/service as transaction_service import app_service/service/wallet_account/service as wallet_account_service import app_service/service/settings/service as settings_service @@ -75,6 +76,7 @@ type overviewModule: overview_module.AccessInterface networksModule: networks_module.AccessInterface networksService: network_service.Service + rampService: ramp_service.Service transactionService: transaction_service.Service keycardService: keycard_service.Service accountsService: accounts_service.Service @@ -107,6 +109,7 @@ proc newModule*( tokenService: token_service.Service, collectibleService: collectible_service.Service, currencyService: currency_service.Service, + rampService: ramp_service.Service, transactionService: transaction_service.Service, walletAccountService: wallet_account_service.Service, settingsService: settings_service.Service, @@ -141,7 +144,7 @@ proc newModule*( result.sendModule = send_module.newModule(result, events, walletAccountService, networkService, currencyService, transactionService, keycardService) result.savedAddressesModule = saved_addresses_module.newModule(result, events, savedAddressService) - result.buySellCryptoModule = buy_sell_crypto_module.newModule(result, events, transactionService) + result.buySellCryptoModule = buy_sell_crypto_module.newModule(result, events, rampService) result.overviewModule = overview_module.newModule(result, events, walletAccountService, currencyService) result.networksModule = networks_module.newModule(result, events, networkService, walletAccountService, settingsService) result.networksService = networkService diff --git a/src/app/modules/shared_models/contract_item.nim b/src/app/modules/shared_models/contract_item.nim new file mode 100644 index 0000000000..661466f07e --- /dev/null +++ b/src/app/modules/shared_models/contract_item.nim @@ -0,0 +1,28 @@ +import stew/shims/strformat + +type Item* = ref object + key: string + chainId: int + address: string + +proc initItem*(chainID: int, address: string): Item = + result = Item() + result.key = $chainID & address + result.chainID = chainID + result.address = address + +proc `$`*(self: Item): string = + result = fmt"""ContractItem( + key: {self.key}, + chainId: {$self.chainId}, + address: {self.address}, + ]""" + +proc key*(self: Item): string {.inline.} = + self.key + +proc chainId*(self: Item): int {.inline.} = + self.chainId + +proc address*(self: Item): string {.inline.} = + self.address diff --git a/src/app/modules/shared_models/contract_model.nim b/src/app/modules/shared_models/contract_model.nim new file mode 100644 index 0000000000..a65e9daa2d --- /dev/null +++ b/src/app/modules/shared_models/contract_model.nim @@ -0,0 +1,59 @@ +import NimQml, Tables, strutils, stew/shims/strformat + +import ./contract_item + +type + ModelRole {.pure.} = enum + Key = UserRole + 1 + ChainId + Address + +QtObject: + type Model* = ref object of QAbstractListModel + items: seq[Item] + + proc setup(self: Model) = + self.QAbstractListModel.setup + + proc delete(self: Model) = + self.items = @[] + self.QAbstractListModel.delete + + proc newModel*(): Model = + new(result, delete) + result.setup + result.items = @[] + + proc `$`*(self: Model): string = + for i in 0 ..< self.items.len: + result &= fmt"""[{i}]:({$self.items[i]})""" + + method rowCount(self: Model, index: QModelIndex = nil): int = + return self.items.len + + method roleNames(self: Model): Table[int, string] = + { + ModelRole.Key.int:"key", + ModelRole.ChainId.int:"chainId", + ModelRole.Address.int:"address", + }.toTable + + method data(self: Model, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.rowCount(): + return + let item = self.items[index.row] + let enumRole = role.ModelRole + case enumRole: + of ModelRole.Key: + result = newQVariant(item.key()) + of ModelRole.ChainId: + result = newQVariant(item.chainId()) + of ModelRole.Address: + result = newQVariant(item.address()) + + proc setItems*(self: Model, items: seq[Item]) = + self.beginResetModel() + self.items = items + self.endResetModel() diff --git a/src/app_service/service/ramp/async_tasks.nim b/src/app_service/service/ramp/async_tasks.nim new file mode 100644 index 0000000000..3c1e1ca385 --- /dev/null +++ b/src/app_service/service/ramp/async_tasks.nim @@ -0,0 +1,47 @@ +import json +import backend/ramp as ramp_backend + + +proc getCryptoServicesTask*(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[QObjectTaskArg](argEncoded) + + try: + let response = ramp_backend.fetchCryptoRampProviders() + + if not response.error.isNil: + raise newException(ValueError, "Error fetching crypto services" & response.error.message) + + arg.finish(%* { + "result": response.result, + }) + except Exception as e: + error "Error fetching crypto services", message = e.msg + arg.finish(%* { + "result": @[], + }) + +type + GetCryptoRampUrlTaskArg* = ref object of QObjectTaskArg + uuid: string + providerID: string + parameters: JsonNode + +proc getCryptoRampUrlTask*(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[GetCryptoRampUrlTaskArg](argEncoded) + + var data = %* { + "uuid": arg.uuid + } + + try: + let parameters = fromJson(arg.parameters, CryptoRampParametersDto) + let response = ramp_backend.fetchCryptoRampUrl(arg.providerID, parameters) + + if not response.error.isNil: + raise newException(ValueError, "Error fetching crypto ramp url" & response.error.message) + data["url"] = response.result + arg.finish(data) + + except Exception as e: + error "Error fetching crypto ramp url", message = e.msg + arg.finish(data) diff --git a/src/app_service/service/ramp/dto.nim b/src/app_service/service/ramp/dto.nim new file mode 100644 index 0000000000..02a3f729b5 --- /dev/null +++ b/src/app_service/service/ramp/dto.nim @@ -0,0 +1,79 @@ + +import json, json_serialization, stew/shims/strformat +import options + +include app_service/common/json_utils +import app_service/service/token/dto + +type + CryptoRampDto* = ref object of RootObj + id*: string + name*: string + description*: string + fees*: string + logoUrl*: string + hostname*: string + supportsSinglePurchase*: bool + supportsRecurrentPurchase*: bool + supportedChainIds*: seq[int] + supportedTokens*: seq[TokenDto] + urlsNeedParameters*: bool + +type + CryptoRampParametersDto* = ref object of RootObj + isRecurrent*: bool + destinationAddress*: Option[string] + chainID*: Option[int] + symbol*: Option[string] + +proc `$`*(self: CryptoRampDto): string = + result = "CryptoRampDto(" + result &= fmt"id:{self.id}, " + result &= fmt"name:{self.name}, " + result &= fmt"description:{self.description}, " + result &= fmt"fees:{self.fees}, " + result &= fmt"logoUrl:{self.logoUrl}, " + result &= fmt"hostname:{self.hostname}, " + result &= fmt"supportsSinglePurchase:{self.supportsSinglePurchase}, " + result &= fmt"supportsRecurrentPurchase:{self.supportsRecurrentPurchase}, " + result &= fmt"supportedChainIds:{self.supportedChainIds}, " + result &= fmt"supportedTokens:{self.supportedTokens}, " + result &= fmt"urlsNeedParameters:{self.urlsNeedParameters}" + result &= ")" + +proc toCryptoRampDto*(jsonObj: JsonNode): CryptoRampDto = + result = CryptoRampDto() + discard jsonObj.getProp("id", result.id) + discard jsonObj.getProp("name", result.name) + discard jsonObj.getProp("description", result.description) + discard jsonObj.getProp("fees", result.fees) + discard jsonObj.getProp("logoUrl", result.logoUrl) + discard jsonObj.getProp("hostname", result.hostname) + discard jsonObj.getProp("supportsSinglePurchase", result.supportsSinglePurchase) + discard jsonObj.getProp("supportsRecurrentPurchase", result.supportsRecurrentPurchase) + for chainID in jsonObj["supportedChainIds"].getElems(): + result.supportedChainIds.add(chainID.getInt()) + for token in jsonObj["supportedTokens"].getElems(): + let tokenDto = Json.decode($token, TokenDto, allowUnknownFields = true) + result.supportedTokens.add(tokenDto) + discard jsonObj.getProp("urlsNeedParameters", result.urlsNeedParameters) + +proc `%`*(self: CryptoRampParametersDto): JsonNode = + result = newJObject() + result["isRecurrent"] = %(self.isRecurrent) + if self.destinationAddress.isSome: + result["destAddress"] = %(self.destinationAddress.get) + if self.chainID.isSome: + result["chainID"] = %(self.chainID.get) + if self.symbol.isSome: + result["symbol"] = %(self.symbol.get) + +proc fromJson*(self: JsonNode, T: typedesc[CryptoRampParametersDto]): CryptoRampParametersDto {.inline.} = + result = CryptoRampParametersDto() + discard self.getProp("isRecurrent", result.isRecurrent) + if self.contains("destAddress"): + result.destinationAddress = some(self["destAddress"].getStr()) + if self.contains("chainID"): + result.chainID = some(self["chainID"].getInt()) + if self.contains("symbol"): + result.symbol = some(self["symbol"].getStr()) diff --git a/src/app_service/service/ramp/service.nim b/src/app_service/service/ramp/service.nim new file mode 100644 index 0000000000..f7b8a241f7 --- /dev/null +++ b/src/app_service/service/ramp/service.nim @@ -0,0 +1,78 @@ +import NimQml, sequtils, sugar, chronicles, json + +import app/core/[main] +import app/core/signals/types +import app/core/tasks/[qt, threadpool] + +import ./dto +export dto + +const SIGNAL_CRYPTO_RAMP_PROVIDERS_READY* = "cryptoRampProvidersReady" +const SIGNAL_CRYPTO_RAMP_URL_READY* = "cryptoRampUrlReady" + +type + CryptoRampProvidersArgs* = ref object of Args + data*: seq[CryptoRampDto] + +type + CryptoRampUrlArgs* = ref object of Args + uuid*: string + url*: string + +logScope: + topics = "ramp-service" + +include async_tasks +include app_service/common/json_utils + +QtObject: + type Service* = ref object of QObject + events: EventEmitter + threadpool: ThreadPool + + proc delete*(self: Service) = + self.QObject.delete + + proc newService*( + events: EventEmitter, + threadpool: ThreadPool, + ): Service = + new(result, delete) + result.QObject.setup + result.events = events + result.threadpool = threadpool + + proc init*(self: Service) = + discard + + proc onFetchCryptoRampProviders*(self: Service, response: string) {.slot.} = + let cryptoServices = parseJson(response){"result"}.getElems().map(x => x.toCryptoRampDto()) + self.events.emit(SIGNAL_CRYPTO_RAMP_PROVIDERS_READY, CryptoRampProvidersArgs(data: cryptoServices)) + + proc fetchCryptoRampProviders*(self: Service) = + let arg = QObjectTaskArg( + tptr: getCryptoServicesTask, + vptr: cast[ByteAddress](self.vptr), + slot: "onFetchCryptoRampProviders", + ) + self.threadpool.start(arg) + + proc onFetchCryptoRampURL*(self: Service, response: string) {.slot.} = + let responseJson = parseJson(response) + let uuid = responseJson{"uuid"}.getStr() + let url = responseJson{"url"}.getStr() + self.events.emit(SIGNAL_CRYPTO_RAMP_URL_READY, CryptoRampUrlArgs( + uuid: uuid, + url: url, + )) + + proc fetchCryptoRampUrl*(self: Service, uuid: string, providerID: string, parameters: CryptoRampParametersDto) = + let arg = GetCryptoRampUrlTaskArg( + tptr: getCryptoRampURLTask, + vptr: cast[ByteAddress](self.vptr), + slot: "onFetchCryptoRampURL", + uuid: uuid, + providerID: providerID, + parameters: %parameters, + ) + self.threadpool.start(arg) diff --git a/src/app_service/service/token/dto.nim b/src/app_service/service/token/dto.nim index 6b2c56a1ce..60d04dedd9 100644 --- a/src/app_service/service/token/dto.nim +++ b/src/app_service/service/token/dto.nim @@ -46,6 +46,9 @@ proc `$`*(self: TokenDto): string = image: {self.image} ]""" +proc key*(self: TokenDto): string = + result = self.address + type TokenSourceDto* = ref object of RootObj name* {.serializedFieldName("name").}: string tokens* {.serializedFieldName("tokens").}: seq[TokenDto] diff --git a/src/app_service/service/token/service.nim b/src/app_service/service/token/service.nim index 8974f9a879..dc1a6ce181 100644 --- a/src/app_service/service/token/service.nim +++ b/src/app_service/service/token/service.nim @@ -11,7 +11,7 @@ import app/core/tasks/[qt, threadpool] import app/core/signals/types import app_service/common/cache import app_service/common/wallet_constants -import ./dto, ./service_items +import ./dto, ./service_items, ./utils export dto, service_items @@ -228,7 +228,7 @@ QtObject: let tokenType = TokenType.ERC20 var updated = false - let unique_key = $token.chainID & token.address + let unique_key = token.flatModelKey() if not any(self.flatTokenList, proc (x: TokenItem): bool = x.key == unique_key): self.flatTokenList.add(TokenItem( key: unique_key, @@ -244,7 +244,7 @@ QtObject: self.flatTokenList.sort(cmpTokenItem) updated = true - let token_by_symbol_key = token.address + let token_by_symbol_key = token.bySymbolModelKey() if not any(self.tokenBySymbolList, proc (x: TokenBySymbolItem): bool = x.key == token_by_symbol_key): self.tokenBySymbolList.add(TokenBySymbolItem( key: token_by_symbol_key, @@ -298,7 +298,7 @@ QtObject: # Remove tokens that are not on list of supported status networks if supportedNetworkChains.contains(token.chainID): # logic for building flat tokens list - let unique_key = $token.chainID & token.address + let unique_key = token.flatModelKey() if flatTokensList.hasKey(unique_key): flatTokensList[unique_key].sources.add(s.name) else: @@ -321,8 +321,7 @@ QtObject: # In case this is a community token the only param reliably unique is its address # as there is always a rare case that a user can create two or more community token # with same symbol and cannot be avoided - let token_by_symbol_key = if token.communityData.id.isEmptyOrWhitespace: token.symbol - else: token.address + let token_by_symbol_key = token.bySymbolModelKey() if tokenBySymbolList.hasKey(token_by_symbol_key): if not tokenBySymbolList[token_by_symbol_key].sources.contains(s.name): tokenBySymbolList[token_by_symbol_key].sources.add(s.name) diff --git a/src/app_service/service/token/utils.nim b/src/app_service/service/token/utils.nim new file mode 100644 index 0000000000..01fef3861f --- /dev/null +++ b/src/app_service/service/token/utils.nim @@ -0,0 +1,12 @@ +import strutils + +import ./dto + +proc flatModelKey*(self: TokenDto): string = + result = $self.chainID & self.address + +proc bySymbolModelKey*(self: TokenDto): string = + if self.communityData.id.isEmptyOrWhitespace: + result = self.symbol + else: + result = self.address diff --git a/src/app_service/service/transaction/async_tasks.nim b/src/app_service/service/transaction/async_tasks.nim index 3c6338a007..a83d4500de 100644 --- a/src/app_service/service/transaction/async_tasks.nim +++ b/src/app_service/service/transaction/async_tasks.nim @@ -67,28 +67,6 @@ proc watchTransactionTask*(argEncoded: string) {.gcsafe, nimcall.} = "isSuccessfull": false } -type - GetCryptoServicesTaskArg* = ref object of QObjectTaskArg - discard - -proc getCryptoServicesTask*(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[GetCryptoServicesTaskArg](argEncoded) - - try: - let response = transactions.fetchCryptoServices() - - if not response.error.isNil: - raise newException(ValueError, "Error fetching crypto services" & response.error.message) - - arg.finish(%* { - "result": response.result, - }) - except Exception as e: - error "Error fetching crypto services", message = e.msg - arg.finish(%* { - "result": @[], - }) - type FetchDecodedTxDataTaskArg* = ref object of QObjectTaskArg txHash: string diff --git a/src/app_service/service/transaction/cryptoRampDto.nim b/src/app_service/service/transaction/cryptoRampDto.nim deleted file mode 100644 index caa52159ca..0000000000 --- a/src/app_service/service/transaction/cryptoRampDto.nim +++ /dev/null @@ -1,53 +0,0 @@ -import json, stew/shims/strformat - -include ../../common/json_utils - -type - CryptoRampDto* = ref object of RootObj - name*: string - description*: string - fees*: string - logoUrl*: string - siteUrl*: string - hostname*: string - recurrentSiteUrl*: string - -proc newDto*( - name: string, - description: string, - fees: string, - logoUrl: string, - siteUrl: string, - hostname: string, - recurrentSiteUrl: string -): CryptoRampDto = - return CryptoRampDto( - name: name, - description: description, - fees: fees, - logoUrl: logoUrl, - siteUrl: siteUrl, - hostname: hostname, - recurrentSiteUrl: recurrentSiteUrl - ) - -proc `$`*(self: CryptoRampDto): string = - result = "CryptoRampDto(" - result &= fmt"name:{self.name}, " - result &= fmt"description:{self.description}, " - result &= fmt"fees:{self.fees}, " - result &= fmt"logoUrl:{self.logoUrl}, " - result &= fmt"siteUrl:{self.siteUrl}, " - result &= fmt"hostname:{self.hostname}" - result &= fmt"recurrentSiteUrl:{self.recurrentSiteUrl}" - result &= ")" - -proc toCryptoRampDto*(jsonObj: JsonNode): CryptoRampDto = - result = CryptoRampDto() - discard jsonObj.getProp("name", result.name) - discard jsonObj.getProp("description", result.description) - discard jsonObj.getProp("fees", result.fees) - discard jsonObj.getProp("logoUrl", result.logoUrl) - discard jsonObj.getProp("siteUrl", result.siteUrl) - discard jsonObj.getProp("hostname", result.hostname) - discard jsonObj.getProp("recurrentSiteUrl", result.recurrentSiteUrl) diff --git a/src/app_service/service/transaction/service.nim b/src/app_service/service/transaction/service.nim index c53484dfc0..734e736bc7 100644 --- a/src/app_service/service/transaction/service.nim +++ b/src/app_service/service/transaction/service.nim @@ -24,7 +24,6 @@ import app_service/service/eth/dto/[coder, method_dto] import ./dto as transaction_dto import ./dtoV2 import ./dto_conversion -import ./cryptoRampDto import app_service/service/eth/utils as eth_utils @@ -42,7 +41,6 @@ const SIGNAL_TRANSACTION_SENT* = "transactionSent" const SIGNAL_SUGGESTED_ROUTES_READY* = "suggestedRoutesReady" const SIGNAL_HISTORY_NON_ARCHIVAL_NODE* = "historyNonArchivalNode" const SIGNAL_HISTORY_ERROR* = "historyError" -const SIGNAL_CRYPTO_SERVICES_READY* = "cryptoServicesReady" const SIGNAL_TRANSACTION_DECODED* = "transactionDecoded" const SIGNAL_OWNER_TOKEN_SENT* = "ownerTokenSent" const SIGNAL_TRANSACTION_SENDING_COMPLETE* = "transactionSendingComplete" @@ -137,9 +135,6 @@ type errCode*: string errDescription*: string -type - CryptoServicesArgs* = ref object of Args - data*: seq[CryptoRampDto] type TransactionDecodedArgs* = ref object of Args dataDecoded*: string @@ -697,18 +692,6 @@ QtObject: except CatchableError as e: error "suggestedRoutes", exception=e.msg - proc onFetchCryptoServices*(self: Service, response: string) {.slot.} = - let cryptoServices = parseJson(response){"result"}.getElems().map(x => x.toCryptoRampDto()) - self.events.emit(SIGNAL_CRYPTO_SERVICES_READY, CryptoServicesArgs(data: cryptoServices)) - - proc fetchCryptoServices*(self: Service) = - let arg = GetCryptoServicesTaskArg( - tptr: getCryptoServicesTask, - vptr: cast[ByteAddress](self.vptr), - slot: "onFetchCryptoServices", - ) - self.threadpool.start(arg) - proc getEstimatedTime*(self: Service, chainId: int, maxFeePerGas: string): EstimatedTime = try: let response = backend.getTransactionEstimatedTime(chainId, maxFeePerGas).result.getInt diff --git a/src/backend/ramp.nim b/src/backend/ramp.nim new file mode 100644 index 0000000000..6be3a38fda --- /dev/null +++ b/src/backend/ramp.nim @@ -0,0 +1,14 @@ +import json + +import ./core as core +import ./response_type +export response_type + +import ../app_service/service/ramp/dto + +proc fetchCryptoRampProviders*(): RpcResponse[JsonNode] = + result = core.callPrivateRPC("wallet_getCryptoOnRamps", %* []) + +proc fetchCryptoRampUrl*(providerID: string, parameters: CryptoRampParametersDto): RpcResponse[JsonNode] = + let payload = %* [providerID, parameters] + result = core.callPrivateRPC("wallet_getCryptoOnRampURL", payload) diff --git a/src/backend/transactions.nim b/src/backend/transactions.nim index f0fbfa45c8..abbda242b5 100644 --- a/src/backend/transactions.nim +++ b/src/backend/transactions.nim @@ -82,9 +82,6 @@ proc getTransfersByAddress*(chainId: int, address: string, toBlock: Uint256, lim proc getTransactionReceipt*(chainId: int, transactionHash: string): RpcResponse[JsonNode] = core.callPrivateRPCWithChainId("eth_getTransactionReceipt", chainId, %* [transactionHash]) -proc fetchCryptoServices*(): RpcResponse[JsonNode] = - result = core.callPrivateRPC("wallet_getCryptoOnRamps", %* []) - proc createMultiTransaction*(multiTransactionCommand: MultiTransactionCommandDto, data: seq[TransactionBridgeDto], password: string): RpcResponse[JsonNode] = let payload = %* [multiTransactionCommand, data, password] result = core.callPrivateRPC("wallet_createMultiTransaction", payload) diff --git a/vendor/status-go b/vendor/status-go index 48ebe24f8e..b9d083c6d5 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 48ebe24f8e986c174e8e933404b81a7d6a3beb3e +Subproject commit b9d083c6d51befb85978ee3b4927c90bcf092625