Files
2026-08-25 21:47:47 +02:00

10 KiB

Delta 0.2.11-pre.004 — CoinGecko, CoinMarketCap et CoinPaprika

1. Base requise

Cette tranche s'applique exclusivement après :

v0.2.10
+ 0.2.11-pre.001
+ 0.2.11-pre.001-fix.001
+ 0.2.11-pre.002
+ 0.2.11-pre.002-fix.001
+ 0.2.11-pre.002-fix.002
+ 0.2.11-pre.002-fix.003
+ 0.2.11-pre.003
+ 0.2.11-pre.003-fix.001

La version Cargo attendue à l'entrée est :

0.2.11-pre.3.fix.1

La version Cargo de sortie est :

0.2.11-pre.4

2. Gate d'entrée et reprise volontaire de RUST-FMT-108

L'opérateur a validé 0.2.11-pre.003-fix.001 le 2026-08-25 avec :

cargo fmt --all                                      exécuté
python3 scripts/audit_rust_workspace_rules.py       1 violation RUST-FMT-108 uniquement
python3 scripts/audit_markdown_tables.py            clean, 117 tables / 107 files
cargo check --workspace                              PASS
cargo clippy --workspace --all-targets               PASS
cargo test -p ksp-offchain-transport-lib             PASS, 22 unit + 2 boundary + 2 public API

RUST-FMT-108 provenait uniquement du staging #[cfg(test)] des modules http_* imposé en pre.003-fix.001 tant qu'aucun consumer de production n'existait. L'opérateur a explicitement demandé de ne pas créer un fix artificiel supplémentaire puisque pre.004 est précisément la tranche qui active ces modules avec leurs premiers consumers runtime.

pre.004 retire donc le staging crate-root de :

http_admission
http_client
http_settings

La cause structurelle de RUST-FMT-108 disparaît sans exception ni allow.

3. Objectif

Implémenter les trois premiers providers SOL/USD de la famille market_price_* :

CoinGecko
CoinMarketCap
CoinPaprika

Le contrat reste :

SOL/USD uniquement
reqwest direct
aucun SDK provider
endpoints officiels fixes
DTOs wire privés
prix public exact, aucun canon f64
credentials absents des Debug/logs/errors
aucun Config dans Off-chain Transport
aucun registry global ni refresh multiple avant leurs tranches dédiées

4. Pont HTTP -> Market Price

market_price_adapter.rs matérialise uniquement les mécanismes réellement partagés par les adapters de prix :

admission non bloquante
capture des timestamps KSP
GET JSON borné via le client HTTP commun
feedback 429 / Retry-After vers le cooldown local
conversion RFC3339 ou Unix seconds vers MarketPriceTimestamp
erreurs de schéma provider sûres sans copie du payload distant
construction du runtime HTTP + admission depuis le descriptor provider

Les primitives http_* restent crate-wide et crate-private afin d'être réutilisables plus tard par d'autres familles telles que swap_quote_*, sans devenir un client HTTP générique public pour les consumers.

5. Credential market-price partagé

market_price_api_key.rs ajoute un holder privé MarketPriceApiKey :

longueur maximale 512 octets
valeur vide rejetée
whitespace externe rejeté
caractères de contrôle rejetés
Debug toujours redacted
aucun logging de la valeur

Ce holder ne lit aucun environnement. Config reste l'unique futur propriétaire de la résolution des secrets.

6. CoinGecko

Le provider public est :

MarketPriceCoinGeckoProvider
MarketPriceCoinGeckoSettings
MarketPriceCoinGeckoAccessMode

Modes V1 :

Keyless
Demo

La configuration respecte les capacités réelles :

Keyless -> aucun credential accepté
Demo activé -> API key obligatoire
Demo désactivé -> credential optionnel pour permettre une config inactive

Le request contract est fixe :

GET https://api.coingecko.com/api/v3/simple/price
ids=solana
vs_currencies=usd
include_last_updated_at=true
header x-cg-demo-api-key uniquement en mode Demo

Le descriptor conserve :

Keyless -> auth none, rate limit dynamique scope IP
Demo -> API key requise, 100 requêtes / 60 s scope account, 10k credits / mois
semantics -> AggregatedMarket

La réponse exige solana.usd et solana.last_updated_at. Le prix est lu via RawValue puis MarketPriceDecimal; le timestamp provider Unix est conservé.

7. CoinMarketCap

Le provider public est :

MarketPriceCoinMarketCapProvider
MarketPriceCoinMarketCapSettings
MarketPriceCoinMarketCapAccessMode

Modes V1 :

Keyless
Basic

La configuration suit la même discipline : keyless refuse un credential et Basic activé exige une API key.

Le réaudit pre.004 retient la surface Simple Price V2 actuelle et n'introduit pas la surface V1 deprecated.

Requests fixes :

Keyless -> GET https://pro-api.coinmarketcap.com/public-api/v2/simple/price
Basic   -> GET https://pro-api.coinmarketcap.com/v2/simple/price
ids=5426
convert=USD
include_last_updated=true
header x-cmc_pro_api_key uniquement en mode Basic

Le descriptor conserve :

Keyless -> auth none, rate limit dynamique scope IP
Basic -> API key requise, 50 requêtes / 60 s scope account, 15k credits / mois
semantics -> AggregatedMarket

