403 lines
10 KiB
Markdown
403 lines
10 KiB
Markdown
<!-- file: deltas/0.2.11/pre.004.md -->
|
|
<!-- version: 1 -->
|
|
|
|
# Delta `0.2.11-pre.004` — CoinGecko, CoinMarketCap et CoinPaprika
|
|
|
|
## 1. Base requise
|
|
|
|
Cette tranche s'applique exclusivement après :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
0.2.11-pre.3.fix.1
|
|
```
|
|
|
|
La version Cargo de sortie est :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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_*` :
|
|
|
|
```text
|
|
CoinGecko
|
|
CoinMarketCap
|
|
CoinPaprika
|
|
```
|
|
|
|
Le contrat reste :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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` :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
MarketPriceCoinGeckoProvider
|
|
MarketPriceCoinGeckoSettings
|
|
MarketPriceCoinGeckoAccessMode
|
|
```
|
|
|
|
Modes V1 :
|
|
|
|
```text
|
|
Keyless
|
|
Demo
|
|
```
|
|
|
|
La configuration respecte les capacités réelles :
|
|
|
|
```text
|
|
Keyless -> aucun credential accepté
|
|
Demo activé -> API key obligatoire
|
|
Demo désactivé -> credential optionnel pour permettre une config inactive
|
|
```
|
|
|
|
Le request contract est fixe :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
MarketPriceCoinMarketCapProvider
|
|
MarketPriceCoinMarketCapSettings
|
|
MarketPriceCoinMarketCapAccessMode
|
|
```
|
|
|
|
Modes V1 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
MarketPriceCoinPaprikaProvider
|
|
MarketPriceCoinPaprikaSettings
|
|
```
|
|
|
|
Il est keyless uniquement : aucun faux champ credential n'est introduit.
|
|
|
|
Request fixe :
|
|
|
|
```text
|
|
GET https://api.coinpaprika.com/v1/tickers/sol-solana
|
|
quotes=USD
|
|
```
|
|
|
|
Le descriptor conserve :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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 :
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
aucun
|
|
```
|
|
|
|
## 15. Fichiers volontairement inchangés
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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é
|
|
|
|
```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
|
|
```
|
|
|
|
Les smokes live restent hors de `pre.004`.
|
|
|
|
## 18. Scope non avancé
|
|
|
|
```text
|
|
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
|
|
```
|