diff --git a/.env.example b/.env.example index f22359f..682a561 100644 --- a/.env.example +++ b/.env.example @@ -14,10 +14,15 @@ KS_SECRET_POSTGRES_DEVNET_URL=postgres://solana:solana@localhost:5432/khadhroony # PostgreSQL database reserved for automated integration tests. KS_SECRET_POSTGRES_TEST_URL=postgres://solana:solana@localhost:5432/khadhroony_test -# Optional independent configuration-file overrides. -# KS_CONFIG_PATH=config/app.config.json +# Optional binary composition override for the desktop demo. +# KB_APP_DEMO_DESKTOP_CONFIG_PATH=config/kb-app-demo-desktop.default.config.json + +# Optional direct logging configuration override. When absent, the desktop uses the path declared by its composition. # KS_LOGGING_CONFIG_PATH=config/logging.config.json +# Optional composition override used by opt-in Devnet scenario tests and the scenarios CLI. +# KS_DEVNET_CONFIG_PATH=config/ks-pipeline-demo-scenarios.default.config.json + # Optional Devnet profile selected by CLI and opt-in scenario tests. # When absent, the first compatible Devnet profile is selected. # KS_DEVNET_PROFILE=local_devnet diff --git a/Cargo.toml b/Cargo.toml index f13231b..ff4728b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ # file: Cargo.toml -# version: 49 +# version: 50 [workspace] resolver = "3" @@ -18,7 +18,7 @@ members = [ ] [workspace.package] -version = "0.5.1-pre.5" +version = "0.5.1-pre.6" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-bot3" diff --git a/ROADMAP.md b/ROADMAP.md index 431fcfe..4026471 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,5 +1,5 @@ - + # ROADMAP — khadhroony-bot3 @@ -91,8 +91,10 @@ Cette version effectue la migration de namespace des bibliothèques Solana et re - imposer `KS_*` aux variables possédées par les composants Solana généralistes et réserver `KB_*` aux variables réellement possédées par `kb-app-demo-desktop` ou de futures crates spécifiques au Bot ; - appliquer symétriquement les classes `*_SECRET_*`, `*_PUBLIC_*` et internes aux namespaces `KS_*` et `KB_*`, avec exposition publique uniquement via une surface explicitement autorisée ; - préserver la sensibilité après substitution : toute valeur composée contenant un `KS_SECRET_*` ou `KB_SECRET_*` reste secrète ; -- séparer la configuration généraliste et la configuration logging dans des documents et schémas indépendants ; -- permettre des profils logging indépendants des profils réseau/applicatifs ; +- remplacer la configuration monolithique par des compositions propres aux binaires (`.default.config.json`) qui sélectionnent des documents/profils spécialisés partageables ; +- séparer au minimum logging, transport, listeners, store, wallet et politiques d’exécution selon leur ownership réel ; `logs_directory` appartient au logging, `wallets_directory` au wallet et les permissions d’envoi à la politique d’exécution ; +- permettre des sélections de profils logging, transport et listeners indépendantes de la composition active du binaire ; +- permettre aux futurs workers et applications de réutiliser les documents généralistes `ks-*` tout en ajoutant une configuration dédiée sans enregistrer leur identité dans `ks-config` ; - distinguer configuration source, configuration runtime résolue et DTO publics/diagnostiques ; - interdire qu'un secret résolu soit renvoyé en clair par logs, erreurs, diagnostics, UI, sérialisation ou payload Tauri ; - garder exemples, schémas JSON, TS-RS et documentation synchronisés. diff --git a/config/README.md b/config/README.md index 4e76c08..2a7d103 100644 --- a/config/README.md +++ b/config/README.md @@ -1,86 +1,110 @@ - + # Configuration locale -Ce dossier contient les deux documents de configuration runtime, leurs exemples et leurs schémas JSON indépendants. +Ce dossier contient les compositions propres aux binaires, les documents de configuration partagés, leurs exemples et les schémas JSON actifs. -## Fichiers chargés par défaut +## Principe de composition -- `app.config.json` : configuration générale Khadhroony Solana ; -- `logging.config.json` : configuration logging/tracing ; -- `schemas/app.config.schema.json` : schéma du document général ; -- `schemas/logging.config.schema.json` : schéma du document logging. +Un binaire ne possède pas une copie complète de toutes les configurations partagées. Il charge un fichier de composition `.default.config.json` qui référence les documents spécialisés et sélectionne explicitement un profil dans chacun. -Les chemins par défaut peuvent être remplacés indépendamment par : +Le desktop utilise par défaut : ```text -KS_CONFIG_PATH -KS_LOGGING_CONFIG_PATH +config/kb-app-demo-desktop.default.config.json ``` -## Exemples conformes - -- `example.app.config.json` : exemple général minimal conforme ; -- `example.logging.config.json` : exemple logging minimal conforme. - -Les exemples servent de référence de structure. Ils ne sont pas sélectionnés automatiquement au démarrage. - -## Sélection indépendante des profils - -Chaque document possède son propre `active_profile` : +Le CLI des scénarios possède également une composition explicite : ```text -app.config.json - active_profile = mainnet_research - -logging.config.json - active_profile = mainnet_research +config/ks-pipeline-demo-scenarios.default.config.json ``` -Ces valeurs sont indépendantes. Changer le profil applicatif ne change pas implicitement le profil logging, et inversement. Les noms peuvent être identiques pour faciliter l’exploitation sans créer de couplage contractuel. +La composition desktop peut être remplacée par `KB_APP_DEMO_DESKTOP_CONFIG_PATH`. Les tests/scénarios Devnet opt-in peuvent remplacer leur composition avec `KS_DEVNET_CONFIG_PATH`. -Le document général ne contient plus de propriété `logging`. Les routes, niveaux, formats et filtres sont exclusivement possédés par `logging.config.json` et `ks-logging`. - -## Variables d’environnement - -`app.config.json` référence notamment : +## Documents partagés actuels ```text -KS_SECRET_HELIUS_API_KEY -KS_SECRET_POSTGRES_MAINNET_URL -KS_SECRET_POSTGRES_DEVNET_URL +config/ +├── kb-app-demo-desktop.default.config.json +├── ks-pipeline-demo-scenarios.default.config.json +├── logging.config.json +├── transport.config.json +├── listeners.config.json +├── example.kb-app-demo-desktop.default.config.json +├── example.logging.config.json +├── example.transport.config.json +├── example.listeners.config.json +└── schemas/ + ├── composition.config.schema.json + ├── logging.config.schema.json + ├── transport.config.schema.json + ├── listeners.config.schema.json + └── resolved.app.config.schema.json ``` -`KS_SECRET_POSTGRES_TEST_URL` reste réservé aux tests d’intégration PostgreSQL. Le fichier réel `.env` reste local et ignoré par Git ; `.env.example` est le modèle versionné. +`resolved.app.config.schema.json` décrit uniquement le contrat runtime `AppConfig/ProfileConfig` reconstruit par `ks-config`. Il ne correspond pas à un fichier runtime chargé directement. -La classification `KS_SECRET_*` / `KS_PUBLIC_*` / `KS_*` et `KB_SECRET_*` / `KB_PUBLIC_*` / `KB_*` est normative. Le camouflage et la propagation de sensibilité dans les valeurs composées sont traités dans la prerelease suivante ; aucun secret ne doit être écrit en clair dans un fichier versionné. +## Sélection des profils -## Schémas +Les documents partagés conservent un `active_profile` pour les consommateurs autonomes. Une composition binaire peut cependant sélectionner un autre profil explicitement : -Les schémas runtime sont localisés exclusivement sous `config/schemas/`. +```text +composition profile mainnet_research + logging_profile = mainnet_research + transport_profile = mainnet_research + listeners_profile = mainnet_research +``` -`ks-config` embarque `schemas/app.config.schema.json`. `ks-logging` embarque `schemas/logging.config.schema.json`. Les fichiers embarqués et leurs versions sur disque doivent rester identiques. +Lorsqu'une composition est utilisée, sa sélection explicite prime pour le binaire. Le même `transport.config.json` ou `listeners.config.json` peut donc être réutilisé par plusieurs binaires avec des combinaisons différentes. -## Migration depuis le format combiné +## Transport -L’ancien `example.config.json` combinait configuration générale et logging dans chaque profil. La migration consiste à : +`transport.config.json` possède les endpoints HTTP et WebSocket, leurs providers, clusters, timeouts, capacités, rôles, limites et politique de reconnexion propre aux endpoints. -1. conserver dans `app.config.json` les sections `app`, `database`, `data`, `solana`, `wallet`, `execution` et `demo` ; -2. extraire chaque ancien bloc `logging` vers le profil homonyme de `logging.config.json` ; -3. sélectionner explicitement un `active_profile` dans chacun des deux documents ; -4. supprimer l’ancien fichier combiné une fois la migration validée. +`ks-onchain-transport` peut consommer directement un `TransportProfileConfig`. Un futur worker n'a donc pas besoin de dépendre d'un `ProfileConfig` applicatif complet pour construire ses pools réseau. + +## Listeners + +`listeners.config.json` possède les ensembles de listeners Solana : + +- `logsSubscribe` par mention de programme ; +- `programSubscribe` ; +- `accountSubscribe` ; +- commitment et rôles d'endpoint associés. + +La séparation prépare les futurs workers de capture, qui pourront associer un profil transport et un profil listeners sans recopier les définitions dans leur propre configuration. + +## Logging + +`logging.config.json` reste possédé par `ks-logging`. Une composition sélectionne le profil logging souhaité. `KS_LOGGING_CONFIG_PATH` reste un override direct du chemin logging pour l'exploitation ; sans override, le chemin déclaré par la composition est utilisé. + +## Configuration encore transitoire dans la composition + +Après `pre.006`, les sections suivantes restent temporairement dans les profils de composition : + +- `database` et `data` ; +- `wallet` ; +- `execution` ; +- `demo`. + +Elles seront séparées dans la prerelease suivante. En particulier, `demo` est propre au desktop et ne doit pas rester durablement dans un contrat généraliste `ks-config`. + +## Variables d'environnement + +Les composants `ks-*` utilisent `KS_SECRET_*`, `KS_PUBLIC_*` ou `KS_*`. Les besoins réellement spécifiques à une application `kb-*` utilisent `KB_SECRET_*`, `KB_PUBLIC_*` ou `KB_*`. + +Les secrets ne sont jamais écrits en clair dans le dépôt. La propagation de sensibilité et les DTO publics sûrs sont traités après la fin du découpage des fichiers de configuration. + +## Exemples et schémas + +Les exemples sous `config/` sont des références conformes et ne sont pas chargés automatiquement. Tous les schémas actifs résident exclusivement sous `config/schemas/`. + +Les fichiers historiques `app.config.json`, `example.app.config.json`, `schemas/app.config.schema.json`, `example.config.json` et `schema.config.json` sont obsolètes et ne doivent pas coexister avec cette architecture. ## Logging de développement -Le fichier `logging.config.json` conserve actuellement les routes globales ainsi que trois fichiers dédiés par crate opérationnelle utilisant `tracing` : `debug.log`, `info.log` et `error.jsonl`. - -Les matérialisateurs consolidés disposent de leurs routes sous les targets `ks-lib-materializer.*`. Les tests de `ks-logging` découvrent dynamiquement les crates déclarant `tracing.workspace = true` et vérifient les routes canoniques du document logging par défaut. +Le fichier `logging.config.json` conserve les routes globales ainsi que trois fichiers dédiés par crate opérationnelle utilisant `tracing` : `debug.log`, `info.log` et `error.jsonl`. Les logs ne doivent contenir ni secret, ni DSN non masqué, ni keypair, ni payload de configuration résolue complet. - -## Limites du profil public Devnet - -`api.devnet.solana.com` est un endpoint public partagé. Les limites configurées par rôle ne sont pas des quotas indépendants fournis par le serveur : leur débit cumulé doit rester sous la limite globale de l’endpoint, et chaque méthode RPC doit également rester sous sa propre limite. - -Le profil `local_devnet` de `app.config.json` utilise donc volontairement des valeurs conservatrices pour les rôles HTTP. Un warning `retry_http_json_rpc_after_rate_limit` reste possible sur un service partagé ; il indique que le cooldown et le retry borné ont été activés. diff --git a/config/app.config.json b/config/app.config.json deleted file mode 100644 index c151396..0000000 --- a/config/app.config.json +++ /dev/null @@ -1,1110 +0,0 @@ -{ - "active_profile": "mainnet_research", - "profiles": [ - { - "name": "local_devnet", - "app": { - "name": "khadhroony-bot3", - "environment": "development", - "auto_reconnect_default": false - }, - "database": { - "enabled": true, - "backend": "postgres", - "postgres": { - "url": "${KS_SECRET_POSTGRES_DEVNET_URL}", - "max_connections": 8, - "connect_timeout_ms": 5000, - "auto_initialize_schema": true - }, - "sqlite": { - "path": "data/local.sqlite", - "create_if_missing": true, - "busy_timeout_ms": 5000, - "max_connections": 1, - "auto_initialize_schema": true, - "use_wal": true - } - }, - "data": { - "wallets_directory": "wallets", - "logs_directory": "logs" - }, - "solana": { - "http_endpoints": [ - { - "name": "devnet_public_http", - "enabled": true, - "provider": "solana-public", - "cluster": "devnet", - "url": "https://api.devnet.solana.com", - "connect_timeout_ms": 4000, - "request_timeout_ms": 10000, - "max_idle_connections_per_host": 4, - "roles": [ - { - "role": "http_queries", - "enabled": true, - "request_kinds": [ - "get_account_info", - "get_balance", - "get_block_commitment", - "get_block_height", - "get_block_production", - "get_blocks", - "get_blocks_with_limit", - "get_cluster_nodes", - "get_epoch_info", - "get_epoch_schedule", - "get_fee_for_message", - "get_first_available_block", - "get_genesis_hash", - "get_health", - "get_highest_snapshot_slot", - "get_identity", - "get_inflation_governor", - "get_inflation_rate", - "get_inflation_reward", - "get_largest_accounts", - "get_latest_blockhash", - "get_leader_schedule", - "get_max_retransmit_slot", - "get_max_shred_insert_slot", - "get_minimum_balance_for_rent_exemption", - "get_multiple_accounts", - "get_recent_performance_samples", - "get_recent_prioritization_fees", - "get_signature_statuses", - "get_slot", - "get_slot_leader", - "get_slot_leaders", - "get_stake_activation", - "get_stake_minimum_delegation", - "get_supply", - "get_token_account_balance", - "get_token_accounts_by_delegate", - "get_token_accounts_by_owner", - "get_token_largest_accounts", - "get_token_supply", - "get_transaction", - "get_transaction_count", - "get_version", - "get_vote_accounts", - "is_blockhash_valid", - "request_airdrop" - ], - "priority": 10, - "requests_per_second": 3, - "burst_capacity": 3, - "max_concurrent_requests": 2, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 10000 - }, - { - "role": "http_transactions", - "enabled": true, - "request_kinds": [ - "simulate_transaction", - "send_transaction", - "get_signature_statuses" - ], - "priority": 20, - "requests_per_second": 1, - "burst_capacity": 1, - "max_concurrent_requests": 1, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 10000 - } - ] - } - ], - "ws_endpoints": [ - { - "name": "devnet_public_ws", - "enabled": true, - "provider": "solana-public", - "cluster": "devnet", - "url": "wss://api.devnet.solana.com", - "connect_timeout_ms": 5000, - "request_timeout_ms": 10000, - "unsubscribe_timeout_ms": 1500, - "write_channel_capacity": 256, - "event_channel_capacity": 1024, - "auto_reconnect": false, - "roles": [ - { - "role": "slot_notifications", - "enabled": true, - "request_kinds": [ - "slot_subscribe", - "slots_updates_subscribe" - ], - "priority": 10, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 16, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "program_logs", - "enabled": true, - "request_kinds": [ - "logs_subscribe_mentions", - "program_subscribe", - "account_subscribe", - "signature_subscribe", - "block_subscribe", - "vote_subscribe" - ], - "priority": 20, - "requests_per_second": 2, - "burst_capacity": 4, - "max_concurrent_requests": 2, - "max_subscriptions": 64, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "root_notifications", - "enabled": true, - "request_kinds": [ - "root_subscribe" - ], - "priority": 15, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 16, - "pause_after_rate_limit_ms": 3000 - } - ] - } - ], - "listeners": { - "enabled": true, - "default_commitment": "confirmed", - "log_listeners": [ - { - "name": "pump_swap_logs", - "enabled": false, - "endpoint_role": "program_logs", - "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", - "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." - } - ], - "program_listeners": [ - { - "name": "pump_fun_program_accounts", - "enabled": false, - "endpoint_role": "program_logs", - "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", - "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." - } - ], - "account_listeners": [] - } - }, - "wallet": { - "wallet_dir": "wallets/temporary/local_devnet", - "cluster": "devnet", - "temporary_wallet_enabled": true, - "temporary_wallet_alias": "local-devnet-operator", - "temporary_wallet_persist": true, - "localnet_send_enabled": false, - "devnet_send_enabled": true, - "testnet_send_enabled": false, - "mainnet_send_enabled": false - }, - "execution": { - "dry_run_default": true, - "require_simulation": true, - "require_operator_confirmation": true, - "localnet_max_spend_lamports": 0, - "devnet_max_spend_lamports": 100000000, - "testnet_max_spend_lamports": 0, - "mainnet_max_spend_lamports": 0, - "max_fee_lamports": 1000000, - "max_compute_unit_price_micro_lamports": 100000, - "recent_blockhash_max_age_slots": 150, - "send_max_retries": 3, - "confirmation_poll_interval_ms": 500, - "confirmation_max_attempts": 120, - "devnet_airdrop_max_lamports": 2000000000 - }, - "demo": { - "live_demo_enabled": true, - "trading_demo_enabled": false - } - }, - { - "name": "mainnet_research", - "app": { - "name": "khadhroony-bot3", - "environment": "research", - "auto_reconnect_default": false - }, - "database": { - "enabled": true, - "backend": "postgres", - "postgres": { - "url": "${KS_SECRET_POSTGRES_MAINNET_URL}", - "max_connections": 16, - "connect_timeout_ms": 5000, - "auto_initialize_schema": true - }, - "sqlite": { - "path": "data/mainnet-import.sqlite", - "create_if_missing": true, - "busy_timeout_ms": 5000, - "max_connections": 1, - "auto_initialize_schema": true, - "use_wal": true - } - }, - "data": { - "wallets_directory": "wallets", - "logs_directory": "logs" - }, - "solana": { - "http_endpoints": [ - { - "name": "mainnet_public_http", - "enabled": true, - "provider": "solana-public", - "cluster": "mainnet-beta", - "url": "https://api.mainnet.solana.com", - "connect_timeout_ms": 4000, - "request_timeout_ms": 10000, - "max_idle_connections_per_host": 4, - "roles": [ - { - "role": "http_queries", - "enabled": true, - "request_kinds": [ - "get_account_info", - "get_balance", - "get_block_commitment", - "get_block_height", - "get_block_production", - "get_blocks", - "get_blocks_with_limit", - "get_cluster_nodes", - "get_epoch_info", - "get_epoch_schedule", - "get_fee_for_message", - "get_first_available_block", - "get_genesis_hash", - "get_health", - "get_highest_snapshot_slot", - "get_identity", - "get_inflation_governor", - "get_inflation_rate", - "get_inflation_reward", - "get_largest_accounts", - "get_latest_blockhash", - "get_leader_schedule", - "get_max_retransmit_slot", - "get_max_shred_insert_slot", - "get_minimum_balance_for_rent_exemption", - "get_multiple_accounts", - "get_recent_performance_samples", - "get_recent_prioritization_fees", - "get_signature_statuses", - "get_slot", - "get_slot_leader", - "get_slot_leaders", - "get_stake_activation", - "get_stake_minimum_delegation", - "get_supply", - "get_token_account_balance", - "get_token_accounts_by_delegate", - "get_token_accounts_by_owner", - "get_token_largest_accounts", - "get_token_supply", - "get_transaction_count", - "get_version", - "get_vote_accounts", - "is_blockhash_valid", - "request_airdrop" - ], - "priority": 50, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "http_fallback", - "enabled": true, - "request_kinds": [ - "get_account_info", - "get_balance", - "get_block_commitment", - "get_block_height", - "get_block_production", - "get_blocks", - "get_blocks_with_limit", - "get_cluster_nodes", - "get_epoch_info", - "get_epoch_schedule", - "get_fee_for_message", - "get_first_available_block", - "get_genesis_hash", - "get_health", - "get_highest_snapshot_slot", - "get_identity", - "get_inflation_governor", - "get_inflation_rate", - "get_inflation_reward", - "get_largest_accounts", - "get_latest_blockhash", - "get_leader_schedule", - "get_max_retransmit_slot", - "get_max_shred_insert_slot", - "get_minimum_balance_for_rent_exemption", - "get_multiple_accounts", - "get_recent_performance_samples", - "get_recent_prioritization_fees", - "get_signature_statuses", - "get_slot", - "get_slot_leader", - "get_slot_leaders", - "get_stake_activation", - "get_stake_minimum_delegation", - "get_supply", - "get_token_account_balance", - "get_token_accounts_by_delegate", - "get_token_accounts_by_owner", - "get_token_largest_accounts", - "get_token_supply", - "get_transaction_count", - "get_version", - "get_vote_accounts", - "is_blockhash_valid", - "request_airdrop" - ], - "priority": 90, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 3000 - } - ] - }, - { - "name": "helius_primary_http", - "enabled": true, - "provider": "helius", - "cluster": "mainnet-beta", - "url": "https://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", - "connect_timeout_ms": 4000, - "request_timeout_ms": 10000, - "max_idle_connections_per_host": 8, - "roles": [ - { - "role": "http_queries", - "enabled": true, - "request_kinds": [ - "get_account_info", - "get_balance", - "get_block_commitment", - "get_block_height", - "get_block_production", - "get_blocks", - "get_blocks_with_limit", - "get_cluster_nodes", - "get_epoch_info", - "get_epoch_schedule", - "get_fee_for_message", - "get_first_available_block", - "get_genesis_hash", - "get_health", - "get_highest_snapshot_slot", - "get_identity", - "get_inflation_governor", - "get_inflation_rate", - "get_inflation_reward", - "get_largest_accounts", - "get_latest_blockhash", - "get_leader_schedule", - "get_max_retransmit_slot", - "get_max_shred_insert_slot", - "get_minimum_balance_for_rent_exemption", - "get_multiple_accounts", - "get_recent_performance_samples", - "get_recent_prioritization_fees", - "get_signature_statuses", - "get_slot", - "get_slot_leader", - "get_slot_leaders", - "get_stake_activation", - "get_stake_minimum_delegation", - "get_supply", - "get_token_account_balance", - "get_token_accounts_by_delegate", - "get_token_accounts_by_owner", - "get_token_largest_accounts", - "get_token_supply", - "get_transaction_count", - "get_version", - "get_vote_accounts", - "is_blockhash_valid", - "request_airdrop" - ], - "priority": 10, - "requests_per_second": 8, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 1500 - }, - { - "role": "http_heavy", - "enabled": true, - "request_kinds": [ - "get_block", - "get_program_accounts", - "get_signatures_for_address", - "get_transaction" - ], - "priority": 20, - "requests_per_second": 2, - "burst_capacity": 1, - "max_concurrent_requests": 2, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 1500 - }, - { - "role": "history_backfill", - "enabled": true, - "request_kinds": [ - "get_block", - "get_program_accounts", - "get_signatures_for_address", - "get_transaction" - ], - "priority": 30, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 1500 - }, - { - "role": "http_transactions", - "enabled": false, - "request_kinds": [ - "simulate_transaction", - "send_transaction", - "get_signature_statuses" - ], - "priority": 40, - "requests_per_second": 1, - "burst_capacity": 1, - "max_concurrent_requests": 1, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 3000 - } - ] - } - ], - "ws_endpoints": [ - { - "name": "mainnet_public_ws_slots", - "enabled": true, - "provider": "solana-public", - "cluster": "mainnet-beta", - "url": "wss://api.mainnet.solana.com", - "connect_timeout_ms": 5000, - "request_timeout_ms": 10000, - "unsubscribe_timeout_ms": 1500, - "write_channel_capacity": 256, - "event_channel_capacity": 1024, - "auto_reconnect": false, - "roles": [ - { - "role": "slot_notifications", - "enabled": true, - "request_kinds": [ - "slot_subscribe", - "slots_updates_subscribe" - ], - "priority": 10, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 16, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "root_notifications", - "enabled": true, - "request_kinds": [ - "root_subscribe" - ], - "priority": 20, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 16, - "pause_after_rate_limit_ms": 3000 - } - ] - }, - { - "name": "helius_primary_ws_programs", - "enabled": true, - "provider": "helius", - "cluster": "mainnet-beta", - "url": "wss://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", - "connect_timeout_ms": 5000, - "request_timeout_ms": 10000, - "unsubscribe_timeout_ms": 1500, - "write_channel_capacity": 512, - "event_channel_capacity": 4096, - "auto_reconnect": false, - "roles": [ - { - "role": "logs_subscribe", - "enabled": true, - "request_kinds": [ - "logs_subscribe_mentions" - ], - "priority": 10, - "requests_per_second": 2, - "burst_capacity": 4, - "max_concurrent_requests": 2, - "max_subscriptions": 128, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "program_subscribe", - "enabled": true, - "request_kinds": [ - "program_subscribe" - ], - "priority": 20, - "requests_per_second": 2, - "burst_capacity": 4, - "max_concurrent_requests": 2, - "max_subscriptions": 128, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "account_subscribe", - "enabled": true, - "request_kinds": [ - "account_subscribe" - ], - "priority": 30, - "requests_per_second": 2, - "burst_capacity": 4, - "max_concurrent_requests": 2, - "max_subscriptions": 128, - "pause_after_rate_limit_ms": 3000 - } - ] - } - ], - "listeners": { - "enabled": true, - "default_commitment": "confirmed", - "log_listeners": [ - { - "name": "pump_swap_logs", - "enabled": true, - "endpoint_role": "program_logs", - "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", - "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." - }, - { - "name": "raydium_cpmm_logs", - "enabled": true, - "endpoint_role": "program_logs", - "program_id": "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", - "purpose": "Détecter les créations de pools et swaps Raydium CPMM." - } - ], - "program_listeners": [ - { - "name": "pump_fun_program_accounts", - "enabled": true, - "endpoint_role": "program_logs", - "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", - "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." - } - ], - "account_listeners": [] - } - }, - "wallet": { - "wallet_dir": "wallets/temporary/mainnet_research", - "cluster": "mainnet-beta", - "temporary_wallet_enabled": false, - "temporary_wallet_alias": "mainnet_research-disabled", - "temporary_wallet_persist": false, - "localnet_send_enabled": false, - "devnet_send_enabled": false, - "testnet_send_enabled": false, - "mainnet_send_enabled": false - }, - "execution": { - "dry_run_default": true, - "require_simulation": true, - "require_operator_confirmation": true, - "localnet_max_spend_lamports": 0, - "devnet_max_spend_lamports": 0, - "testnet_max_spend_lamports": 0, - "mainnet_max_spend_lamports": 0, - "max_fee_lamports": 1000000, - "max_compute_unit_price_micro_lamports": 100000, - "recent_blockhash_max_age_slots": 150, - "send_max_retries": 3, - "confirmation_poll_interval_ms": 500, - "confirmation_max_attempts": 120, - "devnet_airdrop_max_lamports": 0 - }, - "demo": { - "live_demo_enabled": true, - "trading_demo_enabled": false - } - }, - { - "name": "mainnet", - "app": { - "name": "khadhroony-bot3", - "environment": "mainnet", - "auto_reconnect_default": false - }, - "database": { - "enabled": true, - "backend": "postgres", - "postgres": { - "url": "${KS_SECRET_POSTGRES_MAINNET_URL}", - "max_connections": 16, - "connect_timeout_ms": 5000, - "auto_initialize_schema": true - }, - "sqlite": { - "path": "data/mainnet.sqlite", - "create_if_missing": true, - "busy_timeout_ms": 5000, - "max_connections": 1, - "auto_initialize_schema": true, - "use_wal": true - } - }, - "data": { - "wallets_directory": "wallets", - "logs_directory": "logs" - }, - "solana": { - "http_endpoints": [ - { - "name": "mainnet_public_http", - "enabled": true, - "provider": "solana-public", - "cluster": "mainnet-beta", - "url": "https://api.mainnet.solana.com", - "connect_timeout_ms": 4000, - "request_timeout_ms": 10000, - "max_idle_connections_per_host": 4, - "roles": [ - { - "role": "http_queries", - "enabled": true, - "request_kinds": [ - "get_account_info", - "get_balance", - "get_block_commitment", - "get_block_height", - "get_block_production", - "get_blocks", - "get_blocks_with_limit", - "get_cluster_nodes", - "get_epoch_info", - "get_epoch_schedule", - "get_fee_for_message", - "get_first_available_block", - "get_genesis_hash", - "get_health", - "get_highest_snapshot_slot", - "get_identity", - "get_inflation_governor", - "get_inflation_rate", - "get_inflation_reward", - "get_largest_accounts", - "get_latest_blockhash", - "get_leader_schedule", - "get_max_retransmit_slot", - "get_max_shred_insert_slot", - "get_minimum_balance_for_rent_exemption", - "get_multiple_accounts", - "get_recent_performance_samples", - "get_recent_prioritization_fees", - "get_signature_statuses", - "get_slot", - "get_slot_leader", - "get_slot_leaders", - "get_stake_activation", - "get_stake_minimum_delegation", - "get_supply", - "get_token_account_balance", - "get_token_accounts_by_delegate", - "get_token_accounts_by_owner", - "get_token_largest_accounts", - "get_token_supply", - "get_transaction_count", - "get_version", - "get_vote_accounts", - "is_blockhash_valid", - "request_airdrop" - ], - "priority": 50, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "http_fallback", - "enabled": true, - "request_kinds": [ - "get_account_info", - "get_balance", - "get_block_commitment", - "get_block_height", - "get_block_production", - "get_blocks", - "get_blocks_with_limit", - "get_cluster_nodes", - "get_epoch_info", - "get_epoch_schedule", - "get_fee_for_message", - "get_first_available_block", - "get_genesis_hash", - "get_health", - "get_highest_snapshot_slot", - "get_identity", - "get_inflation_governor", - "get_inflation_rate", - "get_inflation_reward", - "get_largest_accounts", - "get_latest_blockhash", - "get_leader_schedule", - "get_max_retransmit_slot", - "get_max_shred_insert_slot", - "get_minimum_balance_for_rent_exemption", - "get_multiple_accounts", - "get_recent_performance_samples", - "get_recent_prioritization_fees", - "get_signature_statuses", - "get_slot", - "get_slot_leader", - "get_slot_leaders", - "get_stake_activation", - "get_stake_minimum_delegation", - "get_supply", - "get_token_account_balance", - "get_token_accounts_by_delegate", - "get_token_accounts_by_owner", - "get_token_largest_accounts", - "get_token_supply", - "get_transaction_count", - "get_version", - "get_vote_accounts", - "is_blockhash_valid", - "request_airdrop" - ], - "priority": 90, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 3000 - } - ] - }, - { - "name": "helius_primary_http", - "enabled": true, - "provider": "helius", - "cluster": "mainnet-beta", - "url": "https://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", - "connect_timeout_ms": 4000, - "request_timeout_ms": 10000, - "max_idle_connections_per_host": 8, - "roles": [ - { - "role": "http_queries", - "enabled": true, - "request_kinds": [ - "get_account_info", - "get_balance", - "get_block_commitment", - "get_block_height", - "get_block_production", - "get_blocks", - "get_blocks_with_limit", - "get_cluster_nodes", - "get_epoch_info", - "get_epoch_schedule", - "get_fee_for_message", - "get_first_available_block", - "get_genesis_hash", - "get_health", - "get_highest_snapshot_slot", - "get_identity", - "get_inflation_governor", - "get_inflation_rate", - "get_inflation_reward", - "get_largest_accounts", - "get_latest_blockhash", - "get_leader_schedule", - "get_max_retransmit_slot", - "get_max_shred_insert_slot", - "get_minimum_balance_for_rent_exemption", - "get_multiple_accounts", - "get_recent_performance_samples", - "get_recent_prioritization_fees", - "get_signature_statuses", - "get_slot", - "get_slot_leader", - "get_slot_leaders", - "get_stake_activation", - "get_stake_minimum_delegation", - "get_supply", - "get_token_account_balance", - "get_token_accounts_by_delegate", - "get_token_accounts_by_owner", - "get_token_largest_accounts", - "get_token_supply", - "get_transaction_count", - "get_version", - "get_vote_accounts", - "is_blockhash_valid", - "request_airdrop" - ], - "priority": 10, - "requests_per_second": 8, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 1500 - }, - { - "role": "http_heavy", - "enabled": true, - "request_kinds": [ - "get_block", - "get_program_accounts", - "get_signatures_for_address", - "get_transaction" - ], - "priority": 20, - "requests_per_second": 2, - "burst_capacity": 1, - "max_concurrent_requests": 2, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 1500 - }, - { - "role": "history_backfill", - "enabled": true, - "request_kinds": [ - "get_block", - "get_program_accounts", - "get_signatures_for_address", - "get_transaction" - ], - "priority": 30, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 1500 - }, - { - "role": "http_transactions", - "enabled": false, - "request_kinds": [ - "simulate_transaction", - "send_transaction", - "get_signature_statuses" - ], - "priority": 40, - "requests_per_second": 1, - "burst_capacity": 1, - "max_concurrent_requests": 1, - "max_subscriptions": 0, - "pause_after_rate_limit_ms": 3000 - } - ] - } - ], - "ws_endpoints": [ - { - "name": "mainnet_public_ws_slots", - "enabled": true, - "provider": "solana-public", - "cluster": "mainnet-beta", - "url": "wss://api.mainnet.solana.com", - "connect_timeout_ms": 5000, - "request_timeout_ms": 10000, - "unsubscribe_timeout_ms": 1500, - "write_channel_capacity": 256, - "event_channel_capacity": 1024, - "auto_reconnect": false, - "roles": [ - { - "role": "slot_notifications", - "enabled": true, - "request_kinds": [ - "slot_subscribe", - "slots_updates_subscribe" - ], - "priority": 10, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 16, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "root_notifications", - "enabled": true, - "request_kinds": [ - "root_subscribe" - ], - "priority": 20, - "requests_per_second": 4, - "burst_capacity": 8, - "max_concurrent_requests": 4, - "max_subscriptions": 16, - "pause_after_rate_limit_ms": 3000 - } - ] - }, - { - "name": "helius_primary_ws_programs", - "enabled": true, - "provider": "helius", - "cluster": "mainnet-beta", - "url": "wss://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", - "connect_timeout_ms": 5000, - "request_timeout_ms": 10000, - "unsubscribe_timeout_ms": 1500, - "write_channel_capacity": 512, - "event_channel_capacity": 4096, - "auto_reconnect": false, - "roles": [ - { - "role": "logs_subscribe", - "enabled": true, - "request_kinds": [ - "logs_subscribe_mentions" - ], - "priority": 10, - "requests_per_second": 2, - "burst_capacity": 4, - "max_concurrent_requests": 2, - "max_subscriptions": 128, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "program_subscribe", - "enabled": true, - "request_kinds": [ - "program_subscribe" - ], - "priority": 20, - "requests_per_second": 2, - "burst_capacity": 4, - "max_concurrent_requests": 2, - "max_subscriptions": 128, - "pause_after_rate_limit_ms": 3000 - }, - { - "role": "account_subscribe", - "enabled": true, - "request_kinds": [ - "account_subscribe" - ], - "priority": 30, - "requests_per_second": 2, - "burst_capacity": 4, - "max_concurrent_requests": 2, - "max_subscriptions": 128, - "pause_after_rate_limit_ms": 3000 - } - ] - } - ], - "listeners": { - "enabled": true, - "default_commitment": "confirmed", - "log_listeners": [ - { - "name": "pump_swap_logs", - "enabled": true, - "endpoint_role": "program_logs", - "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", - "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." - }, - { - "name": "raydium_cpmm_logs", - "enabled": true, - "endpoint_role": "program_logs", - "program_id": "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", - "purpose": "Détecter les créations de pools et swaps Raydium CPMM." - } - ], - "program_listeners": [ - { - "name": "pump_fun_program_accounts", - "enabled": true, - "endpoint_role": "program_logs", - "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", - "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." - } - ], - "account_listeners": [] - } - }, - "wallet": { - "wallet_dir": "wallets/temporary/mainnet", - "cluster": "mainnet-beta", - "temporary_wallet_enabled": false, - "temporary_wallet_alias": "mainnet-disabled", - "temporary_wallet_persist": false, - "localnet_send_enabled": false, - "devnet_send_enabled": false, - "testnet_send_enabled": false, - "mainnet_send_enabled": false - }, - "execution": { - "dry_run_default": true, - "require_simulation": true, - "require_operator_confirmation": true, - "localnet_max_spend_lamports": 0, - "devnet_max_spend_lamports": 0, - "testnet_max_spend_lamports": 0, - "mainnet_max_spend_lamports": 0, - "max_fee_lamports": 1000000, - "max_compute_unit_price_micro_lamports": 100000, - "recent_blockhash_max_age_slots": 150, - "send_max_retries": 3, - "confirmation_poll_interval_ms": 500, - "confirmation_max_attempts": 120, - "devnet_airdrop_max_lamports": 0 - }, - "demo": { - "live_demo_enabled": true, - "trading_demo_enabled": false - } - } - ] -} \ No newline at end of file diff --git a/config/example.kb-app-demo-desktop.default.config.json b/config/example.kb-app-demo-desktop.default.config.json new file mode 100644 index 0000000..ff51ed9 --- /dev/null +++ b/config/example.kb-app-demo-desktop.default.config.json @@ -0,0 +1,74 @@ +{ + "active_profile": "local_devnet", + "sources": { + "logging": "config/logging.config.json", + "transport": "config/transport.config.json", + "listeners": "config/listeners.config.json" + }, + "profiles": [ + { + "name": "local_devnet", + "app": { + "name": "khadhroony-bot3", + "environment": "development", + "auto_reconnect_default": false + }, + "logging_profile": "local_devnet", + "transport_profile": "local_devnet", + "listeners_profile": "local_devnet", + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_DEVNET_URL}", + "max_connections": 8, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/local.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "wallet": { + "wallet_dir": "wallets/temporary/local_devnet", + "cluster": "devnet", + "temporary_wallet_enabled": true, + "temporary_wallet_alias": "local-devnet-operator", + "temporary_wallet_persist": true, + "localnet_send_enabled": false, + "devnet_send_enabled": true, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 100000000, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 2000000000 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + } + ] +} diff --git a/config/example.listeners.config.json b/config/example.listeners.config.json new file mode 100644 index 0000000..b1a40a2 --- /dev/null +++ b/config/example.listeners.config.json @@ -0,0 +1,29 @@ +{ + "active_profile": "local_devnet", + "profiles": [ + { + "name": "local_devnet", + "enabled": true, + "default_commitment": "confirmed", + "log_listeners": [ + { + "name": "pump_swap_logs", + "enabled": false, + "endpoint_role": "program_logs", + "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." + } + ], + "program_listeners": [ + { + "name": "pump_fun_program_accounts", + "enabled": false, + "endpoint_role": "program_logs", + "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", + "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." + } + ], + "account_listeners": [] + } + ] +} diff --git a/config/example.transport.config.json b/config/example.transport.config.json new file mode 100644 index 0000000..230211a --- /dev/null +++ b/config/example.transport.config.json @@ -0,0 +1,157 @@ +{ + "active_profile": "local_devnet", + "profiles": [ + { + "name": "local_devnet", + "http_endpoints": [ + { + "name": "devnet_public_http", + "enabled": true, + "provider": "solana-public", + "cluster": "devnet", + "url": "https://api.devnet.solana.com", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 4, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 10, + "requests_per_second": 3, + "burst_capacity": 3, + "max_concurrent_requests": 2, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 10000 + }, + { + "role": "http_transactions", + "enabled": true, + "request_kinds": [ + "simulate_transaction", + "send_transaction", + "get_signature_statuses" + ], + "priority": 20, + "requests_per_second": 1, + "burst_capacity": 1, + "max_concurrent_requests": 1, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 10000 + } + ] + } + ], + "ws_endpoints": [ + { + "name": "devnet_public_ws", + "enabled": true, + "provider": "solana-public", + "cluster": "devnet", + "url": "wss://api.devnet.solana.com", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 256, + "event_channel_capacity": 1024, + "auto_reconnect": false, + "roles": [ + { + "role": "slot_notifications", + "enabled": true, + "request_kinds": [ + "slot_subscribe", + "slots_updates_subscribe" + ], + "priority": 10, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "program_logs", + "enabled": true, + "request_kinds": [ + "logs_subscribe_mentions", + "program_subscribe", + "account_subscribe", + "signature_subscribe", + "block_subscribe", + "vote_subscribe" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 64, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "root_notifications", + "enabled": true, + "request_kinds": [ + "root_subscribe" + ], + "priority": 15, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ] + } + ] +} diff --git a/config/kb-app-demo-desktop.default.config.json b/config/kb-app-demo-desktop.default.config.json new file mode 100644 index 0000000..7101a88 --- /dev/null +++ b/config/kb-app-demo-desktop.default.config.json @@ -0,0 +1,202 @@ +{ + "active_profile": "mainnet_research", + "sources": { + "logging": "config/logging.config.json", + "transport": "config/transport.config.json", + "listeners": "config/listeners.config.json" + }, + "profiles": [ + { + "name": "local_devnet", + "app": { + "name": "khadhroony-bot3", + "environment": "development", + "auto_reconnect_default": false + }, + "logging_profile": "local_devnet", + "transport_profile": "local_devnet", + "listeners_profile": "local_devnet", + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_DEVNET_URL}", + "max_connections": 8, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/local.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "wallet": { + "wallet_dir": "wallets/temporary/local_devnet", + "cluster": "devnet", + "temporary_wallet_enabled": true, + "temporary_wallet_alias": "local-devnet-operator", + "temporary_wallet_persist": true, + "localnet_send_enabled": false, + "devnet_send_enabled": true, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 100000000, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 2000000000 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + }, + { + "name": "mainnet_research", + "app": { + "name": "khadhroony-bot3", + "environment": "research", + "auto_reconnect_default": false + }, + "logging_profile": "mainnet_research", + "transport_profile": "mainnet_research", + "listeners_profile": "mainnet_research", + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_MAINNET_URL}", + "max_connections": 16, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/mainnet-import.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "wallet": { + "wallet_dir": "wallets/temporary/mainnet_research", + "cluster": "mainnet-beta", + "temporary_wallet_enabled": false, + "temporary_wallet_alias": "mainnet_research-disabled", + "temporary_wallet_persist": false, + "localnet_send_enabled": false, + "devnet_send_enabled": false, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 0, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 0 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + }, + { + "name": "mainnet", + "app": { + "name": "khadhroony-bot3", + "environment": "mainnet", + "auto_reconnect_default": false + }, + "logging_profile": "mainnet", + "transport_profile": "mainnet", + "listeners_profile": "mainnet", + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_MAINNET_URL}", + "max_connections": 16, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/mainnet.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "wallet": { + "wallet_dir": "wallets/temporary/mainnet", + "cluster": "mainnet-beta", + "temporary_wallet_enabled": false, + "temporary_wallet_alias": "mainnet-disabled", + "temporary_wallet_persist": false, + "localnet_send_enabled": false, + "devnet_send_enabled": false, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 0, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 0 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + } + ] +} diff --git a/config/ks-pipeline-demo-scenarios.default.config.json b/config/ks-pipeline-demo-scenarios.default.config.json new file mode 100644 index 0000000..97d9eaf --- /dev/null +++ b/config/ks-pipeline-demo-scenarios.default.config.json @@ -0,0 +1,202 @@ +{ + "active_profile": "local_devnet", + "sources": { + "logging": "config/logging.config.json", + "transport": "config/transport.config.json", + "listeners": "config/listeners.config.json" + }, + "profiles": [ + { + "name": "local_devnet", + "app": { + "name": "khadhroony-bot3", + "environment": "development", + "auto_reconnect_default": false + }, + "logging_profile": "local_devnet", + "transport_profile": "local_devnet", + "listeners_profile": "local_devnet", + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_DEVNET_URL}", + "max_connections": 8, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/local.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "wallet": { + "wallet_dir": "wallets/temporary/local_devnet", + "cluster": "devnet", + "temporary_wallet_enabled": true, + "temporary_wallet_alias": "local-devnet-operator", + "temporary_wallet_persist": true, + "localnet_send_enabled": false, + "devnet_send_enabled": true, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 100000000, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 2000000000 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + }, + { + "name": "mainnet_research", + "app": { + "name": "khadhroony-bot3", + "environment": "research", + "auto_reconnect_default": false + }, + "logging_profile": "mainnet_research", + "transport_profile": "mainnet_research", + "listeners_profile": "mainnet_research", + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_MAINNET_URL}", + "max_connections": 16, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/mainnet-import.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "wallet": { + "wallet_dir": "wallets/temporary/mainnet_research", + "cluster": "mainnet-beta", + "temporary_wallet_enabled": false, + "temporary_wallet_alias": "mainnet_research-disabled", + "temporary_wallet_persist": false, + "localnet_send_enabled": false, + "devnet_send_enabled": false, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 0, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 0 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + }, + { + "name": "mainnet", + "app": { + "name": "khadhroony-bot3", + "environment": "mainnet", + "auto_reconnect_default": false + }, + "logging_profile": "mainnet", + "transport_profile": "mainnet", + "listeners_profile": "mainnet", + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_MAINNET_URL}", + "max_connections": 16, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/mainnet.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "wallet": { + "wallet_dir": "wallets/temporary/mainnet", + "cluster": "mainnet-beta", + "temporary_wallet_enabled": false, + "temporary_wallet_alias": "mainnet-disabled", + "temporary_wallet_persist": false, + "localnet_send_enabled": false, + "devnet_send_enabled": false, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 0, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 0 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + } + ] +} diff --git a/config/listeners.config.json b/config/listeners.config.json new file mode 100644 index 0000000..0bf9388 --- /dev/null +++ b/config/listeners.config.json @@ -0,0 +1,91 @@ +{ + "active_profile": "mainnet_research", + "profiles": [ + { + "name": "local_devnet", + "enabled": true, + "default_commitment": "confirmed", + "log_listeners": [ + { + "name": "pump_swap_logs", + "enabled": false, + "endpoint_role": "program_logs", + "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." + } + ], + "program_listeners": [ + { + "name": "pump_fun_program_accounts", + "enabled": false, + "endpoint_role": "program_logs", + "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", + "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." + } + ], + "account_listeners": [] + }, + { + "name": "mainnet_research", + "enabled": true, + "default_commitment": "confirmed", + "log_listeners": [ + { + "name": "pump_swap_logs", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." + }, + { + "name": "raydium_cpmm_logs", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", + "purpose": "Détecter les créations de pools et swaps Raydium CPMM." + } + ], + "program_listeners": [ + { + "name": "pump_fun_program_accounts", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", + "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." + } + ], + "account_listeners": [] + }, + { + "name": "mainnet", + "enabled": true, + "default_commitment": "confirmed", + "log_listeners": [ + { + "name": "pump_swap_logs", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." + }, + { + "name": "raydium_cpmm_logs", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", + "purpose": "Détecter les créations de pools et swaps Raydium CPMM." + } + ], + "program_listeners": [ + { + "name": "pump_fun_program_accounts", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", + "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." + } + ], + "account_listeners": [] + } + ] +} diff --git a/config/schemas/composition.config.schema.json b/config/schemas/composition.config.schema.json new file mode 100644 index 0000000..c2fd311 --- /dev/null +++ b/config/schemas/composition.config.schema.json @@ -0,0 +1,366 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://khadhroony.local/schemas/composition.config.schema.json", + "title": "Khadhroony binary composition configuration", + "type": "object", + "additionalProperties": false, + "required": [ + "active_profile", + "sources", + "profiles" + ], + "properties": { + "active_profile": { + "$ref": "#/$defs/non_empty_string" + }, + "sources": { + "$ref": "#/$defs/sources" + }, + "profiles": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/profile" + } + } + }, + "$defs": { + "non_empty_string": { + "type": "string", + "minLength": 1 + }, + "app_section": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "environment", + "auto_reconnect_default" + ], + "properties": { + "name": { + "$ref": "#/$defs/non_empty_string" + }, + "environment": { + "$ref": "#/$defs/non_empty_string" + }, + "auto_reconnect_default": { + "type": "boolean" + } + } + }, + "database": { + "type": "object", + "additionalProperties": false, + "required": [ + "enabled", + "backend", + "postgres", + "sqlite" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "backend": { + "type": "string", + "enum": [ + "postgres", + "sqlite" + ] + }, + "postgres": { + "$ref": "#/$defs/postgres" + }, + "sqlite": { + "$ref": "#/$defs/sqlite" + } + } + }, + "postgres": { + "type": "object", + "additionalProperties": false, + "required": [ + "url", + "max_connections", + "connect_timeout_ms", + "auto_initialize_schema" + ], + "properties": { + "url": { + "$ref": "#/$defs/non_empty_string" + }, + "max_connections": { + "type": "integer", + "minimum": 1 + }, + "connect_timeout_ms": { + "type": "integer", + "minimum": 1 + }, + "auto_initialize_schema": { + "type": "boolean" + } + } + }, + "sqlite": { + "type": "object", + "additionalProperties": false, + "required": [ + "path", + "create_if_missing", + "busy_timeout_ms", + "max_connections", + "auto_initialize_schema", + "use_wal" + ], + "properties": { + "path": { + "$ref": "#/$defs/non_empty_string" + }, + "create_if_missing": { + "type": "boolean" + }, + "busy_timeout_ms": { + "type": "integer", + "minimum": 1 + }, + "max_connections": { + "type": "integer", + "minimum": 1 + }, + "auto_initialize_schema": { + "type": "boolean" + }, + "use_wal": { + "type": "boolean" + } + } + }, + "data": { + "type": "object", + "additionalProperties": false, + "required": [ + "wallets_directory", + "logs_directory" + ], + "properties": { + "wallets_directory": { + "$ref": "#/$defs/non_empty_string" + }, + "logs_directory": { + "$ref": "#/$defs/non_empty_string" + } + } + }, + "wallet": { + "type": "object", + "additionalProperties": false, + "required": [ + "wallet_dir", + "cluster", + "temporary_wallet_enabled", + "temporary_wallet_alias", + "temporary_wallet_persist", + "localnet_send_enabled", + "devnet_send_enabled", + "testnet_send_enabled", + "mainnet_send_enabled" + ], + "properties": { + "wallet_dir": { + "$ref": "#/$defs/non_empty_string" + }, + "cluster": { + "$ref": "#/$defs/non_empty_string" + }, + "temporary_wallet_enabled": { + "type": "boolean" + }, + "temporary_wallet_alias": { + "type": "string", + "minLength": 1, + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]*$" + }, + "temporary_wallet_persist": { + "type": "boolean" + }, + "localnet_send_enabled": { + "type": "boolean" + }, + "devnet_send_enabled": { + "type": "boolean" + }, + "testnet_send_enabled": { + "type": "boolean" + }, + "mainnet_send_enabled": { + "type": "boolean" + } + } + }, + "execution": { + "type": "object", + "additionalProperties": false, + "required": [ + "dry_run_default", + "require_simulation", + "require_operator_confirmation", + "localnet_max_spend_lamports", + "devnet_max_spend_lamports", + "testnet_max_spend_lamports", + "mainnet_max_spend_lamports", + "max_fee_lamports", + "max_compute_unit_price_micro_lamports", + "recent_blockhash_max_age_slots", + "send_max_retries", + "confirmation_poll_interval_ms", + "confirmation_max_attempts", + "devnet_airdrop_max_lamports" + ], + "properties": { + "dry_run_default": { + "type": "boolean" + }, + "require_simulation": { + "type": "boolean" + }, + "require_operator_confirmation": { + "type": "boolean" + }, + "localnet_max_spend_lamports": { + "type": "integer", + "minimum": 0 + }, + "devnet_max_spend_lamports": { + "type": "integer", + "minimum": 0 + }, + "testnet_max_spend_lamports": { + "type": "integer", + "minimum": 0 + }, + "mainnet_max_spend_lamports": { + "type": "integer", + "minimum": 0 + }, + "max_fee_lamports": { + "type": "integer", + "minimum": 1 + }, + "max_compute_unit_price_micro_lamports": { + "type": "integer", + "minimum": 0 + }, + "recent_blockhash_max_age_slots": { + "type": "integer", + "minimum": 1 + }, + "send_max_retries": { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + "confirmation_poll_interval_ms": { + "type": "integer", + "minimum": 1, + "maximum": 60000 + }, + "confirmation_max_attempts": { + "type": "integer", + "minimum": 1, + "maximum": 10000 + }, + "devnet_airdrop_max_lamports": { + "type": "integer", + "minimum": 0 + } + } + }, + "demo": { + "type": "object", + "additionalProperties": false, + "required": [ + "live_demo_enabled", + "trading_demo_enabled" + ], + "properties": { + "live_demo_enabled": { + "type": "boolean" + }, + "trading_demo_enabled": { + "type": "boolean" + } + } + }, + "sources": { + "type": "object", + "additionalProperties": false, + "required": [ + "logging", + "transport", + "listeners" + ], + "properties": { + "logging": { + "$ref": "#/$defs/non_empty_string" + }, + "transport": { + "$ref": "#/$defs/non_empty_string" + }, + "listeners": { + "$ref": "#/$defs/non_empty_string" + } + } + }, + "profile": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "app", + "logging_profile", + "transport_profile", + "listeners_profile", + "database", + "data", + "wallet", + "execution", + "demo" + ], + "properties": { + "name": { + "$ref": "#/$defs/non_empty_string" + }, + "app": { + "$ref": "#/$defs/app_section" + }, + "logging_profile": { + "$ref": "#/$defs/non_empty_string" + }, + "transport_profile": { + "$ref": "#/$defs/non_empty_string" + }, + "listeners_profile": { + "$ref": "#/$defs/non_empty_string" + }, + "database": { + "$ref": "#/$defs/database" + }, + "data": { + "$ref": "#/$defs/data" + }, + "wallet": { + "$ref": "#/$defs/wallet" + }, + "execution": { + "$ref": "#/$defs/execution" + }, + "demo": { + "$ref": "#/$defs/demo" + } + } + } + } +} diff --git a/config/schemas/listeners.config.schema.json b/config/schemas/listeners.config.schema.json new file mode 100644 index 0000000..32d4ca6 --- /dev/null +++ b/config/schemas/listeners.config.schema.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://khadhroony.local/schemas/listeners.config.schema.json", + "title": "Khadhroony Solana listener configuration", + "type": "object", + "additionalProperties": false, + "required": [ + "active_profile", + "profiles" + ], + "properties": { + "active_profile": { + "$ref": "#/$defs/non_empty_string" + }, + "profiles": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/profile" + } + } + }, + "$defs": { + "non_empty_string": { + "type": "string", + "minLength": 1 + }, + "log_listener": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "enabled", + "endpoint_role", + "program_id", + "purpose" + ], + "properties": { + "name": { + "$ref": "#/$defs/non_empty_string" + }, + "enabled": { + "type": "boolean" + }, + "endpoint_role": { + "$ref": "#/$defs/non_empty_string" + }, + "program_id": { + "$ref": "#/$defs/non_empty_string" + }, + "purpose": { + "$ref": "#/$defs/non_empty_string" + } + } + }, + "program_listener": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "enabled", + "endpoint_role", + "program_id", + "purpose" + ], + "properties": { + "name": { + "$ref": "#/$defs/non_empty_string" + }, + "enabled": { + "type": "boolean" + }, + "endpoint_role": { + "$ref": "#/$defs/non_empty_string" + }, + "program_id": { + "$ref": "#/$defs/non_empty_string" + }, + "purpose": { + "$ref": "#/$defs/non_empty_string" + } + } + }, + "account_listener": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "enabled", + "endpoint_role", + "account_pubkey", + "purpose" + ], + "properties": { + "name": { + "$ref": "#/$defs/non_empty_string" + }, + "enabled": { + "type": "boolean" + }, + "endpoint_role": { + "$ref": "#/$defs/non_empty_string" + }, + "account_pubkey": { + "$ref": "#/$defs/non_empty_string" + }, + "purpose": { + "$ref": "#/$defs/non_empty_string" + } + } + }, + "profile": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "enabled", + "default_commitment", + "log_listeners", + "program_listeners", + "account_listeners" + ], + "properties": { + "name": { + "$ref": "#/$defs/non_empty_string" + }, + "enabled": { + "type": "boolean" + }, + "default_commitment": { + "$ref": "#/$defs/non_empty_string" + }, + "log_listeners": { + "type": "array", + "items": { + "$ref": "#/$defs/log_listener" + } + }, + "program_listeners": { + "type": "array", + "items": { + "$ref": "#/$defs/program_listener" + } + }, + "account_listeners": { + "type": "array", + "items": { + "$ref": "#/$defs/account_listener" + } + } + } + } + } +} diff --git a/config/schemas/app.config.schema.json b/config/schemas/resolved.app.config.schema.json similarity index 98% rename from config/schemas/app.config.schema.json rename to config/schemas/resolved.app.config.schema.json index c8526fe..cdcae72 100644 --- a/config/schemas/app.config.schema.json +++ b/config/schemas/resolved.app.config.schema.json @@ -1,7 +1,7 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://khadhroony.local/schema/app.config.schema.json", - "title": "Khadhroony Solana application configuration", + "$id": "https://khadhroony.local/schemas/resolved.app.config.schema.json", + "title": "Khadhroony Solana resolved runtime application configuration", "type": "object", "additionalProperties": false, "required": [ diff --git a/config/schemas/transport.config.schema.json b/config/schemas/transport.config.schema.json new file mode 100644 index 0000000..d14726c --- /dev/null +++ b/config/schemas/transport.config.schema.json @@ -0,0 +1,227 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://khadhroony.local/schemas/transport.config.schema.json", + "title": "Khadhroony Solana transport configuration", + "type": "object", + "additionalProperties": false, + "required": [ + "active_profile", + "profiles" + ], + "properties": { + "active_profile": { + "$ref": "#/$defs/non_empty_string" + }, + "profiles": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/profile" + } + } + }, + "$defs": { + "non_empty_string": { + "type": "string", + "minLength": 1 + }, + "http_endpoint": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "enabled", + "provider", + "cluster", + "url", + "connect_timeout_ms", + "request_timeout_ms", + "max_idle_connections_per_host", + "roles" + ], + "properties": { + "name": { + "$ref": "#/$defs/non_empty_string" + }, + "enabled": { + "type": "boolean" + }, + "provider": { + "$ref": "#/$defs/non_empty_string" + }, + "cluster": { + "$ref": "#/$defs/non_empty_string" + }, + "url": { + "type": "string", + "pattern": "^https?://" + }, + "connect_timeout_ms": { + "type": "integer", + "minimum": 1 + }, + "request_timeout_ms": { + "type": "integer", + "minimum": 1 + }, + "max_idle_connections_per_host": { + "type": "integer", + "minimum": 1 + }, + "roles": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/endpoint_role" + } + } + } + }, + "ws_endpoint": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "enabled", + "provider", + "cluster", + "url", + "connect_timeout_ms", + "request_timeout_ms", + "unsubscribe_timeout_ms", + "write_channel_capacity", + "event_channel_capacity", + "auto_reconnect", + "roles" + ], + "properties": { + "name": { + "$ref": "#/$defs/non_empty_string" + }, + "enabled": { + "type": "boolean" + }, + "provider": { + "$ref": "#/$defs/non_empty_string" + }, + "cluster": { + "$ref": "#/$defs/non_empty_string" + }, + "url": { + "type": "string", + "pattern": "^wss?://" + }, + "connect_timeout_ms": { + "type": "integer", + "minimum": 1 + }, + "request_timeout_ms": { + "type": "integer", + "minimum": 1 + }, + "unsubscribe_timeout_ms": { + "type": "integer", + "minimum": 1 + }, + "write_channel_capacity": { + "type": "integer", + "minimum": 1 + }, + "event_channel_capacity": { + "type": "integer", + "minimum": 1 + }, + "auto_reconnect": { + "type": "boolean" + }, + "roles": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/endpoint_role" + } + } + } + }, + "endpoint_role": { + "type": "object", + "additionalProperties": false, + "required": [ + "role", + "enabled", + "request_kinds", + "priority", + "requests_per_second", + "burst_capacity", + "max_concurrent_requests", + "max_subscriptions", + "pause_after_rate_limit_ms" + ], + "properties": { + "role": { + "$ref": "#/$defs/non_empty_string" + }, + "enabled": { + "type": "boolean" + }, + "request_kinds": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/non_empty_string" + } + }, + "priority": { + "type": "integer" + }, + "requests_per_second": { + "type": "integer", + "minimum": 1 + }, + "burst_capacity": { + "type": "integer", + "minimum": 1 + }, + "max_concurrent_requests": { + "type": "integer", + "minimum": 1 + }, + "max_subscriptions": { + "type": "integer" + }, + "pause_after_rate_limit_ms": { + "type": "integer", + "minimum": 1 + } + } + }, + "profile": { + "type": "object", + "additionalProperties": false, + "required": [ + "name", + "http_endpoints", + "ws_endpoints" + ], + "properties": { + "name": { + "$ref": "#/$defs/non_empty_string" + }, + "http_endpoints": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/http_endpoint" + } + }, + "ws_endpoints": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/$defs/ws_endpoint" + } + } + } + } + } +} diff --git a/config/transport.config.json b/config/transport.config.json new file mode 100644 index 0000000..6ee40a9 --- /dev/null +++ b/config/transport.config.json @@ -0,0 +1,847 @@ +{ + "active_profile": "mainnet_research", + "profiles": [ + { + "name": "local_devnet", + "http_endpoints": [ + { + "name": "devnet_public_http", + "enabled": true, + "provider": "solana-public", + "cluster": "devnet", + "url": "https://api.devnet.solana.com", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 4, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 10, + "requests_per_second": 3, + "burst_capacity": 3, + "max_concurrent_requests": 2, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 10000 + }, + { + "role": "http_transactions", + "enabled": true, + "request_kinds": [ + "simulate_transaction", + "send_transaction", + "get_signature_statuses" + ], + "priority": 20, + "requests_per_second": 1, + "burst_capacity": 1, + "max_concurrent_requests": 1, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 10000 + } + ] + } + ], + "ws_endpoints": [ + { + "name": "devnet_public_ws", + "enabled": true, + "provider": "solana-public", + "cluster": "devnet", + "url": "wss://api.devnet.solana.com", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 256, + "event_channel_capacity": 1024, + "auto_reconnect": false, + "roles": [ + { + "role": "slot_notifications", + "enabled": true, + "request_kinds": [ + "slot_subscribe", + "slots_updates_subscribe" + ], + "priority": 10, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "program_logs", + "enabled": true, + "request_kinds": [ + "logs_subscribe_mentions", + "program_subscribe", + "account_subscribe", + "signature_subscribe", + "block_subscribe", + "vote_subscribe" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 64, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "root_notifications", + "enabled": true, + "request_kinds": [ + "root_subscribe" + ], + "priority": 15, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ] + }, + { + "name": "mainnet_research", + "http_endpoints": [ + { + "name": "mainnet_public_http", + "enabled": true, + "provider": "solana-public", + "cluster": "mainnet-beta", + "url": "https://api.mainnet.solana.com", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 4, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 50, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "http_fallback", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 90, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + } + ] + }, + { + "name": "helius_primary_http", + "enabled": true, + "provider": "helius", + "cluster": "mainnet-beta", + "url": "https://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 8, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 10, + "requests_per_second": 8, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "http_heavy", + "enabled": true, + "request_kinds": [ + "get_block", + "get_program_accounts", + "get_signatures_for_address", + "get_transaction" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 1, + "max_concurrent_requests": 2, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "history_backfill", + "enabled": true, + "request_kinds": [ + "get_block", + "get_program_accounts", + "get_signatures_for_address", + "get_transaction" + ], + "priority": 30, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "http_transactions", + "enabled": false, + "request_kinds": [ + "simulate_transaction", + "send_transaction", + "get_signature_statuses" + ], + "priority": 40, + "requests_per_second": 1, + "burst_capacity": 1, + "max_concurrent_requests": 1, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ], + "ws_endpoints": [ + { + "name": "mainnet_public_ws_slots", + "enabled": true, + "provider": "solana-public", + "cluster": "mainnet-beta", + "url": "wss://api.mainnet.solana.com", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 256, + "event_channel_capacity": 1024, + "auto_reconnect": false, + "roles": [ + { + "role": "slot_notifications", + "enabled": true, + "request_kinds": [ + "slot_subscribe", + "slots_updates_subscribe" + ], + "priority": 10, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "root_notifications", + "enabled": true, + "request_kinds": [ + "root_subscribe" + ], + "priority": 20, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + } + ] + }, + { + "name": "helius_primary_ws_programs", + "enabled": true, + "provider": "helius", + "cluster": "mainnet-beta", + "url": "wss://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 512, + "event_channel_capacity": 4096, + "auto_reconnect": false, + "roles": [ + { + "role": "logs_subscribe", + "enabled": true, + "request_kinds": [ + "logs_subscribe_mentions" + ], + "priority": 10, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "program_subscribe", + "enabled": true, + "request_kinds": [ + "program_subscribe" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "account_subscribe", + "enabled": true, + "request_kinds": [ + "account_subscribe" + ], + "priority": 30, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ] + }, + { + "name": "mainnet", + "http_endpoints": [ + { + "name": "mainnet_public_http", + "enabled": true, + "provider": "solana-public", + "cluster": "mainnet-beta", + "url": "https://api.mainnet.solana.com", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 4, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 50, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "http_fallback", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 90, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + } + ] + }, + { + "name": "helius_primary_http", + "enabled": true, + "provider": "helius", + "cluster": "mainnet-beta", + "url": "https://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 8, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 10, + "requests_per_second": 8, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "http_heavy", + "enabled": true, + "request_kinds": [ + "get_block", + "get_program_accounts", + "get_signatures_for_address", + "get_transaction" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 1, + "max_concurrent_requests": 2, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "history_backfill", + "enabled": true, + "request_kinds": [ + "get_block", + "get_program_accounts", + "get_signatures_for_address", + "get_transaction" + ], + "priority": 30, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "http_transactions", + "enabled": false, + "request_kinds": [ + "simulate_transaction", + "send_transaction", + "get_signature_statuses" + ], + "priority": 40, + "requests_per_second": 1, + "burst_capacity": 1, + "max_concurrent_requests": 1, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ], + "ws_endpoints": [ + { + "name": "mainnet_public_ws_slots", + "enabled": true, + "provider": "solana-public", + "cluster": "mainnet-beta", + "url": "wss://api.mainnet.solana.com", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 256, + "event_channel_capacity": 1024, + "auto_reconnect": false, + "roles": [ + { + "role": "slot_notifications", + "enabled": true, + "request_kinds": [ + "slot_subscribe", + "slots_updates_subscribe" + ], + "priority": 10, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "root_notifications", + "enabled": true, + "request_kinds": [ + "root_subscribe" + ], + "priority": 20, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + } + ] + }, + { + "name": "helius_primary_ws_programs", + "enabled": true, + "provider": "helius", + "cluster": "mainnet-beta", + "url": "wss://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 512, + "event_channel_capacity": 4096, + "auto_reconnect": false, + "roles": [ + { + "role": "logs_subscribe", + "enabled": true, + "request_kinds": [ + "logs_subscribe_mentions" + ], + "priority": 10, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "program_subscribe", + "enabled": true, + "request_kinds": [ + "program_subscribe" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "account_subscribe", + "enabled": true, + "request_kinds": [ + "account_subscribe" + ], + "priority": 30, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ] + } + ] +} diff --git a/docs/architecture/ARCHITECTURE.md b/docs/architecture/ARCHITECTURE.md index 2b58bf1..4a2825e 100644 --- a/docs/architecture/ARCHITECTURE.md +++ b/docs/architecture/ARCHITECTURE.md @@ -1,5 +1,5 @@ - + # Architecture générale @@ -29,7 +29,7 @@ Cette représentation indique les relations dominantes. Elle ne remplace pas le ### 2.1 Fondations - `ks-core` fournit les erreurs et identités transversales minimales. -- `ks-config` charge, résout et valide la configuration. +- `ks-config` charge, résout et valide les compositions propres aux binaires ainsi que les documents de configuration Solana partagés. - `ks-logging` initialise le logging et le tracing. - `ks-program-ids` centralise les identifiants de programmes et comptes connus. diff --git a/docs/architecture/CRATE_MAP.md b/docs/architecture/CRATE_MAP.md index 5da6f12..c34afca 100644 --- a/docs/architecture/CRATE_MAP.md +++ b/docs/architecture/CRATE_MAP.md @@ -1,5 +1,5 @@ - + # Carte des crates @@ -8,7 +8,7 @@ | Crate | Type | Responsabilité principale | État documentaire | |------------------------------|------------------------|--------------------------------------------------------------------|------------------------------------------| | `ks-core` | bibliothèque | erreurs, résultat et identité de module partagés | README/TODO/USAGE/CHANGELOG présents | -| `ks-config` | bibliothèque | configuration JSON, environnement, validation et profils | migration/restructuration en `0.5.1` | +| `ks-config` | bibliothèque | compositions binaires, documents JSON partagés, environnement, validation et profils | migration/restructuration en `0.5.1` | | `ks-lib` | bibliothèque | modèles, décodeurs, exécuteurs et matérialisateurs consolidés | README/TODO/USAGE/CHANGELOG présents | | `ks-logging` | bibliothèque | initialisation du logging et du tracing | README/TODO/USAGE/CHANGELOG présents | | `ks-program-ids` | bibliothèque | registre des programmes et comptes Solana connus | README/TODO/USAGE/CHANGELOG présents | diff --git a/docs/architecture/PIPELINE_ARCHITECTURE.md b/docs/architecture/PIPELINE_ARCHITECTURE.md index 96bbe60..82758d2 100644 --- a/docs/architecture/PIPELINE_ARCHITECTURE.md +++ b/docs/architecture/PIPELINE_ARCHITECTURE.md @@ -1,5 +1,5 @@ - + # Architecture du pipeline @@ -35,7 +35,7 @@ Le pipeline assemble les contrôles préalables, plans préparés, signataires, ks-onchain-transport -> acquisition et appels RPC ks-store -> lecture/écriture canonique et replay ks-lib -> contrats et implémentations métier -ks-config -> profils et paramètres opérationnels +ks-config -> compositions binaires, profils partagés et paramètres opérationnels ks-logging -> observabilité ks-wallet -> signataires lorsque requis ``` diff --git a/docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md b/docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md index 3bf605d..2e03150 100644 --- a/docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md +++ b/docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md @@ -1,5 +1,5 @@ - + # Politique de namespace Khadhroony Solana @@ -63,19 +63,25 @@ La classification d'une valeur sensible doit survivre à la substitution. Une ch ## 5. Configuration source, runtime et publique -La restructuration `0.5.1` doit séparer au minimum : +La restructuration `0.5.1` utilise des compositions propres aux binaires et des documents spécialisés partagés : -- la configuration généraliste dans `config/app.config.json`, avec son schéma sous `config/schemas/app.config.schema.json` ; -- la configuration logging dans `config/logging.config.json`, avec son schéma sous `config/schemas/logging.config.schema.json` et des profils logging sélectionnables indépendamment des profils réseau/applicatifs ; -- des exemples conformes mais non chargés par défaut sous `config/example.app.config.json` et `config/example.logging.config.json`. +- `config/kb-app-demo-desktop.default.config.json` compose les besoins du desktop ; +- `config/ks-pipeline-demo-scenarios.default.config.json` compose les besoins du CLI/scénarios ; +- `config/logging.config.json` appartient à `ks-logging` ; +- `config/transport.config.json` contient les profils HTTP/WebSocket partagés ; +- `config/listeners.config.json` contient les profils de subscriptions Solana partagés ; +- tous les schémas actifs résident sous `config/schemas/`. -D'autres documents spécialisés ne sont créés que si l'audit démontre une responsabilité, un cycle de vie ou une validation réellement indépendants. +Le fichier de composition sélectionne explicitement les profils spécialisés. Un nouveau worker ou binaire doit pouvoir fournir son propre `.default.config.json` et une configuration dédiée sans modifier `ks-config` pour enregistrer son existence. + +`AppConfig/ProfileConfig` reste temporairement un contrat runtime résolu afin de préserver les consommateurs pendant la migration. Il n'est plus un document source chargé directement ; son schéma de compatibilité est `config/schemas/resolved.app.config.schema.json` et ses fixtures sont sous `test-fixtures/config/`. La conception doit distinguer : -1. la représentation source, qui peut contenir des références `${KS_*}` ou `${KB_*}` selon le propriétaire du contrat ; -2. la représentation runtime résolue, qui peut porter des secrets ; -3. la représentation publique/diagnostique, construite explicitement et incapable d'exposer une valeur secrète résolue. +1. la représentation source, composée de documents indépendants pouvant contenir des références `${KS_*}` ou `${KB_*}` selon le propriétaire du contrat ; +2. la composition propre au binaire, qui choisit les documents et profils ; +3. la représentation runtime résolue, qui peut porter des secrets ; +4. la représentation publique/diagnostique, construite explicitement et incapable d'exposer une valeur secrète résolue. Une configuration runtime complète ne doit jamais être sérialisée puis « nettoyée » après coup pour produire un payload public. diff --git a/docs/guides/CONFIGURATION.md b/docs/guides/CONFIGURATION.md index f5efb13..af4326c 100644 --- a/docs/guides/CONFIGURATION.md +++ b/docs/guides/CONFIGURATION.md @@ -1,95 +1,134 @@ - + # Guide de configuration ## Objectif -Ce guide décrit le chargement des documents de configuration après leur séparation. La référence d’API générale reste `ks-config/USAGE.md` et la configuration logging appartient à `ks-logging`. +La configuration est organisée autour de compositions propres aux binaires et de documents spécialisés réutilisables. Un binaire choisit les fichiers partagés et les profils dont il a besoin au lieu de recopier une configuration monolithique. ## Fichiers actifs -- `config/app.config.json` : configuration générale chargée par défaut ; -- `config/logging.config.json` : configuration logging chargée par défaut ; -- `config/example.app.config.json` et `config/example.logging.config.json` : exemples minimaux conformes ; -- `config/schemas/app.config.schema.json` : schéma général ; -- `config/schemas/logging.config.schema.json` : schéma logging ; -- `.env`, ou le fichier sélectionné par `KS_ENV_FILE` : valeurs d’environnement non versionnées ; -- `.env.example` : noms de variables attendues sans secrets réels. +Compositions : -`KS_CONFIG_PATH` et `KS_LOGGING_CONFIG_PATH` permettent de remplacer indépendamment les deux chemins par défaut. +- `config/kb-app-demo-desktop.default.config.json` : composition par défaut du desktop ; +- `config/ks-pipeline-demo-scenarios.default.config.json` : composition par défaut du CLI/scénarios. -## Configuration générale +Documents partagés : -L’API recommandée reste `ks_config::read_config_json_file_with_environment`. Elle : +- `config/logging.config.json` ; +- `config/transport.config.json` ; +- `config/listeners.config.json`. -1. charge `.env`, ou le fichier sélectionné par `KS_ENV_FILE`, sans écraser les variables du processus ; -2. lit le JSON général ; -3. résout les placeholders `${KS_*}` / `${KB_*}` ; -4. valide `config/schemas/app.config.schema.json` ; -5. désérialise `AppConfig` et applique les invariants métier ; -6. laisse `active_profile` sélectionner le profil applicatif actif. +Exemples : -```rust -let config = match ks_config::read_config_json_file_with_environment( - std::path::Path::new("config/app.config.json"), - std::path::Path::new("."), -) { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => return std::result::Result::Err(error), -}; -let profile = match ks_config::active_profile(&config) { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => return std::result::Result::Err(error), -}; +- `config/example.kb-app-demo-desktop.default.config.json` ; +- `config/example.logging.config.json` ; +- `config/example.transport.config.json` ; +- `config/example.listeners.config.json`. + +Schémas : + +- `config/schemas/composition.config.schema.json` ; +- `config/schemas/logging.config.schema.json` ; +- `config/schemas/transport.config.schema.json` ; +- `config/schemas/listeners.config.schema.json` ; +- `config/schemas/resolved.app.config.schema.json` pour le contrat runtime reconstruit. + +`.env`, ou le fichier sélectionné par `KS_ENV_FILE`, fournit les valeurs non versionnées. + +## Composition d'un binaire + +Une composition définit : + +1. son `active_profile` ; +2. les chemins des documents partagés ; +3. pour chaque profil, le `logging_profile`, `transport_profile` et `listeners_profile` à sélectionner ; +4. temporairement, les sections qui n'ont pas encore leur document spécialisé. + +Exemple conceptuel : + +```text +kb-app-demo-desktop.default.config.json + sources.logging -> config/logging.config.json + sources.transport -> config/transport.config.json + sources.listeners -> config/listeners.config.json + + profile mainnet_research + logging_profile -> mainnet_research + transport_profile -> mainnet_research + listeners_profile -> mainnet_research ``` -Le profil général ne contient plus de bloc `logging`. +Les `active_profile` propres aux documents partagés restent utiles lorsqu'ils sont chargés seuls. Dans une composition, la référence explicite du binaire prime. -## Configuration logging +## Desktop -`ks_logging::read_logging_json_file_with_environment` charge séparément le document logging et `ks_logging::active_logging_profile` sélectionne son profil actif. +Le chemin de composition du desktop peut être remplacé par : -```rust -let logging = match ks_logging::read_logging_json_file_with_environment( - std::path::Path::new("config/logging.config.json"), - std::path::Path::new("."), -) { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => return std::result::Result::Err(error), -}; -let logging_profile = match ks_logging::active_logging_profile(&logging) { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => return std::result::Result::Err(error), -}; +```text +KB_APP_DEMO_DESKTOP_CONFIG_PATH ``` -Les deux `active_profile` sont indépendants. Il n’existe aucun fallback implicite du profil logging vers le nom du profil applicatif. +Le logging conserve l'override opérationnel : + +```text +KS_LOGGING_CONFIG_PATH +``` + +Sans cet override, le desktop utilise le chemin logging déclaré par sa composition. + +## Scénarios Devnet + +Les scénarios opt-in utilisent : + +```text +KS_DEVNET_CONFIG_PATH +KS_DEVNET_PROFILE +``` + +`KS_DEVNET_CONFIG_PATH` désigne désormais un fichier de composition. Sans override, les scénarios utilisent `config/ks-pipeline-demo-scenarios.default.config.json`. + +## Transport + +`transport.config.json` possède les endpoints HTTP et WebSocket ainsi que leurs rôles et limites. Le profil transport peut être consommé directement par `ks-onchain-transport` sans dépendre d'un profil applicatif complet. + +Cela prépare les futurs workers : un worker de capture pourra sélectionner un profil transport différent du desktop tout en partageant le même document. + +## Listeners + +`listeners.config.json` contient les déclarations de subscriptions par logs, programme et compte. Un profil listeners peut être associé à n'importe quel profil transport compatible par le fichier de composition du consommateur. + +La séparation évite de recopier les listes de `program_id` et les filtres lorsque plusieurs binaires observent la même surface Solana. + +## Logging + +`logging.config.json` appartient à `ks-logging`. Une composition référence son fichier et son profil, mais les types et validations logging ne reviennent pas dans `ks-config`. + +## Contrat runtime résolu + +Pendant la migration `0.5.1`, `ks-config` reconstruit encore un `AppConfig/ProfileConfig` contenant les sections nécessaires aux consommateurs existants. Ce contrat est une projection runtime, pas une configuration source. + +Les tests de compatibilité utilisent les fixtures sous `test-fixtures/config/`. Aucun binaire ne doit charger ces fixtures en production. + +## Étapes suivantes + +Le prochain split doit extraire : + +- database vers le document store ; +- `logs_directory` vers logging ; +- `wallets_directory`, le stockage wallet et les paramètres de wallet vers un document wallet ; +- les permissions `*_send_enabled` du wallet vers la politique execution ; +- execution ; +- configuration spécifique au desktop. + +Après cette étape seulement, les DTO publics, diagnostics et règles de camouflage seront construits sur les nouvelles frontières. ## Invariants -- aucun secret réel ne doit être ajouté aux fichiers versionnés ; -- les placeholders non résolus doivent rester détectables ; -- chaque schéma embarqué doit rester identique au fichier sous `config/schemas/` ; -- les profils applicatifs et logging sont validés indépendamment ; -- `ks-config` ne possède plus de types logging ; -- tant que `0.5.1-pre.006` n’a pas introduit les DTO sûrs, la configuration runtime résolue reste strictement backend et ne doit pas être exposée telle quelle. - -## Diagnostic - -Pour isoler une erreur : - -1. vérifier le fichier d’environnement chargé ; -2. vérifier séparément les chemins app et logging ; -3. valider chaque document contre son schéma ; -4. vérifier son `active_profile` ; -5. vérifier ensuite les rôles HTTP/WebSocket, stockage ou routes logging concernés. - -Ne jamais écrire dans les logs le JSON résolu complet s’il contient une valeur issue de `KS_SECRET_*` ou `KB_SECRET_*`. - -## Références - -- `ks-config/README.md` et `ks-config/USAGE.md` ; -- `ks-logging/README.md` et `ks-logging/USAGE.md` ; -- `config/README.md` ; -- `docs/decisions/KHADHROONY_SOLANA_NAMESPACE_POLICY.md`. +- aucun secret réel dans les fichiers versionnés ; +- tous les schémas actifs sous `config/schemas/` ; +- aucun retour au fichier monolithique `app.config.json` ; +- un fichier de composition appartient à son binaire ; +- un document spécialisé reste indépendant des binaires qui le consomment ; +- `ks-config` fournit les mécanismes partagés sans enregistrer une liste fermée de workers/applications. diff --git a/docs/guides/LOGGING.md b/docs/guides/LOGGING.md index 4567bfb..c702688 100644 --- a/docs/guides/LOGGING.md +++ b/docs/guides/LOGGING.md @@ -1,5 +1,5 @@ - + # Guide de logging et tracing @@ -33,7 +33,7 @@ let guard = match ks_logging::init_logging(config) { }; ``` -Le profil logging est sélectionné indépendamment du profil général de `app.config.json`. +Le document logging reste indépendant de la composition binaire. Une composition `.default.config.json` sélectionne explicitement le profil logging qu’elle veut utiliser ; le `active_profile` propre à `logging.config.json` reste disponible pour les consommateurs autonomes. ## Contrat JSON diff --git a/docs/plans/V0_5_1_KHADHROONY_SOLANA_NAMESPACE_AND_CONFIG_PLAN.md b/docs/plans/V0_5_1_KHADHROONY_SOLANA_NAMESPACE_AND_CONFIG_PLAN.md index b9eb9cb..797ec80 100644 --- a/docs/plans/V0_5_1_KHADHROONY_SOLANA_NAMESPACE_AND_CONFIG_PLAN.md +++ b/docs/plans/V0_5_1_KHADHROONY_SOLANA_NAMESPACE_AND_CONFIG_PLAN.md @@ -1,5 +1,5 @@ - + # Plan temporaire `0.5.1` — namespace Khadhroony Solana et configuration sûre @@ -32,18 +32,18 @@ Les bibliothèques internes Solana utilisent `ks-*` / `ks_*` et leurs variables ## 4. Inventaire des crates et ordre de migration -| Package actuel | Identifiant Rust actuel | Package cible | Identifiant Rust cible | Dépendances workspace directes actuelles | Ordre | -|---|---|---|---|---|---:| -| `kb-core` | `kb_core` | `ks-core` | `ks_core` | — | 1 | -| `kb-program-ids` | `kb_program_ids` | `ks-program-ids` | `ks_program_ids` | — | 2 | -| `kb-config` | `kb_config` | `ks-config` | `ks_config` | kb-core | 3 | -| `kb-logging` | `kb_logging` | `ks-logging` | `ks_logging` | kb-core | 4 | -| `kb-wallet` | `kb_wallet` | `ks-wallet` | `ks_wallet` | kb-core | 5 | -| `kb-lib` | `kb_lib` | `ks-lib` | `ks_lib` | kb-core, kb-program-ids | 6 | -| `kb-onchain-transport` | `kb_onchain_transport` | `ks-onchain-transport` | `ks_onchain_transport` | kb-config, kb-core, kb-lib | 7 | -| `kb-store` | `kb_store` | `ks-store` | `ks_store` | kb-core, kb-lib | 8 | -| `kb-pipeline` | `kb_pipeline` | `ks-pipeline` | `ks_pipeline` | kb-core, kb-config, kb-lib, kb-onchain-transport, kb-program-ids, kb-store | 9 | -| `kb-pipeline-demo-scenarios` | `kb_pipeline_demo_scenarios` | `ks-pipeline-demo-scenarios` | `ks_pipeline_demo_scenarios` | kb-core, kb-config, kb-lib, kb-onchain-transport, kb-pipeline, kb-program-ids, kb-store, kb-wallet | 10 | +| Package actuel | Identifiant Rust actuel | Package cible | Identifiant Rust cible | Dépendances workspace directes actuelles | Ordre | +|------------------------------|------------------------------|------------------------------|------------------------------|----------------------------------------------------------------------------------------------------|------:| +| `kb-core` | `kb_core` | `ks-core` | `ks_core` | — | 1 | +| `kb-program-ids` | `kb_program_ids` | `ks-program-ids` | `ks_program_ids` | — | 2 | +| `kb-config` | `kb_config` | `ks-config` | `ks_config` | kb-core | 3 | +| `kb-logging` | `kb_logging` | `ks-logging` | `ks_logging` | kb-core | 4 | +| `kb-wallet` | `kb_wallet` | `ks-wallet` | `ks_wallet` | kb-core | 5 | +| `kb-lib` | `kb_lib` | `ks-lib` | `ks_lib` | kb-core, kb-program-ids | 6 | +| `kb-onchain-transport` | `kb_onchain_transport` | `ks-onchain-transport` | `ks_onchain_transport` | kb-config, kb-core, kb-lib | 7 | +| `kb-store` | `kb_store` | `ks-store` | `ks_store` | kb-core, kb-lib | 8 | +| `kb-pipeline` | `kb_pipeline` | `ks-pipeline` | `ks_pipeline` | kb-core, kb-config, kb-lib, kb-onchain-transport, kb-program-ids, kb-store | 9 | +| `kb-pipeline-demo-scenarios` | `kb_pipeline_demo_scenarios` | `ks-pipeline-demo-scenarios` | `ks_pipeline_demo_scenarios` | kb-core, kb-config, kb-lib, kb-onchain-transport, kb-pipeline, kb-program-ids, kb-store, kb-wallet | 10 | `kb-app-demo-desktop` est adapté en dernier, mais garde son package, son répertoire et ses identifiants applicatifs. @@ -58,18 +58,18 @@ bin : kb-pipeline-demo-scenarios-cli -> ks-pipeline-demo-scenarios-cli Les valeurs suivantes sont des métriques d'orientation sur les fichiers actifs, hors archives et artefacts générés. Elles montrent qu'un renommage massif en une seule opération serait difficile à diagnostiquer. -| Crate | Références package / fichiers | Références identifiant Rust / fichiers | -|---|---:|---:| -| `kb-core` | 51 / 32 | 3165 / 407 | -| `kb-config` | 56 / 33 | 201 / 44 | -| `kb-lib` | 3145 / 550 | 2785 / 97 | -| `kb-logging` | 72 / 27 | 22 / 3 | -| `kb-program-ids` | 42 / 28 | 1413 / 334 | -| `kb-pipeline` | 287 / 112 | 436 / 38 | -| `kb-pipeline-demo-scenarios` | 145 / 75 | 173 / 18 | -| `kb-onchain-transport` | 125 / 56 | 530 / 50 | -| `kb-store` | 157 / 81 | 344 / 30 | -| `kb-wallet` | 75 / 30 | 53 / 17 | +| Crate | Références package / fichiers | Références identifiant Rust / fichiers | +|------------------------------|------------------------------:|---------------------------------------:| +| `kb-core` | 51 / 32 | 3165 / 407 | +| `kb-config` | 56 / 33 | 201 / 44 | +| `kb-lib` | 3145 / 550 | 2785 / 97 | +| `kb-logging` | 72 / 27 | 22 / 3 | +| `kb-program-ids` | 42 / 28 | 1413 / 334 | +| `kb-pipeline` | 287 / 112 | 436 / 38 | +| `kb-pipeline-demo-scenarios` | 145 / 75 | 173 / 18 | +| `kb-onchain-transport` | 125 / 56 | 530 / 50 | +| `kb-store` | 157 / 81 | 344 / 30 | +| `kb-wallet` | 75 / 30 | 53 / 17 | Le volume `kb-lib` inclut notamment les identités techniques `kb-lib.*`; il ne doit pas être interprété comme autant d'importations Cargo. @@ -145,264 +145,264 @@ Les targets de tracing de crates génériques doivent également migrer vers `ks ### 6.1 Inventaire exhaustif des identités `kb-lib.*` -| Identité actuelle | Identité cible | -|---|---| -| `kb-lib.decoder.adapter.saber_decimal_wrapper` | `ks-lib-decoder.adapter.saber_decimal_wrapper` | -| `kb-lib.decoder.adapter.spl_token_wrap` | `ks-lib-decoder.adapter.spl_token_wrap` | -| `kb-lib.decoder.admin.jupiter_lock` | `ks-lib-decoder.admin.jupiter_lock` | -| `kb-lib.decoder.admin.pump_fees` | `ks-lib-decoder.admin.pump_fees` | -| `kb-lib.decoder.amm.aldrin_v1` | `ks-lib-decoder.amm.aldrin_v1` | -| `kb-lib.decoder.amm.aldrin_v2` | `ks-lib-decoder.amm.aldrin_v2` | -| `kb-lib.decoder.amm.alphaq` | `ks-lib-decoder.amm.alphaq` | -| `kb-lib.decoder.amm.believe` | `ks-lib-decoder.amm.believe` | -| `kb-lib.decoder.amm.bonk_swap` | `ks-lib-decoder.amm.bonk_swap` | -| `kb-lib.decoder.amm.fluxbeam` | `ks-lib-decoder.amm.fluxbeam` | -| `kb-lib.decoder.amm.goon_fi` | `ks-lib-decoder.amm.goon_fi` | -| `kb-lib.decoder.amm.goosefx_gamma` | `ks-lib-decoder.amm.goosefx_gamma` | -| `kb-lib.decoder.amm.goosefx_v2` | `ks-lib-decoder.amm.goosefx_v2` | -| `kb-lib.decoder.amm.guac_swap` | `ks-lib-decoder.amm.guac_swap` | -| `kb-lib.decoder.amm.lifinity_swap_v2` | `ks-lib-decoder.amm.lifinity_swap_v2` | -| `kb-lib.decoder.amm.metadao_futarchy_amm` | `ks-lib-decoder.amm.metadao_futarchy_amm` | -| `kb-lib.decoder.amm.metadao_v0_5` | `ks-lib-decoder.amm.metadao_v0_5` | -| `kb-lib.decoder.amm.meteora_damm_v1` | `ks-lib-decoder.amm.meteora_damm_v1` | -| `kb-lib.decoder.amm.meteora_damm_v2` | `ks-lib-decoder.amm.meteora_damm_v2` | -| `kb-lib.decoder.amm.obric_v2` | `ks-lib-decoder.amm.obric_v2` | -| `kb-lib.decoder.amm.one_dex` | `ks-lib-decoder.amm.one_dex` | -| `kb-lib.decoder.amm.pump_swap` | `ks-lib-decoder.amm.pump_swap` | -| `kb-lib.decoder.amm.raydium_lp_v4` | `ks-lib-decoder.amm.raydium_lp_v4` | -| `kb-lib.decoder.amm.solfi` | `ks-lib-decoder.amm.solfi` | -| `kb-lib.decoder.amm.solfi_v2` | `ks-lib-decoder.amm.solfi_v2` | -| `kb-lib.decoder.amm.vertigo` | `ks-lib-decoder.amm.vertigo` | -| `kb-lib.decoder.amm.virtuals` | `ks-lib-decoder.amm.virtuals` | -| `kb-lib.decoder.amm.woofi` | `ks-lib-decoder.amm.woofi` | -| `kb-lib.decoder.amm.zero_fi` | `ks-lib-decoder.amm.zero_fi` | -| `kb-lib.decoder.amm.zora` | `ks-lib-decoder.amm.zora` | -| `kb-lib.decoder.anchor` | `ks-lib-decoder.anchor` | -| `kb-lib.decoder.api` | `ks-lib-decoder.api` | -| `kb-lib.decoder.bridge.circle_cctp_token_messenger_minter` | `ks-lib-decoder.bridge.circle_cctp_token_messenger_minter` | -| `kb-lib.decoder.bridge.circle_cctp_token_messenger_minter_v2` | `ks-lib-decoder.bridge.circle_cctp_token_messenger_minter_v2` | -| `kb-lib.decoder.bridge.layer_zero_endpoint` | `ks-lib-decoder.bridge.layer_zero_endpoint` | -| `kb-lib.decoder.bridge.layer_zero_executor` | `ks-lib-decoder.bridge.layer_zero_executor` | -| `kb-lib.decoder.clmm.byreal` | `ks-lib-decoder.clmm.byreal` | -| `kb-lib.decoder.clmm.fusion` | `ks-lib-decoder.clmm.fusion` | -| `kb-lib.decoder.clmm.orca_whirlpool` | `ks-lib-decoder.clmm.orca_whirlpool` | -| `kb-lib.decoder.clmm.pancake_swap` | `ks-lib-decoder.clmm.pancake_swap` | -| `kb-lib.decoder.clmm.raydium` | `ks-lib-decoder.clmm.raydium` | -| `kb-lib.decoder.clmm.stabble` | `ks-lib-decoder.clmm.stabble` | -| `kb-lib.decoder.cpmm.raydium` | `ks-lib-decoder.cpmm.raydium` | -| `kb-lib.decoder.dlmm.meteora` | `ks-lib-decoder.dlmm.meteora` | -| `kb-lib.decoder.fees.bags_fee_share_v1` | `ks-lib-decoder.fees.bags_fee_share_v1` | -| `kb-lib.decoder.fees.bags_fee_share_v2` | `ks-lib-decoder.fees.bags_fee_share_v2` | -| `kb-lib.decoder.fees.pump_fees` | `ks-lib-decoder.fees.pump_fees` | -| `kb-lib.decoder.governance.metadao_bid_wall` | `ks-lib-decoder.governance.metadao_bid_wall` | -| `kb-lib.decoder.governance.metadao_futarchy` | `ks-lib-decoder.governance.metadao_futarchy` | -| `kb-lib.decoder.governance.squads_multisig` | `ks-lib-decoder.governance.squads_multisig` | -| `kb-lib.decoder.launchpad.boop_fun` | `ks-lib-decoder.launchpad.boop_fun` | -| `kb-lib.decoder.launchpad.metadao_ico` | `ks-lib-decoder.launchpad.metadao_ico` | -| `kb-lib.decoder.launchpad.meteora_dbc` | `ks-lib-decoder.launchpad.meteora_dbc` | -| `kb-lib.decoder.launchpad.moonit` | `ks-lib-decoder.launchpad.moonit` | -| `kb-lib.decoder.launchpad.orca_wavebreak` | `ks-lib-decoder.launchpad.orca_wavebreak` | -| `kb-lib.decoder.launchpad.printr` | `ks-lib-decoder.launchpad.printr` | -| `kb-lib.decoder.launchpad.pump_fun` | `ks-lib-decoder.launchpad.pump_fun` | -| `kb-lib.decoder.launchpad.pump_pumpup_ai` | `ks-lib-decoder.launchpad.pump_pumpup_ai` | -| `kb-lib.decoder.launchpad.raydium_launchlab` | `ks-lib-decoder.launchpad.raydium_launchlab` | -| `kb-lib.decoder.launchpad.virtuals` | `ks-lib-decoder.launchpad.virtuals` | -| `kb-lib.decoder.lending.clone` | `ks-lib-decoder.lending.clone` | -| `kb-lib.decoder.lending.jupiter_lend_borrow` | `ks-lib-decoder.lending.jupiter_lend_borrow` | -| `kb-lib.decoder.lending.jupiter_lend_earn` | `ks-lib-decoder.lending.jupiter_lend_earn` | -| `kb-lib.decoder.lending.jupiter_lend_flash_loan` | `ks-lib-decoder.lending.jupiter_lend_flash_loan` | -| `kb-lib.decoder.lending.jupiter_lend_liquidity` | `ks-lib-decoder.lending.jupiter_lend_liquidity` | -| `kb-lib.decoder.lending.kamino` | `ks-lib-decoder.lending.kamino` | -| `kb-lib.decoder.lending.marginfi_v2` | `ks-lib-decoder.lending.marginfi_v2` | -| `kb-lib.decoder.lock.raydium_lp` | `ks-lib-decoder.lock.raydium_lp` | -| `kb-lib.decoder.metadata.metaplex_token_metadata` | `ks-lib-decoder.metadata.metaplex_token_metadata` | -| `kb-lib.decoder.metadata.solana_program_metadata` | `ks-lib-decoder.metadata.solana_program_metadata` | -| `kb-lib.decoder.metadata.spl_name_service` | `ks-lib-decoder.metadata.spl_name_service` | -| `kb-lib.decoder.nft.metaplex_bubblegum` | `ks-lib-decoder.nft.metaplex_bubblegum` | -| `kb-lib.decoder.nft.tensor_cnft` | `ks-lib-decoder.nft.tensor_cnft` | -| `kb-lib.decoder.orderbook.jupiter_limit_order` | `ks-lib-decoder.orderbook.jupiter_limit_order` | -| `kb-lib.decoder.orderbook.jupiter_limit_order_v2` | `ks-lib-decoder.orderbook.jupiter_limit_order_v2` | -| `kb-lib.decoder.orderbook.openbook_v2` | `ks-lib-decoder.orderbook.openbook_v2` | -| `kb-lib.decoder.perpetuals.drift_v2` | `ks-lib-decoder.perpetuals.drift_v2` | -| `kb-lib.decoder.perpetuals.jupiter` | `ks-lib-decoder.perpetuals.jupiter` | -| `kb-lib.decoder.perpetuals.phoenix_eternal` | `ks-lib-decoder.perpetuals.phoenix_eternal` | -| `kb-lib.decoder.perpetuals.zeta` | `ks-lib-decoder.perpetuals.zeta` | -| `kb-lib.decoder.router.dflow_aggregator_v4` | `ks-lib-decoder.router.dflow_aggregator_v4` | -| `kb-lib.decoder.router.jupiter_aggregator_v4` | `ks-lib-decoder.router.jupiter_aggregator_v4` | -| `kb-lib.decoder.router.jupiter_aggregator_v6` | `ks-lib-decoder.router.jupiter_aggregator_v6` | -| `kb-lib.decoder.router.jupiter_dca` | `ks-lib-decoder.router.jupiter_dca` | -| `kb-lib.decoder.router.okx_labs_v1` | `ks-lib-decoder.router.okx_labs_v1` | -| `kb-lib.decoder.router.okx_labs_v2` | `ks-lib-decoder.router.okx_labs_v2` | -| `kb-lib.decoder.rwa.ondo_global_markets` | `ks-lib-decoder.rwa.ondo_global_markets` | -| `kb-lib.decoder.solana.core` | `ks-lib-decoder.solana.core` | -| `kb-lib.decoder.spl.account_compression` | `ks-lib-decoder.spl.account_compression` | -| `kb-lib.decoder.spl.associated_token_account` | `ks-lib-decoder.spl.associated_token_account` | -| `kb-lib.decoder.spl.elgamal_registry` | `ks-lib-decoder.spl.elgamal_registry` | -| `kb-lib.decoder.spl.memo` | `ks-lib-decoder.spl.memo` | -| `kb-lib.decoder.spl.noop` | `ks-lib-decoder.spl.noop` | -| `kb-lib.decoder.spl.single_pool` | `ks-lib-decoder.spl.single_pool` | -| `kb-lib.decoder.spl.stake_pool` | `ks-lib-decoder.spl.stake_pool` | -| `kb-lib.decoder.spl.token` | `ks-lib-decoder.spl.token` | -| `kb-lib.decoder.spl.token_2022` | `ks-lib-decoder.spl.token_2022` | -| `kb-lib.decoder.stable.swap_hylo_exchange` | `ks-lib-decoder.stable.swap_hylo_exchange` | -| `kb-lib.decoder.stable.swap_jupiter_stable` | `ks-lib-decoder.stable.swap_jupiter_stable` | -| `kb-lib.decoder.stable.swap_numeraire` | `ks-lib-decoder.stable.swap_numeraire` | -| `kb-lib.decoder.stable.swap_stabble` | `ks-lib-decoder.stable.swap_stabble` | -| `kb-lib.decoder.staking.jito_tip_distribution` | `ks-lib-decoder.staking.jito_tip_distribution` | -| `kb-lib.decoder.staking.kamino_farm` | `ks-lib-decoder.staking.kamino_farm` | -| `kb-lib.decoder.staking.marinade_finance` | `ks-lib-decoder.staking.marinade_finance` | -| `kb-lib.decoder.staking.solayer` | `ks-lib-decoder.staking.solayer` | -| `kb-lib.decoder.storage.solana_record` | `ks-lib-decoder.storage.solana_record` | -| `kb-lib.decoder.strategy.jupiter_dca` | `ks-lib-decoder.strategy.jupiter_dca` | -| `kb-lib.decoder.treasury.helium_treasury_management` | `ks-lib-decoder.treasury.helium_treasury_management` | -| `kb-lib.decoder.vault.carrot_defi` | `ks-lib-decoder.vault.carrot_defi` | -| `kb-lib.decoder.vault.hylo_stability_pool` | `ks-lib-decoder.vault.hylo_stability_pool` | -| `kb-lib.decoder.vault.kamino` | `ks-lib-decoder.vault.kamino` | -| `kb-lib.decoder.vault.kamino_v2` | `ks-lib-decoder.vault.kamino_v2` | -| `kb-lib.decoder.vault.kamino_yvaults` | `ks-lib-decoder.vault.kamino_yvaults` | -| `kb-lib.decoder.vault.meteora` | `ks-lib-decoder.vault.meteora` | -| `kb-lib.decoder.vesting.jupiter_lock` | `ks-lib-decoder.vesting.jupiter_lock` | -| `kb-lib.decoder.vesting.streamflow` | `ks-lib-decoder.vesting.streamflow` | -| `kb-lib.decoder.wallet.jupiter_apepro_smart_wallet` | `ks-lib-decoder.wallet.jupiter_apepro_smart_wallet` | -| `kb-lib.decoder.weighted.swap_stabble` | `ks-lib-decoder.weighted.swap_stabble` | -| `kb-lib.executor.adapter.saber_decimal_wrapper` | `ks-lib-executor.adapter.saber_decimal_wrapper` | -| `kb-lib.executor.adapter.spl_token_wrap` | `ks-lib-executor.adapter.spl_token_wrap` | -| `kb-lib.executor.amm.aldrin_v1` | `ks-lib-executor.amm.aldrin_v1` | -| `kb-lib.executor.amm.aldrin_v2` | `ks-lib-executor.amm.aldrin_v2` | -| `kb-lib.executor.amm.alphaq` | `ks-lib-executor.amm.alphaq` | -| `kb-lib.executor.amm.believe` | `ks-lib-executor.amm.believe` | -| `kb-lib.executor.amm.bonk_swap` | `ks-lib-executor.amm.bonk_swap` | -| `kb-lib.executor.amm.fluxbeam` | `ks-lib-executor.amm.fluxbeam` | -| `kb-lib.executor.amm.goon_fi` | `ks-lib-executor.amm.goon_fi` | -| `kb-lib.executor.amm.goosefx_gamma` | `ks-lib-executor.amm.goosefx_gamma` | -| `kb-lib.executor.amm.goosefx_v2` | `ks-lib-executor.amm.goosefx_v2` | -| `kb-lib.executor.amm.guac_swap` | `ks-lib-executor.amm.guac_swap` | -| `kb-lib.executor.amm.lifinity_swap_v2` | `ks-lib-executor.amm.lifinity_swap_v2` | -| `kb-lib.executor.amm.metadao_v0_5` | `ks-lib-executor.amm.metadao_v0_5` | -| `kb-lib.executor.amm.meteora_damm_v1` | `ks-lib-executor.amm.meteora_damm_v1` | -| `kb-lib.executor.amm.meteora_damm_v2` | `ks-lib-executor.amm.meteora_damm_v2` | -| `kb-lib.executor.amm.obric_v2` | `ks-lib-executor.amm.obric_v2` | -| `kb-lib.executor.amm.one_dex` | `ks-lib-executor.amm.one_dex` | -| `kb-lib.executor.amm.pump_swap` | `ks-lib-executor.amm.pump_swap` | -| `kb-lib.executor.amm.raydium_lp_v4` | `ks-lib-executor.amm.raydium_lp_v4` | -| `kb-lib.executor.amm.solfi` | `ks-lib-executor.amm.solfi` | -| `kb-lib.executor.amm.solfi_v2` | `ks-lib-executor.amm.solfi_v2` | -| `kb-lib.executor.amm.vertigo` | `ks-lib-executor.amm.vertigo` | -| `kb-lib.executor.amm.woofi` | `ks-lib-executor.amm.woofi` | -| `kb-lib.executor.amm.zero_fi` | `ks-lib-executor.amm.zero_fi` | -| `kb-lib.executor.amm.zora` | `ks-lib-executor.amm.zora` | -| `kb-lib.executor.bridge.circle_cctp_token_messenger_minter` | `ks-lib-executor.bridge.circle_cctp_token_messenger_minter` | +| Identité actuelle | Identité cible | +|----------------------------------------------------------------|----------------------------------------------------------------| +| `kb-lib.decoder.adapter.saber_decimal_wrapper` | `ks-lib-decoder.adapter.saber_decimal_wrapper` | +| `kb-lib.decoder.adapter.spl_token_wrap` | `ks-lib-decoder.adapter.spl_token_wrap` | +| `kb-lib.decoder.admin.jupiter_lock` | `ks-lib-decoder.admin.jupiter_lock` | +| `kb-lib.decoder.admin.pump_fees` | `ks-lib-decoder.admin.pump_fees` | +| `kb-lib.decoder.amm.aldrin_v1` | `ks-lib-decoder.amm.aldrin_v1` | +| `kb-lib.decoder.amm.aldrin_v2` | `ks-lib-decoder.amm.aldrin_v2` | +| `kb-lib.decoder.amm.alphaq` | `ks-lib-decoder.amm.alphaq` | +| `kb-lib.decoder.amm.believe` | `ks-lib-decoder.amm.believe` | +| `kb-lib.decoder.amm.bonk_swap` | `ks-lib-decoder.amm.bonk_swap` | +| `kb-lib.decoder.amm.fluxbeam` | `ks-lib-decoder.amm.fluxbeam` | +| `kb-lib.decoder.amm.goon_fi` | `ks-lib-decoder.amm.goon_fi` | +| `kb-lib.decoder.amm.goosefx_gamma` | `ks-lib-decoder.amm.goosefx_gamma` | +| `kb-lib.decoder.amm.goosefx_v2` | `ks-lib-decoder.amm.goosefx_v2` | +| `kb-lib.decoder.amm.guac_swap` | `ks-lib-decoder.amm.guac_swap` | +| `kb-lib.decoder.amm.lifinity_swap_v2` | `ks-lib-decoder.amm.lifinity_swap_v2` | +| `kb-lib.decoder.amm.metadao_futarchy_amm` | `ks-lib-decoder.amm.metadao_futarchy_amm` | +| `kb-lib.decoder.amm.metadao_v0_5` | `ks-lib-decoder.amm.metadao_v0_5` | +| `kb-lib.decoder.amm.meteora_damm_v1` | `ks-lib-decoder.amm.meteora_damm_v1` | +| `kb-lib.decoder.amm.meteora_damm_v2` | `ks-lib-decoder.amm.meteora_damm_v2` | +| `kb-lib.decoder.amm.obric_v2` | `ks-lib-decoder.amm.obric_v2` | +| `kb-lib.decoder.amm.one_dex` | `ks-lib-decoder.amm.one_dex` | +| `kb-lib.decoder.amm.pump_swap` | `ks-lib-decoder.amm.pump_swap` | +| `kb-lib.decoder.amm.raydium_lp_v4` | `ks-lib-decoder.amm.raydium_lp_v4` | +| `kb-lib.decoder.amm.solfi` | `ks-lib-decoder.amm.solfi` | +| `kb-lib.decoder.amm.solfi_v2` | `ks-lib-decoder.amm.solfi_v2` | +| `kb-lib.decoder.amm.vertigo` | `ks-lib-decoder.amm.vertigo` | +| `kb-lib.decoder.amm.virtuals` | `ks-lib-decoder.amm.virtuals` | +| `kb-lib.decoder.amm.woofi` | `ks-lib-decoder.amm.woofi` | +| `kb-lib.decoder.amm.zero_fi` | `ks-lib-decoder.amm.zero_fi` | +| `kb-lib.decoder.amm.zora` | `ks-lib-decoder.amm.zora` | +| `kb-lib.decoder.anchor` | `ks-lib-decoder.anchor` | +| `kb-lib.decoder.api` | `ks-lib-decoder.api` | +| `kb-lib.decoder.bridge.circle_cctp_token_messenger_minter` | `ks-lib-decoder.bridge.circle_cctp_token_messenger_minter` | +| `kb-lib.decoder.bridge.circle_cctp_token_messenger_minter_v2` | `ks-lib-decoder.bridge.circle_cctp_token_messenger_minter_v2` | +| `kb-lib.decoder.bridge.layer_zero_endpoint` | `ks-lib-decoder.bridge.layer_zero_endpoint` | +| `kb-lib.decoder.bridge.layer_zero_executor` | `ks-lib-decoder.bridge.layer_zero_executor` | +| `kb-lib.decoder.clmm.byreal` | `ks-lib-decoder.clmm.byreal` | +| `kb-lib.decoder.clmm.fusion` | `ks-lib-decoder.clmm.fusion` | +| `kb-lib.decoder.clmm.orca_whirlpool` | `ks-lib-decoder.clmm.orca_whirlpool` | +| `kb-lib.decoder.clmm.pancake_swap` | `ks-lib-decoder.clmm.pancake_swap` | +| `kb-lib.decoder.clmm.raydium` | `ks-lib-decoder.clmm.raydium` | +| `kb-lib.decoder.clmm.stabble` | `ks-lib-decoder.clmm.stabble` | +| `kb-lib.decoder.cpmm.raydium` | `ks-lib-decoder.cpmm.raydium` | +| `kb-lib.decoder.dlmm.meteora` | `ks-lib-decoder.dlmm.meteora` | +| `kb-lib.decoder.fees.bags_fee_share_v1` | `ks-lib-decoder.fees.bags_fee_share_v1` | +| `kb-lib.decoder.fees.bags_fee_share_v2` | `ks-lib-decoder.fees.bags_fee_share_v2` | +| `kb-lib.decoder.fees.pump_fees` | `ks-lib-decoder.fees.pump_fees` | +| `kb-lib.decoder.governance.metadao_bid_wall` | `ks-lib-decoder.governance.metadao_bid_wall` | +| `kb-lib.decoder.governance.metadao_futarchy` | `ks-lib-decoder.governance.metadao_futarchy` | +| `kb-lib.decoder.governance.squads_multisig` | `ks-lib-decoder.governance.squads_multisig` | +| `kb-lib.decoder.launchpad.boop_fun` | `ks-lib-decoder.launchpad.boop_fun` | +| `kb-lib.decoder.launchpad.metadao_ico` | `ks-lib-decoder.launchpad.metadao_ico` | +| `kb-lib.decoder.launchpad.meteora_dbc` | `ks-lib-decoder.launchpad.meteora_dbc` | +| `kb-lib.decoder.launchpad.moonit` | `ks-lib-decoder.launchpad.moonit` | +| `kb-lib.decoder.launchpad.orca_wavebreak` | `ks-lib-decoder.launchpad.orca_wavebreak` | +| `kb-lib.decoder.launchpad.printr` | `ks-lib-decoder.launchpad.printr` | +| `kb-lib.decoder.launchpad.pump_fun` | `ks-lib-decoder.launchpad.pump_fun` | +| `kb-lib.decoder.launchpad.pump_pumpup_ai` | `ks-lib-decoder.launchpad.pump_pumpup_ai` | +| `kb-lib.decoder.launchpad.raydium_launchlab` | `ks-lib-decoder.launchpad.raydium_launchlab` | +| `kb-lib.decoder.launchpad.virtuals` | `ks-lib-decoder.launchpad.virtuals` | +| `kb-lib.decoder.lending.clone` | `ks-lib-decoder.lending.clone` | +| `kb-lib.decoder.lending.jupiter_lend_borrow` | `ks-lib-decoder.lending.jupiter_lend_borrow` | +| `kb-lib.decoder.lending.jupiter_lend_earn` | `ks-lib-decoder.lending.jupiter_lend_earn` | +| `kb-lib.decoder.lending.jupiter_lend_flash_loan` | `ks-lib-decoder.lending.jupiter_lend_flash_loan` | +| `kb-lib.decoder.lending.jupiter_lend_liquidity` | `ks-lib-decoder.lending.jupiter_lend_liquidity` | +| `kb-lib.decoder.lending.kamino` | `ks-lib-decoder.lending.kamino` | +| `kb-lib.decoder.lending.marginfi_v2` | `ks-lib-decoder.lending.marginfi_v2` | +| `kb-lib.decoder.lock.raydium_lp` | `ks-lib-decoder.lock.raydium_lp` | +| `kb-lib.decoder.metadata.metaplex_token_metadata` | `ks-lib-decoder.metadata.metaplex_token_metadata` | +| `kb-lib.decoder.metadata.solana_program_metadata` | `ks-lib-decoder.metadata.solana_program_metadata` | +| `kb-lib.decoder.metadata.spl_name_service` | `ks-lib-decoder.metadata.spl_name_service` | +| `kb-lib.decoder.nft.metaplex_bubblegum` | `ks-lib-decoder.nft.metaplex_bubblegum` | +| `kb-lib.decoder.nft.tensor_cnft` | `ks-lib-decoder.nft.tensor_cnft` | +| `kb-lib.decoder.orderbook.jupiter_limit_order` | `ks-lib-decoder.orderbook.jupiter_limit_order` | +| `kb-lib.decoder.orderbook.jupiter_limit_order_v2` | `ks-lib-decoder.orderbook.jupiter_limit_order_v2` | +| `kb-lib.decoder.orderbook.openbook_v2` | `ks-lib-decoder.orderbook.openbook_v2` | +| `kb-lib.decoder.perpetuals.drift_v2` | `ks-lib-decoder.perpetuals.drift_v2` | +| `kb-lib.decoder.perpetuals.jupiter` | `ks-lib-decoder.perpetuals.jupiter` | +| `kb-lib.decoder.perpetuals.phoenix_eternal` | `ks-lib-decoder.perpetuals.phoenix_eternal` | +| `kb-lib.decoder.perpetuals.zeta` | `ks-lib-decoder.perpetuals.zeta` | +| `kb-lib.decoder.router.dflow_aggregator_v4` | `ks-lib-decoder.router.dflow_aggregator_v4` | +| `kb-lib.decoder.router.jupiter_aggregator_v4` | `ks-lib-decoder.router.jupiter_aggregator_v4` | +| `kb-lib.decoder.router.jupiter_aggregator_v6` | `ks-lib-decoder.router.jupiter_aggregator_v6` | +| `kb-lib.decoder.router.jupiter_dca` | `ks-lib-decoder.router.jupiter_dca` | +| `kb-lib.decoder.router.okx_labs_v1` | `ks-lib-decoder.router.okx_labs_v1` | +| `kb-lib.decoder.router.okx_labs_v2` | `ks-lib-decoder.router.okx_labs_v2` | +| `kb-lib.decoder.rwa.ondo_global_markets` | `ks-lib-decoder.rwa.ondo_global_markets` | +| `kb-lib.decoder.solana.core` | `ks-lib-decoder.solana.core` | +| `kb-lib.decoder.spl.account_compression` | `ks-lib-decoder.spl.account_compression` | +| `kb-lib.decoder.spl.associated_token_account` | `ks-lib-decoder.spl.associated_token_account` | +| `kb-lib.decoder.spl.elgamal_registry` | `ks-lib-decoder.spl.elgamal_registry` | +| `kb-lib.decoder.spl.memo` | `ks-lib-decoder.spl.memo` | +| `kb-lib.decoder.spl.noop` | `ks-lib-decoder.spl.noop` | +| `kb-lib.decoder.spl.single_pool` | `ks-lib-decoder.spl.single_pool` | +| `kb-lib.decoder.spl.stake_pool` | `ks-lib-decoder.spl.stake_pool` | +| `kb-lib.decoder.spl.token` | `ks-lib-decoder.spl.token` | +| `kb-lib.decoder.spl.token_2022` | `ks-lib-decoder.spl.token_2022` | +| `kb-lib.decoder.stable.swap_hylo_exchange` | `ks-lib-decoder.stable.swap_hylo_exchange` | +| `kb-lib.decoder.stable.swap_jupiter_stable` | `ks-lib-decoder.stable.swap_jupiter_stable` | +| `kb-lib.decoder.stable.swap_numeraire` | `ks-lib-decoder.stable.swap_numeraire` | +| `kb-lib.decoder.stable.swap_stabble` | `ks-lib-decoder.stable.swap_stabble` | +| `kb-lib.decoder.staking.jito_tip_distribution` | `ks-lib-decoder.staking.jito_tip_distribution` | +| `kb-lib.decoder.staking.kamino_farm` | `ks-lib-decoder.staking.kamino_farm` | +| `kb-lib.decoder.staking.marinade_finance` | `ks-lib-decoder.staking.marinade_finance` | +| `kb-lib.decoder.staking.solayer` | `ks-lib-decoder.staking.solayer` | +| `kb-lib.decoder.storage.solana_record` | `ks-lib-decoder.storage.solana_record` | +| `kb-lib.decoder.strategy.jupiter_dca` | `ks-lib-decoder.strategy.jupiter_dca` | +| `kb-lib.decoder.treasury.helium_treasury_management` | `ks-lib-decoder.treasury.helium_treasury_management` | +| `kb-lib.decoder.vault.carrot_defi` | `ks-lib-decoder.vault.carrot_defi` | +| `kb-lib.decoder.vault.hylo_stability_pool` | `ks-lib-decoder.vault.hylo_stability_pool` | +| `kb-lib.decoder.vault.kamino` | `ks-lib-decoder.vault.kamino` | +| `kb-lib.decoder.vault.kamino_v2` | `ks-lib-decoder.vault.kamino_v2` | +| `kb-lib.decoder.vault.kamino_yvaults` | `ks-lib-decoder.vault.kamino_yvaults` | +| `kb-lib.decoder.vault.meteora` | `ks-lib-decoder.vault.meteora` | +| `kb-lib.decoder.vesting.jupiter_lock` | `ks-lib-decoder.vesting.jupiter_lock` | +| `kb-lib.decoder.vesting.streamflow` | `ks-lib-decoder.vesting.streamflow` | +| `kb-lib.decoder.wallet.jupiter_apepro_smart_wallet` | `ks-lib-decoder.wallet.jupiter_apepro_smart_wallet` | +| `kb-lib.decoder.weighted.swap_stabble` | `ks-lib-decoder.weighted.swap_stabble` | +| `kb-lib.executor.adapter.saber_decimal_wrapper` | `ks-lib-executor.adapter.saber_decimal_wrapper` | +| `kb-lib.executor.adapter.spl_token_wrap` | `ks-lib-executor.adapter.spl_token_wrap` | +| `kb-lib.executor.amm.aldrin_v1` | `ks-lib-executor.amm.aldrin_v1` | +| `kb-lib.executor.amm.aldrin_v2` | `ks-lib-executor.amm.aldrin_v2` | +| `kb-lib.executor.amm.alphaq` | `ks-lib-executor.amm.alphaq` | +| `kb-lib.executor.amm.believe` | `ks-lib-executor.amm.believe` | +| `kb-lib.executor.amm.bonk_swap` | `ks-lib-executor.amm.bonk_swap` | +| `kb-lib.executor.amm.fluxbeam` | `ks-lib-executor.amm.fluxbeam` | +| `kb-lib.executor.amm.goon_fi` | `ks-lib-executor.amm.goon_fi` | +| `kb-lib.executor.amm.goosefx_gamma` | `ks-lib-executor.amm.goosefx_gamma` | +| `kb-lib.executor.amm.goosefx_v2` | `ks-lib-executor.amm.goosefx_v2` | +| `kb-lib.executor.amm.guac_swap` | `ks-lib-executor.amm.guac_swap` | +| `kb-lib.executor.amm.lifinity_swap_v2` | `ks-lib-executor.amm.lifinity_swap_v2` | +| `kb-lib.executor.amm.metadao_v0_5` | `ks-lib-executor.amm.metadao_v0_5` | +| `kb-lib.executor.amm.meteora_damm_v1` | `ks-lib-executor.amm.meteora_damm_v1` | +| `kb-lib.executor.amm.meteora_damm_v2` | `ks-lib-executor.amm.meteora_damm_v2` | +| `kb-lib.executor.amm.obric_v2` | `ks-lib-executor.amm.obric_v2` | +| `kb-lib.executor.amm.one_dex` | `ks-lib-executor.amm.one_dex` | +| `kb-lib.executor.amm.pump_swap` | `ks-lib-executor.amm.pump_swap` | +| `kb-lib.executor.amm.raydium_lp_v4` | `ks-lib-executor.amm.raydium_lp_v4` | +| `kb-lib.executor.amm.solfi` | `ks-lib-executor.amm.solfi` | +| `kb-lib.executor.amm.solfi_v2` | `ks-lib-executor.amm.solfi_v2` | +| `kb-lib.executor.amm.vertigo` | `ks-lib-executor.amm.vertigo` | +| `kb-lib.executor.amm.woofi` | `ks-lib-executor.amm.woofi` | +| `kb-lib.executor.amm.zero_fi` | `ks-lib-executor.amm.zero_fi` | +| `kb-lib.executor.amm.zora` | `ks-lib-executor.amm.zora` | +| `kb-lib.executor.bridge.circle_cctp_token_messenger_minter` | `ks-lib-executor.bridge.circle_cctp_token_messenger_minter` | | `kb-lib.executor.bridge.circle_cctp_token_messenger_minter_v2` | `ks-lib-executor.bridge.circle_cctp_token_messenger_minter_v2` | -| `kb-lib.executor.bridge.layer_zero_endpoint` | `ks-lib-executor.bridge.layer_zero_endpoint` | -| `kb-lib.executor.bridge.layer_zero_executor` | `ks-lib-executor.bridge.layer_zero_executor` | -| `kb-lib.executor.clmm.byreal` | `ks-lib-executor.clmm.byreal` | -| `kb-lib.executor.clmm.fusion` | `ks-lib-executor.clmm.fusion` | -| `kb-lib.executor.clmm.orca_whirlpool` | `ks-lib-executor.clmm.orca_whirlpool` | -| `kb-lib.executor.clmm.pancake_swap` | `ks-lib-executor.clmm.pancake_swap` | -| `kb-lib.executor.clmm.raydium` | `ks-lib-executor.clmm.raydium` | -| `kb-lib.executor.clmm.stabble` | `ks-lib-executor.clmm.stabble` | -| `kb-lib.executor.cpmm.raydium` | `ks-lib-executor.cpmm.raydium` | -| `kb-lib.executor.dlmm.meteora` | `ks-lib-executor.dlmm.meteora` | -| `kb-lib.executor.fees.bags_fee_share_v1` | `ks-lib-executor.fees.bags_fee_share_v1` | -| `kb-lib.executor.fees.bags_fee_share_v2` | `ks-lib-executor.fees.bags_fee_share_v2` | -| `kb-lib.executor.fees.pump_fees` | `ks-lib-executor.fees.pump_fees` | -| `kb-lib.executor.governance.metadao_bid_wall` | `ks-lib-executor.governance.metadao_bid_wall` | -| `kb-lib.executor.governance.metadao_futarchy` | `ks-lib-executor.governance.metadao_futarchy` | -| `kb-lib.executor.governance.squads_multisig` | `ks-lib-executor.governance.squads_multisig` | -| `kb-lib.executor.launchpad.boop_fun` | `ks-lib-executor.launchpad.boop_fun` | -| `kb-lib.executor.launchpad.metadao_ico` | `ks-lib-executor.launchpad.metadao_ico` | -| `kb-lib.executor.launchpad.meteora_dbc` | `ks-lib-executor.launchpad.meteora_dbc` | -| `kb-lib.executor.launchpad.moonit` | `ks-lib-executor.launchpad.moonit` | -| `kb-lib.executor.launchpad.orca_wavebreak` | `ks-lib-executor.launchpad.orca_wavebreak` | -| `kb-lib.executor.launchpad.printr` | `ks-lib-executor.launchpad.printr` | -| `kb-lib.executor.launchpad.pump_fun` | `ks-lib-executor.launchpad.pump_fun` | -| `kb-lib.executor.launchpad.pump_pumpup_ai` | `ks-lib-executor.launchpad.pump_pumpup_ai` | -| `kb-lib.executor.launchpad.raydium_launchlab` | `ks-lib-executor.launchpad.raydium_launchlab` | -| `kb-lib.executor.launchpad.virtuals` | `ks-lib-executor.launchpad.virtuals` | -| `kb-lib.executor.lending.clone` | `ks-lib-executor.lending.clone` | -| `kb-lib.executor.lending.jupiter_lend_borrow` | `ks-lib-executor.lending.jupiter_lend_borrow` | -| `kb-lib.executor.lending.jupiter_lend_earn` | `ks-lib-executor.lending.jupiter_lend_earn` | -| `kb-lib.executor.lending.jupiter_lend_flash_loan` | `ks-lib-executor.lending.jupiter_lend_flash_loan` | -| `kb-lib.executor.lending.jupiter_lend_liquidity` | `ks-lib-executor.lending.jupiter_lend_liquidity` | -| `kb-lib.executor.lending.kamino` | `ks-lib-executor.lending.kamino` | -| `kb-lib.executor.lending.marginfi_v2` | `ks-lib-executor.lending.marginfi_v2` | -| `kb-lib.executor.lock.raydium_lp` | `ks-lib-executor.lock.raydium_lp` | -| `kb-lib.executor.metadata.metaplex_token_metadata` | `ks-lib-executor.metadata.metaplex_token_metadata` | -| `kb-lib.executor.metadata.solana_program_metadata` | `ks-lib-executor.metadata.solana_program_metadata` | -| `kb-lib.executor.metadata.spl_name_service` | `ks-lib-executor.metadata.spl_name_service` | -| `kb-lib.executor.nft.metaplex_bubblegum` | `ks-lib-executor.nft.metaplex_bubblegum` | -| `kb-lib.executor.nft.tensor_cnft` | `ks-lib-executor.nft.tensor_cnft` | -| `kb-lib.executor.orderbook.jupiter_limit_order` | `ks-lib-executor.orderbook.jupiter_limit_order` | -| `kb-lib.executor.orderbook.jupiter_limit_order_v2` | `ks-lib-executor.orderbook.jupiter_limit_order_v2` | -| `kb-lib.executor.orderbook.openbook_v2` | `ks-lib-executor.orderbook.openbook_v2` | -| `kb-lib.executor.perpetuals.drift_v2` | `ks-lib-executor.perpetuals.drift_v2` | -| `kb-lib.executor.perpetuals.jupiter` | `ks-lib-executor.perpetuals.jupiter` | -| `kb-lib.executor.perpetuals.phoenix_eternal` | `ks-lib-executor.perpetuals.phoenix_eternal` | -| `kb-lib.executor.perpetuals.zeta` | `ks-lib-executor.perpetuals.zeta` | -| `kb-lib.executor.router.dflow_aggregator_v4` | `ks-lib-executor.router.dflow_aggregator_v4` | -| `kb-lib.executor.router.jupiter_aggregator_v4` | `ks-lib-executor.router.jupiter_aggregator_v4` | -| `kb-lib.executor.router.jupiter_aggregator_v6` | `ks-lib-executor.router.jupiter_aggregator_v6` | -| `kb-lib.executor.router.okx_labs_v1` | `ks-lib-executor.router.okx_labs_v1` | -| `kb-lib.executor.router.okx_labs_v2` | `ks-lib-executor.router.okx_labs_v2` | -| `kb-lib.executor.rwa.ondo_global_markets` | `ks-lib-executor.rwa.ondo_global_markets` | -| `kb-lib.executor.solana.core` | `ks-lib-executor.solana.core` | -| `kb-lib.executor.solana.transaction` | `ks-lib-executor.solana.transaction` | -| `kb-lib.executor.spl.account_compression` | `ks-lib-executor.spl.account_compression` | -| `kb-lib.executor.spl.associated_token_account` | `ks-lib-executor.spl.associated_token_account` | -| `kb-lib.executor.spl.elgamal_registry` | `ks-lib-executor.spl.elgamal_registry` | -| `kb-lib.executor.spl.memo` | `ks-lib-executor.spl.memo` | -| `kb-lib.executor.spl.noop` | `ks-lib-executor.spl.noop` | -| `kb-lib.executor.spl.single_pool` | `ks-lib-executor.spl.single_pool` | -| `kb-lib.executor.spl.stake_pool` | `ks-lib-executor.spl.stake_pool` | -| `kb-lib.executor.spl.token` | `ks-lib-executor.spl.token` | -| `kb-lib.executor.spl.token-2022` | `ks-lib-executor.spl.token-2022` | -| `kb-lib.executor.spl.token_2022` | `ks-lib-executor.spl.token_2022` | -| `kb-lib.executor.stable.swap_hylo_exchange` | `ks-lib-executor.stable.swap_hylo_exchange` | -| `kb-lib.executor.stable.swap_jupiter_stable` | `ks-lib-executor.stable.swap_jupiter_stable` | -| `kb-lib.executor.stable.swap_numeraire` | `ks-lib-executor.stable.swap_numeraire` | -| `kb-lib.executor.stable.swap_stabble` | `ks-lib-executor.stable.swap_stabble` | -| `kb-lib.executor.staking.jito_tip_distribution` | `ks-lib-executor.staking.jito_tip_distribution` | -| `kb-lib.executor.staking.kamino_farm` | `ks-lib-executor.staking.kamino_farm` | -| `kb-lib.executor.staking.marinade_finance` | `ks-lib-executor.staking.marinade_finance` | -| `kb-lib.executor.staking.solayer` | `ks-lib-executor.staking.solayer` | -| `kb-lib.executor.storage.solana_record` | `ks-lib-executor.storage.solana_record` | -| `kb-lib.executor.strategy.jupiter_dca` | `ks-lib-executor.strategy.jupiter_dca` | -| `kb-lib.executor.treasury.helium_treasury_management` | `ks-lib-executor.treasury.helium_treasury_management` | -| `kb-lib.executor.vault.carrot_defi` | `ks-lib-executor.vault.carrot_defi` | -| `kb-lib.executor.vault.hylo_stability_pool` | `ks-lib-executor.vault.hylo_stability_pool` | -| `kb-lib.executor.vault.kamino` | `ks-lib-executor.vault.kamino` | -| `kb-lib.executor.vault.kamino_v2` | `ks-lib-executor.vault.kamino_v2` | -| `kb-lib.executor.vault.kamino_yvaults` | `ks-lib-executor.vault.kamino_yvaults` | -| `kb-lib.executor.vault.meteora` | `ks-lib-executor.vault.meteora` | -| `kb-lib.executor.vesting.jupiter_lock` | `ks-lib-executor.vesting.jupiter_lock` | -| `kb-lib.executor.vesting.streamflow` | `ks-lib-executor.vesting.streamflow` | -| `kb-lib.executor.wallet.jupiter_apepro_smart_wallet` | `ks-lib-executor.wallet.jupiter_apepro_smart_wallet` | -| `kb-lib.executor.weighted.swap_stabble` | `ks-lib-executor.weighted.swap_stabble` | -| `kb-lib.materializer.admin` | `ks-lib-materializer.admin` | -| `kb-lib.materializer.bridge` | `ks-lib-materializer.bridge` | -| `kb-lib.materializer.compliance.audit` | `ks-lib-materializer.compliance.audit` | -| `kb-lib.materializer.fees` | `ks-lib-materializer.fees` | -| `kb-lib.materializer.governance` | `ks-lib-materializer.governance` | -| `kb-lib.materializer.lending` | `ks-lib-materializer.lending` | -| `kb-lib.materializer.lifecycle` | `ks-lib-materializer.lifecycle` | -| `kb-lib.materializer.liquidity` | `ks-lib-materializer.liquidity` | -| `kb-lib.materializer.metadata.metaplex_token_metadata` | `ks-lib-materializer.metadata.metaplex_token_metadata` | -| `kb-lib.materializer.metadata.solana_program_metadata` | `ks-lib-materializer.metadata.solana_program_metadata` | -| `kb-lib.materializer.metadata.token_2022` | `ks-lib-materializer.metadata.token_2022` | -| `kb-lib.materializer.nft` | `ks-lib-materializer.nft` | -| `kb-lib.materializer.oracle` | `ks-lib-materializer.oracle` | -| `kb-lib.materializer.orderbook` | `ks-lib-materializer.orderbook` | -| `kb-lib.materializer.perpetuals` | `ks-lib-materializer.perpetuals` | -| `kb-lib.materializer.pool.state` | `ks-lib-materializer.pool.state` | -| `kb-lib.materializer.rewards` | `ks-lib-materializer.rewards` | -| `kb-lib.materializer.risk` | `ks-lib-materializer.risk` | -| `kb-lib.materializer.routing` | `ks-lib-materializer.routing` | -| `kb-lib.materializer.staking` | `ks-lib-materializer.staking` | -| `kb-lib.materializer.token.accounts` | `ks-lib-materializer.token.accounts` | -| `kb-lib.materializer.token.metadata_risk` | `ks-lib-materializer.token.metadata_risk` | -| `kb-lib.materializer.trades` | `ks-lib-materializer.trades` | -| `kb-lib.materializer.transaction.annotations` | `ks-lib-materializer.transaction.annotations` | -| `kb-lib.materializer.vault` | `ks-lib-materializer.vault` | +| `kb-lib.executor.bridge.layer_zero_endpoint` | `ks-lib-executor.bridge.layer_zero_endpoint` | +| `kb-lib.executor.bridge.layer_zero_executor` | `ks-lib-executor.bridge.layer_zero_executor` | +| `kb-lib.executor.clmm.byreal` | `ks-lib-executor.clmm.byreal` | +| `kb-lib.executor.clmm.fusion` | `ks-lib-executor.clmm.fusion` | +| `kb-lib.executor.clmm.orca_whirlpool` | `ks-lib-executor.clmm.orca_whirlpool` | +| `kb-lib.executor.clmm.pancake_swap` | `ks-lib-executor.clmm.pancake_swap` | +| `kb-lib.executor.clmm.raydium` | `ks-lib-executor.clmm.raydium` | +| `kb-lib.executor.clmm.stabble` | `ks-lib-executor.clmm.stabble` | +| `kb-lib.executor.cpmm.raydium` | `ks-lib-executor.cpmm.raydium` | +| `kb-lib.executor.dlmm.meteora` | `ks-lib-executor.dlmm.meteora` | +| `kb-lib.executor.fees.bags_fee_share_v1` | `ks-lib-executor.fees.bags_fee_share_v1` | +| `kb-lib.executor.fees.bags_fee_share_v2` | `ks-lib-executor.fees.bags_fee_share_v2` | +| `kb-lib.executor.fees.pump_fees` | `ks-lib-executor.fees.pump_fees` | +| `kb-lib.executor.governance.metadao_bid_wall` | `ks-lib-executor.governance.metadao_bid_wall` | +| `kb-lib.executor.governance.metadao_futarchy` | `ks-lib-executor.governance.metadao_futarchy` | +| `kb-lib.executor.governance.squads_multisig` | `ks-lib-executor.governance.squads_multisig` | +| `kb-lib.executor.launchpad.boop_fun` | `ks-lib-executor.launchpad.boop_fun` | +| `kb-lib.executor.launchpad.metadao_ico` | `ks-lib-executor.launchpad.metadao_ico` | +| `kb-lib.executor.launchpad.meteora_dbc` | `ks-lib-executor.launchpad.meteora_dbc` | +| `kb-lib.executor.launchpad.moonit` | `ks-lib-executor.launchpad.moonit` | +| `kb-lib.executor.launchpad.orca_wavebreak` | `ks-lib-executor.launchpad.orca_wavebreak` | +| `kb-lib.executor.launchpad.printr` | `ks-lib-executor.launchpad.printr` | +| `kb-lib.executor.launchpad.pump_fun` | `ks-lib-executor.launchpad.pump_fun` | +| `kb-lib.executor.launchpad.pump_pumpup_ai` | `ks-lib-executor.launchpad.pump_pumpup_ai` | +| `kb-lib.executor.launchpad.raydium_launchlab` | `ks-lib-executor.launchpad.raydium_launchlab` | +| `kb-lib.executor.launchpad.virtuals` | `ks-lib-executor.launchpad.virtuals` | +| `kb-lib.executor.lending.clone` | `ks-lib-executor.lending.clone` | +| `kb-lib.executor.lending.jupiter_lend_borrow` | `ks-lib-executor.lending.jupiter_lend_borrow` | +| `kb-lib.executor.lending.jupiter_lend_earn` | `ks-lib-executor.lending.jupiter_lend_earn` | +| `kb-lib.executor.lending.jupiter_lend_flash_loan` | `ks-lib-executor.lending.jupiter_lend_flash_loan` | +| `kb-lib.executor.lending.jupiter_lend_liquidity` | `ks-lib-executor.lending.jupiter_lend_liquidity` | +| `kb-lib.executor.lending.kamino` | `ks-lib-executor.lending.kamino` | +| `kb-lib.executor.lending.marginfi_v2` | `ks-lib-executor.lending.marginfi_v2` | +| `kb-lib.executor.lock.raydium_lp` | `ks-lib-executor.lock.raydium_lp` | +| `kb-lib.executor.metadata.metaplex_token_metadata` | `ks-lib-executor.metadata.metaplex_token_metadata` | +| `kb-lib.executor.metadata.solana_program_metadata` | `ks-lib-executor.metadata.solana_program_metadata` | +| `kb-lib.executor.metadata.spl_name_service` | `ks-lib-executor.metadata.spl_name_service` | +| `kb-lib.executor.nft.metaplex_bubblegum` | `ks-lib-executor.nft.metaplex_bubblegum` | +| `kb-lib.executor.nft.tensor_cnft` | `ks-lib-executor.nft.tensor_cnft` | +| `kb-lib.executor.orderbook.jupiter_limit_order` | `ks-lib-executor.orderbook.jupiter_limit_order` | +| `kb-lib.executor.orderbook.jupiter_limit_order_v2` | `ks-lib-executor.orderbook.jupiter_limit_order_v2` | +| `kb-lib.executor.orderbook.openbook_v2` | `ks-lib-executor.orderbook.openbook_v2` | +| `kb-lib.executor.perpetuals.drift_v2` | `ks-lib-executor.perpetuals.drift_v2` | +| `kb-lib.executor.perpetuals.jupiter` | `ks-lib-executor.perpetuals.jupiter` | +| `kb-lib.executor.perpetuals.phoenix_eternal` | `ks-lib-executor.perpetuals.phoenix_eternal` | +| `kb-lib.executor.perpetuals.zeta` | `ks-lib-executor.perpetuals.zeta` | +| `kb-lib.executor.router.dflow_aggregator_v4` | `ks-lib-executor.router.dflow_aggregator_v4` | +| `kb-lib.executor.router.jupiter_aggregator_v4` | `ks-lib-executor.router.jupiter_aggregator_v4` | +| `kb-lib.executor.router.jupiter_aggregator_v6` | `ks-lib-executor.router.jupiter_aggregator_v6` | +| `kb-lib.executor.router.okx_labs_v1` | `ks-lib-executor.router.okx_labs_v1` | +| `kb-lib.executor.router.okx_labs_v2` | `ks-lib-executor.router.okx_labs_v2` | +| `kb-lib.executor.rwa.ondo_global_markets` | `ks-lib-executor.rwa.ondo_global_markets` | +| `kb-lib.executor.solana.core` | `ks-lib-executor.solana.core` | +| `kb-lib.executor.solana.transaction` | `ks-lib-executor.solana.transaction` | +| `kb-lib.executor.spl.account_compression` | `ks-lib-executor.spl.account_compression` | +| `kb-lib.executor.spl.associated_token_account` | `ks-lib-executor.spl.associated_token_account` | +| `kb-lib.executor.spl.elgamal_registry` | `ks-lib-executor.spl.elgamal_registry` | +| `kb-lib.executor.spl.memo` | `ks-lib-executor.spl.memo` | +| `kb-lib.executor.spl.noop` | `ks-lib-executor.spl.noop` | +| `kb-lib.executor.spl.single_pool` | `ks-lib-executor.spl.single_pool` | +| `kb-lib.executor.spl.stake_pool` | `ks-lib-executor.spl.stake_pool` | +| `kb-lib.executor.spl.token` | `ks-lib-executor.spl.token` | +| `kb-lib.executor.spl.token-2022` | `ks-lib-executor.spl.token-2022` | +| `kb-lib.executor.spl.token_2022` | `ks-lib-executor.spl.token_2022` | +| `kb-lib.executor.stable.swap_hylo_exchange` | `ks-lib-executor.stable.swap_hylo_exchange` | +| `kb-lib.executor.stable.swap_jupiter_stable` | `ks-lib-executor.stable.swap_jupiter_stable` | +| `kb-lib.executor.stable.swap_numeraire` | `ks-lib-executor.stable.swap_numeraire` | +| `kb-lib.executor.stable.swap_stabble` | `ks-lib-executor.stable.swap_stabble` | +| `kb-lib.executor.staking.jito_tip_distribution` | `ks-lib-executor.staking.jito_tip_distribution` | +| `kb-lib.executor.staking.kamino_farm` | `ks-lib-executor.staking.kamino_farm` | +| `kb-lib.executor.staking.marinade_finance` | `ks-lib-executor.staking.marinade_finance` | +| `kb-lib.executor.staking.solayer` | `ks-lib-executor.staking.solayer` | +| `kb-lib.executor.storage.solana_record` | `ks-lib-executor.storage.solana_record` | +| `kb-lib.executor.strategy.jupiter_dca` | `ks-lib-executor.strategy.jupiter_dca` | +| `kb-lib.executor.treasury.helium_treasury_management` | `ks-lib-executor.treasury.helium_treasury_management` | +| `kb-lib.executor.vault.carrot_defi` | `ks-lib-executor.vault.carrot_defi` | +| `kb-lib.executor.vault.hylo_stability_pool` | `ks-lib-executor.vault.hylo_stability_pool` | +| `kb-lib.executor.vault.kamino` | `ks-lib-executor.vault.kamino` | +| `kb-lib.executor.vault.kamino_v2` | `ks-lib-executor.vault.kamino_v2` | +| `kb-lib.executor.vault.kamino_yvaults` | `ks-lib-executor.vault.kamino_yvaults` | +| `kb-lib.executor.vault.meteora` | `ks-lib-executor.vault.meteora` | +| `kb-lib.executor.vesting.jupiter_lock` | `ks-lib-executor.vesting.jupiter_lock` | +| `kb-lib.executor.vesting.streamflow` | `ks-lib-executor.vesting.streamflow` | +| `kb-lib.executor.wallet.jupiter_apepro_smart_wallet` | `ks-lib-executor.wallet.jupiter_apepro_smart_wallet` | +| `kb-lib.executor.weighted.swap_stabble` | `ks-lib-executor.weighted.swap_stabble` | +| `kb-lib.materializer.admin` | `ks-lib-materializer.admin` | +| `kb-lib.materializer.bridge` | `ks-lib-materializer.bridge` | +| `kb-lib.materializer.compliance.audit` | `ks-lib-materializer.compliance.audit` | +| `kb-lib.materializer.fees` | `ks-lib-materializer.fees` | +| `kb-lib.materializer.governance` | `ks-lib-materializer.governance` | +| `kb-lib.materializer.lending` | `ks-lib-materializer.lending` | +| `kb-lib.materializer.lifecycle` | `ks-lib-materializer.lifecycle` | +| `kb-lib.materializer.liquidity` | `ks-lib-materializer.liquidity` | +| `kb-lib.materializer.metadata.metaplex_token_metadata` | `ks-lib-materializer.metadata.metaplex_token_metadata` | +| `kb-lib.materializer.metadata.solana_program_metadata` | `ks-lib-materializer.metadata.solana_program_metadata` | +| `kb-lib.materializer.metadata.token_2022` | `ks-lib-materializer.metadata.token_2022` | +| `kb-lib.materializer.nft` | `ks-lib-materializer.nft` | +| `kb-lib.materializer.oracle` | `ks-lib-materializer.oracle` | +| `kb-lib.materializer.orderbook` | `ks-lib-materializer.orderbook` | +| `kb-lib.materializer.perpetuals` | `ks-lib-materializer.perpetuals` | +| `kb-lib.materializer.pool.state` | `ks-lib-materializer.pool.state` | +| `kb-lib.materializer.rewards` | `ks-lib-materializer.rewards` | +| `kb-lib.materializer.risk` | `ks-lib-materializer.risk` | +| `kb-lib.materializer.routing` | `ks-lib-materializer.routing` | +| `kb-lib.materializer.staking` | `ks-lib-materializer.staking` | +| `kb-lib.materializer.token.accounts` | `ks-lib-materializer.token.accounts` | +| `kb-lib.materializer.token.metadata_risk` | `ks-lib-materializer.token.metadata_risk` | +| `kb-lib.materializer.trades` | `ks-lib-materializer.trades` | +| `kb-lib.materializer.transaction.annotations` | `ks-lib-materializer.transaction.annotations` | +| `kb-lib.materializer.vault` | `ks-lib-materializer.vault` | ## 7. Inventaire des variables d'environnement @@ -446,13 +446,15 @@ Les anciennes fixtures `TOKEN_2022_*` et les alias opérateur déjà préfixés ### 7.2 Mapping code/config/tests — 85 noms -| Nom actuel | Nom cible | Classe | -|---|---|---| -| `HELIUS_API_KEY` | `KS_SECRET_HELIUS_API_KEY` | `secret` | -| `KB_CONFIG_PATH` | `KS_CONFIG_PATH` | `internal` | -| `KB_CONFIG_TEST_MISSING` | `KS_CONFIG_TEST_MISSING` | `internal` | -| `KB_DEVNET_AIRDROP_LAMPORTS` | `KS_DEVNET_AIRDROP_LAMPORTS` | `internal` | -| `KB_DEVNET_CONFIG_PATH` | `KS_DEVNET_CONFIG_PATH` | `internal` | +| Nom actuel | Nom cible | Classe | +|------------------------------|-----------------------------------|----------------| +| `HELIUS_API_KEY` | `KS_SECRET_HELIUS_API_KEY` | `secret` | +| `KB_CONFIG_PATH` | `KB_APP_DEMO_DESKTOP_CONFIG_PATH` | `internal` Bot | +| `KB_CONFIG_TEST_MISSING` | `KS_CONFIG_TEST_MISSING` | `internal` | +| `KB_DEVNET_AIRDROP_LAMPORTS` | `KS_DEVNET_AIRDROP_LAMPORTS` | `internal` | +| `KB_DEVNET_CONFIG_PATH` | `KS_DEVNET_CONFIG_PATH` | `internal` | + +La cible `KB_APP_DEMO_DESKTOP_CONFIG_PATH` remplace la transition `KS_CONFIG_PATH` de `pre.004` : une composition appartient au binaire qui la charge, alors que les documents qu’elle référence restent possédés par les crates `ks-*`. | `KB_DEVNET_EXECUTION_TEST` | `KS_DEVNET_EXECUTION_TEST` | `internal` | | `KB_DEVNET_MEMO_EXECUTION_TEST` | `KS_DEVNET_MEMO_EXECUTION_TEST` | `internal` | | `KB_DEVNET_MEMO_SUBMIT` | `KS_DEVNET_MEMO_SUBMIT` | `internal` | @@ -536,50 +538,58 @@ Les anciennes fixtures `TOKEN_2022_*` et les alias opérateur déjà préfixés ### 7.3 Mapping guide opérateur Devnet — 17 noms -| Nom actuel | Nom cible | Classe | -|---|---|---| -| `KB_CONFIRM_DEVNET_DATABASE_RESET` | `KS_CONFIRM_DEVNET_DATABASE_RESET` | `internal` | -| `KB_DEVNET_CLASSIC_DESTINATION_ATA` | `KS_DEVNET_CLASSIC_DESTINATION_ATA` | `internal` | -| `KB_DEVNET_CLASSIC_MINT` | `KS_DEVNET_CLASSIC_MINT` | `internal` | -| `KB_DEVNET_CLASSIC_MINT_KEYPAIR` | `KS_DEVNET_CLASSIC_MINT_KEYPAIR` | `internal` | -| `KB_DEVNET_CLASSIC_RECIPIENT` | `KS_DEVNET_CLASSIC_RECIPIENT` | `internal` | +| Nom actuel | Nom cible | Classe | +|---------------------------------------|---------------------------------------|------------| +| `KB_CONFIRM_DEVNET_DATABASE_RESET` | `KS_CONFIRM_DEVNET_DATABASE_RESET` | `internal` | +| `KB_DEVNET_CLASSIC_DESTINATION_ATA` | `KS_DEVNET_CLASSIC_DESTINATION_ATA` | `internal` | +| `KB_DEVNET_CLASSIC_MINT` | `KS_DEVNET_CLASSIC_MINT` | `internal` | +| `KB_DEVNET_CLASSIC_MINT_KEYPAIR` | `KS_DEVNET_CLASSIC_MINT_KEYPAIR` | `internal` | +| `KB_DEVNET_CLASSIC_RECIPIENT` | `KS_DEVNET_CLASSIC_RECIPIENT` | `internal` | | `KB_DEVNET_CLASSIC_RECIPIENT_KEYPAIR` | `KS_DEVNET_CLASSIC_RECIPIENT_KEYPAIR` | `internal` | -| `KB_DEVNET_CLASSIC_SOURCE_ATA` | `KS_DEVNET_CLASSIC_SOURCE_ATA` | `internal` | -| `KB_DEVNET_RECIPIENT` | `KS_DEVNET_RECIPIENT` | `internal` | -| `KB_DEVNET_SCENARIO` | `KS_DEVNET_SCENARIO` | `internal` | -| `KB_DEVNET_SIGNATURE` | `KS_DEVNET_SIGNATURE` | `internal` | -| `KB_DEVNET_TOKEN_2022_FIXTURE` | `KS_DEVNET_TOKEN_2022_FIXTURE` | `internal` | -| `KB_DEVNET_TOKEN_2022_MINT` | `KS_PUBLIC_DEVNET_TOKEN_2022_MINT` | `public` | -| `KB_DEVNET_TOKEN_2022_MINT_KEYPAIR` | `KS_DEVNET_TOKEN_2022_MINT_KEYPAIR` | `internal` | -| `KB_DEVNET_VALIDATION_DIR` | `KS_DEVNET_VALIDATION_DIR` | `internal` | -| `KB_DEVNET_WALLET_PUBKEY` | `KS_PUBLIC_DEVNET_WALLET_ADDRESS` | `public` | -| `KB_SPL_TOKEN_2022_PROGRAM_ID` | `KS_PUBLIC_SPL_TOKEN_2022_PROGRAM_ID` | `public` | -| `KB_SPL_TOKEN_PROGRAM_ID` | `KS_PUBLIC_SPL_TOKEN_PROGRAM_ID` | `public` | +| `KB_DEVNET_CLASSIC_SOURCE_ATA` | `KS_DEVNET_CLASSIC_SOURCE_ATA` | `internal` | +| `KB_DEVNET_RECIPIENT` | `KS_DEVNET_RECIPIENT` | `internal` | +| `KB_DEVNET_SCENARIO` | `KS_DEVNET_SCENARIO` | `internal` | +| `KB_DEVNET_SIGNATURE` | `KS_DEVNET_SIGNATURE` | `internal` | +| `KB_DEVNET_TOKEN_2022_FIXTURE` | `KS_DEVNET_TOKEN_2022_FIXTURE` | `internal` | +| `KB_DEVNET_TOKEN_2022_MINT` | `KS_PUBLIC_DEVNET_TOKEN_2022_MINT` | `public` | +| `KB_DEVNET_TOKEN_2022_MINT_KEYPAIR` | `KS_DEVNET_TOKEN_2022_MINT_KEYPAIR` | `internal` | +| `KB_DEVNET_VALIDATION_DIR` | `KS_DEVNET_VALIDATION_DIR` | `internal` | +| `KB_DEVNET_WALLET_PUBKEY` | `KS_PUBLIC_DEVNET_WALLET_ADDRESS` | `public` | +| `KB_SPL_TOKEN_2022_PROGRAM_ID` | `KS_PUBLIC_SPL_TOKEN_2022_PROGRAM_ID` | `public` | +| `KB_SPL_TOKEN_PROGRAM_ID` | `KS_PUBLIC_SPL_TOKEN_PROGRAM_ID` | `public` | -## 8. Split configuration / logging +## 8. Décomposition des documents de configuration -L'audit `0.5.0` a confirmé une duplication importante : chaque profil généraliste transporte un bloc logging volumineux. La cible minimale devient : +Le split `pre.005` a d'abord extrait le logging. L'audit suivant a confirmé que le document applicatif restant mélangeait encore des responsabilités ayant des cycles de vie et des consommateurs distincts. La cible devient donc une composition propre à chaque binaire, complétée par des documents spécialisés partageables. + +La convention de composition est : ```text -config/app.config.json -config/logging.config.json -config/example.app.config.json -config/example.logging.config.json -config/schemas/app.config.schema.json -config/schemas/logging.config.schema.json +config/.default.config.json ``` -Les profils applicatifs/réseau et les profils logging sont sélectionnables indépendamment. Un profil `devnet` n'impose donc pas un profil logging `debug`, et `mainnet` n'impose pas mécaniquement un profil logging `release`. +Les premières compositions sont : -Le code duplique actuellement dans `ks-config` et `ks-logging` les concepts : +```text +config/kb-app-demo-desktop.default.config.json +config/ks-pipeline-demo-scenarios.default.config.json +``` -- `LoggingConfig` ; -- `LogTargetConfig` ; -- `LogTargetFilterConfig`. +`pre.006` extrait : -La migration `pre.005` ferme cette dette : `ks-config` ne définit plus de type logging, `ks-logging` possède le document, le schéma et les types runtime, et le desktop charge les deux fichiers indépendamment. `ks-logging` réutilise uniquement les helpers génériques d’environnement de `ks-config`, sans créer de dépendance inverse. +```text +config/logging.config.json +config/transport.config.json +config/listeners.config.json +``` -Aucun troisième document spécialisé n'est créé dans `0.5.1` sans bénéfice de découplage démontré. +avec exemples sous `config/` et schémas source sous `config/schemas/`. Chaque composition sélectionne explicitement les profils logging, transport et listeners qu'elle consomme. Les `active_profile` propres aux documents spécialisés restent des valeurs de repli pour les consommateurs autonomes, mais ne couplent pas les sélections d'un binaire. + +`AppConfig/ProfileConfig` demeure provisoirement un **contrat runtime résolu**, reconstruit par `ks-config`, afin de préserver les consommateurs pendant la migration. Son schéma est `config/schemas/resolved.app.config.schema.json` et ses fixtures de test sont sous `test-fixtures/config/`; il ne correspond plus à un document source chargé au démarrage. + +`pre.007` doit poursuivre la décomposition de `database/data`, wallet, execution et de la configuration réellement spécifique au desktop. À son terme, une composition doit principalement référencer des documents/profils spécialisés et une configuration dédiée au binaire, sans obliger `ks-config` à enregistrer la liste des futurs workers ou exécutables. + +Cette décomposition doit être terminée avant de figer les surfaces `public`/`diagnostic` et la propagation de sensibilité en `pre.008`. ## 9. Source, runtime, public et diagnostic @@ -630,7 +640,8 @@ Avant chaque changement structurel correspondant, conserver ou ajouter des tests - l'interdiction des variables de configuration possédées par le workspace hors namespace d'ownership `KS_*` ou `KB_*` ; - la propagation de sensibilité depuis `KS_SECRET_*` vers les valeurs composées ; - l'impossibilité pour une sentinelle secrète d'apparaître dans `Debug`, logs, erreurs, payloads Tauri, sérialisation publique ou diagnostic ; -- la validation indépendante de `config/schemas/app.config.schema.json` et `config/schemas/logging.config.schema.json` ; +- la validation indépendante des schémas source de composition, logging, transport et listeners sous `config/schemas/` ; +- la validation du schéma `resolved.app.config.schema.json` uniquement comme contrat runtime transitoire ; - l'indépendance des profils généralistes et logging ; - l'absence de duplication structurelle des types logging entre `ks-config` et `ks-logging` ; - le maintien des contrats publics réellement nécessaires après remplacement des types de configuration exposés. @@ -670,27 +681,52 @@ Les tests ne doivent pas figer comme comportement légitime la fuite actuelle de - reporter la propagation de sensibilité et le camouflage effectif après le split config/logging ; - supprimer les anciens noms une fois la migration vérifiée ; - étendre l’audit workspace pour refuser la réintroduction de variables applicatives hors `KS_*` / `KB_*` dans le code, `.env.example`, la configuration exemple et le guide opérateur ; -- ne pas encore modifier les DTO publics ni implémenter le camouflage, qui appartiennent à `pre.006`. +- ne pas encore modifier les DTO publics ni implémenter le camouflage, qui appartiennent désormais à `pre.008` après la fin du split des documents. ### `0.5.1-pre.005` — split config/logging -- **implémenté** : `config/app.config.json` et `config/logging.config.json` deviennent les deux documents chargés par défaut ; -- **implémenté** : les schémas sont centralisés sous `config/schemas/app.config.schema.json` et `config/schemas/logging.config.schema.json` ; -- **implémenté** : `example.app.config.json` et `example.logging.config.json` fournissent des exemples minimaux conformes ; -- **implémenté** : les profils logging et généralistes possèdent chacun leur propre `active_profile` ; +- **implémenté** : extraction initiale du logging hors de l’ancien document applicatif monolithique ; +- **implémenté** : centralisation des schémas sous `config/schemas/` ; +- **implémenté** : profils logging indépendants ; - **implémenté** : `ks-logging` devient propriétaire unique du contrat logging et de sa validation ; -- **implémenté** : le desktop supprime la conversion `ks_config::LoggingConfig` -> `ks_logging::LoggingConfig`. +- **implémenté** : suppression de la conversion `ks_config::LoggingConfig` -> `ks_logging::LoggingConfig`. -### `0.5.1-pre.006` — surfaces publiques sûres +### `0.5.1-pre.006` — composition binaire + transport + listeners + +- **implémenté** : remplacement du rôle de `app.config.json` par des compositions `.default.config.json` ; +- **implémenté** : création de `kb-app-demo-desktop.default.config.json` et d’une composition propre à `ks-pipeline-demo-scenarios` ; +- **implémenté** : extraction des endpoints HTTP/WebSocket vers `transport.config.json` avec schéma et exemple dédiés ; +- **implémenté** : extraction des listeners vers `listeners.config.json` avec schéma et exemple dédiés ; +- **implémenté** : sélection explicite des profils logging/transport/listeners par chaque composition ; +- **implémenté** : maintien temporaire de `AppConfig/ProfileConfig` comme contrat runtime résolu afin de ne pas casser tous les consommateurs pendant la migration ; +- **implémenté** : consommation directe de `TransportProfileConfig` par `ks-onchain-transport` ; +- **implémenté** : suppression des anciens fichiers source `app.config.json`, `example.app.config.json` et de leur schéma source historique ; +- ne pas encore modifier les DTO publics ni la politique de camouflage. + +### `0.5.1-pre.007` — store + wallet + execution + configuration dédiée des binaires + +- extraire `database` vers un document store cohérent avec les frontières de `ks-store` ; +- supprimer `DataConfig` en réaffectant `logs_directory` au document logging et `wallets_directory` au document wallet selon leur ownership réel ; +- auditer la redondance entre `data.wallets_directory` et `wallet.wallet_dir`, puis définir un répertoire racine wallet global et des chemins/alias de profils sans duplication ; +- sortir les paramètres identité/stockage temporaire du wallet vers un document wallet et préparer le contrat `0.5.2` ; +- déplacer `localnet_send_enabled`, `devnet_send_enabled`, `testnet_send_enabled` et `mainnet_send_enabled` hors du wallet vers la politique d'exécution ; +- extraire les politiques d'exécution vers un document indépendant ; +- décider si `app.auto_reconnect_default`, actuellement conservé pour compatibilité, appartient au transport ou doit être supprimé avec migration explicite ; +- sortir `demo` de `ks-config` vers une configuration réellement possédée par `kb-app-demo-desktop` ; +- réduire les compositions à des références vers profils spécialisés et configuration dédiée du binaire ; +- vérifier qu'un nouveau worker peut ajouter son fichier de composition/dédié sans modifier `ks-config` pour enregistrer son existence. + +### `0.5.1-pre.008` — surfaces publiques sûres et camouflage - séparer types source/runtime/public/diagnostic ; +- propager la classification secret/public/internal lors de la résolution des placeholders et valeurs composées ; - retirer `AppConfig`/`ProfileConfig` résolus du payload Tauri public ; - borner les DTO TS-RS et diagnostics ; - valider qu'aucun secret ne traverse logs/UI/erreurs/sérialisation. -### `0.5.1-pre.007` — clôture +### `0.5.1-pre.009` — clôture -- réconciliation complète des namespaces et docs ; +- réconciliation complète des namespaces, compositions, schémas et docs ; - audits finaux ; - suppression des TODO terminés ; - archivage de ce plan et du prompt `030` ; @@ -730,7 +766,8 @@ Le desktop n'est lancé avec `cargo tauri dev` que lorsqu'une frontière runtime - le workspace racine s'appelle toujours `khadhroony-bot3` ; - les variables Solana ont migré vers `KS_*` avec classification nominale sûre et `KB_*` reste réservé aux futurs contrats Bot ; - les identités techniques `kb-lib.*` ciblées ont disparu au profit de `ks-lib-*` ; -- la configuration généraliste et logging sont séparées et validées indépendamment ; +- les binaires utilisent leurs compositions dédiées et les documents logging/transport/listeners sont séparés, partageables et validés indépendamment ; +- database, les répertoires logging/wallet, wallet, execution et les réglages propres au desktop ont quitté la composition avant la construction des surfaces publiques ; - aucune configuration runtime résolue complète ne traverse Tauri ; - aucun secret canari n'apparaît dans les surfaces publiques ou diagnostiques ; - les tests d'API externe, audits et tests workspace sont propres ; diff --git a/docs/rules/RULES_SPECIFIC_KHADHROONY.md b/docs/rules/RULES_SPECIFIC_KHADHROONY.md index a087b30..5ef514f 100644 --- a/docs/rules/RULES_SPECIFIC_KHADHROONY.md +++ b/docs/rules/RULES_SPECIFIC_KHADHROONY.md @@ -1,5 +1,5 @@ - + # Règles spécifiques à `khadhroony-bot3` @@ -237,10 +237,14 @@ Aucun renommage massif de modules n'est autorisé sans étape de contrôle dédi ## Règles de configuration -- La configuration applicative commune doit passer par `ks-config`. -- Les fichiers runtime chargés par défaut sont `config/app.config.json` pour la configuration générale et `config/logging.config.json` pour le logging ; leurs sélections de profils sont indépendantes. -- Les schémas JSON de configuration actifs sont conservés sous `config/schemas/`, notamment `app.config.schema.json` et `logging.config.schema.json`. -- Les exemples conformes sont conservés sous `config/` avec des noms distincts des fichiers runtime, notamment `example.app.config.json` et `example.logging.config.json`. +- La configuration commune Khadhroony Solana passe par `ks-config`, mais chaque binaire possède son fichier de composition et peut ajouter sa configuration dédiée sans rendre `ks-config` dépendant de ce binaire. +- Le nom par défaut d'une composition binaire suit `.default.config.json`. Le desktop utilise `config/kb-app-demo-desktop.default.config.json` ; le CLI des scénarios utilise `config/ks-pipeline-demo-scenarios.default.config.json`. +- Une composition référence les documents spécialisés et sélectionne explicitement les profils qu'elle consomme. Elle ne doit pas recopier les blocs transport, listeners ou logging. +- Les documents partagés actuels sont `config/logging.config.json`, `config/transport.config.json` et `config/listeners.config.json`. Ils conservent chacun des profils nommés et peuvent être consommés indépendamment d'une composition. +- Les schémas JSON actifs sont conservés exclusivement sous `config/schemas/`. `composition.config.schema.json`, `logging.config.schema.json`, `transport.config.schema.json` et `listeners.config.schema.json` décrivent les documents source ; `resolved.app.config.schema.json` décrit uniquement le contrat runtime transitoire reconstruit par `ks-config`. +- Les exemples conformes restent sous `config/` avec un nom distinct des fichiers runtime. Les fixtures d'un contrat runtime résolu appartiennent à `test-fixtures/` et ne doivent pas être chargées en production. +- Les fichiers historiques `config/app.config.json`, `config/example.app.config.json`, `config/schemas/app.config.schema.json`, `config/example.config.json` et `config/schema.config.json` sont interdits dans l'état courant. +- Le chemin de composition desktop peut être remplacé par `KB_APP_DEMO_DESKTOP_CONFIG_PATH`. `KS_DEVNET_CONFIG_PATH` sélectionne une composition pour les scénarios Devnet opt-in. `KS_LOGGING_CONFIG_PATH` reste un override explicite du document logging ; sans override, la composition fournit son chemin. - Les fichiers JSON de configuration ne doivent pas contenir de commentaires. - Les secrets ne doivent pas être écrits en clair dans le dépôt. - Les valeurs sensibles doivent utiliser des variables d'environnement ou un stockage chiffré dédié. diff --git a/kb-app-demo-desktop/CHANGELOG.md b/kb-app-demo-desktop/CHANGELOG.md index 134bd4d..b4befbe 100644 --- a/kb-app-demo-desktop/CHANGELOG.md +++ b/kb-app-demo-desktop/CHANGELOG.md @@ -1,8 +1,14 @@ - + # CHANGELOG — kb-app-demo-desktop +## `0.5.1-pre.006` + +- remplace `config/app.config.json` par la composition dédiée `config/kb-app-demo-desktop.default.config.json`, sélectionnable via `KB_APP_DEMO_DESKTOP_CONFIG_PATH` ; +- résout les profils transport/listeners partagés via `ks-config` et sélectionne explicitement le profil logging déclaré par la composition ; +- conserve les contrats runtime et payloads Tauri actuels jusqu'au chantier public/diagnostic de `pre.008`. + ## `0.5.1-pre.005` - charge séparément `config/app.config.json` et `config/logging.config.json` ; diff --git a/kb-app-demo-desktop/src/app_state.rs b/kb-app-demo-desktop/src/app_state.rs index 955ce09..6cc467b 100644 --- a/kb-app-demo-desktop/src/app_state.rs +++ b/kb-app-demo-desktop/src/app_state.rs @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/src/app_state.rs -// version: 13 +// version: 14 //! Shared Tauri application state and startup initialization. @@ -31,15 +31,26 @@ impl crate::AppState { /// Initializes configuration, logging and shared runtime state. pub(crate) fn initialize() -> ks_core::Result { let config_path = resolve_config_path(); - let logging_config_path = resolve_logging_config_path(); let workspace_root = crate::workspace_root_dir(); - let app_config = match ks_config::read_config_json_file_with_environment( + let composed = match ks_config::read_composed_app_config_with_environment( &config_path, &workspace_root, ) { std::result::Result::Ok(config) => config, std::result::Result::Err(error) => return std::result::Result::Err(error), }; + let composition_profile = match ks_config::active_composition_profile(&composed.composition) + { + std::result::Result::Ok(profile) => profile.clone(), + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let logging_source_path = ks_config::resolve_composition_source_path( + &workspace_root, + composed.composition.sources.logging.as_str(), + ); + let logging_config_path = + resolve_logging_config_path(&workspace_root, &logging_source_path); + let app_config = composed.app_config; let active_profile = match ks_config::active_profile(&app_config) { std::result::Result::Ok(profile) => profile.clone(), std::result::Result::Err(error) => return std::result::Result::Err(error), @@ -51,7 +62,16 @@ impl crate::AppState { std::result::Result::Ok(config) => config, std::result::Result::Err(error) => return std::result::Result::Err(error), }; - let logging_config = match ks_logging::active_logging_profile(&logging_document) { + for profile in &composed.composition.profiles { + match ks_logging::logging_profile(&logging_document, profile.logging_profile.as_str()) { + std::result::Result::Ok(_) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + let logging_config = match ks_logging::logging_profile( + &logging_document, + composition_profile.logging_profile.as_str(), + ) { std::result::Result::Ok(profile) => profile.clone(), std::result::Result::Err(error) => return std::result::Result::Err(error), }; @@ -217,23 +237,38 @@ impl crate::AppState { } fn resolve_config_path() -> std::path::PathBuf { - let configured = std::env::var("KS_CONFIG_PATH").ok(); + let configured = std::env::var("KB_APP_DEMO_DESKTOP_CONFIG_PATH").ok(); return resolve_config_path_from_value(configured.as_deref()); } -fn resolve_logging_config_path() -> std::path::PathBuf { +fn resolve_logging_config_path( + workspace_root: &std::path::Path, + composition_default: &std::path::Path, +) -> std::path::PathBuf { let configured = std::env::var("KS_LOGGING_CONFIG_PATH").ok(); - return resolve_logging_config_path_from_value(configured.as_deref()); + return resolve_logging_config_path_from_value( + workspace_root, + configured.as_deref(), + composition_default, + ); } -fn resolve_logging_config_path_from_value(value: std::option::Option<&str>) -> std::path::PathBuf { +fn resolve_logging_config_path_from_value( + workspace_root: &std::path::Path, + value: std::option::Option<&str>, + composition_default: &std::path::Path, +) -> std::path::PathBuf { if let std::option::Option::Some(configured) = value { let trimmed = configured.trim(); if !trimmed.is_empty() { - return std::path::PathBuf::from(trimmed); + let path = std::path::PathBuf::from(trimmed); + if path.is_absolute() { + return path; + } + return workspace_root.join(path); } } - return crate::workspace_root_dir().join("config/logging.config.json"); + return composition_default.to_path_buf(); } fn resolve_config_path_from_value(value: std::option::Option<&str>) -> std::path::PathBuf { @@ -243,26 +278,29 @@ fn resolve_config_path_from_value(value: std::option::Option<&str>) -> std::path return std::path::PathBuf::from(trimmed); } } - return crate::workspace_root_dir().join("config/app.config.json"); + return crate::workspace_root_dir().join("config/kb-app-demo-desktop.default.config.json"); } #[cfg(test)] mod tests { #[test] - fn resolve_config_path_uses_workspace_default_by_default() { + fn resolve_config_path_uses_desktop_composition_by_default() { let path = super::resolve_config_path_from_value(std::option::Option::None); assert_eq!( path.file_name().and_then(std::ffi::OsStr::to_str), - std::option::Option::Some("app.config.json") + std::option::Option::Some("kb-app-demo-desktop.default.config.json") ); } #[test] - fn resolve_logging_config_path_uses_workspace_default_by_default() { - let path = super::resolve_logging_config_path_from_value(std::option::Option::None); - assert_eq!( - path.file_name().and_then(std::ffi::OsStr::to_str), - std::option::Option::Some("logging.config.json") + fn logging_path_uses_composition_source_without_override() { + let root = std::path::Path::new("/workspace"); + let fallback = root.join("config/logging.config.json"); + let path = super::resolve_logging_config_path_from_value( + root, + std::option::Option::None, + &fallback, ); + assert_eq!(path, fallback); } } diff --git a/kb-app-demo-desktop/src/demo_execution_solana_core.rs b/kb-app-demo-desktop/src/demo_execution_solana_core.rs index 04cb3f6..9988ad4 100644 --- a/kb-app-demo-desktop/src/demo_execution_solana_core.rs +++ b/kb-app-demo-desktop/src/demo_execution_solana_core.rs @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/src/demo_execution_solana_core.rs -// version: 15 +// version: 16 //! Tauri adapter for bounded Solana Core execution on Devnet. @@ -850,11 +850,12 @@ fn memo_summary_payload( mod tests { #[test] fn example_config_exposes_one_devnet_execution_profile() { - let config = - match ks_config::parse_config_json(include_str!("../../config/app.config.json")) { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => panic!("example config parse failed: {error}"), - }; + let config = match ks_config::parse_config_json(include_str!( + "../../test-fixtures/config/resolved.app.config.json" + )) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("example config parse failed: {error}"), + }; let profiles = super::devnet_profile_options(&config); assert_eq!(profiles.len(), 1); assert_eq!(profiles[0].name, "local_devnet"); @@ -864,11 +865,12 @@ mod tests { #[test] fn profile_selection_rejects_mainnet_profiles() { - let config = - match ks_config::parse_config_json(include_str!("../../config/app.config.json")) { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => panic!("example config parse failed: {error}"), - }; + let config = match ks_config::parse_config_json(include_str!( + "../../test-fixtures/config/resolved.app.config.json" + )) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("example config parse failed: {error}"), + }; assert!(crate::select_devnet_profile(&config, "local_devnet").is_ok()); assert!(crate::select_devnet_profile(&config, "mainnet").is_err()); } diff --git a/kb-app-demo-desktop/src/demo_spl_token_2022.rs b/kb-app-demo-desktop/src/demo_spl_token_2022.rs index ee69c9b..1f864a9 100644 --- a/kb-app-demo-desktop/src/demo_spl_token_2022.rs +++ b/kb-app-demo-desktop/src/demo_spl_token_2022.rs @@ -286,7 +286,9 @@ pub(crate) fn demo_spl_token_2022_fixture( let decimals = match decimals_text.parse::() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => { - return std::result::Result::Err(format!("invalid KS_PUBLIC_DEVNET_TOKEN_2022_DECIMALS value: {error}")); + return std::result::Result::Err(format!( + "invalid KS_PUBLIC_DEVNET_TOKEN_2022_DECIMALS value: {error}" + )); }, }; return std::result::Result::Ok(crate::DemoSplToken2022FixturePayload { diff --git a/kb-app-demo-desktop/src/tauri.rs b/kb-app-demo-desktop/src/tauri.rs index cd2be0f..557707c 100644 --- a/kb-app-demo-desktop/src/tauri.rs +++ b/kb-app-demo-desktop/src/tauri.rs @@ -1,5 +1,5 @@ // file: kb-app-demo-desktop/src/tauri.rs -// version: 36 +// version: 37 //! Tauri runtime assembly and private command wrappers. @@ -20,7 +20,7 @@ pub fn run() -> ks_core::Result<()> { }; tracing::info!( target: crate::TRACING_TARGET, - config_path = app_state.config_path(), + composition_config_path = app_state.config_path(), logging_config_path = app_state.logging_config_path(), active_profile = app_state.active_profile().name.as_str(), logging_routes = app_state.logging_route_count(), diff --git a/ks-config/CHANGELOG.md b/ks-config/CHANGELOG.md index 9877c1e..4d4c7b7 100644 --- a/ks-config/CHANGELOG.md +++ b/ks-config/CHANGELOG.md @@ -1,8 +1,17 @@ - + # CHANGELOG — ks-config +## `0.5.1-pre.006` + +- remplace le document source monolithique `app.config.json` par des compositions propres aux binaires, dont `config/kb-app-demo-desktop.default.config.json` et `config/ks-pipeline-demo-scenarios.default.config.json` ; +- extrait les endpoints HTTP/WebSocket vers `config/transport.config.json` et les listeners vers `config/listeners.config.json`, chacun avec schéma et exemple dédiés sous `config/schemas/` / `config/` ; +- conserve `AppConfig`/`ProfileConfig` comme contrat runtime résolu transitoire, reconstruit depuis la composition et les documents partagés ; +- ajoute les APIs typées de chargement, validation et sélection de profils de composition, transport et listeners ; +- ajoute un test d’API externe garantissant que composition, transport et listeners restent accessibles uniquement via les exports crate-root ; +- réserve le split store/data, wallet, execution et configuration spécifique des binaires à `pre.007`, avant les surfaces publiques sûres de `pre.008`. + ## `0.5.1-pre.005` - retire définitivement le bloc `logging` de `ProfileConfig` et du schéma général ; diff --git a/ks-config/README.md b/ks-config/README.md index 7dcef31..2ddfac9 100644 --- a/ks-config/README.md +++ b/ks-config/README.md @@ -1,43 +1,59 @@ - + # ks-config -`ks-config` définit le contrat de configuration générale typé du workspace, son schéma JSON embarqué et les fonctions de chargement, résolution d’environnement, validation et sérialisation. +`ks-config` fournit les contrats de configuration généralistes Khadhroony Solana, les chargeurs de documents spécialisés et la résolution des compositions utilisées par les binaires. ## Responsabilités -- exposer `AppConfig` et les sections de configuration générale ; -- valider le JSON général contre `config/schemas/app.config.schema.json` ; -- appliquer les invariants métier après désérialisation ; -- charger `.env`, ou le fichier sélectionné par `KS_ENV_FILE`, depuis la racine du workspace ; -- résoudre les placeholders namespacés `${KS_*}` / `${KB_*}` et leurs fallbacks ; -- sélectionner le profil applicatif actif ; -- exporter les types généraux nécessaires au frontend avec `ts-rs`. +- charger l'environnement du workspace et résoudre les placeholders `${KS_*}` / `${KB_*}` ; +- valider les documents transport, listeners et composition contre leurs schémas sous `config/schemas/` ; +- fournir les types partagés d'endpoints, listeners et profils runtime ; +- résoudre les références d'une composition vers des profils transport/listeners ; +- reconstruire temporairement `AppConfig/ProfileConfig` pour les consommateurs existants ; +- rester indépendant de la liste des applications et workers futurs. -## Hors périmètre +## Composition -`ks-config` ne possède plus le contrat logging. `LoggingConfig`, les routes, filtres, profils logging et `config/schemas/logging.config.schema.json` appartiennent à `ks-logging`. +Le desktop possède `config/kb-app-demo-desktop.default.config.json`. Le CLI des scénarios possède `config/ks-pipeline-demo-scenarios.default.config.json`. -La crate n’initialise ni PostgreSQL, ni les transports et ne manipule aucun secret de wallet. Elle fournit uniquement la configuration générale validée à ces consommateurs. +Une composition choisit : -## Surface publique +- les documents partagés à charger ; +- le profil logging ; +- le profil transport ; +- le profil listeners ; +- temporairement les sections encore non séparées. -Les principales fonctions sont `read_config_json_file_with_environment`, `parse_config_json`, `validate_config`, `validate_config_json_schema`, `active_profile` et les sérialiseurs JSON. +Un futur worker pourra donc ajouter son propre `.default.config.json` sans créer une branche spécifique dans `ks-config`. -Les types publics couvrent les profils applicatifs, endpoints, listeners, base de données, wallet, exécution et démonstration. +## Documents spécialisés actuels -## Relations +- `config/transport.config.json` / `config/schemas/transport.config.schema.json` ; +- `config/listeners.config.json` / `config/schemas/listeners.config.schema.json` ; +- `config/logging.config.json` reste possédé par `ks-logging` ; +- `config/schemas/resolved.app.config.schema.json` décrit uniquement le contrat runtime transitoire. -- dépend de `ks-core` pour les erreurs structurées ; -- fournit les profils généraux à `ks-store`, `ks-onchain-transport`, `ks-pipeline`, `ks-wallet` et aux applications ; -- fournit à `ks-logging` les helpers génériques de chargement `.env` et de résolution des placeholders ; -- embarque [`../config/schemas/app.config.schema.json`](../config/schemas/app.config.schema.json) ; -- utilise [`../config/app.config.json`](../config/app.config.json) comme configuration générale chargée par défaut et [`../config/example.app.config.json`](../config/example.app.config.json) comme exemple minimal conforme. +Les anciens `config/app.config.json` et `config/example.app.config.json` ne sont plus des fichiers runtime. -## Statut +## API publique principale -Le split config/logging est effectif : un profil applicatif ne transporte plus de bloc logging et la sélection logging est indépendante. La prerelease suivante sépare les représentations source/runtime/public/diagnostic et ferme l’exposition des secrets résolus. +- `read_composed_app_config_with_environment` ; +- `parse_composition_json`, `composition_profile`, `active_composition_profile` ; +- `read_transport_json_file_with_environment`, `transport_profile` ; +- `read_listeners_json_file_with_environment`, `listeners_profile` ; +- `compose_app_config` ; +- `load_workspace_environment`, `resolve_environment_placeholders` ; +- `AppConfig`, `ProfileConfig` pendant la phase de compatibilité. + +## Frontières + +`ks-logging` dépend de `ks-config` pour les helpers d'environnement, mais `ks-config` ne possède aucun type logging runtime. + +`ks-onchain-transport` peut consommer directement `TransportProfileConfig`. Un worker n'a donc pas besoin d'un profil applicatif complet pour construire ses pools réseau. + +Les sections database/data, wallet, execution et demo restent transitoires dans la composition jusqu'au split suivant. ## Documents diff --git a/ks-config/TODO.md b/ks-config/TODO.md index a0fb048..366bcde 100644 --- a/ks-config/TODO.md +++ b/ks-config/TODO.md @@ -1,10 +1,11 @@ - + # TODO — ks-config ## Série `0.5.x` +- [ ] `0.5.1` - sortir `database`, wallet, execution et la configuration spécifique au desktop vers leurs documents/propriétaires dédiés ; réaffecter `logs_directory` à logging et `wallets_directory` à wallet, puis réduire la composition aux références nécessaires. - [ ] `0.5.1` - appliquer à l’exécution les classes `KS_SECRET_*` / `KB_SECRET_*`, `KS_PUBLIC_*` / `KB_PUBLIC_*` et internes, avec propagation de sensibilité aux valeurs composées. - [ ] `0.5.1` - séparer les représentations source, runtime résolue, publique et diagnostic. - [ ] `0.5.1` - interdire qu’un secret résolu soit sérialisé, loggé, inclus dans une erreur ou transmis via Tauri. diff --git a/ks-config/USAGE.md b/ks-config/USAGE.md index 89884f1..1afa6cb 100644 --- a/ks-config/USAGE.md +++ b/ks-config/USAGE.md @@ -1,143 +1,118 @@ - + # Utilisation de ks-config -## Chargement recommandé +## Rôle de la crate + +`ks-config` fournit les types, validations et chargeurs des configurations Khadhroony Solana partagées. Un binaire choisit son propre fichier de composition et combine les profils spécialisés dont il a besoin. + +`ks-config` ne doit pas connaître la liste des futurs workers ou applications. + +## Chargement d'une composition ```rust -let config_result = ks_config::read_config_json_file_with_environment( - std::path::Path::new("config/app.config.json"), +let composed = match ks_config::read_composed_app_config_with_environment( + std::path::Path::new("config/kb-app-demo-desktop.default.config.json"), std::path::Path::new("."), -); -let config = match config_result { +) { Ok(value) => value, Err(error) => return Err(error), }; +let config = composed.app_config; let profile = match ks_config::active_profile(&config) { Ok(value) => value, Err(error) => return Err(error), }; ``` -Cette API charge l’environnement du workspace, résout les placeholders puis applique successivement le schéma JSON général, la désérialisation typée et les invariants métier. +Le chargeur : -Le fichier chargé par défaut par le desktop est `config/app.config.json`. `KS_CONFIG_PATH` permet de remplacer explicitement ce chemin. +1. charge l'environnement du workspace ; +2. valide la composition ; +3. résout les chemins `transport` et `listeners` déclarés par la composition ; +4. charge et valide les deux documents spécialisés ; +5. sélectionne les profils référencés ; +6. reconstruit le contrat runtime `AppConfig/ProfileConfig` ; +7. applique les invariants métier existants. -## Chargement de l’environnement +Le logging reste chargé par `ks-logging`, car son type runtime appartient à cette crate. La composition fournit néanmoins le chemin et le nom de profil à sélectionner. + +## Configuration transport indépendante + +```rust +let transport = match ks_config::read_transport_json_file_with_environment( + std::path::Path::new("config/transport.config.json"), + std::path::Path::new("."), +) { + Ok(value) => value, + Err(error) => return Err(error), +}; +let profile = match ks_config::transport_profile(&transport, "mainnet_research") { + Ok(value) => value, + Err(error) => return Err(error), +}; +``` + +Le document expose `TransportConfigDocument` et `TransportProfileConfig` avec les endpoints HTTP/WebSocket et leurs rôles. + +## Configuration listeners indépendante + +```rust +let listeners = match ks_config::read_listeners_json_file_with_environment( + std::path::Path::new("config/listeners.config.json"), + std::path::Path::new("."), +) { + Ok(value) => value, + Err(error) => return Err(error), +}; +let profile = match ks_config::listeners_profile(&listeners, "mainnet_research") { + Ok(value) => value, + Err(error) => return Err(error), +}; +``` + +`resolved_listener_config` convertit ce profil spécialisé vers le contrat runtime `ListenerConfig` utilisé pendant la phase de transition. + +## Chargement de l'environnement ```rust let report = match ks_config::load_workspace_environment(std::path::Path::new(".")) { Ok(value) => value, Err(error) => return Err(error), }; - -if let Some(path) = report.loaded_path { - println!("environment file: {}", path.display()); -} ``` -`EnvironmentLoadReport` indique le fichier chargé. Par défaut, `ks-config` sélectionne `.env`; `KS_ENV_FILE` permet d’en choisir un autre sans écraser les variables déjà présentes dans le processus. +Par défaut, `ks-config` sélectionne `.env`; `KS_ENV_FILE` permet d'en choisir un autre sans écraser les variables déjà présentes dans le processus. -Les contrats d’environnement utilisent `KS_*` pour Khadhroony Solana et `KB_*` pour les besoins réellement propres au Bot. - -## Résolution explicite des placeholders +## Résolution des placeholders ```rust let raw = r#"{"databaseUrl":"${KS_SECRET_POSTGRES_DEVNET_URL:-postgres://localhost/ks}"}"#; let resolved = ks_config::resolve_environment_placeholders(raw); - assert!(resolved.contains("databaseUrl")); ``` -Cette API retourne encore une chaîne ordinaire pouvant contenir des secrets résolus. Elle reste strictement backend jusqu’à l’introduction de la représentation sensible de `0.5.1-pre.006`. +La chaîne résolue peut encore contenir des secrets. Elle reste strictement backend jusqu'à l'introduction de la représentation sensible dédiée. -## Validation et parsing +## Contrat runtime résolu -```rust -if let Err(error) = ks_config::validate_config_json_schema(raw_json) { - return Err(error); -} -let config = match ks_config::parse_config_json(raw_json) { - Ok(value) => value, - Err(error) => return Err(error), -}; -if let Err(error) = ks_config::validate_config(&config) { - return Err(error); -} -``` +`AppConfig` et `ProfileConfig` restent provisoirement le contrat runtime commun afin de ne pas casser tous les consommateurs pendant les splits successifs. Ils ne correspondent plus à un fichier `config/app.config.json` chargé directement. -`parse_config_json` effectue déjà les deux validations ; les appels séparés servent aux outils de diagnostic. - -## Schéma embarqué - -```rust -let schema_text = ks_config::config_json_schema_text(); -let schema_value = match ks_config::config_json_schema_value() { - Ok(value) => value, - Err(error) => return Err(error), -}; - -println!("embedded app schema bytes={}", schema_text.len()); -assert!(schema_value.is_object()); -``` - -Le schéma actif est [`../config/schemas/app.config.schema.json`](../config/schemas/app.config.schema.json). Les fichiers [`../config/app.config.json`](../config/app.config.json) et [`../config/example.app.config.json`](../config/example.app.config.json) doivent tous deux être conformes. - -## Sélection du profil actif - -```rust -let profile = match ks_config::active_profile(&config) { - Ok(value) => value, - Err(error) => return Err(error), -}; - -println!("active profile: {}", profile.name); -println!("http endpoints: {}", profile.solana.http_endpoints.len()); -println!("websocket endpoints: {}", profile.solana.ws_endpoints.len()); -``` - -Le profil applicatif ne contient plus de configuration logging. Le document logging et son `active_profile` sont chargés par `ks-logging`. - -## Sérialisation - -```rust -let pretty = match ks_config::serialize_config_json_pretty(&config) { - Ok(value) => value, - Err(error) => return Err(error), -}; - -let write_result = std::fs::write("config/generated.app.config.json", pretty); -if let Err(error) = write_result { - return Err(ks_core::Error::new( - "config_write_failed", - format!("cannot write generated configuration: {error}"), - )); -} -``` - -La configuration est validée avant sérialisation. +Le schéma de ce contrat résolu est `config/schemas/resolved.app.config.schema.json`. Les fixtures correspondantes sont sous `test-fixtures/config/` et ne sont pas des configurations runtime. ## Types publics importants -- `AppConfig`, `ProfileConfig`, `AppSectionConfig` ; -- `DatabaseConfig`, `PostgresConfig`, `SqliteConfig`, `DataConfig` ; -- `SolanaConfig`, `HttpEndpointConfig`, `WsEndpointConfig`, `EndpointRoleConfig` ; +- `CompositionConfigDocument`, `CompositionProfileConfig`, `CompositionConfigSources` ; +- `TransportConfigDocument`, `TransportProfileConfig` ; +- `ListenersConfigDocument`, `ListenersProfileConfig` ; +- `AppConfig`, `ProfileConfig` comme contrat runtime de transition ; +- `HttpEndpointConfig`, `WsEndpointConfig`, `EndpointRoleConfig` ; - `ListenerConfig` et ses variantes ; -- `WalletConfig`, `ExecutionConfig`, `DemoConfig`. +- `DatabaseConfig`, `WalletConfig`, `ExecutionConfig` jusqu'à leur split dédié. -Les types logging ne font plus partie de `ks-config`. +Les types logging appartiennent à `ks-logging`. -## Erreurs et invariants +## Prochaine frontière -Les erreurs utilisent `ks_core::Error` avec un code stable. Les validations couvrent notamment l’unicité des profils, l’existence du profil actif, les URLs, les rôles d’endpoints, les limites d’exécution et les contraintes wallet. - -## Tests instructifs - -Les tests vérifient séparément `app.config.json` et `example.app.config.json`, le roundtrip du document général et les invariants `parser_rejects_*` / `schema_rejects_*`. - -## Limites - -- format JSON uniquement ; -- aucune connexion externe n’est ouverte par la crate ; -- la politique de camouflage des valeurs résolues appartient à `0.5.1-pre.006`. +Les sections store/data, wallet, execution et desktop seront sorties de la composition dans la prerelease suivante. Une fois cette décomposition terminée, les surfaces source/runtime/public/diagnostic et le camouflage des secrets pourront être définis sur des responsabilités stables. diff --git a/ks-config/src/composition.rs b/ks-config/src/composition.rs new file mode 100644 index 0000000..f89c599 --- /dev/null +++ b/ks-config/src/composition.rs @@ -0,0 +1,373 @@ +// file: ks-config/src/composition.rs +// version: 1 + +//! Binary composition configuration and resolution into the shared runtime profile contract. + +const COMPOSITION_JSON_SCHEMA: &str = + include_str!("../../config/schemas/composition.config.schema.json"); + +/// Root composition document used by a binary to select shared configuration profiles. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)] +pub struct CompositionConfigDocument { + /// Active binary composition profile name. + pub active_profile: std::string::String, + /// Shared configuration documents referenced by this composition. + pub sources: CompositionConfigSources, + /// Named binary composition profiles. + pub profiles: std::vec::Vec, +} + +/// Paths of shared configuration documents consumed by one binary composition. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)] +pub struct CompositionConfigSources { + /// Logging configuration document path relative to the workspace root unless absolute. + pub logging: std::string::String, + /// Transport configuration document path relative to the workspace root unless absolute. + pub transport: std::string::String, + /// Listener configuration document path relative to the workspace root unless absolute. + pub listeners: std::string::String, +} + +/// Named composition profile referencing shared configuration profiles. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)] +pub struct CompositionProfileConfig { + /// Composition profile code. + pub name: std::string::String, + /// Application metadata retained until the remaining configuration split is completed. + pub app: crate::AppSectionConfig, + /// Logging profile selected from the referenced logging document. + pub logging_profile: std::string::String, + /// Transport profile selected from the referenced transport document. + pub transport_profile: std::string::String, + /// Listener profile selected from the referenced listener document. + pub listeners_profile: std::string::String, + /// Database configuration retained until the store split prerelease. + pub database: crate::DatabaseConfig, + /// Local data directories retained until their owning configuration files are split. + pub data: crate::DataConfig, + /// Wallet configuration retained until the wallet split prerelease. + pub wallet: crate::WalletConfig, + /// Execution policy retained until the execution split prerelease. + pub execution: crate::ExecutionConfig, + /// Desktop demo flags retained until the binary-specific split prerelease. + pub demo: crate::DemoConfig, +} + +/// Returns the embedded binary composition JSON Schema text. +pub fn composition_json_schema_text() -> &'static str { + return COMPOSITION_JSON_SCHEMA; +} + +/// Parses the embedded binary composition JSON Schema into a JSON value. +pub fn composition_json_schema_value() -> ks_core::Result { + let schema_result = serde_json::from_str::(COMPOSITION_JSON_SCHEMA); + return match schema_result { + std::result::Result::Ok(schema) => std::result::Result::Ok(schema), + std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new( + "composition_config_schema_parse_failed", + error.to_string(), + )), + }; +} + +/// Validates raw binary composition JSON against its embedded schema. +pub fn validate_composition_json_schema(raw_json: &str) -> ks_core::Result<()> { + let schema = match composition_json_schema_value() { + std::result::Result::Ok(schema) => schema, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instance = match serde_json::from_str::(raw_json) { + std::result::Result::Ok(instance) => instance, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "composition_config_json_parse_failed", + error.to_string(), + )); + }, + }; + let validator = match jsonschema::validator_for(&schema) { + std::result::Result::Ok(validator) => validator, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "composition_config_schema_compile_failed", + error.to_string(), + )); + }, + }; + return match validator.validate(&instance) { + std::result::Result::Ok(()) => std::result::Result::Ok(()), + std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new( + "composition_config_schema_validation_failed", + error.to_string(), + )), + }; +} + +/// Parses and validates one binary composition document. +pub fn parse_composition_json(raw_json: &str) -> ks_core::Result { + match validate_composition_json_schema(raw_json) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let document = match serde_json::from_str::(raw_json) { + std::result::Result::Ok(document) => document, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "composition_config_json_decode_failed", + error.to_string(), + )); + }, + }; + return match validate_composition_document(&document) { + std::result::Result::Ok(()) => std::result::Result::Ok(document), + std::result::Result::Err(error) => std::result::Result::Err(error), + }; +} + +/// Reads and parses one binary composition document from a filesystem path. +pub fn read_composition_json_file( + path: &std::path::Path, +) -> ks_core::Result { + let raw_json = match std::fs::read_to_string(path) { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "composition_config_file_read_failed", + error.to_string(), + )); + }, + }; + return parse_composition_json(&raw_json); +} + +/// Loads workspace environment values, resolves placeholders and parses one binary composition document. +pub fn read_composition_json_file_with_environment( + path: &std::path::Path, + workspace_root: &std::path::Path, +) -> ks_core::Result { + match crate::load_workspace_environment(workspace_root) { + std::result::Result::Ok(_) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let raw_json = match std::fs::read_to_string(path) { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "composition_config_file_read_failed", + error.to_string(), + )); + }, + }; + let resolved = crate::resolve_environment_placeholders(&raw_json); + return parse_composition_json(&resolved); +} + +/// Returns one named binary composition profile. +pub fn composition_profile<'a>( + document: &'a CompositionConfigDocument, + profile_name: &str, +) -> ks_core::Result<&'a CompositionProfileConfig> { + for profile in &document.profiles { + if profile.name == profile_name { + return std::result::Result::Ok(profile); + } + } + return std::result::Result::Err(ks_core::Error::new( + "composition_profile_not_found", + profile_name.to_string(), + )); +} + +/// Returns the active binary composition profile. +pub fn active_composition_profile( + document: &CompositionConfigDocument, +) -> ks_core::Result<&CompositionProfileConfig> { + return composition_profile(document, document.active_profile.as_str()); +} + +/// Resolves a source path relative to the workspace root unless already absolute. +pub fn resolve_composition_source_path( + workspace_root: &std::path::Path, + configured_path: &str, +) -> std::path::PathBuf { + let path = std::path::PathBuf::from(configured_path); + if path.is_absolute() { + return path; + } + return workspace_root.join(path); +} + +/// Resolves all shared transport/listener selections into the existing runtime application contract. +pub fn compose_app_config( + document: &CompositionConfigDocument, + transport: &crate::TransportConfigDocument, + listeners: &crate::ListenersConfigDocument, +) -> ks_core::Result { + let mut profiles = std::vec::Vec::::new(); + for profile in &document.profiles { + let transport_profile = + match crate::transport_profile(transport, profile.transport_profile.as_str()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let listeners_profile = + match crate::listeners_profile(listeners, profile.listeners_profile.as_str()) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + profiles.push(crate::ProfileConfig { + name: profile.name.clone(), + app: profile.app.clone(), + database: profile.database.clone(), + data: profile.data.clone(), + solana: crate::SolanaConfig { + http_endpoints: transport_profile.http_endpoints.clone(), + ws_endpoints: transport_profile.ws_endpoints.clone(), + listeners: crate::resolved_listener_config(listeners_profile), + }, + wallet: profile.wallet.clone(), + execution: profile.execution.clone(), + demo: profile.demo.clone(), + }); + } + let config = crate::AppConfig { + active_profile: document.active_profile.clone(), + profiles, + }; + return match crate::validate_config(&config) { + std::result::Result::Ok(()) => std::result::Result::Ok(config), + std::result::Result::Err(error) => std::result::Result::Err(error), + }; +} + +/// Reads a binary composition and its referenced transport/listener documents into the resolved runtime contract. +pub fn read_composed_app_config_with_environment( + composition_path: &std::path::Path, + workspace_root: &std::path::Path, +) -> ks_core::Result { + let composition = + match read_composition_json_file_with_environment(composition_path, workspace_root) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let transport_path = + resolve_composition_source_path(workspace_root, composition.sources.transport.as_str()); + let listeners_path = + resolve_composition_source_path(workspace_root, composition.sources.listeners.as_str()); + let transport = + match crate::read_transport_json_file_with_environment(&transport_path, workspace_root) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let listeners = + match crate::read_listeners_json_file_with_environment(&listeners_path, workspace_root) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let app_config = match compose_app_config(&composition, &transport, &listeners) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + return std::result::Result::Ok(crate::ComposedAppConfig { + composition, + app_config, + transport_path, + listeners_path, + }); +} + +/// Resolved application configuration and the source documents needed by a binary. +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct ComposedAppConfig { + /// Parsed binary composition document. + pub composition: CompositionConfigDocument, + /// Resolved runtime application configuration. + pub app_config: crate::AppConfig, + /// Resolved transport source path. + pub transport_path: std::path::PathBuf, + /// Resolved listener source path. + pub listeners_path: std::path::PathBuf, +} + +/// Validates a typed binary composition document before referenced documents are loaded. +pub fn validate_composition_document(document: &CompositionConfigDocument) -> ks_core::Result<()> { + match crate::require_non_empty(&document.active_profile, "active_profile") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + for source in [ + &document.sources.logging, + &document.sources.transport, + &document.sources.listeners, + ] { + match crate::require_non_empty(source, "composition.sources") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if document.profiles.is_empty() { + return std::result::Result::Err(ks_core::Error::new( + "composition_profiles_empty", + "at least one composition profile is required", + )); + } + let mut names = std::collections::BTreeSet::::new(); + for profile in &document.profiles { + match crate::require_non_empty(&profile.name, "composition.profile.name") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + for reference in + [&profile.logging_profile, &profile.transport_profile, &profile.listeners_profile] + { + match crate::require_non_empty(reference, "composition.profile.reference") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + if !names.insert(profile.name.clone()) { + return std::result::Result::Err(ks_core::Error::new( + "composition_profile_name_duplicate", + profile.name.clone(), + )); + } + } + return match active_composition_profile(document) { + std::result::Result::Ok(_) => std::result::Result::Ok(()), + std::result::Result::Err(error) => std::result::Result::Err(error), + }; +} + +#[cfg(test)] +mod tests { + const DEFAULT_COMPOSITION: &str = + include_str!("../../config/kb-app-demo-desktop.default.config.json"); + const EXAMPLE_COMPOSITION: &str = + include_str!("../../config/example.kb-app-demo-desktop.default.config.json"); + const DEFAULT_TRANSPORT: &str = include_str!("../../config/transport.config.json"); + const DEFAULT_LISTENERS: &str = include_str!("../../config/listeners.config.json"); + + #[test] + fn default_and_example_compositions_validate() { + assert!(super::parse_composition_json(DEFAULT_COMPOSITION).is_ok()); + assert!(super::parse_composition_json(EXAMPLE_COMPOSITION).is_ok()); + } + + #[test] + fn default_composition_resolves_shared_transport_and_listener_profiles() { + let composition = match super::parse_composition_json(DEFAULT_COMPOSITION) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("default composition must parse: {error}"), + }; + let transport = match crate::parse_transport_json(DEFAULT_TRANSPORT) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("default transport must parse: {error}"), + }; + let listeners = match crate::parse_listeners_json(DEFAULT_LISTENERS) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("default listeners must parse: {error}"), + }; + let resolved = super::compose_app_config(&composition, &transport, &listeners); + assert!(resolved.is_ok()); + } +} diff --git a/ks-config/src/lib.rs b/ks-config/src/lib.rs index 45daec0..4582f76 100644 --- a/ks-config/src/lib.rs +++ b/ks-config/src/lib.rs @@ -1,20 +1,79 @@ // file: ks-config/src/lib.rs -// version: 6 +// version: 7 //! Khadhroony Solana application configuration contract and loading helpers. #![warn(missing_docs)] #![deny(unreachable_pub)] #![forbid(unsafe_code)] +mod composition; mod environment; +mod listeners; mod settings; +mod transport; +/// Exposes the resolved application configuration plus its composition source. +pub use self::composition::ComposedAppConfig; +/// Exposes the binary composition document type. +pub use self::composition::CompositionConfigDocument; +/// Exposes binary composition source paths. +pub use self::composition::CompositionConfigSources; +/// Exposes one named binary composition profile. +pub use self::composition::CompositionProfileConfig; +/// Exposes the active binary composition profile resolver. +pub use self::composition::active_composition_profile; +/// Exposes the resolved runtime composition builder. +pub use self::composition::compose_app_config; +/// Exposes the binary composition JSON Schema text. +pub use self::composition::composition_json_schema_text; +/// Exposes the binary composition JSON Schema value parser. +pub use self::composition::composition_json_schema_value; +/// Exposes one named binary composition profile resolver. +pub use self::composition::composition_profile; +/// Exposes binary composition parsing from JSON. +pub use self::composition::parse_composition_json; +/// Exposes composed application loading with referenced shared documents. +pub use self::composition::read_composed_app_config_with_environment; +/// Exposes binary composition loading from a filesystem path. +pub use self::composition::read_composition_json_file; +/// Exposes binary composition loading with workspace environment resolution. +pub use self::composition::read_composition_json_file_with_environment; +/// Exposes composition source path resolution. +pub use self::composition::resolve_composition_source_path; +/// Exposes typed binary composition validation. +pub use self::composition::validate_composition_document; +/// Exposes binary composition JSON Schema validation. +pub use self::composition::validate_composition_json_schema; /// Exposes the environment loading report. pub use self::environment::EnvironmentLoadReport; /// Exposes workspace environment-file loading. pub use self::environment::load_workspace_environment; /// Exposes environment placeholder resolution. pub use self::environment::resolve_environment_placeholders; +/// Exposes the independent listener configuration document. +pub use self::listeners::ListenersConfigDocument; +/// Exposes one named listener profile. +pub use self::listeners::ListenersProfileConfig; +/// Exposes the active listener profile resolver. +pub use self::listeners::active_listeners_profile; +/// Exposes the listener JSON Schema text. +pub use self::listeners::listeners_json_schema_text; +/// Exposes the listener JSON Schema value parser. +pub use self::listeners::listeners_json_schema_value; +/// Exposes one named listener profile resolver. +pub use self::listeners::listeners_profile; +/// Exposes listener configuration parsing from JSON. +pub use self::listeners::parse_listeners_json; +/// Exposes listener configuration loading from a filesystem path. +pub use self::listeners::read_listeners_json_file; +/// Exposes listener configuration loading with workspace environment resolution. +pub use self::listeners::read_listeners_json_file_with_environment; +/// Exposes conversion from a listener profile to the resolved runtime contract. +pub use self::listeners::resolved_listener_config; +/// Exposes typed listener document validation. +pub use self::listeners::validate_listeners_document; +/// Exposes listener JSON Schema validation. +pub use self::listeners::validate_listeners_json_schema; /// Exposes the account listener configuration type. pub use self::settings::AccountListenerConfig; /// Exposes the root application configuration type. @@ -53,21 +112,52 @@ pub use self::settings::WalletConfig; pub use self::settings::WsEndpointConfig; /// Exposes the active profile resolver. pub use self::settings::active_profile; -/// Exposes the embedded JSON Schema text. +/// Exposes the embedded JSON Schema text for the resolved runtime contract. pub use self::settings::config_json_schema_text; -/// Exposes the embedded JSON Schema value parser. +/// Exposes the embedded JSON Schema value parser for the resolved runtime contract. pub use self::settings::config_json_schema_value; -/// Exposes the configuration parser from a JSON string. +/// Exposes resolved runtime configuration parsing from a JSON string. pub use self::settings::parse_config_json; -/// Exposes the configuration loader from a filesystem path. +/// Exposes resolved runtime configuration loading from a filesystem path. pub use self::settings::read_config_json_file; -/// Exposes configuration loading with workspace environment resolution. +/// Exposes resolved runtime configuration loading with workspace environment resolution. pub use self::settings::read_config_json_file_with_environment; -/// Exposes the compact JSON serializer for configuration values. +/// Exposes the compact JSON serializer for resolved runtime configuration values. pub use self::settings::serialize_config_json; -/// Exposes the pretty JSON serializer for configuration values. +/// Exposes the pretty JSON serializer for resolved runtime configuration values. pub use self::settings::serialize_config_json_pretty; -/// Exposes the typed configuration validator. +/// Exposes the typed resolved runtime configuration validator. pub use self::settings::validate_config; -/// Exposes the JSON Schema validator for raw JSON configuration. +/// Exposes the JSON Schema validator for raw resolved runtime configuration JSON. pub use self::settings::validate_config_json_schema; +/// Exposes the independent transport configuration document. +pub use self::transport::TransportConfigDocument; +/// Exposes one named transport profile. +pub use self::transport::TransportProfileConfig; +/// Exposes the active transport profile resolver. +pub use self::transport::active_transport_profile; +/// Exposes transport configuration parsing from JSON. +pub use self::transport::parse_transport_json; +/// Exposes transport configuration loading from a filesystem path. +pub use self::transport::read_transport_json_file; +/// Exposes transport configuration loading with workspace environment resolution. +pub use self::transport::read_transport_json_file_with_environment; +/// Exposes the transport JSON Schema text. +pub use self::transport::transport_json_schema_text; +/// Exposes the transport JSON Schema value parser. +pub use self::transport::transport_json_schema_value; +/// Exposes one named transport profile resolver. +pub use self::transport::transport_profile; +/// Exposes typed transport document validation. +pub use self::transport::validate_transport_document; +/// Exposes transport JSON Schema validation. +pub use self::transport::validate_transport_json_schema; + +/// Internal non-empty string validation shared by configuration documents. +pub(crate) use self::settings::require_non_empty; +/// Internal typed HTTP endpoint validation shared by resolved and transport documents. +pub(crate) use self::settings::validate_http_endpoint; +/// Internal typed listener validation shared by resolved and listener documents. +pub(crate) use self::settings::validate_listeners; +/// Internal typed WebSocket endpoint validation shared by resolved and transport documents. +pub(crate) use self::settings::validate_ws_endpoint; diff --git a/ks-config/src/listeners.rs b/ks-config/src/listeners.rs new file mode 100644 index 0000000..27d5f5a --- /dev/null +++ b/ks-config/src/listeners.rs @@ -0,0 +1,224 @@ +// file: ks-config/src/listeners.rs +// version: 1 + +//! Independent Solana listener configuration document loading and validation. + +const LISTENERS_JSON_SCHEMA: &str = + include_str!("../../config/schemas/listeners.config.schema.json"); + +/// Root listener document containing independently selectable named profiles. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)] +pub struct ListenersConfigDocument { + /// Active listener profile used by consumers that do not provide an explicit selection. + pub active_profile: std::string::String, + /// Named listener profiles available in this document. + pub profiles: std::vec::Vec, +} + +/// Named listener profile. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)] +pub struct ListenersProfileConfig { + /// Profile code. + pub name: std::string::String, + /// Enables listener creation. + pub enabled: bool, + /// Default commitment used by listener subscriptions. + pub default_commitment: std::string::String, + /// Log listeners filtered by program mentions. + pub log_listeners: std::vec::Vec, + /// Program account listeners. + pub program_listeners: std::vec::Vec, + /// Account listeners. + pub account_listeners: std::vec::Vec, +} + +/// Returns the embedded listener JSON Schema text. +pub fn listeners_json_schema_text() -> &'static str { + return LISTENERS_JSON_SCHEMA; +} + +/// Parses the embedded listener JSON Schema into a JSON value. +pub fn listeners_json_schema_value() -> ks_core::Result { + let schema_result = serde_json::from_str::(LISTENERS_JSON_SCHEMA); + return match schema_result { + std::result::Result::Ok(schema) => std::result::Result::Ok(schema), + std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new( + "listeners_config_schema_parse_failed", + error.to_string(), + )), + }; +} + +/// Validates raw listener JSON against the embedded listener schema. +pub fn validate_listeners_json_schema(raw_json: &str) -> ks_core::Result<()> { + let schema = match listeners_json_schema_value() { + std::result::Result::Ok(schema) => schema, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instance = match serde_json::from_str::(raw_json) { + std::result::Result::Ok(instance) => instance, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "listeners_config_json_parse_failed", + error.to_string(), + )); + }, + }; + let validator = match jsonschema::validator_for(&schema) { + std::result::Result::Ok(validator) => validator, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "listeners_config_schema_compile_failed", + error.to_string(), + )); + }, + }; + return match validator.validate(&instance) { + std::result::Result::Ok(()) => std::result::Result::Ok(()), + std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new( + "listeners_config_schema_validation_failed", + error.to_string(), + )), + }; +} + +/// Parses and validates one independent listener configuration document. +pub fn parse_listeners_json(raw_json: &str) -> ks_core::Result { + match validate_listeners_json_schema(raw_json) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let document = match serde_json::from_str::(raw_json) { + std::result::Result::Ok(document) => document, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "listeners_config_json_decode_failed", + error.to_string(), + )); + }, + }; + return match validate_listeners_document(&document) { + std::result::Result::Ok(()) => std::result::Result::Ok(document), + std::result::Result::Err(error) => std::result::Result::Err(error), + }; +} + +/// Reads and parses one listener configuration document from a filesystem path. +pub fn read_listeners_json_file( + path: &std::path::Path, +) -> ks_core::Result { + let raw_json = match std::fs::read_to_string(path) { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "listeners_config_file_read_failed", + error.to_string(), + )); + }, + }; + return parse_listeners_json(&raw_json); +} + +/// Loads workspace environment values, resolves placeholders and parses listener configuration. +pub fn read_listeners_json_file_with_environment( + path: &std::path::Path, + workspace_root: &std::path::Path, +) -> ks_core::Result { + match crate::load_workspace_environment(workspace_root) { + std::result::Result::Ok(_) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let raw_json = match std::fs::read_to_string(path) { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "listeners_config_file_read_failed", + error.to_string(), + )); + }, + }; + let resolved = crate::resolve_environment_placeholders(&raw_json); + return parse_listeners_json(&resolved); +} + +/// Returns one named listener profile. +pub fn listeners_profile<'a>( + document: &'a ListenersConfigDocument, + profile_name: &str, +) -> ks_core::Result<&'a ListenersProfileConfig> { + for profile in &document.profiles { + if profile.name == profile_name { + return std::result::Result::Ok(profile); + } + } + return std::result::Result::Err(ks_core::Error::new( + "listeners_profile_not_found", + profile_name.to_string(), + )); +} + +/// Returns the active listener profile declared by the document. +pub fn active_listeners_profile( + document: &ListenersConfigDocument, +) -> ks_core::Result<&ListenersProfileConfig> { + return listeners_profile(document, document.active_profile.as_str()); +} + +/// Converts one named listener profile into the resolved runtime listener contract. +pub fn resolved_listener_config(profile: &ListenersProfileConfig) -> crate::ListenerConfig { + return crate::ListenerConfig { + enabled: profile.enabled, + default_commitment: profile.default_commitment.clone(), + log_listeners: profile.log_listeners.clone(), + program_listeners: profile.program_listeners.clone(), + account_listeners: profile.account_listeners.clone(), + }; +} + +/// Validates a typed listener configuration document. +pub fn validate_listeners_document(document: &ListenersConfigDocument) -> ks_core::Result<()> { + match crate::require_non_empty(&document.active_profile, "active_profile") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if document.profiles.is_empty() { + return std::result::Result::Err(ks_core::Error::new( + "listeners_config_profiles_empty", + "at least one listener profile is required", + )); + } + let mut names = std::collections::BTreeSet::::new(); + for profile in &document.profiles { + match crate::require_non_empty(&profile.name, "listeners.profile.name") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if !names.insert(profile.name.clone()) { + return std::result::Result::Err(ks_core::Error::new( + "listeners_profile_name_duplicate", + profile.name.clone(), + )); + } + let resolved = resolved_listener_config(profile); + match crate::validate_listeners(&resolved) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + return match active_listeners_profile(document) { + std::result::Result::Ok(_) => std::result::Result::Ok(()), + std::result::Result::Err(error) => std::result::Result::Err(error), + }; +} + +#[cfg(test)] +mod tests { + const DEFAULT_LISTENERS: &str = include_str!("../../config/listeners.config.json"); + const EXAMPLE_LISTENERS: &str = include_str!("../../config/example.listeners.config.json"); + + #[test] + fn default_and_example_listener_configs_validate() { + assert!(super::parse_listeners_json(DEFAULT_LISTENERS).is_ok()); + assert!(super::parse_listeners_json(EXAMPLE_LISTENERS).is_ok()); + } +} diff --git a/ks-config/src/settings.rs b/ks-config/src/settings.rs index 92e7e9d..76b76db 100644 --- a/ks-config/src/settings.rs +++ b/ks-config/src/settings.rs @@ -1,23 +1,24 @@ // file: ks-config/src/settings.rs -// version: 20 +// version: 21 -//! Typed configuration models shared by applications and workers. +//! Resolved runtime configuration models retained during the `0.5.1` source-document split. use ts_rs::TS; // rust-rules: derive-import -const CONFIG_JSON_SCHEMA: &str = include_str!("../../config/schemas/app.config.schema.json"); +const CONFIG_JSON_SCHEMA: &str = + include_str!("../../config/schemas/resolved.app.config.schema.json"); -/// Root configuration containing every named profile. +/// Resolved runtime configuration containing every composed named profile. #[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] #[ts(export, export_to = "../frontend/ts/bindings/ks_config/settings/AppConfig.ts")] pub struct AppConfig { /// Active profile name. pub active_profile: std::string::String, - /// Named profiles available in this configuration file. + /// Named profiles reconstructed from the active binary composition and shared documents. pub profiles: std::vec::Vec, } -/// Configuration profile selected by the root active profile name. +/// Resolved runtime profile selected by the root active profile name. #[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize, TS)] #[ts( export, @@ -382,12 +383,12 @@ pub struct DemoConfig { pub trading_demo_enabled: bool, } -/// Returns the embedded JSON Schema text used for configuration validation. +/// Returns the embedded JSON Schema text used for resolved runtime contract validation. pub fn config_json_schema_text() -> &'static str { return CONFIG_JSON_SCHEMA; } -/// Parses the embedded JSON Schema into a JSON value. +/// Parses the embedded resolved runtime JSON Schema into a JSON value. pub fn config_json_schema_value() -> ks_core::Result { let schema_result = serde_json::from_str::(CONFIG_JSON_SCHEMA); return match schema_result { @@ -399,7 +400,7 @@ pub fn config_json_schema_value() -> ks_core::Result { }; } -/// Validates a raw JSON configuration string against the embedded JSON Schema. +/// Validates a raw resolved runtime JSON string against the embedded compatibility schema. pub fn validate_config_json_schema(raw_json: &str) -> ks_core::Result<()> { let schema = match config_json_schema_value() { std::result::Result::Ok(schema) => schema, @@ -433,7 +434,7 @@ pub fn validate_config_json_schema(raw_json: &str) -> ks_core::Result<()> { }; } -/// Parses the application configuration from a JSON string and validates it. +/// Parses the resolved runtime application contract from a JSON string and validates it. pub fn parse_config_json(raw_json: &str) -> ks_core::Result { match validate_config_json_schema(raw_json) { std::result::Result::Ok(()) => (), @@ -454,7 +455,7 @@ pub fn parse_config_json(raw_json: &str) -> ks_core::Result { }; } -/// Reads and parses the application configuration from a filesystem path. +/// Reads and parses a resolved runtime application snapshot from a filesystem path. pub fn read_config_json_file(path: &std::path::Path) -> ks_core::Result { let raw_json = match std::fs::read_to_string(path) { std::result::Result::Ok(content) => content, @@ -468,7 +469,7 @@ pub fn read_config_json_file(path: &std::path::Path) -> ks_core::Result ks_core::Result<()> { return validate_listeners(&config.listeners); } -fn validate_http_endpoint(config: &HttpEndpointConfig) -> ks_core::Result<()> { +pub(crate) fn validate_http_endpoint(config: &HttpEndpointConfig) -> ks_core::Result<()> { match validate_endpoint_common(&config.name, &config.provider, &config.cluster, &config.roles) { std::result::Result::Ok(()) => (), std::result::Result::Err(error) => return std::result::Result::Err(error), @@ -731,7 +732,7 @@ fn validate_http_endpoint(config: &HttpEndpointConfig) -> ks_core::Result<()> { return std::result::Result::Ok(()); } -fn validate_ws_endpoint(config: &WsEndpointConfig) -> ks_core::Result<()> { +pub(crate) fn validate_ws_endpoint(config: &WsEndpointConfig) -> ks_core::Result<()> { match validate_endpoint_common(&config.name, &config.provider, &config.cluster, &config.roles) { std::result::Result::Ok(()) => (), std::result::Result::Err(error) => return std::result::Result::Err(error), @@ -826,7 +827,7 @@ fn validate_endpoint_role(config: &EndpointRoleConfig) -> ks_core::Result<()> { return std::result::Result::Ok(()); } -fn validate_listeners(config: &ListenerConfig) -> ks_core::Result<()> { +pub(crate) fn validate_listeners(config: &ListenerConfig) -> ks_core::Result<()> { match require_non_empty(&config.default_commitment, "solana.listeners.default_commitment") { std::result::Result::Ok(()) => (), std::result::Result::Err(error) => return std::result::Result::Err(error), @@ -1015,7 +1016,7 @@ fn validate_wallet_execution_pair( return std::result::Result::Ok(()); } -fn require_non_empty(value: &str, field_name: &str) -> ks_core::Result<()> { +pub(crate) fn require_non_empty(value: &str, field_name: &str) -> ks_core::Result<()> { if value.trim().is_empty() { return std::result::Result::Err(ks_core::Error::new( "config_field_empty", @@ -1045,14 +1046,18 @@ mod tests { } } - const DEFAULT_CONFIG: &str = include_str!("../../config/app.config.json"); - const EXAMPLE_CONFIG: &str = include_str!("../../config/example.app.config.json"); + const DEFAULT_CONFIG: &str = + include_str!("../../test-fixtures/config/resolved.app.config.json"); + const EXAMPLE_CONFIG: &str = + include_str!("../../test-fixtures/config/example.resolved.app.config.json"); fn parse_default_value() -> serde_json::Value { let result = serde_json::from_str::(DEFAULT_CONFIG); match result { std::result::Result::Ok(value) => return value, - std::result::Result::Err(error) => panic!("default app config must be valid JSON: {error}"), + std::result::Result::Err(error) => { + panic!("default app config must be valid JSON: {error}") + }, } } diff --git a/ks-config/src/transport.rs b/ks-config/src/transport.rs new file mode 100644 index 0000000..613a722 --- /dev/null +++ b/ks-config/src/transport.rs @@ -0,0 +1,240 @@ +// file: ks-config/src/transport.rs +// version: 1 + +//! Independent Solana transport configuration document loading and validation. + +const TRANSPORT_JSON_SCHEMA: &str = + include_str!("../../config/schemas/transport.config.schema.json"); + +/// Root transport document containing independently selectable named profiles. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)] +pub struct TransportConfigDocument { + /// Active transport profile used by consumers that do not provide an explicit selection. + pub active_profile: std::string::String, + /// Named transport profiles available in this document. + pub profiles: std::vec::Vec, +} + +/// Named Solana HTTP and WebSocket endpoint profile. +#[derive(Clone, Debug, serde::Deserialize, Eq, PartialEq, serde::Serialize)] +pub struct TransportProfileConfig { + /// Profile code. + pub name: std::string::String, + /// HTTP JSON-RPC endpoints. + pub http_endpoints: std::vec::Vec, + /// Standard Solana WebSocket endpoints. + pub ws_endpoints: std::vec::Vec, +} + +/// Returns the embedded transport JSON Schema text. +pub fn transport_json_schema_text() -> &'static str { + return TRANSPORT_JSON_SCHEMA; +} + +/// Parses the embedded transport JSON Schema into a JSON value. +pub fn transport_json_schema_value() -> ks_core::Result { + let schema_result = serde_json::from_str::(TRANSPORT_JSON_SCHEMA); + return match schema_result { + std::result::Result::Ok(schema) => std::result::Result::Ok(schema), + std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new( + "transport_config_schema_parse_failed", + error.to_string(), + )), + }; +} + +/// Validates raw transport JSON against the embedded transport schema. +pub fn validate_transport_json_schema(raw_json: &str) -> ks_core::Result<()> { + let schema = match transport_json_schema_value() { + std::result::Result::Ok(schema) => schema, + std::result::Result::Err(error) => return std::result::Result::Err(error), + }; + let instance = match serde_json::from_str::(raw_json) { + std::result::Result::Ok(instance) => instance, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "transport_config_json_parse_failed", + error.to_string(), + )); + }, + }; + let validator = match jsonschema::validator_for(&schema) { + std::result::Result::Ok(validator) => validator, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "transport_config_schema_compile_failed", + error.to_string(), + )); + }, + }; + return match validator.validate(&instance) { + std::result::Result::Ok(()) => std::result::Result::Ok(()), + std::result::Result::Err(error) => std::result::Result::Err(ks_core::Error::new( + "transport_config_schema_validation_failed", + error.to_string(), + )), + }; +} + +/// Parses and validates one independent transport configuration document. +pub fn parse_transport_json(raw_json: &str) -> ks_core::Result { + match validate_transport_json_schema(raw_json) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let document = match serde_json::from_str::(raw_json) { + std::result::Result::Ok(document) => document, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "transport_config_json_decode_failed", + error.to_string(), + )); + }, + }; + return match validate_transport_document(&document) { + std::result::Result::Ok(()) => std::result::Result::Ok(document), + std::result::Result::Err(error) => std::result::Result::Err(error), + }; +} + +/// Reads and parses one transport configuration document from a filesystem path. +pub fn read_transport_json_file( + path: &std::path::Path, +) -> ks_core::Result { + let raw_json = match std::fs::read_to_string(path) { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "transport_config_file_read_failed", + error.to_string(), + )); + }, + }; + return parse_transport_json(&raw_json); +} + +/// Loads workspace environment values, resolves placeholders and parses transport configuration. +pub fn read_transport_json_file_with_environment( + path: &std::path::Path, + workspace_root: &std::path::Path, +) -> ks_core::Result { + match crate::load_workspace_environment(workspace_root) { + std::result::Result::Ok(_) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + let raw_json = match std::fs::read_to_string(path) { + std::result::Result::Ok(content) => content, + std::result::Result::Err(error) => { + return std::result::Result::Err(ks_core::Error::new( + "transport_config_file_read_failed", + error.to_string(), + )); + }, + }; + let resolved = crate::resolve_environment_placeholders(&raw_json); + return parse_transport_json(&resolved); +} + +/// Returns one named transport profile. +pub fn transport_profile<'a>( + document: &'a TransportConfigDocument, + profile_name: &str, +) -> ks_core::Result<&'a TransportProfileConfig> { + for profile in &document.profiles { + if profile.name == profile_name { + return std::result::Result::Ok(profile); + } + } + return std::result::Result::Err(ks_core::Error::new( + "transport_profile_not_found", + profile_name.to_string(), + )); +} + +/// Returns the active transport profile declared by the document. +pub fn active_transport_profile( + document: &TransportConfigDocument, +) -> ks_core::Result<&TransportProfileConfig> { + return transport_profile(document, document.active_profile.as_str()); +} + +/// Validates a typed transport configuration document. +pub fn validate_transport_document(document: &TransportConfigDocument) -> ks_core::Result<()> { + match crate::require_non_empty(&document.active_profile, "active_profile") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if document.profiles.is_empty() { + return std::result::Result::Err(ks_core::Error::new( + "transport_config_profiles_empty", + "at least one transport profile is required", + )); + } + let mut names = std::collections::BTreeSet::::new(); + for profile in &document.profiles { + match crate::require_non_empty(&profile.name, "transport.profile.name") { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + if !names.insert(profile.name.clone()) { + return std::result::Result::Err(ks_core::Error::new( + "transport_profile_name_duplicate", + profile.name.clone(), + )); + } + if profile.http_endpoints.is_empty() || profile.ws_endpoints.is_empty() { + return std::result::Result::Err(ks_core::Error::new( + "transport_profile_endpoints_empty", + profile.name.clone(), + )); + } + for endpoint in &profile.http_endpoints { + match crate::validate_http_endpoint(endpoint) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + for endpoint in &profile.ws_endpoints { + match crate::validate_ws_endpoint(endpoint) { + std::result::Result::Ok(()) => (), + std::result::Result::Err(error) => return std::result::Result::Err(error), + } + } + } + return match active_transport_profile(document) { + std::result::Result::Ok(_) => std::result::Result::Ok(()), + std::result::Result::Err(error) => std::result::Result::Err(error), + }; +} + +#[cfg(test)] +mod tests { + const DEFAULT_TRANSPORT: &str = include_str!("../../config/transport.config.json"); + const EXAMPLE_TRANSPORT: &str = include_str!("../../config/example.transport.config.json"); + + #[test] + fn default_and_example_transport_configs_validate() { + assert!(super::parse_transport_json(DEFAULT_TRANSPORT).is_ok()); + assert!(super::parse_transport_json(EXAMPLE_TRANSPORT).is_ok()); + } + + #[test] + fn schema_rejects_unsupported_transport_surface() { + let value_result = serde_json::from_str::(DEFAULT_TRANSPORT); + let mut value = match value_result { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + panic!("default transport config must parse: {error}") + }, + }; + value["profiles"][0]["advanced_streams"] = serde_json::json!([]); + let raw_result = serde_json::to_string(&value); + let raw = match raw_result { + std::result::Result::Ok(raw) => raw, + std::result::Result::Err(error) => { + panic!("transport test value must serialize: {error}") + }, + }; + assert!(super::validate_transport_json_schema(&raw).is_err()); + } +} diff --git a/ks-config/tests/external_composition_api.rs b/ks-config/tests/external_composition_api.rs new file mode 100644 index 0000000..d6f1be4 --- /dev/null +++ b/ks-config/tests/external_composition_api.rs @@ -0,0 +1,32 @@ +// file: ks-config/tests/external_composition_api.rs +// version: 1 + +//! External API coverage for binary composition and shared configuration documents. + +#[test] +fn external_consumer_resolves_composition_through_crate_root_exports() { + let composition = match ks_config::parse_composition_json(include_str!( + "../../config/example.kb-app-demo-desktop.default.config.json" + )) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("composition must parse: {error}"), + }; + let transport = match ks_config::parse_transport_json(include_str!( + "../../config/example.transport.config.json" + )) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("transport must parse: {error}"), + }; + let listeners = match ks_config::parse_listeners_json(include_str!( + "../../config/example.listeners.config.json" + )) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("listeners must parse: {error}"), + }; + let resolved = match ks_config::compose_app_config(&composition, &transport, &listeners) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("composition must resolve: {error}"), + }; + assert_eq!(resolved.active_profile, composition.active_profile); + assert_eq!(resolved.profiles.len(), composition.profiles.len()); +} diff --git a/ks-logging/CHANGELOG.md b/ks-logging/CHANGELOG.md index 11339f2..4fc55ac 100644 --- a/ks-logging/CHANGELOG.md +++ b/ks-logging/CHANGELOG.md @@ -1,8 +1,13 @@ - + # CHANGELOG — ks-logging +## `0.5.1-pre.006` + +- ajoute la sélection explicite d'un profil logging nommé afin qu'une composition binaire puisse choisir un profil indépendamment du `active_profile` propre au document logging ; +- conserve `config/logging.config.json` comme document partagé autonome, référencé par les compositions plutôt qu'imposé par un fichier applicatif monolithique. + ## `0.5.1-pre.005` - devient propriétaire unique des types `LoggingConfig`, `LogTargetConfig` et `LogTargetFilterConfig` ; diff --git a/ks-logging/README.md b/ks-logging/README.md index 89e7ba8..0d8a1fa 100644 --- a/ks-logging/README.md +++ b/ks-logging/README.md @@ -1,5 +1,5 @@ - + # ks-logging @@ -20,13 +20,13 @@ `ks-logging` dépend de `ks-config` uniquement pour le chargement du fichier `.env` et la résolution générique `${KS_*}` / `${KB_*}`. `ks-config` ne dépend pas de `ks-logging` et ne duplique aucun type logging. -Le desktop charge `config/app.config.json` et `config/logging.config.json` séparément puis transmet directement le profil logging actif à `init_logging`. +Le desktop lit le chemin `logging` et le `logging_profile` depuis `config/kb-app-demo-desktop.default.config.json`, charge le document logging puis transmet le profil explicitement sélectionné à `init_logging`. ## API publique - `read_logging_json_file_with_environment` charge le document logging ; - `parse_logging_json` et `validate_logging_json_schema` valident le format ; -- `active_logging_profile` sélectionne le profil actif ; +- `active_logging_profile` sélectionne le profil autonome actif et `logging_profile` résout un profil explicitement nommé par une composition ; - `LoggingConfigDocument` et `LoggingProfileConfig` décrivent le document ; - `LoggingConfig`, `LogTargetConfig` et `LogTargetFilterConfig` décrivent le runtime ; - `init_logging` initialise le subscriber global ; diff --git a/ks-logging/USAGE.md b/ks-logging/USAGE.md index 136509e..175acc8 100644 --- a/ks-logging/USAGE.md +++ b/ks-logging/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de ks-logging @@ -23,7 +23,7 @@ let guard = match ks_logging::init_logging(config) { }; ``` -Le desktop utilise `config/logging.config.json` par défaut. `KS_LOGGING_CONFIG_PATH` peut sélectionner un autre document sans modifier `KS_CONFIG_PATH` ni le profil applicatif. +Le desktop utilise normalement le chemin déclaré dans sa composition `config/kb-app-demo-desktop.default.config.json`. `KS_LOGGING_CONFIG_PATH` peut encore remplacer explicitement ce chemin sans modifier le fichier de composition. ## Format du document @@ -47,7 +47,7 @@ Le schéma formel est `config/schemas/logging.config.schema.json`. `config/examp ## Sélection indépendante -Le `active_profile` logging n’est jamais déduit de `app.config.json`. Un opérateur peut donc utiliser, par exemple, un profil applicatif `mainnet_research` avec un profil logging `local_devnet` ou tout autre profil logging explicitement défini. +Le `active_profile` logging reste le défaut autonome du document. Lorsqu’un binaire utilise une composition, il sélectionne explicitement son `logging_profile` avec `ks_logging::logging_profile`; cette sélection prime pour ce binaire sans modifier le document partagé. ## Construction directe diff --git a/ks-logging/src/document.rs b/ks-logging/src/document.rs index e4556c9..9428e60 100644 --- a/ks-logging/src/document.rs +++ b/ks-logging/src/document.rs @@ -1,5 +1,5 @@ // file: ks-logging/src/document.rs -// version: 1 +// version: 2 //! Independent logging configuration document loading and validation. @@ -132,21 +132,29 @@ pub fn read_logging_json_file_with_environment( return parse_logging_json(&resolved); } -/// Returns the active runtime logging profile selected by the logging document. -pub fn active_logging_profile( - document: &LoggingConfigDocument, -) -> ks_core::Result<&crate::LoggingConfig> { +/// Returns one named runtime logging profile. +pub fn logging_profile<'a>( + document: &'a LoggingConfigDocument, + profile_name: &str, +) -> ks_core::Result<&'a crate::LoggingConfig> { for profile in &document.profiles { - if profile.name == document.active_profile { + if profile.name == profile_name { return std::result::Result::Ok(&profile.config); } } return std::result::Result::Err(ks_core::Error::new( - "active_logging_profile_not_found", - document.active_profile.clone(), + "logging_profile_not_found", + profile_name.to_string(), )); } +/// Returns the active runtime logging profile selected by the logging document. +pub fn active_logging_profile( + document: &LoggingConfigDocument, +) -> ks_core::Result<&crate::LoggingConfig> { + return logging_profile(document, document.active_profile.as_str()); +} + /// Validates a typed logging configuration document. pub fn validate_logging_document(document: &LoggingConfigDocument) -> ks_core::Result<()> { match require_non_empty(&document.active_profile, "active_profile") { @@ -330,7 +338,9 @@ mod tests { let result = serde_json::from_str::(DEFAULT_LOGGING_CONFIG); return match result { std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => panic!("default logging config must be JSON: {error}"), + std::result::Result::Err(error) => { + panic!("default logging config must be JSON: {error}") + }, }; } @@ -357,7 +367,9 @@ mod tests { for entry_result in entries { let entry = match entry_result { std::result::Result::Ok(entry) => entry, - std::result::Result::Err(error) => panic!("workspace entry must be readable: {error}"), + std::result::Result::Err(error) => { + panic!("workspace entry must be readable: {error}") + }, }; let cargo_path = entry.path().join("Cargo.toml"); if !cargo_path.is_file() { @@ -413,6 +425,7 @@ mod tests { assert_eq!(document.active_profile, "mainnet_research"); assert_eq!(document.profiles.len(), 3); assert!(super::active_logging_profile(&document).is_ok()); + assert!(super::logging_profile(&document, "local_devnet").is_ok()); } #[test] diff --git a/ks-logging/src/lib.rs b/ks-logging/src/lib.rs index 27b5297..bb48574 100644 --- a/ks-logging/src/lib.rs +++ b/ks-logging/src/lib.rs @@ -1,5 +1,5 @@ // file: ks-logging/src/lib.rs -// version: 8 +// version: 9 //! Logging and tracing initialization for applications and workers. #![warn(missing_docs)] @@ -32,6 +32,8 @@ pub use self::document::active_logging_profile; pub use self::document::logging_json_schema_text; /// Exposes the embedded logging JSON Schema parser. pub use self::document::logging_json_schema_value; +/// Exposes one named logging profile resolver. +pub use self::document::logging_profile; /// Exposes logging configuration parsing from JSON. pub use self::document::parse_logging_json; /// Exposes logging configuration loading from a filesystem path. diff --git a/ks-onchain-transport/CHANGELOG.md b/ks-onchain-transport/CHANGELOG.md index 8a364c0..fc1926f 100644 --- a/ks-onchain-transport/CHANGELOG.md +++ b/ks-onchain-transport/CHANGELOG.md @@ -1,8 +1,13 @@ - + # CHANGELOG — ks-onchain-transport +## `0.5.1-pre.006` + +- permet de construire directement les pools HTTP et WebSocket depuis `ks_config::TransportProfileConfig` ; +- conserve temporairement les constructeurs `from_profile` sur le contrat runtime résolu pour compatibilité pendant la décomposition de configuration. + ## `0.5.1-pre.002` - renomme `kb-onchain-transport` en `ks-onchain-transport` et `kb_onchain_transport` en `ks_onchain_transport` ; diff --git a/ks-onchain-transport/README.md b/ks-onchain-transport/README.md index 2f227df..f988738 100644 --- a/ks-onchain-transport/README.md +++ b/ks-onchain-transport/README.md @@ -1,5 +1,5 @@ - + # ks-onchain-transport @@ -10,6 +10,7 @@ - requêtes JSON-RPC HTTP standard ; - sessions et pools WebSocket ; - routage par rôle d’endpoint ; +- consommation directe des profils partagés définis par `config/transport.config.json` via `ks-config` ; - adaptation des réponses RPC vers les contrats canoniques ; - simulation, soumission et confirmation de transactions ; - acquisition de signatures et transactions pour le backfill ; diff --git a/ks-onchain-transport/USAGE.md b/ks-onchain-transport/USAGE.md index be5e504..c05949c 100644 --- a/ks-onchain-transport/USAGE.md +++ b/ks-onchain-transport/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de ks-onchain-transport @@ -9,7 +9,7 @@ La crate expose les contrats publics nécessaires aux communications RPC HTTP et ## Prérequis -- une configuration `ks-config` contenant au moins un endpoint compatible ; +- un profil de transport `ks-config::TransportProfileConfig` contenant au moins un endpoint compatible ; - un runtime Tokio pour les opérations asynchrones ; - des limites et engagements adaptés à l’opération demandée. @@ -32,9 +32,9 @@ fn build_http_client( ```rust fn select_query_client( - profile: &ks_config::ProfileConfig, + profile: &ks_config::TransportProfileConfig, ) -> ks_core::Result { - let pool_result = ks_onchain_transport::HttpEndpointPool::from_profile(profile); + let pool_result = ks_onchain_transport::HttpEndpointPool::from_transport_profile(profile); let pool = match pool_result { Ok(pool) => pool, Err(error) => return Err(error), @@ -44,7 +44,7 @@ fn select_query_client( } ``` -`HttpEndpointPool::snapshot` fournit un état sérialisable des endpoints actifs et de leurs rôles. +`HttpEndpointPool::snapshot` fournit un état sérialisable des endpoints actifs et de leurs rôles. Les constructeurs `from_profile` restent temporairement disponibles pour le contrat runtime `ProfileConfig` résolu pendant la migration `0.5.1`. ## Classer une méthode RPC diff --git a/ks-onchain-transport/src/http_pool.rs b/ks-onchain-transport/src/http_pool.rs index 9a1db7a..0e90565 100644 --- a/ks-onchain-transport/src/http_pool.rs +++ b/ks-onchain-transport/src/http_pool.rs @@ -1,5 +1,5 @@ // file: ks-onchain-transport/src/http_pool.rs -// version: 9 +// version: 10 //! HTTP endpoint pool and role-based routing. @@ -11,10 +11,21 @@ pub struct HttpEndpointPool { } impl crate::HttpEndpointPool { - /// Builds a pool from the active profile HTTP endpoint list. + /// Builds a pool from an independently selected transport profile. + pub fn from_transport_profile( + profile: &ks_config::TransportProfileConfig, + ) -> ks_core::Result { + return crate::HttpEndpointPool::from_http_endpoints(&profile.http_endpoints); + } + + /// Builds a pool from the resolved application profile HTTP endpoint list. pub fn from_profile(profile: &ks_config::ProfileConfig) -> ks_core::Result { + return crate::HttpEndpointPool::from_http_endpoints(&profile.solana.http_endpoints); + } + + fn from_http_endpoints(endpoints: &[ks_config::HttpEndpointConfig]) -> ks_core::Result { let mut clients = std::vec::Vec::new(); - for endpoint in &profile.solana.http_endpoints { + for endpoint in endpoints { if !endpoint.enabled { continue; } @@ -184,6 +195,26 @@ mod tests { } } + #[test] + fn transport_profile_builds_http_pool_without_resolved_app_profile() { + let profile = ks_config::TransportProfileConfig { + name: "direct".to_string(), + http_endpoints: std::vec![endpoint( + "direct", + "http_queries", + std::vec!["get_version".to_string()], + )], + ws_endpoints: std::vec::Vec::new(), + }; + let pool = match crate::HttpEndpointPool::from_transport_profile(&profile) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + panic!("transport profile pool creation failed: {error}") + }, + }; + assert_eq!(pool.snapshot().len(), 1); + } + #[test] fn new_rejects_empty_pool() { let result = crate::HttpEndpointPool::new(std::vec::Vec::new()); diff --git a/ks-onchain-transport/src/ws_pool.rs b/ks-onchain-transport/src/ws_pool.rs index fc87f72..92ba166 100644 --- a/ks-onchain-transport/src/ws_pool.rs +++ b/ks-onchain-transport/src/ws_pool.rs @@ -1,5 +1,5 @@ // file: ks-onchain-transport/src/ws_pool.rs -// version: 7 +// version: 8 //! WebSocket endpoint pool and role-based routing. @@ -11,10 +11,21 @@ pub struct WsEndpointPool { } impl crate::WsEndpointPool { - /// Builds a pool from the active profile WebSocket endpoint list. + /// Builds a pool from an independently selected transport profile. + pub fn from_transport_profile( + profile: &ks_config::TransportProfileConfig, + ) -> ks_core::Result { + return crate::WsEndpointPool::from_ws_endpoints(&profile.ws_endpoints); + } + + /// Builds a pool from the resolved application profile WebSocket endpoint list. pub fn from_profile(profile: &ks_config::ProfileConfig) -> ks_core::Result { + return crate::WsEndpointPool::from_ws_endpoints(&profile.solana.ws_endpoints); + } + + fn from_ws_endpoints(endpoints: &[ks_config::WsEndpointConfig]) -> ks_core::Result { let mut clients = std::vec::Vec::new(); - for endpoint in &profile.solana.ws_endpoints { + for endpoint in endpoints { if !endpoint.enabled { continue; } @@ -164,6 +175,26 @@ mod tests { } } + #[test] + fn transport_profile_builds_ws_pool_without_resolved_app_profile() { + let profile = ks_config::TransportProfileConfig { + name: "direct".to_string(), + http_endpoints: std::vec::Vec::new(), + ws_endpoints: std::vec![endpoint( + "direct", + "slot_notifications", + std::vec!["slot_subscribe".to_string()], + )], + }; + let pool = match crate::WsEndpointPool::from_transport_profile(&profile) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => { + panic!("transport profile pool creation failed: {error}") + }, + }; + assert_eq!(pool.snapshot().len(), 1); + } + #[test] fn new_rejects_empty_pool() { let result = crate::WsEndpointPool::new(std::vec::Vec::new()); diff --git a/ks-pipeline-demo-scenarios/CHANGELOG.md b/ks-pipeline-demo-scenarios/CHANGELOG.md index 004bd98..b14035c 100644 --- a/ks-pipeline-demo-scenarios/CHANGELOG.md +++ b/ks-pipeline-demo-scenarios/CHANGELOG.md @@ -1,8 +1,14 @@ - + # CHANGELOG — ks-pipeline-demo-scenarios +## `0.5.1-pre.006` + +- remplace la dépendance runtime au document applicatif monolithique par `config/ks-pipeline-demo-scenarios.default.config.json` pour les scénarios Devnet opt-in ; +- résout la composition par `ks-config`, puis conserve le contrat `AppConfig/ProfileConfig` runtime existant afin de ne pas requalifier les scénarios réseau ; +- aligne les fixtures unitaires sur les snapshots runtime sous `test-fixtures/config/` plutôt que sur une configuration source de binaire. + ## `0.5.1-pre.005` - aligne les scénarios et fixtures sur `config/app.config.json` comme document général par défaut ; diff --git a/ks-pipeline-demo-scenarios/USAGE.md b/ks-pipeline-demo-scenarios/USAGE.md index b859e9d..76e2f66 100644 --- a/ks-pipeline-demo-scenarios/USAGE.md +++ b/ks-pipeline-demo-scenarios/USAGE.md @@ -1,5 +1,5 @@ - + # Utilisation de ks-pipeline-demo-scenarios @@ -113,7 +113,7 @@ KS_SECRET_POSTGRES_TEST_URL='postgresql://…' \ cargo test -p ks-pipeline-demo-scenarios optional_devnet_token_2022_metadata_campaign_from_env -- --nocapture ``` -`KS_DEVNET_PROFILE` peut sélectionner explicitement le profil Devnet et `KS_DEVNET_CONFIG_PATH` peut remplacer `config/app.config.json`. Le profil doit utiliser un wallet persistant, autoriser les soumissions Devnet et respecter les plafonds de dépense et de frais. +`KS_DEVNET_PROFILE` peut sélectionner explicitement le profil Devnet et `KS_DEVNET_CONFIG_PATH` peut remplacer la composition `config/ks-pipeline-demo-scenarios.default.config.json`. La composition résout ensuite les profils transport/listeners et le profil runtime doit utiliser un wallet persistant, autoriser les soumissions Devnet et respecter les plafonds de dépense et de frais. La sortie `TOKEN_2022_METADATA_FIXTURE` conserve le mint et la signature de préparation. Chaque ligne `TOKEN_2022_METADATA_STEP` conserve l’opération, la signature, le slot, la postcondition, le nombre de matérialisations et, pour `Emit`, la taille du `returnData`. La campagne de référence de `pre.012` a été exécutée avec succès et ses preuves sont conservées dans `SPL_TOKEN_2022_METADATA_DEVNET_VALIDATION_MATRIX.json` et dans le rapport `docs/validation/V0_4_8_METADATA_VALIDATION_REPORT.md` ; le rapport détaillé `pre.012` est archivé sous `olddocs/archivekbot3/docs/validation/`. Une nouvelle exécution produit de nouvelles transactions et ne remplace ces preuves qu’après validation explicite. diff --git a/ks-pipeline-demo-scenarios/src/environment.rs b/ks-pipeline-demo-scenarios/src/environment.rs index 88c68ed..ca8727f 100644 --- a/ks-pipeline-demo-scenarios/src/environment.rs +++ b/ks-pipeline-demo-scenarios/src/environment.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/environment.rs -// version: 5 +// version: 6 //! Environment initialization for opt-in demonstration scenarios. @@ -151,8 +151,9 @@ pub async fn prepare_demo_devnet_profile_store( #[cfg(test)] mod tests { fn example_config() -> ks_config::AppConfig { - return match ks_config::parse_config_json(include_str!("../../config/app.config.json")) - { + return match ks_config::parse_config_json(include_str!( + "../../test-fixtures/config/resolved.app.config.json" + )) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("example config parse failed: {error}"), }; diff --git a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/collection_verify_campaign.rs b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/collection_verify_campaign.rs index acbe406..178477b 100644 --- a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/collection_verify_campaign.rs +++ b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/collection_verify_campaign.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/collection_verify_campaign.rs -// version: 4 +// version: 5 //! Confirmed Devnet collection parent/member `Verify -> Unverify` campaign. @@ -648,13 +648,16 @@ mod tests { let path = std::path::PathBuf::from(value); if path.is_absolute() { path } else { workspace_root.join(path) } }, - std::result::Result::Err(_) => workspace_root.join("config/app.config.json"), + std::result::Result::Err(_) => { + workspace_root.join("config/ks-pipeline-demo-scenarios.default.config.json") + }, }; - let config = ks_config::read_config_json_file_with_environment( + let composed = ks_config::read_composed_app_config_with_environment( config_path.as_path(), workspace_root, ) .unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}")); + let config = composed.app_config; let profile_name = std::env::var("KS_DEVNET_PROFILE").ok(); let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref()) .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); diff --git a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/create_mint_campaign.rs b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/create_mint_campaign.rs index 7f27937..519de6c 100644 --- a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/create_mint_campaign.rs +++ b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/create_mint_campaign.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/create_mint_campaign.rs -// version: 12 +// version: 13 //! Confirmed Devnet `Create -> Mint` campaign for one Metaplex asset family. @@ -813,13 +813,16 @@ mod tests { let path = std::path::PathBuf::from(value); if path.is_absolute() { path } else { workspace_root.join(path) } }, - std::result::Result::Err(_) => workspace_root.join("config/app.config.json"), + std::result::Result::Err(_) => { + workspace_root.join("config/ks-pipeline-demo-scenarios.default.config.json") + }, }; - let config = ks_config::read_config_json_file_with_environment( + let composed = ks_config::read_composed_app_config_with_environment( config_path.as_path(), workspace_root, ) .unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}")); + let config = composed.app_config; let profile_name = std::env::var("KS_DEVNET_PROFILE").ok(); let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref()) .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); diff --git a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs index 5ee48e7..328f99b 100644 --- a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs +++ b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/devnet_execution.rs -// version: 20 +// version: 21 //! Real Devnet simulation and submission for current Metaplex Token Metadata operations. @@ -1267,7 +1267,7 @@ fn validate_available_signers( mod tests { fn example_devnet_profile() -> ks_config::ProfileConfig { let config = match ks_config::parse_config_json(include_str!( - "../../../../config/app.config.json" + "../../../../test-fixtures/config/resolved.app.config.json" )) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("example config parse failed: {error}"), diff --git a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/escrow_campaign.rs b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/escrow_campaign.rs index 0ec675f..54a1831 100644 --- a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/escrow_campaign.rs +++ b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/escrow_campaign.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/escrow_campaign.rs -// version: 4 +// version: 5 //! Confirmed Devnet Token Owned Escrow campaign for current Metaplex escrow operations. @@ -973,13 +973,16 @@ mod tests { let path = std::path::PathBuf::from(value); if path.is_absolute() { path } else { workspace_root.join(path) } }, - std::result::Result::Err(_) => workspace_root.join("config/app.config.json"), + std::result::Result::Err(_) => { + workspace_root.join("config/ks-pipeline-demo-scenarios.default.config.json") + }, }; - let config = ks_config::read_config_json_file_with_environment( + let composed = ks_config::read_composed_app_config_with_environment( config_path.as_path(), workspace_root, ) .unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}")); + let config = composed.app_config; let profile_name = std::env::var("KS_DEVNET_PROFILE").ok(); let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref()) .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); diff --git a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/maintenance_campaign.rs b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/maintenance_campaign.rs index c5f2d8f..9590501 100644 --- a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/maintenance_campaign.rs +++ b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/maintenance_campaign.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/maintenance_campaign.rs -// version: 6 +// version: 7 //! Final bounded Devnet qualification campaign for current Metaplex maintenance operations. @@ -577,13 +577,16 @@ mod tests { let path = std::path::PathBuf::from(value); if path.is_absolute() { path } else { workspace_root.join(path) } }, - std::result::Result::Err(_) => workspace_root.join("config/app.config.json"), + std::result::Result::Err(_) => { + workspace_root.join("config/ks-pipeline-demo-scenarios.default.config.json") + }, }; - let config = ks_config::read_config_json_file_with_environment( + let composed = ks_config::read_composed_app_config_with_environment( config_path.as_path(), workspace_root, ) .unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}")); + let config = composed.app_config; let profile_name = std::env::var("KS_DEVNET_PROFILE").ok(); let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref()) .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); diff --git a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/pnft_lifecycle_campaign.rs b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/pnft_lifecycle_campaign.rs index f92e84a..c0cc234 100644 --- a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/pnft_lifecycle_campaign.rs +++ b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/pnft_lifecycle_campaign.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/pnft_lifecycle_campaign.rs -// version: 6 +// version: 7 //! Confirmed Devnet pNFT delegate, lock, unlock, revoke and transfer campaign. @@ -1071,13 +1071,16 @@ mod tests { let path = std::path::PathBuf::from(value); if path.is_absolute() { path } else { workspace_root.join(path) } }, - std::result::Result::Err(_) => workspace_root.join("config/app.config.json"), + std::result::Result::Err(_) => { + workspace_root.join("config/ks-pipeline-demo-scenarios.default.config.json") + }, }; - let config = ks_config::read_config_json_file_with_environment( + let composed = ks_config::read_composed_app_config_with_environment( config_path.as_path(), workspace_root, ) .unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}")); + let config = composed.app_config; let profile_name = std::env::var("KS_DEVNET_PROFILE").ok(); let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref()) .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); diff --git a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/print_burn_campaign.rs b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/print_burn_campaign.rs index e170219..6169b44 100644 --- a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/print_burn_campaign.rs +++ b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/print_burn_campaign.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/print_burn_campaign.rs -// version: 7 +// version: 8 //! Devnet printable master NFT `Print -> Burn` campaign. @@ -1180,13 +1180,16 @@ mod tests { let path = std::path::PathBuf::from(value); if path.is_absolute() { path } else { workspace_root.join(path) } }, - std::result::Result::Err(_) => workspace_root.join("config/app.config.json"), + std::result::Result::Err(_) => { + workspace_root.join("config/ks-pipeline-demo-scenarios.default.config.json") + }, }; - let config = ks_config::read_config_json_file_with_environment( + let composed = ks_config::read_composed_app_config_with_environment( config_path.as_path(), workspace_root, ) .unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}")); + let config = composed.app_config; let profile_name = std::env::var("KS_DEVNET_PROFILE").ok(); let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref()) .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); diff --git a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs index 383c7d9..431a551 100644 --- a/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs +++ b/ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/metadata/metaplex_token_metadata/use_campaign.rs -// version: 4 +// version: 5 //! Bounded Devnet availability probe for the current Metaplex `Use` surface. @@ -425,13 +425,16 @@ mod tests { let path = std::path::PathBuf::from(value); if path.is_absolute() { path } else { workspace_root.join(path) } }, - std::result::Result::Err(_) => workspace_root.join("config/app.config.json"), + std::result::Result::Err(_) => { + workspace_root.join("config/ks-pipeline-demo-scenarios.default.config.json") + }, }; - let config = ks_config::read_config_json_file_with_environment( + let composed = ks_config::read_composed_app_config_with_environment( config_path.as_path(), workspace_root, ) .unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}")); + let config = composed.app_config; let profile_name = std::env::var("KS_DEVNET_PROFILE").ok(); let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref()) .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); diff --git a/ks-pipeline-demo-scenarios/src/solana.rs b/ks-pipeline-demo-scenarios/src/solana.rs index 83bf8d6..30712b4 100644 --- a/ks-pipeline-demo-scenarios/src/solana.rs +++ b/ks-pipeline-demo-scenarios/src/solana.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/solana.rs -// version: 14 +// version: 15 //! Devnet Solana execution orchestration with canonical post-validation. @@ -1224,11 +1224,12 @@ pub(crate) fn emit( #[cfg(test)] mod tests { fn example_devnet_profile() -> ks_config::ProfileConfig { - let config = - match ks_config::parse_config_json(include_str!("../../config/app.config.json")) { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => panic!("example config parse failed: {error}"), - }; + let config = match ks_config::parse_config_json(include_str!( + "../../test-fixtures/config/resolved.app.config.json" + )) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("example config parse failed: {error}"), + }; return match crate::resolve_demo_devnet_profile(&config, std::option::Option::None) { std::result::Result::Ok(profile) => profile, std::result::Result::Err(error) => panic!("Devnet profile resolution failed: {error}"), diff --git a/ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs b/ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs index 84417d3..8a4d00d 100644 --- a/ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs +++ b/ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/associated_token_account.rs -// version: 13 +// version: 14 //! Devnet ATA execution with stateful and canonical post-validation. @@ -993,12 +993,12 @@ mod tests { } fn example_devnet_profile() -> ks_config::ProfileConfig { - let config = - match ks_config::parse_config_json(include_str!("../../../config/app.config.json")) - { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => panic!("example config parse failed: {error}"), - }; + let config = match ks_config::parse_config_json(include_str!( + "../../../test-fixtures/config/resolved.app.config.json" + )) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("example config parse failed: {error}"), + }; return match crate::resolve_demo_devnet_profile(&config, std::option::Option::None) { std::result::Result::Ok(profile) => profile, std::result::Result::Err(error) => panic!("Devnet profile resolution failed: {error}"), diff --git a/ks-pipeline-demo-scenarios/src/spl/memo.rs b/ks-pipeline-demo-scenarios/src/spl/memo.rs index af515e9..502f022 100644 --- a/ks-pipeline-demo-scenarios/src/spl/memo.rs +++ b/ks-pipeline-demo-scenarios/src/spl/memo.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/memo.rs -// version: 9 +// version: 10 //! Devnet SPL Memo v4 execution with canonical post-validation. @@ -754,12 +754,12 @@ fn decode_completed(summary: &ks_pipeline::DecodeReplaySummary) -> bool { #[cfg(test)] mod tests { fn example_devnet_profile() -> ks_config::ProfileConfig { - let config = - match ks_config::parse_config_json(include_str!("../../../config/app.config.json")) - { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => panic!("example config parse failed: {error}"), - }; + let config = match ks_config::parse_config_json(include_str!( + "../../../test-fixtures/config/resolved.app.config.json" + )) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("example config parse failed: {error}"), + }; return match crate::resolve_demo_devnet_profile(&config, std::option::Option::None) { std::result::Result::Ok(profile) => profile, std::result::Result::Err(error) => panic!("Devnet profile resolution failed: {error}"), diff --git a/ks-pipeline-demo-scenarios/src/spl/token/execution.rs b/ks-pipeline-demo-scenarios/src/spl/token/execution.rs index 264ff72..d848656 100644 --- a/ks-pipeline-demo-scenarios/src/spl/token/execution.rs +++ b/ks-pipeline-demo-scenarios/src/spl/token/execution.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/token/execution.rs -// version: 13 +// version: 14 //! Devnet classic SPL Token execution with stateful and canonical post-validation. @@ -975,7 +975,7 @@ pub(crate) fn decode_completed(summary: &ks_pipeline::DecodeReplaySummary) -> bo mod tests { fn example_devnet_profile() -> ks_config::ProfileConfig { let config = match ks_config::parse_config_json(include_str!( - "../../../../config/app.config.json" + "../../../../test-fixtures/config/resolved.app.config.json" )) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("example config parse failed: {error}"), diff --git a/ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs b/ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs index b7cd250..24e3de6 100644 --- a/ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs +++ b/ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/token/lifecycle.rs -// version: 12 +// version: 13 //! Controlled Devnet lifecycle for freshly prepared classic SPL Token accounts. @@ -1688,7 +1688,7 @@ mod tests { fn example_devnet_profile() -> ks_config::ProfileConfig { let config = match ks_config::parse_config_json(include_str!( - "../../../../config/app.config.json" + "../../../../test-fixtures/config/resolved.app.config.json" )) { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => panic!("example config parse failed: {error}"), diff --git a/ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/campaign.rs b/ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/campaign.rs index 90ebe68..f758c9f 100644 --- a/ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/campaign.rs +++ b/ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/campaign.rs @@ -1,5 +1,5 @@ // file: ks-pipeline-demo-scenarios/src/spl/token_2022/metadata/campaign.rs -// version: 7 +// version: 8 //! Ordered confirmed Devnet campaign for the five Token-2022 Token Metadata instructions. @@ -502,13 +502,16 @@ mod tests { let path = std::path::PathBuf::from(value); if path.is_absolute() { path } else { workspace_root.join(path) } }, - std::result::Result::Err(_) => workspace_root.join("config/app.config.json"), + std::result::Result::Err(_) => { + workspace_root.join("config/ks-pipeline-demo-scenarios.default.config.json") + }, }; - let config = ks_config::read_config_json_file_with_environment( + let composed = ks_config::read_composed_app_config_with_environment( config_path.as_path(), workspace_root, ) .unwrap_or_else(|error| panic!("Devnet configuration loading failed: {error}")); + let config = composed.app_config; let profile_name = std::env::var("KS_DEVNET_PROFILE").ok(); let mut profile = crate::resolve_demo_devnet_profile(&config, profile_name.as_deref()) .unwrap_or_else(|error| panic!("Devnet profile resolution failed: {error}")); diff --git a/ks-pipeline/CHANGELOG.md b/ks-pipeline/CHANGELOG.md index 14f9985..92f9acc 100644 --- a/ks-pipeline/CHANGELOG.md +++ b/ks-pipeline/CHANGELOG.md @@ -1,8 +1,13 @@ - + # CHANGELOG — ks-pipeline +## `0.5.1-pre.006` + +- déplace ses fixtures de configuration vers le contrat runtime résolu sous `test-fixtures/config/` ; +- ne modifie aucune orchestration ou qualification réseau. + ## `0.5.1-pre.005` - aligne les tests de configuration sur le nouveau document général `config/app.config.json` après extraction du logging ; diff --git a/ks-pipeline/src/spl_token_stateful.rs b/ks-pipeline/src/spl_token_stateful.rs index d7f61ab..4ae52bc 100644 --- a/ks-pipeline/src/spl_token_stateful.rs +++ b/ks-pipeline/src/spl_token_stateful.rs @@ -1,5 +1,5 @@ // file: ks-pipeline/src/spl_token_stateful.rs -// version: 5 +// version: 6 //! Stateful Localnet and Devnet readiness checks for classic SPL Token operations. @@ -1439,11 +1439,12 @@ mod tests { } fn local_devnet_profile() -> ks_config::ProfileConfig { - let config = - match ks_config::parse_config_json(include_str!("../../config/app.config.json")) { - std::result::Result::Ok(value) => value, - std::result::Result::Err(error) => panic!("example config parse failed: {error}"), - }; + let config = match ks_config::parse_config_json(include_str!( + "../../test-fixtures/config/resolved.app.config.json" + )) { + std::result::Result::Ok(value) => value, + std::result::Result::Err(error) => panic!("example config parse failed: {error}"), + }; for profile in config.profiles { if profile.name == "local_devnet" { return profile; diff --git a/prompts/030_v0_5_1_khadhroony_solana_namespace_and_config.md b/prompts/030_v0_5_1_khadhroony_solana_namespace_and_config.md index 65072bd..3d60a14 100644 --- a/prompts/030_v0_5_1_khadhroony_solana_namespace_and_config.md +++ b/prompts/030_v0_5_1_khadhroony_solana_namespace_and_config.md @@ -1,5 +1,5 @@ - + # Khadhroony Bot3 — `0.5.1` — migration Khadhroony Solana et configuration sûre @@ -61,7 +61,7 @@ Le workspace, le dépôt et le répertoire racine restent nommés `khadhroony-bo - `VERSION_DEVELOPMENT_LIFECYCLE.md` ; 4. `docs/architecture/PROJECT_OBJECTIVES.md`, `CRATE_MAP.md`, `ARCHITECTURE.md`, `PIPELINE_ARCHITECTURE.md`, `STORAGE_ARCHITECTURE.md` et `SURFACE_CRATE_MATRIX.md` ; 5. `README.md`, `USAGE.md`, `TODO.md` et `CHANGELOG.md` des dix crates à renommer et de `kb-app-demo-desktop` ; -6. `config/app.config.json`, `config/logging.config.json`, leurs schémas sous `config/schemas/`, `.env.example` et tous les exemples/configurations de test ; +6. les compositions `config/.default.config.json`, les documents partagés `logging.config.json`, `transport.config.json`, `listeners.config.json`, leurs schémas sous `config/schemas/`, `.env.example` et les fixtures runtime sous `test-fixtures/config/` ; 7. les tests d'API externe, tests TS-RS, scripts d'audit et références de noms de crates/targets/identités ; 8. les documents archivés `0.5.0` uniquement comme historique, jamais comme source prioritaire face à la politique active et au code courant. @@ -329,12 +329,27 @@ Le détail doit être confirmé par `0.5.1-pre.001`, mais la trajectoire cible e ### `0.5.1-pre.005` — split configuration/logging -- documents et schémas indépendants ; -- profils logging indépendants ; -- propriété unique des contrats logging ; -- migration/compatibilité explicite du document monolithique précédent. +- extraire le logging dans son document et son schéma indépendants ; +- rendre les profils logging indépendants ; +- transférer la propriété des contrats logging à `ks-logging`. -### `0.5.1-pre.006` — runtime/public, camouflage et Tauri sûr +### `0.5.1-pre.006` — composition binaire + transport + listeners + +- remplacer le rôle du document monolithique par des compositions `.default.config.json` ; +- fournir une composition dédiée au desktop et une composition dédiée aux scénarios ; +- extraire transport et listeners dans des documents/schémas indépendants ; +- conserver `AppConfig/ProfileConfig` comme contrat runtime résolu transitoire ; +- permettre aux transports de consommer directement `TransportProfileConfig`. + +### `0.5.1-pre.007` — store + wallet + execution + configuration dédiée des binaires + +- extraire database vers store, `logs_directory` vers logging et les chemins de wallets vers wallet selon leur ownership réel ; +- déplacer les permissions `*_send_enabled` du wallet vers execution ; +- sortir la configuration spécifique au desktop du domaine `ks-config` ; +- réduire les compositions à des références vers profils spécialisés et configuration dédiée ; +- vérifier qu'un futur worker peut ajouter sa propre composition sans enregistrer son identité dans `ks-config`. + +### `0.5.1-pre.008` — runtime/public, camouflage et Tauri sûr - séparer source/runtime/public/diagnostic ; - propager la sensibilité des valeurs composées ; @@ -342,9 +357,9 @@ Le détail doit être confirmé par `0.5.1-pre.001`, mais la trajectoire cible e - diagnostics sûrs et camouflage systématique ; - tests de non-divulgation et TS-RS aligné. -### `0.5.1-pre.007` — réconciliation et clôture +### `0.5.1-pre.009` — réconciliation et clôture -- audit exhaustif des anciens préfixes ; +- audit exhaustif des anciens préfixes et anciens documents ; - documentation finale ; - TODO/changelogs ; - validations workspace ; diff --git a/scripts/audit_khadhroony_workspace_rules.py b/scripts/audit_khadhroony_workspace_rules.py old mode 100755 new mode 100644 index 366a158..52cf9e7 --- a/scripts/audit_khadhroony_workspace_rules.py +++ b/scripts/audit_khadhroony_workspace_rules.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # file: scripts/audit_khadhroony_workspace_rules.py -# version: 21 +# version: 22 """Audit mechanically verifiable rules specific to khadhroony-bot3.""" @@ -908,10 +908,15 @@ def audit_environment_namespaces(root: pathlib.Path) -> list[Violation]: ) placeholder = re.compile(r"\$\{([A-Z][A-Z0-9_]*)") for relative in [ - "config/app.config.json", + "config/kb-app-demo-desktop.default.config.json", + "config/ks-pipeline-demo-scenarios.default.config.json", "config/logging.config.json", - "config/example.app.config.json", + "config/transport.config.json", + "config/listeners.config.json", + "config/example.kb-app-demo-desktop.default.config.json", "config/example.logging.config.json", + "config/example.transport.config.json", + "config/example.listeners.config.json", ]: config = root / relative if not config.is_file(): @@ -949,16 +954,24 @@ def audit_environment_namespaces(root: pathlib.Path) -> list[Violation]: def audit_configuration_split(root: pathlib.Path) -> list[Violation]: - """Require the independent application and logging configuration contract.""" + """Require binary composition plus independent shared configuration documents.""" violations: list[Violation] = [] required_files = [ - "config/app.config.json", + "config/kb-app-demo-desktop.default.config.json", + "config/ks-pipeline-demo-scenarios.default.config.json", "config/logging.config.json", - "config/example.app.config.json", + "config/transport.config.json", + "config/listeners.config.json", + "config/example.kb-app-demo-desktop.default.config.json", "config/example.logging.config.json", - "config/schemas/app.config.schema.json", + "config/example.transport.config.json", + "config/example.listeners.config.json", + "config/schemas/composition.config.schema.json", "config/schemas/logging.config.schema.json", + "config/schemas/transport.config.schema.json", + "config/schemas/listeners.config.schema.json", + "config/schemas/resolved.app.config.schema.json", ] for relative in required_files: if (root / relative).is_file(): @@ -971,58 +984,82 @@ def audit_configuration_split(root: pathlib.Path) -> list[Violation]: "required split configuration file is missing", ) ) - app_path = root / "config/app.config.json" - if app_path.is_file(): + composition_paths = [ + "config/kb-app-demo-desktop.default.config.json", + "config/ks-pipeline-demo-scenarios.default.config.json", + ] + for relative in composition_paths: + path = root / relative + if not path.is_file(): + continue try: - app = json.loads(app_path.read_text(encoding="utf-8")) + composition = json.loads(path.read_text(encoding="utf-8")) except json.JSONDecodeError: - app = None - if isinstance(app, dict): - for index, profile in enumerate(app.get("profiles", []), 1): - if isinstance(profile, dict) and "logging" in profile: - violations.append( - Violation( - "KH_CFG002", - "config/app.config.json", - index, - "application profiles must not embed logging configuration", - ) - ) - logging_path = root / "config/logging.config.json" - if logging_path.is_file(): - try: - logging = json.loads(logging_path.read_text(encoding="utf-8")) - except json.JSONDecodeError: - logging = None - if isinstance(logging, dict): - if not isinstance(logging.get("active_profile"), str): + composition = None + if not isinstance(composition, dict): + continue + sources = composition.get("sources") + if not isinstance(sources, dict) or set(sources) != {"logging", "transport", "listeners"}: + violations.append( + Violation( + "KH_CFG002", + relative, + 1, + "binary composition must reference logging, transport and listeners sources", + ) + ) + for index, profile in enumerate(composition.get("profiles", []), 1): + if not isinstance(profile, dict): + continue + if "solana" in profile or "logging" in profile: violations.append( Violation( "KH_CFG003", - "config/logging.config.json", - 1, - "logging configuration must define its own active_profile", + relative, + index, + "composition profiles must reference transport/listener/logging profiles instead of embedding them", ) ) - if not isinstance(logging.get("profiles"), list) or not logging.get("profiles"): - violations.append( - Violation( - "KH_CFG004", - "config/logging.config.json", - 1, - "logging configuration must define independent named profiles", + for key in ["logging_profile", "transport_profile", "listeners_profile"]: + if not isinstance(profile.get(key), str) or not profile.get(key, "").strip(): + violations.append( + Violation( + "KH_CFG004", + relative, + index, + f"composition profile must define non-empty `{key}`", + ) ) - ) - legacy = ["config/example.config.json", "config/schema.config.json"] + for relative in ["config/logging.config.json", "config/transport.config.json", "config/listeners.config.json"]: + path = root / relative + if not path.is_file(): + continue + try: + document = json.loads(path.read_text(encoding="utf-8")) + except json.JSONDecodeError: + document = None + if not isinstance(document, dict): + continue + if not isinstance(document.get("active_profile"), str): + violations.append(Violation("KH_CFG005", relative, 1, "shared configuration document must define active_profile")) + if not isinstance(document.get("profiles"), list) or not document.get("profiles"): + violations.append(Violation("KH_CFG006", relative, 1, "shared configuration document must define named profiles")) + legacy = [ + "config/app.config.json", + "config/example.app.config.json", + "config/schemas/app.config.schema.json", + "config/example.config.json", + "config/schema.config.json", + ] for relative in legacy: if not (root / relative).exists(): continue violations.append( Violation( - "KH_CFG005", + "KH_CFG007", relative, 1, - "legacy combined configuration file must be removed after the split", + "legacy monolithic application configuration file must be removed", ) ) return violations diff --git a/config/example.app.config.json b/test-fixtures/config/example.resolved.app.config.json similarity index 100% rename from config/example.app.config.json rename to test-fixtures/config/example.resolved.app.config.json diff --git a/test-fixtures/config/resolved.app.config.json b/test-fixtures/config/resolved.app.config.json new file mode 100644 index 0000000..cfd290c --- /dev/null +++ b/test-fixtures/config/resolved.app.config.json @@ -0,0 +1,1110 @@ +{ + "active_profile": "mainnet_research", + "profiles": [ + { + "name": "local_devnet", + "app": { + "name": "khadhroony-bot3", + "environment": "development", + "auto_reconnect_default": false + }, + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_DEVNET_URL}", + "max_connections": 8, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/local.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "solana": { + "http_endpoints": [ + { + "name": "devnet_public_http", + "enabled": true, + "provider": "solana-public", + "cluster": "devnet", + "url": "https://api.devnet.solana.com", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 4, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 10, + "requests_per_second": 3, + "burst_capacity": 3, + "max_concurrent_requests": 2, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 10000 + }, + { + "role": "http_transactions", + "enabled": true, + "request_kinds": [ + "simulate_transaction", + "send_transaction", + "get_signature_statuses" + ], + "priority": 20, + "requests_per_second": 1, + "burst_capacity": 1, + "max_concurrent_requests": 1, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 10000 + } + ] + } + ], + "ws_endpoints": [ + { + "name": "devnet_public_ws", + "enabled": true, + "provider": "solana-public", + "cluster": "devnet", + "url": "wss://api.devnet.solana.com", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 256, + "event_channel_capacity": 1024, + "auto_reconnect": false, + "roles": [ + { + "role": "slot_notifications", + "enabled": true, + "request_kinds": [ + "slot_subscribe", + "slots_updates_subscribe" + ], + "priority": 10, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "program_logs", + "enabled": true, + "request_kinds": [ + "logs_subscribe_mentions", + "program_subscribe", + "account_subscribe", + "signature_subscribe", + "block_subscribe", + "vote_subscribe" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 64, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "root_notifications", + "enabled": true, + "request_kinds": [ + "root_subscribe" + ], + "priority": 15, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ], + "listeners": { + "enabled": true, + "default_commitment": "confirmed", + "log_listeners": [ + { + "name": "pump_swap_logs", + "enabled": false, + "endpoint_role": "program_logs", + "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." + } + ], + "program_listeners": [ + { + "name": "pump_fun_program_accounts", + "enabled": false, + "endpoint_role": "program_logs", + "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", + "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." + } + ], + "account_listeners": [] + } + }, + "wallet": { + "wallet_dir": "wallets/temporary/local_devnet", + "cluster": "devnet", + "temporary_wallet_enabled": true, + "temporary_wallet_alias": "local-devnet-operator", + "temporary_wallet_persist": true, + "localnet_send_enabled": false, + "devnet_send_enabled": true, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 100000000, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 2000000000 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + }, + { + "name": "mainnet_research", + "app": { + "name": "khadhroony-bot3", + "environment": "research", + "auto_reconnect_default": false + }, + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_MAINNET_URL}", + "max_connections": 16, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/mainnet-import.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "solana": { + "http_endpoints": [ + { + "name": "mainnet_public_http", + "enabled": true, + "provider": "solana-public", + "cluster": "mainnet-beta", + "url": "https://api.mainnet.solana.com", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 4, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 50, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "http_fallback", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 90, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + } + ] + }, + { + "name": "helius_primary_http", + "enabled": true, + "provider": "helius", + "cluster": "mainnet-beta", + "url": "https://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 8, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 10, + "requests_per_second": 8, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "http_heavy", + "enabled": true, + "request_kinds": [ + "get_block", + "get_program_accounts", + "get_signatures_for_address", + "get_transaction" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 1, + "max_concurrent_requests": 2, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "history_backfill", + "enabled": true, + "request_kinds": [ + "get_block", + "get_program_accounts", + "get_signatures_for_address", + "get_transaction" + ], + "priority": 30, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "http_transactions", + "enabled": false, + "request_kinds": [ + "simulate_transaction", + "send_transaction", + "get_signature_statuses" + ], + "priority": 40, + "requests_per_second": 1, + "burst_capacity": 1, + "max_concurrent_requests": 1, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ], + "ws_endpoints": [ + { + "name": "mainnet_public_ws_slots", + "enabled": true, + "provider": "solana-public", + "cluster": "mainnet-beta", + "url": "wss://api.mainnet.solana.com", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 256, + "event_channel_capacity": 1024, + "auto_reconnect": false, + "roles": [ + { + "role": "slot_notifications", + "enabled": true, + "request_kinds": [ + "slot_subscribe", + "slots_updates_subscribe" + ], + "priority": 10, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "root_notifications", + "enabled": true, + "request_kinds": [ + "root_subscribe" + ], + "priority": 20, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + } + ] + }, + { + "name": "helius_primary_ws_programs", + "enabled": true, + "provider": "helius", + "cluster": "mainnet-beta", + "url": "wss://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 512, + "event_channel_capacity": 4096, + "auto_reconnect": false, + "roles": [ + { + "role": "logs_subscribe", + "enabled": true, + "request_kinds": [ + "logs_subscribe_mentions" + ], + "priority": 10, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "program_subscribe", + "enabled": true, + "request_kinds": [ + "program_subscribe" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "account_subscribe", + "enabled": true, + "request_kinds": [ + "account_subscribe" + ], + "priority": 30, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ], + "listeners": { + "enabled": true, + "default_commitment": "confirmed", + "log_listeners": [ + { + "name": "pump_swap_logs", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." + }, + { + "name": "raydium_cpmm_logs", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", + "purpose": "Détecter les créations de pools et swaps Raydium CPMM." + } + ], + "program_listeners": [ + { + "name": "pump_fun_program_accounts", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", + "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." + } + ], + "account_listeners": [] + } + }, + "wallet": { + "wallet_dir": "wallets/temporary/mainnet_research", + "cluster": "mainnet-beta", + "temporary_wallet_enabled": false, + "temporary_wallet_alias": "mainnet_research-disabled", + "temporary_wallet_persist": false, + "localnet_send_enabled": false, + "devnet_send_enabled": false, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 0, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 0 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + }, + { + "name": "mainnet", + "app": { + "name": "khadhroony-bot3", + "environment": "mainnet", + "auto_reconnect_default": false + }, + "database": { + "enabled": true, + "backend": "postgres", + "postgres": { + "url": "${KS_SECRET_POSTGRES_MAINNET_URL}", + "max_connections": 16, + "connect_timeout_ms": 5000, + "auto_initialize_schema": true + }, + "sqlite": { + "path": "data/mainnet.sqlite", + "create_if_missing": true, + "busy_timeout_ms": 5000, + "max_connections": 1, + "auto_initialize_schema": true, + "use_wal": true + } + }, + "data": { + "wallets_directory": "wallets", + "logs_directory": "logs" + }, + "solana": { + "http_endpoints": [ + { + "name": "mainnet_public_http", + "enabled": true, + "provider": "solana-public", + "cluster": "mainnet-beta", + "url": "https://api.mainnet.solana.com", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 4, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 50, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "http_fallback", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 90, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + } + ] + }, + { + "name": "helius_primary_http", + "enabled": true, + "provider": "helius", + "cluster": "mainnet-beta", + "url": "https://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", + "connect_timeout_ms": 4000, + "request_timeout_ms": 10000, + "max_idle_connections_per_host": 8, + "roles": [ + { + "role": "http_queries", + "enabled": true, + "request_kinds": [ + "get_account_info", + "get_balance", + "get_block_commitment", + "get_block_height", + "get_block_production", + "get_blocks", + "get_blocks_with_limit", + "get_cluster_nodes", + "get_epoch_info", + "get_epoch_schedule", + "get_fee_for_message", + "get_first_available_block", + "get_genesis_hash", + "get_health", + "get_highest_snapshot_slot", + "get_identity", + "get_inflation_governor", + "get_inflation_rate", + "get_inflation_reward", + "get_largest_accounts", + "get_latest_blockhash", + "get_leader_schedule", + "get_max_retransmit_slot", + "get_max_shred_insert_slot", + "get_minimum_balance_for_rent_exemption", + "get_multiple_accounts", + "get_recent_performance_samples", + "get_recent_prioritization_fees", + "get_signature_statuses", + "get_slot", + "get_slot_leader", + "get_slot_leaders", + "get_stake_activation", + "get_stake_minimum_delegation", + "get_supply", + "get_token_account_balance", + "get_token_accounts_by_delegate", + "get_token_accounts_by_owner", + "get_token_largest_accounts", + "get_token_supply", + "get_transaction_count", + "get_version", + "get_vote_accounts", + "is_blockhash_valid", + "request_airdrop" + ], + "priority": 10, + "requests_per_second": 8, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "http_heavy", + "enabled": true, + "request_kinds": [ + "get_block", + "get_program_accounts", + "get_signatures_for_address", + "get_transaction" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 1, + "max_concurrent_requests": 2, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "history_backfill", + "enabled": true, + "request_kinds": [ + "get_block", + "get_program_accounts", + "get_signatures_for_address", + "get_transaction" + ], + "priority": 30, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 1500 + }, + { + "role": "http_transactions", + "enabled": false, + "request_kinds": [ + "simulate_transaction", + "send_transaction", + "get_signature_statuses" + ], + "priority": 40, + "requests_per_second": 1, + "burst_capacity": 1, + "max_concurrent_requests": 1, + "max_subscriptions": 0, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ], + "ws_endpoints": [ + { + "name": "mainnet_public_ws_slots", + "enabled": true, + "provider": "solana-public", + "cluster": "mainnet-beta", + "url": "wss://api.mainnet.solana.com", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 256, + "event_channel_capacity": 1024, + "auto_reconnect": false, + "roles": [ + { + "role": "slot_notifications", + "enabled": true, + "request_kinds": [ + "slot_subscribe", + "slots_updates_subscribe" + ], + "priority": 10, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "root_notifications", + "enabled": true, + "request_kinds": [ + "root_subscribe" + ], + "priority": 20, + "requests_per_second": 4, + "burst_capacity": 8, + "max_concurrent_requests": 4, + "max_subscriptions": 16, + "pause_after_rate_limit_ms": 3000 + } + ] + }, + { + "name": "helius_primary_ws_programs", + "enabled": true, + "provider": "helius", + "cluster": "mainnet-beta", + "url": "wss://mainnet.helius-rpc.com/?api-key=${KS_SECRET_HELIUS_API_KEY}", + "connect_timeout_ms": 5000, + "request_timeout_ms": 10000, + "unsubscribe_timeout_ms": 1500, + "write_channel_capacity": 512, + "event_channel_capacity": 4096, + "auto_reconnect": false, + "roles": [ + { + "role": "logs_subscribe", + "enabled": true, + "request_kinds": [ + "logs_subscribe_mentions" + ], + "priority": 10, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "program_subscribe", + "enabled": true, + "request_kinds": [ + "program_subscribe" + ], + "priority": 20, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + }, + { + "role": "account_subscribe", + "enabled": true, + "request_kinds": [ + "account_subscribe" + ], + "priority": 30, + "requests_per_second": 2, + "burst_capacity": 4, + "max_concurrent_requests": 2, + "max_subscriptions": 128, + "pause_after_rate_limit_ms": 3000 + } + ] + } + ], + "listeners": { + "enabled": true, + "default_commitment": "confirmed", + "log_listeners": [ + { + "name": "pump_swap_logs", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "pAMMBay6oceH9fJKBRHGP5D4bD4sWpmSwMn52FMfXEA", + "purpose": "Détecter les swaps, créations de pool et changements de liquidité Pump Swap." + }, + { + "name": "raydium_cpmm_logs", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "CPMMoo8L3F4NbTegBCKVNunggL7H1ZpdTHKxQB5qKP1C", + "purpose": "Détecter les créations de pools et swaps Raydium CPMM." + } + ], + "program_listeners": [ + { + "name": "pump_fun_program_accounts", + "enabled": true, + "endpoint_role": "program_logs", + "program_id": "6EF8rrecthR5Dkzon8Nwu78hRvfCKubJ14M5uBEwF6P", + "purpose": "Détecter les changements de comptes utiles à la création ou migration de tokens Pump Fun." + } + ], + "account_listeners": [] + } + }, + "wallet": { + "wallet_dir": "wallets/temporary/mainnet", + "cluster": "mainnet-beta", + "temporary_wallet_enabled": false, + "temporary_wallet_alias": "mainnet-disabled", + "temporary_wallet_persist": false, + "localnet_send_enabled": false, + "devnet_send_enabled": false, + "testnet_send_enabled": false, + "mainnet_send_enabled": false + }, + "execution": { + "dry_run_default": true, + "require_simulation": true, + "require_operator_confirmation": true, + "localnet_max_spend_lamports": 0, + "devnet_max_spend_lamports": 0, + "testnet_max_spend_lamports": 0, + "mainnet_max_spend_lamports": 0, + "max_fee_lamports": 1000000, + "max_compute_unit_price_micro_lamports": 100000, + "recent_blockhash_max_age_slots": 150, + "send_max_retries": 3, + "confirmation_poll_interval_ms": 500, + "confirmation_max_attempts": 120, + "devnet_airdrop_max_lamports": 0 + }, + "demo": { + "live_demo_enabled": true, + "trading_demo_enabled": false + } + } + ] +}