From 9379e2033d1c1b506a595d4eaf855f599b8e07f0 Mon Sep 17 00:00:00 2001 From: Anioke Sebastian Date: Mon, 9 Mar 2026 01:29:52 +0100 Subject: [PATCH] fix: load and validate required env vars at startup (#24) --- .env.example | 19 ++++++++++--------- Cargo.lock | 7 +++++++ Cargo.toml | 3 ++- src/config.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 8 +++++++- 5 files changed, 69 insertions(+), 11 deletions(-) create mode 100644 src/config.rs diff --git a/.env.example b/.env.example index c89843b..7a520f9 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,13 @@ -# Copy this file to .env and customize values as needed for your local -# development or CI environment. +# Copy this file to .env and replace placeholder values. -# Soroban network selection (profile name). Valid values: testnet, mainnet, sandbox -SOROBAN_NETWORK=testnet +# Target Stellar network (for example: testnet, mainnet). +STELLAR_NETWORK=testnet -# Override the RPC endpoint and network passphrase if necessary -SOROBAN_RPC_URL=https://rpc.testnet.soroban.stellar.org -SOROBAN_NETWORK_PASSPHRASE="Test SDF Network ; September 2015" +# Platform account secret seed used for signing platform transactions. +STELLAR_PLATFORM_SECRET=SBXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +# Horizon REST API endpoint for the selected network. +HORIZON_URL=https://horizon-testnet.stellar.org -# Example keypair file path; tools may read this -# SOROBAN_KEY_FILE=./keys/contract-key.json +# Soroban RPC endpoint for the selected network. +SOROBAN_RPC_URL=https://rpc.testnet.soroban.stellar.org diff --git a/Cargo.lock b/Cargo.lock index 8d505a3..e081a8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,6 +193,7 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" name = "contract-fox" version = "0.1.0" dependencies = [ + "dotenvy", "ed25519-dalek", "reqwest", "serde", @@ -432,6 +433,12 @@ dependencies = [ "stellar-strkey", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "downcast-rs" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index a5d05b4..df54428 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,4 +25,5 @@ tokio = { version = "1", features = ["rt-multi-thread", "macros"] } stellar-strkey = "0.0.8" ed25519-dalek = "2" tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } +dotenvy = "0.15" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..6a51152 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,43 @@ +use std::env; + +use thiserror::Error; + +#[derive(Debug, Clone)] +pub struct Config { + pub stellar_network: String, + pub stellar_platform_secret: String, + pub horizon_url: String, + pub soroban_rpc_url: String, +} + +#[derive(Debug, Error)] +pub enum ConfigError { + #[error("missing required environment variable: {0}")] + MissingEnvVar(&'static str), + + #[error("environment variable {name} cannot be empty")] + EmptyEnvVar { name: &'static str }, +} + +impl Config { + pub fn from_env() -> Result { + dotenvy::dotenv().ok(); + + Ok(Self { + stellar_network: read_required_env("STELLAR_NETWORK")?, + stellar_platform_secret: read_required_env("STELLAR_PLATFORM_SECRET")?, + horizon_url: read_required_env("HORIZON_URL")?, + soroban_rpc_url: read_required_env("SOROBAN_RPC_URL")?, + }) + } +} + +fn read_required_env(name: &'static str) -> Result { + let value = env::var(name).map_err(|_| ConfigError::MissingEnvVar(name))?; + + if value.trim().is_empty() { + return Err(ConfigError::EmptyEnvVar { name }); + } + + Ok(value) +} diff --git a/src/main.rs b/src/main.rs index 3dfcc60..6e7612d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod errors; pub mod friendbot; @@ -5,4 +6,9 @@ pub mod horizon; mod setup; pub mod utils; -fn main() {} +fn main() { + if let Err(err) = config::Config::from_env() { + eprintln!("Startup configuration error: {err}"); + std::process::exit(1); + } +}