// file: crates/ksp-config-lib/src/offchain_transport.rs // version: 2 //! Adapter from Config-owned Off-chain Transport documents to the provider-agnostic market-price runtime service. /// Effective standard Off-chain Transport configuration resolved from Config. pub struct ResolvedOffchainTransportConfig { effective: crate::ResolvedConfigJson, file_id: crate::ConfigFileId, profile_id: String, selection_source: crate::ConfigProfileSelectionSource, service: ksp_offchain_transport_lib::MarketPriceService, source_path: std::path::PathBuf, } impl crate::ResolvedOffchainTransportConfig { /// Returns the detailed environment-resolved effective Config view with secret-safe diagnostics. #[must_use] pub const fn effective(&self) -> &crate::ResolvedConfigJson { return &self.effective; } /// Returns the logical Config document identifier used by this runtime configuration. #[must_use] pub const fn file_id(&self) -> &crate::ConfigFileId { return &self.file_id; } /// Returns the selected standard Off-chain Transport profile identifier. #[must_use] pub fn profile_id(&self) -> &str { return self.profile_id.as_str(); } /// Returns the source that selected the standard Off-chain Transport profile. #[must_use] pub const fn selection_source(&self) -> crate::ConfigProfileSelectionSource { return self.selection_source; } /// Returns the provider-agnostic market-price runtime service. #[must_use] pub const fn service(&self) -> &ksp_offchain_transport_lib::MarketPriceService { return &self.service; } /// Returns the physical source Config document path. #[must_use] pub fn source_path(&self) -> &std::path::Path { return self.source_path.as_path(); } } impl std::fmt::Debug for crate::ResolvedOffchainTransportConfig { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let registry = self.service.registry(); return formatter .debug_struct("ResolvedOffchainTransportConfig") .field("effective", &self.effective) .field("file_id", &self.file_id) .field("profile_id", &self.profile_id) .field("provider_registry", ®istry) .field("selection_source", &self.selection_source) .field("source_path", &self.source_path) .finish(); } } impl crate::ConfigDocumentEngine { /// Loads the standard Off-chain Transport document and maps one profile to the generic market-price runtime service. pub fn load_resolved_offchain_transport_config( &self, requested_profile: std::option::Option<&str>, environment: &crate::ConfigEnvironment, ) -> ksp_core_lib::Result { let file_id = crate::ConfigFileId::new(crate::FILE_ID_STD_OFFCHAIN_TRANSPORT); let file_id = match file_id { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let profile = self.load_resolved_profile(&file_id, requested_profile); let profile = match profile { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; return resolve_offchain_transport_profile(&profile, environment); } /// Maps an already resolved standard Off-chain Transport profile to the runtime service while preserving composite selection provenance. pub fn resolve_offchain_transport_config_profile( &self, profile: &crate::ResolvedConfigProfile, environment: &crate::ConfigEnvironment, ) -> ksp_core_lib::Result { if profile.file_id().as_str() != crate::FILE_ID_STD_OFFCHAIN_TRANSPORT { return std::result::Result::Err(effective_error(profile, "resolved Config profile does not reference the standard Off-chain Transport document")); } let descriptor = self.registry().descriptor(profile.file_id()); if let std::result::Result::Err(error) = descriptor { return std::result::Result::Err(error); } return resolve_offchain_transport_profile(profile, environment); } } #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct EffectiveOffchainTransportSource { format_version: u32, market_price: EffectiveMarketPriceSource, profile_id: String, } #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct EffectiveMarketPriceSource { birdeye: EffectiveBirdeyeSource, coinbase_exchange: EffectiveEnabledSource, coingecko: EffectiveCoinGeckoSource, coinmarketcap: EffectiveCoinMarketCapSource, coinpaprika: EffectiveEnabledSource, dexscreener: EffectiveDexScreenerSource, jupiter: EffectiveJupiterSource, kraken: EffectiveEnabledSource, } #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct EffectiveEnabledSource { enabled: bool, } #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct EffectiveBirdeyeSource { api_key: std::option::Option, enabled: bool, } #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct EffectiveCoinGeckoSource { access_mode: ksp_offchain_transport_lib::MarketPriceCoinGeckoAccessMode, api_key: std::option::Option, enabled: bool, } #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct EffectiveCoinMarketCapSource { access_mode: ksp_offchain_transport_lib::MarketPriceCoinMarketCapAccessMode, api_key: std::option::Option, enabled: bool, } #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct EffectiveDexScreenerSource { enabled: bool, sol_usd_pair_address: std::option::Option, } #[derive(serde::Deserialize)] #[serde(deny_unknown_fields)] struct EffectiveJupiterSource { access_mode: ksp_offchain_transport_lib::MarketPriceJupiterAccessMode, api_key: std::option::Option, enabled: bool, } fn resolve_offchain_transport_profile( profile: &crate::ResolvedConfigProfile, environment: &crate::ConfigEnvironment, ) -> ksp_core_lib::Result { ksp_logging_lib::trace!( target: crate::TRACING_TARGET, profile_id = profile.profile_id(), "mapping standard Off-chain Transport Config profile" ); let effective = profile.resolve_effective_environment_detailed(environment); let effective = match effective { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let source = serde_json::from_value::(effective.value().clone()); let source = match source { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err( effective_error(profile, "effective Off-chain Transport Config cannot be decoded into the runtime adapter contract").with_source(error), ); }, }; if source.format_version != 1 { return std::result::Result::Err(effective_error(profile, "effective Off-chain Transport format_version is unsupported")); } if source.profile_id != profile.profile_id() { return std::result::Result::Err(effective_error(profile, "effective Off-chain Transport profile_id does not match the selected profile")); } let setups = map_market_price_setups(source.market_price, &effective, profile); let setups = match setups { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let service = ksp_offchain_transport_lib::MarketPriceService::new(setups); let service = match service { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { return std::result::Result::Err(offchain_contract_error( profile, "effective market-price providers fail the Off-chain Transport runtime contract", &error, )); }, }; let provider_count = service.registry().entries().len(); ksp_logging_lib::debug!( target: crate::TRACING_TARGET, profile_id = profile.profile_id(), format_version = source.format_version, provider_count, "mapped standard Off-chain Transport Config to market-price service" ); return std::result::Result::Ok(crate::ResolvedOffchainTransportConfig { effective, file_id: profile.file_id().clone(), profile_id: profile.profile_id().to_owned(), selection_source: profile.selection_source(), service, source_path: profile.path().to_path_buf(), }); } fn map_market_price_setups( source: EffectiveMarketPriceSource, effective: &crate::ResolvedConfigJson, profile: &crate::ResolvedConfigProfile, ) -> ksp_core_lib::Result> { let birdeye = map_birdeye(source.birdeye, effective, profile); let birdeye = match birdeye { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let coinbase_exchange = ksp_offchain_transport_lib::MarketPriceCoinbaseExchangeSettings::new(source.coinbase_exchange.enabled); let coinbase_exchange = match coinbase_exchange { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(provider_contract_error(profile, "coinbase_exchange", &error)), }; let coingecko = map_coingecko(source.coingecko, effective, profile); let coingecko = match coingecko { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let coinmarketcap = map_coinmarketcap(source.coinmarketcap, effective, profile); let coinmarketcap = match coinmarketcap { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let coinpaprika = ksp_offchain_transport_lib::MarketPriceCoinPaprikaSettings::new(source.coinpaprika.enabled); let coinpaprika = match coinpaprika { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(provider_contract_error(profile, "coinpaprika", &error)), }; let dexscreener = map_dexscreener(source.dexscreener, effective, profile); let dexscreener = match dexscreener { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let jupiter = map_jupiter(source.jupiter, effective, profile); let jupiter = match jupiter { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; let kraken = ksp_offchain_transport_lib::MarketPriceKrakenSettings::new(source.kraken.enabled); let kraken = match kraken { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(provider_contract_error(profile, "kraken", &error)), }; return std::result::Result::Ok(std::vec![ ksp_offchain_transport_lib::MarketPriceProviderSetup::Birdeye(birdeye), ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinbaseExchange(coinbase_exchange), ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinGecko(coingecko), ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinMarketCap(coinmarketcap), ksp_offchain_transport_lib::MarketPriceProviderSetup::CoinPaprika(coinpaprika), ksp_offchain_transport_lib::MarketPriceProviderSetup::DexScreener(dexscreener), ksp_offchain_transport_lib::MarketPriceProviderSetup::Jupiter(jupiter), ksp_offchain_transport_lib::MarketPriceProviderSetup::Kraken(kraken), ]); } fn map_birdeye( source: EffectiveBirdeyeSource, effective: &crate::ResolvedConfigJson, profile: &crate::ResolvedConfigProfile, ) -> ksp_core_lib::Result { if source.api_key.is_some() && let std::result::Result::Err(error) = validate_secret_field_provenance(effective, "/market_price/birdeye/api_key", profile, "birdeye") { return std::result::Result::Err(error); } let settings = ksp_offchain_transport_lib::MarketPriceBirdeyeSettings::new(source.enabled, source.api_key); return match settings { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(error) => std::result::Result::Err(provider_contract_error(profile, "birdeye", &error)), }; } fn map_coingecko( source: EffectiveCoinGeckoSource, effective: &crate::ResolvedConfigJson, profile: &crate::ResolvedConfigProfile, ) -> ksp_core_lib::Result { if source.api_key.is_some() && let std::result::Result::Err(error) = validate_secret_field_provenance(effective, "/market_price/coingecko/api_key", profile, "coingecko") { return std::result::Result::Err(error); } let settings = match source.access_mode { ksp_offchain_transport_lib::MarketPriceCoinGeckoAccessMode::Demo => { ksp_offchain_transport_lib::MarketPriceCoinGeckoSettings::demo(source.enabled, source.api_key) }, ksp_offchain_transport_lib::MarketPriceCoinGeckoAccessMode::Keyless => { ksp_offchain_transport_lib::MarketPriceCoinGeckoSettings::keyless(source.enabled) }, _ => { return std::result::Result::Err( effective_error(profile, "Off-chain Transport CoinGecko access mode is not supported by this Config adapter") .with_context("provider", "coingecko") .with_context("field", "access_mode"), ); }, }; return match settings { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(error) => std::result::Result::Err(provider_contract_error(profile, "coingecko", &error)), }; } fn map_coinmarketcap( source: EffectiveCoinMarketCapSource, effective: &crate::ResolvedConfigJson, profile: &crate::ResolvedConfigProfile, ) -> ksp_core_lib::Result { if source.api_key.is_some() && let std::result::Result::Err(error) = validate_secret_field_provenance(effective, "/market_price/coinmarketcap/api_key", profile, "coinmarketcap") { return std::result::Result::Err(error); } let settings = match source.access_mode { ksp_offchain_transport_lib::MarketPriceCoinMarketCapAccessMode::Basic => { ksp_offchain_transport_lib::MarketPriceCoinMarketCapSettings::basic(source.enabled, source.api_key) }, ksp_offchain_transport_lib::MarketPriceCoinMarketCapAccessMode::Keyless => { ksp_offchain_transport_lib::MarketPriceCoinMarketCapSettings::keyless(source.enabled) }, _ => { return std::result::Result::Err( effective_error(profile, "Off-chain Transport CoinMarketCap access mode is not supported by this Config adapter") .with_context("provider", "coinmarketcap") .with_context("field", "access_mode"), ); }, }; return match settings { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(error) => std::result::Result::Err(provider_contract_error(profile, "coinmarketcap", &error)), }; } fn map_dexscreener( source: EffectiveDexScreenerSource, effective: &crate::ResolvedConfigJson, profile: &crate::ResolvedConfigProfile, ) -> ksp_core_lib::Result { if source.sol_usd_pair_address.is_some() && let std::result::Result::Err(error) = validate_public_field_provenance(effective, "/market_price/dexscreener/sol_usd_pair_address", profile, "dexscreener") { return std::result::Result::Err(error); } let settings = ksp_offchain_transport_lib::MarketPriceDexScreenerSettings::new(source.enabled, source.sol_usd_pair_address); return match settings { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(error) => std::result::Result::Err(provider_contract_error(profile, "dexscreener", &error)), }; } fn map_jupiter( source: EffectiveJupiterSource, effective: &crate::ResolvedConfigJson, profile: &crate::ResolvedConfigProfile, ) -> ksp_core_lib::Result { if source.api_key.is_some() && let std::result::Result::Err(error) = validate_secret_field_provenance(effective, "/market_price/jupiter/api_key", profile, "jupiter") { return std::result::Result::Err(error); } let settings = match source.access_mode { ksp_offchain_transport_lib::MarketPriceJupiterAccessMode::Free => { ksp_offchain_transport_lib::MarketPriceJupiterSettings::free(source.enabled, source.api_key) }, ksp_offchain_transport_lib::MarketPriceJupiterAccessMode::Keyless => ksp_offchain_transport_lib::MarketPriceJupiterSettings::keyless(source.enabled), _ => { return std::result::Result::Err( effective_error(profile, "Off-chain Transport Jupiter access mode is not supported by this Config adapter") .with_context("provider", "jupiter") .with_context("field", "access_mode"), ); }, }; return match settings { std::result::Result::Ok(value) => std::result::Result::Ok(value), std::result::Result::Err(error) => std::result::Result::Err(provider_contract_error(profile, "jupiter", &error)), }; } fn validate_secret_field_provenance( effective: &crate::ResolvedConfigJson, pointer: &str, profile: &crate::ResolvedConfigProfile, provider: &'static str, ) -> ksp_core_lib::Result<()> { let provenance = match effective.provenance_at(pointer) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( effective_error(profile, "Off-chain Transport secret provenance is unavailable") .with_context("provider", provider) .with_context("field", pointer), ); }, }; let mut has_secret_environment = false; for item in provenance { let variable_name = match item.variable_name() { std::option::Option::Some(value) => value, std::option::Option::None => continue, }; let sensitivity = crate::ConfigSensitivity::from_variable_name(variable_name); let sensitivity = match sensitivity { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if !sensitivity.is_secret() { return std::result::Result::Err( effective_error(profile, "Off-chain Transport credentials may reference only secret environment variables") .with_context("provider", provider) .with_context("field", pointer), ); } has_secret_environment = true; } if !has_secret_environment { return std::result::Result::Err( effective_error(profile, "Off-chain Transport credentials require secret environment provenance") .with_context("provider", provider) .with_context("field", pointer), ); } return std::result::Result::Ok(()); } fn validate_public_field_provenance( effective: &crate::ResolvedConfigJson, pointer: &str, profile: &crate::ResolvedConfigProfile, provider: &'static str, ) -> ksp_core_lib::Result<()> { let provenance = match effective.provenance_at(pointer) { std::option::Option::Some(value) => value, std::option::Option::None => { return std::result::Result::Err( effective_error(profile, "Off-chain Transport public provenance is unavailable") .with_context("provider", provider) .with_context("field", pointer), ); }, }; for item in provenance { let variable_name = match item.variable_name() { std::option::Option::Some(value) => value, std::option::Option::None => continue, }; let sensitivity = crate::ConfigSensitivity::from_variable_name(variable_name); let sensitivity = match sensitivity { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; if sensitivity != crate::ConfigSensitivity::Public { return std::result::Result::Err( effective_error(profile, "public Off-chain Transport fields may reference only public environment variables") .with_context("provider", provider) .with_context("field", pointer), ); } } return std::result::Result::Ok(()); } fn provider_contract_error(profile: &crate::ResolvedConfigProfile, provider: &'static str, error: &ksp_core_lib::Error) -> ksp_core_lib::Error { return offchain_contract_error(profile, "effective market-price provider settings fail the Off-chain Transport runtime contract", error) .with_context("provider", provider); } fn offchain_contract_error(profile: &crate::ResolvedConfigProfile, reason: &'static str, error: &ksp_core_lib::Error) -> ksp_core_lib::Error { return effective_error(profile, reason) .with_context("offchain_transport_error_domain", error.code().domain()) .with_context("offchain_transport_error_code", error.code().code()); } fn effective_error(profile: &crate::ResolvedConfigProfile, reason: &'static str) -> ksp_core_lib::Error { return ksp_core_lib::Error::new(crate::ERROR_CODE_EFFECTIVE_CONFIG_INVALID, "effective Config cannot be mapped to the requested runtime contract") .with_context("file_id", profile.file_id().as_str()) .with_context("profile_id", profile.profile_id()) .with_context("reason", reason); } #[cfg(test)] #[path = "../unit_tests/offchain_transport.rs"] mod tests;