From 652dd115a226b272f2a2dffb7217162974a9efbd Mon Sep 17 00:00:00 2001 From: Benjtalkshow Date: Wed, 29 Jul 2026 01:18:15 +0100 Subject: [PATCH] fix(dune): correct event-name casing and decode logic in dashboard queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Soroban #[contractevent] emits snake_case topic symbols (event_created, winner_paid, funds_added, ...), not the PascalCase struct names these queries filtered on. Verified against live mainnet data: every query returned 0 rows until the names were corrected. Also: - 04/07: fix the payout-model comments — the contract pushes payment inside select_winners; there is no separate claim step, so time-to-payout is creation -> selection, not claim latency - 05: count hackathon submitters (open-submission pillar) as builders, not only applicants - 09: actually isolate crowdfunding contributions by joining funds_added to Crowdfunding-pillar events (was summing all add_funds) - 06: correct the id/event_id note — event_cancelled carries id --- docs/dune-queries/01_tvl_current.sql | 17 +++++---- docs/dune-queries/02_tvl_over_time.sql | 16 ++++---- docs/dune-queries/03_bounties_funded.sql | 6 ++- docs/dune-queries/04_total_payouts.sql | 8 ++-- docs/dune-queries/05_unique_participants.sql | 20 ++++++---- docs/dune-queries/06_event_outcomes.sql | 13 ++++--- docs/dune-queries/07_avg_size_and_ttp.sql | 13 ++++--- docs/dune-queries/08_payout_by_token.sql | 8 ++-- .../09_crowdfunding_contributions.sql | 37 ++++++++++++++----- .../10_event_created_decode_test.sql | 10 +++-- 10 files changed, 90 insertions(+), 58 deletions(-) diff --git a/docs/dune-queries/01_tvl_current.sql b/docs/dune-queries/01_tvl_current.sql index 423918d..542e123 100644 --- a/docs/dune-queries/01_tvl_current.sql +++ b/docs/dune-queries/01_tvl_current.sql @@ -3,14 +3,15 @@ -- Total escrow currently held by the contract = inflows - outflows. -- -- Decoding (see 10_event_created_decode_test.sql for the why): --- event name -> topics_decoded '$[0].symbol' +-- event name -> topics_decoded '$[0].symbol' (snake_case: #[contractevent] +-- lowercases the struct name, e.g. EventCreated -> event_created) -- fields -> rebuild data_decoded '$.map' into MAP(name -> ScVal JSON), -- then read each field by its ScVal type ($.i128, $.u64, ...). -- --- Inflows: EventCreated.total_budget (non-Crowdfunding — escrowed at creation) --- FundsAdded.amount (partner top-ups + crowdfunding contributions) --- Outflows: WinnerPaid.amount, MilestoneClaimed.amount, --- ContributorRefunded.amount, OwnerResidualRefunded.amount +-- Inflows: event_created.total_budget (non-Crowdfunding — escrowed at creation) +-- funds_added.amount (partner top-ups + crowdfunding contributions) +-- Outflows: winner_paid.amount, milestone_claimed.amount, +-- contributor_refunded.amount, owner_residual_refunded.amount -- Amounts are the values the contract actually escrows/releases (the protocol -- fee is charged separately, not embedded here). Divide by 1e7 for display. @@ -31,17 +32,17 @@ WITH ev AS ( inflows AS ( SELECT CAST(JSON_EXTRACT_SCALAR(f['total_budget'], '$.i128') AS DOUBLE) AS amount FROM ev - WHERE ev_name = 'EventCreated' + WHERE ev_name = 'event_created' AND JSON_EXTRACT_SCALAR(f['pillar'], '$.vec[0].symbol') <> 'Crowdfunding' UNION ALL SELECT CAST(JSON_EXTRACT_SCALAR(f['amount'], '$.i128') AS DOUBLE) FROM ev - WHERE ev_name = 'FundsAdded' + WHERE ev_name = 'funds_added' ), outflows AS ( SELECT CAST(JSON_EXTRACT_SCALAR(f['amount'], '$.i128') AS DOUBLE) AS amount FROM ev - WHERE ev_name IN ('WinnerPaid', 'MilestoneClaimed', 'ContributorRefunded', 'OwnerResidualRefunded') + WHERE ev_name IN ('winner_paid', 'milestone_claimed', 'contributor_refunded', 'owner_residual_refunded') ) SELECT (COALESCE((SELECT SUM(amount) FROM inflows), 0) diff --git a/docs/dune-queries/02_tvl_over_time.sql b/docs/dune-queries/02_tvl_over_time.sql index 38144a9..96e951d 100644 --- a/docs/dune-queries/02_tvl_over_time.sql +++ b/docs/dune-queries/02_tvl_over_time.sql @@ -2,8 +2,8 @@ -- Panel type: area chart x=day y=tvl_display -- -- Decoding (see 10_event_created_decode_test.sql): event name is --- topics_decoded '$[0].symbol'; fields come from the data_decoded '$.map' --- ScVal map, read by type ($.i128, ...). +-- topics_decoded '$[0].symbol' (snake_case), fields come from the +-- data_decoded '$.map' ScVal map, read by type ($.i128, ...). WITH ev AS ( SELECT @@ -25,24 +25,24 @@ signed AS ( day, CASE -- Inflow: non-crowdfunding creation escrows the budget - WHEN ev_name = 'EventCreated' + WHEN ev_name = 'event_created' AND JSON_EXTRACT_SCALAR(f['pillar'], '$.vec[0].symbol') <> 'Crowdfunding' THEN CAST(JSON_EXTRACT_SCALAR(f['total_budget'], '$.i128') AS DOUBLE) -- Inflow: add_funds (crowdfunding + partner top-ups) - WHEN ev_name = 'FundsAdded' + WHEN ev_name = 'funds_added' THEN CAST(JSON_EXTRACT_SCALAR(f['amount'], '$.i128') AS DOUBLE) -- Outflow: payouts and refunds - WHEN ev_name IN ('WinnerPaid', 'MilestoneClaimed', - 'ContributorRefunded', 'OwnerResidualRefunded') + WHEN ev_name IN ('winner_paid', 'milestone_claimed', + 'contributor_refunded', 'owner_residual_refunded') THEN -CAST(JSON_EXTRACT_SCALAR(f['amount'], '$.i128') AS DOUBLE) ELSE 0 END AS delta FROM ev - WHERE ev_name IN ('EventCreated', 'FundsAdded', 'WinnerPaid', - 'MilestoneClaimed', 'ContributorRefunded', 'OwnerResidualRefunded') + WHERE ev_name IN ('event_created', 'funds_added', 'winner_paid', + 'milestone_claimed', 'contributor_refunded', 'owner_residual_refunded') ), daily_delta AS ( SELECT day, SUM(delta) / 1e7 AS daily_change_display diff --git a/docs/dune-queries/03_bounties_funded.sql b/docs/dune-queries/03_bounties_funded.sql index 09b064a..d1f1fa7 100644 --- a/docs/dune-queries/03_bounties_funded.sql +++ b/docs/dune-queries/03_bounties_funded.sql @@ -1,7 +1,9 @@ -- Boundless On-chain: Events created — count and budget by pillar and month -- Panel type: grouped bar chart x=month y=events_created color=pillar -- --- Decoding: see 10_event_created_decode_test.sql. +-- Decoding: see 10_event_created_decode_test.sql. Event name is snake_case +-- on-chain (event_created); pillar enum value is PascalCase (Hackathon, +-- Bounty, Grant, Crowdfunding) — verify with query 10's pillar_raw. WITH ev AS ( SELECT @@ -15,7 +17,7 @@ WITH ev AS ( FROM stellar.history_contract_events WHERE contract_id = '{{CONTRACT_ADDRESS}}' AND closed_at_date >= DATE '{{START_DATE}}' - AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') = 'EventCreated' + AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') = 'event_created' ) SELECT month, diff --git a/docs/dune-queries/04_total_payouts.sql b/docs/dune-queries/04_total_payouts.sql index cec13f7..b04e39c 100644 --- a/docs/dune-queries/04_total_payouts.sql +++ b/docs/dune-queries/04_total_payouts.sql @@ -1,8 +1,10 @@ -- Boundless On-chain: Total payouts to builders -- Panel type: bar chart x=month y=total_paid_display color=payout_type -- --- WinnerPaid -> Hackathon / Bounty (single-release; fires at claim_prize) --- MilestoneClaimed -> Grant / Crowdfunding (multi-release, per milestone) +-- winner_paid -> Hackathon / Bounty (single-release; fires inside +-- select_winners — the contract pushes payment there, +-- there is no separate claim step) +-- milestone_claimed -> Grant / Crowdfunding (multi-release, per milestone) -- Both carry event_id, recipient (address), amount (i128). -- Decoding: see 10_event_created_decode_test.sql. @@ -19,7 +21,7 @@ WITH ev AS ( FROM stellar.history_contract_events WHERE contract_id = '{{CONTRACT_ADDRESS}}' AND closed_at_date >= DATE '{{START_DATE}}' - AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') IN ('WinnerPaid', 'MilestoneClaimed') + AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') IN ('winner_paid', 'milestone_claimed') ) SELECT month, diff --git a/docs/dune-queries/05_unique_participants.sql b/docs/dune-queries/05_unique_participants.sql index ae6027b..85f1517 100644 --- a/docs/dune-queries/05_unique_participants.sql +++ b/docs/dune-queries/05_unique_participants.sql @@ -1,8 +1,14 @@ -- Boundless On-chain: Unique builder and organizer wallets -- Panel type: counter (two rows: builders, organizers) -- --- Decoding: event name is topics_decoded '$[0].symbol'; addresses are read --- from the data_decoded '$.map' as '$.address'. See 10_event_created_decode_test.sql. +-- Builders participate through one of three on-chain footprints: +-- applied (Bounty — gated apply) +-- submitted (Hackathon — open submission, no prior apply) +-- winner_paid / milestone_claimed (received a payout on any pillar) +-- Counting only 'applied' would miss every hackathon entrant, since that +-- pillar's participants submit without applying. +-- Decoding: event name is snake_case; addresses read as '$.address'. +-- See 10_event_created_decode_test.sql. WITH ev AS ( SELECT @@ -17,18 +23,18 @@ WITH ev AS ( WHERE contract_id = '{{CONTRACT_ADDRESS}}' AND closed_at_date >= DATE '{{START_DATE}}' AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') - IN ('Applied', 'WinnerPaid', 'MilestoneClaimed', 'EventCreated') + IN ('applied', 'submitted', 'winner_paid', 'milestone_claimed', 'event_created') ) --- Builders: applied to or received a payout from any event +-- Builders: applied to, submitted to, or received a payout from any event SELECT 'builders' AS role, COUNT(DISTINCT addr) AS unique_wallets FROM ( SELECT JSON_EXTRACT_SCALAR(f['applicant'], '$.address') AS addr - FROM ev WHERE ev_name = 'Applied' + FROM ev WHERE ev_name IN ('applied', 'submitted') UNION SELECT JSON_EXTRACT_SCALAR(f['recipient'], '$.address') - FROM ev WHERE ev_name IN ('WinnerPaid', 'MilestoneClaimed') + FROM ev WHERE ev_name IN ('winner_paid', 'milestone_claimed') ) t UNION ALL @@ -38,4 +44,4 @@ SELECT 'organizers' AS role, COUNT(DISTINCT JSON_EXTRACT_SCALAR(f['owner'], '$.address')) AS unique_wallets FROM ev -WHERE ev_name = 'EventCreated' +WHERE ev_name = 'event_created' diff --git a/docs/dune-queries/06_event_outcomes.sql b/docs/dune-queries/06_event_outcomes.sql index d861ac6..25507e6 100644 --- a/docs/dune-queries/06_event_outcomes.sql +++ b/docs/dune-queries/06_event_outcomes.sql @@ -2,8 +2,9 @@ -- Panel type: bar chart or table -- -- Buckets every created event into completed_with_payout / cancelled / --- active_or_pending. Note the id field differs by event: --- EventCreated carries 'id'; all later events carry 'event_id'. +-- active_or_pending. Note the id field name differs by event: +-- event_created and event_cancelled carry 'id'; +-- winner_paid / milestone_claimed carry 'event_id'. -- Decoding: see 10_event_created_decode_test.sql. WITH ev AS ( @@ -20,21 +21,21 @@ WITH ev AS ( WHERE contract_id = '{{CONTRACT_ADDRESS}}' AND closed_at_date >= DATE '{{START_DATE}}' AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') - IN ('EventCreated', 'EventCancelled', 'WinnerPaid', 'MilestoneClaimed') + IN ('event_created', 'event_cancelled', 'winner_paid', 'milestone_claimed') ), created AS ( SELECT CAST(JSON_EXTRACT_SCALAR(f['id'], '$.u64') AS BIGINT) AS event_id, JSON_EXTRACT_SCALAR(f['pillar'], '$.vec[0].symbol') AS pillar - FROM ev WHERE ev_name = 'EventCreated' + FROM ev WHERE ev_name = 'event_created' ), cancelled AS ( SELECT DISTINCT CAST(JSON_EXTRACT_SCALAR(f['id'], '$.u64') AS BIGINT) AS event_id - FROM ev WHERE ev_name = 'EventCancelled' + FROM ev WHERE ev_name = 'event_cancelled' ), paid AS ( SELECT DISTINCT CAST(JSON_EXTRACT_SCALAR(f['event_id'], '$.u64') AS BIGINT) AS event_id - FROM ev WHERE ev_name IN ('WinnerPaid', 'MilestoneClaimed') + FROM ev WHERE ev_name IN ('winner_paid', 'milestone_claimed') ) SELECT c.pillar, diff --git a/docs/dune-queries/07_avg_size_and_ttp.sql b/docs/dune-queries/07_avg_size_and_ttp.sql index 59fab82..60fc0b3 100644 --- a/docs/dune-queries/07_avg_size_and_ttp.sql +++ b/docs/dune-queries/07_avg_size_and_ttp.sql @@ -1,9 +1,10 @@ -- Boundless On-chain: Average budget and time-to-first-payout -- Panel type: table / single numbers -- --- Time-to-payout is measured to the first WinnerPaid/MilestoneClaimed. Under --- the pull model, WinnerPaid fires when a winner claims (claim_prize), not at --- select_winners, so this includes claim latency. +-- Time-to-payout is measured from event_created to the first +-- winner_paid / milestone_claimed. The contract pushes single-release +-- payouts inside select_winners (there is no separate claim step), so this +-- is creation -> winner selection, not claim latency. -- Decoding: see 10_event_created_decode_test.sql. WITH ev AS ( @@ -20,20 +21,20 @@ WITH ev AS ( WHERE contract_id = '{{CONTRACT_ADDRESS}}' AND closed_at_date >= DATE '{{START_DATE}}' AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') - IN ('EventCreated', 'WinnerPaid', 'MilestoneClaimed') + IN ('event_created', 'winner_paid', 'milestone_claimed') ), created AS ( SELECT CAST(JSON_EXTRACT_SCALAR(f['id'], '$.u64') AS BIGINT) AS event_id, CAST(JSON_EXTRACT_SCALAR(f['total_budget'], '$.i128') AS DOUBLE) / 1e7 AS budget_display, closed_at AS created_at - FROM ev WHERE ev_name = 'EventCreated' + FROM ev WHERE ev_name = 'event_created' ), first_payout AS ( SELECT CAST(JSON_EXTRACT_SCALAR(f['event_id'], '$.u64') AS BIGINT) AS event_id, MIN(closed_at) AS first_paid_at - FROM ev WHERE ev_name IN ('WinnerPaid', 'MilestoneClaimed') + FROM ev WHERE ev_name IN ('winner_paid', 'milestone_claimed') GROUP BY 1 ) SELECT diff --git a/docs/dune-queries/08_payout_by_token.sql b/docs/dune-queries/08_payout_by_token.sql index d9f49c2..ba839b3 100644 --- a/docs/dune-queries/08_payout_by_token.sql +++ b/docs/dune-queries/08_payout_by_token.sql @@ -1,7 +1,7 @@ -- Boundless On-chain: Payout volume grouped by payment token -- Panel type: table -- --- Token address is on EventCreated (field 'token'); join to payout events by +-- Token address is on event_created (field 'token'); join to payout events by -- event_id. Add a CASE map for readable token symbols. -- Decoding: see 10_event_created_decode_test.sql. @@ -18,19 +18,19 @@ WITH ev AS ( WHERE contract_id = '{{CONTRACT_ADDRESS}}' AND closed_at_date >= DATE '{{START_DATE}}' AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') - IN ('EventCreated', 'WinnerPaid', 'MilestoneClaimed') + IN ('event_created', 'winner_paid', 'milestone_claimed') ), payouts AS ( SELECT CAST(JSON_EXTRACT_SCALAR(f['event_id'], '$.u64') AS BIGINT) AS event_id, CAST(JSON_EXTRACT_SCALAR(f['amount'], '$.i128') AS DOUBLE) / 1e7 AS amount_display - FROM ev WHERE ev_name IN ('WinnerPaid', 'MilestoneClaimed') + FROM ev WHERE ev_name IN ('winner_paid', 'milestone_claimed') ), created AS ( SELECT CAST(JSON_EXTRACT_SCALAR(f['id'], '$.u64') AS BIGINT) AS event_id, JSON_EXTRACT_SCALAR(f['token'], '$.address') AS token_address - FROM ev WHERE ev_name = 'EventCreated' + FROM ev WHERE ev_name = 'event_created' ) SELECT c.token_address, diff --git a/docs/dune-queries/09_crowdfunding_contributions.sql b/docs/dune-queries/09_crowdfunding_contributions.sql index d4559b2..9777db9 100644 --- a/docs/dune-queries/09_crowdfunding_contributions.sql +++ b/docs/dune-queries/09_crowdfunding_contributions.sql @@ -1,9 +1,10 @@ --- Boundless On-chain: Contribution inflows — daily volume +-- Boundless On-chain: Crowdfunding contribution inflows — daily volume -- Panel type: line chart x=day y=total_contributed_display -- --- FundsAdded fires for every add_funds call (crowdfunding contributions AND --- partner top-ups on other pillars). To isolate crowdfunding, join event_id to --- EventCreated where pillar = 'Crowdfunding'. +-- funds_added fires for every add_funds call (crowdfunding contributions AND +-- partner top-ups on other pillars). This query isolates crowdfunding by +-- joining each funds_added.event_id to event_created where pillar = +-- 'Crowdfunding'. For all-pillar contribution volume, drop the join. -- Decoding: see 10_event_created_decode_test.sql. WITH ev AS ( @@ -19,13 +20,29 @@ WITH ev AS ( FROM stellar.history_contract_events WHERE contract_id = '{{CONTRACT_ADDRESS}}' AND closed_at_date >= DATE '{{START_DATE}}' - AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') = 'FundsAdded' + AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') IN ('funds_added', 'event_created') +), +crowdfunding_events AS ( + SELECT DISTINCT CAST(JSON_EXTRACT_SCALAR(f['id'], '$.u64') AS BIGINT) AS event_id + FROM ev + WHERE ev_name = 'event_created' + AND JSON_EXTRACT_SCALAR(f['pillar'], '$.vec[0].symbol') = 'Crowdfunding' +), +contributions AS ( + SELECT + day, + CAST(JSON_EXTRACT_SCALAR(f['event_id'], '$.u64') AS BIGINT) AS event_id, + JSON_EXTRACT_SCALAR(f['contributor'], '$.address') AS contributor, + CAST(JSON_EXTRACT_SCALAR(f['amount'], '$.i128') AS DOUBLE) AS amount + FROM ev + WHERE ev_name = 'funds_added' ) SELECT - day, - COUNT(*) AS contribution_count, - COUNT(DISTINCT JSON_EXTRACT_SCALAR(f['contributor'], '$.address')) AS unique_contributors, - SUM(CAST(JSON_EXTRACT_SCALAR(f['amount'], '$.i128') AS DOUBLE)) / 1e7 AS total_contributed_display -FROM ev + c.day, + COUNT(*) AS contribution_count, + COUNT(DISTINCT c.contributor) AS unique_contributors, + SUM(c.amount) / 1e7 AS total_contributed_display +FROM contributions c +JOIN crowdfunding_events cf ON c.event_id = cf.event_id GROUP BY 1 ORDER BY 1 DESC diff --git a/docs/dune-queries/10_event_created_decode_test.sql b/docs/dune-queries/10_event_created_decode_test.sql index 921e817..6f26771 100644 --- a/docs/dune-queries/10_event_created_decode_test.sql +++ b/docs/dune-queries/10_event_created_decode_test.sql @@ -1,9 +1,11 @@ --- Boundless On-chain: EventCreated decode test +-- Boundless On-chain: event_created decode test -- Run this FIRST to confirm the Dune pipeline decodes events for the contract. -- --- Real stellar.history_contract_events shapes (verified against live data): +-- Real stellar.history_contract_events shapes (verified against live mainnet): -- topics_decoded : JSON array of ScVal objects. Event name is at $[0].symbol --- (e.g. [{"symbol":"EventCreated"}]), NOT $[0]. +-- and is SNAKE_CASE — #[contractevent] lowercases the struct +-- name, so EventCreated is emitted as 'event_created', +-- WinnerPaid as 'winner_paid', etc. NOT PascalCase. -- data_decoded : ScVal map — {"map":[{"key":{"symbol":"id"},"val":{"u64":"7"}}, ...]}. -- Each field's value is wrapped by its ScVal type; there is no -- flat $.id. We rebuild it into a MAP(field_name -> ScVal JSON) @@ -28,7 +30,7 @@ WITH ev AS ( FROM stellar.history_contract_events WHERE contract_id = '{{CONTRACT_ADDRESS}}' AND closed_at_date >= DATE '{{START_DATE}}' - AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') = 'EventCreated' + AND JSON_EXTRACT_SCALAR(topics_decoded, '$[0].symbol') = 'event_created' ) SELECT CAST(JSON_EXTRACT_SCALAR(f['id'], '$.u64') AS BIGINT) AS event_id,