Exposing SNS Voting-Participation Shares After Reward Settlement

Heads up, I’m planning to implement and raise a PR for a small SNS Governance change to make each SNS neuron’s voting-participation weighting available after a reward event settles.

The planned implementation simply preserves the exact per-neuron reward shares SNS governance already calculates, tags them with the latest reward-event timestamp, and will expose them through the governance canister’s get_neuron and list_neurons endpoints. It will work even when native SNS maturity based voting rewards are set to zero and without the need to retain ballots beyond settlement.

This has been a recurring unmet need for several years. Ballots are cleared at settlement, and the window for dapp canisters to capture them can be as short as zero seconds. The feature enhancement I’m proposing will unblock a bunch of different ways in which the community should be able to extend SNS governance rewards system (rather than chucking away the work the governance canister is already performing).

Related context:

2025: https://forum.dfinity.org/t/maintaining-sns-proposal-ballot-records/43714/22

2024: https://forum.dfinity.org/t/sns-governance-feature-proposal-show-the-neuron-reward-portion-of-each-reward-event-so-that-snss-can-pay-stakeholders-in-proportion-to-the-voting-participation/32837

2022: https://forum.dfinity.org/t/sns-proposal-ended-ballot-information/17300


Having review the related code I don’t think this requires a big change at all. Hoping to have a PR to raise in the next day or two, along with tests.

@bjoern, please let me know if there’s anyone in DFINITY who will be willing to review the PR, offer feedback, and conditionally merge the PR in.

Thanks :victory_hand:

cc @Snassy-icp

This sounds like a very worthwhile change, Alex. Preserving the calculation at settlement inside Governance is much stronger than requiring downstream canisters to race ballot deletion or infer participation from later maturity changes.

A few details seem important for making the exposed evidence dependable:

  • The stored value should be the exact participation weight calculated before applying the native reward purse, rather than only the resulting maturity amount. Otherwise an SNS with zero native rewards would expose zero for every neuron and lose the relative allocation.

  • It would be useful to expose the event’s total participation weight so consumers can verify that the sum of the neuron weights reconciles exactly.

  • Please consider retaining a small bounded history, perhaps the latest five reward events as previously proposed. A latest-only value leaves consumers with a short collection window and means one missed event is permanently unrecoverable.

  • The behavior for zero participation, newly created neurons, and neurons disbursed or removed after settlement should be explicit. In particular, settled evidence should not disappear before it can be collected.

  • Using reward_event_end_timestamp_seconds as the canonical event identity sounds right, especially when list_neurons pagination could overlap settlement.

This would be immediately useful to ic-query. We could collect an API-exhausted neuron snapshot for one event, bracket the Governance version and reward event, reject mixed-event pagination, reconcile the weights against the event total, and publish an atomic cached allocation report. That would be much stronger than the maturity-delta reconciliation currently required.

I agree this does not need to preserve ballots beyond settlement. It provides the narrower artifact required for participation-proportional distributions. Full ballot history remains a separate governance-transparency question.

Very supportive of the direction and happy to test the resulting public API from ic-query once a PR is available.

Please loop in @daniel-wong for the PR review and possibly deeper technical discussions.

A concrete mainnet run may help make the integration case precise.

1. Select an SNS

ic-query resolves the deployed SNS catalog through SNS-W:

icq sns list --verbose

Abbreviated live output from 4 August 2026:

network: ic
sns_wasm_canister_id: qaa6y-5yaaa-aaaaa-aaafa-cai
sns_count: 53
source_endpoint: https://icp-api.io

ID   NAME            ROOT                          GOVERNANCE                    LEDGER
--   --------------  ---------------------------   ---------------------------   ---------------------------
 1   Dragginz        zxeu2-7aaaa-aaaaq-aaafa-cai   zqfso-syaaa-aaaaq-aaafq-cai   zfcdd-tqaaa-aaaaq-aaaga-cai
 2   OpenChat        3e3x2-xyaaa-aaaaq-aaalq-cai   2jvtu-yqaaa-aaaaq-aaama-cai   2ouva-viaaa-aaaaq-aaamq-cai
 3   unnamed-23ten   23ten-uaaaa-aaaaq-aaapa-cai   24scz-zyaaa-aaaaq-aaapq-cai   7ajy4-sqaaa-aaaaq-aaaqa-cai
...

For convenience the following commands select Dragginz by catalog ID:

SNS=1

The ID is only lookup/display metadata. The resulting evidence records and compares the stable Root, Governance, ledger, swap, index and SNS-W principals.

2. Exhaust the current neuron API

The existing command brackets a strict list_neurons walk with complete reads of:

  • get_running_sns_version;
  • the nervous-system parameters; and
  • get_latest_reward_event.

