# Delta `0.2.11-pre.007` — Birdeye + registry + availability ## 1. Base requise Cette tranche s'applique exclusivement après `0.2.11-pre.006` accepté par l'opérateur. Version Cargo attendue à l'entrée : ```text 0.2.11-pre.6 ``` Version Cargo de sortie : ```text 0.2.11-pre.7 ``` Le gate d'entrée ciblé du `2026-08-25` est PASS : `cargo fmt --all`, audit Rust clean avec 0 candidate export, audit Markdown clean à 120 tables / 111 fichiers, `cargo check --workspace`, `cargo clippy --workspace --all-targets` et `cargo test -p ksp-offchain-transport-lib` avec 43 unitaires + 4 boundary + 5 public API. `cargo test --workspace` n'a pas été rejoué sur l'état exact `pre.006` avant le passage explicite à cette tranche. ## 2. Objet strict de la tranche `pre.007` ferme l'inventaire V1 des huit providers et matérialise le registry provider-neutral : ```text Birdeye Standard MarketPriceProviderRegistry MarketPriceProviderRegistryEntry availability générique consultable metadata de coût provider informatif ``` La tranche n'ajoute pas encore : ```text refresh(provider_id) refresh multiple/all service de dispatch provider Config Off-chain Transport smoke live fallback/consensus ``` ## 3. Birdeye Standard Ajout de : ```text market_price_birdeye.rs unit_tests/market_price_birdeye.rs MarketPriceBirdeyeSettings MarketPriceBirdeyeProvider ``` Le réaudit officiel courant confirme : ```text base URL https://public-api.birdeye.so endpoint GET /defi/price mint SOL So11111111111111111111111111111111111111112 chain header x-chain: solana auth header X-API-KEY Standard 0 USD, 30 000 compute units inclus limite compte 1 RPS Price Single 3 compute units par appel ``` Une clé Birdeye est obligatoire lorsque le provider est activé. Un provider désactivé peut être construit sans secret afin que Config et le futur service puissent représenter un état non actif sans inventer de credential. La clé reste dans `MarketPriceApiKey`, n'est jamais lue directement depuis l'environnement par Off-chain Transport et n'apparaît ni dans `Debug`, ni dans les logs, ni dans les erreurs. Le request builder utilise uniquement l'origine officielle fixe, le mint WSOL fixe et `x-chain = solana`. La réponse Price Single est normalisée depuis : ```text data.value data.updateUnixTime success ``` `data.value` passe par `serde_json::RawValue` puis `MarketPriceDecimal` sans round-trip `f64`. `updateUnixTime` devient un vrai `provider_timestamp` en secondes Unix converties en millisecondes. La sémantique retenue est : ```text MarketPriceSemantics::SolanaSpot ``` La provenance sûre est : ```text birdeye:solana:wsol:value ``` Une réponse `success = false`, un prix absent/null, un timestamp absent/pathologique ou un JSON incompatible sont rejetés sans copier le body distant dans les diagnostics. ## 4. Quota et coût provider informatifs Birdeye utilise des compute units. Pour ne pas les confondre avec des crédits abstraits ou des requêtes HTTP, `MarketPriceProviderQuotaUnit` gagne : ```text ComputeUnits ``` `MarketPriceProviderRequestCost` représente un coût unitaire documenté pour l'opération SOL/USD courante. Le descriptor Birdeye expose : ```text long_term_quota = 30 000 ComputeUnits / Month sol_usd_request_cost = 3 ComputeUnits ``` Ces valeurs sont informatives. KSP ne décrémente pas localement le quota et ne prétend pas connaître un solde authoritative : un autre processus ou une autre API du même compte peut consommer les mêmes compute units. `MarketPriceProviderDescriptor::with_sol_usd_request_cost` vérifie que le coût et le quota utilisent la même unité lorsqu'un quota est présent. ## 5. Registry provider-neutral Ajout de : ```text market_price_registry.rs unit_tests/market_price_registry.rs MarketPriceProviderRegistry MarketPriceProviderRegistryEntry ERROR_CODE_MARKET_PRICE_REGISTRY_INVALID ``` Une entrée contient seulement : ```text MarketPriceProviderDescriptor MarketPriceProviderState ``` Le registry : ```text est borné à 64 entrées trie les entrées par provider_id rejette les provider_id dupliqués permet lookup descriptor/state/entry par identifiant opaque expose une slice déterministe d'entrées ne contient aucun nom ou dispatch provider spécifique ``` Le constructeur reste public et provider-neutral, comme `MarketPriceProviderDescriptor::new` déjà existant. Cela ne donne aucun accès à un endpoint ou à un client HTTP. Le futur service `pre.008` possédera les transitions runtime et le dispatch des adapters ; la HID ne devra pas construire ou matcher des providers spécifiques. Un canari de complétude construit les huit adapters V1 en mode désactivé et vérifie l'inventaire ordonné : ```text birdeye coinbase_exchange coingecko coinmarketcap coinpaprika dexscreener jupiter kraken ``` ## 6. Availability générique `MarketPriceProviderAvailability` gagne deux helpers publics provider-neutral : ```text is_refresh_eligible() retry_at() ``` Ils permettent à un consumer d'interpréter `Ready`, `CoolingDown` ou `TemporarilyUnavailable` sans parser des erreurs ni connaître le fournisseur. Les catégories existantes restent distinctes : ```text AuthenticationUnavailable CoolingDown Disabled Misconfigured QuotaUnavailable Ready TemporarilyUnavailable ``` `pre.007` ne relie pas encore automatiquement les erreurs d'un refresh à ces transitions. Cette mutation runtime appartient au service de `pre.008`. ## 7. Façade publique La taxonomie reste capability-scoped : ```text MarketPriceBirdeye* MarketPriceProviderRegistry* MarketPriceProviderRequestCost ``` Aucun DTO wire Birdeye, `HttpRestClient`, limiter interne ou primitive `reqwest` n'est exporté. Le registry public reste générique ; aucun consumer n'a besoin d'un enum CoinGecko/Birdeye/Jupiter pour l'inspecter. ## 8. Tests déterministes ajoutés Les nouveaux canaries couvrent : ```text Birdeye API key obligatoire si enabled redaction du secret Birdeye endpoint fixe et headers attendus mint WSOL fixe 1 RPS par compte 30 000 compute units informatifs 3 compute units par Price Single prix exact via RawValue updateUnixTime -> provider_timestamp success false rejeté prix null rejeté timestamp absent rejeté request cost nul rejeté mismatch unité quota/coût rejeté registry des huit providers complet ordre déterministe du registry doublons provider_id rejetés lookup descriptor/state générique availability is_refresh_eligible/retry_at absence de noms provider dans le module registry ``` ## 9. Documentation réconciliée Mise à jour de : ```text docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md ``` Le plan enregistre le réaudit Birdeye courant et précise que quotas/cost metadata restent non-authoritative. ## 10. Fichiers ajoutés ```text crates/ksp-offchain-transport-lib/src/market_price_birdeye.rs crates/ksp-offchain-transport-lib/src/market_price_registry.rs crates/ksp-offchain-transport-lib/unit_tests/market_price_birdeye.rs crates/ksp-offchain-transport-lib/unit_tests/market_price_registry.rs deltas/0.2.11/pre.007.md ``` ## 11. Fichiers modifiés ```text Cargo.toml crates/ksp-offchain-transport-lib/src/error.rs crates/ksp-offchain-transport-lib/src/lib.rs crates/ksp-offchain-transport-lib/src/market_price_provider.rs crates/ksp-offchain-transport-lib/tests/dependency_boundary.rs crates/ksp-offchain-transport-lib/tests/public_api.rs crates/ksp-offchain-transport-lib/unit_tests/market_price_provider.rs docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md ``` ## 12. Validation attendue ```bash cargo fmt --all python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.11 cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-offchain-transport-lib cargo test --workspace ``` `pre.008` ne doit commencer qu'après un gate propre de `pre.007` ou un delta fix explicite.