# Share of ETH held in MPC wallets: investigation log **Result (snapshot 2026-10-29): ≈17% of ETH supply (20.9M of 122.17M ETH), 90% interval ≈8–33%.** The answer depends mostly on two facts nobody publishes: whether Coinbase custody signs with MPC (14.2M ETH under Coinbase control) and whether Binance does (7.9M ETH). Scenarios: 10.4% if neither uses MPC, 27.4% if both fully do. ## Definition We count ETH whose *signing authority* is an MPC/TSS ECDSA key (the full key is never assembled): - execution-layer (EL) ETH in an EOA or EIP-7702-delegated EOA whose key is MPC-held, and - staked ETH whose validator withdrawal address is such an EOA. Not counted: - ETH in smart contracts, even if MPC keys control them (e.g. a Safe with MPC signers). - Staked ETH with contract withdrawal credentials (Lido, EigenPods, Safes) or BLS (0x00) credentials. - Economic exposure such as stETH held in an MPC wallet. - Shamir/key-sharding schemes that reassemble the key, HSM custody and multisig contracts. These are *not* MPC. ## Reproduce ``` # 1. raw data (network; ~1 GB validator dump) curl -s --compressed -o data/raw/validators_head.json https://ethereum-beacon-api.publicnode.com/eth/v1/beacon/states/head/validators curl -s --compressed -o data/raw/pending_deposits.json https://ethereum-beacon-api.publicnode.com/eth/v1/beacon/states/head/pending_deposits python3 -I scripts/beacon_aggregate.py # -> data/beacon_by_withdrawal.csv python3 -I scripts/classify_code.py data/raw/withdrawal_addrs.txt data/withdrawal_code.csv python3 -I scripts/cred_features.py # -> data/cred_features.csv python3 -I scripts/blockscout_top_holders.py 100 # -> data/raw/blockscout_top.jsonl (>=100 ETH) for r in https://relay.ultrasound.money https://boost-relay.flashbots.net https://aestus.live https://agnostic-relay.net https://global.titanrelay.xyz; do python3 -I scripts/relay_fee_recipients.py $r 30; done python3 -I scripts/fee_recipient_map.py # -> data/withdrawal_fee_recipients.csv python3 -I scripts/deposit_senders.py 21500000 26400000 # -> data/raw/deposit_txs.csv python3 -I scripts/profile_addresses.py data/raw/unlabeled_eoa_top.txt data/profiles_el.jsonl # resumable python3 -I scripts/profile_addresses.py data/raw/staking_profile_targets.txt data/profiles_staking.jsonl # labels: see data/labels/README.md; fingerprints: research/onchain_fingerprints.md (scripts/fingerprint/aggregate.py) # 2. model (offline, ~2 min) python3 -I scripts/attribute.py # -> data/holdings_attributed.csv (every ETH of supply -> entity or cohort) python3 -I scripts/estimate.py # -> results/estimate.json, results/breakdown.csv ``` To test a different belief, edit `assumptions/mpc_shares.csv` (low/mid/high per entity or cohort, each with its basis) and rerun `estimate.py`. ## Steps and findings (chronological) | # | Step | Script / source | Finding | |---|---|---|---| | 1 | Total supply = EL balances + beacon balances − processed deposits | ultrasound.money `supply-parts`; `data/supply.json` | **122,172,606 ETH**: EL excl. deposit contract 76.66M, validators 44.24M, deposit queue 1.275M (matches beacon `pending_deposits`) | | 2 | Aggregate all 2.39M validators + pending deposits per withdrawal credential | `beacon_aggregate.py` | 45.51M staked or queued across 37,917 credentials | | 3 | `eth_getCode` on all 31,104 withdrawal addresses | `classify_code.py` | Withdrawal to EOA 26.79M (58.9%), 7702-EOA 1.44M, contract 16.89M (37.1%), BLS 0.40M. **MPC is impossible for 17.3M staked ETH** | | 4 | Top holders ≥100 ETH from Blockscout (43,250 addresses) | `blockscout_top_holders.py` | Cover 85.2% of EL ETH. Blockscout flags 7702 as `proxy_type=eip7702`; contract flag agrees with RPC on 14,797/14,800 | | 5 | Balance staleness check, 300 random addresses vs RPC | `balance_check.py` | Blockscout aggregate 0.8% high (some small addresses since emptied). Accepted; well inside other uncertainties | | 6 | Key-management research: exchanges, custodians, ETFs | `research/custody_tech_exchanges_custodians.{md,json}` | Coinbase Custody: filings describe sharded keys reconstituted to sign. Anchorage, Fidelity, Zodia, Paxos and Ledger use HSMs. Copper, Fireblocks, Ceffu and Cobo use MPC. Binance probably TSS (2020 evidence). OKX: encrypted single keys. Robinhood: MPC vendor per 10-K | | 7 | Key-management research: treasuries, staking providers, retail | `research/custody_tech_treasury_staking_retail.{md,json}` | BitMine 6.05M ETH (5.23M staked), custodian undisclosed. SharpLink: Anchorage + Coinbase. Retail MPC only ~0.1–0.2M ETH; Privy, Turnkey and CDP v2 are TEE/SSS, not MPC | | 8 | Free label set: PoR lists, Etherscan scrapes, Dune spellbook, DefiLlama, OLI, Rocket Pool/EigenLayer/StakeWise registries | `data/labels/README.md` | 218k labeled addresses | | 9 | Manual tracing of the largest unlabeled staking EOA | Routescan txlist | `0x8e60…8224` (3.62M ETH): funded by Binance 100, forwards withdrawals via `0x26ad…` to the Binance 14 hot wallet → **Binance**. All manual attributions with evidence: `data/manual_attributions.csv` | | 10 | 30 days of MEV-relay payloads (185k slots) → validator → fee recipient | `relay_fee_recipients.py`, `fee_recipient_map.py` | Credentials holding 42.3M of 45.5M staked ETH proposed. Labeled fee recipients attribute 2.87M to Coinbase; clustering found Bitcoin Suisse (623k), Binance and Gemini | | 11 | Direct deposit-contract txs since 2025 (207k) decoded for withdrawal credentials | `deposit_senders.py` | Sender labels attribute e.g. Upbit (646k credential). Batch-deposit logs (`deposit_events.py`) only partly fetched (Routescan throttling); not used | | 12 | Transaction profiles of the largest unlabeled EOAs: first/last activity, labeled counterparties | `profile_addresses.py` | 782 of the 3,000 largest unlabeled EOAs profiled before throttling forced a stop. Rules: pre-2019 and dormant since 2020 → `legacy_dormant`; a labeled exchange/custodian first funder or counterparty → `linked`; otherwise `unknown_modern` / `unknown_legacy_active` | | 13 | On-chain custody-platform fingerprints (agent) | `research/onchain_fingerprints.md`, `data/fingerprint/` | "Coinbase Prime: Custody Deposit Funder 1" gas-funds **29,983 Coinbase custody vaults holding 4.26M ETH** (184 ETF-tagged). BitGo TSS identified via forwarder parents (only ~7k ETH). Fireblocks has no general fingerprint (each customer has its own gas station) | | 14 | Attribution: every ETH → entity or cohort | `attribute.py` → `data/holdings_attributed.csv` | Reconciles to supply within 511 ETH | | 15 | Estimate: per-key MPC share (split-uniform around the best guess), Gaussian copula ρ=0.5, plus scenarios | `estimate.py`, `assumptions/mpc_shares.csv` | See Result | ## Result detail (results/estimate.json, results/groups.csv) | Group (results/groups.csv) | ETH | % supply | MPC ETH at best guess | |---|---:|---:|---:| | Cannot be MPC: contracts, contract/BLS withdrawal credentials | 26.43M | 21.6% | 0 | | Coinbase family (exchange, Prime/Custody incl. fingerprinted vaults, ETFs, US gov) | 14.17M | 11.6% | 3.49M (24.6%) | | Binance (PoR wallets + staking) | 7.85M | 6.4% | 5.02M (64.0%) | | Other identified exchanges, custodians, treasuries, funds | 20.46M | 16.7% | 6.02M | | Unattributed staking EOAs (size/age cohorts) | 10.67M | 8.7% | 2.26M | | Unattributed EL EOAs ≥100 ETH (profiled cohorts + unprofiled) | 31.27M | 25.6% | 3.91M | | Tail <100 ETH | 11.33M | 9.3% | 0.23M | The MPC column sums to the 20.93M point estimate (`linked` rows blended with the unknown-modern prior, as in the headline). | Statistic | % of supply | |---|---:| | Point estimate (all assumptions at best guess) | **17.1%** | | Monte Carlo median, ρ=0.5 (90% interval) | 19.1% (8.2–33.3%) | | ρ=0, independent errors (90%) | 19.7% (15.0–24.8%) | | ρ=1, fully correlated (90%) | 17.3% (5.4–38.4%) | | Hard bounds (all low / all high) | 4.0–40.9% | | Coinbase not MPC / fully MPC | 14.4% / 25.2% | | Binance not MPC / fully MPC | 13.0% / 19.3% | | Neither / both | 10.4% / 27.4% | ## Decisions and caveats - Balances use the `latest` block; public RPCs prune historical state. The drift over the snapshot window is negligible. - The deposit contract's EL balance (92.94M) is excluded; it would double-count staked ETH. - Blockscout tag names that are generic ("Fee Recipient", "Beacon Depositor", "Null", "Genesis Address") are not treated as entities. - `linked` attribution (exchange-funded wallets) can be a customer's own wallet rather than the exchange's. Those rows therefore get the average of the entity's and the unknown-modern assumption, with the wider range. - The research "best guess" is treated as a median, not a mode, hence the split-uniform distribution. Correlation ρ=0.5 is a judgement call: errors in reading custody practice are likely shared, but not fully. - Throttled public APIs left two jobs incomplete: EL profiling (782/3,000) and the batch-deposit log scan. Both are resumable. Finishing them would shrink the `el_unprofiled_ge100` bucket (19.1M ETH, the second-largest uncertainty) but cannot settle the Coinbase/Binance questions. - What would move the number most: (1) a primary disclosure of Coinbase Custody's current signing scheme; (2) Binance's; (3) BitMine's custodian (its FY2026 10-K is due); (4) a labeled-address source such as Arkham/Nansen for the 9.7M ETH `unknown_modern` cohort. ## Publishing (2026-10-29) - `scripts/build_site.py` renders `site/index.html` from `results/estimate.json`, `results/groups.csv` and `assumptions/mpc_shares.csv`; no number on the page is typed by hand. `estimate.py` now also writes `results/groups.csv`, whose MPC column sums exactly to the point estimate (it blends `linked` rows the same way as the headline). Headline unchanged at 17.1%. - Served by Caddy with on-demand TLS behind a DNS-checking permission gate; see `deploy/DEPLOY.md`. Verified end to end: Let's Encrypt certificates issued over IPv4 and IPv6 (sslip.io test names), and the page fetched from outside the server. - The agent sub-reports referenced by `research/custody_tech_treasury_staking_retail.md` were moved from the session scratchpad into `research/agent_notes/`. ## Report and cleanup (2026-10-29) - `report/mpc_eth_share.tex`: self-contained LaTeX summary of the whole investigation (definition, supply, pipeline, attribution, evidence, uncertainty model, results, caveats, reproduction, publication). Build: `cd report && latexmk -pdf mpc_eth_share.tex`. - Removed superseded intermediates in `data/fingerprint/raw/`: un-enriched inputs whose `_enriched` versions hold every row and column, the `cbp_f1/f2` and `cbp_funder2_from*` chunks merged into `cbp_funder_recipients_enriched.csv` (all rows verified present), and the `type4_*_old` files. Also removed the empty bloxroute relay dump (that relay failed) and `__pycache__`. Results unchanged. - Agent notes no longer point at session-scratchpad paths. - Git: bulky re-downloadable raw data is ignored (`.gitignore`); the derived inputs `attribute.py` needs are tracked, so the model step reproduces from a clean checkout.