It then reads all neuron pages and repeats those three reads. Any changed bracket, duplicate neuron ID, overlapping page, cursor regression, row-ceiling violation or missing API-exhaustion evidence rejects the checkpoint.

icq sns reward checkpoint "$SNS"

The live Dragginz result was:

network: ic
sns_id: 1
name: Dragginz
root_canister_id: zxeu2-7aaaa-aaaaq-aaafa-cai
governance_canister_id: zqfso-syaaa-aaaaq-aaafq-cai
collection_status: api_exhausted_observed
point_in_time_guaranteed: no
collection_started_at: 2026-08-04T10:56:30Z
collection_completed_at: 2026-08-04T10:58:21Z
page_count: 126
row_count: 12530
collection_row_ceiling: 200000
client_query_count: 134
reward_event_end_timestamp_seconds: 1785801600
reward_event_round: 20749
reward_event_distributed: 0.00
aggregate_unstaked_maturity: 0.00
aggregate_staked_maturity: 0.00
aggregate_combined_maturity: 0.00

This is a useful real example of the missing evidence:

  • Governance successfully settled a specific reward event.
  • The complete neuron API contained 12,530 neurons.
  • Native maturity distribution was zero.
  • Consequently, all observed maturity totals were zero.
  • The existing API therefore provides no way to reconstruct the relative participation allocation after settlement.

The ballots have gone, and maturity deltas contain no information from which the per-neuron proportions can be recovered.

3. Existing positive-reward reconciliation

For an appropriately configured SNS with a positive native reward distribution, the current workaround is:

SNS=1
BEFORE=dragginz-before.json

icq sns reward checkpoint "$SNS" --json > "$BEFORE"

BEFORE_EVENT="$(
  jq -er '.reward_event_after.end_timestamp_seconds' "$BEFORE"
)"

printf 'before reward event: %s\n' "$BEFORE_EVENT"

# Run after the immediate next reward event has settled.
icq sns reward checkpoint "$SNS" --json > "$AFTER"

AFTER_EVENT="$(
  jq -er '.reward_event_after.end_timestamp_seconds' "$AFTER"
)"

printf 'after reward event: %s\n' "$AFTER_EVENT"
test "$AFTER_EVENT" -gt "$BEFORE_EVENT"

icq sns reward diff "$BEFORE" "$AFTER" --json > "$DIFF"

jq '{
  allocation_status,
  before_event: .before.reward_event_end_timestamp_seconds,
  after_event: .after.reward_event_end_timestamp_seconds,
  distributed_e8s_equivalent,
  aggregate_maturity_delta_e8s_equivalent,
  summed_neuron_maturity_delta_e8s_equivalent,
  aggregate_reconciled,
  per_neuron_reconciled,
  row_count: (.rows | length),
  invalid_reasons
}' "$DIFF"

A positive allocation is accepted only if:

sum(per-neuron maturity deltas)
    == aggregate maturity delta
    == reward_event.distributed_e8s_equivalent

It also requires non-negative explained deltas, stable canister identity, an immediate-successor reward event, no missing neurons, and no maturity conversion during the observation interval.

This is useful verification, but it is necessarily an inference from minted maturity. It cannot recover participation when the native reward purse is zero, as the live Dragginz event demonstrates.

4. Flow enabled by the proposed Governance evidence

The proposed field would replace that inference with the exact value Governance already computes during settlement.

For reward event E and neuron i, retain:

E   = reward_event_end_timestamp_seconds
w_i = exact pre-purse participation weight calculated for neuron i

An API-exhausted collector can then:

  1. Read and retain the complete latest reward event and running Governance version.
  2. Exhaust list_neurons.
  3. Select each neuron’s weight tagged with exactly E.
  4. Reject a missing weight, duplicate neuron, mixed event timestamp or unstable bracket.
  5. Calculate with checked integer arithmetic:
W = sum(w_i)
  1. Reconcile W with an authoritative event-level total participation weight, if that total is exposed.
  2. Atomically publish rows of:
reward_event_end_timestamp_seconds
neuron_id
participation_weight
total_participation_weight
  1. Let an external reward system distribute a separate pool P proportionally:
neuron_payout_i = floor(P * w_i / W)

The payout system must separately define deterministic remainder handling and an authenticated beneficiary or claim mechanism. Neuron permissions do not identify a canonical payment beneficiary.

Most importantly, this works when:

reward_event.distributed_e8s_equivalent = 0

because w_i and W describe voting participation before the native reward purse is applied.

The live Dragginz example would therefore change from “12,530 zero maturity rows with no recoverable allocation” into a directly collectable, event-labelled participation allocation—without retaining any ballots and without polling proposal deadlines.

