docs: add event schema and Dune analytics guide - #80
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds comprehensive Boundless Soroban event documentation and Dune SQL templates for TVL, funding, payouts, participation, outcomes, token volumes, crowdfunding contributions, decoding validation, dashboard layout, maintenance, and documentation workflow triggers. ChangesDune analytics integration
Estimated code review effort: 3 (Moderate) | ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Hi @ryzen-xp trust you are doing great? |
yeah !! , |
topic_1/data do not exist in stellar.history_contract_events. The real columns are: topics_decoded VARCHAR JSON array — index 0 is the event-name symbol data_decoded VARCHAR JSON object — keyed by field name Replace all topic_1 references with: JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') Replace all data references with: JSON_EXTRACT_SCALAR(data_decoded, '$.field') Fix applied to all 10 .sql files and the matching code blocks in docs/dune-analytics.md. Also add correct column table to §1.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
docs/dune-queries/05_unique_participants.sql (1)
17-17: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valuePrefer
UNION ALLwhen deduplication is handled by the outer query. UsingUNIONforces the database engine to perform an unnecessary deduplication step on the combined result set. Since the outer query is doingCOUNT(DISTINCT ...),UNION ALLis faster and more idiomatic.
docs/dune-queries/05_unique_participants.sql#L17-L17: ChangeUNIONtoUNION ALL.docs/dune-analytics.md#L376-L376: ChangeUNIONtoUNION ALL.♻️ Proposed fix (apply to both sites)
- UNION + UNION ALL🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/dune-queries/05_unique_participants.sql` at line 17, Replace UNION with UNION ALL at docs/dune-queries/05_unique_participants.sql:17 and docs/dune-analytics.md:376, preserving the outer COUNT(DISTINCT ...) deduplication behavior.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/dune-queries/01_tvl_current.sql`:
- Around line 21-51: Replace the unaggregated inflows/outflows CTEs and FULL
OUTER JOIN in docs/dune-queries/01_tvl_current.sql lines 21-51 with a
single-pass SUM(CASE ...) aggregation, mirroring 02_tvl_over_time.sql; apply the
identical correction to the markdown query in docs/dune-analytics.md lines
262-286 so both calculate TVL without Cartesian multiplication.
In `@docs/dune-queries/06_event_outcomes.sql`:
- Line 37: Replace the subtraction-based active_or_pending calculation in
docs/dune-queries/06_event_outcomes.sql at lines 37-37 with an explicit count of
rows where both paid and cancelled event IDs are null. Apply the same explicit
null-check count in docs/dune-analytics.md at lines 425-425, preserving the
active_or_pending alias.
---
Nitpick comments:
In `@docs/dune-queries/05_unique_participants.sql`:
- Line 17: Replace UNION with UNION ALL at
docs/dune-queries/05_unique_participants.sql:17 and docs/dune-analytics.md:376,
preserving the outer COUNT(DISTINCT ...) deduplication behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 9e1de45d-f97e-4353-8bb8-e9126b56adee
📒 Files selected for processing (11)
docs/dune-analytics.mddocs/dune-queries/01_tvl_current.sqldocs/dune-queries/02_tvl_over_time.sqldocs/dune-queries/03_bounties_funded.sqldocs/dune-queries/04_total_payouts.sqldocs/dune-queries/05_unique_participants.sqldocs/dune-queries/06_event_outcomes.sqldocs/dune-queries/07_avg_size_and_ttp.sqldocs/dune-queries/08_payout_by_token.sqldocs/dune-queries/09_crowdfunding_contributions.sqldocs/dune-queries/10_event_created_decode_test.sql
| WITH inflows AS ( | ||
| -- Non-crowdfunding events: budget deposited at creation | ||
| SELECT CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.total_budget') AS DOUBLE) AS amount | ||
| FROM stellar.history_contract_events | ||
| WHERE contract_id = '{{CONTRACT_ADDRESS}}' | ||
| AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'EventCreated' | ||
| AND JSON_EXTRACT_SCALAR(data_decoded, '$.pillar') != 'Crowdfunding' | ||
|
|
||
| UNION ALL | ||
|
|
||
| -- All add_funds deposits (crowdfunding contributions + partner top-ups) | ||
| SELECT CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE) AS amount | ||
| FROM stellar.history_contract_events | ||
| WHERE contract_id = '{{CONTRACT_ADDRESS}}' | ||
| AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'FundsAdded' | ||
| ), | ||
| outflows AS ( | ||
| SELECT CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE) AS amount | ||
| FROM stellar.history_contract_events | ||
| WHERE contract_id = '{{CONTRACT_ADDRESS}}' | ||
| AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') IN ( | ||
| 'WinnerPaid', | ||
| 'MilestoneClaimed', | ||
| 'ContributorRefunded', | ||
| 'OwnerResidualRefunded' | ||
| ) | ||
| ) | ||
| SELECT | ||
| (COALESCE(SUM(i.amount), 0) - COALESCE(SUM(o.amount), 0)) / 1e7 AS tvl_usdc | ||
| FROM inflows i | ||
| FULL OUTER JOIN outflows o ON 1 = 1 |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
Cartesian product logic error in TVL calculation. Performing a FULL OUTER JOIN ... ON 1=1 on unaggregated CTEs creates a Cartesian product, multiplying every inflow row by the total count of outflow rows. This drastically inflates the tvl_usdc total. You can safely fix this by replacing the CTEs and the join with a single-pass SUM(CASE ...) query, mirroring the elegant logic already used in 02_tvl_over_time.sql.
docs/dune-queries/01_tvl_current.sql#L21-L51: Replace theinflowsandoutflowsCTEs and the join with the single-pass snippet below.docs/dune-analytics.md#L262-L286: Replace the markdown snippet identically so the documentation matches the query correctly.
🐛 Proposed fix (apply to both sites)
-WITH inflows AS (
- -- Non-crowdfunding events: budget deposited at creation
- SELECT CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.total_budget') AS DOUBLE) AS amount
- FROM stellar.history_contract_events
- WHERE contract_id = '{{CONTRACT_ADDRESS}}'
- AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'EventCreated'
- AND JSON_EXTRACT_SCALAR(data_decoded, '$.pillar') != 'Crowdfunding'
-
- UNION ALL
-
- -- All add_funds deposits (crowdfunding contributions + partner top-ups)
- SELECT CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE) AS amount
- FROM stellar.history_contract_events
- WHERE contract_id = '{{CONTRACT_ADDRESS}}'
- AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'FundsAdded'
-),
-outflows AS (
- SELECT CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE) AS amount
- FROM stellar.history_contract_events
- WHERE contract_id = '{{CONTRACT_ADDRESS}}'
- AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') IN (
- 'WinnerPaid',
- 'MilestoneClaimed',
- 'ContributorRefunded',
- 'OwnerResidualRefunded'
- )
-)
-SELECT
- (COALESCE(SUM(i.amount), 0) - COALESCE(SUM(o.amount), 0)) / 1e7 AS tvl_usdc
-FROM inflows i
-FULL OUTER JOIN outflows o ON 1 = 1
+SELECT
+ COALESCE(SUM(
+ CASE
+ WHEN JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'EventCreated'
+ AND JSON_EXTRACT_SCALAR(data_decoded, '$.pillar') != 'Crowdfunding'
+ THEN CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.total_budget') AS DOUBLE)
+ WHEN JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'FundsAdded'
+ THEN CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE)
+ WHEN JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') IN (
+ 'WinnerPaid', 'MilestoneClaimed',
+ 'ContributorRefunded', 'OwnerResidualRefunded'
+ )
+ THEN -CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE)
+ ELSE 0
+ END
+ ), 0) / 1e7 AS tvl_usdc
+FROM stellar.history_contract_events
+WHERE contract_id = '{{CONTRACT_ADDRESS}}'
+ AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') IN (
+ 'EventCreated', 'FundsAdded',
+ 'WinnerPaid', 'MilestoneClaimed',
+ 'ContributorRefunded', 'OwnerResidualRefunded'
+ )📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| WITH inflows AS ( | |
| -- Non-crowdfunding events: budget deposited at creation | |
| SELECT CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.total_budget') AS DOUBLE) AS amount | |
| FROM stellar.history_contract_events | |
| WHERE contract_id = '{{CONTRACT_ADDRESS}}' | |
| AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'EventCreated' | |
| AND JSON_EXTRACT_SCALAR(data_decoded, '$.pillar') != 'Crowdfunding' | |
| UNION ALL | |
| -- All add_funds deposits (crowdfunding contributions + partner top-ups) | |
| SELECT CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE) AS amount | |
| FROM stellar.history_contract_events | |
| WHERE contract_id = '{{CONTRACT_ADDRESS}}' | |
| AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'FundsAdded' | |
| ), | |
| outflows AS ( | |
| SELECT CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE) AS amount | |
| FROM stellar.history_contract_events | |
| WHERE contract_id = '{{CONTRACT_ADDRESS}}' | |
| AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') IN ( | |
| 'WinnerPaid', | |
| 'MilestoneClaimed', | |
| 'ContributorRefunded', | |
| 'OwnerResidualRefunded' | |
| ) | |
| ) | |
| SELECT | |
| (COALESCE(SUM(i.amount), 0) - COALESCE(SUM(o.amount), 0)) / 1e7 AS tvl_usdc | |
| FROM inflows i | |
| FULL OUTER JOIN outflows o ON 1 = 1 | |
| SELECT | |
| COALESCE(SUM( | |
| CASE | |
| WHEN JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'EventCreated' | |
| AND JSON_EXTRACT_SCALAR(data_decoded, '$.pillar') != 'Crowdfunding' | |
| THEN CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.total_budget') AS DOUBLE) | |
| WHEN JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') = 'FundsAdded' | |
| THEN CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE) | |
| WHEN JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') IN ( | |
| 'WinnerPaid', 'MilestoneClaimed', | |
| 'ContributorRefunded', 'OwnerResidualRefunded' | |
| ) | |
| THEN -CAST(JSON_EXTRACT_SCALAR(data_decoded, '$.amount') AS DOUBLE) | |
| ELSE 0 | |
| END | |
| ), 0) / 1e7 AS tvl_usdc | |
| FROM stellar.history_contract_events | |
| WHERE contract_id = '{{CONTRACT_ADDRESS}}' | |
| AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0]') IN ( | |
| 'EventCreated', 'FundsAdded', | |
| 'WinnerPaid', 'MilestoneClaimed', | |
| 'ContributorRefunded', 'OwnerResidualRefunded' | |
| ) |
📍 Affects 2 files
docs/dune-queries/01_tvl_current.sql#L21-L51(this comment)docs/dune-analytics.md#L262-L286
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/dune-queries/01_tvl_current.sql` around lines 21 - 51, Replace the
unaggregated inflows/outflows CTEs and FULL OUTER JOIN in
docs/dune-queries/01_tvl_current.sql lines 21-51 with a single-pass SUM(CASE
...) aggregation, mirroring 02_tvl_over_time.sql; apply the identical correction
to the markdown query in docs/dune-analytics.md lines 262-286 so both calculate
TVL without Cartesian multiplication.
| COUNT(c.event_id) AS total_created, | ||
| COUNT(p.event_id) AS completed_with_payout, | ||
| COUNT(cx.event_id) AS cancelled, | ||
| COUNT(c.event_id) - COUNT(p.event_id) - COUNT(cx.event_id) AS active_or_pending |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Avoid subtraction for active counts. The subtraction formula assumes an event is exclusively paid or cancelled. If an event is cancelled after a milestone was claimed, it falls into both the paid and cancelled sets. Subtracting both from the total will result in a negative count for active_or_pending. Use an explicit null check instead.
docs/dune-queries/06_event_outcomes.sql#L37-L37: Update the subtraction logic to count rows that have neither outcome.docs/dune-analytics.md#L425-L425: Apply the same explicit count.
💡 Proposed fix (apply to both sites)
- COUNT(c.event_id) - COUNT(p.event_id) - COUNT(cx.event_id) AS active_or_pending
+ SUM(CASE WHEN p.event_id IS NULL AND cx.event_id IS NULL THEN 1 ELSE 0 END) AS active_or_pending📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| COUNT(c.event_id) - COUNT(p.event_id) - COUNT(cx.event_id) AS active_or_pending | |
| SUM(CASE WHEN p.event_id IS NULL AND cx.event_id IS NULL THEN 1 ELSE 0 END) AS active_or_pending |
📍 Affects 2 files
docs/dune-queries/06_event_outcomes.sql#L37-L37(this comment)docs/dune-analytics.md#L425-L425
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/dune-queries/06_event_outcomes.sql` at line 37, Replace the
subtraction-based active_or_pending calculation in
docs/dune-queries/06_event_outcomes.sql at lines 37-37 with an explicit count of
rows where both paid and cancelled event IDs are null. Apply the same explicit
null-check count in docs/dune-analytics.md at lines 425-425, preserving the
active_or_pending alias.
…uired check The build job only triggered on contracts/** and toolchain paths, so a docs-only PR never ran it — and since build is a required status check, those PRs were unmergeable (BLOCKED with 'build expected', not even admin-overridable). Add docs/** to the triggers: build recompiles the contract (a no-op pass for docs) and the required check is satisfied. Also brings this branch up to current testnet.
Quota reachedYour plan allows 300 CI/CD file units per month. You've used 297 and this scan would add 60 more (total: 357). |
…n) (#97) The queries merged in #80 return zero rows against Dune: they filtered on JSON_EXTRACT_SCALAR(topics_decoded,'$[0]') (an ScVal object, never the event name) and read data_decoded as flat $.field (it's a type-wrapped ScVal map). Both silently yield null, so every dashboard panel is empty. Verified the real stellar.history_contract_events shapes on live Dune data and rewrote all 10 dune-queries/*.sql to: - filter the event name via topics_decoded '$[0].symbol' - decode fields by rebuilding data_decoded '$.map' into MAP(field -> ScVal JSON) with map_from_entries(), then reading each by ScVal type ($.u64, $.i128, $.address, $.string, $.vec[0].symbol) - add the closed_at_date partition filter (avoids full-table scans) - to_hex(transaction_hash) (it is varbinary) Every primitive was executed against live Soroban events on Dune. The one field that can't be checked without a real Boundless event — pillar's unit- enum encoding — is emitted as pillar_raw in the decode test for confirmation. Doc fixes: rewrote the §1 decoding reference; corrected the fee accounting (escrow holds the full budget, fee charged on top; fee revenue is not in the events); added the ManagerProposed/ManagerChanged/PendingManagerCancelled events (#88); pointed §4 at the canonical .sql files instead of duplicating now-corrected SQL inline.
Summary
Adds the event-emission documentation and Dune analytics guide called for in
the on-chain analytics task. No contract code is changed — this is docs-only.
What's in this PR
docs/dune-analytics.md(new, 560 lines)Complete reference for anyone building analytics on the Boundless contract:
(
stellar.history_contract_events,stellar.contract_data, etc.), columnlayout, ScVal auto-decoding rules.
with topic name, per-field ScVal type, and analytics notes:
EventCreated,EventCancelled,FundsAdded,ContributorRefunded,OwnerResidualRefundedApplied,ApplicationWithdrawn,Submitted,SubmissionWithdrawnWinnersSelected,WinnerPaid,MilestoneClaimedAdminUpdated,PendingAdminSet,FeeAccountUpdated,FeeBpsUpdated,ProfileContractUpdated,TokenRegistered,TokenDeregistered,Paused,UnpausedPendingUpgradeProposed,PendingUpgradeCancelled,UpgradeApplied,Upgraded(legacy alias),Migratedsemantics, Crowdfunding vs other pillars distinction.
.sqlfiles).Boundless On-chain Dune dashboard.
docs/dune-queries/(new, 10.sqlfiles)Each file is a standalone, paste-ready Dune query. All use
{{CONTRACT_ADDRESS}}as a Dune parameter so the same queries work on testnet and mainnet.
01_tvl_current.sql02_tvl_over_time.sql03_bounties_funded.sql04_total_payouts.sql05_unique_participants.sql06_event_outcomes.sql07_avg_size_and_ttp.sql08_payout_by_token.sql09_crowdfunding_contributions.sql10_event_created_decode_test.sqlAnalytics correctness notes
Every analytics-relevant event carries decodable
id/amount/asset/addressfields:idonEventCreated;event_idon all subsequent eventstotal_budget(create),amount(FundsAdded, WinnerPaid, MilestoneClaimed, refunds)tokenonEventCreated; join viaevent_idfor payout eventsowner,contributor,recipient,applicanton relevant eventsAll
amountfields in events are net-of-fee — the protocol fee is deductedbefore
remaining_escrowis credited. No fee adjustment is needed in SQL if therate changes.
Crowdfunding vs other pillars:
EventCreated.total_budgetis a funding goalfor Crowdfunding (escrow starts at 0). The TVL queries exclude it from creation
inflows and rely solely on
FundsAddedfor crowdfunding inflows.Stale field removed from docs
The
Appliedevent documentation previously listed acredit_cost: U32fieldcarried over from the pre-1.1.0 schema. Credits moved off-chain in the 1.1.0
upgrade; the field was dropped from the
Appliedstruct. This PR fixes the docto match the live contract:
Appliedcarries only{ event_id, applicant }.Acceptance criteria status
decodable (
id,amount,asset,address,topic)10_event_created_decode_test.sqldecodesEventCreatedfiltered to thecontract address and returns
id,pillar,owner,token,total_budget,titleto paste; dashboard URL will be added to
dune-analytics.mdonce published)Summary by CodeRabbit