Feb 26, 2026
We read TON's Elector contract so you don't have to
RSQUAD
Dmitrii Kuznetsov
Nikita Monakhov
In God we trust. All others must bring data.
W. Edwards Deming 

We spent a week reading the FunC source code of TON's Elector contract. Not because we wanted to — because nobody had written a proper breakdown of how validator elections actually work under the hood. 

So here it is: how you get elected, why your 2M stake might only count as 1.5M, and where your TON can quietly disappear if you're not careful.

The cycle: four stages, 36.5 hours, zero downtime

TON's validation cycle has four phases, each governed by ConfigParam 15:

Election (~6h). The network opens applications. Validators submit their stake, public key, ADNL address, and max_factor to the Elector contract. Think of it as a job application — but with money on the line.

Freeze (~2h). Applications close. The Elector sorts candidates, runs its selection algorithm, and locks in the validator set. No more late entries.

Validation (~18h). The real work. Elected validators produce and sign blocks on Masterchain and Workchains. Consensus runs. The network lives.

Holding (~9h). Validators are done, but their stake stays locked. Why? Because misbehavior might surface later. This is the dispute window — anyone can file a complaint with cryptographic proof, and if more then 66% of validators (weighted by stake) agree, the offender gets slashed.

To ensure continuous network operation, two validation cycles (rounds) operate in an overlapping manner, each with its own validator set. While one set validates, the other is either electing or holding. Seamless rotation. No gaps.

Tick-tock: the heartbeat no one sends

Here's the thing that surprises people: nobody triggers these transitions. There's no cron job. No external bot. The Elector is a tick-tock contract — it gets an implicit system message injected by the collator into every masterchain block.

On each tick-tock, the Elector reads the clock, compares it against config parameters, and decides:

  • Time to open a new election round? Open it.
  • Application window closed? Sort candidates, compute stakes, finalize the set.
  • Holding period over? Release funds.

Gas for all of this? Zero. The Elector is listed in ConfigParam 31 — the whitelist of governance contracts exempt from both gas and storage fees. Tick-tock transactions on the Elector consume no gas at all. No one pays. The protocol just runs it — because it has to.

All election logic is triggered this way. Pure determinism. No humans in the loop.

Submitting a stake: the entry checks

A validator submits a stake application via elector_new_stake#4e73744b. The message includes the validator's public key, ADNL address, max_factor, and the stake amount. The Elector runs a gauntlet of checks before accepting:

  1. Masterchain only. Only masterchain addresses can apply. Workchain addresses get their stake refunded immediately.
  2. Signature verification. The bid must be properly signed.
  3. max_factor ≥ 1.0. Otherwise — rejected.
  4. Anti-whale check. Your stake can't be less than 1/4096 of the total currently accumulated stakes.
  5. Correct election_id. You must be applying to the right round.
  6. Elections still open. If they're already finalized — too late.
  7. Duplicate key? Top up. If your public key already has a registered stake, the new amount is added — but only if sent from the same address.
  8. Above min_stake. Currently 300,000 TON on mainnet. Below that? Funds returned.

Every rejected application means the stake goes back to the sender. No penalties for trying.

Conducting elections: who actually gets in

This is where it gets interesting. The Elector Contract doesn't just pick the richest validators. It optimizes for Total Effective Stake (TES) — a metric that balances decentralization and economic security.

Step 1: Prechecks. Before the Elector even touches the candidate list, it runs a series of gate checks — fast, cheap, and brutal:

  1. If now() < elect_close — the stake acceptance window is still open. Do nothing. Wait.
  2. If no config contract exists (ConfigParam 0 is null) — postpone. Can't finalize without config.
  3. Load stake limits from ConfigParam 17: min_stake, max_stake, min_total_stake, max_stake_factor.
  4. If total accumulated stake < min_total_stake — postpone. Not enough skin in the game.
  5. If the failed flag is set (no new stakes received since last failure) — postpone. Nothing changed, no point recalculating.
  6. If the finished flag is set — do nothing. Elections already finalized.

