# Delta `0.2.2-pre.004` — cinq wrappers HTTP Tokens typés ## Base requise Livraison précédente corrigée et validée localement par l'opérateur : ```text 0.2.2-pre.003-fix.001 workspace.package.version = "0.2.2-pre.3.fix.1" ``` La validation opérateur du 2026-08-18 a confirmé : ```text cargo fmt --all OK cargo check --workspace OK cargo clippy --workspace --all-targets OK cargo test -p ksp-onchain-transport-lib OK ``` Résultats Transport de cette base : ```text 98 unit tests 10 public API tests 3 release completeness tests 0 warning signalé par check/clippy ``` Le plan canonique reste `docs/plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md` version 3. ## Objectif Clôturer la famille Tokens attribuée à `0.2.2` avec cinq wrappers publics typés : ```text getTokenAccountBalance getTokenAccountsByDelegate getTokenAccountsByOwner getTokenLargestAccounts getTokenSupply ``` Tous passent par la foundation HTTP commune : ```text wrapper typé -> descriptor audité -> execute_standard_rpc -> pool/admission/retry/deadline -> reqwest HTTP -> JSON-RPC validation -> décodage DTO KSP ``` Aucun wrapper Token ne contacte `reqwest` directement et aucune crate SPL n'est ajoutée. ## Version Cargo Conformément à `VER-ID-009` : ```text 0.2.2-pre.3.fix.1 -> 0.2.2-pre.4 ``` Aucune dépendance ni feature Cargo n'est ajoutée ou retirée. ## Réaudit ciblé du contrat courant Les pages RPC Solana courantes ont été revérifiées le 2026-08-18 pour cette tranche : ```text https://solana.com/docs/rpc/http/gettokenaccountbalance https://solana.com/docs/rpc/http/gettokenaccountsbydelegate https://solana.com/docs/rpc/http/gettokenaccountsbyowner https://solana.com/docs/rpc/http/gettokenlargestaccounts https://solana.com/docs/rpc/http/gettokensupply ``` Le contrat reste cohérent avec le plan `009` : - `getTokenAccountBalance`, `getTokenLargestAccounts` et `getTokenSupply` acceptent un `commitment` optionnel ; - `getTokenAccountsByDelegate` et `getTokenAccountsByOwner` prennent un selector exclusif `{mint}` ou `{programId}` puis une config Account optionnelle ; - les cinq réponses sont contextualisées ; - les deux méthodes de liste renvoient des `pubkey + account` et réutilisent donc le DTO Account générique ; - `TokenAmount.uiAmount` reste nullable ; `uiAmountString` reste la représentation textuelle à préserver ; - `getTokenLargestAccounts` renvoie les 20 plus gros token accounts côté RPC, sans que KSP invente un tri supplémentaire. Le réaudit complémentaire Agave `v4.2.1` déjà fixé en `pre.001-fix.001` reste la référence d'implémentation ciblée ; aucune divergence nouvelle nécessitant un changement de DTO n'a été identifiée pour cette tranche. ## Surface typée Tokens ### `getTokenAccountBalance` Signature publique conceptuelle : ```text role + token account Pubkey + Option -> SolanaRpcResponse ``` Le wrapper préserve `amount`, `decimals`, `uiAmount` nullable et `uiAmountString`, et conserve une erreur JSON-RPC distante comme erreur applicative RPC. ### `getTokenAccountsByDelegate` Signature publique conceptuelle : ```text role + delegate Pubkey + SolanaTokenAccountSelector + Option -> SolanaRpcResponse> ``` Le selector KSP empêche par construction les objets contenant simultanément `mint` et `programId`. La config Account réutilise les encodings, `dataSlice`, `commitment` et `minContextSlot` activés en `pre.003`. ### `getTokenAccountsByOwner` Signature publique conceptuelle : ```text role + owner Pubkey + SolanaTokenAccountSelector + Option -> SolanaRpcResponse> ``` Le wrapper partage le même chemin privé de sérialisation/décodage que la variante Delegate afin d'éviter deux implémentations de wire parallèles. Un config Account explicitement vide est omis du tableau `params`. ### `getTokenLargestAccounts` Signature publique conceptuelle : ```text role + mint Pubkey + Option -> SolanaRpcResponse> ``` L'ordre retourné par le serveur est conservé. Chaque `address` est validée et convertie en `ksp_core_lib::Pubkey` sans recopier une valeur wire invalide dans le diagnostic. ### `getTokenSupply` Signature publique conceptuelle : ```text role + mint Pubkey + Option -> SolanaRpcResponse ``` `amount` reste une chaîne exacte et KSP ne convertit pas la supply en type métier SPL ni en entier borné localement. ## Mutualisation activée en production Les helpers Token préparés en `pre.002` deviennent production-live uniquement parce qu'ils ont maintenant des consommateurs runtime réels : - sérialisation de `SolanaTokenAccountSelector` ; - décodage `SolanaTokenAmount` ; - décodage `SolanaTokenAccountBalance` ; - wire structs Token privés. `SolanaAccountInfoConfig::is_empty` devient `pub(crate)` pour permettre aux deux wrappers de liste Token de réutiliser la règle d'omission de config vide déjà utilisée par Accounts. Cette modification reste interne à la crate et n'élargit pas l'API publique externe. Les helpers Cluster préparatoires restent inchangés et ne deviennent pas production-live avant `pre.005`/`pre.006`. ## Validation locale des paramètres Aucun nouveau code d'erreur local n'est nécessaire dans cette tranche : - le selector `Mint | ProgramId` garantit exactement une variante par construction ; - les owner/delegate/mint/token-account sont des `Pubkey` typées avant l'appel ; - les configs Account/Commitment réutilisent les types déjà validés ; - aucune cardinalité supplémentaire documentée ne doit être imposée côté client. `ERROR_CODE_INVALID_RPC_PARAMETERS` introduit en `pre.003` reste inchangé. ## Fixtures HTTP déterministes ajoutées ```text crates/ksp-onchain-transport-lib/fixtures/http/get_token_account_balance.success.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_account_balance.error.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_accounts_by_delegate.success.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_accounts_by_owner.success.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_accounts_by_owner.invalid_pubkey.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_largest_accounts.success.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_largest_accounts.invalid_address.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_supply.success.json ``` Les tests utilisent un serveur HTTP loopback et n'accèdent pas à Internet. ## Couverture de tests ajoutée Les tests Token couvrent notamment : - request exacte de chacune des cinq méthodes ; - selector `programId` pour Delegate ; - selector `mint` pour Owner ; - config Account complète et omission d'un config explicitement vide ; - commitment optionnel ; - `uiAmount: null` ; - montant/supply textuel exact ; - conservation de l'ordre pour `getTokenLargestAccounts` ; - décodage `jsonParsed` des accounts sous forme JSON opaque Transport ; - erreur JSON-RPC applicative ; - rejet d'une `pubkey` ou `address` invalide dans une réponse typée. Le test public compile explicitement les cinq méthodes depuis `HttpTransportPool`. Un canari release fige l'ensemble Tokens `0.2.2` exact : ```text getTokenAccountBalance getTokenAccountsByDelegate getTokenAccountsByOwner getTokenLargestAccounts getTokenSupply ``` Il vérifie également `Read + RetrySafe` pour chacun. Les 12 wrappers Cluster restent hors de cette tranche. Après application, la cible Transport attendue devient : ```text 106 unit tests 11 public API tests 4 release completeness tests ``` ## Fichiers ajoutés ```text crates/ksp-onchain-transport-lib/fixtures/http/get_token_account_balance.success.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_account_balance.error.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_accounts_by_delegate.success.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_accounts_by_owner.success.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_accounts_by_owner.invalid_pubkey.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_largest_accounts.success.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_largest_accounts.invalid_address.json crates/ksp-onchain-transport-lib/fixtures/http/get_token_supply.success.json deltas/0.2.2/pre.004.md ``` ## Fichiers modifiés ```text Cargo.toml crates/ksp-onchain-transport-lib/src/lib.rs crates/ksp-onchain-transport-lib/src/rpc_accounts.rs crates/ksp-onchain-transport-lib/src/rpc_tokens.rs crates/ksp-onchain-transport-lib/unit_tests/rpc_tokens.rs crates/ksp-onchain-transport-lib/tests/public_api.rs crates/ksp-onchain-transport-lib/tests/release_completeness.rs ``` ## Fichiers supprimés Aucun. ## Fichiers volontairement inchangés ```text deltas/0.2.2/pre.003.md deltas/0.2.2/pre.003-fix.001.md docs/plans/009-V0_2_2_HTTP_ACCOUNTS_TOKENS_CLUSTER_PLAN.md ROADMAP.md CHANGELOG.md crates/ksp-onchain-transport-lib/src/rpc_common.rs crates/ksp-onchain-transport-lib/src/rpc_cluster.rs crates/ksp-onchain-transport-lib/src/rpc_method.rs crates/ksp-config-lib/** config/** ``` Les deltas déjà publiés restent des traces historiques et ne sont pas réécrits. ## Contrôles statiques effectués pendant la préparation - `workspace.package.version` vaut `0.2.2-pre.4` ; - les huit nouvelles fixtures JSON sont syntaxiquement valides ; - les cinq wrappers utilisent `execute_standard_rpc` ; - aucun `reqwest` direct n'est ajouté dans `rpc_tokens.rs` ; - aucun `#[allow(dead_code)]` ou suppression de lint n'est ajouté ; - aucun helper Cluster n'est activé en production ; - aucune dépendance ou feature Cargo n'est ajoutée ; - aucune crate SPL n'est importée ; - la surface publique Accounts de `pre.003` reste présente ; - les deltas historiques restent inchangés. ## Validations Rust non exécutées dans le sandbox Le sandbox de préparation ne fournit pas la toolchain Cargo utilisée par le checkout opérateur. Les commandes suivantes doivent donc être rejouées après application : ```bash cargo fmt --all cargo check --workspace cargo clippy --workspace --all-targets cargo test -p ksp-onchain-transport-lib ``` Les validations ne sont pas déclarées réussies tant qu'elles n'ont pas été exécutées sur le checkout opérateur. ## Questions ouvertes Aucune question bloquante. ## Suite Après validation et commit de `0.2.2-pre.004`, poursuivre avec `0.2.2-pre.005` pour les sept wrappers Cluster simples : ```text getClusterNodes getEpochInfo getEpochSchedule getHighestSnapshotSlot getIdentity getMaxRetransmitSlot getMaxShredInsertSlot ```