diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Modules-Package.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Modules-Package.xcscheme
index dcb8e093..c47c0684 100644
--- a/.swiftpm/xcode/xcshareddata/xcschemes/Modules-Package.xcscheme
+++ b/.swiftpm/xcode/xcshareddata/xcschemes/Modules-Package.xcscheme
@@ -771,6 +771,20 @@
ReferencedContainer = "container:">
+
+
+
+
{
/// Returns item if bounded as associated value.
- var item: T? {
+ public var item: T? {
switch self {
case let .insert(newItem):
return newItem
@@ -46,6 +46,41 @@ public enum DataProviderChange {
}
}
+enum DataProviderDiff {
+ /// New items has been added.
+ /// Item is passed as associated value.
+ case insert(newItem: T)
+
+ /// Existing item has been updated
+ /// Item is passed as associated value.
+ case update(newItem: T)
+
+ /// New remote items.
+ case remote(newItem: T)
+
+ /// Existing local item
+ case local(newItem: T)
+
+ /// Existing item has been removed.
+ /// Identifier of the item is passed as associated value.
+ case delete(deletedIdentifier: String)
+
+ var item: T? {
+ switch self {
+ case let .remote(newItem):
+ return newItem
+ case let .local(newItem):
+ return nil
+ case .delete:
+ return nil
+ case let .insert(newItem):
+ return newItem
+ case let .update(newItem):
+ return newItem
+ }
+ }
+}
+
/**
* Struct designed to store options needed to describe how an observer should be handled by data provider.
*/
@@ -65,6 +100,12 @@ public struct DataProviderObserverOptions {
/// mechanism.
public var waitsInProgressSyncOnAdd: Bool
+ /// Asks data provider to notify observer if no difference from remote.
+ /// If this value is `false` (default value) then observer is only notified when
+ /// there are difference from remote source.
+ /// if this value is `true` and no diff observer will notify with local source values
+ public var notifyIfNoDiff: Bool
+
/// - parameters:
/// - alwaysNotifyOnRefresh: Asks data provider to notify observer in any case after
/// synchronization completes.
@@ -80,10 +121,12 @@ public struct DataProviderObserverOptions {
public init(
alwaysNotifyOnRefresh: Bool = false,
- waitsInProgressSyncOnAdd: Bool = true
+ waitsInProgressSyncOnAdd: Bool = true,
+ notifyIfNoDiff: Bool = false
) {
self.alwaysNotifyOnRefresh = alwaysNotifyOnRefresh
self.waitsInProgressSyncOnAdd = waitsInProgressSyncOnAdd
+ self.notifyIfNoDiff = notifyIfNoDiff
}
}
@@ -117,6 +160,10 @@ public struct StreamableProviderObserverOptions {
/// By default ```true```.
public var refreshWhenEmpty: Bool
+ /// Will notify just when local repository was updated.
+ /// By default ```false```.
+ public var notifyJustWhenUpdated: Bool
+
/// - parameters:
/// - alwaysNotifyOnRefresh: Asks data provider to notify observer in any case
/// after synchronization completes.
@@ -143,12 +190,14 @@ public struct StreamableProviderObserverOptions {
alwaysNotifyOnRefresh: Bool = false,
waitsInProgressSyncOnAdd: Bool = true,
initialSize: Int = 0,
- refreshWhenEmpty: Bool = true
+ refreshWhenEmpty: Bool = true,
+ notifyJustWhenUpdated: Bool = false
) {
self.alwaysNotifyOnRefresh = alwaysNotifyOnRefresh
self.waitsInProgressSyncOnAdd = waitsInProgressSyncOnAdd
self.initialSize = initialSize
self.refreshWhenEmpty = refreshWhenEmpty
+ self.notifyJustWhenUpdated = notifyJustWhenUpdated
}
}
diff --git a/Sources/RobinHood/Classes/DataProvider/Difference/ListDifferenceCalculator.swift b/Sources/RobinHood/Classes/DataProvider/Difference/ListDifferenceCalculator.swift
index 7f2e4720..12343276 100644
--- a/Sources/RobinHood/Classes/DataProvider/Difference/ListDifferenceCalculator.swift
+++ b/Sources/RobinHood/Classes/DataProvider/Difference/ListDifferenceCalculator.swift
@@ -140,6 +140,7 @@ public final class ListDifferenceCalculator: ListDifferenceCalc
switch change {
case let .insert(newItem):
insertItems[newItem.identifier] = newItem
+
case let .update(newItem):
if let oldItemIndex = allItems
.firstIndex(where: { $0.identifier == newItem.identifier })
diff --git a/Sources/RobinHood/Classes/DataProvider/SingleValueProvider/AnySingleValueProvider.swift b/Sources/RobinHood/Classes/DataProvider/SingleValueProvider/AnySingleValueProvider.swift
new file mode 100644
index 00000000..9afb68b5
--- /dev/null
+++ b/Sources/RobinHood/Classes/DataProvider/SingleValueProvider/AnySingleValueProvider.swift
@@ -0,0 +1,53 @@
+import Foundation
+
+public class AnySingleValueProvider: SingleValueProviderProtocol {
+ public typealias Model = T
+
+ private let fetchClosure: (((Result?) -> Void)?) -> CompoundOperationWrapper
+
+ private let addObserverClosure: (
+ AnyObject,
+ DispatchQueue?,
+ @escaping ([DataProviderChange]) -> Void,
+ @escaping (Error) -> Void,
+ DataProviderObserverOptions
+ ) -> Void
+
+ private let removeObserverClosure: (AnyObject) -> Void
+
+ private let refreshClosure: () -> Void
+
+ public var executionQueue: OperationQueue
+
+ public init(_ dataProvider: U) where U.Model == Model {
+ fetchClosure = dataProvider.fetch(with:)
+ addObserverClosure = dataProvider.addObserver
+ removeObserverClosure = dataProvider.removeObserver
+ refreshClosure = dataProvider.refresh
+ executionQueue = dataProvider.executionQueue
+ }
+
+ public func fetch(with completionBlock: ((Result?) -> Void)?)
+ -> CompoundOperationWrapper
+ {
+ fetchClosure(completionBlock)
+ }
+
+ public func addObserver(
+ _ observer: AnyObject,
+ deliverOn queue: DispatchQueue?,
+ executing updateBlock: @escaping ([DataProviderChange]) -> Void,
+ failing failureBlock: @escaping (Error) -> Void,
+ options: DataProviderObserverOptions
+ ) {
+ addObserverClosure(observer, queue, updateBlock, failureBlock, options)
+ }
+
+ public func removeObserver(_ observer: AnyObject) {
+ removeObserverClosure(observer)
+ }
+
+ public func refresh() {
+ refreshClosure()
+ }
+}
diff --git a/Sources/SSFAccountManagment/Classes/Import/AccountImportService.swift b/Sources/SSFAccountManagment/Classes/Import/AccountImportService.swift
index fd250218..f50c4689 100644
--- a/Sources/SSFAccountManagment/Classes/Import/AccountImportService.swift
+++ b/Sources/SSFAccountManagment/Classes/Import/AccountImportService.swift
@@ -84,6 +84,7 @@ public final class AccountImportService {
case let .failure(error):
continuation.resume(throwing: error)
+
case .none:
let error = BaseOperationError.parentOperationCancelled
continuation.resume(throwing: error)
@@ -124,6 +125,7 @@ extension AccountImportService: AccountCreatable {
case let .failure(error):
continuation.resume(throwing: error)
+
case .none:
let error = BaseOperationError.parentOperationCancelled
continuation.resume(throwing: error)
@@ -195,6 +197,7 @@ extension AccountImportService: AccountImportable {
case let .failure(error):
continuation.resume(throwing: error)
+
case .none:
let error = BaseOperationError.parentOperationCancelled
continuation.resume(throwing: error)
diff --git a/Sources/SSFAssetManagment/AssetFetching/ChainAssetsFetchingService.swift b/Sources/SSFAssetManagment/AssetFetching/ChainAssetsFetchingService.swift
index 66a9ac15..d1dad4a8 100644
--- a/Sources/SSFAssetManagment/AssetFetching/ChainAssetsFetchingService.swift
+++ b/Sources/SSFAssetManagment/AssetFetching/ChainAssetsFetchingService.swift
@@ -128,11 +128,19 @@ private extension ChainAssetsFetchingService {
func sortByPrice(chainAssets: [ChainAsset], order: AssetSortOrder) -> [ChainAsset] {
chainAssets.sorted {
+ let firstPriceDataSorted = $0.asset.priceData
+ .sorted { $0.currencyId < $1.currencyId }
+ let firstPriceString = firstPriceDataSorted.first?.price ?? ""
+ let firstPrice = Decimal(string: firstPriceString)
+ let secondPriceDataSorted = $1.asset.priceData
+ .sorted { $0.currencyId < $1.currencyId }
+ let secondPriceString = secondPriceDataSorted.first?.price ?? ""
+ let secondPrice = Decimal(string: secondPriceString)
switch order {
case .ascending:
- return $0.asset.price ?? 0 < $1.asset.price ?? 0
+ return firstPrice ?? 0 < secondPrice ?? 0
case .descending:
- return $0.asset.price ?? 0 > $1.asset.price ?? 0
+ return secondPrice ?? 0 > firstPrice ?? 0
}
}
}
diff --git a/Sources/SSFAssetManagmentStorage/ChainModelMapper.swift b/Sources/SSFAssetManagmentStorage/ChainModelMapper.swift
index f681a94b..6c28b3ec 100644
--- a/Sources/SSFAssetManagmentStorage/ChainModelMapper.swift
+++ b/Sources/SSFAssetManagmentStorage/ChainModelMapper.swift
@@ -8,16 +8,28 @@ public enum ChainModelMapperError: Error {
case missingChainId
}
-public final class ChainModelMapper {
+public final class ChainModelMapper: CoreDataMapperProtocol {
public var entityIdentifierFieldName: String { #keyPath(CDChain.chainId) }
public typealias DataProviderModel = ChainModel
public typealias CoreDataEntity = CDChain
- private let apiKeyInjector: ApiKeyInjector
+ public init() {}
- public init(apiKeyInjector: ApiKeyInjector) {
- self.apiKeyInjector = apiKeyInjector
+ private func createPriceData(from entity: CDPriceData) -> PriceData? {
+ guard let currencyId = entity.currencyId,
+ let priceId = entity.priceId,
+ let price = entity.price else
+ {
+ return nil
+ }
+ return PriceData(
+ currencyId: currencyId,
+ priceId: priceId,
+ price: price,
+ fiatDayChange: Decimal(string: entity.fiatDayByChange ?? ""),
+ coingeckoPriceId: entity.coingeckoPriceId
+ )
}
private func createAsset(from entity: CDAsset) -> AssetModel? {
@@ -61,30 +73,39 @@ public final class ChainModelMapper {
priceProvider = PriceProvider(type: type, id: id, precision: Int16(precision))
}
+ let priceDatas: [PriceData] = entity.priceData.or([]).compactMap { data in
+ guard let priceData = data as? CDPriceData else {
+ return nil
+ }
+ return createPriceData(from: priceData)
+ }
+ let tokenProperties = TokenProperties(
+ type: createChainAssetModelType(from: entity.type),
+ isNative: entity.isNative,
+ staking: staking
+ )
return AssetModel(
id: id,
name: name,
symbol: symbol,
- isUtility: entity.isUtility,
precision: UInt16(bitPattern: entity.precision),
icon: entity.icon,
- substrateType: createChainAssetModelType(from: entity.type),
- ethereumType: nil,
- tokenProperties: TokenProperties(),
- price: entity.price as Decimal?,
- priceId: nil,
- coingeckoPriceId: nil,
- priceProvider: nil
+ tokenProperties: tokenProperties,
+ existentialDeposit: entity.existentialDeposit,
+ isUtility: entity.isUtility,
+ purchaseProviders: purchaseProviders,
+ ethereumType: createEthereumAssetType(from: entity.ethereumType),
+ priceProvider: priceProvider,
+ coingeckoPriceId: entity.priceId,
+ priceData: priceDatas
)
}
- private func createChainNode(from entity: CDChainNode, chainId: String) -> ChainNodeModel {
+ private func createChainNode(from entity: CDChainNode, chainId _: String) -> ChainNodeModel {
let apiKey: ChainNodeModel.ApiKey?
- if let keyName = entity.apiKeyName,
- let nodeApiKey = apiKeyInjector.getNodeApiKey(for: chainId, apiKeyName: keyName)
- {
- apiKey = ChainNodeModel.ApiKey(queryName: nodeApiKey, keyName: keyName)
+ if let queryName = entity.apiQueryName, let keyName = entity.apiKeyName {
+ apiKey = ChainNodeModel.ApiKey(queryName: queryName, keyName: keyName)
} else {
apiKey = nil
}
@@ -101,22 +122,69 @@ public final class ChainModelMapper {
from model: ChainModel,
context: NSManagedObjectContext
) {
- let assets = (model.tokens.tokens ?? []).map {
+ let tokens: Set = model.tokens.tokens ?? []
+ let assets = tokens.compactMap { assetModel in
let assetEntity = CDAsset(context: context)
- assetEntity.id = $0.id ?? "0"
- assetEntity.icon = $0.icon
- assetEntity.precision = Int16(bitPattern: $0.precision)
- assetEntity.price = $0.price as NSDecimalNumber?
- assetEntity.symbol = $0.symbol
- assetEntity.name = $0.name
- assetEntity.type = $0.substrateType?.rawValue
+ assetEntity.id = assetModel.id
+ assetEntity.icon = assetModel.icon
+ assetEntity.precision = Int16(bitPattern: assetModel.precision)
+ assetEntity.priceId = assetModel.coingeckoPriceId
+ assetEntity.symbol = assetModel.symbol
+ assetEntity.existentialDeposit = assetModel.existentialDeposit
+ assetEntity.color = assetModel.tokenProperties?.color
+ assetEntity.name = assetModel.name
+ assetEntity.currencyId = assetModel.tokenProperties?.currencyId
+ assetEntity.type = assetModel.tokenProperties?.type?.rawValue
+ assetEntity.isUtility = assetModel.isUtility
+ assetEntity.isNative = assetModel.tokenProperties?.isNative ?? false
+ assetEntity.staking = assetModel.tokenProperties?.staking?.rawValue
+ assetEntity.ethereumType = assetModel.ethereumType?.rawValue
let priceProviderContext = CDPriceProvider(context: context)
+ priceProviderContext.type = assetModel.priceProvider?.type.rawValue
+ priceProviderContext.id = assetModel.priceProvider?.id
+ if let precision = assetModel.priceProvider?.precision {
+ priceProviderContext.precision = "\(precision)"
+ }
assetEntity.priceProvider = priceProviderContext
+ let purchaseProviders: [String]? = assetModel.purchaseProviders?.map(\.rawValue)
+ assetEntity.purchaseProviders = purchaseProviders
+
+ let priceData: [CDPriceData] = assetModel.priceData.map { priceData in
+ let entity = CDPriceData(context: context)
+ entity.currencyId = priceData.currencyId
+ entity.priceId = priceData.priceId
+ entity.price = priceData.price
+ entity.fiatDayByChange = String("\(priceData.fiatDayChange)")
+ entity.coingeckoPriceId = priceData.coingeckoPriceId
+ return entity
+ }
+
+ if let oldAssets = entity.assets as? Set,
+ let updatedAsset = oldAssets.first(where: { cdAsset in
+ cdAsset.id == assetModel.id
+ })
+ {
+ if let oldPrices = updatedAsset.priceData as? Set {
+ for cdPriceData in oldPrices {
+ if !priceData.contains(where: { $0.currencyId == cdPriceData.currencyId }) {
+ context.delete(cdPriceData)
+ }
+ }
+ }
+ }
+ assetEntity.priceData = Set(priceData) as NSSet
+
return assetEntity
}
+ if let oldAssets = entity.assets as? Set {
+ for cdAsset in oldAssets {
+ context.delete(cdAsset)
+ }
+ }
+
entity.assets = Set(assets) as NSSet
}
@@ -139,6 +207,7 @@ public final class ChainModelMapper {
nodeEntity.url = node.url
nodeEntity.name = node.name
+ nodeEntity.apiQueryName = node.apikey?.queryName
nodeEntity.apiKeyName = node.apikey?.keyName
return nodeEntity
@@ -180,6 +249,7 @@ public final class ChainModelMapper {
nodeEntity.url = node.url
nodeEntity.name = node.name
+ nodeEntity.apiQueryName = node.apikey?.queryName
nodeEntity.apiKeyName = node.apikey?.keyName
return nodeEntity
@@ -230,6 +300,7 @@ public final class ChainModelMapper {
nodeEntity.url = node.url
nodeEntity.name = node.name
+ nodeEntity.apiQueryName = node.apikey?.queryName
nodeEntity.apiKeyName = node.apikey?.keyName
entity.selectedNode = nodeEntity
@@ -238,14 +309,12 @@ public final class ChainModelMapper {
private func createExternalApi(from entity: CDChain) -> ChainModel.ExternalApiSet? {
var staking: ChainModel.BlockExplorer?
if let type = entity.stakingApiType, let url = entity.stakingApiUrl {
- let apiKey = getBlockExplorerApiKey(for: type, chainId: entity.chainId)
- staking = ChainModel.BlockExplorer(type: type, url: url, apiKey: apiKey)
+ staking = ChainModel.BlockExplorer(type: type, url: url)
}
var history: ChainModel.BlockExplorer?
if let type = entity.historyApiType, let url = entity.historyApiUrl {
- let apiKey = getBlockExplorerApiKey(for: type, chainId: entity.chainId)
- history = ChainModel.BlockExplorer(type: type, url: url, apiKey: apiKey)
+ history = ChainModel.BlockExplorer(type: type, url: url)
}
var crowdloans: ChainModel.ExternalResource?
@@ -253,6 +322,11 @@ public final class ChainModelMapper {
crowdloans = ChainModel.ExternalResource(type: type, url: url)
}
+ var pricing: ChainModel.BlockExplorer?
+ if let type = entity.pricingApiType, let url = entity.pricingApiUrl {
+ pricing = ChainModel.BlockExplorer(type: type, url: url)
+ }
+
let explorers = createExplorers(from: entity)
if staking != nil || history != nil || crowdloans != nil || explorers != nil {
@@ -260,7 +334,8 @@ public final class ChainModelMapper {
staking: staking,
history: history,
crowdloans: crowdloans,
- explorers: explorers
+ explorers: explorers,
+ pricing: pricing
)
} else {
return nil
@@ -268,35 +343,43 @@ public final class ChainModelMapper {
}
private func createXcmConfig(from entity: CDChain) -> XcmChain? {
- guard let versionRaw = entity.xcmConfig?.xcmVersion else {
+ guard let versionRaw = entity.xcmConfig?.xcmVersion,
+ let availableAssets = entity.xcmConfig?.availableAssets,
+ let availableDestinations = entity.xcmConfig?.availableDestinations else
+ {
return nil
}
let version = XcmCallFactoryVersion(rawValue: versionRaw)
- let availableXcmAssets = entity.xcmConfig?.availableAssets?
- .allObjects as? [CDXcmAvailableAsset] ?? []
- let assets: [XcmAvailableAsset] = availableXcmAssets.compactMap { entity in
- guard let id = entity.id, let symbol = entity.symbol else {
+ let assets: [XcmAvailableAsset] = availableAssets.compactMap { entity in
+ guard let entity = entity as? CDXcmAvailableAsset,
+ let id = entity.id,
+ let symbol = entity.symbol else
+ {
return nil
}
- return XcmAvailableAsset(id: id, symbol: symbol)
+ return XcmAvailableAsset(id: id, symbol: symbol, minAmount: nil)
}
- let availableXcmAssetDestinations = entity.xcmConfig?.availableDestinations?
- .allObjects as? [CDXcmAvailableDestination] ?? []
- let destinations: [XcmAvailableDestination] = availableXcmAssetDestinations.compactMap {
- guard let chainId = $0.chainId else {
+ let destinations: [XcmAvailableDestination] = availableDestinations.compactMap { entity in
+ guard let entity = entity as? CDXcmAvailableDestination,
+ let chainId = entity.chainId,
+ let assetsEntities = entity.assets else
+ {
return nil
}
- let assetsEntities = $0.assets?.allObjects as? [CDXcmAvailableAsset] ?? []
+
let assets: [XcmAvailableAsset] = assetsEntities.compactMap { entity in
- guard let id = entity.id, let symbol = entity.symbol else {
+ guard let entity = entity as? CDXcmAvailableAsset,
+ let id = entity.id,
+ let symbol = entity.symbol else
+ {
return nil
}
- return XcmAvailableAsset(id: id, symbol: symbol)
+ return XcmAvailableAsset(id: id, symbol: symbol, minAmount: entity.minAmount)
}
return XcmAvailableDestination(
chainId: chainId,
- bridgeParachainId: $0.bridgeParachainId,
+ bridgeParachainId: entity.bridgeParachainId,
assets: assets
)
}
@@ -353,14 +436,17 @@ public final class ChainModelMapper {
}
private func updateExternalApis(in entity: CDChain, from apis: ChainModel.ExternalApiSet?) {
- entity.stakingApiType = apis?.staking?.type.rawValue
+ entity.stakingApiType = apis?.staking?.type?.rawValue
entity.stakingApiUrl = apis?.staking?.url
- entity.historyApiType = apis?.history?.type.rawValue
+ entity.historyApiType = apis?.history?.type?.rawValue
entity.historyApiUrl = apis?.history?.url
entity.crowdloansApiType = apis?.crowdloans?.type
entity.crowdloansApiUrl = apis?.crowdloans?.url
+
+ entity.pricingApiType = apis?.pricing?.type?.rawValue
+ entity.pricingApiUrl = apis?.pricing?.url
}
private func createChainAssetModelType(from rawValue: String?) -> SubstrateAssetType? {
@@ -391,7 +477,9 @@ public final class ChainModelMapper {
let configEntity = CDChainXcmConfig(context: context)
configEntity.xcmVersion = xcmConfig.xcmVersion?.rawValue
- configEntity.destWeightIsPrimitive = xcmConfig.destWeightIsPrimitive ?? false
+ if let destWeightIsPrimitive = xcmConfig.destWeightIsPrimitive {
+ configEntity.destWeightIsPrimitive = destWeightIsPrimitive
+ }
let availableAssets = xcmConfig.availableAssets.map {
let entity = CDXcmAvailableAsset(context: context)
@@ -401,7 +489,7 @@ public final class ChainModelMapper {
}
configEntity.availableAssets = Set(availableAssets) as NSSet
- let destinationEntities = xcmConfig.availableDestinations.compactMap {
+ let destinationEntities = xcmConfig.availableDestinations.map {
let destinationEntity = CDXcmAvailableDestination(context: context)
destinationEntity.chainId = $0.chainId
@@ -409,6 +497,7 @@ public final class ChainModelMapper {
let entity = CDXcmAvailableAsset(context: context)
entity.id = $0.id
entity.symbol = $0.symbol
+ entity.minAmount = $0.minAmount
return entity
}
destinationEntity.assets = Set(availableAssets) as NSSet
@@ -421,17 +510,6 @@ public final class ChainModelMapper {
entity.xcmConfig = configEntity
}
- private func getBlockExplorerApiKey(for type: String, chainId: ChainModel.Id?) -> String? {
- guard let blockExplorerType = BlockExplorerType(rawValue: type),
- let chainId else
- {
- return nil
- }
- return apiKeyInjector.getBlockExplorerKey(for: blockExplorerType, chainId: chainId)
- }
-}
-
-extension ChainModelMapper: CoreDataMapperProtocol {
public func transform(entity: CDChain) throws -> ChainModel {
guard let chainId = entity.chainId else {
throw ChainModelMapperError.missingChainId
@@ -478,19 +556,17 @@ extension ChainModelMapper: CoreDataMapperProtocol {
let externalApiSet = createExternalApi(from: entity)
let xcm = createXcmConfig(from: entity)
- var rank: UInt16?
- if let rankString = entity.rank {
- rank = UInt16(rankString)
- }
-
let chainModel = ChainModel(
- rank: rank,
disabled: entity.disabled,
chainId: chainId,
parentId: entity.parentId,
- paraId: nil,
name: entity.name!,
- tokens: ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: []),
+ tokens: ChainRemoteTokens(
+ type: .config,
+ whitelist: nil,
+ utilityId: nil,
+ tokens: []
+ ),
xcm: xcm,
nodes: Set(nodes),
types: types,
@@ -500,7 +576,14 @@ extension ChainModelMapper: CoreDataMapperProtocol {
selectedNode: selectedNode,
customNodes: customNodesSet,
iosMinAppVersion: entity.minimalAppVersion,
- properties: ChainProperties(addressPrefix: String(entity.addressPrefix))
+ properties: ChainProperties(
+ addressPrefix: String(entity.addressPrefix),
+ rank: entity.rank,
+ paraId: entity.paraId,
+ ethereumBased: entity.isEthereumBased,
+ crowdloans: entity.hasCrowdloans
+ ),
+ identityChain: entity.identityChain
)
let assetsArray: [AssetModel] = entity.assets.or([]).compactMap { anyAsset in
@@ -527,16 +610,18 @@ extension ChainModelMapper: CoreDataMapperProtocol {
from model: ChainModel,
using context: NSManagedObjectContext
) throws {
- if let rank = model.rank {
+ if let rank = model.properties.rank {
entity.rank = "\(rank)"
}
entity.disabled = model.disabled
entity.chainId = model.chainId
+ entity.paraId = model.properties.paraId
entity.parentId = model.parentId
entity.name = model.name
entity.types = model.types?.url
entity.typesOverrideCommon = model.types.map { NSNumber(value: $0.overridesCommon) }
+ entity.addressPrefix = Int16(bitPattern: UInt16(model.properties.addressPrefix) ?? 69)
entity.icon = model.icon
entity.isEthereumBased = model.isEthereumBased
entity.isTestnet = model.isTestnet
@@ -544,7 +629,7 @@ extension ChainModelMapper: CoreDataMapperProtocol {
entity.isTipRequired = model.isTipRequired
entity.minimalAppVersion = model.iosMinAppVersion
entity.options = model.options?.map(\.rawValue) as? NSArray
-
+ entity.identityChain = model.identityChain
updateEntityAsset(for: entity, from: model, context: context)
updateEntityNodes(for: entity, from: model, context: context)
updateExternalApis(in: entity, from: model.externalApi)
diff --git a/Sources/SSFAssetManagmentStorage/ChainRepositoryFactory.swift b/Sources/SSFAssetManagmentStorage/ChainRepositoryFactory.swift
new file mode 100644
index 00000000..2c6908c0
--- /dev/null
+++ b/Sources/SSFAssetManagmentStorage/ChainRepositoryFactory.swift
@@ -0,0 +1,36 @@
+import Foundation
+import RobinHood
+import SSFModels
+import SSFUtils
+
+public final class ChainRepositoryFactory {
+ let storageFacade: StorageFacadeProtocol
+
+ public init(storageFacade: StorageFacadeProtocol = SubstrateDataStorageFacade.shared!) {
+ self.storageFacade = storageFacade
+ }
+
+ public func createRepository(
+ for filter: NSPredicate? = nil,
+ sortDescriptors: [NSSortDescriptor] = []
+ ) -> CoreDataRepository {
+ let mapper = ChainModelMapper()
+ return storageFacade.createRepository(
+ filter: filter,
+ sortDescriptors: sortDescriptors,
+ mapper: AnyCoreDataMapper(mapper)
+ )
+ }
+
+ public func createAsyncRepository(
+ for filter: NSPredicate? = nil,
+ sortDescriptors: [NSSortDescriptor] = []
+ ) -> AsyncCoreDataRepositoryDefault {
+ let mapper = ChainModelMapper()
+ return storageFacade.createAsyncRepository(
+ filter: filter,
+ sortDescriptors: sortDescriptors,
+ mapper: AnyCoreDataMapper(mapper)
+ )
+ }
+}
diff --git a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/.xccurrentversion b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/.xccurrentversion
index 09ac93ea..f929d5f6 100644
--- a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/.xccurrentversion
+++ b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/.xccurrentversion
@@ -3,6 +3,6 @@
_XCCurrentVersionName
- SubstrateDataModel_v5.xcdatamodel
+ SubstrateDataModel_v8.xcdatamodel
diff --git a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel.xcdatamodel/contents b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel.xcdatamodel/contents
index 94a62ff8..6ab456d3 100644
--- a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel.xcdatamodel/contents
+++ b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel.xcdatamodel/contents
@@ -1,5 +1,5 @@
-
+
@@ -75,6 +75,10 @@
+
+
+
+
@@ -92,4 +96,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v2.xcdatamodel/contents b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v2.xcdatamodel/contents
index 931add12..f18cd528 100644
--- a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v2.xcdatamodel/contents
+++ b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v2.xcdatamodel/contents
@@ -1,5 +1,5 @@
-
+
@@ -121,6 +121,10 @@
+
+
+
+
diff --git a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v3.xcdatamodel/contents b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v3.xcdatamodel/contents
index 47df51a3..e6e3fdc5 100644
--- a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v3.xcdatamodel/contents
+++ b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v3.xcdatamodel/contents
@@ -1,5 +1,5 @@
-
+
@@ -129,6 +129,10 @@
+
+
+
+
diff --git a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v4.xcdatamodel/contents b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v4.xcdatamodel/contents
index fd56e97c..08890152 100644
--- a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v4.xcdatamodel/contents
+++ b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v4.xcdatamodel/contents
@@ -123,6 +123,10 @@
+
+
+
+
diff --git a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v5.xcdatamodel/contents b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v5.xcdatamodel/contents
index d73ca316..792771c0 100644
--- a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v5.xcdatamodel/contents
+++ b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v5.xcdatamodel/contents
@@ -37,7 +37,10 @@
+
+
+
@@ -136,6 +139,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v6.xcdatamodel/contents b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v6.xcdatamodel/contents
new file mode 100644
index 00000000..74c328ae
--- /dev/null
+++ b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v6.xcdatamodel/contents
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v7.xcdatamodel/contents b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v7.xcdatamodel/contents
new file mode 100644
index 00000000..251f73f7
--- /dev/null
+++ b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v7.xcdatamodel/contents
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v8.xcdatamodel/contents b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v8.xcdatamodel/contents
new file mode 100644
index 00000000..a82226c7
--- /dev/null
+++ b/Sources/SSFAssetManagmentStorage/Resources/SubstrateDataModel.xcdatamodeld/SubstrateDataModel_v8.xcdatamodel/contents
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Sources/SSFChainlinkProvider/ChainlinkOperationFactory.swift b/Sources/SSFChainlinkProvider/ChainlinkOperationFactory.swift
new file mode 100644
index 00000000..79e47803
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/ChainlinkOperationFactory.swift
@@ -0,0 +1,98 @@
+import Foundation
+import RobinHood
+import SSFChainRegistry
+import SSFModels
+import SSFNetwork
+import SSFUtils
+import Web3
+import Web3ContractABI
+
+public protocol ChainlinkOperationFactoryProtocol {
+ func priceCall(for chainAsset: ChainAsset, connection: Web3.Eth?) -> BaseOperation?
+}
+
+public final class ChainlinkOperationFactory: ChainlinkOperationFactoryProtocol {
+ private let chainRegistry: ChainRegistryProtocol
+
+ public init(chainRegistry: ChainRegistryProtocol) {
+ self.chainRegistry = chainRegistry
+ }
+
+ public func priceCall(for chainAsset: ChainAsset, connection: Web3.Eth?) -> BaseOperation? {
+ let operation = ManualOperation()
+
+ do {
+ guard let contract = chainAsset.asset.priceProvider?.id else {
+ throw ConvenienceError(error: "Missing price contract address")
+ }
+ guard let ws = connection else {
+ throw ConvenienceError(
+ error: "Can't get ethereum connection for chain: \(chainAsset.chain.name)"
+ )
+ }
+
+ let receiverAddress = try EthereumAddress(rawAddress: contract.hexToBytes())
+
+ let outputs: [ABI.Element.InOut] = [
+ ABI.Element.InOut(name: "roundId", type: .uint(bits: 80)),
+ ABI.Element.InOut(name: "answer", type: .int(bits: 256)),
+ ABI.Element.InOut(name: "startedAt", type: .int(bits: 256)),
+ ABI.Element.InOut(name: "updatedAt", type: .int(bits: 256)),
+ ABI.Element.InOut(name: "answeredInRound", type: .uint(bits: 80)),
+ ]
+ let method = ABI.Element.Function(
+ name: "latestRoundData",
+ inputs: [],
+ outputs: outputs,
+ constant: false,
+ payable: false
+ )
+ let priceCall = try EthereumCall(
+ to: receiverAddress,
+ value: EthereumQuantity(quantity: .zero),
+ data: EthereumData(ethereumValue: .string(method.methodString))
+ )
+ ws.call(call: priceCall, block: .latest) { resp in
+ switch resp.status {
+ case let .success(result):
+ let decoded = ABIDecoder.decode(
+ types: outputs,
+ data: Data(hex: result.hex())
+ )
+ guard let price: BigInt = decoded?[safe: 1] as? BigInt,
+ let precision = chainAsset.asset.priceProvider?.precision,
+ let priceDecimal = Decimal.fromSubstrateAmount(
+ BigUInt(price),
+ precision: precision
+ ) else
+ {
+ let error = ConvenienceError(error: "Decoding price error")
+ operation.result = .failure(error)
+ operation.finish()
+ return
+ }
+
+ let priceData = PriceData(
+ currencyId: "usd",
+ priceId: contract,
+ price: "\(priceDecimal)",
+ fiatDayChange: nil,
+ coingeckoPriceId: chainAsset.asset.coingeckoPriceId
+ )
+ operation.result = .success(priceData)
+ operation.finish()
+
+ case let .failure(error):
+ operation.result = .failure(error)
+ operation.finish()
+ }
+ }
+ } catch {
+ operation.result = .failure(error)
+ operation.finish()
+ return nil
+ }
+
+ return operation
+ }
+}
diff --git a/Sources/SSFChainlinkProvider/ChainlinkService.swift b/Sources/SSFChainlinkProvider/ChainlinkService.swift
new file mode 100644
index 00000000..a971ff2f
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/ChainlinkService.swift
@@ -0,0 +1,51 @@
+import RobinHood
+import SSFChainRegistry
+import SSFModels
+import SSFUtils
+import SSFPrices
+
+public final class ChainlinkService: PriceProviderServiceProtocol {
+ private let chainlinkOperationFactory: ChainlinkOperationFactoryProtocol
+ private let chainRegistry: ChainRegistryProtocol
+
+ public init(
+ chainlinkOperationFactory: ChainlinkOperationFactory,
+ chainRegistry: ChainRegistryProtocol
+ ) {
+ self.chainlinkOperationFactory = chainlinkOperationFactory
+ self.chainRegistry = chainRegistry
+ }
+
+ public func getPrices(for chainAssets: [ChainAsset], currencies: [Currency]) async -> [PriceData] {
+ let operations = await createChainlinkOperations(for: chainAssets, currencies: currencies)
+ return operations.compactMap {
+ try? $0.extractNoCancellableResultData()
+ }
+ }
+
+ private func createChainlinkOperations(
+ for chainAssets: [ChainAsset],
+ currencies: [Currency]
+ ) async -> [BaseOperation] {
+ guard currencies.count == 1, currencies.first?.id == Currency.defaultCurrency().id else {
+ return []
+ }
+ let chainlinkProvider = chainAssets.map { $0.chain }
+ .first(where: { $0.options?.contains(.chainlinkProvider) == true })
+ guard let provider = chainlinkProvider else {
+ return []
+ }
+ do {
+ let connection = try await chainRegistry.getEthereumConnection(for: provider)
+ let chainlinkPriceChainAsset = chainAssets
+ .filter { $0.asset.priceProvider?.type == .chainlink }
+
+ let operations = chainlinkPriceChainAsset
+ .map { chainlinkOperationFactory.priceCall(for: $0, connection: connection) }
+ return operations.compactMap { $0 }
+ } catch {
+ print("can't create ethereum connection for \(provider.name)")
+ return []
+ }
+ }
+}
diff --git a/Sources/SSFChainlinkProvider/eip_mew/ABI/ABI.swift b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABI.swift
new file mode 100644
index 00000000..643759a1
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABI.swift
@@ -0,0 +1,26 @@
+//
+// Created by Alex Vlasov on 25/10/2018.
+// Copyright © 2018 Alex Vlasov. All rights reserved.
+//
+
+import Foundation
+
+public struct ABI {}
+
+protocol ABIElementPropertiesProtocol {
+ var isStatic: Bool { get }
+ var isArray: Bool { get }
+ var isTuple: Bool { get }
+ var arraySize: ABI.Element.ArraySize { get }
+ var subtype: ABI.Element.ParameterType? { get }
+ var memoryUsage: UInt64 { get }
+ var emptyValue: Any { get }
+}
+
+protocol ABIEncoding {
+ var abiRepresentation: String { get }
+}
+
+protocol ABIValidation {
+ var isValid: Bool { get }
+}
diff --git a/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIDecoding.swift b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIDecoding.swift
new file mode 100644
index 00000000..7a9129f4
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIDecoding.swift
@@ -0,0 +1,323 @@
+//
+// Created by Alex Vlasov on 25/10/2018.
+// Copyright © 2018 Alex Vlasov. All rights reserved.
+//
+
+import BigInt
+import CryptoSwift
+import Foundation
+
+// swiftlint:disable identifier_name
+
+public struct ABIDecoder {}
+
+public extension ABIDecoder {
+ static func decode(types: [ABI.Element.InOut], data: Data) -> [AnyObject]? {
+ let params = types.compactMap { el -> ABI.Element.ParameterType in
+ el.type
+ }
+ return decode(types: params, data: data)
+ }
+
+ static func decode(types: [ABI.Element.ParameterType], data: Data) -> [AnyObject]? {
+ // print("Full data: \n" + data.toHexString())
+ var toReturn = [AnyObject]()
+ var consumed: UInt64 = 0
+ for i in 0 ..< types.count {
+ let (v, c) = decodeSignleType(type: types[i], data: data, pointer: consumed)
+ guard let valueUnwrapped = v, let consumedUnwrapped = c else { return nil }
+ toReturn.append(valueUnwrapped)
+ consumed += consumedUnwrapped
+ }
+ guard toReturn.count == types.count else { return nil }
+ return toReturn
+ }
+
+ // swiftlint:disable:next function_body_length cyclomatic_complexity
+ static func decodeSignleType(
+ type: ABI.Element.ParameterType,
+ data: Data,
+ pointer: UInt64 = 0
+ ) -> (value: AnyObject?, bytesConsumed: UInt64?) {
+ let (elData, nextPtr) = followTheData(type: type, data: data, pointer: pointer)
+ guard let elementItself = elData, let nextElementPointer = nextPtr else {
+ return (nil, nil)
+ }
+ switch type {
+ case let .uint(bits):
+ // print("Uint256 element itself: \n" + elementItself.toHexString())
+ guard elementItself.count >= 32 else { break }
+ let mod = BigUInt(1) << bits
+ let dataSlice = elementItself[0 ..< 32]
+ let v = BigUInt(dataSlice) % mod
+ // print("Uint256 element is: \n" + String(v))
+ return (v as AnyObject, type.memoryUsage)
+ case let .int(bits):
+ // print("Int256 element itself: \n" + elementItself.toHexString())
+ guard elementItself.count >= 32 else { break }
+ let mod = BigInt(1) << bits
+ let dataSlice = elementItself[0 ..< 32]
+ let v = BigInt.fromTwosComplement(data: dataSlice) % mod
+ // print("Int256 element is: \n" + String(v))
+ return (v as AnyObject, type.memoryUsage)
+ case .address:
+ // print("Address element itself: \n" + elementItself.toHexString())
+ guard elementItself.count >= 32 else { break }
+ let dataSlice = elementItself[12 ..< 32]
+ let address = Address(data: dataSlice)
+ // print("Address element is: \n" + String(address.address))
+ return (address as AnyObject, type.memoryUsage)
+ case .bool:
+ // print("Bool element itself: \n" + elementItself.toHexString())
+ guard elementItself.count >= 32 else { break }
+ let dataSlice = elementItself[0 ..< 32]
+ let v = BigUInt(dataSlice)
+ // print("Address element is: \n" + String(v))
+ if v == BigUInt(36) ||
+ v == BigUInt(32) ||
+ v == BigUInt(28) ||
+ v == BigUInt(1)
+ {
+ return (true as AnyObject, type.memoryUsage)
+ } else if v == BigUInt(35) ||
+ v == BigUInt(31) ||
+ v == BigUInt(27) ||
+ v == BigUInt(0)
+ {
+ return (false as AnyObject, type.memoryUsage)
+ }
+ case let .bytes(length):
+ // print("Bytes32 element itself: \n" + elementItself.toHexString())
+ guard elementItself.count >= 32 else { break }
+ let dataSlice = elementItself[0 ..< length]
+ // print("Bytes32 element is: \n" + String(dataSlice.toHexString()))
+ return (dataSlice as AnyObject, type.memoryUsage)
+ case .string:
+ // print("String element itself: \n" + elementItself.toHexString())
+ guard elementItself.count >= 32 else { break }
+ var dataSlice = elementItself[0 ..< 32]
+ let length = UInt64(BigUInt(dataSlice))
+ guard elementItself.count >= 32 + length else { break }
+ dataSlice = elementItself[32 ..< 32 + length]
+ guard let string = String(data: dataSlice, encoding: .utf8) else { break }
+ // print("String element is: \n" + String(string))
+ return (string as AnyObject, type.memoryUsage)
+ case .dynamicBytes:
+ // print("Bytes element itself: \n" + elementItself.toHexString())
+ guard elementItself.count >= 32 else { break }
+ var dataSlice = elementItself[0 ..< 32]
+ let length = UInt64(BigUInt(dataSlice))
+ guard elementItself.count >= 32 + length else { break }
+ dataSlice = elementItself[32 ..< 32 + length]
+ // print("Bytes element is: \n" + String(dataSlice.toHexString()))
+ return (dataSlice as AnyObject, type.memoryUsage)
+ case let .array(type: subType, length: length):
+ switch type.arraySize {
+ case .dynamicSize:
+ // print("Dynamic array element itself: \n" +
+ // elementItself.toHexString())
+ if subType.isStatic {
+ // uint[] like, expect length and elements
+ guard elementItself.count >= 32 else { break }
+ var dataSlice = elementItself[0 ..< 32]
+ let length = UInt64(BigUInt(dataSlice))
+ guard elementItself.count >= 32 + subType.memoryUsage * length else { break }
+ dataSlice = elementItself[32 ..< 32 + subType.memoryUsage * length]
+ var subpointer: UInt64 = 32
+ var toReturn = [AnyObject]()
+ for _ in 0 ..< length {
+ let (v, c) = decodeSignleType(
+ type: subType,
+ data: elementItself,
+ pointer: subpointer
+ )
+ guard let valueUnwrapped = v, let consumedUnwrapped = c else { break }
+ toReturn.append(valueUnwrapped)
+ subpointer += consumedUnwrapped
+ }
+ return (toReturn as AnyObject, type.memoryUsage)
+ } else {
+ // in principle is true for tuple[], so will work for string[] too
+ guard elementItself.count >= 32 else { break }
+ var dataSlice = elementItself[0 ..< 32]
+ let length = UInt64(BigUInt(dataSlice))
+ guard elementItself.count >= 32 else { break }
+ dataSlice = Data(elementItself[32 ..< elementItself.count])
+ var subpointer: UInt64 = 0
+ var toReturn = [AnyObject]()
+ // print("Dynamic array sub element itself: \n" +
+ // dataSlice.toHexString())
+ for _ in 0 ..< length {
+ let (v, c) = decodeSignleType(
+ type: subType,
+ data: dataSlice,
+ pointer: subpointer
+ )
+ guard let valueUnwrapped = v, let consumedUnwrapped = c else { break }
+ toReturn.append(valueUnwrapped)
+ subpointer += consumedUnwrapped
+ }
+ return (toReturn as AnyObject, nextElementPointer)
+ }
+ case let .staticSize(staticLength):
+ // print("Static array element itself: \n" +
+ // elementItself.toHexString())
+ guard length == staticLength else { break }
+ var toReturn = [AnyObject]()
+ var consumed: UInt64 = 0
+ for _ in 0 ..< length {
+ let (v, c) = decodeSignleType(
+ type: subType,
+ data: elementItself,
+ pointer: consumed
+ )
+ guard let valueUnwrapped = v,
+ let consumedUnwrapped = c else { return (nil, nil) }
+ toReturn.append(valueUnwrapped)
+ consumed += consumedUnwrapped
+ }
+ if subType.isStatic {
+ return (toReturn as AnyObject, consumed)
+ } else {
+ return (toReturn as AnyObject, nextElementPointer)
+ }
+ case .notArray:
+ break
+ }
+ case let .tuple(types: subTypes):
+ // print("Tuple element itself: \n" + elementItself.toHexString())
+ var toReturn = [AnyObject]()
+ var consumed: UInt64 = 0
+ for i in 0 ..< subTypes.count {
+ let (v, c) = decodeSignleType(
+ type: subTypes[i],
+ data: elementItself,
+ pointer: consumed
+ )
+ guard let valueUnwrapped = v, let consumedUnwrapped = c else { return (nil, nil) }
+ toReturn.append(valueUnwrapped)
+ consumed += consumedUnwrapped
+ }
+ // print("Tuple element is: \n" + String(describing: toReturn))
+ if type.isStatic {
+ return (toReturn as AnyObject, consumed)
+ } else {
+ return (toReturn as AnyObject, nextElementPointer)
+ }
+ case .function:
+ // print("Function element itself: \n" + elementItself.toHexString())
+ guard elementItself.count >= 32 else { break }
+ let dataSlice = elementItself[8 ..< 32]
+ // print("Function element is: \n" + String(dataSlice.toHexString()))
+ return (dataSlice as AnyObject, type.memoryUsage)
+ }
+ return (nil, nil)
+ }
+
+ fileprivate static func followTheData(
+ type: ABI.Element.ParameterType,
+ data: Data,
+ pointer: UInt64 = 0
+ ) -> (elementEncoding: Data?, nextElementPointer: UInt64?) {
+ // print("Follow the data: \n" + data.toHexString())
+ // print("At pointer: \n" + String(pointer))
+ if type.isStatic {
+ guard data.count >= pointer + type.memoryUsage else { return (nil, nil) }
+ let elementItself = data[pointer ..< pointer + type.memoryUsage]
+ let nextElement = pointer + type.memoryUsage
+ // print("Got element itself: \n" + elementItself.toHexString())
+ // print("Next element pointer: \n" + String(nextElement))
+ return (Data(elementItself), nextElement)
+ } else {
+ guard data.count >= pointer + type.memoryUsage else { return (nil, nil) }
+ let dataSlice = data[pointer ..< pointer + type.memoryUsage]
+ let bn = BigUInt(dataSlice)
+ if bn > UINT64_MAX || bn >= data.count {
+ // there are ERC20 contracts that use bytes32 intead of string. Let's be optimistic
+ // and return some data
+ if case .string = type {
+ let nextElement = pointer + type.memoryUsage
+ let preambula = BigUInt(32).abiEncode(bits: 256)!
+ return (preambula + Data(dataSlice), nextElement)
+ } else if case .dynamicBytes = type {
+ let nextElement = pointer + type.memoryUsage
+ let preambula = BigUInt(32).abiEncode(bits: 256)!
+ return (preambula + Data(dataSlice), nextElement)
+ }
+ return (nil, nil)
+ }
+ let elementPointer = UInt64(bn)
+ let elementItself = data[elementPointer ..< UInt64(data.count)]
+ let nextElement = pointer + type.memoryUsage
+ // print("Got element itself: \n" + elementItself.toHexString())
+ // print("Next element pointer: \n" + String(nextElement))
+ return (Data(elementItself), nextElement)
+ }
+ }
+
+ static func decodeLog(
+ event: ABI.Element.Event,
+ eventLogTopics: [Data],
+ eventLogData: Data
+ ) -> [String: Any]? {
+ if event.topic != eventLogTopics[0], !event.anonymous {
+ return nil
+ }
+ var eventContent = [String: Any]()
+ eventContent["name"] = event.name
+ let logs = eventLogTopics
+ let dataForProcessing = eventLogData
+ let indexedInputs = event.inputs.filter { inp -> Bool in
+ inp.indexed
+ }
+ if logs.count == 1, !indexedInputs.isEmpty {
+ return nil
+ }
+ let nonIndexedInputs = event.inputs.filter { inp -> Bool in
+ !inp.indexed
+ }
+ let nonIndexedTypes = nonIndexedInputs.compactMap { inp -> ABI.Element.ParameterType in
+ inp.type
+ }
+ guard logs.count == indexedInputs.count + 1 else { return nil }
+ var indexedValues = [AnyObject]()
+ for i in 0 ..< indexedInputs.count {
+ let data = logs[i + 1]
+ let input = indexedInputs[i]
+ if !input.type.isStatic || input.type.isArray || input.type.memoryUsage != 32 {
+ let (v, _) = ABIDecoder.decodeSignleType(type: .bytes(length: 32), data: data)
+ guard let valueUnwrapped = v else { return nil }
+ indexedValues.append(valueUnwrapped)
+ } else {
+ let (v, _) = ABIDecoder.decodeSignleType(type: input.type, data: data)
+ guard let valueUnwrapped = v else { return nil }
+ indexedValues.append(valueUnwrapped)
+ }
+ }
+ let v = ABIDecoder.decode(types: nonIndexedTypes, data: dataForProcessing)
+ guard let nonIndexedValues = v else { return nil }
+ var indexedInputCounter = 0
+ var nonIndexedInputCounter = 0
+ for i in 0 ..< event.inputs.count {
+ let el = event.inputs[i]
+ if el.indexed {
+ let name = "\(i)"
+ let value = indexedValues[indexedInputCounter]
+ eventContent[name] = value
+ if el.name != "" {
+ eventContent[el.name] = value
+ }
+ indexedInputCounter += 1
+ } else {
+ let name = "\(i)"
+ let value = nonIndexedValues[nonIndexedInputCounter]
+ eventContent[name] = value
+ if el.name != "" {
+ eventContent[el.name] = value
+ }
+ nonIndexedInputCounter += 1
+ }
+ }
+ return eventContent
+ }
+}
diff --git a/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIElements.swift b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIElements.swift
new file mode 100644
index 00000000..07ba2e1f
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIElements.swift
@@ -0,0 +1,296 @@
+//
+// Created by Alex Vlasov on 25/10/2018.
+// Copyright © 2018 Alex Vlasov. All rights reserved.
+//
+
+import Foundation
+import Web3ContractABI
+
+public extension ABI {
+ // JSON Decoding
+ struct Input: Decodable {
+ public var name: String?
+ public var type: String
+ public var indexed: Bool?
+ public var components: [Input]?
+ }
+
+ struct Output: Decodable {
+ public var name: String?
+ public var type: String
+ public var components: [Output]?
+ }
+
+ struct Record: Decodable {
+ public var name: String?
+ public var type: String?
+ public var payable: Bool?
+ public var constant: Bool?
+ public var stateMutability: String?
+ public var inputs: [ABI.Input]?
+ public var outputs: [ABI.Output]?
+ public var anonymous: Bool?
+ }
+
+ enum Element {
+ public enum ArraySize { // bytes for convenience
+ case staticSize(UInt64)
+ case dynamicSize
+ case notArray
+ }
+
+ case function(Function)
+ case constructor(Constructor)
+ case fallback(Fallback)
+ case event(Event)
+
+ public enum StateMutability {
+ case payable
+ case mutating
+ case view
+ case pure
+
+ var isConstant: Bool {
+ switch self {
+ case .payable:
+ return false
+ case .mutating:
+ return false
+ default:
+ return true
+ }
+ }
+
+ var isPayable: Bool {
+ switch self {
+ case .payable:
+ return true
+ default:
+ return false
+ }
+ }
+ }
+
+ public typealias InOutName = String
+
+ public struct InOut: Equatable {
+ public let name: InOutName
+ public let type: ParameterType
+
+ public init(name: InOutName, type: ParameterType) {
+ self.name = name
+ self.type = type
+ }
+ }
+
+ public struct Function: Equatable {
+ public let name: String?
+ public let inputs: [InOut]
+ public let outputs: [InOut]
+ public let stateMutability: StateMutability? = nil
+ public let constant: Bool
+ public let payable: Bool
+
+ public init(
+ name: String?,
+ inputs: [InOut],
+ outputs: [InOut],
+ constant: Bool,
+ payable: Bool
+ ) {
+ self.name = name
+ self.inputs = inputs
+ self.outputs = outputs
+ self.constant = constant
+ self.payable = payable
+ }
+ }
+
+ public struct Constructor {
+ public let inputs: [InOut]
+ public let constant: Bool
+ public let payable: Bool
+ public init(inputs: [InOut], constant: Bool, payable: Bool) {
+ self.inputs = inputs
+ self.constant = constant
+ self.payable = payable
+ }
+ }
+
+ public struct Fallback {
+ public let constant: Bool
+ public let payable: Bool
+
+ public init(constant: Bool, payable: Bool) {
+ self.constant = constant
+ self.payable = payable
+ }
+ }
+
+ public struct Event {
+ public let name: String
+ public let inputs: [Input]
+ public let anonymous: Bool
+
+ public init(name: String, inputs: [Input], anonymous: Bool) {
+ self.name = name
+ self.inputs = inputs
+ self.anonymous = anonymous
+ }
+
+ public struct Input {
+ public let name: String
+ public let type: ParameterType
+ public let indexed: Bool
+
+ public init(name: String, type: ParameterType, indexed: Bool) {
+ self.name = name
+ self.type = type
+ self.indexed = indexed
+ }
+ }
+ }
+ }
+}
+
+public extension ABI.Element {
+ func encodeParameters(_ parameters: [AnyObject]) -> Data? {
+ switch self {
+ case let .constructor(constructor):
+ guard parameters.count == constructor.inputs.count else { return nil }
+ guard let data = ABIEncoder.encode(types: constructor.inputs, values: parameters) else { return nil }
+ return data
+ case .event:
+ return nil
+ case .fallback:
+ return nil
+ case let .function(function):
+ guard parameters.count == function.inputs.count else { return nil }
+ let signature = function.methodEncoding
+ guard let data = ABIEncoder.encode(types: function.inputs, values: parameters) else { return nil }
+ return signature + data
+ }
+ }
+}
+
+public extension ABI.Element {
+ func decodeReturnData(_ data: Data) -> [String: Any]? {
+ switch self {
+ case .constructor:
+ return nil
+ case .event:
+ return nil
+ case .fallback:
+ return nil
+ case let .function(function):
+ if data.isEmpty, function.outputs.count == 1 {
+ let name = "0"
+ let value = function.outputs[0].type.emptyValue
+ var returnArray = [String: Any]()
+ returnArray[name] = value
+ if function.outputs[0].name != "" {
+ returnArray[function.outputs[0].name] = value
+ }
+ return returnArray
+ }
+
+ guard function.outputs.count * 32 <= data.count else { return nil }
+ var returnArray = [String: Any]()
+ var i = 0
+ guard let values = ABIDecoder.decode(types: function.outputs, data: data) else { return nil }
+ for output in function.outputs {
+ let name = "\(i)"
+ returnArray[name] = values[i]
+ if output.name != "" {
+ returnArray[output.name] = values[i]
+ }
+ i += 1
+ }
+ return returnArray
+ }
+ }
+
+ func decodeInputData(_ rawData: Data) -> [String: Any]? {
+ var data = rawData
+ var sig: Data?
+ switch rawData.count % 32 {
+ case 0:
+ break
+ case 4:
+ sig = rawData[0 ..< 4]
+ data = Data(rawData[4 ..< rawData.count])
+ default:
+ return nil
+ }
+ switch self {
+ case let .constructor(function):
+ if data.isEmpty, function.inputs.count == 1 {
+ let name = "0"
+ let value = function.inputs[0].type.emptyValue
+ var returnArray = [String: Any]()
+ returnArray[name] = value
+ if function.inputs[0].name != "" {
+ returnArray[function.inputs[0].name] = value
+ }
+ return returnArray
+ }
+
+ guard function.inputs.count * 32 <= data.count else { return nil }
+ var returnArray = [String: Any]()
+ var i = 0
+ guard let values = ABIDecoder.decode(types: function.inputs, data: data) else { return nil }
+ for input in function.inputs {
+ let name = "\(i)"
+ returnArray[name] = values[i]
+ if input.name != "" {
+ returnArray[input.name] = values[i]
+ }
+ i += 1
+ }
+ return returnArray
+ case .event:
+ return nil
+ case .fallback:
+ return nil
+ case let .function(function):
+ if sig != nil, sig != function.methodEncoding {
+ return nil
+ }
+ if data.isEmpty, function.inputs.count == 1 {
+ let name = "0"
+ let value = function.inputs[0].type.emptyValue
+ var returnArray = [String: Any]()
+ returnArray[name] = value
+ if function.inputs[0].name != "" {
+ returnArray[function.inputs[0].name] = value
+ }
+ return returnArray
+ }
+
+ guard function.inputs.count * 32 <= data.count else { return nil }
+ var returnArray = [String: Any]()
+ var i = 0
+ guard let values = ABIDecoder.decode(types: function.inputs, data: data) else { return nil }
+ for input in function.inputs {
+ let name = "\(i)"
+ returnArray[name] = values[i]
+ if input.name != "" {
+ returnArray[input.name] = values[i]
+ }
+ i += 1
+ }
+ return returnArray
+ }
+ }
+}
+
+public extension ABI.Element.Event {
+ func decodeReturnedLogs(eventLogTopics: [Data], eventLogData: Data) -> [String: Any]? {
+ guard let eventContent = ABIDecoder.decodeLog(
+ event: self,
+ eventLogTopics: eventLogTopics,
+ eventLogData: eventLogData
+ ) else { return nil }
+ return eventContent
+ }
+}
diff --git a/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIEncoding.swift b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIEncoding.swift
new file mode 100644
index 00000000..ba3279be
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIEncoding.swift
@@ -0,0 +1,398 @@
+//
+// Created by Alex Vlasov on 25/10/2018.
+// Copyright © 2018 Alex Vlasov. All rights reserved.
+//
+
+import BigInt
+import CryptoSwift
+import Foundation
+import SSFUtils
+
+// swiftlint:disable identifier_name
+
+public struct ABIEncoder {}
+
+public extension ABIEncoder {
+ static func convertToBigUInt(_ value: AnyObject) -> BigUInt? {
+ switch value {
+ case let v as BigUInt:
+ return v
+ case let v as BigInt:
+ switch v.sign {
+ case .minus:
+ return nil
+ case .plus:
+ return v.magnitude
+ }
+ case let v as String:
+ let base10 = BigUInt(v, radix: 10)
+ if base10 != nil {
+ return base10!
+ }
+ let base16 = BigUInt(v.stringRemoveHexPrefix(), radix: 16)
+ if base16 != nil {
+ return base16!
+ }
+ case let v as UInt:
+ return BigUInt(v)
+ case let v as UInt8:
+ return BigUInt(v)
+ case let v as UInt16:
+ return BigUInt(v)
+ case let v as UInt32:
+ return BigUInt(v)
+ case let v as UInt64:
+ return BigUInt(v)
+ case let v as Int:
+ if v < 0 { return nil }
+ return BigUInt(v)
+ case let v as Int8:
+ if v < 0 { return nil }
+ return BigUInt(v)
+ case let v as Int16:
+ if v < 0 { return nil }
+ return BigUInt(v)
+ case let v as Int32:
+ if v < 0 { return nil }
+ return BigUInt(v)
+ case let v as Int64:
+ if v < 0 { return nil }
+ return BigUInt(v)
+ default:
+ return nil
+ }
+ return nil
+ }
+
+ static func convertToBigInt(_ value: AnyObject) -> BigInt? {
+ switch value {
+ case let v as BigUInt:
+ return BigInt(v)
+ case let v as BigInt:
+ return v
+ case let v as String:
+ let base10 = BigInt(v, radix: 10)
+ if base10 != nil {
+ return base10
+ }
+ let base16 = BigInt(v.stringRemoveHexPrefix(), radix: 16)
+ if base16 != nil {
+ return base16
+ }
+ case let v as UInt:
+ return BigInt(v)
+ case let v as UInt8:
+ return BigInt(v)
+ case let v as UInt16:
+ return BigInt(v)
+ case let v as UInt32:
+ return BigInt(v)
+ case let v as UInt64:
+ return BigInt(v)
+ case let v as Int:
+ return BigInt(v)
+ case let v as Int8:
+ return BigInt(v)
+ case let v as Int16:
+ return BigInt(v)
+ case let v as Int32:
+ return BigInt(v)
+ case let v as Int64:
+ return BigInt(v)
+ default:
+ return nil
+ }
+ return nil
+ }
+
+ static func convertToData(_ value: AnyObject, type: ABI.Element.ParameterType) -> Data? {
+ switch value {
+ case let d as Data:
+ return d
+ case let d as String:
+ if type.isNumber {
+ guard let value = IntegerLiteralType(d) else { return nil }
+ return convertToData(value as AnyObject, type: type)
+ }
+ if d.hasHexPrefix() {
+ return Data(hex: d)
+ }
+ let str = d.data(using: .utf8)
+ if str != nil {
+ return str
+ }
+ case let d as [UInt8]:
+ return Data(d)
+ case let d as Address:
+ return d.data
+ case let d as [IntegerLiteralType]:
+ var bytesArray = [UInt8]()
+ for el in d {
+ guard el >= 0, el <= 255 else { return nil }
+ bytesArray.append(UInt8(el))
+ }
+ return Data(bytesArray)
+ case is IntegerLiteralType:
+ return convertToBigInt(value)?.toTwosComplement()
+ default:
+ return nil
+ }
+ return nil
+ }
+
+ static func encode(types: [ABI.Element.InOut], values: [AnyObject]) -> Data? {
+ guard types.count == values.count else { return nil }
+ let params = types.compactMap { el -> ABI.Element.ParameterType in
+ el.type
+ }
+ return encode(types: params, values: values)
+ }
+
+ static func encode(types: [ABI.Element.ParameterType], values: [AnyObject]) -> Data? {
+ guard types.count == values.count else { return nil }
+ var tails = [Data]()
+ var heads = [Data]()
+ for i in 0 ..< types.count {
+ let enc = encodeSingleType(type: types[i], value: values[i])
+ guard let encoding = enc else { return nil }
+ if types[i].isStatic {
+ heads.append(encoding)
+ tails.append(Data())
+ } else {
+ heads.append(Data(repeating: 0x0, count: 32))
+ tails.append(encoding)
+ }
+ }
+ var headsConcatenated = Data()
+ for head in heads {
+ headsConcatenated.append(head)
+ }
+ var tailsPointer = BigUInt(headsConcatenated.count)
+ headsConcatenated = Data()
+ var tailsConcatenated = Data()
+ for i in 0 ..< types.count {
+ let head = heads[i]
+ let tail = tails[i]
+ if !types[i].isStatic {
+ guard let newHead = tailsPointer.abiEncode(bits: 256) else { return nil }
+ headsConcatenated.append(newHead)
+ tailsConcatenated.append(tail)
+ tailsPointer += BigUInt(tail.count)
+ } else {
+ headsConcatenated.append(head)
+ tailsConcatenated.append(tail)
+ }
+ }
+ return headsConcatenated + tailsConcatenated
+ }
+
+ // swiftlint:disable:next function_body_length cyclomatic_complexity
+ static func encodeSingleType(type: ABI.Element.ParameterType, value: AnyObject) -> Data? {
+ switch type {
+ case .uint:
+ if let biguint = convertToBigUInt(value) {
+ return biguint.abiEncode(bits: 256)
+ }
+ if let bigint = convertToBigInt(value) {
+ return bigint.abiEncode(bits: 256)
+ }
+ case .int:
+ if let biguint = convertToBigUInt(value) {
+ return biguint.abiEncode(bits: 256)
+ }
+ if let bigint = convertToBigInt(value) {
+ return bigint.abiEncode(bits: 256)
+ }
+ case .address:
+ if let string = value as? String {
+ guard let address = Address(address: string) else { return nil }
+ let data = address.data
+ return data.setLengthLeft(32)
+ } else if let address = value as? Address {
+ let data = address.data
+ return data.setLengthLeft(32)
+ } else if let data = value as? Data {
+ return data.setLengthLeft(32)
+ }
+ case .bool:
+ if let bool = value as? Bool {
+ if bool {
+ return BigUInt(1).abiEncode(bits: 256)
+ } else {
+ return BigUInt(0).abiEncode(bits: 256)
+ }
+ }
+ case let .bytes(length):
+ guard let data = convertToData(value, type: type) else { break }
+ if data.count > length { break }
+ return data.setLengthRight(32)
+ case .string:
+ if let string = value as? String {
+ var dataGuess: Data?
+ if string.hasHexPrefix() {
+ dataGuess = Data(hex: string.lowercased())
+ } else {
+ dataGuess = string.data(using: .utf8)
+ }
+ guard let data = dataGuess else { break }
+ let minLength = ((data.count + 31) / 32) * 32
+ let paddedData = data.setLengthRight(minLength)
+ let length = BigUInt(data.count)
+ guard let head = length.abiEncode(bits: 256) else { break }
+ let total = head + paddedData
+ return total
+ }
+ case .dynamicBytes:
+ guard let data = convertToData(value, type: type) else { break }
+ let minLength = ((data.count + 31) / 32) * 32
+ let paddedData = data.setLengthRight(minLength)
+ let length = BigUInt(data.count)
+ guard let head = length.abiEncode(bits: 256) else { break }
+ let total = head + paddedData
+ return total
+ case let .array(type: subType, length: length):
+ switch type.arraySize {
+ case .dynamicSize:
+ guard length == 0 else { break }
+ guard let val = value as? [AnyObject] else { break }
+ guard let lengthEncoding = BigUInt(val.count).abiEncode(bits: 256) else { break }
+ if subType.isStatic {
+ // work in a previous context
+ var toReturn = Data()
+ for i in 0 ..< val.count {
+ let enc = encodeSingleType(type: subType, value: val[i])
+ guard let encoding = enc else { break }
+ toReturn.append(encoding)
+ }
+ let total = lengthEncoding + toReturn
+ // print("Dynamic array of static types encoding :\n" +
+ // String(total.toHexString()))
+ return total
+ } else {
+ // create new context
+ var tails = [Data]()
+ var heads = [Data]()
+ for i in 0 ..< val.count {
+ let enc = encodeSingleType(type: subType, value: val[i])
+ guard let encoding = enc else { return nil }
+ heads.append(Data(repeating: 0x0, count: 32))
+ tails.append(encoding)
+ }
+ var headsConcatenated = Data()
+ for h in heads {
+ headsConcatenated.append(h)
+ }
+ var tailsPointer = BigUInt(headsConcatenated.count)
+ headsConcatenated = Data()
+ var tailsConcatenated = Data()
+ for i in 0 ..< val.count {
+ let head = heads[i]
+ let tail = tails[i]
+ if tail != Data() {
+ guard let newHead = tailsPointer.abiEncode(bits: 256) else { return nil }
+ headsConcatenated.append(newHead)
+ tailsConcatenated.append(tail)
+ tailsPointer += BigUInt(tail.count)
+ } else {
+ headsConcatenated.append(head)
+ tailsConcatenated.append(tail)
+ }
+ }
+ let total = lengthEncoding + headsConcatenated + tailsConcatenated
+ // print("Dynamic array of dynamic types encoding :\n" +
+ // String(total.toHexString()))
+ return total
+ }
+ case let .staticSize(staticLength):
+ guard staticLength != 0 else { break }
+ guard let val = value as? [AnyObject] else { break }
+ guard staticLength == val.count else { break }
+ if subType.isStatic {
+ // work in a previous context
+ var toReturn = Data()
+ for i in 0 ..< val.count {
+ let enc = encodeSingleType(type: subType, value: val[i])
+ guard let encoding = enc else { break }
+ toReturn.append(encoding)
+ }
+ // print("Static array of static types encoding :\n" +
+ // String(toReturn.toHexString()))
+ let total = toReturn
+ return total
+ } else {
+ // create new context
+ var tails = [Data]()
+ var heads = [Data]()
+ for i in 0 ..< val.count {
+ let enc = encodeSingleType(type: subType, value: val[i])
+ guard let encoding = enc else { return nil }
+ heads.append(Data(repeating: 0x0, count: 32))
+ tails.append(encoding)
+ }
+ var headsConcatenated = Data()
+ for h in heads {
+ headsConcatenated.append(h)
+ }
+ var tailsPointer = BigUInt(headsConcatenated.count)
+ headsConcatenated = Data()
+ var tailsConcatenated = Data()
+ for i in 0 ..< val.count {
+ let tail = tails[i]
+ guard let newHead = tailsPointer.abiEncode(bits: 256) else { return nil }
+ headsConcatenated.append(newHead)
+ tailsConcatenated.append(tail)
+ tailsPointer += BigUInt(tail.count)
+ }
+ let total = headsConcatenated + tailsConcatenated
+ // print("Static array of dynamic types encoding :\n" +
+ // String(total.toHexString()))
+ return total
+ }
+ case .notArray:
+ break
+ }
+ case let .tuple(types: subTypes):
+ var tails = [Data]()
+ var heads = [Data]()
+ guard let val = value as? [AnyObject] else { break }
+ for i in 0 ..< subTypes.count {
+ let enc = encodeSingleType(type: subTypes[i], value: val[i])
+ guard let encoding = enc else { return nil }
+ if subTypes[i].isStatic {
+ heads.append(encoding)
+ tails.append(Data())
+ } else {
+ heads.append(Data(repeating: 0x0, count: 32))
+ tails.append(encoding)
+ }
+ }
+ var headsConcatenated = Data()
+ for h in heads {
+ headsConcatenated.append(h)
+ }
+ var tailsPointer = BigUInt(headsConcatenated.count)
+ headsConcatenated = Data()
+ var tailsConcatenated = Data()
+ for i in 0 ..< subTypes.count {
+ let head = heads[i]
+ let tail = tails[i]
+ if !subTypes[i].isStatic {
+ guard let newHead = tailsPointer.abiEncode(bits: 256) else { return nil }
+ headsConcatenated.append(newHead)
+ tailsConcatenated.append(tail)
+ tailsPointer += BigUInt(tail.count)
+ } else {
+ headsConcatenated.append(head)
+ tailsConcatenated.append(tail)
+ }
+ }
+ let total = headsConcatenated + tailsConcatenated
+ return total
+ case .function:
+ if let data = value as? Data {
+ return data.setLengthLeft(32)
+ }
+ }
+ return nil
+ }
+}
diff --git a/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIParameterTypes.swift b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIParameterTypes.swift
new file mode 100644
index 00000000..342520e0
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/eip_mew/ABI/ABIParameterTypes.swift
@@ -0,0 +1,253 @@
+//
+// Created by Alex Vlasov on 25/10/2018.
+// Copyright © 2018 Alex Vlasov. All rights reserved.
+//
+
+import BigInt
+import Foundation
+import Web3ContractABI
+
+public extension ABI.Element {
+ /// Specifies the type that parameters in a contract have.
+ enum ParameterType: ABIElementPropertiesProtocol {
+ case uint(bits: UInt64)
+ case int(bits: UInt64)
+ case address
+ case function
+ case bool
+ case bytes(length: UInt64)
+ indirect case array(type: ParameterType, length: UInt64)
+ case dynamicBytes
+ case string
+ indirect case tuple(types: [ParameterType])
+
+ var isNumber: Bool {
+ switch self {
+ case .int, .uint:
+ return true
+ default:
+ return false
+ }
+ }
+
+ var isStatic: Bool {
+ switch self {
+ case .string:
+ return false
+ case .dynamicBytes:
+ return false
+ case let .array(type: type, length: length):
+ if length == 0 {
+ return false
+ }
+ if !type.isStatic {
+ return false
+ }
+ return true
+ case let .tuple(types: types):
+ for type in types where !type.isStatic {
+ return false
+ }
+ return true
+ case .bytes(length: _):
+ return true
+ default:
+ return true
+ }
+ }
+
+ var isArray: Bool {
+ switch self {
+ case .array(type: _, length: _):
+ return true
+ default:
+ return false
+ }
+ }
+
+ var isTuple: Bool {
+ switch self {
+ case .tuple:
+ return true
+ default:
+ return false
+ }
+ }
+
+ var subtype: ABI.Element.ParameterType? {
+ switch self {
+ case .array(type: let type, length: _):
+ return type
+ default:
+ return nil
+ }
+ }
+
+ var memoryUsage: UInt64 {
+ switch self {
+ case let .array(_, length: length):
+ if length == 0 {
+ return 32
+ }
+ if isStatic {
+ return 32 * length
+ }
+ return 32
+ case let .tuple(types: types):
+ if !isStatic {
+ return 32
+ }
+ var sum: UInt64 = 0
+ for type in types {
+ sum += type.memoryUsage
+ }
+ return sum
+ default:
+ return 32
+ }
+ }
+
+ var emptyValue: Any {
+ switch self {
+ case .uint(bits: _):
+ return BigUInt(0)
+ case .int(bits: _):
+ return BigUInt(0)
+ case .address:
+ return Address(address: "0x0000000000000000000000000000000000000000")!
+ case .function:
+ return Data(repeating: 0x00, count: 24)
+ case .bool:
+ return false
+ case let .bytes(length: length):
+ return Data(repeating: 0x00, count: Int(length))
+ case let .array(type: type, length: length):
+ let emptyValueOfType = type.emptyValue
+ return Array(repeating: emptyValueOfType, count: Int(length))
+ case .dynamicBytes:
+ return Data()
+ case .string:
+ return ""
+ case .tuple(types: _):
+ return [Any]()
+ }
+ }
+
+ var arraySize: ABI.Element.ArraySize {
+ switch self {
+ case .array(type: _, length: let length):
+ if length == 0 {
+ return ArraySize.dynamicSize
+ }
+ return ArraySize.staticSize(length)
+ default:
+ return ArraySize.notArray
+ }
+ }
+ }
+}
+
+extension ABI.Element.ParameterType: Equatable {
+ public static func == (lhs: ABI.Element.ParameterType, rhs: ABI.Element.ParameterType) -> Bool {
+ switch (lhs, rhs) {
+ case let (.uint(length1), .uint(length2)):
+ return length1 == length2
+ case let (.int(length1), .int(length2)):
+ return length1 == length2
+ case (.address, .address):
+ return true
+ case (.bool, .bool):
+ return true
+ case let (.bytes(length1), .bytes(length2)):
+ return length1 == length2
+ case (.function, .function):
+ return true
+ case let (.array(type1, length1), .array(type2, length2)):
+ return type1 == type2 && length1 == length2
+ case (.dynamicBytes, .dynamicBytes):
+ return true
+ case (.string, .string):
+ return true
+ default:
+ return false
+ }
+ }
+}
+
+public extension ABI.Element.Function {
+ var signature: String {
+ "\(name ?? "")(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))"
+ }
+
+ var methodString: String {
+ String(signature.sha3(.keccak256).prefix(8))
+ }
+
+ var methodEncoding: Data {
+ signature.data(using: .ascii)!.sha3(.keccak256)[0 ... 3]
+ }
+}
+
+// MARK: - Event topic
+
+public extension ABI.Element.Event {
+ var signature: String {
+ "\(name)(\(inputs.map { $0.type.abiRepresentation }.joined(separator: ",")))"
+ }
+
+ var topic: Data {
+ signature.data(using: .ascii)!.sha3(.keccak256)
+ }
+}
+
+extension ABI.Element.ParameterType: ABIEncoding {
+ public var abiRepresentation: String {
+ switch self {
+ case let .uint(bits):
+ return "uint\(bits)"
+ case let .int(bits):
+ return "int\(bits)"
+ case .address:
+ return "address"
+ case .bool:
+ return "bool"
+ case let .bytes(length):
+ return "bytes\(length)"
+ case .dynamicBytes:
+ return "bytes"
+ case .function:
+ return "function"
+ case let .array(type: type, length: length):
+ if length == 0 {
+ return "\(type.abiRepresentation)[]"
+ }
+ return "\(type.abiRepresentation)[\(length)]"
+ case let .tuple(types: types):
+ let typesRepresentation = types.map { $0.abiRepresentation }
+ let typesJoined = typesRepresentation.joined(separator: ",")
+ return "tuple(\(typesJoined))"
+ case .string:
+ return "string"
+ }
+ }
+}
+
+extension ABI.Element.ParameterType: ABIValidation {
+ public var isValid: Bool {
+ switch self {
+ case let .uint(bits), let .int(bits):
+ return bits > 0 && bits <= 256 && bits % 8 == 0
+ case let .bytes(length):
+ return length > 0 && length <= 32
+ case let .array(type: type, _):
+ return type.isValid
+ case let .tuple(types: types):
+ for type in types where !type.isValid {
+ return false
+ }
+ return true
+ default:
+ return true
+ }
+ }
+}
diff --git a/Sources/SSFChainlinkProvider/eip_mew/ABI/BigInt+ABI.swift b/Sources/SSFChainlinkProvider/eip_mew/ABI/BigInt+ABI.swift
new file mode 100644
index 00000000..be37a3a8
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/eip_mew/ABI/BigInt+ABI.swift
@@ -0,0 +1,58 @@
+//
+// BigInt+ABI.swift
+// MEWwalletKit
+//
+// Created by Nail Galiaskarov on 3/18/21.
+// Copyright © 2021 MyEtherWallet Inc. All rights reserved.
+//
+
+import BigInt
+import Foundation
+import SSFUtils
+
+extension BigInt {
+ func toTwosComplement() -> Data {
+ if sign == BigInt.Sign.plus {
+ return magnitude.serialize()
+ } else {
+ let serializedLength = magnitude.serialize().count
+ let MAX = BigUInt(1) << (serializedLength * 8)
+ let twoComplement = MAX - magnitude
+ return twoComplement.serialize()
+ }
+ }
+}
+
+extension BigUInt {
+ func abiEncode(bits: UInt64) -> Data? {
+ let data = serialize()
+ let paddedLength = Int(ceil(Double(bits) / 8.0))
+ let padded = data.setLengthLeft(paddedLength)
+ return padded
+ }
+}
+
+extension BigInt {
+ func abiEncode(bits: UInt64) -> Data? {
+ let isNegative = self < BigInt(0)
+ let data = toTwosComplement()
+ let paddedLength = Int(ceil(Double(bits) / 8.0))
+ let padded = data.setLengthLeft(paddedLength, isNegative: isNegative)
+ return padded
+ }
+}
+
+extension BigInt {
+ static func fromTwosComplement(data: Data) -> BigInt {
+ let isPositive = ((data[0] & 128) >> 7) == 0
+ if isPositive {
+ let magnitude = BigUInt(data)
+ return BigInt(magnitude)
+ } else {
+ let MAX = (BigUInt(1) << (data.count * 8))
+ let magnitude = MAX - BigUInt(data)
+ let bigint = BigInt(0) - BigInt(magnitude)
+ return bigint
+ }
+ }
+}
diff --git a/Sources/SSFChainlinkProvider/eip_mew/Address.swift b/Sources/SSFChainlinkProvider/eip_mew/Address.swift
new file mode 100644
index 00000000..2cf45847
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/eip_mew/Address.swift
@@ -0,0 +1,64 @@
+//
+// Address.swift
+// MEWwalletKit
+//
+// Created by Mikhail Nikanorov on 4/25/19.
+// Copyright © 2019 MyEtherWallet Inc. All rights reserved.
+//
+
+import CryptoSwift
+import Foundation
+
+public struct Address: CustomDebugStringConvertible {
+ public enum Ethereum {
+ static let length = 42
+ }
+
+ private var _address: String
+ public var address: String {
+ _address
+ }
+
+ public var data: Data {
+ Data(hex: address)
+ }
+
+ public init?(data: Data, prefix: String? = nil) {
+ self.init(address: data.toHexString(), prefix: prefix)
+ }
+
+ public init(raw: String) {
+ _address = raw
+ }
+
+ public init?(address: String, prefix: String? = nil) {
+ var address = address
+ if let prefix = prefix, !address.hasPrefix(prefix) {
+ address.insert(contentsOf: prefix, at: address.startIndex)
+ }
+ if address.stringAddHexPrefix().count == Address.Ethereum.length, prefix == nil,
+ address.isHex(), let eip55address = address.stringAddHexPrefix().eip55()
+ {
+ _address = eip55address
+ } else {
+ _address = address
+ }
+ }
+
+ public init?(ethereumAddress: String) {
+ let value = ethereumAddress.stringAddHexPrefix()
+ guard value.count == Address.Ethereum.length, value.isHex(),
+ let address = value.eip55() else { return nil } // 42 = 0x + 20bytes
+ _address = address
+ }
+
+ public var debugDescription: String {
+ _address
+ }
+}
+
+extension Address: Equatable {
+ public static func == (lhs: Address, rhs: Address) -> Bool {
+ lhs._address.lowercased() == rhs._address.lowercased()
+ }
+}
diff --git a/Sources/SSFChainlinkProvider/eip_mew/Extensions/String/String+EIP55.swift b/Sources/SSFChainlinkProvider/eip_mew/Extensions/String/String+EIP55.swift
new file mode 100644
index 00000000..03943ca8
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/eip_mew/Extensions/String/String+EIP55.swift
@@ -0,0 +1,42 @@
+//
+// String+EIP55.swift
+// fearless
+//
+// Created by Soramitsu on 30.08.2023.
+// Copyright © 2023 Soramitsu. All rights reserved.
+//
+
+import Foundation
+
+extension String {
+ func eip55() -> String? {
+ guard isHex() else {
+ return nil
+ }
+ var address = self
+ let hasHexPrefix = address.hasHexPrefix()
+ if hasHexPrefix {
+ address.removeHexPrefix()
+ }
+ address = address.lowercased()
+ guard let hash = address.data(using: .ascii)?.sha3(.keccak256).toHexString() else {
+ return nil
+ }
+
+ var eip55 = zip(address, hash).map { addr, hash -> String in
+ switch (addr, hash) {
+ case ("0", _), ("1", _), ("2", _), ("3", _), ("4", _), ("5", _), ("6", _), ("7", _),
+ ("8", _), ("9", _):
+ return String(addr)
+ case (_, "8"), (_, "9"), (_, "a"), (_, "b"), (_, "c"), (_, "d"), (_, "e"), (_, "f"):
+ return String(addr).uppercased()
+ default:
+ return String(addr).lowercased()
+ }
+ }.joined()
+ if hasHexPrefix {
+ eip55.addHexPrefix()
+ }
+ return eip55
+ }
+}
diff --git a/Sources/SSFChainlinkProvider/eip_mew/Extensions/String/String+Hex.swift b/Sources/SSFChainlinkProvider/eip_mew/Extensions/String/String+Hex.swift
new file mode 100644
index 00000000..3aa53b3d
--- /dev/null
+++ b/Sources/SSFChainlinkProvider/eip_mew/Extensions/String/String+Hex.swift
@@ -0,0 +1,74 @@
+//
+// String+Hex.swift
+// MEWwalletKit
+//
+// Created by Mikhail Nikanorov on 4/25/19.
+// Copyright © 2019 MyEtherWallet Inc. All rights reserved.
+//
+
+import Foundation
+
+private let _nonHexCharacterSet = CharacterSet(charactersIn: "0123456789ABCDEF").inverted
+
+extension String {
+ func isHex() -> Bool {
+ var rawHex = self
+ rawHex.removeHexPrefix()
+ rawHex = rawHex.uppercased()
+ return rawHex.rangeOfCharacter(from: _nonHexCharacterSet) == nil
+ }
+
+ func isHexWithPrefix() -> Bool {
+ guard hasHexPrefix() else { return false }
+ return isHex()
+ }
+
+ mutating func removeHexPrefix() {
+ if hasPrefix("0x") {
+ let indexStart = index(startIndex, offsetBy: 2)
+ self = String(self[indexStart...])
+ }
+ }
+
+ mutating func addHexPrefix() {
+ if !hasPrefix("0x") {
+ self = "0x" + self
+ }
+ }
+
+ mutating func alignHexBytes() {
+ guard isHex(), count % 2 != 0 else {
+ return
+ }
+ let hasPrefix = self.hasPrefix("0x")
+ if hasPrefix {
+ removeHexPrefix()
+ }
+ self = "0" + self
+ if hasPrefix {
+ addHexPrefix()
+ }
+ }
+
+ func hasHexPrefix() -> Bool {
+ hasPrefix("0x")
+ }
+
+ func stringRemoveHexPrefix() -> String {
+ var string = self
+ string.removeHexPrefix()
+ return string
+ }
+
+ func stringAddHexPrefix() -> String {
+ var string = self
+ string.addHexPrefix()
+ return string
+ }
+
+ func stringWithAlignedHexBytes() -> String {
+ var string = self
+ string.alignHexBytes()
+ return string
+ }
+}
diff --git a/Sources/SSFCoingeckoProvider/CoingeckoOperationFactory.swift b/Sources/SSFCoingeckoProvider/CoingeckoOperationFactory.swift
new file mode 100644
index 00000000..b61b17ba
--- /dev/null
+++ b/Sources/SSFCoingeckoProvider/CoingeckoOperationFactory.swift
@@ -0,0 +1,101 @@
+import Foundation
+import RobinHood
+import SSFModels
+
+enum CoingeckoAPI {
+ static let baseURL = URL(string: "https://api.coingecko.com/api/v3")!
+ static let price = "simple/price"
+}
+
+public protocol CoingeckoOperationFactoryProtocol {
+ func fetchPriceOperation(
+ for tokenIds: [String],
+ currencies: [Currency]
+ ) -> BaseOperation<[PriceData]>
+}
+
+public final class CoingeckoOperationFactory {
+ private func buildURLForAssets(
+ _ tokenIds: [String],
+ method: String,
+ currencies: [Currency]
+ ) -> URL? {
+ guard var components = URLComponents(
+ url: CoingeckoAPI.baseURL.appendingPathComponent(method),
+ resolvingAgainstBaseURL: false
+ ) else { return nil }
+
+ let tokenIDParam = tokenIds.joined(separator: ",")
+ let currencyParam = currencies.map { $0.id }.joined(separator: ",")
+
+ components.queryItems = [
+ URLQueryItem(name: "ids", value: tokenIDParam),
+ URLQueryItem(name: "vs_currencies", value: currencyParam),
+ URLQueryItem(name: "include_24hr_change", value: "true"),
+ ]
+
+ return components.url
+ }
+}
+
+extension CoingeckoOperationFactory: CoingeckoOperationFactoryProtocol {
+ public func fetchPriceOperation(
+ for tokenIds: [String],
+ currencies: [Currency]
+ ) -> BaseOperation<[PriceData]> {
+ guard let url = buildURLForAssets(
+ tokenIds,
+ method: CoingeckoAPI.price,
+ currencies: currencies
+ ) else {
+ return BaseOperation.createWithError(NetworkBaseError.invalidUrl)
+ }
+
+ let requestFactory = BlockNetworkRequestFactory {
+ var request = URLRequest(url: url)
+
+ request.setValue(
+ HttpContentType.json.rawValue,
+ forHTTPHeaderField: HttpHeaderKey.contentType.rawValue
+ )
+
+ request.httpMethod = HttpMethod.get.rawValue
+
+ return request
+ }
+
+ let resultFactory = AnyNetworkResultFactory<[PriceData]> { data in
+
+ let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
+ return tokenIds.compactMap { assetId in
+ guard let priceDataJson = json?[assetId] as? [String: Any] else {
+ return nil
+ }
+
+ return currencies.compactMap { currency in
+ let price = priceDataJson[currency.id] as? CGFloat
+ let dayChange = priceDataJson["\(currency.id)_24h_change"] as? CGFloat
+
+ guard let price = price else {
+ return nil
+ }
+
+ return PriceData(
+ currencyId: currency.id,
+ priceId: assetId,
+ price: String(describing: price),
+ fiatDayChange: Decimal(dayChange ?? 0.0),
+ coingeckoPriceId: assetId
+ )
+ }
+ }.reduce([], +)
+ }
+
+ let operation = NetworkOperation(
+ requestFactory: requestFactory,
+ resultFactory: resultFactory
+ )
+
+ return operation
+ }
+}
diff --git a/Sources/SSFCoingeckoProvider/CoingeckoService.swift b/Sources/SSFCoingeckoProvider/CoingeckoService.swift
new file mode 100644
index 00000000..9265474a
--- /dev/null
+++ b/Sources/SSFCoingeckoProvider/CoingeckoService.swift
@@ -0,0 +1,39 @@
+import RobinHood
+import SSFModels
+import SSFUtils
+import SSFPrices
+
+public final class CoingeckoService: PriceProviderServiceProtocol {
+ let coingeckoOperationFactory: CoingeckoOperationFactoryProtocol
+
+ public init(coingeckoOperationFactory: CoingeckoOperationFactoryProtocol) {
+ self.coingeckoOperationFactory = coingeckoOperationFactory
+ }
+
+ public func getPrices(for chainAssets: [ChainAsset], currencies: [Currency]) async -> [PriceData] {
+ let operation = createCoingeckoOperation(for: chainAssets, currencies: currencies)
+ do {
+ return try operation.extractNoCancellableResultData()
+ } catch {
+ return []
+ }
+ }
+
+ private func createCoingeckoOperation(
+ for chainAssets: [ChainAsset],
+ currencies: [Currency]
+ ) -> BaseOperation<[PriceData]> {
+ let priceIds = chainAssets
+ .map { $0.asset.coingeckoPriceId }
+ .compactMap { $0 }
+ .uniq(predicate: { $0 })
+ guard priceIds.isNotEmpty else {
+ return BaseOperation.createWithResult([])
+ }
+ let operation = coingeckoOperationFactory.fetchPriceOperation(
+ for: priceIds,
+ currencies: currencies
+ )
+ return operation
+ }
+}
diff --git a/Sources/SSFEtherscanIndexer/EtherscanHistoryRequest.swift b/Sources/SSFEtherscanIndexer/EtherscanHistoryRequest.swift
index 21d85895..6a0239eb 100644
--- a/Sources/SSFEtherscanIndexer/EtherscanHistoryRequest.swift
+++ b/Sources/SSFEtherscanIndexer/EtherscanHistoryRequest.swift
@@ -9,15 +9,15 @@ final class EtherscanHistoryRequest: RequestConfig {
address: String
) {
let action: String = chainAsset.asset.ethereumType == .normal ? "txlist" : "tokentx"
- var queryItems = [
+ let queryItems = [
URLQueryItem(name: "module", value: "account"),
URLQueryItem(name: "action", value: action),
URLQueryItem(name: "address", value: address),
]
- if let apiKey = chainAsset.chain.externalApi?.history?.apiKey {
- queryItems.append(URLQueryItem(name: "apikey", value: apiKey))
- }
+// if let apiKey = chainAsset.chain.externalApi?.history?.apiKey {
+// queryItems.append(URLQueryItem(name: "apikey", value: apiKey))
+// }
super.init(
baseURL: baseURL,
diff --git a/Sources/SSFHelpers/SSFHelpers/SSFHelpers/Classes/Tests/ChainModelGenerator.swift b/Sources/SSFHelpers/SSFHelpers/SSFHelpers/Classes/Tests/ChainModelGenerator.swift
index 3304df52..cac5c52f 100644
--- a/Sources/SSFHelpers/SSFHelpers/SSFHelpers/Classes/Tests/ChainModelGenerator.swift
+++ b/Sources/SSFHelpers/SSFHelpers/SSFHelpers/Classes/Tests/ChainModelGenerator.swift
@@ -6,7 +6,7 @@ public enum ChainModelGenerator {
name: String? = nil,
chainId: String? = nil,
parentId: String? = nil,
- paraId: String? = nil,
+ paraId _: String? = nil,
count: Int,
withTypes: Bool = true,
staking: StakingType? = nil,
@@ -56,11 +56,9 @@ public enum ChainModelGenerator {
)
let chain = ChainModel(
- rank: nil,
disabled: false,
chainId: chainId,
parentId: parentId,
- paraId: paraId,
name: name ?? String(chainId.reversed()),
tokens: ChainRemoteTokens(
type: .config,
@@ -76,7 +74,8 @@ public enum ChainModelGenerator {
externalApi: externalApi,
customNodes: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: String(index))
+ properties: ChainProperties(addressPrefix: String(index)),
+ identityChain: nil
)
let asset = generateAssetWithId("", symbol: "", assetPresicion: 12, chainId: chainId)
@@ -96,11 +95,9 @@ public enum ChainModelGenerator {
availableAssets: [XcmAvailableAsset]
) -> ChainModel {
ChainModel(
- rank: nil,
disabled: chain.disabled,
chainId: chain.chainId,
parentId: chain.parentId,
- paraId: chain.paraId,
name: chain.name,
tokens: chain.tokens,
xcm: XcmChain(
@@ -114,10 +111,10 @@ public enum ChainModelGenerator {
icon: chain.icon,
options: chain.options,
externalApi: chain.externalApi,
- selectedNode: chain.selectedNode,
customNodes: chain.customNodes,
iosMinAppVersion: chain.iosMinAppVersion,
- properties: chain.properties
+ properties: chain.properties,
+ identityChain: chain.identityChain
)
}
@@ -164,11 +161,9 @@ public enum ChainModelGenerator {
)
let chain = ChainModel(
- rank: nil,
disabled: false,
chainId: chainId,
parentId: nil,
- paraId: nil,
name: UUID().uuidString,
tokens: ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: []),
xcm: xcm,
@@ -179,7 +174,8 @@ public enum ChainModelGenerator {
externalApi: externalApi,
customNodes: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: String(addressPrefix))
+ properties: ChainProperties(addressPrefix: String(addressPrefix)),
+ identityChain: nil
)
let chainAssetsArray: [AssetModel] = (0 ..< count).map { index in
generateAssetWithId(
@@ -210,23 +206,15 @@ public enum ChainModelGenerator {
id: identifier,
name: "",
symbol: symbol,
- isUtility: true,
precision: assetPresicion,
- icon: nil,
- substrateType: substrateAssetType,
- ethereumType: nil,
- tokenProperties:
- TokenProperties(
+ tokenProperties: TokenProperties(
priceId: nil,
currencyId: currencyId,
color: nil,
type: substrateAssetType,
isNative: true
),
- price: nil,
- priceId: nil,
- coingeckoPriceId: nil,
- priceProvider: nil
+ isUtility: true
)
}
@@ -251,8 +239,7 @@ public enum ChainModelGenerator {
if staking != nil {
stakingApi = ChainModel.BlockExplorer(
type: "test",
- url: URL(string: "https://staking.io/\(chainId)-\(UUID().uuidString).json")!,
- apiKey: nil
+ url: URL(string: "https://staking.io/\(chainId)-\(UUID().uuidString).json")!
)
} else {
stakingApi = nil
diff --git a/Sources/SSFModels/SSFModels/AssetModel.swift b/Sources/SSFModels/SSFModels/AssetModel.swift
index aa0be09a..0f24d164 100644
--- a/Sources/SSFModels/SSFModels/AssetModel.swift
+++ b/Sources/SSFModels/SSFModels/AssetModel.swift
@@ -6,7 +6,7 @@ public struct TokenProperties: Codable, Hashable {
public let color: String?
public let type: SubstrateAssetType?
public let isNative: Bool?
- public let stacking: String?
+ public let staking: RawStakingType?
public init(
priceId: String? = nil,
@@ -14,67 +14,102 @@ public struct TokenProperties: Codable, Hashable {
color: String? = nil,
type: SubstrateAssetType? = nil,
isNative: Bool = false,
- stacking: String? = ""
+ staking: RawStakingType? = nil
) {
self.priceId = priceId
self.currencyId = currencyId
self.color = color
self.type = type
self.isNative = isNative
- self.stacking = stacking
+ self.staking = staking
}
+
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+
+ priceId = try? container.decode(String.self, forKey: .priceId)
+ currencyId = nil
+ color = try? container.decode(String.self, forKey: .color)
+ type = try? container.decode(SubstrateAssetType.self, forKey: .type)
+ isNative = try? container.decode(Bool.self, forKey: .isNative)
+ staking = try? container.decode(RawStakingType.self, forKey: .staking)
+ }
+
+ public func encode(to _: Encoder) throws {}
}
-public struct AssetModel: Equatable, Codable, Hashable {
+extension TokenProperties {
+ private enum CodingKeys: String, CodingKey {
+ case priceId
+ case currencyId
+ case color
+ case type
+ case isNative
+ case staking
+ }
+}
+
+public struct AssetModel: Equatable, Codable, Hashable, Identifiable {
public typealias Id = String
public typealias PriceId = String
+ public var identifier: String { id }
+
public let id: String
public let name: String
public let symbol: String
- public let isUtility: Bool
public let precision: UInt16
public let icon: URL?
- public let substrateType: SubstrateAssetType?
+ public let existentialDeposit: String?
+ public let isUtility: Bool
+ public let purchaseProviders: [PurchaseProvider]?
public let ethereumType: EthereumAssetType?
public let tokenProperties: TokenProperties?
- public let price: Decimal?
- public let priceId: String?
- public let coingeckoPriceId: String?
public let priceProvider: PriceProvider?
+ public let coingeckoPriceId: PriceId?
+ public var priceId: PriceId? {
+ if let priceProvider = priceProvider {
+ return priceProvider.id
+ }
+
+ return coingeckoPriceId
+ }
+
public var symbolUppercased: String {
symbol.uppercased()
}
+ public var priceData: [PriceData]
+
public init(
id: String,
name: String,
symbol: String,
- isUtility: Bool,
precision: UInt16,
icon: URL? = nil,
- substrateType: SubstrateAssetType?,
- ethereumType: EthereumAssetType?,
tokenProperties: TokenProperties?,
- price: Decimal?,
- priceId: String?,
- coingeckoPriceId: String?,
- priceProvider: PriceProvider?
+ existentialDeposit: String? = nil,
+ isUtility: Bool,
+ purchaseProviders: [PurchaseProvider]? = nil,
+ ethereumType: EthereumAssetType? = nil,
+ priceProvider: PriceProvider? = nil,
+ coingeckoPriceId: PriceId? = nil,
+ priceData: [PriceData] = []
) {
self.id = id
self.symbol = symbol
self.name = name
- self.isUtility = isUtility
self.precision = precision
self.icon = icon
- self.substrateType = substrateType
+ self.existentialDeposit = existentialDeposit
+ self.isUtility = isUtility
+ self.purchaseProviders = purchaseProviders
self.ethereumType = ethereumType
self.tokenProperties = tokenProperties
- self.price = price
- self.priceId = priceId
- self.coingeckoPriceId = coingeckoPriceId
self.priceProvider = priceProvider
+ self.coingeckoPriceId = coingeckoPriceId
+ self.priceData = priceData
}
public init(from decoder: Decoder) throws {
@@ -83,51 +118,58 @@ public struct AssetModel: Equatable, Codable, Hashable {
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
symbol = try container.decode(String.self, forKey: .symbol)
- isUtility = try container.decode(Bool.self, forKey: .isUtility)
precision = try container.decode(UInt16.self, forKey: .precision)
icon = try? container.decode(URL?.self, forKey: .icon)
- substrateType = try? container.decode(SubstrateAssetType?.self, forKey: .substrateType)
+ existentialDeposit = try? container.decode(String?.self, forKey: .existentialDeposit)
+ isUtility = (try? container.decode(Bool?.self, forKey: .isUtility)) ?? false
+ purchaseProviders = try? container.decode(
+ [PurchaseProvider]?.self,
+ forKey: .purchaseProviders
+ )
ethereumType = try? container.decode(EthereumAssetType?.self, forKey: .ethereumType)
tokenProperties = try? container.decode(TokenProperties?.self, forKey: .tokenProperties)
- price = nil
- priceId = nil
- coingeckoPriceId = nil
- priceProvider = nil
+
+ coingeckoPriceId = try? container.decode(String?.self, forKey: .priceId)
+ priceProvider = try container.decodeIfPresent(PriceProvider.self, forKey: .priceProvider)
+
+ priceData = []
}
public func encode(to _: Encoder) throws {}
- public func replacingPrice(_: PriceData) -> AssetModel {
+ public func replacingPrice(_ priceData: [PriceData]) -> AssetModel {
AssetModel(
id: id,
name: name,
symbol: symbol,
- isUtility: isUtility,
precision: precision,
icon: icon,
- substrateType: substrateType,
+ tokenProperties: tokenProperties, existentialDeposit: existentialDeposit,
+ isUtility: isUtility,
+ purchaseProviders: purchaseProviders,
ethereumType: ethereumType,
- tokenProperties: tokenProperties,
- price: price,
- priceId: priceId,
+ priceProvider: priceProvider,
coingeckoPriceId: coingeckoPriceId,
- priceProvider: priceProvider
+ priceData: priceData
)
}
+ public func getPrice(for currency: Currency) -> PriceData? {
+ priceData.first { $0.currencyId == currency.id }
+ }
+
public static func == (lhs: AssetModel, rhs: AssetModel) -> Bool {
lhs.id == rhs.id &&
lhs.name == rhs.name &&
- lhs.isUtility == rhs.isUtility &&
lhs.precision == rhs.precision &&
lhs.icon == rhs.icon &&
lhs.symbol == rhs.symbol &&
lhs.tokenProperties == rhs.tokenProperties &&
- lhs.substrateType == rhs.substrateType &&
+ lhs.existentialDeposit == rhs.existentialDeposit &&
+ lhs.isUtility == rhs.isUtility &&
+ lhs.purchaseProviders == rhs.purchaseProviders &&
lhs.ethereumType == rhs.ethereumType &&
- lhs.tokenProperties == rhs.tokenProperties &&
- lhs.priceId == rhs.priceId &&
- lhs.coingeckoPriceId == rhs.coingeckoPriceId
+ lhs.priceProvider == rhs.priceProvider
}
public func hash(into hasher: inout Hasher) {
@@ -140,12 +182,14 @@ extension AssetModel {
case id
case name
case symbol
- case isUtility
- case precision
case icon
case tokenProperties
- case substrateType
+ case priceId
+ case existentialDeposit
+ case isUtility
+ case purchaseProviders
case ethereumType
- case currencyId
+ case priceProvider
+ case precision
}
}
diff --git a/Sources/SSFModels/SSFModels/ChainAsset.swift b/Sources/SSFModels/SSFModels/ChainAsset.swift
index f4e0262f..547f0bbd 100644
--- a/Sources/SSFModels/SSFModels/ChainAsset.swift
+++ b/Sources/SSFModels/SSFModels/ChainAsset.swift
@@ -151,7 +151,7 @@ public extension ChainAsset {
var hasStaking: Bool {
let model: AssetModel? = chain.tokens.tokens?.first { $0.id == asset.id }
- return model?.tokenProperties?.stacking != nil
+ return model?.tokenProperties?.staking != nil
}
var storagePath: StorageCodingPath {
diff --git a/Sources/SSFModels/SSFModels/ChainModel.swift b/Sources/SSFModels/SSFModels/ChainModel.swift
index b0147fd9..c6c16f4e 100644
--- a/Sources/SSFModels/SSFModels/ChainModel.swift
+++ b/Sources/SSFModels/SSFModels/ChainModel.swift
@@ -51,7 +51,7 @@ public struct ChainRemoteTokens: Codable, Hashable {
}
}
-public struct ChainProperties: Codable {
+public struct ChainProperties: Codable, Equatable {
public let addressPrefix: String
public let rank: String?
public let paraId: String?
@@ -76,35 +76,28 @@ public struct ChainProperties: Codable {
public final class ChainModel: Codable, Identifiable {
public typealias Id = String
- public let disabled: Bool
- public let chainId: Id
- public let externalApi: ExternalApiSet?
public var tokens: ChainRemoteTokens
-
public var identifier: String { chainId }
- public let rank: UInt16?
-
+ public let disabled: Bool
+ public let chainId: Id
public let parentId: Id?
- public let paraId: String?
public let name: String
-
public let xcm: XcmChain?
public let nodes: Set
public let types: TypesSettings?
public let icon: URL?
public let options: [ChainOptions]?
-
+ public let externalApi: ExternalApiSet?
public var selectedNode: ChainNodeModel?
public let customNodes: Set?
public let iosMinAppVersion: String?
public let properties: ChainProperties
+ public let identityChain: String?
public init(
- rank: UInt16?,
disabled: Bool,
chainId: Id,
parentId: Id? = nil,
- paraId: String?,
name: String,
tokens: ChainRemoteTokens,
xcm: XcmChain?,
@@ -116,13 +109,12 @@ public final class ChainModel: Codable, Identifiable {
selectedNode: ChainNodeModel? = nil,
customNodes: Set? = nil,
iosMinAppVersion: String?,
- properties: ChainProperties
+ properties: ChainProperties,
+ identityChain: String?
) {
- self.rank = rank
self.disabled = disabled
self.chainId = chainId
self.parentId = parentId
- self.paraId = paraId
self.name = name
self.tokens = tokens
self.xcm = xcm
@@ -135,6 +127,7 @@ public final class ChainModel: Codable, Identifiable {
self.customNodes = customNodes
self.iosMinAppVersion = iosMinAppVersion
self.properties = properties
+ self.identityChain = identityChain
}
public var isRelaychain: Bool {
@@ -271,7 +264,9 @@ public final class ChainModel: Codable, Identifiable {
}
public func utilityChainAssets() -> [ChainAsset] {
- []
+ tokens.tokens?.filter { $0.isUtility }.map {
+ ChainAsset(chain: self, asset: $0)
+ } ?? []
}
public func seedTag(metaId: MetaAccountId, accountId: AccountId? = nil) -> String {
@@ -294,11 +289,9 @@ public final class ChainModel: Codable, Identifiable {
public func replacingSelectedNode(_ node: ChainNodeModel?) -> ChainModel {
ChainModel(
- rank: rank,
disabled: disabled,
chainId: chainId,
parentId: parentId,
- paraId: paraId,
name: name,
tokens: tokens,
xcm: xcm,
@@ -310,17 +303,16 @@ public final class ChainModel: Codable, Identifiable {
selectedNode: node,
customNodes: customNodes,
iosMinAppVersion: iosMinAppVersion,
- properties: properties
+ properties: properties,
+ identityChain: identityChain
)
}
public func replacingCustomNodes(_ newCustomNodes: [ChainNodeModel]) -> ChainModel {
ChainModel(
- rank: rank,
disabled: disabled,
chainId: chainId,
parentId: parentId,
- paraId: paraId,
name: name,
tokens: tokens,
xcm: xcm,
@@ -332,7 +324,29 @@ public final class ChainModel: Codable, Identifiable {
selectedNode: selectedNode,
customNodes: Set(newCustomNodes),
iosMinAppVersion: iosMinAppVersion,
- properties: properties
+ properties: properties,
+ identityChain: identityChain
+ )
+ }
+
+ public func replacing(_ tokens: ChainRemoteTokens) -> ChainModel {
+ ChainModel(
+ disabled: disabled,
+ chainId: chainId,
+ parentId: parentId,
+ name: name,
+ tokens: tokens,
+ xcm: xcm,
+ nodes: nodes,
+ types: types,
+ icon: icon,
+ options: options,
+ externalApi: externalApi,
+ selectedNode: selectedNode,
+ customNodes: customNodes,
+ iosMinAppVersion: iosMinAppVersion,
+ properties: properties,
+ identityChain: identityChain
)
}
}
@@ -350,19 +364,22 @@ public extension ChainModel {
extension ChainModel: Hashable {
public static func == (lhs: ChainModel, rhs: ChainModel) -> Bool {
- lhs.rank == rhs.rank
+ lhs.disabled == rhs.disabled
&& lhs.chainId == rhs.chainId
- && lhs.externalApi == rhs.externalApi
+ && lhs.parentId == rhs.parentId
+ && lhs.name == rhs.name
&& lhs.tokens == rhs.tokens
- && lhs.options == rhs.options
+ && lhs.xcm == rhs.xcm
+ && lhs.nodes == rhs.nodes
&& lhs.types == rhs.types
&& lhs.icon == rhs.icon
- && lhs.name == rhs.name
- && lhs.nodes == rhs.nodes
- && lhs.iosMinAppVersion == rhs.iosMinAppVersion
+ && lhs.options == rhs.options
+ && lhs.externalApi == rhs.externalApi
&& lhs.selectedNode == rhs.selectedNode
- && lhs.xcm == rhs.xcm
- && lhs.disabled == rhs.disabled
+ && lhs.customNodes == rhs.customNodes
+ && lhs.iosMinAppVersion == rhs.iosMinAppVersion
+ && lhs.properties == rhs.properties
+ && lhs.identityChain == rhs.identityChain
}
public func hash(into hasher: inout Hasher) {
@@ -382,6 +399,7 @@ public enum ChainOptions: String, Codable {
case nft
case utilityFeePayment
case chainlinkProvider
+ case checkAppId
case unsupported
@@ -418,22 +436,27 @@ public extension ChainModel {
}
struct BlockExplorer: Codable, Hashable {
- public let type: BlockExplorerType
+ enum CodingKeys: String, CodingKey {
+ case type
+ case url
+ }
+
+ public let type: BlockExplorerType?
public let url: URL
- public let apiKey: String?
- public init?(
- type: String,
- url: URL,
- apiKey: String?
- ) {
+ public init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ type = try? container.decode(BlockExplorerType.self, forKey: .type)
+ url = try container.decode(URL.self, forKey: .url)
+ }
+
+ public init?(type: String, url: URL) {
guard let externalApiType = BlockExplorerType(rawValue: type) else {
return nil
}
self.type = externalApiType
self.url = url
- self.apiKey = apiKey
}
}
@@ -458,6 +481,7 @@ public extension ChainModel {
case polkascan
case etherscan
case reef
+ case oklink
case unknown
public init(from decoder: Decoder) throws {
@@ -488,24 +512,28 @@ public extension ChainModel {
public let history: BlockExplorer?
public let crowdloans: ExternalResource?
public let explorers: [ExternalApiExplorer]?
+ public let pricing: BlockExplorer?
public init(
staking: ChainModel.BlockExplorer? = nil,
history: ChainModel.BlockExplorer? = nil,
crowdloans: ChainModel.ExternalResource? = nil,
- explorers: [ChainModel.ExternalApiExplorer]? = nil
+ explorers: [ChainModel.ExternalApiExplorer]? = nil,
+ pricing: ChainModel.BlockExplorer? = nil
) {
self.staking = staking
self.history = history
self.crowdloans = crowdloans
self.explorers = explorers
+ self.pricing = pricing
}
public static func == (lhs: ExternalApiSet, rhs: ExternalApiSet) -> Bool {
lhs.staking == rhs.staking &&
lhs.history == rhs.history &&
lhs.crowdloans == rhs.crowdloans &&
- Set(lhs.explorers ?? []) == Set(rhs.explorers ?? [])
+ Set(lhs.explorers ?? []) == Set(rhs.explorers ?? []) &&
+ lhs.pricing == rhs.pricing
}
}
@@ -575,7 +603,7 @@ public extension ChainModel.ExternalApiExplorer {
var transactionType: ChainModel.SubscanType {
switch type {
- case .etherscan:
+ case .etherscan, .oklink:
return .tx
default:
return .extrinsic
diff --git a/Sources/SSFModels/SSFModels/RemoteChain.swift b/Sources/SSFModels/SSFModels/RemoteChain.swift
index f80e8fcf..7b64b8e8 100644
--- a/Sources/SSFModels/SSFModels/RemoteChain.swift
+++ b/Sources/SSFModels/SSFModels/RemoteChain.swift
@@ -45,9 +45,11 @@ public struct XcmAvailableDestination: Codable, Hashable {
public struct XcmAvailableAsset: Codable, Hashable {
public let id: String
public let symbol: String
+ public let minAmount: String?
- public init(id: String, symbol: String) {
+ public init(id: String, symbol: String, minAmount: String?) {
self.id = id
self.symbol = symbol
+ self.minAmount = minAmount
}
}
diff --git a/Sources/SSFNetwork/Operations/ManualOperation.swift b/Sources/SSFNetwork/Operations/ManualOperation.swift
new file mode 100644
index 00000000..00bacd0c
--- /dev/null
+++ b/Sources/SSFNetwork/Operations/ManualOperation.swift
@@ -0,0 +1,72 @@
+import Foundation
+import RobinHood
+
+public final class ManualOperation: BaseOperation {
+ private let lockQueue = DispatchQueue(
+ label: "jp.co.soramitsu.asyncoperation",
+ attributes: .concurrent
+ )
+
+ override public var isAsynchronous: Bool {
+ true
+ }
+
+ private var _isExecuting: Bool = false
+ override public private(set) var isExecuting: Bool {
+ get {
+ lockQueue.sync { () -> Bool in
+ _isExecuting
+ }
+ }
+ set {
+ willChangeValue(forKey: "isExecuting")
+ lockQueue.sync(flags: [.barrier]) {
+ _isExecuting = newValue
+ }
+ didChangeValue(forKey: "isExecuting")
+ }
+ }
+
+ private var _isFinished: Bool = false
+ override public private(set) var isFinished: Bool {
+ get {
+ lockQueue.sync { () -> Bool in
+ _isFinished
+ }
+ }
+ set {
+ willChangeValue(forKey: "isFinished")
+ lockQueue.sync(flags: [.barrier]) {
+ _isFinished = newValue
+ }
+ didChangeValue(forKey: "isFinished")
+ }
+ }
+
+ override public func start() {
+ isFinished = false
+ isExecuting = true
+ main()
+ }
+
+ override public func main() {
+ super.main()
+
+ if isCancelled {
+ finish()
+ return
+ }
+
+ if result != nil {
+ finish()
+ return
+ }
+ }
+
+ public func finish() {
+ if isExecuting {
+ isExecuting = false
+ isFinished = true
+ }
+ }
+}
diff --git a/Sources/SSFOklinkIndexer/OklinkHistoryRequest.swift b/Sources/SSFOklinkIndexer/OklinkHistoryRequest.swift
index 0b4d3677..54510c93 100644
--- a/Sources/SSFOklinkIndexer/OklinkHistoryRequest.swift
+++ b/Sources/SSFOklinkIndexer/OklinkHistoryRequest.swift
@@ -14,9 +14,9 @@ final class OklinkHistoryRequest: RequestConfig {
]
var headers: [HTTPHeader]?
- if let apiKey = chainAsset.chain.externalApi?.history?.apiKey {
- headers?.append(HTTPHeader(field: "Ok-Access-Key", value: apiKey))
- }
+// if let apiKey = chainAsset.chain.externalApi?.history?.apiKey {
+// headers?.append(HTTPHeader(field: "Ok-Access-Key", value: apiKey))
+// }
super.init(
baseURL: baseUrl,
diff --git a/Sources/SSFPrices/PriceProviderServiceModel.swift b/Sources/SSFPrices/PriceProviderServiceModel.swift
new file mode 100644
index 00000000..63b8a3e0
--- /dev/null
+++ b/Sources/SSFPrices/PriceProviderServiceModel.swift
@@ -0,0 +1,6 @@
+import SSFModels
+
+public struct PriceProviderServiceModel {
+ let type: PriceProviderType
+ let service: PriceProviderServiceProtocol
+}
diff --git a/Sources/SSFPrices/PriceProviderServiceProtocol.swift b/Sources/SSFPrices/PriceProviderServiceProtocol.swift
new file mode 100644
index 00000000..4dcdc7b7
--- /dev/null
+++ b/Sources/SSFPrices/PriceProviderServiceProtocol.swift
@@ -0,0 +1,6 @@
+import SSFModels
+
+// sourcery: AutoMockable
+public protocol PriceProviderServiceProtocol {
+ func getPrices(for chainAssets: [ChainAsset], currencies: [Currency]) async -> [PriceData]
+}
diff --git a/Sources/SSFPrices/PricesService.swift b/Sources/SSFPrices/PricesService.swift
new file mode 100644
index 00000000..ebd01215
--- /dev/null
+++ b/Sources/SSFPrices/PricesService.swift
@@ -0,0 +1,193 @@
+import SSFModels
+import RobinHood
+import Foundation
+import SSFAssetManagment
+
+// sourcery: AutoMockable
+public protocol PricesServiceProtocol {
+ func getPriceDataFromCache(for chainAssets: [ChainAsset], currencies: [Currency]) async -> [ChainAsset: [PriceData]]
+ func getPriceDataFromAPI(for chainAssets: [ChainAsset], currencies: [Currency]) async -> [ChainAsset: [PriceData]]
+}
+
+public final class PricesService {
+ private let chainRepository: AnyDataProviderRepository
+ private let chainAssetFetcher: ChainAssetFetchingServiceProtocol
+ private let operationQueue: OperationQueue
+ private let priceProviders: [PriceProviderServiceModel]
+ private let chainlinkService: PriceProviderServiceProtocol?
+ private let coingeckoService: PriceProviderServiceProtocol?
+ private let subqueryService: PriceProviderServiceProtocol?
+ private var chainAssets: [ChainAsset] = []
+ private var currencies: [Currency] = []
+
+ public init(
+ chainRepository: AnyDataProviderRepository,
+ chainAssetFetcher: ChainAssetFetchingServiceProtocol,
+ operationQueue: OperationQueue,
+ priceProviders: [PriceProviderServiceModel],
+ chainlinkService: PriceProviderServiceProtocol?,
+ coingeckoService: PriceProviderServiceProtocol?,
+ subqueryService: PriceProviderServiceProtocol?
+ ) {
+ self.chainRepository = chainRepository
+ self.chainAssetFetcher = chainAssetFetcher
+ self.operationQueue = operationQueue
+ self.priceProviders = priceProviders
+ self.chainlinkService = chainlinkService
+ self.coingeckoService = coingeckoService
+ self.subqueryService = subqueryService
+ }
+
+ public func getPriceDataFromCache(
+ for chainAssets: [ChainAsset],
+ currencies: [Currency]
+ ) async -> [ChainAsset: [PriceData]] {
+ let allChainAssets = await chainAssetFetcher.fetch(filters: [], sorts: [], forceUpdate: false)
+ let requestedChainAssets = allChainAssets.filter { dbChainAsset in
+ chainAssets.contains { chainAsset in
+ return chainAsset.chain.chainId == dbChainAsset.chain.chainId && chainAsset.asset.id == dbChainAsset.asset.id
+ }
+ }
+ var chainAssetsPriceData: [ChainAsset: [PriceData]] = [:]
+ requestedChainAssets.forEach { chainAsset in
+ chainAssetsPriceData[chainAsset] = chainAsset.asset.priceData.filter({ priceData in
+ currencies.map { $0.id }.contains(priceData.currencyId)
+ })
+ }
+ return chainAssetsPriceData
+ }
+
+ public func getPriceDataFromAPI(
+ for chainAssets: [ChainAsset],
+ currencies: [Currency]
+ ) async -> [ChainAsset: [PriceData]] {
+ async let chainlinkPrices = await self.priceProviders.first { $0.type == .chainlink }?.service
+ .getPrices(for: chainAssets, currencies: currencies) ?? []
+ async let coingeckoPrices = await self.priceProviders.first { $0.type == .coingecko }?.service
+ .getPrices(for: chainAssets, currencies: currencies) ?? []
+ async let subqueryPrices = await self.priceProviders.first { $0.type == .coingecko }?.service
+ .getPrices(for: chainAssets, currencies: currencies) ?? []
+ let mergedPrices = await merge(chainlinkPrices: chainlinkPrices, coingeckoPrices: coingeckoPrices, soraSubqueryPrices: subqueryPrices)
+ handle(prices: mergedPrices, for: chainAssets)
+ var chainAssetsPriceData: [ChainAsset: [PriceData]] = [:]
+ chainAssets.forEach { chainAsset in
+ chainAssetsPriceData[chainAsset] = mergedPrices.filter { $0.priceId == chainAsset.asset.priceId }
+ }
+ return chainAssetsPriceData
+ }
+}
+
+// MARK: - Private methods
+
+private extension PricesService {
+ func merge(chainlinkPrices: [PriceData], coingeckoPrices: [PriceData], soraSubqueryPrices: [PriceData]) -> [PriceData] {
+ var prices: [PriceData] = []
+ prices = self.merge(coingeckoPrices: coingeckoPrices, chainlinkPrices: chainlinkPrices)
+ prices = self.merge(coingeckoPrices: prices, soraSubqueryPrices: soraSubqueryPrices)
+ return prices
+ }
+
+ func merge(coingeckoPrices: [PriceData], chainlinkPrices: [PriceData]) -> [PriceData] {
+ if chainlinkPrices.isEmpty {
+ let prices = makePrices(from: coingeckoPrices, for: .chainlink)
+ return coingeckoPrices + prices
+ }
+ let caPriceIds = Set(chainAssets.compactMap { $0.asset.coingeckoPriceId })
+ let sqPriceIds = Set(chainlinkPrices.compactMap { $0.coingeckoPriceId })
+
+ let replacedFiatDayChange: [PriceData] = chainlinkPrices.compactMap { chainlinkPrice in
+ let coingeckoPrice = coingeckoPrices
+ .first(where: { $0.coingeckoPriceId == chainlinkPrice.coingeckoPriceId })
+ return chainlinkPrice.replaceFiatDayChange(fiatDayChange: coingeckoPrice?.fiatDayChange)
+ }
+
+ let filtered = coingeckoPrices.filter { coingeckoPrice in
+ guard let coingeckoPriceId = coingeckoPrice.coingeckoPriceId else {
+ return true
+ }
+ return !caPriceIds.intersection(sqPriceIds).contains(coingeckoPriceId)
+ }
+
+ return filtered + replacedFiatDayChange
+ }
+
+ func merge(
+ coingeckoPrices: [PriceData],
+ soraSubqueryPrices: [PriceData]
+ ) -> [PriceData] {
+ if soraSubqueryPrices.isEmpty {
+ let prices = makePrices(from: coingeckoPrices, for: .sorasubquery)
+ return coingeckoPrices + prices
+ }
+ let caPriceIds = Set(chainAssets.compactMap { $0.asset.priceId })
+ let sqPriceIds = Set(soraSubqueryPrices.compactMap { $0.priceId })
+
+ let filtered = coingeckoPrices.filter { coingeckoPrice in
+ let chainAsset = chainAssets
+ .first { $0.asset.coingeckoPriceId == coingeckoPrice.priceId }
+ guard let priceId = chainAsset?.asset.priceId else {
+ return true
+ }
+ return !caPriceIds.intersection(sqPriceIds).contains(priceId)
+ }
+
+ return filtered + soraSubqueryPrices
+ }
+
+ func makePrices(
+ from coingeckoPrices: [PriceData],
+ for type: PriceProviderType
+ ) -> [PriceData] {
+ let typePriceChainAssets = chainAssets
+ .filter { $0.asset.priceProvider?.type == type }
+
+ let prices = coingeckoPrices.filter { coingeckoPrice in
+ typePriceChainAssets.contains { chainAsset in
+ chainAsset.asset.coingeckoPriceId == coingeckoPrice.coingeckoPriceId
+ }
+ }
+
+ let newPrices: [PriceData] = prices.compactMap { price in
+ guard let chainAsset = typePriceChainAssets
+ .first(where: { $0.asset.coingeckoPriceId == price.coingeckoPriceId }) else
+ {
+ return nil
+ }
+ return PriceData(
+ currencyId: price.currencyId,
+ priceId: chainAsset.asset.priceId ?? price.priceId,
+ price: price.price,
+ fiatDayChange: price.fiatDayChange,
+ coingeckoPriceId: price.coingeckoPriceId
+ )
+ }
+ return newPrices
+ }
+
+ func handle(prices: [PriceData], for chainAssets: [ChainAsset]) {
+ var updatedChains: [ChainModel] = []
+ let uniqChains: [ChainModel] = chainAssets.compactMap { $0.chain }.uniq { $0.chainId }
+ for chain in uniqChains {
+ var updatedAssets: [AssetModel] = []
+ for chainAsset in chain.chainAssets {
+ let assetPrices = prices.filter { $0.priceId == chainAsset.asset.priceId }
+ let updatedAsset = chainAsset.asset.replacingPrice(assetPrices)
+ updatedAssets.append(updatedAsset)
+ }
+ let chainRemoteTokens = ChainRemoteTokens(
+ type: chain.tokens.type,
+ whitelist: chain.tokens.whitelist,
+ utilityId: chain.tokens.utilityId,
+ tokens: Set(updatedAssets)
+ )
+ let updatedChain = chain.replacing(chainRemoteTokens)
+ updatedChains.append(updatedChain)
+ }
+ let saveOperation = chainRepository.saveOperation({
+ updatedChains
+ }, {
+ []
+ })
+ operationQueue.addOperation(saveOperation)
+ }
+}
diff --git a/Sources/SSFSoraSubqueryProvider/PriceFetcher/SoraSubqueryPrice.swift b/Sources/SSFSoraSubqueryProvider/PriceFetcher/SoraSubqueryPrice.swift
new file mode 100644
index 00000000..a9952240
--- /dev/null
+++ b/Sources/SSFSoraSubqueryProvider/PriceFetcher/SoraSubqueryPrice.swift
@@ -0,0 +1,23 @@
+import Foundation
+import SSFUtils
+
+struct SoraSubqueryPriceResponse: Decodable {
+ let entities: SoraSubqueryPricePage
+}
+
+struct SoraSubqueryPricePage: Decodable {
+ let nodes: [SoraSubqueryPrice]
+ let pageInfo: SubqueryPageInfo
+}
+
+struct SoraSubqueryPrice: Decodable {
+ enum CodingKeys: String, CodingKey {
+ case id
+ case priceUsd = "priceUSD"
+ case priceChangeDay
+ }
+
+ let id: String
+ let priceUsd: String?
+ let priceChangeDay: Decimal?
+}
diff --git a/Sources/SSFSoraSubqueryProvider/PriceFetcher/SoraSubqueryPriceFetcher.swift b/Sources/SSFSoraSubqueryProvider/PriceFetcher/SoraSubqueryPriceFetcher.swift
new file mode 100644
index 00000000..1fca53cc
--- /dev/null
+++ b/Sources/SSFSoraSubqueryProvider/PriceFetcher/SoraSubqueryPriceFetcher.swift
@@ -0,0 +1,114 @@
+import Foundation
+import RobinHood
+import SSFIndexers
+import SSFModels
+import SSFNetwork
+import SSFUtils
+
+enum SubqueryPriceFetcherError: Error {
+ case missingBlockExplorer
+}
+
+public protocol SoraSubqueryPriceFetcherProtocol {
+ func fetchPriceOperation(
+ for chainAssets: [ChainAsset]
+ ) -> BaseOperation<[PriceData]>
+}
+
+public final class SoraSubqueryPriceFetcher: SoraSubqueryPriceFetcherProtocol {
+ public func fetchPriceOperation(
+ for chainAssets: [ChainAsset]
+ ) -> BaseOperation<[PriceData]> {
+ AwaitOperation { [weak self] in
+ guard let self else { return [] }
+
+ guard let blockExplorer = chainAssets.first(where: { chainAsset in
+ chainAsset.chain.knownChainEquivalent == .soraMain
+ })?.chain.externalApi?.pricing else {
+ throw SubqueryPriceFetcherError.missingBlockExplorer
+ }
+ let priceIds = chainAssets.map { $0.asset.priceProvider?.id }.compactMap { $0 }
+ let prices = try await self.fetch(priceIds: priceIds, url: blockExplorer.url)
+
+ return prices.compactMap { price in
+ let chainAsset = chainAssets
+ .first(where: { $0.asset.tokenProperties?.currencyId == price.id })
+
+ guard let chainAsset = chainAsset,
+ chainAsset.asset.priceProvider?.type == .sorasubquery,
+ let priceId = chainAsset.asset.priceId else
+ {
+ return nil
+ }
+
+ return PriceData(
+ currencyId: "usd",
+ priceId: priceId,
+ price: "\(price.priceUsd.or("0"))",
+ fiatDayChange: price.priceChangeDay,
+ coingeckoPriceId: chainAsset.asset.coingeckoPriceId
+ )
+ }
+ }
+ }
+
+ private func fetch(
+ priceIds: [String],
+ url: URL
+ ) async throws -> [SoraSubqueryPrice] {
+ var prices: [SoraSubqueryPrice] = []
+ var cursor = ""
+ var allPricesFetched = false
+
+ while !allPricesFetched {
+ let response = try await loadNewPrices(url: url, priceIds: priceIds, cursor: cursor)
+ prices = prices + response.nodes
+ allPricesFetched = response.pageInfo.hasNextPage.or(false) == false
+ cursor = response.pageInfo.endCursor.or("")
+ }
+
+ return prices
+ }
+
+ private func loadNewPrices(
+ url: URL,
+ priceIds: [String],
+ cursor: String
+ ) async throws -> SoraSubqueryPricePage {
+ let request = try SoraSubqueryPricesRequest(
+ baseURL: url,
+ query: queryString(priceIds: priceIds, cursor: cursor)
+ )
+ let worker = NetworkWorkerDefault()
+ let response: GraphQLResponse = try await worker
+ .performRequest(with: request)
+
+ switch response {
+ case let .data(data):
+ return data.entities
+ case let .errors(error):
+ throw error
+ }
+ }
+
+ private func queryString(priceIds: [String], cursor: String) -> String {
+ """
+ query FiatPriceQuery {
+ entities: assets(
+ first: 100
+ after: "\(cursor)",
+ filter: {id: {in: \(priceIds)}}) {
+ nodes {
+ id
+ priceUSD
+ priceChangeDay
+ }
+ pageInfo {
+ hasNextPage
+ endCursor
+ }
+ }
+ }
+ """
+ }
+}
diff --git a/Sources/SSFSoraSubqueryProvider/PriceFetcher/SoraSubqueryPricesRequest.swift b/Sources/SSFSoraSubqueryProvider/PriceFetcher/SoraSubqueryPricesRequest.swift
new file mode 100644
index 00000000..c3251467
--- /dev/null
+++ b/Sources/SSFSoraSubqueryProvider/PriceFetcher/SoraSubqueryPricesRequest.swift
@@ -0,0 +1,30 @@
+import Foundation
+import RobinHood
+import SSFModels
+import SSFNetwork
+import SSFUtils
+
+class SoraSubqueryPricesRequest: RequestConfig {
+ init(
+ baseURL: URL,
+ query: String
+ ) throws {
+ let defaultHeaders = [
+ HTTPHeader(
+ field: HttpHeaderKey.contentType.rawValue,
+ value: HttpContentType.json.rawValue
+ ),
+ ]
+
+ let info = JSON.dictionaryValue(["query": JSON.stringValue(query)])
+ let data = try JSONEncoder().encode(info)
+
+ super.init(
+ baseURL: baseURL,
+ method: .post,
+ endpoint: nil,
+ headers: defaultHeaders,
+ body: data
+ )
+ }
+}
diff --git a/Sources/SSFSoraSubqueryProvider/PriceFetcher/SubqueryPageInfo.swift b/Sources/SSFSoraSubqueryProvider/PriceFetcher/SubqueryPageInfo.swift
new file mode 100644
index 00000000..310c297e
--- /dev/null
+++ b/Sources/SSFSoraSubqueryProvider/PriceFetcher/SubqueryPageInfo.swift
@@ -0,0 +1,21 @@
+struct SubqueryPageInfo: Decodable {
+ let startCursor: String?
+ let endCursor: String?
+ let hasNextPage: Bool?
+
+ func toContext() -> [String: String]? {
+ if startCursor == nil, endCursor == nil {
+ return nil
+ }
+ var context: [String: String] = [:]
+ if let startCursor = startCursor {
+ context["startCursor"] = startCursor
+ }
+
+ if let endCursor = endCursor {
+ context["endCursor"] = endCursor
+ }
+
+ return context
+ }
+}
diff --git a/Sources/SSFSoraSubqueryProvider/SoraSubqueryService.swift b/Sources/SSFSoraSubqueryProvider/SoraSubqueryService.swift
new file mode 100644
index 00000000..d0119e03
--- /dev/null
+++ b/Sources/SSFSoraSubqueryProvider/SoraSubqueryService.swift
@@ -0,0 +1,37 @@
+import RobinHood
+import SSFModels
+import SSFPrices
+
+public final class SoraSubqueryService: PriceProviderServiceProtocol {
+ let soraSubqueryFetcher: SoraSubqueryPriceFetcherProtocol
+
+ public init(soraSubqueryFetcher: SoraSubqueryPriceFetcherProtocol) {
+ self.soraSubqueryFetcher = soraSubqueryFetcher
+ }
+
+ public func getPrices(for chainAssets: [ChainAsset], currencies: [Currency]) async -> [PriceData] {
+ let operation = createSoraSubqueryOperation(for: chainAssets, currencies: currencies)
+ do {
+ return try operation.extractNoCancellableResultData()
+ } catch {
+ return []
+ }
+ }
+
+ private func createSoraSubqueryOperation(
+ for chainAssets: [ChainAsset],
+ currencies: [Currency]
+ ) -> BaseOperation<[PriceData]> {
+ guard currencies.count == 1, currencies.first?.id == Currency.defaultCurrency().id else {
+ return BaseOperation.createWithResult([])
+ }
+
+ let chainAssets = chainAssets.filter { $0.asset.priceProvider?.type == .sorasubquery }
+ guard chainAssets.isNotEmpty else {
+ return BaseOperation.createWithResult([])
+ }
+
+ let operation = soraSubqueryFetcher.fetchPriceOperation(for: chainAssets)
+ return operation
+ }
+}
diff --git a/Sources/SSFTransactionHistory/Model/TransactionContext.swift b/Sources/SSFTransactionHistory/Model/TransactionContext.swift
index 27e853d4..5c71ad74 100644
--- a/Sources/SSFTransactionHistory/Model/TransactionContext.swift
+++ b/Sources/SSFTransactionHistory/Model/TransactionContext.swift
@@ -189,7 +189,6 @@ struct TransactionContext {
)
return
}
-
case .migration:
return nil
}
diff --git a/Sources/SSFUtils/SSFUtils/Classes/Data/Data+Length.swift b/Sources/SSFUtils/SSFUtils/Classes/Data/Data+Length.swift
new file mode 100644
index 00000000..0560e52b
--- /dev/null
+++ b/Sources/SSFUtils/SSFUtils/Classes/Data/Data+Length.swift
@@ -0,0 +1,38 @@
+//
+// Data+Length.swift
+// MEWwalletKit
+//
+// Created by Mikhail Nikanorov on 4/29/19.
+// Copyright © 2019 MyEtherWallet Inc. All rights reserved.
+//
+
+import Foundation
+
+public extension Data {
+ mutating func setLength(_ length: Int, appendFromLeft: Bool = true, negative: Bool = false) {
+ guard count < length else {
+ return
+ }
+
+ let leftLength = length - count
+
+ if appendFromLeft {
+ self = Data(repeating: negative ? 0xFF : 0x00, count: leftLength) + self
+ } else {
+ self += Data(repeating: negative ? 0xFF : 0x00, count: leftLength)
+ }
+ }
+
+ func setLengthLeft(_ toBytes: Int, isNegative: Bool = false) -> Data {
+ var data = self
+ data.setLength(toBytes, negative: isNegative)
+ return data
+ }
+
+ func setLengthRight(_ toBytes: Int, isNegative: Bool = false) -> Data {
+ var data = self
+ data.setLength(toBytes, appendFromLeft: false, negative: isNegative)
+
+ return data
+ }
+}
diff --git a/Sources/SSFUtils/SSFUtils/Classes/Network/WebSocketEngine.swift b/Sources/SSFUtils/SSFUtils/Classes/Network/WebSocketEngine.swift
index f25361bc..ecb5074a 100644
--- a/Sources/SSFUtils/SSFUtils/Classes/Network/WebSocketEngine.swift
+++ b/Sources/SSFUtils/SSFUtils/Classes/Network/WebSocketEngine.swift
@@ -170,7 +170,6 @@ public final class WebSocketEngine {
connection.disconnect()
logger?.debug("Cancel socket connection")
-
case .waitingReconnection:
logger?.debug("Cancel reconnection scheduler due to disconnection")
reconnectionScheduler.cancel()
diff --git a/Sources/SSFXCM/Classes/CallPathDeterminer.swift b/Sources/SSFXCM/Classes/CallPathDeterminer.swift
index fd0d88a9..60b7a928 100644
--- a/Sources/SSFXCM/Classes/CallPathDeterminer.swift
+++ b/Sources/SSFXCM/Classes/CallPathDeterminer.swift
@@ -72,7 +72,6 @@ final class CallPathDeterminerImpl: CallPathDeterminer {
return .xTokensTransferMultiasset
case (.xTokens, .parachain):
return .xTokensTransferMultiasset
-
case (.polkadotXcm, .relaychain):
return .polkadotXcmLimitedReserveWithdrawAssets
case (.polkadotXcm, .nativeParachain):
diff --git a/Sources/SSFXCM/Classes/Models/XcmCallPath.swift b/Sources/SSFXCM/Classes/Models/XcmCallPath.swift
index b6b1aa21..9b57e902 100644
--- a/Sources/SSFXCM/Classes/Models/XcmCallPath.swift
+++ b/Sources/SSFXCM/Classes/Models/XcmCallPath.swift
@@ -14,12 +14,10 @@ enum XcmCallPath: StorageCodingPathProtocol {
switch self {
case .parachainId:
return (moduleName: "parachainInfo", itemName: "parachainId")
-
case .xcmPalletLimitedTeleportAssets:
return (moduleName: "xcmPallet", itemName: "limited_teleport_assets")
case .xcmPalletLimitedReserveTransferAssets:
return (moduleName: "xcmPallet", itemName: "limited_reserve_transfer_assets")
-
case .polkadotXcmTeleportAssets:
return (moduleName: "polkadotXcm", itemName: "teleport_assets")
case .polkadotXcmLimitedTeleportAssets:
@@ -28,12 +26,10 @@ enum XcmCallPath: StorageCodingPathProtocol {
return (moduleName: "polkadotXcm", itemName: "limited_reserve_transfer_assets")
case .polkadotXcmLimitedReserveWithdrawAssets:
return (moduleName: "polkadotXcm", itemName: "limited_reserve_withdraw_assets")
-
case .xTokensTransfer:
return (moduleName: "xTokens", itemName: "transfer")
case .xTokensTransferMultiasset:
return (moduleName: "xTokens", itemName: "transfer_multiasset")
-
case .bridgeProxyBurn:
return (moduleName: "bridgeProxy", itemName: "burn")
case .bridgeProxyTransactions:
diff --git a/Sources/SSFXCM/Classes/Models/XcmDestination.swift b/Sources/SSFXCM/Classes/Models/XcmDestination.swift
index f7901c13..0be40bca 100644
--- a/Sources/SSFXCM/Classes/Models/XcmDestination.swift
+++ b/Sources/SSFXCM/Classes/Models/XcmDestination.swift
@@ -8,7 +8,7 @@ enum XcmChainType {
case soraMainnet
static func determineChainType(for chain: ChainModel) throws -> XcmChainType {
- guard let paraId = UInt32(chain.paraId ?? "") else {
+ guard let paraId = UInt32(chain.properties.paraId ?? "") else {
if chain.knownChainEquivalent == .soraMain || chain.knownChainEquivalent == .soraTest {
return .soraMainnet
}
diff --git a/Sources/SSFXCM/Classes/XcmCallFactory.swift b/Sources/SSFXCM/Classes/XcmCallFactory.swift
index 7fe85645..1c5e5fb7 100644
--- a/Sources/SSFXCM/Classes/XcmCallFactory.swift
+++ b/Sources/SSFXCM/Classes/XcmCallFactory.swift
@@ -83,7 +83,7 @@ final class XcmCallFactory: XcmCallFactoryProtocol {
weightLimit: BigUInt?,
path: XcmCallPath
) -> RuntimeCall {
- let destParachainId = UInt32(destChainModel.paraId ?? "")
+ let destParachainId = UInt32(destChainModel.properties.paraId ?? "")
let destParents: UInt8 = destChainModel.isRelaychain ? 1 : 0
let destination = createVersionedMultiLocation(
version: version,
@@ -138,7 +138,7 @@ final class XcmCallFactory: XcmCallFactoryProtocol {
weightLimit: BigUInt,
path: XcmCallPath
) -> RuntimeCall {
- let destParachainId = UInt32(destChainModel.paraId ?? "")
+ let destParachainId = UInt32(destChainModel.properties.paraId ?? "")
let destParents: UInt8 = destChainModel.isRelaychain ? 1 : 0
let destination = createVersionedMultiLocation(
version: version,
@@ -191,7 +191,7 @@ final class XcmCallFactory: XcmCallFactoryProtocol {
amount: amount
)
- let destParachainId = UInt32(destChainModel.paraId ?? "")
+ let destParachainId = UInt32(destChainModel.properties.paraId ?? "")
let destination = createVersionedMultiLocation(
version: version,
chainModel: destChainModel,
@@ -247,7 +247,7 @@ final class XcmCallFactory: XcmCallFactoryProtocol {
amount: amount
)
- let destParachainId = UInt32(destChainModel.paraId ?? "")
+ let destParachainId = UInt32(destChainModel.properties.paraId ?? "")
let destination = createVersionedMultiLocation(
version: version,
chainModel: destChainModel,
@@ -295,7 +295,7 @@ final class XcmCallFactory: XcmCallFactoryProtocol {
let networkId = BridgeTypesGenericNetworkId(from: destChainModel)
let assetId = SoraAssetId(wrappedValue: currencyId)
- let destParachainId = UInt32(destChainModel.paraId ?? "")
+ let destParachainId = UInt32(destChainModel.properties.paraId ?? "")
let destParents: UInt8 = destChainModel.isRelaychain ? 1 : 0
let destination = createVersionedMultiLocation(
version: .V3,
diff --git a/Tests/SSFAccountManagmentTests/AccountManagementServiceTests.swift b/Tests/SSFAccountManagmentTests/AccountManagementServiceTests.swift
index d1e18aef..a0b02658 100644
--- a/Tests/SSFAccountManagmentTests/AccountManagementServiceTests.swift
+++ b/Tests/SSFAccountManagmentTests/AccountManagementServiceTests.swift
@@ -68,14 +68,15 @@ final class AccountManagementServiceTests: XCTestCase {
Task { [weak self] in
do {
- let updatedAccount = try await self?.service?.updateEnabilibilty(for: chainAsset.chainAssetId.id)
-
+ let updatedAccount = try await self?.service?
+ .updateEnabilibilty(for: chainAsset.chainAssetId.id)
+
DispatchQueue.main.async { [weak self] in
XCTAssertTrue(
self?.accountManagementWorker?
.saveAccountCompletionCalled ?? false
)
-
+
XCTAssertTrue(updatedAccount != nil)
}
} catch {
@@ -102,10 +103,9 @@ final class AccountManagementServiceTests: XCTestCase {
private extension AccountManagementServiceTests {
enum TestData {
static let chain = ChainModel(
- rank: 1,
disabled: true,
chainId: "Kusama",
- paraId: "test",
+ parentId: "2",
name: "test",
tokens: ChainRemoteTokens(
type: .config,
@@ -117,22 +117,37 @@ private extension AccountManagementServiceTests {
nodes: [],
icon: nil,
iosMinAppVersion: nil,
- properties: .init(addressPrefix: "1", rank: "2", paraId: "test", ethereumBased: true)
+ properties: .init(
+ addressPrefix: "1",
+ rank: "2",
+ paraId: "test",
+ ethereumBased: true,
+ crowdloans: nil
+ ),
+ identityChain: nil
)
static let asset = AssetModel(
id: "2",
name: "test",
symbol: "XOR",
- isUtility: true,
precision: 1,
- substrateType: .soraAsset,
+ icon: nil,
+ tokenProperties: TokenProperties(
+ priceId: nil,
+ currencyId: nil,
+ color: nil,
+ type: .soraAsset,
+ isNative: false,
+ staking: nil
+ ),
+ existentialDeposit: nil,
+ isUtility: true,
+ purchaseProviders: nil,
ethereumType: nil,
- tokenProperties: nil,
- price: nil,
- priceId: nil,
+ priceProvider: nil,
coingeckoPriceId: nil,
- priceProvider: nil
+ priceData: []
)
static let chainAccounts = ChainAccountModel(
diff --git a/Tests/SSFAccountManagmentTests/JSONExportServiceTests.swift b/Tests/SSFAccountManagmentTests/JSONExportServiceTests.swift
index 790d596d..3de46274 100644
--- a/Tests/SSFAccountManagmentTests/JSONExportServiceTests.swift
+++ b/Tests/SSFAccountManagmentTests/JSONExportServiceTests.swift
@@ -97,11 +97,9 @@ extension JSONExportServiceTests {
)
static let chain = ChainModel(
- rank: 1,
disabled: true,
chainId: "Kusama",
parentId: "2",
- paraId: "test",
name: "test",
tokens: ChainRemoteTokens(
type: .config,
@@ -113,7 +111,14 @@ extension JSONExportServiceTests {
nodes: [],
icon: nil,
iosMinAppVersion: nil,
- properties: .init(addressPrefix: "1", rank: "2", paraId: "test", ethereumBased: true)
+ properties: .init(
+ addressPrefix: "1",
+ rank: "2",
+ paraId: "test",
+ ethereumBased: true,
+ crowdloans: nil
+ ),
+ identityChain: nil
)
static let response = ChainAccountResponse(
diff --git a/Tests/SSFAccountManagmentTests/MnemonicExportServiceTests.swift b/Tests/SSFAccountManagmentTests/MnemonicExportServiceTests.swift
index 45d1a27f..cbbc3c74 100644
--- a/Tests/SSFAccountManagmentTests/MnemonicExportServiceTests.swift
+++ b/Tests/SSFAccountManagmentTests/MnemonicExportServiceTests.swift
@@ -97,11 +97,9 @@ extension MnemonicExportServiceTests {
)
static let chain = ChainModel(
- rank: 1,
disabled: true,
chainId: "Kusama",
parentId: "2",
- paraId: "test",
name: "test",
tokens: ChainRemoteTokens(
type: .config,
@@ -113,7 +111,14 @@ extension MnemonicExportServiceTests {
nodes: [],
icon: nil,
iosMinAppVersion: nil,
- properties: .init(addressPrefix: "1", rank: "2", paraId: "test", ethereumBased: true)
+ properties: .init(
+ addressPrefix: "1",
+ rank: "2",
+ paraId: "test",
+ ethereumBased: true,
+ crowdloans: nil
+ ),
+ identityChain: nil
)
static let response = ChainAccountResponse(
diff --git a/Tests/SSFAccountManagmentTests/SeedExportServiceTests.swift b/Tests/SSFAccountManagmentTests/SeedExportServiceTests.swift
index 07ea471b..a23c03d0 100644
--- a/Tests/SSFAccountManagmentTests/SeedExportServiceTests.swift
+++ b/Tests/SSFAccountManagmentTests/SeedExportServiceTests.swift
@@ -92,11 +92,9 @@ extension SeedExportServiceTests {
)
static let chain = ChainModel(
- rank: 1,
disabled: true,
chainId: "Kusama",
parentId: "2",
- paraId: "test",
name: "test",
tokens: ChainRemoteTokens(
type: .config,
@@ -108,7 +106,14 @@ extension SeedExportServiceTests {
nodes: [],
icon: nil,
iosMinAppVersion: nil,
- properties: .init(addressPrefix: "1", rank: "2", paraId: "test", ethereumBased: true)
+ properties: .init(
+ addressPrefix: "1",
+ rank: "2",
+ paraId: "test",
+ ethereumBased: true,
+ crowdloans: nil
+ ),
+ identityChain: nil
)
static let response = ChainAccountResponse(
diff --git a/Tests/SSFAssetManagmentTests/ChainAssetsFetchWorkerTests.swift b/Tests/SSFAssetManagmentTests/ChainAssetsFetchWorkerTests.swift
index 933d9b22..1a7b01b8 100644
--- a/Tests/SSFAssetManagmentTests/ChainAssetsFetchWorkerTests.swift
+++ b/Tests/SSFAssetManagmentTests/ChainAssetsFetchWorkerTests.swift
@@ -41,7 +41,7 @@ private extension ChainAssetsFetchWorkerTests {
func prepareRepostory() -> CoreDataRepository {
let facade = SubstrateStorageTestFacade()
let apiKeyInjector = ApiKeyInjectorMock()
- let mapper = ChainModelMapper(apiKeyInjector: apiKeyInjector)
+ let mapper = ChainModelMapper()
let chains: [ChainModel] = (0 ..< 10).map { index in
ChainModelGenerator.generateChain(
diff --git a/Tests/SSFAssetManagmentTests/ChainAssetsFetchingServiceTests.swift b/Tests/SSFAssetManagmentTests/ChainAssetsFetchingServiceTests.swift
index 6413035e..f751bda7 100644
--- a/Tests/SSFAssetManagmentTests/ChainAssetsFetchingServiceTests.swift
+++ b/Tests/SSFAssetManagmentTests/ChainAssetsFetchingServiceTests.swift
@@ -420,11 +420,8 @@ final class ChainAssetsFetchingServiceTests: XCTestCase {
func testFetchSortPrice() async {
// arrange
let chain = ChainModel(
- rank: 1,
disabled: true,
chainId: "Kusama",
- parentId: "2",
- paraId: "test",
name: "test",
tokens: ChainRemoteTokens(
type: .config,
@@ -436,10 +433,20 @@ final class ChainAssetsFetchingServiceTests: XCTestCase {
nodes: [],
icon: nil,
iosMinAppVersion: nil,
- properties: .init(addressPrefix: "1", rank: "2", paraId: "test", ethereumBased: true)
+ properties: .init(
+ addressPrefix: "1",
+ rank: "1",
+ paraId: "test",
+ ethereumBased: true,
+ crowdloans: nil
+ ),
+ identityChain: nil
)
- let extectedAssetArray = chain.tokens.tokens?.compactMap { ChainAsset(chain: chain, asset: $0) }
+ let extectedAssetArray = chain.tokens.tokens?.compactMap { ChainAsset(
+ chain: chain,
+ asset: $0
+ ) }
let chainAssetsFetcher = ChainAssetsFetchWorkerProtocolMock()
chainAssetsFetcher.getChainAssetsModelsReturnValue = extectedAssetArray
@@ -551,7 +558,12 @@ final class ChainAssetsFetchingServiceTests: XCTestCase {
let chain = TestData.chain
let asset = TestData.asset
let chainAsset = ChainAsset(chain: chain, asset: asset)
- chain.tokens = ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: [asset])
+ chain.tokens = ChainRemoteTokens(
+ type: .config,
+ whitelist: nil,
+ utilityId: nil,
+ tokens: [asset]
+ )
let chainWithStacking = TestData.chainWithStacking
let assetWithStacking = TestData.assetWithStacking
@@ -618,11 +630,9 @@ final class ChainAssetsFetchingServiceTests: XCTestCase {
func testFetchSortAssetId() async {
// arrange
let chain = ChainModel(
- rank: 3,
disabled: true,
chainId: "Kusama",
parentId: "2",
- paraId: "test",
name: "test",
tokens: ChainRemoteTokens(
type: .config,
@@ -634,10 +644,20 @@ final class ChainAssetsFetchingServiceTests: XCTestCase {
nodes: [],
icon: nil,
iosMinAppVersion: nil,
- properties: .init(addressPrefix: "1", rank: "2", paraId: "test", ethereumBased: true)
+ properties: .init(
+ addressPrefix: "1",
+ rank: "3",
+ paraId: "test",
+ ethereumBased: true,
+ crowdloans: nil
+ ),
+ identityChain: nil
)
- let extectedAssetArray = chain.tokens.tokens?.compactMap { ChainAsset(chain: chain, asset: $0) }
+ let extectedAssetArray = chain.tokens.tokens?.compactMap { ChainAsset(
+ chain: chain,
+ asset: $0
+ ) }
let chainAssetsFetcher = ChainAssetsFetchWorkerProtocolMock()
chainAssetsFetcher.getChainAssetsModelsReturnValue = extectedAssetArray
@@ -662,10 +682,8 @@ final class ChainAssetsFetchingServiceTests: XCTestCase {
private extension ChainAssetsFetchingServiceTests {
enum TestData {
static let chain = ChainModel(
- rank: 1,
disabled: true,
chainId: "Kusama",
- paraId: "test",
name: "test",
tokens: ChainRemoteTokens(
type: .config,
@@ -677,29 +695,40 @@ private extension ChainAssetsFetchingServiceTests {
nodes: [],
icon: nil,
iosMinAppVersion: nil,
- properties: .init(addressPrefix: "test", ethereumBased: true, crowdloans: true)
+ properties: .init(
+ addressPrefix: "test",
+ ethereumBased: true,
+ crowdloans: true
+ ),
+ identityChain: nil
)
static let asset = AssetModel(
id: "2",
name: "test",
symbol: "XOR",
- isUtility: true,
precision: 1,
- substrateType: .soraAsset,
+ icon: nil,
+ tokenProperties: TokenProperties(
+ priceId: nil,
+ currencyId: nil,
+ color: nil,
+ type: .soraAsset,
+ isNative: false,
+ staking: nil
+ ),
+ existentialDeposit: nil,
+ isUtility: true,
+ purchaseProviders: nil,
ethereumType: nil,
- tokenProperties: nil,
- price: nil,
- priceId: nil,
+ priceProvider: nil,
coingeckoPriceId: nil,
- priceProvider: nil
+ priceData: []
)
-
+
static let chainWithStacking = ChainModel(
- rank: 2,
disabled: true,
chainId: "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3",
- paraId: "test",
name: "test1",
tokens: ChainRemoteTokens(
type: .config,
@@ -711,22 +740,35 @@ private extension ChainAssetsFetchingServiceTests {
nodes: [],
icon: nil,
iosMinAppVersion: nil,
- properties: .init(addressPrefix: "1", crowdloans: false)
+ properties: ChainProperties(
+ addressPrefix: "1",
+ rank: "2",
+ paraId: "test"
+ ),
+ identityChain: nil
)
static let assetWithStacking = AssetModel(
id: "3",
name: "test",
symbol: "XOR2",
- isUtility: true,
precision: 1,
- substrateType: .soraAsset,
+ icon: nil,
+ tokenProperties: TokenProperties(
+ priceId: nil,
+ currencyId: nil,
+ color: nil,
+ type: .soraAsset,
+ isNative: false,
+ staking: .relayChain
+ ),
+ existentialDeposit: nil,
+ isUtility: true,
+ purchaseProviders: nil,
ethereumType: nil,
- tokenProperties: TokenProperties(stacking: "relaychain"),
- price: nil,
- priceId: nil,
+ priceProvider: nil,
coingeckoPriceId: nil,
- priceProvider: nil
+ priceData: []
)
}
}
diff --git a/Tests/SSFIndexersTests/History/BaseHistoryServiceTestCase.swift b/Tests/SSFIndexersTests/History/BaseHistoryServiceTestCase.swift
index d2398d19..6150edf0 100644
--- a/Tests/SSFIndexersTests/History/BaseHistoryServiceTestCase.swift
+++ b/Tests/SSFIndexersTests/History/BaseHistoryServiceTestCase.swift
@@ -36,13 +36,13 @@ class BaseHistoryServiceTestCase: XCTestCase {
substrateType: nil,
ethereumType: ethereumType,
tokenProperties:
- TokenProperties(
- priceId: nil,
- currencyId: nil,
- color: nil,
- type: .normal,
- isNative: true
- ),
+ TokenProperties(
+ priceId: nil,
+ currencyId: nil,
+ color: nil,
+ type: .normal,
+ isNative: true
+ ),
price: nil,
priceId: nil,
coingeckoPriceId: nil,
@@ -56,7 +56,12 @@ class BaseHistoryServiceTestCase: XCTestCase {
parentId: "2",
paraId: "test",
name: "test",
- tokens: ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: [asset]),
+ tokens: ChainRemoteTokens(
+ type: .config,
+ whitelist: nil,
+ utilityId: nil,
+ tokens: [asset]
+ ),
xcm: nil,
nodes: [],
types: nil,
diff --git a/Tests/SSFTransferServiceTests/SubstrateCallFactoryTests.swift b/Tests/SSFTransferServiceTests/SubstrateCallFactoryTests.swift
index df986c82..fcfc869b 100644
--- a/Tests/SSFTransferServiceTests/SubstrateCallFactoryTests.swift
+++ b/Tests/SSFTransferServiceTests/SubstrateCallFactoryTests.swift
@@ -375,7 +375,8 @@ final class SubstrateCallFactoryTests: XCTestCase {
private func generateAccountId(for chain: ChainModel) -> AccountId {
let chainFormate: SFChainFormat = chain
- .isEthereumBased ? .sfEthereum : .sfSubstrate(UInt16(chain.properties.addressPrefix) ?? 69)
+ .isEthereumBased ? .sfEthereum :
+ .sfSubstrate(UInt16(chain.properties.addressPrefix) ?? 69)
let accountId = AddressFactory.randomAccountId(for: chainFormate)
return accountId
}
diff --git a/Tests/SSFXCMTests/BridgeProxyBurnCallTests.swift b/Tests/SSFXCMTests/BridgeProxyBurnCallTests.swift
index bb7fc6e5..51e60a8d 100644
--- a/Tests/SSFXCMTests/BridgeProxyBurnCallTests.swift
+++ b/Tests/SSFXCMTests/BridgeProxyBurnCallTests.swift
@@ -158,17 +158,27 @@ extension BridgeProxyBurnCallTests {
.appendingPathExtension("json")
static let chain = ChainModel(
- rank: 1,
disabled: false,
chainId: "b0a8d493285c2df73290dfb7e61f870f17b41801197a149ca93654499ea3dafe",
- paraId: "1",
name: "test",
- tokens: ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: []),
+ tokens: ChainRemoteTokens(
+ type: .config,
+ whitelist: nil,
+ utilityId: nil,
+ tokens: []
+ ),
xcm: nil,
nodes: Set([ChainNodeModel(url: TestData.url, name: "test", apikey: nil)]),
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "1")
+ properties: .init(
+ addressPrefix: "1",
+ rank: "1",
+ paraId: "1",
+ ethereumBased: true,
+ crowdloans: nil
+ ),
+ identityChain: nil
)
static let subNetworkId = BridgeTypesSubNetworkId(from: chain)
diff --git a/Tests/SSFXCMTests/XcmCallFactoryTests.swift b/Tests/SSFXCMTests/XcmCallFactoryTests.swift
index e34027fd..15667570 100644
--- a/Tests/SSFXCMTests/XcmCallFactoryTests.swift
+++ b/Tests/SSFXCMTests/XcmCallFactoryTests.swift
@@ -250,13 +250,14 @@ extension XcmCallFactoryTests {
static let chain = XcmChain(
xcmVersion: .V1,
destWeightIsPrimitive: true,
- availableAssets: [.init(id: "0", symbol: "0")],
+ availableAssets: [.init(id: "0", symbol: "0", minAmount: nil)],
availableDestinations: [.init(
chainId: "1",
bridgeParachainId: "2",
assets: [.init(
id: "0",
- symbol: "0"
+ symbol: "0",
+ minAmount: nil
)]
)]
)
@@ -268,17 +269,25 @@ extension XcmCallFactoryTests {
)
static let model = ChainModel(
- rank: 0,
disabled: false,
chainId: "0",
- paraId: "0",
name: "model",
- tokens: ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: []),
+ tokens: ChainRemoteTokens(
+ type: .config,
+ whitelist: nil,
+ utilityId: nil,
+ tokens: []
+ ),
xcm: chain,
nodes: Set([node]),
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "0")
+ properties: .init(
+ addressPrefix: "0",
+ rank: "0",
+ paraId: "0"
+ ),
+ identityChain: nil
)
}
}
diff --git a/Tests/SSFXCMTests/XcmChainsConfigFetcherTests.swift b/Tests/SSFXCMTests/XcmChainsConfigFetcherTests.swift
index 2f5756cd..79dc86c6 100644
--- a/Tests/SSFXCMTests/XcmChainsConfigFetcherTests.swift
+++ b/Tests/SSFXCMTests/XcmChainsConfigFetcherTests.swift
@@ -173,10 +173,8 @@ final class XcmChainsConfigFetcherTests: XCTestCase {
extension XcmChainsConfigFetcherTests {
enum TestData {
static let firstChain = ChainModel(
- rank: 0,
disabled: false,
chainId: "0",
- paraId: "1001",
name: "test1",
tokens: ChainRemoteTokens(
type: .config,
@@ -189,14 +187,16 @@ extension XcmChainsConfigFetcherTests {
destWeightIsPrimitive: true,
availableAssets: [.init(
id: "0",
- symbol: "0"
+ symbol: "0",
+ minAmount: nil
)],
availableDestinations: [.init(
chainId: "0",
bridgeParachainId: "2",
assets: [.init(
id: "1",
- symbol: "1"
+ symbol: "1",
+ minAmount: nil
)]
)]
),
@@ -207,14 +207,13 @@ extension XcmChainsConfigFetcherTests {
)]),
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "0")
+ properties: ChainProperties(addressPrefix: "0"),
+ identityChain: nil
)
static let secondChain = ChainModel(
- rank: 1,
disabled: false,
chainId: "1",
- paraId: "1002",
name: "test2",
tokens: ChainRemoteTokens(
type: .config,
@@ -227,12 +226,17 @@ extension XcmChainsConfigFetcherTests {
destWeightIsPrimitive: true,
availableAssets: [.init(
id: "1",
- symbol: "1"
+ symbol: "1",
+ minAmount: nil
)],
availableDestinations: [.init(
chainId: "1",
bridgeParachainId: "2",
- assets: [.init(id: "0", symbol: "0")]
+ assets: [.init(
+ id: "0",
+ symbol: "0",
+ minAmount: nil
+ )]
)]
),
nodes: Set([ChainNodeModel(
@@ -242,14 +246,16 @@ extension XcmChainsConfigFetcherTests {
)]),
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "1")
+ properties: ChainProperties(
+ addressPrefix: "1",
+ paraId: "1002"
+ ),
+ identityChain: nil
)
static let errorChain = ChainModel(
- rank: 2,
disabled: false,
chainId: "2",
- paraId: "1",
name: "test3",
tokens: ChainRemoteTokens(
type: .config,
@@ -265,7 +271,11 @@ extension XcmChainsConfigFetcherTests {
)]),
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "2")
+ properties: ChainProperties(
+ addressPrefix: "2",
+ paraId: "1"
+ ),
+ identityChain: nil
)
}
}
diff --git a/Tests/SSFXCMTests/XcmDependencyContainerTests.swift b/Tests/SSFXCMTests/XcmDependencyContainerTests.swift
index 6a82c17f..a1d92839 100644
--- a/Tests/SSFXCMTests/XcmDependencyContainerTests.swift
+++ b/Tests/SSFXCMTests/XcmDependencyContainerTests.swift
@@ -62,25 +62,30 @@ extension XcmDependencyContainerTests {
)
static let chainModel = ChainModel(
- rank: 0,
disabled: false,
chainId: "0",
- paraId: "1001",
name: "test1",
- tokens: ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: []),
+ tokens: ChainRemoteTokens(
+ type: .config,
+ whitelist: nil,
+ utilityId: nil,
+ tokens: []
+ ),
xcm: XcmChain(
xcmVersion: .V3,
destWeightIsPrimitive: true,
availableAssets: [.init(
id: "0",
- symbol: "0"
+ symbol: "0",
+ minAmount: nil
)],
availableDestinations: [.init(
chainId: "0",
bridgeParachainId: "2",
assets: [.init(
id: "1",
- symbol: "1"
+ symbol: "1",
+ minAmount: nil
)]
)]
),
@@ -91,7 +96,10 @@ extension XcmDependencyContainerTests {
)]),
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "0")
+ properties: ChainProperties(
+ addressPrefix: "0"
+ ),
+ identityChain: nil
)
static let runtimeProvider = RuntimeProvider(
diff --git a/Tests/SSFXCMTests/XcmDestinationTests.swift b/Tests/SSFXCMTests/XcmDestinationTests.swift
index 4c56d2fa..99a63e81 100644
--- a/Tests/SSFXCMTests/XcmDestinationTests.swift
+++ b/Tests/SSFXCMTests/XcmDestinationTests.swift
@@ -34,26 +34,40 @@ extension XcmDestinationTests {
.appendingPathExtension("json")
static let chain = ChainModel(
- rank: 1,
disabled: false,
chainId: "1",
- paraId: "1001",
name: "test",
- tokens: ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: []),
+ tokens: ChainRemoteTokens(
+ type: .config,
+ whitelist: nil,
+ utilityId: nil,
+ tokens: []
+ ),
xcm: nil,
- nodes: Set([ChainNodeModel(url: TestData.url, name: "test", apikey: nil)]),
+ nodes: Set([ChainNodeModel(
+ url: TestData.url,
+ name: "test",
+ apikey: nil
+ )]),
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "1")
+ properties: ChainProperties(
+ addressPrefix: "0",
+ rank: "1"
+ ),
+ identityChain: nil
)
static let errorChain = ChainModel(
- rank: 1,
disabled: false,
chainId: "1",
- paraId: "1",
name: "test",
- tokens: ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: []),
+ tokens: ChainRemoteTokens(
+ type: .config,
+ whitelist: nil,
+ utilityId: nil,
+ tokens: []
+ ),
xcm: nil,
nodes: Set([ChainNodeModel(
url: TestData.url,
@@ -62,7 +76,11 @@ extension XcmDestinationTests {
)]),
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "1")
+ properties: ChainProperties(
+ addressPrefix: "1",
+ rank: "1"
+ ),
+ identityChain: nil
)
}
}
diff --git a/Tests/SSFXCMTests/XcmExtrinsicBuilderTests.swift b/Tests/SSFXCMTests/XcmExtrinsicBuilderTests.swift
index f1baabd6..b10402e0 100644
--- a/Tests/SSFXCMTests/XcmExtrinsicBuilderTests.swift
+++ b/Tests/SSFXCMTests/XcmExtrinsicBuilderTests.swift
@@ -197,25 +197,30 @@ final class XcmExtrinsicBuilderTests: XCTestCase {
private extension XcmExtrinsicBuilderTests {
private enum TestData {
static let chainModel = ChainModel(
- rank: 0,
disabled: false,
chainId: "0",
- paraId: "0",
name: "model",
- tokens: ChainRemoteTokens(type: .config, whitelist: nil, utilityId: nil, tokens: []),
+ tokens: ChainRemoteTokens(
+ type: .config,
+ whitelist: nil,
+ utilityId: nil,
+ tokens: []
+ ),
xcm: XcmChain(
xcmVersion: .V1,
destWeightIsPrimitive: true,
availableAssets: [.init(
id: "0",
- symbol: "0"
+ symbol: "0",
+ minAmount: nil
)],
availableDestinations: [.init(
chainId: "1",
bridgeParachainId: "2",
assets: [.init(
id: "0",
- symbol: "0"
+ symbol: "0",
+ minAmount: nil
)]
)]
),
@@ -224,10 +229,14 @@ private extension XcmExtrinsicBuilderTests {
name: "node",
apikey: nil
)]),
-
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "0")
+ properties: ChainProperties(
+ addressPrefix: "0",
+ rank: "0",
+ paraId: "0"
+ ),
+ identityChain: nil
)
static let reserveTransferCall = ReserveTransferAssetsCall(
diff --git a/Tests/SSFXCMTests/XcmExtrinsicServiceTests.swift b/Tests/SSFXCMTests/XcmExtrinsicServiceTests.swift
index b1fbb017..6c6d6858 100644
--- a/Tests/SSFXCMTests/XcmExtrinsicServiceTests.swift
+++ b/Tests/SSFXCMTests/XcmExtrinsicServiceTests.swift
@@ -153,10 +153,8 @@ private extension XcmExtrinsicServiceTests {
)
static let fromChain = ChainModel(
- rank: 0,
disabled: false,
chainId: "0",
- paraId: "1001",
name: "test1",
tokens: ChainRemoteTokens(
type: .config,
@@ -168,38 +166,34 @@ private extension XcmExtrinsicServiceTests {
id: "0",
name: "0",
symbol: "0",
- isUtility: true,
precision: 0,
- substrateType: .soraAsset,
- ethereumType: nil,
- tokenProperties:
- TokenProperties(
- priceId: "0",
- currencyId: "0",
- color: "0",
- type: .soraAsset,
- isNative: true
- ),
- price: nil,
- priceId: nil,
- coingeckoPriceId: nil,
- priceProvider: nil
+ tokenProperties: TokenProperties(
+ priceId: "0",
+ currencyId: "0",
+ color: "0",
+ type: .soraAsset,
+ isNative: true
+ ),
+ isUtility: true
),
]
- )),
+ )
+ ),
xcm: XcmChain(
xcmVersion: .V3,
destWeightIsPrimitive: true,
availableAssets: [.init(
id: "0",
- symbol: "0"
+ symbol: "0",
+ minAmount: nil
)],
availableDestinations: [.init(
chainId: "0",
bridgeParachainId: "2",
assets: [.init(
id: "1",
- symbol: "1"
+ symbol: "1",
+ minAmount: nil
)]
)]
),
@@ -210,7 +204,12 @@ private extension XcmExtrinsicServiceTests {
)]),
icon: nil,
iosMinAppVersion: nil,
- properties: ChainProperties(addressPrefix: "0")
+ properties: ChainProperties(
+ addressPrefix: "0",
+ rank: "0",
+ paraId: "1001"
+ ),
+ identityChain: nil
)
static let paths: [XcmCallPath] = [