Only if all gates pass does the Elector proceed to the actual selection.

Step 2: Sort and filter. 

  1. All candidates are sorted by stake (descending). 
  2. Those below min_stake are cut. 
  3. Those beyond max_validators are cut.

Step 3: Maximize Total Effective Stake (TES). 

From the set of participants who passed the filters, a subset is selected such that the total effective stake is maximized.

The TES is calculated sequentially for all participants, starting from the value of min_validators param to the last one, as the sum of the effective stakes of the preceding participants in the descending order of stake amount.

The key insight: if you're a whale with 2M TON stake and the staker with the smallest stake has 500k, your effective stake isn't 2M — it's decreased by the smallest validator's stake multiplied by your passed max_factor. A max_factor of 3 means your effective contribution is at most 500k × 3 = 1,5M. 

The rest? Sent to the credits hashmap — you can withdraw it anytime, but it doesn't count toward consensus weight.

This mechanism prevents a single whale from dominating voting power. TON's max_stake_factor in config is currently 3, meaning no validator's effective stake can be more than 3× the smallest one's.

Step 4: Verify validators number. After finding the optimal set, the Elector checks the number of validators ≥ min_validators

If either fails — elections are postponed. Rolled into a new round. The network doesn't compromise on security thresholds.

A real example

Imagine 

  • 14 candidates
  • min_validators = 10
  • Stakes range from 350k to 2MTON with varying max_factor values.

The algorithm doesn't just grab the top 7 with the highest raw stakes (even though their theoretical TES would be largest). It's forced to start from at least 10 validators. It tests all subsets from 10 to 14 and finds that 13 validators yield the maximum TES of 8,200k — because the 14th candidate (350k TON) would actually decrease TES by dragging down the effective contribution of the hugest stakes.

That last candidate meets min_stake. They're technically eligible. But including them makes the network less economically secure. So they're excluded by math, not by authority.

Note 1. If validator’s effective stake is less than sent Ton amount then the diff is saved to `credits` hashmap — validator can withdraw this amount at any time.

Note 2. All election participants will be tested during the maximisation of TES. Even if the participant decreases the TES, the algorithm keeps checking all the rest.

Where the rewards come from

Validators don't work for free. The reward pool accumulates with every block:

  • 1.7 TON per masterchain block (protocol subsidy via ConfigParam 14)
  • 1.0 TON per basechain block
  • Transaction fees — gas and storage fees from every transaction

When a basechain splits into multiple shardchains, the 1.0 TON subsidy splits proportionally — keeping the per-time reward consistent regardless of shard count.

These accumulate in the Elector's internal reward pool during the validation period. Nothing is paid out until the holding phase ends. Then each validator receives a share proportional to their effective stake weight — not their raw stake.

Staked 2M but your effective stake was capped at 1,5M? So you get rewards based on 1,5M. The 500k excess sitting in credits earns nothing.

Corner cases that bite

  1. stake > max_stake — Accepted. But the excess won't be returned during rewards. Funds are effectively lost.
  2. stake < min_stake — Rejected. Funds returned to sender.
  3. stake < total_stake / 4096 — Rejected. Anti-dust protection.
  4. max_factor > max_stake_factor — Accepted. max_factor is silently overridden by config value.
  5. max_factor < 1 — Rejected. Funds returned.
  6. All validators pass — All effective stakes equal the minimum accepted stake.
  7. max_factor = 1 — Everyone gets the same weight.

The most dangerous edge case: sending more than max_stake. The Elector accepts it, uses max_stake for calculations, and the surplus is gone. Not returned. Not credited. Just lost. Read the code before you stake it out.

The bigger picture

TON's validation cycle is a self-operating machine. The Elector contract handles elections, stake management, reward distribution, and penalty enforcement — all triggered by tick-tock messages with no human intervention.

Every parameter is on-chain. Every transition is deterministic. Every rule is enforced by code running on TVM.

Share this post