Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
Free 30-min Web3 Consultation
Book Consultation
Smart Contract Security Audits
View Audit Services
Custom DeFi Protocol Development
Explore DeFi
Full-Stack Web3 dApp Development
View App Services
LABS
Guides

How to Implement Environment Management for Testnets and Mainnet

A step-by-step guide for developers on configuring secure, multi-network environments in Web3 projects using Hardhat and Foundry, focusing on secrets management and deployment safety.
Chainscore © 2026
introduction
DEVELOPER ESSENTIALS

How to Implement Environment Management for Testnets and Mainnet

A structured approach to managing blockchain environments is critical for secure and efficient smart contract development. This guide covers best practices for separating testnet and mainnet configurations.

Effective environment management separates configuration from code, preventing costly mistakes like deploying to the wrong network. The core principle is to use environment variables and configuration files to define network-specific parameters such as RPC endpoints, private keys, and contract addresses. This avoids hardcoding sensitive data, making your project portable and secure. Tools like dotenv for Node.js or python-dotenv for Python are standard for loading variables from a .env file, which should be added to your .gitignore.

A robust setup involves creating distinct configuration objects for each network. For example, you might define a networks object in a config.js or hardhat.config.ts file. For Ethereum, common entries include sepolia for testing and mainnet for production. Each entry specifies the chain ID, RPC URL, and an optional explorer URL. This abstraction allows your deployment scripts and front-end to reference config.networks.sepolia.rpcUrl instead of a literal string, enabling easy network switches.

Managing Private Keys and Mnemonics

