๐ Environment Variables
Environment variables are used to store configuration and secrets outside of your source code. They allow you to change behavior between development, testing, and production without modifying the code itself.
In Web3 projects, environment variables are critical for security.
Why environment variables matter in Web3
Most blockchain applications rely on sensitive values such as:
- RPC endpoints (Infura, Alchemy, QuickNode, Helius)
- Private keys / signer keys
- API keys (indexers, explorers, price feeds)
- Contract addresses that differ per network
Hard-coding these values into your codebase is dangerous and often leads to fund loss or key leaks.
๐จ Golden Rule: Never commit private keys or API keys to GitHub. Ever. Bots scan every commit globally, and leaked keys = drained wallets in seconds.
๐ Essential Resources & Tutorials
- ๐ Official dotenv docs: https://www.npmjs.com/package/dotenvย
- ๐ 12-Factor App (Config): https://12factor.net/configย
- ๐ก๏ธ OWASP Secrets Management: https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.htmlย
- ๐ GitHub Secret Scanning: https://docs.github.com/en/code-security/secret-scanningย
- ๐ Environment Variables Best Practices: https://www.twilio.com/blog/environment-variables-pythonย
๐ Quick Setup
Install dotenv
npm install dotenvCreate .env file
Create a .env file in your project root:
# RPC Provider URLs
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
MAINNET_RPC_URL=https://mainnet.infura.io/v3/YOUR_API_KEY
# Private Keys (use dev/test wallets only!)
PRIVATE_KEY=0xabc123def456...
# API Keys
ALCHEMY_API_KEY=your_alchemy_key
ETHERSCAN_API_KEY=your_etherscan_key
# Network Configuration
CHAIN_ID=11155111
NETWORK_NAME=sepolia
# Contract Addresses (optional, for reusing deployed contracts)
CONTRACT_ADDRESS=0x1234...Ensure .env is in .gitignore
# Environment variables
.env
.env.local
.env.*.local
# Dependencies
node_modules/
# Build outputs
dist/
build/
coverage/๐ป Code Examples
Hardhat Configuration
// hardhat.config.ts
import * as dotenv from "dotenv";
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
// Load environment variables
dotenv.config();
// Validate required environment variables
if (!process.env.PRIVATE_KEY) {
throw new Error("Please set PRIVATE_KEY in your .env file");
}
if (!process.env.SEPOLIA_RPC_URL) {
throw new Error("Please set SEPOLIA_RPC_URL in your .env file");
}
const config: HardhatUserConfig = {
solidity: "0.8.24",
networks: {
hardhat: {
chainId: 1337,
},
sepolia: {
url: process.env.SEPOLIA_RPC_URL,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
chainId: 11155111,
},
mainnet: {
url: process.env.MAINNET_RPC_URL || "",
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
chainId: 1,
},
},
etherscan: {
apiKey: process.env.ETHERSCAN_API_KEY,
},
};
export default config;Next.js / React Frontend
// next.config.js or next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
env: {
// Only expose PUBLIC variables to the browser
NEXT_PUBLIC_CHAIN_ID: process.env.CHAIN_ID,
NEXT_PUBLIC_CONTRACT_ADDRESS: process.env.NEXT_PUBLIC_CONTRACT_ADDRESS,
// These stay server-side only
// PRIVATE_KEY is NOT exposed
},
};
module.exports = nextConfig;// lib/config.ts
// Server-side config (never exposed to browser)
export const serverConfig = {
privateKey: process.env.PRIVATE_KEY || "",
rpcUrl: process.env.SEPOLIA_RPC_URL || "",
etherscanApiKey: process.env.ETHERSCAN_API_KEY || "",
};
// Client-side config (exposed via NEXT_PUBLIC_ prefix)
export const clientConfig = {
chainId: process.env.NEXT_PUBLIC_CHAIN_ID || "11155111",
contractAddress: process.env.NEXT_PUBLIC_CONTRACT_ADDRESS || "",
};Node.js Script Example
// scripts/deploy.ts
import { ethers } from "hardhat";
import * as dotenv from "dotenv";
dotenv.config();
async function main() {
// Access environment variables
const contractName = process.env.CONTRACT_NAME || "MyContract";
const constructorArgs = process.env.CONSTRUCTOR_ARGS
? JSON.parse(process.env.CONSTRUCTOR_ARGS)
: [];
console.log(`Deploying ${contractName}...`);
const Contract = await ethers.getContractFactory(contractName);
const contract = await Contract.deploy(...constructorArgs);
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log(`${contractName} deployed to: ${address}`);
console.log(`Update your .env: CONTRACT_ADDRESS=${address}`);
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});Environment Variable Validation
// utils/env.ts
import * as dotenv from "dotenv";
dotenv.config();
interface EnvConfig {
PRIVATE_KEY: string;
SEPOLIA_RPC_URL: string;
ETHERSCAN_API_KEY?: string;
}
function validateEnv(): EnvConfig {
const required = ["PRIVATE_KEY", "SEPOLIA_RPC_URL"];
const missing = required.filter((key) => !process.env[key]);
if (missing.length > 0) {
throw new Error(
`Missing required environment variables: ${missing.join(", ")}`
);
}
// Validate private key format
if (!process.env.PRIVATE_KEY?.startsWith("0x")) {
throw new Error("PRIVATE_KEY must start with 0x");
}
if (process.env.PRIVATE_KEY.length !== 66) {
throw new Error("PRIVATE_KEY must be 66 characters (0x + 64 hex chars)");
}
return {
PRIVATE_KEY: process.env.PRIVATE_KEY,
SEPOLIA_RPC_URL: process.env.SEPOLIA_RPC_URL,
ETHERSCAN_API_KEY: process.env.ETHERSCAN_API_KEY,
};
}
export const env = validateEnv();๐ .env.example Template
Always commit a .env.example file (without real values) so others know what variables are needed:
# .env.example
# ============================================
# RPC PROVIDER URLs
# ============================================
# Get your API key from: https://www.alchemy.com/ or https://www.infura.io/
SEPOLIA_RPC_URL=https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
MAINNET_RPC_URL=https://mainnet.infura.io/v3/YOUR_API_KEY
# ============================================
# WALLET CONFIGURATION
# ============================================
# โ ๏ธ WARNING: Use a dev/test wallet only!
# Never use a wallet with real funds for development
PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000
# ============================================
# API KEYS
# ============================================
# Get your API key from: https://etherscan.io/apis
ETHERSCAN_API_KEY=your_etherscan_api_key_here
ALCHEMY_API_KEY=your_alchemy_api_key_here
# ============================================
# NETWORK CONFIGURATION
# ============================================
CHAIN_ID=11155111
NETWORK_NAME=sepolia
# ============================================
# DEPLOYED CONTRACT ADDRESSES (Optional)
# ============================================
CONTRACT_ADDRESS=๐ก๏ธ Best Practices
โ Do
- โ
Use
.env.example- Show required variables without exposing secrets - โ Validate on startup - Check all required variables exist
- โ Use separate wallets - Dev/test wallet โ production wallet
- โ Rotate keys regularly - Especially if compromised
- โ Use TypeScript - Type your environment variables
- โ Document variables - Add comments explaining what each variable does
- โ
Use different
.envfiles -.env.local,.env.production, etc.
โ Donโt
- โ Never commit
.env- Add to.gitignoreimmediately - โ Never log secrets - Donโt
console.log(process.env.PRIVATE_KEY) - โ Never expose in frontend - Donโt put secrets in
NEXT_PUBLIC_*variables - โ Never share screenshots - Never share terminal output with secrets
- โ Never use production keys in dev - Use separate API keys for each environment
๐จ EMERGENCY: What to Do If You Leaked Secrets on GitHub
โฑ๏ธ Immediate Actions (First 5 Minutes)
-
๐จ STOP THE BLEEDING
# Revoke API keys immediately # - Alchemy: Go to dashboard โ Revoke API key # - Infura: Go to dashboard โ Revoke API key # - Etherscan: Regenerate API key -
๐ฐ MOVE FUNDS IMMEDIATELY
- If you leaked a private key, move ALL funds from that wallet RIGHT NOW
- Create a new wallet
- Transfer everything to the new wallet
- Do not wait - bots monitor GitHub 24/7
-
๐ REGENERATE ALL SECRETS
- All API keys that were exposed
- All private keys (and move funds first!)
- All passwords/tokens
๐งน Clean Up GitHub History (First Hour)
โ ๏ธ Warning: Removing secrets from Git history is complex. Once committed, assume they are compromised.
Option 1: If the repo is new/private and you havenโt pushed much:
# Delete the repo and recreate it (easiest)
# GitHub โ Settings โ Delete this repositoryOption 2: Remove secrets from Git history (advanced):
# Use git-filter-repo (recommended)
git filter-repo --path .env --invert-paths
# Force push (โ ๏ธ Coordinate with team first!)
git push origin --force --allOption 3: Use BFG Repo-Cleaner:
# Install BFG
brew install bfg # or download from https://rtyley.github.io/bfg-repo-cleaner/
# Remove .env file from history
bfg --delete-files .env
# Clean up
git reflog expire --expire=now --all
git gc --prune=now --aggressive๐ Check What Was Exposed
# Search your Git history for exposed secrets
git log --all --full-history --source -- .env
# Check what was committed
git show <commit-hash>:.env๐ Verify Secrets Are Removed
-
Check GitHubโs Secret Scanning:
- GitHub automatically scans for exposed secrets
- Check: Repository โ Security โ Secret scanning alerts
-
Manual search:
- Search your repo for the exposed key/secret
- Use GitHubโs search:
repo:yourusername/yourrepo "exposed_key"
-
Check Git history:
# Verify .env is no longer in history git log --all --full-history -- .env # Should return nothing if successfully removed
๐ Prevention Checklist
-
.envis in.gitignore -
.env.exampleis committed (template only) - Pre-commit hooks installed (prevent accidental commits)
- GitHub secret scanning enabled
- Team is trained on security practices
- Using separate dev/test API keys
- Regular security audits of commits
๐ ๏ธ Tools to Prevent Future Leaks
-
git-secrets (AWS)
git secrets --install git secrets --register-aws -
trufflehog (Scan for secrets)
# Scan your repo trufflehog git file://. --json -
pre-commit hooks
# .pre-commit-config.yaml repos: - repo: https://github.com/Yelp/detect-secrets rev: v1.4.0 hooks: - id: detect-secrets
๐ Related Topics
- ๐ Hardhat Setup Guide - Complete Hardhat environment setup
- ๐ Git Basics - Version control best practices
- ๐ก๏ธ Smart Contract Security - Security best practices
๐ Further Reading
- The Twelve-Factor App: Configย
- OWASP Secrets Management Cheat Sheetย
- GitHub: Removing sensitive data from a repositoryย
- GitGuardian: Secrets Detectionย
Remember: In Web3, security isnโt optional. One leaked key can drain your entire wallet. Always err on the side of caution. ๐