Liquid Democracy
Vote delegation on the OhShii Launcher. Delegation is live on both the OHSHII ecosystem DAO (ONS) and every per-campaign DAO (SONS). This document covers the user model, the canister architecture, the public API, the security and rate-limit model, and the operational contract that ties the launcher and the locker together. It details the ONS implementation; the SONS implementation shares the same delegation engine, with the differences summarized under Scope.
Table of contents
Section titled “Table of contents”- Overview
- Scope: ONS and SONS
- Core concepts
- User flows
- Canister architecture
- Public Candid API
- The cascade — how delegated votes propagate
- Security and rate limits
- Frontend contract
- Cross-canister dependency:
lock_storage - Event log and observability
- Error reference
- FAQ
Overview
Section titled “Overview”Liquid Democracy lets a verified ONS voter (the follower) delegate
their vote to another verified principal (the delegate / followee).
When the delegate casts a vote on an ONS proposal, every follower’s
vote is automatically applied with the follower’s own quadratic voting
power, computed fresh from lock_storage at the moment the delegate
voted.
The model is shallow by design: a follower’s delegation chain is
exactly one hop. A principal cannot be both a delegate and a follower
at the same time on ONS — the accepts_followers toggle enforces this
bipartite-graph invariant. There are no transitive cascades, no cycle
detection, no recursion budgets to tune.
Delegation is opt-in on both sides:
- The delegate must explicitly enable
accepts_followers(off by default). - The follower must explicitly call
follow(delegate).
Either party can dissolve the relationship at any time:
- The follower calls
unfollow(). - The delegate calls
dismiss_follower(follower). - The delegate disables
accepts_followers, which removes every follower atomically.
Scope: ONS and SONS
Section titled “Scope: ONS and SONS”Vote delegation is live on both governance layers:
- ONS — the OHSHII ecosystem-wide DAO that governs the launcher, the locker, and shared protocol parameters. This is the implementation the rest of this document details.
- SONS — per-campaign mini-DAOs running on their own
sons_governancecanister instances. SONS runs the same delegation engine (thefollow/unfollow/set_accepts_followers/dismiss_followersurface, the 500-follower cap, the 100-per-tick cascade on the 5-minute timer, and the one-hop / bipartite / snapshot / direct-vote-wins invariants), with the differences listed below.
ONS vs SONS — what differs
Section titled “ONS vs SONS — what differs”| Aspect | ONS (ohshii_governance) | SONS (sons_governance) |
|---|---|---|
| Cascade trigger | Only proposals whose required_token_canister_id == OHSHII_LEDGER_CANISTER_ID cascade — and since create_proposal coerces that field to OHSHII, that is every ONS proposal. | Every round-1 direct vote cascades — each instance governs a single campaign token, so there is no per-proposal token gate. |
| Cascade dispatch | cascade_delegated_votes(...).await inline in vote(). | ic_cdk::futures::spawn(...) so a cascade trap cannot roll back the committed direct vote. |
| Voting-power source | Fetched from lock_storage via a batched inter-canister call (async cascade). | Computed canister-locally from the instance’s own locks (fully synchronous cascade, zero ICC). |
| Verification gate | Fires when world_id_cfg.enabled || decideid_cfg.enabled (both default off); checked against ONS’s own verified-user sets (sync). | Per-DAO requires_verification flag, default on; checked by a bounded-wait is_verified ICC to ONS behind the V-4 circuit breaker, with a permanent “Forever Verified” local cache. |
De-escalation (unfollow, dismiss_follower) | Verified when a gate is on — they are not exempt. | Skip verification by design (sync de-escalation paths). When the gate is off, the acquisition paths fall back to a local active-lock anti-Sybil check. |
| DAO-assigned delegation | None. | BatchSetFollow — a critical governance proposal can assign a delegate to a list of followers without each follower’s signed consent (the proposal is the authorization), still enforcing accepts_followers (re-checked at execution), no self-follow, the bipartite invariant, the 500-follower cap, and a 300-per-batch limit. |
ONS is single-token by construction: create_proposal coerces
required_token_canister_id to the OHSHII ledger for every proposal (the
field is kept on the wire for API/back-compat), so an ONS proposal is always
an OHSHII proposal — a direct caller cannot create a non-OHSHII
(campaign-token) proposal on ONS, and the launcher UI already sends OHSHII.
The cascade therefore fans out to all ONS proposals; the
== OHSHII_LEDGER_CANISTER_ID cascade gate and the vote() “campaign-token”
branch are defensive and can no longer be reached on ONS.
Imported DAOs run the same sons_governance WASM, so they expose the
identical delegation surface, including BatchSetFollow.
The remainder of this document describes the ONS implementation in
detail — its Candid API, stable-memory layout, and the lock_storage
cross-canister dependency. SONS shares the same user model and cascade
architecture; only the points in the table above differ.
Core concepts
Section titled “Core concepts”Delegate (followee)
Section titled “Delegate (followee)”A verified principal who has explicitly opted in to receive
delegations by enabling the accepts_followers toggle. A delegate
cannot themselves follow anyone — the toggle and the follow operation
are mutually exclusive on the same DAO.
A delegate may have up to 500 followers (MAX_FOLLOWERS_PER_FOLLOWEE).
This cap exists so the destructive bulk-removal path (toggle OFF) can
complete inline within IC’s per-message instruction limit.
Follower
Section titled “Follower”A verified principal with non-zero OHSHII voting power who has
called follow(delegate). A follower has at most one active follow
relationship — to switch delegate they must unfollow() first.
Voting power snapshot
Section titled “Voting power snapshot”When the delegate votes, the canister fetches each follower’s
quadratic voting power from lock_storage once, in a single batched
ICC, and freezes the result inside a PendingDelegationEntry. The
timer applies that snapshot synchronously, so a follower’s later lock
expiry between enqueue and apply does not cancel the delegation.
If a follower’s voting power is zero at the moment the delegate votes,
the resulting Vote row carries 0 VP and contributes nothing to the
tally — but the row is still written, so the follower’s vote count
reflects participation.
Verification gate (Universal Participation Gate)
Section titled “Verification gate (Universal Participation Gate)”The verification gate has expanded
from delegation-only to a Universal Participation Gate. On ONS
DAOs the gate fires when EITHER world_id_cfg.enabled OR
decideid_cfg.enabled is true; when on, the following ONS write
methods all require the caller to be verified by at least one
method (is_any_method_verified):
vote()— direct votecreate_proposal()— proposal creationfollow()— delegation acquisition (V-1 verify pair: caller AND followee)set_accepts_followers(true)— delegate opt-in (V-1 verify caller)
Verification is the implicit economic gate that makes the entire DAO
participation surface Sybil-resistant — anonymous principals cannot
participate, and fresh principals must pay the verification cost (a
small ICP anchor fee or a Proof-of-Personhood credential).
The de-escalation methods (unfollow, dismiss_follower) are not
exempt: whenever a verification gate is enabled they require the caller
to be verified, exactly like the acquisition methods. They differ only
when verification is OFF — because they merely REMOVE state they carry
no additional anti-Sybil substitute, whereas the acquisition paths fall
back to a lock-existence check in that mode.
The verification storage is per-method on ONS (WORLD_ID_VERIFIED_PRINCIPALS
DECIDEID_VERIFIED_PRINCIPALS). Verifications PERSIST across method-disable: a user verified via World ID keeps voting access if World ID’senabledflag is later flipped to false. Re-enabling restores access without a re-verification round-trip.
Why ONS does NOT use Forever Verified (SONS-only): on
this canister is_any_method_verified is already a sync stable-map
lookup against the per-method verified sets — there is no ICC to
save. Forever Verified (the permanent local cache that bypasses the
verify ICC on cache hit) is structurally a SONS-family feature
because SONS canisters reach across the subnet to query ONS for
verification on every gated call. ONS itself is the source of
truth, so caching makes no sense here.
Cascade
Section titled “Cascade”The asynchronous fan-out of delegated votes from a single direct vote
to N follower votes. Implemented as an enqueue step inside vote()
(captures voting power snapshots) plus a periodic timer step
(process_pending_delegations) that drains up to 100 entries per tick
and applies each as a synchronous Vote row.
User flows
Section titled “User flows”Delegating your vote
Section titled “Delegating your vote”- Visit the OnsVotingPage and switch to the Following tab.
- In the “Follow someone” panel, paste the delegate’s principal id.
- The UI runs two pre-flight checks:
am_i_accepting_followers()— if true, the button is disabled because you are already a delegate; you’d need to disable accept-followers first.accepts_followers(target)— if false, the button is disabled with a clear notice that the target is not accepting followers.
- Click Follow. The wallet (Internet Identity, NFID, or Plug)
prompts to sign. The canister fetches your voting power from
lock_storageand registers the relationship if your VP is > 0. - From this moment on, every ONS vote the delegate casts triggers an automatic vote from your wallet within ~5 minutes.
Becoming a delegate
Section titled “Becoming a delegate”- Visit the Following tab on the OnsVotingPage.
- In the “Become a delegate” section at the top, click Enable accept-followers.
- A confirmation modal explains the implications: other verified ONS voters may now delegate their voting power to you, and any existing follow you had on someone else will be auto-removed.
- Confirm. From this moment on, anyone can call
follow(your_principal).
Stepping down as a delegate
Section titled “Stepping down as a delegate”- From the Following tab, click the Disable accept-followers button.
- A destructive-confirmation modal appears showing the current follower count and a preview of follower principals. The action is irreversible — the delegate’s followers will need to follow them again from scratch if they ever re-enable the toggle.
- Confirm. The canister inline-removes every follower (up to 500),
resets the follower count, and emits a single
AcceptsFollowersDisabledsummary event in the audit log.
Removing a single follower
Section titled “Removing a single follower”A delegate can selectively kick one follower without disabling the toggle (useful when a previously-trusted peer is no longer appropriate):
- Open the Voting Power expandable section on the OnsVotingPage.
- Click Show N Followers to expand the list.
- Click the red ✕ next to the principal you want to remove.
- A small confirmation modal asks for confirmation; click to proceed.
- The relationship is removed immediately; the kicked follower can
follow(you)again later if they wish (subject to your toggle still being on).
Unfollowing
Section titled “Unfollowing”From the Following tab, click the Unfollow button next to
the delegate row. There is no confirmation modal — the operation is
fully reversible (you can follow() the same delegate again right
afterwards), so the friction is intentionally low.
Canister architecture
Section titled “Canister architecture”The feature lives in the ohshii_governance canister (launcher repo).
Five new stable maps + one secondary index hold the persistent state,
all kept in lockstep by the helper API:
| Stable map | Memory ID | Purpose |
|---|---|---|
FOLLOWS | 71 | follower → followee (forward index) |
FOLLOWERS_BY_FOLLOWEE | 72 | (followee, follower) reverse index for range scans |
PENDING_DELEGATIONS | 73 | FIFO cascade queue keyed (enqueued_at_ns, proposal_id, follower) |
ACCEPTS_FOLLOWERS_FLAGS | 76 | Opt-in toggle set; presence = ON |
FOLLOWERS_COUNT | 77 | Atomic capacity counter followee → u32 |
PENDING_DELEGATIONS_INDEX | 78 | Secondary index (proposal_id, follower) for O(log N) dedup |
In addition, four heap-only rate-limit maps and two single-tuple global counters live in the canister’s heap. These wipe on every upgrade by design, providing a “nuclear-reset” lever for ops if a heap map ever fills up under attack.
Public Candid API
Section titled “Public Candid API”Four state-mutating updates and eight read-only regular queries.
No method takes a dao_id argument — the feature is single-DAO
by construction. No method is a composite query — the frontend
orchestrates cross-canister fanout itself.
// === Updates ==="follow": (principal) -> (variant { Ok; Err: text });"unfollow": () -> (variant { Ok; Err: text });"set_accepts_followers": (bool) -> (variant { Ok; Err: text });"dismiss_follower": (principal) -> (variant { Ok; Err: text });
// === Queries ==="accepts_followers": (principal) -> (bool) query;"am_i_accepting_followers": () -> (bool) query;"get_my_followee": () -> (opt principal) query;"get_followee_of": (principal) -> (opt principal) query;"get_my_followers": (nat32) -> (FollowersPage) query;"get_followers_of": (principal, nat32) -> (FollowersPage) query;"get_my_followers_count": () -> (nat32) query;"get_pending_delegation_queue_size": () -> (nat64) query;
type FollowersPage = record { followers: vec principal; // up to `max` rows total_count: nat32; // canonical follower count truncated: bool; // true if total_count > followers.len()};The max argument on the paged-followers queries is clamped server-side
to the cap of 500. The frontend uses 20 by default and offers a
“Load more” affordance up to 100 (the lock_storage batch ceiling).
The cascade — how delegated votes propagate
Section titled “The cascade — how delegated votes propagate”Two-stage model
Section titled “Two-stage model”-
Enqueue stage (synchronous, runs inside
vote()after the direct vote commits):- Verify the proposal’s required token is the OHSHII ledger — the cascade fans out only OHSHII-token proposals (see Scope).
- List the followers of the voter via a stable range scan (capped at 500 by the canister’s per-followee cap).
- De-duplicate against any followers who already direct-voted on this proposal or already have an entry queued (via the secondary index).
- Fetch every eligible follower’s voting power from
lock_storagein a single batched ICC, parallelised across chunks of 100 if necessary. Bounded wait with a 15-second timeout; on timeout the affected chunk is logged and dropped. - Push one
PendingDelegationEntryper follower, carrying the frozen voting-power snapshot.
-
Apply stage (synchronous, runs inside the existing
run_periodic_tasks()timer):- Drain up to 100 entries from the queue per tick (~5 minutes by default).
- For each entry, re-verify the proposal is still open, the
follower is still verified, the follow is still active, and the
follower has not direct-voted in the interim. If any check fails,
log a
DelegatedVoteFailedevent and drop the entry. - Apply the snapshot: write the
Voterow, update the proposal tally, append to the follower’s voter history, log aDelegatedVoteCastevent.
The apply stage performs no inter-canister calls. This is the property that makes the cascade cycle-safe — a popular delegate’s vote that fans out to hundreds of followers cannot stall the timer or drain the canister via repeated long-running ICCs.
Latency
Section titled “Latency”Direct vote: visible on the proposal tally immediately.
Delegated vote: visible within one timer tick (~5 minutes by default). For proposals that close quickly, both the proposal voting period and the cascade tick rate are tuned to ensure delegated votes have time to settle.
Direct vote priority
Section titled “Direct vote priority”If a follower casts their own direct vote on a proposal between the
moment the cascade enqueues their entry and the moment the timer
applies it, the direct vote always wins. The timer detects this via a
proposal-vote lookup and drops the cascade entry with reason
direct_vote_already_cast.
Security and rate limits
Section titled “Security and rate limits”Verification gate (Universal Participation Gate)
Section titled “Verification gate (Universal Participation Gate)”Every state-mutating delegation method — follow,
set_accepts_followers, and the de-escalation methods unfollow
and dismiss_follower (plus vote and create_proposal) — requires
the caller to be verified via World ID or DecideID
(is_any_method_verified) whenever a verification gate is enabled. The
check is enforced both at the inspect_message boundary (cycle saver,
rejects unverified callers pre-Candid-decode) and inside each method
body (the authoritative check). The de-escalation methods differ only
in that, when verification is OFF, they apply no lock-existence
anti-Sybil substitute — they only remove state, never add it.
For SONS DAOs the same Universal Participation Gate is governed by
the per-DAO requires_verification flag (SONS-side semantics: a
bounded-wait ICC to ONS behind the V-4 circuit breaker). Each SONS
DAO can flip its own gate via a CriticalGovernanceOperation
proposal — verification is opt-in per DAO, not enforced
ecosystem-wide.
Bipartite-graph invariant
Section titled “Bipartite-graph invariant”The accepts_followers toggle enforces a strict role separation:
- A principal with the toggle on cannot call
follow(_). - A principal currently following someone cannot have their toggle on.
Enabling the toggle while currently following someone auto-removes
the follow first. The auto-removal is logged as FollowRemoved with
reason auto_on_toggle_accept.
Bounded inter-canister waits
Section titled “Bounded inter-canister waits”Every batched VP fetch from the launcher to the locker uses
Call::bounded_wait(...).change_timeout(15). If the locker becomes
unresponsive, the timeout fires and the call returns an error rather
than leaving a callback indefinitely in flight. This protects the
launcher’s ability to be stopped and upgraded.
Four-layer rate-limit model
Section titled “Four-layer rate-limit model”| Layer | Defence |
|---|---|
| L1 cycle floor | Every method aborts before doing expensive work if the canister cycle balance is below MIN_CYCLES_FOR_DELEGATION_CASCADE = 500B. Last line of defence; activates after attack starts. |
| L2 global cap | Single-tuple counter (no per-key allocation). follow = 200 calls/min canister-wide; dismiss_follower = 60/min. Sybil-resistant by construction — a swarm of fresh identities cannot collectively exceed the cap. |
| L3 per-caller cap | Heap maps keyed by principal. follow = 5/hr; unfollow = 1/hr + 1-hr cooldown; set_accepts_followers = 3/hr + 5-min cooldown; dismiss_follower = 30/hr. Friction-only against a single misbehaving identity; bypassable by Sybil but caps growth via lazy cleanup + hard cap on the map itself. |
| L4 economic gate | Implicit: every method requires verified status, which means the caller paid a verification cost. Bounds Sybil amplification to roughly the daily verification quota. |
Reentrancy guard
Section titled “Reentrancy guard”follow() is the only delegation update with an await (the VP
fetch). It acquires a per-caller in-memory lock via FollowOpGuard,
which releases on Drop — including in cleanup context after a trap.
This prevents a single caller from having two follow() calls in
flight at once, which could otherwise race on the capacity reservation.
Atomic capacity reservation
Section titled “Atomic capacity reservation”The MAX_FOLLOWERS_PER_FOLLOWEE = 500 cap is enforced atomically
inside register_follow (in the canister’s memory layer): a single
with(borrow_mut) block reads the counter, checks the cap, increments,
and inserts both index entries. Concurrent follow() calls on the
same delegate race only at this point, and the race is resolved
deterministically by IC’s serial message scheduler — no caller can
slip past the cap, no counter drift can develop.
Frontend contract
Section titled “Frontend contract”The Following tab on the OnsVotingPage is implemented in
src/frontend/src/components/DelegationPanel.jsx. It enforces three
hard rules:
- No update calls on render. All four delegation mutators are
invoked exclusively from user-action handlers (button click on the
toggle, the Follow button, the Unfollow button, the dismiss “X”).
No
useEffectdata-load may invoke them. - Two-query fanout for VP display. The Voting Power panel
computes
Total / Own / Delegatedvoting power by callingohshii_governance.get_my_followers(...)(regular query) and thenlock_storage.calculate_quadratic_voting_power_batch(...)(regular query) directly from the browser. Two round-trips, both fast, neither composite. - Destructive confirmation on toggle OFF. The “Disable accept- followers” path always opens a modal that lists the current follower count and a preview of follower principals before the call goes through. This protects delegates from accidentally nuking their entire follower base.
Cross-canister dependency: lock_storage
Section titled “Cross-canister dependency: lock_storage”The cascade depends on a method exposed by lock_storage (in the
OhShii Locker repo):
calculate_quadratic_voting_power_batch : (vec principal, principal) -> (vec QuadraticVotingPowerResult) query;- Up to 100 principals per call (the launcher chunks larger inputs).
- Returns one VP result per input principal, same index order.
- Plain
#[query]— not composite.
Lockstep deployment. If the launcher upgrade lands first while the locker still does not expose this method, every follower-cascade will trap on the missing endpoint after the direct vote has committed. The deployment runbook is:
- Deploy the locker upgrade with
calculate_quadratic_voting_power_batch. - Verify the method appears in
lock_storage’s public Candid (e.g. via Candid UI ordfx canister call lock_storage __get_candid_interface_tmp_hack). - Deploy the launcher upgrade with the cascade enabled.
Event log and observability
Section titled “Event log and observability”Every delegation-related state change is recorded in the existing
ohshii_governance event log, queryable through get_recent_events,
get_events, and the System Status → Stable Logs tab in the UI.
Success events
Section titled “Success events”FollowRegistered { follower, followee }— a new follow.FollowRemoved { follower, followee, reason }— a follow ended; reason is one ofunfollow,auto_on_toggle_accept,dismissed_by_followee.AcceptsFollowersEnabled { who, auto_unfollowed_target }— toggle turned on;auto_unfollowed_targetisSome(_)if the user was following someone at the moment they enabled the toggle.AcceptsFollowersDisabled { who, followers_removed }— toggle turned off; the count of followers cleared inline.DelegatedVoteCast { proposal_id, follower, followee, voting_power, decision }— the timer applied a delegated vote.
Failure events
Section titled “Failure events”FollowFailed { caller, target, reason }— any failed follow / unfollow / dismiss attempt, with a closed-set reason code.AcceptsFollowersToggleFailed { who, requested, reason }— toggle rejected (rate limit, unverified, cycle floor).DelegatedVoteFailed { proposal_id, follower, followee, reason }— a queue entry was dropped before applying; reason explains why.SystemLifecycle { event_type: "delegation_cascade_skipped_low_cycles" }— the canister was below the cycle floor when a cascade tried to fire.SystemLifecycle { event_type: "followers_count_drift_detected" }— the periodic drift detector found a mismatch between the canonical follower counter and the index-based count. Should never fire under normal operation; investigate immediately if it does.
Error reference
Section titled “Error reference”User-facing errors returned as Err(text) from the four updates.
Each value corresponds to a stable closed-set reason code in the
audit log.
| Reason | When it appears |
|---|---|
not_verified | Caller has neither World ID nor DecideID verification. |
self_follow | Caller principal equals the followee principal. |
already_following | Caller already has a registered follow; must unfollow() first. |
caller_accepts_followers_on_this_dao | Caller has the delegate toggle on; cannot follow others. |
target_does_not_accept_followers_on_this_dao | Followee has not enabled the toggle. |
target_not_verified | Followee is not a verified principal. |
followee_at_capacity | Followee already has the maximum 500 followers. |
caller_has_no_voting_power | Caller has no eligible OHSHII locks (need ≥ 45-day duration to count). |
vp_lookup_failed | The bounded-wait call to lock_storage timed out or returned an undecodable response. Retry. |
not_following | unfollow() called by a principal with no active follow. |
caller_not_a_delegate | dismiss_follower() called by a principal with the toggle off. |
not_follower_of_caller | dismiss_follower() named a principal that does not currently follow the caller. |
rate_limit_follow / rate_limit_unfollow / rate_limit_toggle / rate_limit_dismiss | Per-caller window or cooldown exceeded; OR the canister-wide global cap was reached (try again in a minute). |
cycle_floor | Canister is conserving cycles; try again in a few minutes. |
reentrant_follow_in_flight | Same caller has another follow() request still in flight. |
Does delegating change my own ability to vote directly?
Section titled “Does delegating change my own ability to vote directly?”No. You can always cast a direct vote on any open ONS proposal. If you do, the cascade detects it and drops the corresponding queue entry without applying the delegated vote — your direct vote always wins.
What happens if my voting power changes after I delegate?
Section titled “What happens if my voting power changes after I delegate?”Delegation snapshots your VP at the moment the delegate votes, not at the moment you set up the delegation. Locks that mature, expire, or are added between two of the delegate’s votes will be reflected correctly the next time the cascade runs.
If your VP drops to zero between snapshots, your delegated Vote row
is still written (so your participation count is recorded) but
contributes 0 to the tally — exactly as if you had cast a direct
zero-VP vote.
Can I delegate to multiple people?
Section titled “Can I delegate to multiple people?”No. Each principal can have at most one active delegate at any time.
To switch delegates you must unfollow() first, then follow(new_one).
Can a delegate be a follower of someone else?
Section titled “Can a delegate be a follower of someone else?”No. The bipartite-graph invariant prevents this on ONS. If you want
to switch from being a delegate to being a follower (or vice versa),
toggle accepts_followers to off first (which removes your existing
followers) before calling follow(...).
Is delegation permanent?
Section titled “Is delegation permanent?”No. Either party can dissolve the relationship at any time:
- The follower calls
unfollow(). - The delegate calls
dismiss_follower(follower)or disables theiraccepts_followerstoggle.
What happens if my delegate gets compromised?
Section titled “What happens if my delegate gets compromised?”You can call unfollow() immediately to stop receiving any further
delegated votes. Any votes the delegate has already cast cannot be
retracted, but future ones will not propagate to your wallet.
How long does it take for a delegated vote to show up?
Section titled “How long does it take for a delegated vote to show up?”Up to one timer tick (~5 minutes by default) after the delegate casts their vote. The OnsVotingPage shows your delegated votes in your vote history alongside direct votes once the timer has applied them.
Are delegated votes weighted differently?
Section titled “Are delegated votes weighted differently?”No. A delegated vote uses the follower’s own quadratic voting power, applied with the same formula as a direct vote. The delegate’s voting power is unaffected — they vote with their own VP, and each follower votes with theirs.
Does my LGE tier (Fish / Shark / Whale) include delegated VP?
Section titled “Does my LGE tier (Fish / Shark / Whale) include delegated VP?”No. The Voter Benefits Protocol tier ladder gates LGE purchase limits on each principal’s personal stake in OHSHII locks only. Liquid Democracy delegates votes, not tier privileges — so a Whale who accepts a hundred followers does NOT share their Whale-tier purchase limit with those followers, and a Guest who follows a Whale does NOT inherit any of the Whale’s tier benefits.
The get_lge_eligibility(principal) endpoint that powers the tier
badge in the UI and the purchase-limit check in the launcher backend
queries lock_storage.calculate_quadratic_voting_power(principal, OHSHII_LEDGER)
for that single principal — never the cascade-aggregated total
displayed in the Voting Power panel. The “Total VP / Own VP /
Delegated to You” headline is purely informational and never feeds
into a tier lookup.
Can I see who is following me?
Section titled “Can I see who is following me?”Yes. Open the Voting Power expandable section on the OnsVotingPage —
when your accepts_followers toggle is on, the “Show N Followers”
button reveals the list, including each follower’s current voting
power. From this list you can also dismiss any single follower.
Does the canister charge me cycles for delegating?
Section titled “Does the canister charge me cycles for delegating?”No. Cycle costs are paid by the ohshii_governance canister, funded
through the protocol treasury. The four-layer rate-limit model is the
defence against cycle-drain attacks; it caps the total work the
canister can be asked to do per minute.
How does this interact with the World ID / DecideID daily caps?
Section titled “How does this interact with the World ID / DecideID daily caps?”Delegation respects the same verification gate as direct voting. If
your verification has been cleared (for instance, after a governance-authorized verification reset for ops reasons), follow() will reject you with
not_verified until you re-verify. Existing follows are not
auto-revoked when verification is cleared; the cascade’s apply step
will simply drop your queue entries with follower_unverified until
you re-verify.
Related documentation
Section titled “Related documentation”GOVERNANCE.md— overall governance model (ONS vs SONS, proposal categories, voting parameters).WORKFLOWS.md— step-by-step LGE and proposal flows.ARCHITECTURE.md— canister layout and stable-storage conventions.WORLD_ID_VERIFICATION.md— the verification gate that delegation requires.FRONTEND.md— frontend architecture: React + JSX + JSDoc@ts-checktype-checking, the canister-types.js / utils/canister.js bridges, Candid wire conventions, and the TypeScript version policy (workspace TS is the source of truth).