Skip to content

feat: single source of truth for public config in config/<env>.env - #65

Merged
shev-titan merged 6 commits into
devfrom
feat/hardhat-env-loader
Sep 17, 2026
Merged

shev-titan merged 6 commits into
devfrom
feat/hardhat-env-loader

Conversation

@lsheva

@lsheva lsheva commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Public, per-environment configuration now lives in config/dev.env and config/prd.env, and every consumer reads those same files.

  • Hardhat — env-loader plugin resolves config/<name>.env via --env <name>, then applies machine/secret override files from envLoader.overrideEnvFiles
  • Keeper / market-maker — node --env-file, replacing the market-maker's custom dotenv loader
  • Subgraphs — ENV_FILE=… pnpm prepare:env, replacing set -a && source ../.env
  • ECS deploys — the workflow sources the config file and builds the task-definition environment from the keys it declares

Secrets stay in the repo-root .env locally and in GitHub Secrets in CI. Both still win over the config file, because the real environment takes precedence over --env-file.

Workflows no longer name each variable four times

The two ECS workflows previously declared every value in the step env:, again as a jq --arg, again in the jq object, and once more as a :- default. They now derive the environment block from the keys in the config file and name only secrets and computed values, so a public setting is written exactly once. Empty values are dropped so apps apply their own defaults; those defaults were checked against the ones the workflows used to hardcode (DRY_RUN false, DISCOVERY_MODE events, SWEEP_INTERVAL_MS 60000, HEALTH_PORT 3000, MAKER_HEALTH_PORT 3001) and they match.

NETWORK canonicalized to base

NETWORK meant three different things: the keeper required base-mainnet, the subgraph manifests and market-maker YAML used base, and Hardhat declared a network literally named base-mainnet. The GitHub environment that deploys mainnet already sets NETWORK=base, so the keeper and Hardhat were the outliers. The keeper still accepts base-mainnet as a deprecated alias, since task definitions registered before this change carry it.

Also

  • Staging deploys disabled: stg removed from the deploy workflows, config/stg.env and contracts' run:stg deleted
  • Dead docker compose script removed from points-indexer (no compose file existed)
  • config/*.env values are unquoted, since docker run --env-file does not strip quotes in every CLI version

Test plan

  • keeper and market-maker: typecheck, lint, and 506 tests pass
  • All six touched workflow files parse as YAML
  • Environment-block generation produces the expected 25 keys from the real config/dev.env, dropping empty values and including secrets
  • loadConfig() (keeper) and loadPortfolioConfig() (market-maker) both load end-to-end from config/dev.env + dummy secrets
  • NETWORK resolves for base, base-sepolia, the base-mainnet alias, and errors clearly on an unknown value
  • Both subgraphs render from config/dev.env and from .env.example (the CI path)
  • Confirm local ../.env / .env values win over the named env file for overlapping keys
  • Deploy to dev and confirm the rendered task definition matches the previous revision's environment

Follow-ups

  • config/prd.env still lacks PERPS_ADDRESS, FUTURES_ADDRESS, HASHPRICE_USD_ADDRESS, BTC_USD_FEED_ADDRESS and the start blocks. These exist nowhere in GitHub either — the main environment has no variables at all — so they are marked with TODOs. The keeper and maker fail loudly at startup without them.
  • VAULT_START_BLOCK has never been set, so the vault subgraph has been rendering an empty startBlock. Pre-existing, not introduced here.
  • The dev GitHub Environment variables (NETWORK, PERPS_ADDRESS, …) are now unread and can be deleted. Only KEEPER_DESIRED_COUNT, MAKER_DESIRED_COUNT and the Goldsky/AWS ones still matter.

Load per-environment config from config/<name>.env with machine-local
override files, and wire npm scripts to --env.
Document the secret keys expected in override env files for local and CI use.
NETWORK meant three different things: the keeper required base-mainnet,
the subgraph manifests and market-maker YAML used base, and the Hardhat
config declared a network literally named base-mainnet. The GitHub
environment that deploys mainnet already sets NETWORK=base, so the
keeper and Hardhat were the outliers.

Settle on base / base-sepolia / hardhat everywhere. The keeper still
accepts base-mainnet as a deprecated alias, since task definitions
registered before this change carry it, and keeps Alchemy's own
base-mainnet spelling for the RPC subdomain.
Contract addresses, start blocks and runtime knobs were duplicated
across GitHub environment variables, the deploy workflows and ad-hoc
loaders, so the same value could disagree between local runs, the
subgraphs and ECS.

Make config/dev.env and config/prd.env the single source for public
values, and have every consumer read them: the keeper and market-maker
via node --env-file, the subgraph renderers via ENV_FILE, and the
deploy workflows by sourcing the file. Secrets stay in the repo-root
.env locally and in GitHub Secrets in CI; both still win, because the
real environment takes precedence over --env-file.

The two ECS workflows built their environment block by naming each
variable four times: once in the step env, once as a jq --arg, once in
the jq object, and once more as a :- default. They now derive the block
from the keys declared in the config file and name only secrets and
computed values, so a public setting is written exactly once. Empty
values are dropped so apps apply their own defaults, which were checked
to match the defaults the workflows used to hardcode.

Also drop the market-maker's custom dotenv loader in favour of
--env-file, disable the stg deploy path, and remove a dead compose
script in points-indexer.

config/prd.env is still missing the mainnet addresses and start blocks;
they exist nowhere in GitHub either, and are marked with TODOs.
@lsheva lsheva changed the title feat(contracts): add Hardhat --env loader plugin feat: single source of truth for public config in config/<env>.env Sep 17, 2026
pnpm runs scripts with sh, which is dash on the CI runners. POSIX `.`
searches PATH when its operand contains no slash, so `ENV_FILE=.env.example`
failed with ".env.example: not found" while passing locally, where
/bin/sh is bash and falls back to the current directory.

Prefix a bare filename with ./ before sourcing, leaving paths that
already contain a slash untouched.
@shev-titan
shev-titan merged commit 9ead5c4 into dev Sep 17, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants