diff --git a/build.gradle.kts b/build.gradle.kts index b8405a825..2809f2342 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.12.25" +version = "1.12.26" repositories { google() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/assets/AssetProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/assets/AssetProcessor.kt index 602b5dccc..af0756c09 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/assets/AssetProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/assets/AssetProcessor.kt @@ -7,6 +7,7 @@ import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol import exchange.dydx.abacus.utils.mutable import exchange.dydx.abacus.utils.safeSet +import indexer.models.configs.ConfigsAssetMetadata import indexer.models.configs.ConfigsMarketAsset internal interface AssetProcessorProtocol { @@ -97,3 +98,89 @@ internal class AssetProcessor( return received } } +internal interface AssetMetadataProcessorProtocol { + fun process( + assetId: String, + payload: ConfigsAssetMetadata, + ): Asset +} + +internal class AssetMetadataProcessor( + parser: ParserProtocol, + private val localizer: LocalizerProtocol? +) : BaseProcessor(parser), AssetMetadataProcessorProtocol { + private val assetConfigurationsResourcesKeyMap = mapOf( + "string" to mapOf( + "website" to "websiteLink", + "technical_doc" to "whitepaperLink", + "cmc" to "coinMarketCapsLink", + ), + ) + + private val assetConfigurationsKeyMap = mapOf( + "string" to mapOf( + "name" to "name", + ), + "strings" to mapOf( + "sector_tags" to "tags", + ), + ) + + override fun process( + assetId: String, + payload: ConfigsAssetMetadata, + ): Asset { + val imageUrl = "https://mainnet-metadata-service-logos.s3.ap-northeast-1.amazonaws.com/$assetId.png" + val primaryDescriptionKey = "__ASSETS.$assetId.PRIMARY" + val secondaryDescriptionKey = "__ASSETS.$assetId.SECONDARY" + val primaryDescription = localizer?.localize(primaryDescriptionKey) + val secondaryDescription = localizer?.localize(secondaryDescriptionKey) + + return Asset( + id = assetId, + name = payload.name, + tags = payload.sector_tags, + resources = AssetResources( + websiteLink = payload.urls["website"], + whitepaperLink = payload.urls["technical_doc"], + coinMarketCapsLink = payload.urls["cmc"], + imageUrl = imageUrl, + primaryDescriptionKey = primaryDescriptionKey, + secondaryDescriptionKey = secondaryDescriptionKey, + primaryDescription = primaryDescription, + secondaryDescription = secondaryDescription, + ), + ) + } + + override fun received( + existing: Map?, + payload: Map + ): Map? { + return existing + } + + internal fun receivedConfigurations( + assetId: String, + asset: Map?, + payload: Map, + ): Map { + val received = transform(asset, payload, assetConfigurationsKeyMap) + val urls = payload["urls"] as Map + val resources = transform( + parser.asNativeMap(asset?.get("resources")), + urls, + assetConfigurationsResourcesKeyMap, + ).mutable() + val imageUrl = "https://mainnet-metadata-service-logos.s3.ap-northeast-1.amazonaws.com/$assetId.png" + val primaryDescriptionKey = "__ASSETS.$assetId.PRIMARY" + val secondaryDescriptionKey = "__ASSETS.$assetId.SECONDARY" + resources.safeSet("imageUrl", imageUrl) + resources.safeSet("primaryDescriptionKey", primaryDescriptionKey) + resources.safeSet("secondaryDescriptionKey", secondaryDescriptionKey) + received["id"] = assetId + received["resources"] = resources + + return received + } +} diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/assets/AssetsProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/assets/AssetsProcessor.kt index 70c0f0349..4b7205793 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/assets/AssetsProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/assets/AssetsProcessor.kt @@ -6,16 +6,36 @@ import exchange.dydx.abacus.processor.utils.MarketId import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol import exchange.dydx.abacus.utils.mutable +import indexer.models.configs.ConfigsAssetMetadata import indexer.models.configs.ConfigsMarketAsset internal class AssetsProcessor( parser: ParserProtocol, - localizer: LocalizerProtocol? + localizer: LocalizerProtocol?, + val metadataService: Boolean = false, ) : BaseProcessor(parser) { private val assetProcessor = AssetProcessor(parser = parser, localizer = localizer) + private val assetMetadataProcessor = AssetMetadataProcessor(parser = parser, localizer = localizer) override fun environmentChanged() { assetProcessor.environment = environment + assetMetadataProcessor.environment = environment + } + + internal fun processMetadataConfigurations( + existing: MutableMap, + payload: Map, + ): MutableMap { + for ((assetId, data) in payload) { + val asset = assetMetadataProcessor.process( + assetId = assetId, + payload = data, + ) + + existing[assetId] = asset + } + + return existing } internal fun processConfigurations( @@ -50,12 +70,20 @@ internal class AssetsProcessor( if (assetId != null) { val marketPayload = parser.asNativeMap(data) if (marketPayload != null) { - val receivedAsset = assetProcessor.receivedConfigurations( - assetId, - parser.asNativeMap(existing?.get(assetId)), - marketPayload, - deploymentUri, - ) + val receivedAsset = if (metadataService) { + assetMetadataProcessor.receivedConfigurations( + assetId, + parser.asNativeMap(existing?.get(assetId)), + marketPayload, + ) + } else { + assetProcessor.receivedConfigurations( + assetId, + parser.asNativeMap(existing?.get(assetId)), + marketPayload, + deploymentUri, + ) + } assets[assetId] = receivedAsset } } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt index f7d25b28b..7d6f18f02 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/configs/V4StateManagerConfigs.kt @@ -55,7 +55,8 @@ class V4StateManagerConfigs( "status":"/v1/status" }, "configs":{ - "markets":"/configs/markets.json" + "markets":"/configs/markets.json", + "assets": "https://66iv2m87ol.execute-api.ap-northeast-1.amazonaws.com/mainnet/metadata-service/v1/info" }, "launchIncentive":{ "graphql":"/query/ccar-perpetuals", diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/PerpTradingStateMachine.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/PerpTradingStateMachine.kt index 98e89761b..9703fed7d 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/PerpTradingStateMachine.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/PerpTradingStateMachine.kt @@ -13,8 +13,9 @@ class PerpTradingStateMachine( useParentSubaccount: Boolean, staticTyping: Boolean = false, trackingProtocol: TrackingProtocol?, + metadataService: Boolean = false, ) : - TradingStateMachine(environment, localizer, formatter, maxSubaccountNumber, useParentSubaccount, staticTyping, trackingProtocol) { + TradingStateMachine(environment, localizer, formatter, maxSubaccountNumber, useParentSubaccount, staticTyping, trackingProtocol, metadataService) { /* Placeholder for now. Eventually, the code specifically for Perpetual will be in this class */ diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+Markets.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+Markets.kt index 0828e4797..4d8954668 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+Markets.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine+Markets.kt @@ -8,6 +8,7 @@ import exchange.dydx.abacus.state.changes.Changes import exchange.dydx.abacus.state.changes.StateChanges import indexer.models.IndexerCompositeMarketObject import indexer.models.IndexerWsMarketUpdateResponse +import indexer.models.configs.ConfigsAssetMetadata import indexer.models.configs.ConfigsMarketAsset import kollections.iListOf import kollections.toIList @@ -211,6 +212,38 @@ internal fun TradingStateMachine.receivedBatchedMarketsChanges( } } +internal fun TradingStateMachine.processMarketsConfigurationsWithMetadataService( + payload: Map, + subaccountNumber: Int?, + deploymentUri: String, +): StateChanges { + internalState.assets = assetsProcessor.processMetadataConfigurations( + existing = internalState.assets, + payload = payload, + ) + + marketsCalculator.calculate(internalState.marketsSummary) + val subaccountNumbers = MarginCalculator.getChangedSubaccountNumbers( + parser = parser, + subaccounts = internalState.wallet.account.subaccounts, + subaccountNumber = subaccountNumber ?: 0, + tradeInput = internalState.input.trade, + ) + return if (subaccountNumber != null) { + StateChanges( + changes = iListOf(Changes.markets, Changes.assets, Changes.subaccount, Changes.input), + markets = null, + subaccountNumbers = subaccountNumbers, + ) + } else { + StateChanges( + changes = iListOf(Changes.markets, Changes.assets), + markets = null, + subaccountNumbers = null, + ) + } +} + internal fun TradingStateMachine.processMarketsConfigurations( payload: Map, subaccountNumber: Int?, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt index cd5d6af01..a797ccc28 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/model/TradingStateMachine.kt @@ -78,6 +78,7 @@ import exchange.dydx.abacus.utils.mutableMapOf import exchange.dydx.abacus.utils.safeSet import exchange.dydx.abacus.utils.typedSafeSet import exchange.dydx.abacus.validator.InputValidator +import indexer.models.configs.ConfigsAssetMetadata import indexer.models.configs.ConfigsMarketAsset import kollections.JsExport import kollections.iListOf @@ -100,6 +101,7 @@ open class TradingStateMachine( private val useParentSubaccount: Boolean, val staticTyping: Boolean = false, private val trackingProtocol: TrackingProtocol?, + val metadataService: Boolean = false, ) { internal var internalState: InternalState = InternalState() @@ -113,6 +115,7 @@ open class TradingStateMachine( val processor = AssetsProcessor( parser = parser, localizer = localizer, + metadataService = metadataService, ) processor.environment = environment processor @@ -314,17 +317,30 @@ open class TradingStateMachine( ): StateChanges { val json = parser.decodeJsonObject(payload) if (staticTyping) { - val parsedAssetPayload = parser.asTypedStringMap(json) - if (parsedAssetPayload == null) { - Logger.e { "Error parsing asset payload" } - return StateChanges.noChange - } + if (metadataService) { + val parsedAssetPayload = parser.asTypedStringMap(json) + if (parsedAssetPayload == null) { + Logger.e { "Error parsing asset payload" } + return StateChanges.noChange + } + return processMarketsConfigurationsWithMetadataService( + payload = parsedAssetPayload, + subaccountNumber = subaccountNumber, + deploymentUri = deploymentUri, + ) + } else { + val parsedAssetPayload = parser.asTypedStringMap(json) + if (parsedAssetPayload == null) { + Logger.e { "Error parsing asset payload" } + return StateChanges.noChange + } - return processMarketsConfigurations( - payload = parsedAssetPayload, - subaccountNumber = subaccountNumber, - deploymentUri = deploymentUri, - ) + return processMarketsConfigurations( + payload = parsedAssetPayload, + subaccountNumber = subaccountNumber, + deploymentUri = deploymentUri, + ) + } } else { return if (json != null) { receivedMarketsConfigurationsDeprecated(json, subaccountNumber, deploymentUri) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt index b1f60d2dd..332e04339 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt @@ -122,6 +122,7 @@ internal class StateManagerAdaptorV2( useParentSubaccount = appConfigs.accountConfigs.subaccountConfigs.useParentSubaccount, staticTyping = appConfigs.staticTyping, trackingProtocol = ioImplementations.tracking, + metadataService = appConfigs.metadataService, ) internal val jsonEncoder = JsonEncoder() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/Configs.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/Configs.kt index d7bc413a9..39d7473f1 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/Configs.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/Configs.kt @@ -222,6 +222,7 @@ data class AppConfigsV2( var enableLogger: Boolean = false, var triggerOrderToast: Boolean = false, var staticTyping: Boolean = false, + var metadataService: Boolean = false, ) { companion object { val forApp = AppConfigsV2( diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SystemSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SystemSupervisor.kt index d1f93e758..4487ccaf0 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SystemSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SystemSupervisor.kt @@ -90,16 +90,31 @@ internal class SystemSupervisor( } private fun retrieveMarketConfigs() { - val oldState = stateMachine.state - val url = helper.configs.configsUrl("markets") - if (url != null) { - helper.get(url, null, null) { _, response, httpCode, _ -> - if (helper.success(httpCode) && response != null) { - update( - // TODO, subaccountNumber required to refresh - stateMachine.configurations(response, null, helper.deploymentUri), - oldState, - ) + if (stateMachine.metadataService) { + val oldState = stateMachine.state + val url = helper.configs.configsUrl("assets") + if (url != null) { + helper.post(url, null, null) { _, response, httpCode, _ -> + if (helper.success(httpCode) && response != null) { + update( + stateMachine.configurations(response, null, helper.deploymentUri), + oldState, + ) + } + } + } + } else { + val oldState = stateMachine.state + val url = helper.configs.configsUrl("markets") + if (url != null) { + helper.get(url, null, null) { _, response, httpCode, _ -> + if (helper.success(httpCode) && response != null) { + update( + // TODO, subaccountNumber required to refresh + stateMachine.configurations(response, null, helper.deploymentUri), + oldState, + ) + } } } } diff --git a/src/commonMain/kotlin/indexer/models/configs/ConfigsMarketAssetResponse.kt b/src/commonMain/kotlin/indexer/models/configs/ConfigsMarketAssetResponse.kt index 1c480ea34..9586fa3c4 100644 --- a/src/commonMain/kotlin/indexer/models/configs/ConfigsMarketAssetResponse.kt +++ b/src/commonMain/kotlin/indexer/models/configs/ConfigsMarketAssetResponse.kt @@ -1,6 +1,7 @@ package indexer.models.configs import exchange.dydx.abacus.utils.IList +import exchange.dydx.abacus.utils.IMap import kotlinx.serialization.Serializable /** @@ -14,3 +15,16 @@ data class ConfigsMarketAsset( val coinMarketCapsLink: String? = null, val tags: IList? = null, ) + +/** + * @description Asset from MetadataService Info response + */ +@Suppress("ConstructorParameterNaming") +@Serializable +data class ConfigsAssetMetadata( + val name: String, + val logo: String, + val urls: IMap, + val sector_tags: IList? = null, +// val exchanges: IList +)