Le parser vérifie :

status.error_code = 0
un seul item SOL id 5426
symbol = SOL
une seule quote USD
price exact via RawValue
last_updated RFC3339 valide

La provenance normalisée conserve explicitement la génération v2.

8. CoinPaprika

Le provider public est :

MarketPriceCoinPaprikaProvider
MarketPriceCoinPaprikaSettings

Il est keyless uniquement : aucun faux champ credential n'est introduit.

Request fixe :

GET https://api.coinpaprika.com/v1/tickers/sol-solana
quotes=USD

Le descriptor conserve :

auth none
10 requêtes / seconde scope IP
20k requests / mois
semantics -> AggregatedMarket

Le parser vérifie id = sol-solana, symbol = SOL, lit quotes.USD.price exactement via RawValue et conserve last_updated RFC3339.

9. Numeric safety

MarketPriceDecimal gagne un parser crate-private de serde_json::value::RawValue.

Il accepte :

nombre JSON exact
ou chaîne JSON contenant un décimal valide

Il ne passe jamais par f64. Les tests couvrent notamment une mantisse dont les derniers chiffres seraient perdus par une conversion flottante intermédiaire.

10. Availability et rate limiting dans cette tranche

Chaque provider possède son HttpAdmissionController local construit depuis son descriptor.

Le chemin runtime :

fetch_sol_usd
-> enabled check
-> try_admit non bloquant
-> request timestamp
-> GET provider
-> si 429 : Retry-After enregistré dans le cooldown
-> receive timestamp
-> parser typed provider
-> MarketPriceObservation

Un provider localement non admissible retourne http_admission_deferred avec un délai safe ; aucun sleep n'est introduit dans l'adapter.

Le registry global et la projection générique d'availability restent réservés à pre.007, et l'orchestration multiple à pre.008.

11. Tests ajoutés ou étendus

Les tests déterministes couvrent :

modes keyless/keyed et credentials requis/interdits
Debug settings sans secret
headers sensibles présents uniquement dans le bon mode
URLs et query fixes
capabilities descriptors et quotas
réponses SOL/USD valides
prix JSON exact sans f64
provider timestamps
identités provider incorrectes rejetées
schema/provider status incohérents rejetés
endpoint CMC V2 exact
absence de SDK provider
absence de Config et tracing direct
HTTP production actif mais non exporté publiquement

Aucun smoke réseau n'est ajouté dans cette tranche.

12. Fichiers ajoutés

crates/ksp-offchain-transport-lib/src/market_price_adapter.rs
crates/ksp-offchain-transport-lib/src/market_price_api_key.rs
crates/ksp-offchain-transport-lib/src/market_price_coingecko.rs
crates/ksp-offchain-transport-lib/src/market_price_coinmarketcap.rs
crates/ksp-offchain-transport-lib/src/market_price_coinpaprika.rs
crates/ksp-offchain-transport-lib/unit_tests/market_price_coingecko.rs
crates/ksp-offchain-transport-lib/unit_tests/market_price_coinmarketcap.rs
crates/ksp-offchain-transport-lib/unit_tests/market_price_coinpaprika.rs
deltas/0.2.11/pre.004.md

13. Fichiers modifiés

Cargo.toml
crates/ksp-offchain-transport-lib/Cargo.toml
crates/ksp-offchain-transport-lib/src/error.rs
crates/ksp-offchain-transport-lib/src/http_admission.rs
crates/ksp-offchain-transport-lib/src/http_client.rs
crates/ksp-offchain-transport-lib/src/http_settings.rs
crates/ksp-offchain-transport-lib/src/lib.rs
crates/ksp-offchain-transport-lib/src/market_price_decimal.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/http_admission.rs
crates/ksp-offchain-transport-lib/unit_tests/market_price_decimal.rs
docs/plans/018-V0_2_11_OFFCHAIN_PRICE_TRANSPORT_PLAN.md
docs/validation/014-V0_2_11_OFFCHAIN_PRICE_TRANSPORT.md

14. Fichiers supprimés

aucun

15. Fichiers volontairement inchangés

CHANGELOG.md
ROADMAP.md
README.md
.env.example
config/**
crates/ksp-config-lib/**
crates/ksp-onchain-transport-lib/**
prompts/**

ROADMAP.md et CHANGELOG.md restent hors de cette prerelease conformément à leur ownership de release.

16. Validations exécutées dans le sandbox

python3 scripts/audit_rust_workspace_rules.py
    General Rust rule audit: clean
    Rust export completeness audit: 0 candidate(s)
    KSP workspace Rust rule audit: clean

python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.11
    clean, 118 tables / 108 files

contrôle lignes Rust > 160 sur ksp-offchain-transport-lib
    PASS, 0 ligne

Le sandbox de génération ne possède pas Cargo/Rust. cargo fmt, cargo check, cargo clippy et les tests Cargo ne sont donc pas déclarés exécutés ici.

17. Gate opérateur demandé

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

Les smokes live restent hors de pre.004.

18. Scope non avancé

Kraken
Coinbase Exchange
Jupiter
DexScreener
Birdeye
registry provider global
availability service global
refresh single/multiple générique
Config std.offchain_transport
.env.example secrets provider
ksp-app-solprices-desk
fallback automatique
consensus de prix
SOL/EUR
prix SPL générique