Never commit private keys or seed phrases to version control. These should be loaded at runtime from environment variables (e.g., PRIVATE_KEY or MNEMONIC). For team development, use secret management services or encrypted environment files. When using a mnemonic, ensure the derivation path (like m/44'/60'/0'/0) is correct for your target network. Hardware wallets or dedicated signer services provide the highest security for mainnet deployments, isolating private key access.

Testing frameworks like Hardhat and Foundry have built-in environment management. Hardhat uses its configuration file to define networks and can automatically use environment variables. Foundry's forge commands accept an --rpc-url flag and a --private-key flag, which can be sourced from env vars. For front-end applications, libraries like wagmi and ethers read the network from the user's connected wallet, but you must still configure provider endpoints for read-only operations or wallet-less interactions.

A practical workflow involves creating environment files: .env.example (a template with placeholder keys), .env.development (for local or testnet use), and .env.production (for mainnet, often populated by CI/CD systems). Your deployment script should explicitly check the target network. For instance, a script might require a DEPLOY_NETWORK variable and fail if it's set to mainnet without additional confirmation, adding a safeguard against accidental production deploys.

prerequisites
PREREQUISITES

How to Implement Environment Management for Testnets and Mainnet

A structured approach to managing blockchain network configurations is essential for secure and efficient development. This guide covers environment variables, configuration files, and best practices for separating testnet and mainnet deployments.

Effective environment management separates configuration from code, a principle critical for Web3 development. You should maintain distinct configurations for different networks like Ethereum Sepolia testnet and Ethereum mainnet. This prevents accidental mainnet transactions during testing and allows for network-specific RPC endpoints, contract addresses, and private keys. Use environment variables (via a .env file) for sensitive data like PRIVATE_KEY and INFURA_API_KEY, ensuring they are never hardcoded and are excluded from version control using .gitignore.

A robust setup involves a configuration manager. Create a JavaScript or TypeScript file (e.g., config.ts) that exports network objects based on the process.env.NODE_ENV or a NETWORK variable. For example, your configuration can define an object with rpcUrl, chainId, explorerUrl, and key contract addresses for each network. Tools like Hardhat and Foundry have built-in support for network configurations in their config files (hardhat.config.js, foundry.toml), which should reference your environment variables.

For smart contract deployment, use mnemonic phrases or private keys loaded from environment variables. Libraries like dotenv load variables from .env. In a Hardhat script, you access the network via hre.network.name. Always verify that the intended network matches before deploying; a common pattern is to check hre.network.name !== "hardhat" before using a live network signer. This prevents costly mistakes on mainnet.

Managing contract addresses across environments is another key challenge. After deployment, save the deployed contract addresses and ABIs to a network-specific JSON file (e.g., deployments/sepolia.json). Your frontend or backend application can then import the correct artifact based on the current environment. For dApp frontends, consider using wagmi's configuration or a similar library that allows you to define chains and contracts conditionally.

Security is paramount. Never commit .env files or any file containing private keys. Use different accounts for testnets and mainnet. For production CI/CD pipelines, inject secrets through your platform's secure secret management (e.g., GitHub Secrets, GitLab CI Variables). Regularly audit your configuration files and access controls. Implementing these practices creates a scalable, secure foundation for multi-network blockchain development.

core-principles
DEVELOPER GUIDE

How to Implement Environment Management for Testnets and Mainnet

A practical guide to structuring and managing environment variables, configurations, and secrets across Web3 development stages.

Effective Web3 environment management separates configuration from code, preventing hardcoded values and enabling secure, reproducible deployments. The core principle is to use environment variables for all network-specific data: RPC endpoints, private keys, contract addresses, and chain IDs. For JavaScript/TypeScript projects, libraries like dotenv load variables from a .env file, which should be added to .gitignore. A basic structure defines keys like MAINNET_RPC_URL, TESTNET_PRIVATE_KEY, and ETHERSCAN_API_KEY. This approach allows you to switch between Ethereum mainnet, Sepolia testnet, or a local Anvil node by changing a single variable, not your source code.

For robust multi-environment setups, create configuration files that map environment names to their respective settings. Instead of a single .env, use config/testnet.js and config/mainnet.js modules that export objects containing all necessary parameters. A common pattern is to have a getConfig(chainId) function that returns the correct settings. This is critical because contract addresses differ per network; the Uniswap V3 Factory address on mainnet (0x1F98431c8aD98523631AE4a59f267346ea31F984) is not the same on Goerli. Your deployment scripts and front-end clients should import this configuration, ensuring consistency and reducing errors from manual entry.

Secret management for private keys and API keys requires additional security layers. Never commit files containing live private keys to version control. For development, use .env.local files. For production or team settings, use dedicated secret managers like Infura's dashboard, AWS Secrets Manager, or GitHub encrypted secrets (for CI/CD). Tools like Hardhat and Foundry have built-in support for loading variables from the environment, often via process.env. In Foundry, you can store a key in .env as PRIVATE_KEY and reference it in forge commands using --private-key ${PRIVATE_KEY}. For front-end applications, only expose public configuration; secrets must remain on the backend.

Implement validation for your environment variables to fail fast during application startup. Use a library like envalid to define required variables, their types, and default values for optional ones. This prevents runtime errors caused by missing or malformed configuration. For example, you can validate that RPC_URL is a valid URL and CHAIN_ID is a number that matches a known network. Automate configuration loading in your deployment pipeline. In a GitHub Actions workflow, you can inject secrets as environment variables and run a script that builds the config object for the target network, whether deploying a smart contract with hardhat deploy --network sepolia or building a static site with Vite.

Finally, document your environment setup clearly in a README.md or SETUP.md. List all required variables, explain how to obtain them (e.g., "Get a Sepolia RPC URL from Alchemy"), and provide a template .env.example file for new developers. This reduces onboarding friction and ensures all team members use identical configurations, leading to more reliable development and testing across testnet and mainnet environments.

BLOCKCHAIN DEVELOPMENT

Environment Types and Their Purposes

A comparison of common blockchain environments used for development, testing, and production deployment.

Feature / PurposeLocal DevelopmentTestnetMainnet

Primary Use Case

Initial smart contract coding and unit testing

Integration testing and public simulation

Live production deployment

Network State

Isolated, controlled by developer

Public, but with valueless tokens

Public, live with real value

Transaction Cost

Free (gas simulated)

Free (faucet tokens)

Paid (real ETH/other native token)

Consensus / Finality

Instant (local node)

Standard protocol delay (e.g., 12-15 sec for PoS)

Standard protocol delay with economic security

Data Persistence

Ephemeral (resets on restart)

Persistent but can be reset by network upgrades

Immutable and permanent

External Dependencies

None (self-contained)

Live oracles, indexers, bridges (testnet versions)

All live production services

Security Testing

Logic errors only

Economic and game theory simulations

Real-world adversarial conditions

Recommended For

All initial development and debugging

Pre-launch audits, UI/UX testing, load testing

Final deployment of audited, verified contracts

hardhat-configuration
DEVELOPER GUIDE

Configuring Network Environments in Hardhat

A practical guide to managing multi-network deployments, environment variables, and RPC configurations for Ethereum development.

Hardhat's network configuration is defined in the hardhat.config.js file, allowing you to specify distinct settings for local development, public testnets, and mainnet. Each network entry requires an url for the JSON-RPC endpoint and an accounts field for the private keys used to sign transactions. For local testing, the built-in Hardhat Network (localhost or hardhat) provides a zero-configuration environment. For external networks like Sepolia or Mainnet, you must provide a node provider URL from services like Alchemy, Infura, or a public RPC.

Managing private keys securely is critical. Hardhat supports loading keys from environment variables, which is the recommended practice to avoid committing secrets to version control. You can use the dotenv package to load a .env file. In your config, the accounts field can be set to [process.env.PRIVATE_KEY] for a single key or use { mnemonic: process.env.MNEMONIC } for a HD wallet. Never hardcode private keys directly into your configuration file.

Example Configuration

Here is a practical hardhat.config.js snippet for multiple environments:

javascript
require('@nomicfoundation/hardhat-toolbox');
require('dotenv').config();

module.exports = {
  solidity: "0.8.20",
  networks: {
    hardhat: {},
    sepolia: {
      url: process.env.SEPOLIA_RPC_URL,
      accounts: [process.env.DEPLOYER_PRIVATE_KEY]
    },
    mainnet: {
      url: process.env.MAINNET_RPC_URL,
      accounts: [process.env.DEPLOYER_PRIVATE_KEY]
    }
  }
};

This structure keeps secrets in .env and uses clear network names for targeting deployments with --network sepolia.

For advanced scenarios, you can configure gas prices, block confirmations, and custom chain IDs. Setting gasPrice is often necessary for mainnet deployments to ensure timely transaction inclusion. The chainId field is automatically inferred for standard networks but must be explicitly set for private or custom chains. You can also define multiple deployer accounts by providing an array of private keys to the accounts field, which Hardhat will use sequentially.

To execute a deployment script on a specific network, use the --network flag with the Hardhat CLI: npx hardhat run scripts/deploy.js --network sepolia. You can programmatically access the selected network within your scripts via hre.network.name to implement conditional logic. This environment management pattern is essential for building robust, multi-stage deployment pipelines from testnet validation to mainnet launch.

foundry-configuration
DEVELOPER GUIDE

Configuring Network Environments in Foundry

Learn how to manage multiple blockchain networks—from local development to mainnet deployments—using Foundry's environment configuration system.

Foundry's environment management is configured through a combination of command-line arguments, environment variables, and the foundry.toml file. The primary tool for network interaction is the forge command, which uses the --rpc-url flag to specify the Ethereum node endpoint. For example, forge script MyScript --rpc-url https://eth-mainnet.g.alchemy.com/v2/KEY targets the Ethereum mainnet. To avoid exposing private keys in commands, you should use the --private-key flag with an environment variable, like --private-key $DEPLOYER_PRIVATE_KEY. This foundational approach is flexible but can become repetitive.

For consistent and secure configuration, define your network settings in foundry.toml. You can create profiles for different environments. A common setup includes [profile.default] for local development and [profile.sepolia] for testing. Within a profile, you can set the RPC URL, chain ID, and even a default sender address. For sensitive data like private keys, never hardcode them in the config file. Instead, reference environment variables using the $VARIABLE_NAME syntax. This keeps your configuration reusable and your secrets safe.

Here is a practical foundry.toml example:

toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
rpc_url = "http://127.0.0.1:8545"
chain_id = 31337

[profile.sepolia]
rpc_url = "${RPC_SEPOLIA_URL}"
chain_id = 11155111
sender = "${DEPLOYER_ADDRESS}"

With this setup, you can run forge script MyScript --profile sepolia, and Foundry will automatically use the RPC URL and chain ID defined in the sepolia profile. The sender address and the corresponding private key (passed via --private-key $KEY) must be for the same account.

Managing private keys and API endpoints securely is critical. Use a .env file in your project root (ensure it's in .gitignore) to store variables like RPC_SEPOLIA_URL, DEPLOYER_PRIVATE_KEY, and ETHERSCAN_API_KEY. Foundry doesn't load .env files automatically; you must source them in your shell or use a tool like dotenv via foundry.toml by adding dotenv = [".env"] to your profile. For production mainnet deployments, consider using hardware wallet integration via the --ledger and --trezor flags or a dedicated secure secret management service.

To verify and interact with contracts on Etherscan, configure API keys per chain. Add an [etherscan] table to your foundry.toml:

toml
[etherscan]
sepolia = { key = "${ETHERSCAN_API_KEY}", url = "https://api-sepolia.etherscan.io/api" }
ethereum = { key = "${ETHERSCAN_API_KEY}", url = "https://api.etherscan.io/api" }

After deployment, you can verify with forge verify-contract --chain sepolia <address> <contract>. This structured approach allows you to seamlessly switch between a local Anvil instance, a testnet, and mainnet by simply changing the --profile flag, making your development and deployment workflow robust and reproducible.

secrets-management-tools
SECURE DEVELOPMENT

Tools for Secrets and Configuration Management

Managing API keys, RPC endpoints, and contract addresses securely across testnet and mainnet environments is critical for safe Web3 development.

05

Configuration Objects for Contract Addresses

Maintain a versioned JSON or JavaScript configuration object that maps network IDs to deployed contract addresses and other chain-specific parameters.

  • Example config.json:
json
{
  "1": {
    "daiAddress": "0x6B175...",
    "uniswapRouter": "0x7a250..."
  },
  "11155111": {
    "daiAddress": "0xMock...",
    "uniswapRouter": "0xMock..."
  }
}
  • Import this config and select values based on chainId. This pattern is used by major dApps and protocols like Uniswap.
preventing-accidental-deploys
ENVIRONMENT MANAGEMENT

Implementing Safeguards Against Accidental Mainnet Deploys

Prevent costly mistakes by implementing robust environment management strategies for your blockchain development workflow.

Accidental mainnet deployments are a critical risk in Web3 development, where a simple misconfiguration can lead to irreversible loss of funds or contract ownership. The primary cause is a lack of enforced separation between development, testing, and production environments. Effective environment management creates explicit, automated guardrails that prevent code intended for a testnet from ever reaching a live blockchain. This guide outlines a multi-layered approach using environment variables, configuration files, and CI/CD checks to eliminate this risk.

The foundation of environment safety is segregating private keys and RPC endpoints. Never hardcode these values. Instead, use environment variable files (.env) that are explicitly excluded from version control via .gitignore. Structure your project with separate files like .env.development, .env.testnet, and .env.mainnet. Your deployment scripts should require a NODE_ENV or DEPLOY_ENV variable to run, forcing explicit environment selection. Tools like dotenv or direnv can load these variables securely based on the context.

Enhance this with programmatic checks in your deployment scripts (e.g., Hardhat or Foundry scripts). Before any transaction is sent, validate the target network. A simple check can abort the process if a mainnet RPC URL is detected during a local test. For example, in a Hardhat script, you can verify network.name or the chain ID. More advanced safeguards include checking that the deployer address matches an expected, environment-specific whitelist of safe addresses, preventing a test wallet from being used on mainnet.

Integrate these checks into your Continuous Integration and Deployment (CI/CD) pipeline. Configure your pipeline (e.g., GitHub Actions, GitLab CI) to only expose mainnet keys during deployments from the main branch and only after a manual approval step. For testnets, use keys stored as repository secrets. The pipeline should run the network validation checks mentioned above as a mandatory step. This creates an automated, repeatable, and auditable process where human error is mitigated by system design.

For teams, establish a clear protocol. Use branch protection rules, mandate pull request reviews for any deployment script changes, and maintain a runbook. Consider using dedicated tooling like OpenZeppelin Defender for secure, managed deployments with built-in multi-signature approvals and upgrade timelocks. By combining technical enforcement (env variables, script checks) with process safeguards (CI/CD, review protocols), you build a defense-in-depth strategy that makes deploying to the wrong network virtually impossible.

ENVIRONMENT MANAGEMENT

Frequently Asked Questions

Common questions and solutions for managing blockchain environments, from local development to production mainnet deployments.

A mainnet is the live, production blockchain where transactions have real economic value (e.g., Ethereum mainnet). A testnet is a separate, parallel network used for development and testing, where tokens have no real-world value (e.g., Sepolia, Goerli).

Key Differences:

  • Token Value: Mainnet uses ETH, SOL, etc. Testnets use free "faucet" tokens.
  • Consensus & State: Testnets may have different consensus rules and are periodically reset.
  • Purpose: Use testnets for deploying and testing smart contracts without financial risk. Use mainnet for final, user-facing applications.

Always test thoroughly on a testnet that matches your target mainnet's EVM version and features before deploying.

conclusion
IMPLEMENTATION SUMMARY

Conclusion and Next Steps

This guide has outlined the critical practice of managing separate environments for blockchain development. Proper configuration is essential for security, cost control, and reliable testing.

Implementing a robust environment management strategy is non-negotiable for professional Web3 development. The core principles are: isolating private keys and RPC endpoints, using environment variables via .env files, and programmatically switching configurations based on a NODE_ENV-like flag. This prevents catastrophic mistakes like deploying test contracts to mainnet or leaking production keys. Frameworks like Hardhat and Foundry have built-in support for network configuration, making this separation straightforward. Always commit a .env.example file to your repository to document required variables without exposing secrets.

Your next step is to automate and secure this workflow. Integrate environment checks into your CI/CD pipeline using GitHub Actions or GitLab CI. For example, run unit tests on a forked mainnet, integration tests on a local Anvil or Hardhat Network, and only deploy to a live testnet on a specific branch. Use secret management tools like GitHub Secrets, Doppler, or AWS Secrets Manager to inject credentials securely in production. For teams, consider using configuration management tools like Terraform or Docker Compose to standardize environment setups across all developers.

To deepen your understanding, explore advanced patterns. Learn about using mnemonic phrases or hardware wallet integration for key management instead of raw private keys in files. Study how to estimate and manage gas costs differently across networks using tools like eth-gas-reporter. Implement upgradeable contract patterns using OpenZeppelin's TransparentUpgradeableProxy, which requires careful management of proxy admin addresses per environment. Finally, always verify your contracts on block explorers like Etherscan for mainnet and testnets, a process that also requires environment-specific API keys.