45 KiB
Plan 0.2.11 — Off-chain price transport SOL/USD multi-provider
Statut courant : 0.2.11-pre.010-fix.001 corrige uniquement un canari de hardening qui dupliquait lexicalement le scanner global de ownership logging et faisait échouer cargo test --workspace. Le hardening fonctionnel de pre.010 reste inchangé ; l'autorité de détection des bypass logging demeure ksp-logging-lib/tests/ownership.rs. Le gate du fix doit être rejoué avant pre.011.
1. Base et autorité
Base stable autoritaire :
v0.2.10
État vérifié dans l'archive Gitea fournie :
workspace.package.version = 0.2.10
deltas/0.2.10/rel.001.md présent
prompts/016-V0_2_11_START_PROMPT.md présent
ksp-offchain-transport-lib absente
metadata .git absente de l'archive Gitea
Cette tranche ouvre :
workspace.package.version = 0.2.11-pre.1
commit attendu = v0.2.11-pre.001
aucun tag prerelease
Ordre d'autorité pendant 0.2.11 :
règles normatives KSP
archive stable v0.2.10 et code réellement livré
décisions opérateur consignées dans le présent plan
validation 014 et deltas immuables de 0.2.11
prompt 016 pour les contraintes non supersédées
sources provider officielles actuelles pour les faits externes
2. Supersessions explicites du prompt d'ouverture
Le brainstorming opérateur du 2026-08-25 remplace plusieurs hypothèses préparatoires du prompt 016.
minimum V1 SOL/USD uniquement
nombre de providers un ou plusieurs, sans limite artificielle à un provider initial
providers ciblés tous les candidats gratuits retenus par le gate pre.001
transport provider HTTP REST avec reqwest uniquement
SDK provider interdit dans 0.2.11
SOL/EUR hors scope de cette première release
fallback automatique hors scope
consensus/agrégation KSP hors scope
future application ksp-app-solprices-desk en 0.2.12
rôle de l'application HID pure, aucune connaissance d'un provider
DexScreener V1 un client SOL/USD sur une paire configurée et validée
Le retrait de SOL/EUR n'interdit pas son ajout ultérieur. Il évite de forcer dès V1 une conversion fiat externe ou de privilégier artificiellement les providers multi-fiat.
3. Mission opérationnelle
0.2.11 doit créer ksp-offchain-transport-lib comme propriétaire de la récupération de prix off-chain SOL/USD et des différences opérationnelles entre providers.
La surface commune doit permettre à un consumer de :
lister les providers de prix connus du service
observer leur description et leur sémantique générique
observer leur disponibilité runtime
observer leur capacité de refresh et le prochain instant admissible
rafraîchir un provider par identifiant opaque
rafraîchir plusieurs providers sans violer leurs limites respectives
obtenir une observation SOL/USD typée et sa provenance
obtenir une absence/erreur/cooldown sans connaissance du fournisseur
La crate possède les exceptions provider. Un consumer ne doit pas coder :
URL provider
header d'auth provider
ID CoinGecko ou CoinMarketCap
mint Jupiter/Birdeye
paire Kraken/Coinbase
pair address DexScreener
rate limit provider
forme JSON wire provider
mapping de status HTTP provider
3.1 Taxonomie interne durable de ksp-offchain-transport-lib
ksp-offchain-transport-lib est une crate de transport off-chain hétérogène. 0.2.11 n'en définit que la première famille fonctionnelle ; la crate elle-même ne doit pas être structurée comme si toutes ses responsabilités futures étaient des prix.
La convention retenue à partir de pre.002-fix.001 est :
constants.rs préoccupations partagées à toute la crate
error.rs erreurs partagées / codes crate-wide
http_* mécanique HTTP réutilisable entre familles lorsqu'elle sera matérialisée
market_price_* prix de marché / spot et observations normalisées
swap_quote_* réservé aux futures quotes montant/route, uniquement lorsqu'elles seront réellement implémentées
<future_capability>_* nouvelle famille uniquement lorsqu'un scope réel l'exige
Les fichiers génériques decimal.rs, observation.rs, provider.rs et settings.rs introduits initialement par pre.002 sont donc corrigés avant toute extension runtime : la fonctionnalité actuelle est préfixée market_price_*. Les futurs domaines ne doivent pas être ajoutés dans ces modules uniquement parce qu'ils utilisent eux aussi un provider HTTP.
La même frontière s'applique à la façade publique. À partir de pre.002-fix.003, tout symbole public spécifique à cette famille porte un nom explicitement MarketPrice* ou MARKET_PRICE_* : MarketPriceDecimal, MarketPriceObservation, MarketPriceProviderId, MarketPriceProviderDescriptor, MarketPriceProviderRateLimit, etc. Aucun alias public Price* n'est conservé pendant cette prerelease, car il figerait précisément l'ambiguïté que le correctif supprime. Une future famille swap_quote_* devra employer son propre préfixe public SwapQuote*; une abstraction sans préfixe de famille ne pourra être créée que lorsqu'un invariant réellement partagé aura été démontré.
La règle est responsabilité avant taille : un module commun n'est promu au niveau crate-wide que lorsqu'au moins deux familles réelles partagent le même invariant. Ainsi, le HTTP, le scheduling ou une identité provider pourront être factorisés plus tard si leur partage est effectivement démontré ; aucune abstraction générique n'est créée aujourd'hui uniquement pour anticiper Jupiter Quotes.
3.2 Lecture, écriture et verbes HTTP
La capacité market_price de 0.2.11 est strictement read-only. La crate n'est toutefois pas déclarée architecturalement read-only pour toutes ses versions futures.
L'ownership dépend de la sémantique métier, pas du verbe HTTP :
GET/POST qui récupère ou calcule une donnée off-chain peut appartenir à Off-chain Transport
quote de swap dépendante d'un montant et d'une route future famille swap_quote, pas market_price
construction distante d'un payload sans soumission à auditer selon le contrat futur
soumission/signature d'une transaction Solana reste hors d'Off-chain Transport
écriture distante ayant un effet métier externe nécessite un scope/contrat explicite avant ajout
Un futur endpoint Jupiter utilisant POST ne devient donc pas automatiquement une « écriture » KSP. Inversement, aucune opération mutante distante n'est autorisée implicitement sous prétexte qu'elle passe par reqwest.
4. Frontière avec la future ksp-app-solprices-desk
La release suivante doit créer :
ksp-app-solprices-desk
Son rôle est purement HID.
Elle dépend de la façade publique de ksp-offchain-transport-lib et ne connaît aucun provider individuellement. Son premier tableau pourra afficher génériquement :
nom d'affichage provider
prix SOL/USD
fraîcheur disponible
état runtime
prochain refresh possible
bouton refresh
Un bouton de refresh multiple/all appelle une opération générique du service. L'application ne fait aucun sleep, aucun token bucket, aucun retry provider et aucun dispatch par nom de provider.
La composition Rust de l'application peut utiliser Config pour obtenir les settings puis construire le service Off-chain Transport, mais elle ne matche pas les variantes provider pour exécuter une requête.
5. Baseline technique interne
L'inventaire de v0.2.10 confirme que les primitives génériques nécessaires existent déjà au workspace :
reqwest ^0.13
serde ^1.0
serde_json ^1.0
tokio ^1.53
http ^1.5
chrono ^0.4 si parsing timestamp réellement nécessaire
Décision de dépendances V1 :
aucun SDK CoinGecko
aucun SDK CoinMarketCap
aucun SDK CoinPaprika
aucun SDK Kraken
aucun SDK Coinbase
aucun SDK Jupiter
aucun SDK Birdeye
aucun SDK DexScreener
aucune bibliothèque decimal provider/externe ajoutée par facilité
Les adapters utilisent reqwest et des DTOs wire privés KSP. 0.2.11-pre.001 n'ajoute encore aucune crate ni dépendance.
6. Audit provider actuel du 2026-08-25
Les huit providers ci-dessous satisfont le critère fonctionnel retenu : accès gratuit actuel, HTTP REST simple et possibilité de produire SOL/USD sans SDK provider.
| Provider | Accès gratuit V1 | Auth V1 | Limite ou quota à modéliser | Sémantique SOL/USD | Verdict |
|---|---|---|---|---|---|
| CoinGecko | keyless ou Demo | aucune ou Demo key | keyless dynamique IP, Demo 100 par min et 10k mois | agrégateur marché CoinGecko | IN |
| CoinMarketCap | keyless ou Basic | aucune ou API key | keyless dynamique IP, Basic 50 par min et 15k mois | agrégateur marché CoinMarketCap | IN |
| CoinPaprika | Free | aucune | 10 par sec IP et 20k mois, refresh moyen 5 min | agrégateur marché CoinPaprika | IN |
| Kraken | public Spot REST | aucune | cadence sûre documentée à 1 par sec ou moins | dernier trade du marché Kraken SOL/USD | IN |
| Coinbase Exchange | public Exchange REST | aucune | 10 par sec IP, burst 15 | dernier trade du produit Coinbase SOL-USD | IN |
| Jupiter Price V3 | keyless ou Free | aucune ou API key | keyless 0.5 RPS, Free 1 RPS | heuristique de prix USD orientée Solana | IN |
| Birdeye | Standard | X-API-KEY obligatoire | 1 RPS compte, 30k CU, Price Single 3 CU | spot USD token Solana Birdeye | IN |
| DexScreener | API publique gratuite | aucune | 300 par min pour pairs/tokens | priceUsd de la paire DEX Solana configurée | IN |
Sources officielles principales auditées :
https://docs.coingecko.com/docs/keyless-public-api
https://docs.coingecko.com/reference/simple-price
https://www.coingecko.com/en/api/pricing
https://coinmarketcap.com/api/documentation/pro-api-reference/keyless-public-api
https://coinmarketcap.com/api/documentation/pro-api-reference/cryptocurrency
https://coinmarketcap.com/api/documentation/pro-api-reference/cryptocurrency/simple-price-v2
https://coinmarketcap.com/api/documentation/pro-api-reference/cryptocurrency/simple-price-v1
https://coinmarketcap.com/api/pricing
https://docs.coinpaprika.com/api-reference/rest-api/introduction
https://docs.coinpaprika.com/api-reference/tickers/get-ticker-for-a-specific-coin
https://docs.coinpaprika.com/api-plans
https://support.kraken.com/articles/206548367-what-are-the-api-rate-limits-
https://support.kraken.com/hc/articles/360000919986-public-endpoint-examples-you-can-try-them-directly-in-a-web-browser-
https://docs.kraken.com/api/docs/rest-api/get-ticker-information
https://docs.cdp.coinbase.com/exchange/rest-api/rate-limits
https://docs.cdp.coinbase.com/api-reference/exchange-api/rest-api/products/get-product-ticker
https://docs.cdp.coinbase.com/api-reference/exchange-api/rest-api/products/get-single-product
https://developers.jup.ag/docs/portal/setup
https://developers.jup.ag/docs/portal/plans
https://developers.jup.ag/docs/portal/migration
https://developers.jup.ag/docs/api-reference/price
https://developers.jup.ag/docs/price
https://docs.birdeye.so/docs/pricing
https://docs.birdeye.so/docs/rate-limiting
https://docs.birdeye.so/reference/get-defi-price
https://docs.birdeye.so/docs/compute-unit-cost
https://docs.dexscreener.com/api/reference
https://docs.dexscreener.com/
Les chiffres externes sont une photographie d'audit, pas une constante éternelle. Les adapters doivent rester capables de classer 429 et les informations serveur sans supposer que la documentation ne changera jamais.
Réaudit pre.004 : CoinMarketCap Simple Price est implémenté sur la surface V2 actuelle. La surface Simple Price V1 est documentée comme deprecated et n'est pas utilisée par KSP. Les deux modes V1 retenus restent distincts : endpoint public keyless V2 sans credential, ou endpoint Pro V2 Basic avec X-CMC_PRO_API_KEY.
Réaudit pre.006 : le Developer Platform Jupiter courant utilise https://api.jup.ag pour les accès keyless et API-key. Le keyless est documenté à 0,5 RPS et le plan Free à 1 RPS. Price V3 expose usdPrice comme prix USD unique et blockId comme information de récence ; createdAt décrit la création du token et ne doit pas être transformé en timestamp du prix. DexScreener documente l'endpoint direct /latest/dex/pairs/{chainId}/{pairId} à 300 requêtes par minute. KSP sélectionne uniquement chainId = solana et interdit dans V1 les endpoints de search ou de découverte de pools.
Réaudit pre.007 : Birdeye Standard reste gratuit à 30 000 compute units inclus et 1 RPS au niveau du compte. L'endpoint Price Single GET /defi/price consomme actuellement 3 compute units, exige X-API-KEY, accepte x-chain: solana et le mint canonique WSOL, et fournit value plus updateUnixTime. Le quota et le coût par requête sont des metadata informatives ; KSP ne décrémente jamais localement le quota comme une vérité authoritative.
6.1 Gratuité technique et conditions d'usage
Le verdict IN signifie ici qu'un accès HTTP gratuit permet techniquement de développer et live-tester le provider dans 0.2.11. Il ne constitue pas une affirmation de gratuité commerciale permanente.
L'audit du 2026-08-25 relève notamment :
CoinGecko keyless est présenté pour faible volume, expérimentation/prototypage et usages assimilés ; le plan Demo porte ses propres conditions et attribution
CoinMarketCap keyless est présenté pour évaluation/prototypage ; un plan authentifié reste la voie documentée pour un usage avec allowance dédiée
CoinPaprika Free est annoncé pour usage personnel ; l'usage commercial relève des offres payantes actuelles
Conséquence KSP :
les adapters gratuits restent implémentables en V1
aucune documentation stable KSP ne promet une gratuité commerciale durable
les conditions d'usage et plans gratuits sont réaudités au gate live final avant publication stable
un changement de plan/licence peut rendre un provider administrativement indisponible sans casser le contrat provider-neutral
7. Providers écartés de V1
Pyth Hermes
Pyth reste intéressant comme oracle mais la transition Pyth Core annoncée pour le 2026-08-26 rend son modèle d'accès immédiatement mouvant et ne garantit pas un accès gratuit durable après la période d'essai.
Décision :
Pyth = OUT de 0.2.11 V1
Un audit ultérieur pourra l'ajouter comme sémantique oracle distincte.
Binance
Aucune assimilation implicite de SOL/USDT à SOL/USD n'est admise.
Décision :
Binance = OUT tant qu'un produit SOL/USD direct et pertinent n'est pas retenu explicitement
8. Identité et sémantique de la paire V1
Le contrat public V1 ne devient pas un moteur générique de symboles.
La seule paire fonctionnelle exposée est :
SOL/USD
Les identifiants propriétaires restent privés aux adapters ou aux settings provider :
CoinGecko coin id solana
CoinMarketCap asset id 5426
CoinPaprika coin id sol-solana
Kraken pair SOLUSD
Coinbase product SOL-USD
Jupiter mint SOL canonique attendu par Price V3
Birdeye mint SOL canonique attendu par Price Single
DexScreener pair address configurée, base SOL vérifiée
Le résultat commun ne prétend pas que les prix sont équivalents. Le descriptor générique expose une classe de sémantique, par exemple :
AggregatedMarket
ExchangeLastTrade
SolanaHeuristic
SolanaSpot
DexPairUsd
Le nom final des variantes appartient à pre.002, mais la distinction sémantique est obligatoire.
9. Cas DexScreener V1
Décision opérateur : 0.2.11 implémente bien DexScreener, mais uniquement comme client SOL/USD.
Le client V1 :
utilise un pair address Solana configuré
appelle l'endpoint pair direct
vérifie chainId = solana
vérifie que la paire correspond au SOL attendu
lit uniquement priceUsd pour le contrat commun
conserve la pair address comme provenance provider sûre
ne recherche pas automatiquement la meilleure pool
ne sélectionne pas par liquidité
ne construit aucun consensus entre pools
Le choix d'une paire explicite évite d'introduire silencieusement une politique de sélection DEX. Une future version pourra remplacer cette stratégie sans modifier le contrat HID de ksp-app-solprices-desk.
10. Représentation numérique
Le gate rejette f64 comme représentation canonique publique du prix.
Décision matérialisée en pre.002 : MarketPriceDecimal est le type décimal KSP borné, sérialisable sans perte et construit depuis la représentation textuelle du provider.
Contrat V1 exact :
coefficient positif u128
scale bornée à 18 décimales
entrée textuelle bornée à 96 octets
normalisation des zéros terminaux
parsing décimal et notation scientifique bornés sans passage par f64
aucun NaN
aucun Inf
overflow rejeté
zéro/négatif rejetés pour un prix réussi
serialization canonique décimale en chaîne
Le wire d'un provider peut être JSON number ou string. L'adapter le convertit vers le type KSP sans utiliser as_f64() comme vérité canonique.
MarketPriceDecimal sérialise toujours sa valeur canonique sous forme de chaîne décimale non scientifique. Le zéro est invalide pour ce type puisqu'il représente exclusivement un prix réussi ; l'absence de prix reste donc hors de la valeur numérique elle-même.
Cette solution évite d'ajouter une crate decimal uniquement pour V1 et prépare le passage frontend sans perte IEEE-754.
11. Observation commune et fraîcheur
Une observation SOL/USD commune doit distinguer :
provider_id opaque
prix décimal KSP
sémantique de prix
instant de départ requête KSP
instant de réception KSP
instant provider optionnel lorsqu'il existe réellement
provenance provider sûre et bornée
pre.002 matérialise ce contrat avec MarketPriceObservation, MarketPriceTimestamp (millisecondes UTC depuis Unix epoch) et MarketPriceProvenance bornée à 256 octets sans caractères de contrôle. Le constructeur d'observation rejette un instant de réception antérieur au départ de requête.
Règles :
ne jamais inventer un timestamp provider
ne jamais interpréter un timestamp de création token comme timestamp de prix
ne jamais transformer block id, pair creation time ou autre champ en faux update time
absence de prix != prix zéro
stale != transport indisponible
12. Registry et état runtime
ksp-offchain-transport-lib possède un registry runtime des providers de prix activés/configurés.
Deux surfaces génériques doivent rester distinctes :
capabilities statiques auditées
état runtime courant
Le descriptor statique peut exposer :
provider id opaque
nom d'affichage
sémantique
mode d'auth générique
limitation générique
support SOL/USD
pre.002 fixe MarketPriceProviderId, MarketPriceProviderDescriptor, MarketPriceProviderAuthMode, MarketPriceProviderRateLimit, MarketPriceProviderLongTermQuota et leurs enums de scope/période/unité. pre.007 ajoute MarketPriceProviderRequestCost et l'unité ComputeUnits afin de représenter sans perte le modèle Birdeye. Les limites fixes exigent un budget et une fenêtre non nuls ; les limites dynamiques restent explicitement distinctes. Les quotas longs termes et coûts unitaires sont descriptifs et ne deviennent jamais un compteur local de quota restant.
L'état runtime peut représenter au minimum :
ready
cooling down avec prochain instant admissible
temporarily unavailable
authentication unavailable
quota unavailable
misconfigured
disabled
MarketPriceProviderAvailability et MarketPriceProviderState matérialisent cette projection générique dès pre.002. pre.007 ajoute MarketPriceProviderRegistry et MarketPriceProviderRegistryEntry : le registry est borné, trié de façon déterministe par provider_id, rejette les doublons et n'expose que descriptor + state. Son module ne contient aucun dispatch par nom de provider. pre.008 ajoute MarketPriceService, propriétaire du dispatch réel et des mutations d'availability, tout en conservant le registry comme projection générique détachable. MarketPriceProviderSetup contient uniquement la sélection provider-specific nécessaire à la composition initiale ; elle ne traverse pas les opérations runtime destinées à la HID.
MarketPriceProviderAvailability expose aussi génériquement l'éligibilité immédiate au refresh et un retry_at lorsqu'il est réellement connu. AuthenticationUnavailable, QuotaUnavailable, Misconfigured, Disabled, CoolingDown, TemporarilyUnavailable et Ready restent distincts sans parsing provider dans les consumers.
Les noms Rust de cette fondation sont désormais matérialisés ; aucun consumer ne doit parser des strings d'erreur pour connaître cet état.
13. Rate limiting et refresh multiple
Le rate limiting appartient entièrement à ksp-offchain-transport-lib.
Le modèle doit savoir représenter :
cadence fixe
burst si documenté
fenêtre par seconde ou minute
quota long terme informatif
limite dynamique/server-driven
scope IP, compte ou organisation lorsque pertinent
Les quotas mensuels provider ne sont pas décrémentés comme une vérité authoritative locale : un autre processus peut consommer le même compte. Ils sont exposés comme capacités documentées ; les réponses provider restent la vérité runtime.
Refresh individuel
Un refresh(provider_id) :
vérifie l'éligibilité locale
exécute si autorisé
met à jour observation et état
classe 429 et Retry-After
ne contourne jamais le cooldown
Refresh multiple/all
Un refresh multiple V1 :
valide la sélection complète avant le premier dispatch
évalue chaque provider indépendamment
exécute séquentiellement dans un ordre déterministe
ne bloque pas Coinbase parce que Jupiter est en cooldown
ne dort pas pour attendre les providers non éligibles
retourne un résultat générique par provider
expose le prochain instant admissible pour les providers différés
Le choix séquentiel de V1 privilégie la simplicité, la reproductibilité des effets et l'absence de scheduler implicite. Il n'interdit pas une évolution concurrente future, mais une telle évolution devra définir explicitement ordre observable, admission et interactions de rate limiting au lieu d'être supposée par la HID. La future UI peut donc désactiver/annoter un bouton à partir de l'état retourné sans connaître la règle provider.
14. HTTP commun et résilience
Les adapters REST réutilisent les patterns sûrs déjà éprouvés dans KSP sans dépendre de ksp-onchain-transport-lib.
Décisions V1 :
reqwest uniquement
HTTPS officiel fixe dans les adapters V1
redirect désactivé
Referer automatique désactivé
proxy système/implicite désactivé
retries implicites reqwest désactivés
connect timeout borné : défaut 5 s, hard max 30 s
request timeout borné : défaut 10 s, hard max 120 s
GET uniquement pour les prix V1
body réponse borné avant désérialisation : défaut 1 MiB, hard max 4 MiB
JSON syntaxiquement validé avant remise à l'adapter typed
429 classé explicitement
Retry-After delta-seconds honoré et borné à 1 h lorsqu'il est exploitable
408 et 5xx classés transitoires
401/403 classés auth/access
URL retirée des erreurs reqwest avant contexte KSP
aucun body remote brut dans KspError
Il n'existe pas d'URL provider arbitraire dans la Config V1. Cela évite SSRF, redirection de credential et confusion de provenance.
Les primitives http_* sont crate-private : elles ne créent pas un client HTTP générique public contournant les capacités métier. Les adapters market_price_*, puis de futures familles telles que swap_quote_*, les consomment derrière leur propre contrat.
Le limiter pre.003 est non bloquant. Une cadence fixe est matérialisée par un token bucket lissé ; lorsqu'aucun burst n'est documenté, la capacité locale initiale reste volontairement 1. Lorsqu'un burst est documenté, sa capacité est indépendante du nombre moyen de requêtes de la fenêtre et peut donc lui être supérieure. Une limite dynamique n'invente aucune cadence locale et apprend seulement des réponses provider, notamment 429. Un Retry-After serveur peut prolonger le cooldown mais ne peut pas dépasser une borne défensive d'une heure. pre.008 projette ces délais dans MarketPriceProviderAvailability et ne dort jamais : refresh, refresh_many et refresh_all retournent immédiatement un état non éligible lorsqu'un prochain instant admissible est encore dans le futur.
15. Config provider-capability-aware
Config reste l'unique propriétaire des documents, de l'environnement et des secrets. pre.009 matérialise les identifiants durables :
file_id document = cfg.std.offchain_transport
file_id schema = schema.std.offchain_transport
fichier = std.offchain_transport.json
schema = std.offchain_transport.schema.json
format_version = 1
Le document reste générique Off-chain Transport et place la capacité actuelle sous market_price. Cette structure évite de transformer le document durable en Config dédiée uniquement au prix lorsque d'autres familles telles que swap_quote apparaîtront.
Deux profils versionnés servent de canaries :
public_keyless = providers réellement utilisables sans secret ; Birdeye et DexScreener désactivés
all_free = modes gratuits à clé activés + paire DexScreener explicite
Les branches correspondent aux capacités réelles de chaque provider.
| Provider | Champs spécifiques V1 |
|---|---|
| CoinGecko | enabled, access_mode keyless ou demo, api_key conditionnelle |
| CoinMarketCap | enabled, access_mode keyless ou basic, api_key conditionnelle |
| CoinPaprika | enabled |
| Kraken | enabled |
| Coinbase Exchange | enabled |
| Jupiter | enabled, access_mode keyless ou free, api_key conditionnelle |
| Birdeye | enabled, api_key requise si activé |
| DexScreener | enabled, sol_usd_pair_address requise si activé |
Le schema conditionne les credentials au mode choisi et interdit un faux champ api_key sur les modes keyless. L'adapter effectif applique en plus la provenance : une API key provider doit provenir d'une variable classée Secret (KSP_SECRET_* ou namespace KSPB secret admis par Config), même si un littéral non vide est syntaxiquement valide JSON. Les erreurs et Debug ne recopient jamais le credential.
La paire DexScreener n'est pas un secret. Un littéral validé comme Pubkey Solana est admis ; si la valeur vient de l'environnement, sa provenance doit rester Public, avec le canari versionné KSP_PUBLIC_DEXSCREENER_SOL_USD_PAIR_ADDRESS. Pour respecter « requise seulement si activé », MarketPriceDexScreenerSettings accepte désormais l'absence de paire lorsque le provider est désactivé et la refuse toujours lorsqu'il est activé.
.env.example inventorie les variables nécessaires sans valeur réelle. Les applications desktop empaquetent aussi le nouveau document et son schema parce que prepare_packaged_runtime synchronise l'intégralité du registry Config.
Les limites provider ne deviennent pas des valeurs libres permettant à Config/UI d'augmenter la cadence. Une future policy pourra permettre un throttle plus strict si un besoin réel apparaît.
16. Ownership des erreurs et indisponibilités
Une panne d'un provider ne doit pas rendre tous les autres inexploitables.
Le service classe par provider :
configuration manquante/invalide
DNS/connect/timeout
TLS
HTTP 4xx
HTTP 429
HTTP 5xx
JSON invalide ou schema drift
asset/pair mismatch
prix absent
prix numérique invalide
staleness provider si mesurable
Un refresh multiple retourne donc des outcomes partiels. Le service peut conserver un provider dans l'inventaire avec un état indisponible, au lieu de forcer l'application à connaître pourquoi Birdeye ou un autre provider manque.
17. Threat model V1
| Risque | Contrôle prévu |
|---|---|
| fuite API key | Config propriétaire, header privé, redaction logs/debug/erreurs |
| SSRF ou exfiltration vers URL configurée | origines provider fixes en V1, aucun endpoint arbitraire |
| redirect credential | redirects HTTP désactivés |
| proxy environnement implicite | policy no-proxy cohérente avec les clients KSP sensibles |
| réponse JSON énorme | limite de taille avant désérialisation |
| prix NaN, Inf, négatif ou overflow | parser décimal KSP borné |
| faux asset par collision de symbole | IDs provider fixes et validations d'identité |
| mauvaise paire DexScreener | pair address explicite et identité SOL vérifiée |
| 429 répétés | limiter local, Retry-After, cooldown provider |
| provider down | état provider séparé, autres providers continuent |
| stale data | timestamps réels conservés, aucune fraîcheur inventée |
| schema drift | DTOs wire privés stricts et erreur provider classée |
| quota partagé hors processus | quota local non présenté comme authoritative |
| différences de prix | sémantique/provenance visible, aucun consensus KSP V1 |
| log d'une réponse sensible | aucun payload remote brut dans les erreurs/logs |
18. Tests et smokes attendus
Tests déterministes
Prévoir :
parsing décimal string/JSON number/scientifique
limites et overflow
mapping de chaque wire provider
asset/pair mismatch
prix null/absent
HTTP status classification
429 et Retry-After
rate limiter par policy
cooldown
états unavailable/auth/quota
refresh single
refresh all partiel
un provider en échec n'empêche pas les autres
redaction credential
Config branches provider
public API sans type wire provider
Smokes live opt-in
Une tranche technique dédiée en fin de release doit tester les adapters réellement retenus avec un accès gratuit courant.
Le smoke :
ne hardcode aucun secret
n'affiche aucun secret
interroge uniquement SOL/USD
vérifie prix positif et parseable
vérifie provider et identité attendue
respecte chaque cadence
ne compare pas à un prix exact figé
n'échoue pas tout le batch parce qu'un provider externe est indisponible
Les providers keyed gratuits peuvent nécessiter des credentials opérateur via Config. Les smokes keyless restent séparables pour ne pas obliger à posséder toutes les clés au même instant.
19. Hors périmètre 0.2.11
SOL/EUR
autres fiat
prix d'autres assets ou SPL tokens publics
Price Desk Tauri
modification Wallet Desk
provider SDK
WebSocket/SSE price streaming
OHLCV/candles
historique/backfill prix
order books
quotes/routing/swap
metadata token/IPFS/Arweave
consensus multi-provider
moyenne/médiane KSP
fallback automatique
choix automatique de pool DexScreener
cache durable
nouvelle abstraction réseau universelle
Pyth
Binance sans SOL/USD direct retenu
Le fait que Jupiter/Birdeye/DexScreener soient capables de couvrir d'autres tokens ne les met pas dans la surface publique V1.
20. Forecast souple des prereleases
Chaque tranche technique vise environ 15 à 20 minutes de travail effectif lorsque le sujet s'y prête. Cette durée est une cible de granularité et pas une durée maximale : un build lent, un live test, un diagnostic ou une difficulté réelle peut légitimement prolonger une tranche.
Le forecast reste volontairement souple. Une tranche peut être scindée, fusionnée ou réordonnée par delta si la réalité technique l'exige, à condition de préserver les responsabilités de clôture. Le nombre de prereleases prévu n'impose ni une session de conversation par tranche, ni une scission de session : plusieurs prereleases peuvent être réalisées dans une même session de travail.
Chaque entrée porte désormais un statut directement modifiable. Lorsqu'un fix d'une tranche est nécessaire, il peut être ajouté sous la prerelease concernée avec un titre ####, sans transformer le forecast en tableau ni casser sa lisibilité.
pre.001 — Audit, sizing et cadrage multi-provider
Statut : réalisé.
Audit externe des providers, comparaison des sémantiques, réduction du scope V1 à SOL/USD, architecture HID de la future application, threat model, numeric design, sizing et planification.
pre.001-fix.001 — Forecast souple éditable
Statut : réalisé.
Remplacement du forecast tabulaire par des sous-sections éditables, clarification de la cible de 15 à 20 minutes et de l'absence de relation obligatoire entre prerelease et session de conversation. Aucun changement du scope technique de 0.2.11.
pre.002 — Fondation de ksp-offchain-transport-lib
Statut : réalisé.
Création de la crate et de sa façade publique initiale : MarketPriceDecimal, paire SOL/USD, observation/provenance/timestamps, identifiants/descriptors provider, capacités auth/rate-limit/quota, settings communs minimaux et états d'availability provider-neutral. Aucun HTTP, limiter actif, credential provider ou adapter wire n'est avancé depuis pre.003+.
pre.002-fix.001 — Rustdoc, Logging et taxonomie Off-chain
Statut : réalisé.
Correction des deux warnings missing_docs des tests d'intégration, ajout de ksp-logging-lib avec src/constants.rs et TRACING_TARGET = "ksp-offchain-transport-lib", instrumentation KSP des validations/constructions comportementales, et renommage des modules actuels en market_price_* pour préparer les futures familles hétérogènes telles que swap_quote_* sans transformer la crate entière en module de prix.
pre.002-fix.002 — Conformité Clippy implicit_return
Statut : réalisé.
Correction du canari dependency_boundary pour respecter -D clippy::implicit-return, sans changement de contrat ni de comportement runtime. La version technique workspace devient 0.2.11-pre.2.fix.2.
pre.002-fix.003 — Nommage public MarketPrice*
Statut : réalisé.
Préfixage de toute la surface publique spécifique à la famille market_price en MarketPrice* / MARKET_PRICE_*, y compris observation, décimal, timestamps, provenance, provider settings/descriptors/limits/availability et codes d'erreur. Aucun alias Price* n'est conservé : le correctif évite de devoir renommer ces API lorsque d'autres familles telles que swap_quote_* seront ajoutées. La version technique workspace devient 0.2.11-pre.2.fix.3.
pre.003 — HTTP REST commun et rate limiting
Statut : réalisé avec correctif.
Client HTTP REST commun crate-private, classification d'erreurs HTTP/reqwest, bornes de body et timeouts, redaction, désactivation explicite des redirects/proxy/retries implicites, JSON syntaxiquement validé, limiter token-bucket générique non bloquant et cooldown provider-driven borné. Aucun adapter provider n'est avancé.
pre.003-fix.001 — Façade crate-root, staging et burst documenté
Statut : réalisé ; gate Cargo/Clippy/tests PASS, RUST-FMT-108 structurel repris par pre.004.
Correction de l'ordre alphabétique des constantes d'erreur, utilisation systématique de la façade crate-root pour les items http_* partagés (impl crate::... et signatures crate-wide), suppression des #[allow(dead_code)] préparatoires au profit de #[cfg(test)] conformément à RUST-API-008, et correction du contrat de burst : un burst documenté peut dépasser la cadence moyenne de la fenêtre, comme le cas Coinbase 10 req/s, burst 15. La version technique workspace devient 0.2.11-pre.3.fix.1.
pre.004 — CoinGecko, CoinMarketCap et CoinPaprika
Statut : réalisé.
Activation en production du socle http_* et implémentation des trois adapters d'agrégateurs avec DTOs wire privés, parsing décimal exact via RawValue, timestamps provider lorsque fournis, endpoints officiels fixes, auth correspondant aux capacités réelles et limites provider décrites dans leurs descriptors. CoinGecko supporte keyless ou Demo key, CoinMarketCap keyless ou Basic key sur Simple Price V2, CoinPaprika reste keyless. Aucun registry global, Config ou smoke live n'est avancé.
pre.004-fix.001 — Ordre des constantes, Clippy et fixture CoinMarketCap V2
Statut : réalisé, gate opérateur complet PASS.
Correctif strict de pre.004 : tri alphabétique complet des constantes d'erreur, suppression des deux warnings clippy::collapsible_if dans les builders CoinGecko/CoinMarketCap, et correction de la fixture CoinMarketCap V2 dont le JSON concaténé omettait le guillemet ouvrant de last_updated. Aucun contrat provider, endpoint, parsing runtime, rate limit ou scope de release n'est modifié. La version technique workspace devient 0.2.11-pre.4.fix.1.
pre.005 — Kraken et Coinbase Exchange
Statut : réalisé, gate opérateur ciblé PASS ; workspace complet non rejoué.
Implémentation des deux adapters exchange keyless avec DTOs wire privés et endpoints HTTPS fixes. Kraken utilise le ticker public SOLUSD, retient le dernier trade c[0], applique une cadence locale conservatrice de 1 requête par seconde et ne fabrique aucun timestamp provider absent du ticker. Coinbase Exchange utilise /products/SOL-USD/ticker, conserve price et time, et modélise le token bucket public documenté à 10 requêtes par seconde par IP avec burst 15. Les deux adapters exposent MarketPriceSemantics::ExchangeLastTrade; aucun registry global, Config, SDK provider ou smoke live n'est avancé.
pre.006 — Jupiter et DexScreener
Statut : réalisé, gate opérateur ciblé PASS ; workspace complet non rejoué.
Implémentation des adapters Jupiter Price V3 et DexScreener avec DTOs wire privés et endpoints HTTPS officiels fixes. Jupiter distingue keyless à 0,5 requête par seconde et Free key à 1 requête par seconde sur le même api.jup.ag, utilise uniquement le mint WSOL et conserve blockId comme provenance de récence sans le convertir en timestamp. DexScreener exige un pair address Solana explicite lorsqu'il est activé, appelle uniquement /latest/dex/pairs/solana/{pairId}, vérifie chainId = solana, le pair address retourné et le mint WSOL comme base avant de lire priceUsd. Aucune découverte de pool, aucun tri de liquidité, aucun consensus et aucun registry global ne sont avancés.
pre.007 — Birdeye, registry et availability
Statut : réalisé, gate opérateur complet PASS.
Implémentation de Birdeye Standard avec X-API-KEY, endpoint Price Single Solana/WSOL, prix exact et timestamp provider réel. Le descriptor modélise 1 RPS par compte, 30 000 compute units de quota informatif et 3 compute units par refresh SOL/USD. MarketPriceProviderRegistry finalise l'inventaire runtime provider-neutral des huit adapters sous forme d'entrées descriptor + state, avec ordre déterministe et doublons rejetés. Les transitions runtime et le dispatch restent réservés à pre.008. Aucun dispatch de refresh, Config ou smoke live n'est avancé.
pre.008 — Refresh individuel et multiple
Statut : réalisé ; pre.008-fix.001 corrige le gate opérateur Clippy/boundary sans changement fonctionnel.
MarketPriceService possède les huit adapters configurés, le registry runtime et le dispatch provider-specific interne. La construction accepte MarketPriceProviderSetup pour la future couche de composition, puis l'usage devient intégralement provider-agnostic via registry(), refresh(provider_id), refresh_many(...) et refresh_all(). Les échecs provider sont classés en availability générique ; un résultat en erreur n'interrompt pas les autres providers d'un batch. Les états non éligibles sont projetés sans requête réseau, les cooldowns expirés redeviennent tentables, et aucune opération ne dort pour attendre un rate limit. Aucun fallback, consensus, agrégation, Config ou scheduling périodique n'est avancé.
pre.008-fix.001 conserve ce contrat. Il collapse uniquement un if signalé par Clippy et rend le canari de boundary précis : les rustdocs peuvent mentionner explicitement l'absence de consensus, tandis que le test interdit les fonctions exécutables aggregate/consensus et le chemin fallback_provider.
pre.009 — Config Off-chain Transport
Statut : réalisé ; gate opérateur complet PASS.
Ajout de cfg.std.offchain_transport / schema.std.offchain_transport, du document standard V1, de son schema strict, d'un exemple et de la fixture Config. Le domaine market_price configure les huit providers sans rendre leurs cadences modifiables. public_keyless fonctionne sans secret ; all_free référence quatre credentials KSP_SECRET_* et une paire DexScreener KSP_PUBLIC_*. ResolvedOffchainTransportConfig résout l'environnement, contrôle la provenance des credentials/public fields et construit MarketPriceService via MarketPriceProviderSetup, sans dépendance inverse. Les deux applications desktop embarquent les nouvelles ressources Config. La tranche réconcilie aussi DexScreener pour qu'une paire soit requise uniquement quand le provider est activé.
pre.010 — Hardening et complétude technique
Statut : réalisé ; pre.010-fix.001 corrige un faux positif du canari local de hardening, gate du fix à rejouer.
Durcissement de la façade publique avec #[non_exhaustive] sur les enums évolutives, absorption future-safe de ces enums par l'adapter Config, canaries de complétude exacte des huit providers, tests adversariaux numeric/timestamps/provenance/redaction et vérification de l'absence d'URL/rate-limit provider configurables. Les premiers README.md / USAGE.md d'Off-chain Transport et les ajouts Config associés sont des brouillons techniques ; pre.012 reste seule propriétaire de la réconciliation documentaire finale. Le contrat refresh_many/refresh_all V1 est explicitement réaffirmé séquentiel et déterministe.
pre.010-fix.001 retire du canari Off-chain la vérification lexicale du chemin logging direct : elle dupliquait le scanner global de ksp-logging-lib et son propre littéral de test était lui-même détecté comme bypass. Le scanner workspace reste l'autorité unique pour cette frontière ; aucune politique logging ni logique runtime Off-chain ne change.
pre.011 — Gate technique et live final
Statut : planifié.
Smokes gratuits possibles, graphes Cargo, validations techniques finales et caractérisation actuelle des providers.
pre.012 — Réconciliation documentaire finale
Statut : planifié.
Réconciliation finale du plan, de la validation, des README/USAGE, de l'architecture et des références durables après les gates techniques.
pre.013 — Préparation minimale de publication
Statut : planifié.
Prompt 0.2.12, CHANGELOG.md, ROADMAP.md et mécanique de version uniquement, sans reprendre les responsabilités techniques ou documentaires des deux tranches précédentes.
rel.001 — Publication stable v0.2.11
Statut : planifié.
Publication stable après validation de la dernière prerelease et vérification de la cohérence de release.
Les responsabilités de clôture restent strictement séparées :
pre.011 = technique/live
pre.012 = documentation finale
pre.013 = prompt suivant + CHANGELOG + ROADMAP seulement
21. Gate de sizing pre.001
Verdict : GO sans split de release.
Justification :
huit adapters REST sont petits et indépendants
aucun SDK provider n'est nécessaire
les primitives HTTP/serde/tokio sont déjà au workspace
la seule paire publique est SOL/USD
le contrat commun reste volontairement petit
la complexité réelle est isolée dans registry/rate limiting/availability
les adapters peuvent être livrés par groupes de prereleases courtes
0.2.11 reste toutefois une release multi-provider et ne doit pas compresser artificiellement les gates de sécurité/live.