feat(activity): surface vesting events in the activity feed - #444
Merged
zachyo merged 1 commit intoSep 2, 2026
Merged
Conversation
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
|
@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! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
#336 fixed the topic list properly — but only for the token contract. The
vesting contract emits thirteen topics and
TRACKED_EVENT_TOPICScontainednone 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_TOPICSbeside the existing list, polled againstthe vesting contract's address and folded into the same subscription so the
feed stays one ordered stream.
(contractId, topic), not topic alone. Vesting eventsresolve to a namespaced
vesting:*type, which keeps a single string key forthe feed to switch on while leaving the shared names unambiguous.
docs/events.json: recipient from topic 1for
create/release/revoke/clf_ext, the releasable half of therevoketuple, the total from the
batchtuple, and the address in the data slot forprune/prop_adm/acc_adm.clf_extdeliberately shows no amount — itstuple is ledger numbers, not tokens.
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,pauseandunpauseas emitted by both contracts. Itis actually six —
prop_adm,revokedandupgradecollide too. All sixare 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.
revokedandupgradewere undocumented until #436 added them todocs/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. Everyrevoke_authorizationevent was silently dropped.cncl_admwas missing entirely. Measured against the contract source:ActivityFeedswitched onunauthorizeandrevoke_admin, neither of whichis a real topic, so those arms were dead code and the events they meant to
label rendered as "Other".
useContractEventsalso had its own copy of the decode switch — which is how itand
lib/stellar.tsdrifted apart in the first place. Both now share onedecoder, removing ~80 lines of duplication.
Testing
lib/__tests__/trackedEventTopics.test.tsreads the contract sourcesdirectly 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.tscovers 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.
eslintreports 0 errors on everychanged file;
tscreports no new errors.One thing left open
ActivityFeedtakes the vesting address as an optional prop, andTokenDashboardresolves it fromTrackedDeployment.vestingContractId— afield that already exists for exactly this purpose. Nothing currently writes
that field, so in practice it is
undefinedand the feed behaves as beforeuntil it is populated. The app has no persisted token → vesting association
anywhere today;
VestingDashboardhas 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.ymlremains an invalid workflowfile (
components:is a YAML sequence where Actions requires a scalar), so theLint / Type Check / Jest steps do not execute on this PR either. Details in #437.
Closes #408