Governance & Voting
Delegation

Delegation

Delegation hands your AE voting weight to another account so that their vote speaks for your balance on polls you do not vote in yourself. It is stored in the governance Registry, applies to every poll at once, and is undone whenever you choose. It moves no coins: your AE stays in your wallet, spendable, the entire time.

The rules

RuleDetail
ScopeGlobal. One delegation applies to every poll, present and future.
CardinalityOne delegation out per account; any number in.
DurationUntil you change or revoke it. Nothing expires it.
OverrideVoting on a poll yourself overrides your delegation for that poll only.
TransitivityChains resolve, to any depth. A delegatee who has themselves delegated passes the weight along.
CustodyNone. No coins move, nothing is locked, no allowance is needed.
CountingDelegations and balances are read at the poll's close height (or live, if the poll has not closed).
Self-delegationRejected on chain: CALLER_IS_DELEGATEE_DISALLOWED.
CyclesNot prevented on chain. Broken off-chain at tally time, so each balance counts at most once.

The entrypoints

Delegation lives on the Registry, not on individual polls, which is why it is global.

EntrypointPurpose
delegate(delegatee)Delegate your voting power. Stateful.
revoke_delegation()Take it back. Stateful.
delegatee(account)Who an account delegates to, as option(address)
delegators(account)The accounts delegating directly to this one
delegations()The full delegation map, map(address, address)

delegate on an account that already has a delegation replaces it. There is no need to revoke first, and no way to have two. The registry stores delegations as a plain address-to-address map, which is why one account can have only one outgoing delegation.

Two events record every change: Delegation(delegator, delegatee) and RevokeDelegation(delegator).

Delegate

node scripts/superhero-governance.mjs delegate <ak_address>
node scripts/superhero-governance.mjs revoke-delegation
node scripts/superhero-governance.mjs delegate ak_xyz456...

The script validates that the target is a well-formed ak_ account address and refuses to delegate to your own address. Both commands are a single transaction.

Inspect the current state. Both default to your own wallet when you pass no address:

node scripts/superhero-governance.mjs delegatee            # who do I delegate to?
node scripts/superhero-governance.mjs delegatee ak_xyz...  # who do they delegate to?
node scripts/superhero-governance.mjs delegators           # who delegates to me?
node scripts/superhero-governance.mjs delegators ak_xyz... # who delegates to them?

In the app, delegation lives on the My Account tab at superhero.com/voting/account (opens in a new tab): paste an address, save the delegation, or revoke it. The same screen shows your AE balance, your current delegation, and how many accounts delegate to you. Your delegation status also appears on every individual poll page, under Your Governance Power.

Delegation is transitive

A delegatee who has themselves delegated passes the weight along the chain. If A delegates to B and B delegates to C, then C votes with A's weight on any poll where neither A nor B has voted. The governance API models this as a tree: GET delegatedPower/:address returns delegatedPower, a delegationTree and a flattenedDelegationTree.

Check who your delegatee delegates to before you delegate, with repeated delegatee calls:

node scripts/superhero-governance.mjs delegatee ak_your_delegatee...

delegators(account) returns only direct delegators, meaning the accounts pointing straight at that address. It does not include accounts further up a chain whose weight also reaches them. The app's account screen counts delegators from the flattened delegation tree returned by the governance API, which is why that number can exceed the direct count from the contract.

Cycles

The registry rejects only self-delegation:

stateful entrypoint delegate(delegatee : address) =
  require(Call.caller != delegatee, "CALLER_IS_DELEGATEE_DISALLOWED")
  put(state{ delegations[Call.caller] = delegatee })

Each call sees one edge, so A→B followed by B→A is two valid transactions, and the delegation map ends up holding a two-cycle. Longer rings are equally possible.

Cycles are broken off-chain, at tally time, by the governance backend. The tally walks the delegation graph from each voter to the accounts delegating to them, and outward from there, and refuses to follow an edge into any of three sets:

SkippedWhy
The address the walk started fromCloses a ring back to the voter
Any address already visited on this walkCloses a ring anywhere else
Any account that voted on the poll itselfAn account that voted speaks for itself

Each balance is counted at most once per poll. The cycle is cut at the edge that would revisit a node; no balance is double-counted and no balance is dropped for being in a ring; it counts for whoever it reaches first along the walk. The third rule is the mechanism behind "voting yourself overrides your delegation".

There is no depth cap (a chain of any length resolves) and no cap on how much delegated weight one account can accumulate. If you and someone else delegate to each other, the walk cuts one of the two edges and the outcome depends on which of you votes.

Voting yourself overrides delegation

If you vote on a poll, your own vote counts for your balance on that poll and your delegatee's vote does not. The override is per-poll and lasts only while your vote stands: revoke your vote and your delegation takes effect again for that poll. Delegate once to someone whose judgement you trust, then vote yourself on the questions you care about.

To abstain from a poll, revoking your vote is not enough: your weight falls back to your delegatee. Revoke the delegation as well, or vote for an abstain option if the poll offers one.

Before you delegate

A delegation is indefinite, transitive, and applies to polls that do not exist yet, and the weight your delegatee votes with grows with your balance. None of this puts your coins at risk: a delegatee cannot spend, move or lock your AE, because the registry stores an address-to-address mapping and nothing more.

Delegation history

The Superhero backend indexes delegation transactions, including revoked ones:

EndpointReturns
GET /api/governance/delegationsPaginated delegations; includeRevoked=true to include revoked ones
GET /api/governance/delegations/:accountAddressOne account's delegation and revocation history, in chronological order

Each record carries the delegator, the delegatee, the transaction hash and block height and, if it was later revoked, revoked, revoked_hash, revoked_block_height and revoked_at. Use this when you need the delegation graph at a past height; the contract only holds the present state.

Related