Skip to main content
For easier integration, you can add the Satellite contract as a dependency and use its interfaces inside your smart contract. To do that using Foundry, you should first install it using:
and then we recommend configuring a remapping inside your foundry.toml file:
Or if you are using NPM (e.g. with Hardhat), you can just install it using:

How it works

The contract uses historical token balances as voting power. Because the system operates on timestamps rather than block numbers, the same historical moment can be proven across multiple chains simultaneously — Satellite resolves the correct block number per chain internally. This makes it possible to aggregate cross-chain balances in a single vote without manually tracking chain-specific block heights. Proving individual storage slots for every voter via the Storage Proof API would be impractical at scale. Instead, only two things need to be proven once per vote: the block header (to anchor the timestamp on-chain) and the token account’s storage root (a single 32-byte value that commits to the entire account storage at that block). After that, each voter’s balance is verified on-demand at vote time using an MPT proof they fetch themselves from any standard RPC node (eth_getProof). The contract checks this proof against the already-proven storage root using verifyOnlyStorage from Satellite — no Storage Proof API call is needed per voter.
In practice, getting the MPT proof and constructing the transaction would be abstracted away from the user behind a frontend (or a frontend + backend). The UI would fetch the MPT proof from an RPC node, construct the transaction, and submit it on the user’s behalf — voters would just click “Vote” without being aware of the proof mechanics.
Our example flow is:
  1. Operator initializes a vote at a historical checkpoint timestamp via initializeVote.
  2. One-time proof request is submitted to the Storage Proof API for that timestamp. It only needs to cover the block header (timestamp → block number) and the token account’s storage root. Proof generation takes non-trivial time — submit requests as early as possible, well before the voting window opens.
  3. Voters call vote with an MPT proof for their own balance slot, fetched from any RPC node via eth_getProof at the checkpoint block. The contract verifies the proof on-chain against the proven storage root and derives voting power without any additional Storage Proof API requests.
  4. Operator closes the vote via closeVote, which tallies results and records the winning outcome on-chain.
To find the correct balancesSlot for your token, run forge inspect <YourToken> storage-layout. For most OpenZeppelin ERC-20 tokens the _balances mapping is at slot 0.

Example contract