Skip to content

feat(activity): surface vesting events in the activity feed - #444

Merged
zachyo merged 1 commit into
soropad:masterfrom
DSOTec:feat/issue-408-vesting-activity-events
Sep 2, 2026
Merged

feat(activity): surface vesting events in the activity feed#444
zachyo merged 1 commit into
soropad:masterfrom
DSOTec:feat/issue-408-vesting-activity-events

Conversation

@DSOTec

@DSOTec DSOTec commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Problem

#336 fixed the topic list properly — but only for the token contract. The
vesting contract emits thirteen topics and TRACKED_EVENT_TOPICS contained
none of them, so no vesting activity was ever visible: not a schedule being
created, not a recipient claiming, not an admin revoking a grant, not a cliff
being extended. Recipients had no audit trail for the contract holding their
tokens.

Solution

  • TRACKED_VESTING_EVENT_TOPICS beside the existing list, polled against
    the vesting contract's address and folded into the same subscription so the
    feed stays one ordered stream.
  • Decoding keyed on (contractId, topic), not topic alone. Vesting events
    resolve to a namespaced vesting:* type, which keeps a single string key for
    the feed to switch on while leaving the shared names unambiguous.
  • Vesting payloads decoded from docs/events.json: recipient from topic 1
    for create/release/revoke/clf_ext, the releasable half of the revoke
    tuple, the total from the batch tuple, and the address in the data slot for
    prune/prop_adm/acc_adm. clf_ext deliberately shows no amount — its
    tuple is ledger numbers, not tokens.
  • Icons, labels and colours for each case in ActivityFeed, following Activity feed & indexer only decode 4 event types — admin/compliance actions are invisible #305.
    Labels name the contract ("Vesting paused" vs "Token paused").

Note on the shared topic names

The issue flags init, pause and unpause as emitted by both contracts. It
is actually sixprop_adm, revoked and upgrade collide too. All six
are covered, and a test asserts the set, so if it ever grows the disambiguation
is forced to keep up.

Note on the topic count

The issue lists eleven vesting topics; the contract emits thirteen.
revoked and upgrade were undocumented until #436 added them to
docs/events.json, so both are included here.

Two adjacent bugs found by deriving the list from the contract

Both are the same blindness this issue is about, and both are fixed:

  • The token list tracked rvk_auth, which the contract has never emitted
    it emits rev_auth. Every revoke_authorization event was silently dropped.
    cncl_adm was missing entirely. Measured against the contract source:

    tracked but never emitted: [ 'rvk_auth' ]
    emitted but not tracked  : [ 'cncl_adm', 'rev_auth' ]
    vesting topics tracked   : 0 of 13
    
  • ActivityFeed switched on unauthorize and revoke_admin, neither of which
    is a real topic, so those arms were dead code and the events they meant to
    label rendered as "Other".

useContractEvents also had its own copy of the decode switch — which is how it
and lib/stellar.ts drifted apart in the first place. Both now share one
decoder, removing ~80 lines of duplication.

Testing

lib/__tests__/trackedEventTopics.test.ts reads the contract sources
directly and asserts both lists match the emitted sets exactly. This is the
guard that was missing: an untracked topic is not an error at runtime, it is
just an event that never appears. It fails on all eight assertions against the
pre-fix lists
.

lib/__tests__/activityDecode.test.ts covers every vesting payload shape,
the six shared names resolving differently per emitting contract, a malformed
data slot, backward compatibility for three-argument callers, and that the token
paths still decode.

24 new tests, all passing. Full suite: 180 → 204 passing, with the same 5
pre-existing failing suites before and after. eslint reports 0 errors on every
changed file; tsc reports no new errors.

One thing left open

ActivityFeed takes the vesting address as an optional prop, and
TokenDashboard resolves it from TrackedDeployment.vestingContractId — a
field that already exists for exactly this purpose. Nothing currently writes
that field
, so in practice it is undefined and the feed behaves as before
until it is populated. The app has no persisted token → vesting association
anywhere today; VestingDashboard has the admin type the address in by hand.

Wiring that up means deciding where the association is recorded (at deploy time,
or when an admin first points the vesting dashboard at a contract), which felt
like a product decision rather than part of this fix. The plumbing is complete
and tested; say the word and I will follow up with it.

Unrelated but still true: .github/workflows/ci.yml remains an invalid workflow
file (components: is a YAML sequence where Actions requires a scalar), so the
Lint / Type Check / Jest steps do not execute on this PR either. Details in #437.

Closes #408

soropad#336 fixed the topic list properly, but only for the token contract. The
vesting contract emits thirteen topics and the list contained none of
them, so no vesting activity was ever visible: not a schedule being
created, not a recipient claiming, not an admin revoking a grant. For a
product whose headline feature is vesting, recipients had no audit trail
for the contract holding their tokens.

- Add TRACKED_VESTING_EVENT_TOPICS beside the existing list and poll it
  against the vesting contract's address, folded into the same
  subscription so the feed stays one ordered stream.
- Key decoding on (contractId, topic) rather than topic alone. init,
  pause, unpause, prop_adm, revoked and upgrade are emitted by both
  contracts with identical topic tuples, so topic-only filtering cannot
  tell them apart; vesting events resolve to a namespaced `vesting:*`
  type, which keeps a single string key for the feed to switch on.
- Decode the vesting payloads from docs/events.json: recipient from
  topic 1 for create/release/revoke/clf_ext, the releasable half of the
  revoke tuple, the total from the batch tuple, and the address in the
  data slot for prune/prop_adm/acc_adm. clf_ext deliberately shows no
  amount — its tuple is ledger numbers, not tokens.
- Give each vesting case an icon, label and colour in ActivityFeed,
  following the pattern soropad#305 established. Labels name the contract
  ("Vesting paused" vs "Token paused") so the shared names read
  unambiguously.

The issue lists eleven vesting topics; the contract emits thirteen.
`revoked` and `upgrade` were undocumented until soropad#436 added them to
docs/events.json, so both are included here.

Two adjacent problems the same blindness had produced, found by deriving
the list from the contract rather than by hand:

- The token list tracked `rvk_auth`, which the contract has never
  emitted — it emits `rev_auth` — so every revoke_authorization event
  was silently dropped. `cncl_adm` was missing entirely.
- ActivityFeed switched on `unauthorize` and `revoke_admin`, neither of
  which is a real topic, so those arms were dead and the events they
  meant to label rendered as "Other". Now `rev_auth` and `revoked`.

useContractEvents had its own copy of the decode switch, which is how it
and lib/stellar.ts drifted apart. Both now share one decoder, removing
about 80 lines of duplication.

Tested with two new suites. lib/__tests__/trackedEventTopics.test.ts
reads the contract sources directly and asserts both lists match the
emitted sets exactly — it fails against the pre-fix lists on all eight
assertions, which is the guard that was missing. activityDecode.test.ts
covers every vesting payload shape, the six shared names resolving
differently per contract, a malformed data slot, and that the token
paths still decode. 24 new tests; no change to the pre-existing failures.

Closes soropad#408
@drips-wave

drips-wave Bot commented Aug 31, 2026

Copy link
Copy Markdown

@DSOTec Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@zachyo
zachyo merged commit 9e0eec0 into soropad:master Sep 2, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The activity feed ignores every vesting event

2 participants