--- name: ops-check-balances description: Internal — for Boundless team members only. Audit native ETH, market deposit, prover collateral, and distributor ZKC reserve balances for every operator-managed address (provers, distributor, order generators, signal). Use when the user wants to know which addresses need topping up, asks about the balance of provers/OGs/distributor/signal signers, says something is "running low" or "out of gas", or wants a periodic operational health check on operator wallets. Defaults to prod env (mainnets + prod testnets) — pass `--all` to also include staging. --- # Check Operator Balances Audit balances on every Boundless-operated address: native ETH, market deposit (`balanceOf`), prover collateral (`balanceOfCollateral`), and the distributor's bridged-ZKC ERC20 reserve. The list of addresses, per-role thresholds, and which checks apply (gas vs market deposit) are loaded at runtime from the **runbooks** repo. The skill itself contains only public protocol data (chain → market/collateral-token addresses, public RPCs). ## Prerequisites 1. **CLI tools**: `cast` (from [foundry](https://book.getfoundry.sh/)), `jq`, `python3`, `curl`, and standard Unix utilities. `python3` is used for wei → human-readable conversion (`awk` silently truncates large integers) and for parsing `deployment.toml`. `curl` fetches the contract registry from the public boundless repo. 2. **`gh` CLI authenticated against boundless-xyz** (required). The canonical address list and thresholds live in the private `boundless-xyz/runbooks` repo and are fetched at runtime via `gh api` — no local clone needed. Verify with: ```bash gh api -H "Accept: application/vnd.github.raw" \ repos/boundless-xyz/runbooks/contents/addresses/operator_addresses.json | head -5 ``` If that fails with a 401/403, run `gh auth login` and ensure your account has access to `boundless-xyz/runbooks`. 3. **`network_secrets.toml`** at the repo root (optional). Public RPCs are usually sufficient. When a balance comes back as exactly `0` it may be rate-limited rather than actually empty — in that case retry against the private QuikNode endpoint from `[networks..rpc]`. The runbook has setup instructions. ## What to check For each address in `operator_addresses.json`, on every chain it operates on: 1. **Native ETH balance** (when `needs_gas: true`) — `cast balance --rpc-url `. Required to pay gas. 2. **Market deposit** (when `needs_market_check: true`) — `cast call "balanceOf(address)(uint256)" `. Funds available to pay for orders (requestor) or to claim as rewards (prover). 3. **Prover collateral** (when `role: prover`) — `cast call "balanceOfCollateral(address)(uint256)" `. Stake the prover has put up. 4. **Distributor ZKC reserve** (when `role: distributor`) — `cast call "balanceOf(address)(uint256)" `. The distributor's raw ERC20 balance of bridged ZKC, used to top up provers' collateral. **Not the same as `balanceOfCollateral`** — that's deposited stake; this is the unstaked reserve. ## Thresholds Loaded from `operator_addresses.json`'s `thresholds` block. Resolved field-by-field with priority: ``` per-entry override → by_role[role] → by_chain[chain] → default ``` ETH thresholds for **distributor-managed roles** (provers, OGs) come from `by_chain` because the distributor's `ETH_THRESHOLD` varies by ~2 orders of magnitude across chains (0.008 ETH on Taiko vs 1 ETH on Base Sepolia staging). Audit thresholds are tied to those values: WARN ≈ `0.5 × ETH_THRESHOLD` (distributor missed at least one cycle), CRIT ≈ `0.125 × ETH_THRESHOLD` (auto-top-up clearly broken). ETH thresholds for **non-distributor-managed roles** (`distributor` itself, `signal` signers) come from `by_role` and ignore the chain. Distributor's WARN matches the operational `DISTRIBUTOR_ETH_ALERT_THRESHOLD` of 0.5; signal signers use a smaller threshold tuned to their ~0.0001 ETH/day burn. ZKC thresholds (prover collateral, distributor reserve) come from `by_role.prover` and `by_role.distributor` respectively, since they're uniform across chains. `eth_warn`/`eth_crit` gate the **native-ETH gas** balance. To alert on an address's **market deposit** (`balanceOf`) instead of (or in addition to) gas, set `deposit_warn`/`deposit_crit` (ETH-equivalent units). These are **opt-in**: there is no default, so the script flags a deposit (`DEPOSIT-LOW` / `DEPOSIT-CRIT`) only for entries that resolve a deposit threshold. Used for requestors whose deposit is the operational signal (e.g. KOG, deposit WARN 1 / CRIT 0.1, while its gas stays on the base-mainnet default). The resolved values the skill consumes live in `operator_addresses.json`'s `thresholds` block. The runbook README no longer mirrors a values table — the operational source of truth is the distributor Pulumi config in the boundless repo (`infra/distributor/Pulumi.l-prod-{chain_id}.yaml` / `Pulumi.l-staging-{chain_id}.yaml`, keys like `ETH_THRESHOLD` / `DISTRIBUTOR_ETH_ALERT_THRESHOLD`). The `deposit_warn`/`deposit_crit` overrides are runbook-local and exist only in `operator_addresses.json`. A specific entry can override any field by setting a `thresholds: { eth_warn: ..., ... }` block on it — only the fields you set are overridden; the rest fall through. ## Public chain → contract addresses Contract addresses (market, collateral token) are **fetched at runtime** from the public `contracts/deployment.toml` in the boundless repo on GitHub. The skill never hardcodes them — when a chain is added or addresses rotate, it's a single edit to `deployment.toml` and the skill picks it up on next run, no code change. ```bash DEPLOYMENT_TOML_URL="https://raw.githubusercontent.com/boundless-xyz/boundless/main/contracts/deployment.toml" ``` The chain label used in `operator_addresses.json` must match a `[deployment.