Tagging every value with reward_event_end_timestamp_seconds also makes mixed-event pagination detectable. A small bounded history would make collection more operationally robust: the Dragginz walk above required 126 pages and approximately 111 seconds, while a latest-only value must always be collected before the following reward event replaces it.

This is a narrow settlement artifact rather than permanent ballot history. It directly addresses the zero-to-round_duration_seconds ballot collection window documented in the [2022 discussion](https://forum.dfinity.org/t/sns-proposal-ended-ballot-information/17300), while avoiding the storage and governance-transparency questions involved in retaining every ballot.

(going through it manually to make sure it all works, it was a lot of code and already getting some freaky errors)

Thanks. Give me some time to look into this. Sounds interesting.

Sorry. I haven’t gotten to this yet. Let me reply to you on Monday or so.

No worries, I’ve got a working implementation that I’m happy with. I’ve just finished up running tests against that new SNS version in a project I intend to launch as a new SNS soon. All looks good.

Will have a writuep and PR ready for Monday hopefully. Looking forward to your feedback on it whenever you get a chance next week.

PR raised - feat(sns-governance): expose latest reward-event shares on neurons by aodl · Pull Request #11078 · dfinity/ic

The PR is best understood as adding a public “reward receipt” to every SNS neuron.

At the moment, an SNS reward event can report that rewards were distributed, but an outside tool cannot easily determine each neuron’s exact share. The PR adds two pieces of information to each neuron:

  • which reward event it last participated in;
  • how many reward shares it contributed.

Because this information is returned through existing public neuron-listing methods, services such as ic-query could repeatedly collect it and calculate or estimate each neuron’s reward.

The useful purpose

For ordinary users, this could make rewards easier to understand:

“Neuron 123 participated in yesterday’s reward event and earned approximately this amount.”

It also makes reward calculations independently auditable.

The privacy issue

The same data can become a public activity diary. An observer could record daily snapshots and learn:

  • when a neuron participated;
  • how large its relative reward contribution was;
  • whether its activity pattern changed;
  • which neurons regularly behave alike.

A neuron ID is pseudonymous, not necessarily anonymous. If someone later connects it to a person or organization, the
historical records become much more revealing.

The field does not directly disclose private keys, ownership, or voting direction. It also does not take away stake
or voting power. The incremental disclosure is activity and relative reward weight.

How SNS could become a precedent for NNS

There is no automatic SNS-to-NNS path. A hypothetical progression would require several separate decisions:

  1. The SNS field is accepted as a useful public transparency feature.

  2. Dashboards and tools begin depending on it.

  3. Public per-neuron reward data becomes treated as an established design pattern.

  4. Someone proposes adding the equivalent feature to NNS neurons.

  5. The NNS implementation copies the SNS disclosure model without applying the NNS’s existing private-neuron
    protections.

  6. Public collectors build a long-term database of NNS reward participation.

That would require a separate NNS code change, review, canister upgrade proposal, and NNS vote. This PR cannot make it happen by itself.

What that could mean for a large holder

If the holder’s neuron IDs were known or could be connected to them, observers might be able to estimate:

  • their relative governance influence;
  • which days they participated;
  • how their influence changed;
  • whether several neurons appear to be operated together;
  • when stake may have been reorganized.

That could bring unwanted scrutiny, targeted phishing, public pressure, or attempts to associate a pseudonymous holding with a real identity.

It still would not automatically reveal:

  • the neuron controller or private keys;
  • the beneficial owner of unlinked neurons;
  • how the neuron voted;
  • an exact combined holding where the holder uses multiple unlinked neurons.

Splitting neurons would make owner-level analysis less reliable, but it is not a complete privacy solution. Timing, following patterns, transactions, or public disclosures might still allow probabilistic clustering.

The important existing NNS protection

The NNS already distinguishes public and private neurons. Recent votes are publicly returned for public neurons—known neurons are necessarily public—but are redacted for private neurons unless requested by their controller or hotkey. Current NNS implementation ( https://github.com/dfinity/ic/blob/3ec5d044f5dc45a93943ec14258891a1c3c272be/rs/nns/governance/src/neuron/types.rs#L907-L952 )

Therefore, the dangerous hypothetical is not simply “this reaches the NNS.” It is:

An equivalent feature reaches the NNS but bypasses or weakens the NNS’s existing visibility rules.

The comment we posted asks for the most relevant protections: avoid anonymous bulk disclosure, restrict individual disclosure to authorized callers, expose aggregate totals instead, and require any NNS equivalent to undergo a separate privacy and governance review.

Yeah, this needs to be carefully reviewed and all the privacy concerns mitigated. No wonder Wenzel was so adamant we should be a named Neuron lol.

By design everything about SNS neuron ownership and configuration is public, including voting power, and has always been public (unlike the NNS). This PR doesn’t change that, it only exposes the participation weightings of those neurons (which isn’t about ownership, configuration, or VP, it’s simply about how active they are).

It’s also a very small change. But as always, the change should be carefully reviewed :+1:

Not sure what this has to do with Wenzel, don’t give him the credit.

Well you remember after me trying to save the SNS, putting money where my mouth was to clean up Dodge.. @EnzoPlayer0ne and Leo put a lot of effort into making a temporary dashboard to show exactly how much I’d “infested” the SNS that I put years of effort into supporting.

Of course you’d remember that.

That history is why “it only shows activity” is not especially reassuring. A public ownership map plus a public activity map is no longer just transparency; it is a profile.

I accept that this PR does not change the SNS’s existing public-ownership model. My concern is the precedent created by treating increasingly detailed, persistent behavioural metadata as an insignificant addition - especially if that pattern is later proposed for the NNS, where private neurons are an explicit part of the design.

Privacy rarely disappears in one dramatic proposal. It is usually rounded down, one small optional field at a time.

So yes, this may be a small first step. I would still prefer a guardrail at the top of the slope to an argument about its gradient at the bottom.

Just trying to avoid death by a thousand cu*ts.

Completely get you. But bear in mind maturity is public too (in SNSs). Maturity tells you the same thing.

If there is a concrete SNS out there that doesn’t want this change and they can provide a defensible reason why, I think that would be a good contribution to this discussion.

We know for sure there are plenty of devs who would make use of this to improve SNS governance - and that has been the case for years.

This is a change that has always been just around the corner.

Finally… SNS activity is already public. Collecting that information requires a hack and unprofessional workaround that doesn’t perform well under edge cases. That’s what this addresses.

I completely get the slippery slope concern, and I respect the fact that you’re concerned. Hopefully you agree though that in this particular cases, we’re good?

Well now you’re talking just like a politician.

The SNS is dead, you know that - I’m working to incentivise people to care about it. You’re apparently flogging said dead horse.

This PR is SNS-specific and does not itself alter NNS privacy. My concern is the architectural precedent it establishes for publishing persistent, per-neuron metadata derived from ballots.

The NNS deliberately distinguishes public and private neurons. In particular, recent ballots are redacted for private neurons when requested by an unauthorized caller. A reward-participation weighting is also ballot-derived: it records the aggregate voting power exercised by a neuron during a reward event. If an equivalent field were later added to the NNS without applying the same visibility rules, it would reveal activity that the current private-neuron model is intended to protect.

I would therefore like the following boundary recorded explicitly before this merges:

  • this disclosure is SNS-specific because SNS neuron records are currently public by design;
  • it does not establish the appropriate disclosure policy for NNS neurons;
  • any NNS equivalent must undergo a separate privacy and governance review;
  • any ballot-derived NNS participation field must respect Visibility;
  • private-neuron values must be redacted from anonymous get_neuron_info, neuron listings and indexing APIs; and
  • controllers and authorized hotkeys may still retrieve their own values.

This is not about making reporting more difficult. It is about preserving an existing NNS rule: choosing a private neuron must continue to protect its ballot-derived activity.

A future implementation should not be able to bypass that protection merely by describing the same information as “reward participation” instead of “voting history."

As the largest participant in the SNS, I am explicitly asking that it inherit the same public/private neuron protections already available in the NNS; supporting an SNS should not require surrendering the privacy afforded to NNS participants.

We have big plans for the SNS going forward.

As mentioned this doesn’t change the NNS. As mentioned this changes nothing about the privacy of SNSs. I’m more than happy with the restrictions/boundaries you suggest, which are already the case (the NNS has an explicit public/private model).

No it’s not.

Ah, cool, I’m glad you agree.

Me too.

If the NNS thinks its setup is the best one, then why is it offering SNSes something completely different in terms of privacy? DAOs should be able to configure this for themselves at least.

Well, I personally don’t want the NNS to change anything in SNSes except to make security fixes. SNSes should be sovereign, with the NNS providing secure software with options that the SNSes can configure. It’s a matter of principle.

Sounds great, why don’t you make a new post, read up on the context, make the case for your suggestion, do the work, and raise a pull request.

Oh, I see. Well that’s a confused mixture of ideas. I’m not sure you understand what you just said, nor what the SNS framework explicitly is. I know you mean well though

Can you clarify. What is the SNS framework ? I mean, let’s clear the confusion and mixure of ideas