From 441aa4903ea3e87af03b5262ab4c463154b2bcaa Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Fri, 3 Oct 2025 08:44:31 +0200 Subject: [PATCH 01/14] add dune query and latest deployment --- .../deployment_mainnet_2025-09-21.json | 49 ++ dune_query_with_nft.sql | 630 ++++++++++++++++++ 2 files changed, 679 insertions(+) create mode 100644 deployments/deployment_mainnet_2025-09-21.json create mode 100644 dune_query_with_nft.sql diff --git a/deployments/deployment_mainnet_2025-09-21.json b/deployments/deployment_mainnet_2025-09-21.json new file mode 100644 index 0000000..d16eb3f --- /dev/null +++ b/deployments/deployment_mainnet_2025-09-21.json @@ -0,0 +1,49 @@ +{ + "USDU": "0x4695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c", + "gasToken": "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", + "collateralRegistry": "0xf21c277fcd910dc78e6ef205ec769aa3cdbc3b0535cde5c304bf175c72ba20", + "UBTC": { + "collateral": "0x3687f0b57779ce0b6720285f4db622ab000725411183690ea37c5c995298e49", + "addressesRegistry": "0x94dcb2432a4af4b0b9a6f69a725567d3f1f7b626387a0634f18d8750708a77", + "borrowerOperations": "0x1fbc3c3f3758c9d637a4720a85c7c15ef5e2c9a0a94b7692e672c68791f635c", + "troveManager": "0x116b8ccdab70af2662fb9b8d29a505db315ef6bb32dd4370615b074a352274e", + "troveNft": "0x57b1e2a9babd2f90fb56fe55b67b859474f84e81291cb05be1340a7f7042359", + "stabilityPool": "0x7c8543248623eddaba11b596cd4802e55b46814355b8a1cb2ce7ef523d299a", + "sortedTroves": "0x61dadaf5bf71d81d4bffc2232880fed4720bace175868a51f309c0f64fd05aa", + "activePool": "0x37b1dd4c931081138df0b004493aa50af5b71b3924462a2cfc245d8bb083f3f", + "defaultPool": "0x5b78a98e3d9073612621b7b57cdc4d87c5e9ee531b1add8a895bc06c9347be9", + "collSurplusPool": "0x57675cd68e36101553ad1eba03bc10d0b2548968d2732645bd49834708e886b", + "gasPool": "0x6d4bca3cc94999353d821da04cb67ab6cd0594d1cfcf2859042e5bb352c9e2c", + "interestRouter": "0x4b20f1b3b44060abdcb0b1f1018edc78622ea0065605c7cec1754bf522f497a", + "liquidationManager": "0x63a139f850ce8526ab7d65e18341fb79189668c46fc15fca86046088a1b681f", + "redemptionManager": "0x7687a3e75b73bf2fb80ab442bf67707ef21e1c7ca8a4b764146ede9143bb416", + "batchManager": "0x74d9cb4bce38b8f018ea344a70c822fabd2e1f807d4e53abd5c956cfa2d148", + "priceFeed": "0x7b24488112e5a222db34bf2536cd1e1dc0c6a4626eb4eb256bc3e3f990c71be", + "hintHelpers": "0x27d0d8e2a800303e28b7f41bd49933133c4853586e2613aa4457d9400adc81e", + "multiTroveGetter": "0x37b1dd4c931081138df0b004493aa50af5b71b3924462a2cfc245d8bb083f3f", + "troveManagerEventsEmitter": "0x8d5d5ead639f66d455c29f43bae0332c04282fd0f25e5c1b0e6a71c943b63e", + "underlyingAddress": "0x3687f0b57779ce0b6720285f4db622ab000725411183690ea37c5c995298e49" + }, + "GBTC": { + "collateral": "0x4d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55", + "addressesRegistry": "0x305078f5237d5654f159148c9e4fc067ec10e76ef56bfe460336a73efa4a27f", + "borrowerOperations": "0x186174c5af724ef59c278b59872ecbcdcbfde4c7e780fa794aad732708dfb52", + "troveManager": "0x78507dfaedce3558c5b98b4cc34f265452230b53225e5fbe4d91d700b481322", + "troveNft": "0x5e0bcd997c22de21ac7716cd38fdb881b56764d20fa6bbe1647281abc3e1800", + "stabilityPool": "0x65ccbeb1516bd1ae841b0729dd0798cb86027fbbbce929f647a2d057655c054", + "sortedTroves": "0x7f706d36f1d2f8faad1ed03c276ee4573919bcb04189fae1b076c554be41786", + "activePool": "0x2f8bc83077a82b0fb7121de4952a1c23e68685a6ad30009a619ef1848b895dc", + "defaultPool": "0x7ed69ae7f5196804f6ba3ebbe3790d8fde0c4d1b835258223c43f67c95f4932", + "collSurplusPool": "0x6b049703ae7d2edc03348806d831cecadbe5306e84431c5c6b592a3714f7cc6", + "gasPool": "0x1aa2c3e690e1b1210ab6698f97281505369ac517b2e983b137bb3b47c32a323", + "interestRouter": "0x307d7378c2543140d607297f4a863fbfe83cb0804297532c52bdd246a7b7d69", + "liquidationManager": "0x34eb92dff01b310eb48a654067bfa1603f92a6b27eed5ad226a9a9d41ae42d1", + "redemptionManager": "0x7f451319b676e7e2a9aca8a8a3f1827d6b49bc97231f15793d6f944eb39b4e3", + "batchManager": "0x79e28912b8b4c2a5ba2a27e3e4490f185ea4ae1e87575e259abfee60a75b026", + "priceFeed": "0x240af0b78af03ce5bf3e037a7582e989b8169e2d30fd3e6fbb55a79bafbed6a", + "hintHelpers": "0x27d0d8e2a800303e28b7f41bd49933133c4853586e2613aa4457d9400adc81e", + "multiTroveGetter": "0x2f8bc83077a82b0fb7121de4952a1c23e68685a6ad30009a619ef1848b895dc", + "troveManagerEventsEmitter": "0x6dcfc4ce8ca0c664d5f6c5ee434c9379d15e616246db092cd9e21e5871e4377", + "underlyingAddress": "0x4d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55" + } +} \ No newline at end of file diff --git a/dune_query_with_nft.sql b/dune_query_with_nft.sql new file mode 100644 index 0000000..815d0b2 --- /dev/null +++ b/dune_query_with_nft.sql @@ -0,0 +1,630 @@ +-- USDU Protocol - Daily User Balances Query +-- Network: Starknet Mainnet +-- Purpose: Track daily user positions for BTCFi incentive system integration +-- This query extracts data directly from USDU protocol events and tracks NFT ownership + +WITH +-- ============================================================================ +-- CONFIGURATION CONSTANTS +-- Define all contract addresses and static values used throughout the query +-- ============================================================================ +cfg AS ( + SELECT + -- Deployment block number + -- This significantly improves query performance by avoiding unnecessary historical scans + 2338809 AS deployment_block, + + -- Market address (GBTC AddressesRegistry) - raw hex for calculations + 0x0305078f5237d5654f159148c9e4fc067ec10e76ef56bfe460336a73efa4a27f AS market_address_raw, + -- Same address cast to VARBINARY for output formatting + CAST(0x0305078f5237d5654f159148c9e4fc067ec10e76ef56bfe460336a73efa4a27f AS VARBINARY) AS market_address, + -- Human-readable protocol name + 'Uncap Protocol' AS market_name, + -- Protocol owner/operator + 'Uncap' AS market_owner, + -- GBTC token contract address - raw hex + 0x04d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55 AS gbtc_addr_raw, + -- Same GBTC address cast to VARBINARY for output + CAST(0x04d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55 AS VARBINARY) AS gbtc_addr, + -- USDU stablecoin contract address - raw hex + 0x04695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c AS usdu_addr_raw, + -- Same USDU address cast to VARBINARY for output + CAST(0x04695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c AS VARBINARY) AS usdu_addr, + -- Protocol scaling factor (1e18 for 18 decimals) + 1e18 AS protocol_scale, + -- TroveManagerEventsEmitter contract that emits all the events we need + 0x06dcfc4ce8ca0c664d5f6c5ee434c9379d15e616246db092cd9e21e5871e4377 AS events_emitter, + -- TroveNFT contract that tracks ownership via ERC721 Transfer events + 0x05e0bcd997c22de21ac7716cd38fdb881b56764d20fa6bbe1647281abc3e1800 AS trove_nft +), + +-- ============================================================================ +-- EXTRACT TROVEUPDATED EVENTS +-- These events fire whenever a trove's state changes (debt, collateral, etc) +-- ============================================================================ +trove_updated_events AS ( + SELECT + -- Convert block timestamp to date (strips time, keeps only YYYY-MM-DD) + DATE(block_time AT TIME ZONE 'UTC') AS date, + -- Keep full timestamp for ordering events within same day + block_time AS evt_block_time, + -- Transaction hash for debugging/tracing + transaction_hash AS evt_tx_hash, + -- Extract data from TroveUpdated event structure: + -- All fields are non-indexed, u256 values take 2 felts (low, high) + -- data[1], data[2] = trove_id (low, high) + -- data[3], data[4] = debt (low, high) + -- data[5], data[6] = coll (low, high) + -- data[7], data[8] = stake (low, high) + -- data[9], data[10] = annual_interest_rate (low, high) + -- Combine u256 parts as hex string + -- Each part is exactly 32 hex chars (128 bits) + LOWER(CONCAT('0x', + "right"(SUBSTRING(TO_HEX(data[2]), 3), 32), -- high part, exactly 32 chars + "right"(SUBSTRING(TO_HEX(data[1]), 3), 32) -- low part, exactly 32 chars + )) AS trove_id, -- Full u256 as hex string (64 chars total) + -- Total debt including principal + interest + redistributions, converted from wei + CAST(bytearray_to_uint256(data[3]) AS DOUBLE) / 1e18 AS debt, + -- Total collateral including redistributions, converted from wei + CAST(bytearray_to_uint256(data[5]) AS DOUBLE) / 1e18 AS coll, + -- Annual interest rate as decimal (e.g., 0.05 = 5%) + CAST(bytearray_to_uint256(data[9]) AS DOUBLE) / 1e18 AS annual_interest_rate + FROM starknet.events + WHERE from_address = (SELECT events_emitter FROM cfg) + -- Filter to TroveUpdated events by selector + AND keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 + -- Performance optimization: Only scan events after deployment + AND block_number >= (SELECT deployment_block FROM cfg) +), + +-- ============================================================================ +-- EXTRACT ERC721 TRANSFER EVENTS FROM TROVENFT +-- Track NFT transfers to know current owner of each trove +-- Transfer event structure: from_address, to_address, token_id +-- Using Starknet's generic events table structure +-- ============================================================================ +nft_transfer_events AS ( + SELECT + -- Date of the transfer + DATE(block_time AT TIME ZONE 'UTC') AS date, + -- Full timestamp for ordering transfers + block_time AS evt_block_time, + -- Transaction hash + transaction_hash AS evt_tx_hash, + -- Extract data from Transfer event + -- ERC721 Transfer in this implementation: + -- keys[1] = event selector + -- keys[2] = from_address (indexed) + -- keys[3] = to_address (indexed) + -- keys[4], keys[5] = token_id (u256 as two felts, indexed) + -- Token ID is indexed in keys array (u256 = 2 felts at keys[4], keys[5]) + LOWER(CONCAT('0x', + "right"(SUBSTRING(TO_HEX(keys[5]), 3), 32), -- high part, exactly 32 chars + "right"(SUBSTRING(TO_HEX(keys[4]), 3), 32) -- low part, exactly 32 chars + )) AS trove_id, -- Full u256 as hex string (64 chars total) + -- From address (0x0 for minting) + keys[2] AS from_addr, + -- To address (new owner, 0x0 for burning) + keys[3] AS to_addr + FROM starknet.events + WHERE from_address = (SELECT trove_nft FROM cfg) + -- Filter to Transfer events by selector + AND keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 + -- Performance optimization: Only scan events after deployment + AND block_number >= (SELECT deployment_block FROM cfg) +), + +-- ============================================================================ +-- CALCULATE CURRENT NFT OWNER FOR EACH TROVE ON EACH DAY +-- Track ownership changes over time, accounting for transfers +-- ============================================================================ +trove_ownership_history AS ( + SELECT + date, + evt_block_time, + trove_id, + to_addr AS owner, -- The recipient becomes the new owner + -- Create a sequence number to track the order of transfers + ROW_NUMBER() OVER (PARTITION BY trove_id ORDER BY evt_block_time) AS transfer_seq + FROM nft_transfer_events + WHERE to_addr != 0x0000000000000000000000000000000000000000 -- Exclude burns +), + +-- Get the current owner of each trove (carry forward from last transfer) +-- This handles cases where troves are modified without ownership changes +trove_current_owners AS ( + SELECT + trove_id, + owner, + evt_block_time AS ownership_start_time, + -- Get the next transfer time (or NULL if this is the current owner) + LEAD(evt_block_time) OVER (PARTITION BY trove_id ORDER BY evt_block_time) AS ownership_end_time + FROM trove_ownership_history +), + +-- ============================================================================ +-- GET LATEST TROVE STATE FOR EACH TROVE +-- This will be used when we have Transfer events without TroveUpdated +-- ============================================================================ +latest_trove_states AS ( + SELECT + trove_id, + debt, + coll, + annual_interest_rate, + ROW_NUMBER() OVER (PARTITION BY trove_id ORDER BY evt_block_time DESC) AS rn + FROM trove_updated_events +), + +current_trove_states AS ( + SELECT + trove_id, + debt, + coll, + annual_interest_rate + FROM latest_trove_states + WHERE rn = 1 +), + +-- ============================================================================ +-- COMBINE TROVE DATA WITH CURRENT OWNERS +-- Handle both TroveUpdated events AND Transfer-only events +-- ============================================================================ +-- First, get positions from TroveUpdated events +trove_positions_from_updates AS ( + SELECT + tu.date, + tu.evt_block_time, + tu.trove_id, + COALESCE( + LOWER(CONCAT('0x', TO_HEX(tco.owner))), + CONCAT('UNKNOWN_OWNER_', tu.trove_id) + ) AS user, + tu.debt AS entire_debt, + tu.coll AS entire_coll, + tu.annual_interest_rate, + CASE + WHEN tco.owner IS NULL THEN TRUE + ELSE FALSE + END AS is_orphan_trove + FROM trove_updated_events tu + LEFT JOIN trove_current_owners tco + ON tu.trove_id = tco.trove_id + AND tu.evt_block_time >= tco.ownership_start_time + AND (tu.evt_block_time < tco.ownership_end_time OR tco.ownership_end_time IS NULL) +), + +-- Then, get positions from Transfer events (carry forward last known state) +trove_positions_from_transfers AS ( + SELECT + toh.date, + toh.evt_block_time, + toh.trove_id, + LOWER(CONCAT('0x', TO_HEX(toh.owner))) AS user, + cts.debt AS entire_debt, + cts.coll AS entire_coll, + cts.annual_interest_rate, + FALSE AS is_orphan_trove + FROM trove_ownership_history toh + INNER JOIN current_trove_states cts + ON toh.trove_id = cts.trove_id + -- Only include transfers that don't have a corresponding TroveUpdated on the same block + WHERE NOT EXISTS ( + SELECT 1 + FROM trove_updated_events tue + WHERE tue.trove_id = toh.trove_id + AND tue.evt_block_time = toh.evt_block_time + ) +), + +-- Combine both sources +trove_positions AS ( + SELECT * FROM trove_positions_from_updates + UNION ALL + SELECT * FROM trove_positions_from_transfers +), + +-- ============================================================================ +-- GET DAILY END-OF-DAY POSITIONS +-- For each day with activity, get the LAST state of each trove +-- ============================================================================ +daily_trove_states_raw AS ( + SELECT + date, + trove_id, + user, + entire_debt, + entire_coll, + annual_interest_rate, + is_orphan_trove, + evt_block_time, + -- Rank events to get the last one per trove per day + ROW_NUMBER() OVER ( + PARTITION BY trove_id, date + ORDER BY evt_block_time DESC + ) AS rn + FROM trove_positions +), + +daily_trove_states AS ( + SELECT + date, + trove_id, + user, + entire_debt, + entire_coll, + annual_interest_rate, + is_orphan_trove + FROM daily_trove_states_raw + WHERE rn = 1 -- Only keep the last event of each day for each trove +), + +-- ============================================================================ +-- HANDLE OWNERSHIP CONTINUITY +-- Get daily ownership for each trove (for continuous position tracking) +-- ============================================================================ +daily_trove_ownership AS ( + SELECT DISTINCT + date, + trove_id, + -- Find the owner on this date based on ownership periods + FIRST_VALUE(owner) OVER ( + PARTITION BY trove_id, date + ORDER BY ownership_start_time DESC + ) AS owner + FROM ( + SELECT DISTINCT date FROM trove_positions + ) d + CROSS JOIN trove_current_owners tco + WHERE d.date >= DATE(tco.ownership_start_time) + AND (d.date < DATE(tco.ownership_end_time) OR tco.ownership_end_time IS NULL) +), + +-- ============================================================================ +-- RECOMBINE POSITIONS WITH CONTINUOUS OWNERSHIP +-- Update trove positions with carried-forward ownership +-- ============================================================================ +positions_with_ownership AS ( + SELECT + dts.date, + dts.trove_id, + -- Use ownership from continuous tracking, keeping UNKNOWN_OWNER flags + COALESCE( + CASE + WHEN dto.owner IS NOT NULL THEN LOWER(CONCAT('0x', TO_HEX(dto.owner))) + ELSE NULL + END, + dts.user + ) AS user, + dts.entire_debt, + dts.entire_coll, + dts.annual_interest_rate, + dts.is_orphan_trove + FROM daily_trove_states dts + LEFT JOIN daily_trove_ownership dto + ON dts.trove_id = dto.trove_id AND dts.date = dto.date +), + +-- Extend positions to include today even if no events +latest_positions AS ( + SELECT + trove_id, + user, + entire_debt, + entire_coll, + annual_interest_rate, + is_orphan_trove, + date AS last_event_date + FROM positions_with_ownership + WHERE date = (SELECT MAX(date) FROM positions_with_ownership) +), + +positions_extended AS ( + SELECT * FROM positions_with_ownership + UNION ALL + -- Add today's date with last known positions if no events today + SELECT + DATE(CURRENT_TIMESTAMP AT TIME ZONE 'UTC') AS date, + trove_id, + user, + entire_debt, + entire_coll, + annual_interest_rate, + is_orphan_trove + FROM latest_positions + WHERE last_event_date < DATE(CURRENT_TIMESTAMP AT TIME ZONE 'UTC') +), + +-- ============================================================================ +-- DETERMINE DATE RANGE FOR EACH USER +-- Note: Users might appear/disappear as NFTs are transferred +-- Include UNKNOWN_OWNER entries for debugging +-- ============================================================================ +user_range AS ( + SELECT + user, + -- First day this user owned any trove + MIN(date) AS d0, + -- Today's date (we need data up to today for active positions) + DATE(CURRENT_TIMESTAMP AT TIME ZONE 'UTC') AS d1 + FROM positions_extended + WHERE user IS NOT NULL + GROUP BY user +), + +-- ============================================================================ +-- CREATE CONTINUOUS DATE SERIES +-- Generate one row for every day from first activity to today +-- ============================================================================ +user_spine AS ( + SELECT + r.user, + x AS date -- Each date in the sequence becomes a row + FROM user_range r + -- SEQUENCE generates array of dates from d0 to d1 + -- UNNEST converts array into rows + CROSS JOIN UNNEST(SEQUENCE(r.d0, r.d1)) AS t(x) +), + +-- ============================================================================ +-- FILL FORWARD POSITIONS FOR CONTINUITY +-- Carry forward last known values for days without events +-- ============================================================================ +-- First, compute the carried-forward values +positions_filled AS ( + SELECT + s.date, + s.user, + pwo.trove_id, + -- Carry forward last known values for each trove + LAST_VALUE(pwo.entire_debt) OVER ( + PARTITION BY s.user, pwo.trove_id + ORDER BY s.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS entire_debt, + LAST_VALUE(pwo.entire_coll) OVER ( + PARTITION BY s.user, pwo.trove_id + ORDER BY s.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS entire_coll, + LAST_VALUE(pwo.annual_interest_rate) OVER ( + PARTITION BY s.user, pwo.trove_id + ORDER BY s.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS annual_interest_rate, + LAST_VALUE(CASE WHEN pwo.is_orphan_trove THEN 1 ELSE 0 END) OVER ( + PARTITION BY s.user, pwo.trove_id + ORDER BY s.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS is_orphan_flag + FROM user_spine s + LEFT JOIN positions_extended pwo + ON pwo.user = s.user AND pwo.date = s.date +), + +-- Then aggregate across all troves for each user +continuous_positions_raw AS ( + SELECT + date, + user, + -- Sum across all user's troves + SUM(entire_debt) AS entire_debt, + SUM(entire_coll) AS entire_coll, + -- Debt-weighted average interest rate + CASE + WHEN SUM(entire_debt) > 0 + THEN SUM(entire_debt * annual_interest_rate) / SUM(entire_debt) + ELSE 0 + END AS annual_interest_rate, + -- Track if this user has any orphan troves + MAX(is_orphan_flag) AS has_orphan_troves + FROM positions_filled + WHERE entire_debt IS NOT NULL OR entire_coll IS NOT NULL + GROUP BY date, user +), + +-- Apply daily interest accrual using simple interest +-- Note: USDU uses simple interest, not compound interest +continuous_positions AS ( + SELECT + date, + user, + -- For each day, calculate the debt with accrued interest + -- If it's been N days since last update, add N days of interest + entire_debt * (1 + annual_interest_rate * + DATE_DIFF('day', + LAG(date, 1, date) OVER (PARTITION BY user ORDER BY date), + date + ) / 365 + ) AS entire_debt, + entire_coll, + annual_interest_rate, + has_orphan_troves + FROM continuous_positions_raw +), + +-- ============================================================================ +-- CALCULATE DAILY CHANGES +-- Track day-over-day changes in positions for interest calculation +-- ============================================================================ +daily_changes AS ( + SELECT + date, + user, + entire_debt, + entire_coll, + annual_interest_rate, + has_orphan_troves, + -- Get yesterday's debt for comparison + LAG(entire_debt, 1) OVER (PARTITION BY user ORDER BY date) AS prev_debt, + -- Get yesterday's collateral + LAG(entire_coll, 1) OVER (PARTITION BY user ORDER BY date) AS prev_coll, + -- Calculate debt change from yesterday to today + COALESCE(entire_debt, 0) - COALESCE( + LAG(entire_debt, 1) OVER (PARTITION BY user ORDER BY date), 0 + ) AS debt_change, + -- Calculate collateral change from yesterday to today + COALESCE(entire_coll, 0) - COALESCE( + LAG(entire_coll, 1) OVER (PARTITION BY user ORDER BY date), 0 + ) AS coll_change + FROM continuous_positions + -- Only include days where user has active positions + WHERE entire_debt > 0 OR entire_coll > 0 +), + +-- ============================================================================ +-- PRICE DATA +-- Using OpenBlockLabs price aggregator (same as Opus) +-- ============================================================================ +-- Daily prices +daily_prices AS ( + SELECT + DATE(timestamp AT TIME ZONE 'UTC') AS date, + price AS gbtc_price + FROM dune.openblocklabs.result_starknet_prices_daily + WHERE contract_address = 0x04d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55 -- GBTC +), + +-- Latest price as fallback for current day +latest_price AS ( + SELECT price AS gbtc_price + FROM ( + SELECT price, + ROW_NUMBER() OVER (ORDER BY timestamp DESC) AS rn + FROM dune.openblocklabs.result_starknet_prices_daily + WHERE contract_address = 0x04d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55 + ) t + WHERE rn = 1 +), + +-- Combine daily and latest prices +price_data AS ( + SELECT + d.date, + COALESCE(dp.gbtc_price, lp.gbtc_price, 100000) AS gbtc_price -- Fallback to 100k if no price + FROM (SELECT DISTINCT date FROM daily_changes) d + LEFT JOIN daily_prices dp ON dp.date = d.date + CROSS JOIN latest_price lp +), + +-- ============================================================================ +-- FORMAT FINAL OUTPUT +-- Combine all data and format according to required schema +-- ============================================================================ +final_output AS ( + SELECT + -- Date for this row of data + dc.date, + -- User address (current NFT owner, may include UNKNOWN_OWNER flags) + dc.user, + + -- Market identification (from config) + cfg.market_address, + cfg.market_name, + cfg.market_owner, + + -- Collateral asset information + cfg.gbtc_addr AS asset, + 'GBTC' AS asset_symbol, + 18 AS asset_decimals, -- Values are already scaled to 18 decimals in events + + -- Debt asset information + cfg.usdu_addr AS debt_asset, + 'USDU' AS debt_asset_symbol, + 18 AS debt_decimals, + + -- Accumulator = 1.0 (we use entire_debt which already includes all interest) + 1.0 AS latest_accumulator, + cfg.protocol_scale, + + -- Prices + COALESCE(pd.gbtc_price, 100000) AS asset_price, + 1.0 AS debt_price, + + -- Raw balance data + dc.entire_coll AS total_supplied_raw, + dc.entire_debt AS total_borrowed_raw, + + -- Daily collateral changes (positive = deposits, negative = withdrawals) + dc.coll_change AS raw_amount_deposit, + dc.coll_change AS adjusted_amount_deposit, + + -- Daily debt changes (positive = new borrowing, negative = repayments) + dc.debt_change AS raw_amount_borrowed, + dc.debt_change AS adjusted_amount_borrowed, + + -- Adjusted balances (raw * accumulator, but accumulator = 1.0) + dc.entire_coll AS total_supplied_adjusted, + dc.entire_debt AS total_borrowed_adjusted, + + -- Token amounts (same as adjusted) + dc.entire_coll AS total_supplied_tokens, + dc.entire_debt AS total_borrowed_tokens, + + -- USD values + dc.entire_coll * COALESCE(pd.gbtc_price, 100000) AS total_supplied_usd, + dc.entire_debt * 1.0 AS total_borrowed_usd, + (dc.entire_coll * COALESCE(pd.gbtc_price, 100000)) - (dc.entire_debt * 1.0) AS net_usd, + + -- Interest rate data + dc.annual_interest_rate AS effective_apr_daily, + + -- Daily interest calculation: debt * (annual_rate / 365) + -- This represents the interest accrued for holding this debt position for one day + dc.entire_debt * (dc.annual_interest_rate / 365) AS interest_tokens_daily, + + -- Interest in USD + dc.entire_debt * (dc.annual_interest_rate / 365) * 1.0 AS interest_usd_daily, + + -- Debug flag: indicates if this user has orphan troves + dc.has_orphan_troves + + FROM daily_changes dc + CROSS JOIN cfg + LEFT JOIN price_data pd + ON pd.date = dc.date +) + +-- ============================================================================ +-- FINAL SELECT +-- Output only rows with actual positions, ordered by date and user +-- ============================================================================ +SELECT + date, + user, + market_address, + market_name, + market_owner, + asset, + asset_symbol, + asset_decimals, + debt_asset, + debt_asset_symbol, + debt_decimals, + latest_accumulator, + protocol_scale, + asset_price, + debt_price, + total_supplied_raw, + total_borrowed_raw, + raw_amount_deposit, + adjusted_amount_deposit, + raw_amount_borrowed, + adjusted_amount_borrowed, + total_supplied_adjusted, + total_borrowed_adjusted, + total_supplied_tokens, + total_borrowed_tokens, + total_supplied_usd, + total_borrowed_usd, + net_usd, + effective_apr_daily, + interest_tokens_daily, + interest_usd_daily +FROM final_output +WHERE + -- Only include rows where user has some position + (total_supplied_adjusted IS NOT NULL AND total_supplied_adjusted <> 0) + OR + (total_borrowed_adjusted IS NOT NULL AND total_borrowed_adjusted <> 0) +ORDER BY date DESC, user From 07730d6718b2550e5972b704c5c9df96e455d076 Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:22:35 +0200 Subject: [PATCH 02/14] Example queries and dune query --- dune_query_with_nft.sql | 86 ++-- example_queries/5155365.sql | 48 ++ example_queries/5720005.sql | 518 ++++++++++++++++++++ example_queries/active_pool_updates.sql | 25 + example_queries/interest_rates.sql | 149 ++++++ example_queries/protocol_revenue.sql | 168 +++++++ example_queries/token_holders.sql | 110 +++++ example_queries/total_usdu_supply.sql | 15 + example_queries/trove_debt_and_interest.sql | 39 ++ example_queries/trove_operations.sql | 40 ++ example_queries/tvl.sql | 132 +++++ example_queries/usdu_holders_no_dust.sql | 110 +++++ scripts/utils.ts | 26 - 13 files changed, 1395 insertions(+), 71 deletions(-) create mode 100644 example_queries/5155365.sql create mode 100644 example_queries/5720005.sql create mode 100644 example_queries/active_pool_updates.sql create mode 100644 example_queries/interest_rates.sql create mode 100644 example_queries/protocol_revenue.sql create mode 100644 example_queries/token_holders.sql create mode 100644 example_queries/total_usdu_supply.sql create mode 100644 example_queries/trove_debt_and_interest.sql create mode 100644 example_queries/trove_operations.sql create mode 100644 example_queries/tvl.sql create mode 100644 example_queries/usdu_holders_no_dust.sql diff --git a/dune_query_with_nft.sql b/dune_query_with_nft.sql index 815d0b2..dca83ac 100644 --- a/dune_query_with_nft.sql +++ b/dune_query_with_nft.sql @@ -12,30 +12,34 @@ cfg AS ( SELECT -- Deployment block number -- This significantly improves query performance by avoiding unnecessary historical scans - 2338809 AS deployment_block, + 2759669 AS deployment_block, - -- Market address (GBTC AddressesRegistry) - raw hex for calculations - 0x0305078f5237d5654f159148c9e4fc067ec10e76ef56bfe460336a73efa4a27f AS market_address_raw, + -- Market address (WWBTC AddressesRegistry) - raw hex for calculations + 0x042a37aa9263b01191286f0f800cc85c676441fb9d27d74bbf3ebcbf4e373d81 AS market_address_raw, -- Same address cast to VARBINARY for output formatting - CAST(0x0305078f5237d5654f159148c9e4fc067ec10e76ef56bfe460336a73efa4a27f AS VARBINARY) AS market_address, + CAST(0x042a37aa9263b01191286f0f800cc85c676441fb9d27d74bbf3ebcbf4e373d81 AS VARBINARY) AS market_address, -- Human-readable protocol name 'Uncap Protocol' AS market_name, -- Protocol owner/operator 'Uncap' AS market_owner, - -- GBTC token contract address - raw hex - 0x04d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55 AS gbtc_addr_raw, - -- Same GBTC address cast to VARBINARY for output - CAST(0x04d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55 AS VARBINARY) AS gbtc_addr, + -- WWBTC token contract address - raw hex + 0x075d9e518f46a9ca0404fb0a7d386ce056dadf57fd9a0e8659772cb517be4a18 AS wwbtc_addr_raw, + -- Same WWBTC address cast to VARBINARY for output + CAST(0x075d9e518f46a9ca0404fb0a7d386ce056dadf57fd9a0e8659772cb517be4a18 AS VARBINARY) AS wwbtc_addr, -- USDU stablecoin contract address - raw hex 0x04695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c AS usdu_addr_raw, -- Same USDU address cast to VARBINARY for output - CAST(0x04695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c AS VARBINARY) AS usdu_addr, + CAST(0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 AS VARBINARY) AS usdu_addr, + -- WBTC token contract address for price fetching + 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac AS wbtc_price_addr, + -- Debt price (USDU is pegged to $1) + 1.0 AS debt_price, -- Protocol scaling factor (1e18 for 18 decimals) 1e18 AS protocol_scale, -- TroveManagerEventsEmitter contract that emits all the events we need - 0x06dcfc4ce8ca0c664d5f6c5ee434c9379d15e616246db092cd9e21e5871e4377 AS events_emitter, + 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 AS events_emitter, -- TroveNFT contract that tracks ownership via ERC721 Transfer events - 0x05e0bcd997c22de21ac7716cd38fdb881b56764d20fa6bbe1647281abc3e1800 AS trove_nft + 0x04a1e17f1e4d5fdd839fa6a4c52a742b9d10871028b6ffcfb2f1de8dd5dc2cd4 AS trove_nft ), -- ============================================================================ @@ -116,22 +120,23 @@ nft_transfer_events AS ( -- ============================================================================ -- CALCULATE CURRENT NFT OWNER FOR EACH TROVE ON EACH DAY --- Track ownership changes over time, accounting for transfers +-- Track ownership changes over time, accounting for transfers and burns -- ============================================================================ trove_ownership_history AS ( SELECT date, evt_block_time, trove_id, - to_addr AS owner, -- The recipient becomes the new owner + to_addr AS owner, -- The recipient becomes the new owner (0x0 for burns) -- Create a sequence number to track the order of transfers ROW_NUMBER() OVER (PARTITION BY trove_id ORDER BY evt_block_time) AS transfer_seq FROM nft_transfer_events - WHERE to_addr != 0x0000000000000000000000000000000000000000 -- Exclude burns + -- Include all transfers, including burns (to_addr = 0x0) ), -- Get the current owner of each trove (carry forward from last transfer) -- This handles cases where troves are modified without ownership changes +-- Burns (to_addr = 0x0) will set ownership_end_time for the previous owner trove_current_owners AS ( SELECT trove_id, @@ -140,6 +145,7 @@ trove_current_owners AS ( -- Get the next transfer time (or NULL if this is the current owner) LEAD(evt_block_time) OVER (PARTITION BY trove_id ORDER BY evt_block_time) AS ownership_end_time FROM trove_ownership_history + WHERE owner != 0x0000000000000000000000000000000000000000 -- Only track actual owners, not burns ), -- ============================================================================ @@ -208,8 +214,10 @@ trove_positions_from_transfers AS ( FROM trove_ownership_history toh INNER JOIN current_trove_states cts ON toh.trove_id = cts.trove_id + -- Only include actual ownership transfers, not burns + WHERE toh.owner != 0x0000000000000000000000000000000000000000 -- Only include transfers that don't have a corresponding TroveUpdated on the same block - WHERE NOT EXISTS ( + AND NOT EXISTS ( SELECT 1 FROM trove_updated_events tue WHERE tue.trove_id = toh.trove_id @@ -474,25 +482,27 @@ daily_changes AS ( -- ============================================================================ -- PRICE DATA --- Using OpenBlockLabs price aggregator (same as Opus) +-- Using OpenBlockLabs price aggregator -- ============================================================================ -- Daily prices daily_prices AS ( SELECT DATE(timestamp AT TIME ZONE 'UTC') AS date, - price AS gbtc_price + price AS wbtc_price FROM dune.openblocklabs.result_starknet_prices_daily - WHERE contract_address = 0x04d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55 -- GBTC + CROSS JOIN cfg + WHERE contract_address = cfg.wbtc_price_addr ), -- Latest price as fallback for current day latest_price AS ( - SELECT price AS gbtc_price + SELECT price AS wbtc_price FROM ( SELECT price, ROW_NUMBER() OVER (ORDER BY timestamp DESC) AS rn FROM dune.openblocklabs.result_starknet_prices_daily - WHERE contract_address = 0x04d931635e7d9d617823f2b7431cad3ea021dbfa3ebb07df2229cd6c8bdc7b55 + CROSS JOIN cfg + WHERE contract_address = cfg.wbtc_price_addr ) t WHERE rn = 1 ), @@ -501,7 +511,7 @@ latest_price AS ( price_data AS ( SELECT d.date, - COALESCE(dp.gbtc_price, lp.gbtc_price, 100000) AS gbtc_price -- Fallback to 100k if no price + COALESCE(dp.wbtc_price, lp.wbtc_price) AS wbtc_price FROM (SELECT DISTINCT date FROM daily_changes) d LEFT JOIN daily_prices dp ON dp.date = d.date CROSS JOIN latest_price lp @@ -524,8 +534,8 @@ final_output AS ( cfg.market_owner, -- Collateral asset information - cfg.gbtc_addr AS asset, - 'GBTC' AS asset_symbol, + cfg.wwbtc_addr AS asset, + 'WWBTC' AS asset_symbol, 18 AS asset_decimals, -- Values are already scaled to 18 decimals in events -- Debt asset information @@ -538,8 +548,8 @@ final_output AS ( cfg.protocol_scale, -- Prices - COALESCE(pd.gbtc_price, 100000) AS asset_price, - 1.0 AS debt_price, + pd.wbtc_price AS asset_price, + cfg.debt_price AS debt_price, -- Raw balance data dc.entire_coll AS total_supplied_raw, @@ -562,9 +572,9 @@ final_output AS ( dc.entire_debt AS total_borrowed_tokens, -- USD values - dc.entire_coll * COALESCE(pd.gbtc_price, 100000) AS total_supplied_usd, - dc.entire_debt * 1.0 AS total_borrowed_usd, - (dc.entire_coll * COALESCE(pd.gbtc_price, 100000)) - (dc.entire_debt * 1.0) AS net_usd, + dc.entire_coll * pd.wbtc_price AS total_supplied_usd, + dc.entire_debt * cfg.debt_price AS total_borrowed_usd, + (dc.entire_coll * pd.wbtc_price) - (dc.entire_debt * cfg.debt_price) AS net_usd, -- Interest rate data dc.annual_interest_rate AS effective_apr_daily, @@ -574,7 +584,7 @@ final_output AS ( dc.entire_debt * (dc.annual_interest_rate / 365) AS interest_tokens_daily, -- Interest in USD - dc.entire_debt * (dc.annual_interest_rate / 365) * 1.0 AS interest_usd_daily, + dc.entire_debt * (dc.annual_interest_rate / 365) * cfg.debt_price AS interest_usd_daily, -- Debug flag: indicates if this user has orphan troves dc.has_orphan_troves @@ -594,30 +604,16 @@ SELECT user, market_address, market_name, - market_owner, asset, asset_symbol, asset_decimals, + total_supplied_tokens, + total_borrowed_tokens, debt_asset, debt_asset_symbol, debt_decimals, - latest_accumulator, - protocol_scale, - asset_price, - debt_price, - total_supplied_raw, - total_borrowed_raw, - raw_amount_deposit, - adjusted_amount_deposit, - raw_amount_borrowed, - adjusted_amount_borrowed, - total_supplied_adjusted, - total_borrowed_adjusted, - total_supplied_tokens, - total_borrowed_tokens, total_supplied_usd, total_borrowed_usd, - net_usd, effective_apr_daily, interest_tokens_daily, interest_usd_daily diff --git a/example_queries/5155365.sql b/example_queries/5155365.sql new file mode 100644 index 0000000..f4cd570 --- /dev/null +++ b/example_queries/5155365.sql @@ -0,0 +1,48 @@ +select + contract_address, + 'WETH' as collateral_type, + evt_tx_hash as tx_hash, + evt_index as tx_index, + evt_block_time as block_time, + evt_block_number as block_number, + _troveId as trove_id, + _operation as operation, + _debtChangeFromOperation/1e18 as debt_change, + _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, + _collChangeFromOperation/1e18 as collateral_change, + _annualInterestRate/1e16 as interest_rates +from liquity_v2_ethereum.troveManager_wETH_evt_TroveOperation + +union all + +select + contract_address, + 'rETH' as collateral_type, + evt_tx_hash as tx_hash, + evt_index as tx_index, + evt_block_time as block_time, + evt_block_number as block_number, + _troveId as trove_id, + _operation as operation, + _debtChangeFromOperation/1e18 as debt_change, + _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, + _collChangeFromOperation/1e18 as collateral_change, + _annualInterestRate/1e16 as interest_rates +from liquity_v2_ethereum.troveManager_rETH_evt_TroveOperation + +union all + +select + contract_address, + 'wstETH' as collateral_type, + evt_tx_hash as tx_hash, + evt_index as tx_index, + evt_block_time as block_time, + evt_block_number as block_number, + _troveId as trove_id, + _operation as operation, + _debtChangeFromOperation/1e18 as debt_change, + _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, + _collChangeFromOperation/1e18 as collateral_change, + _annualInterestRate/1e16 as interest_rates +from liquity_v2_ethereum.troveManager_wstETH_evt_TroveOperation diff --git a/example_queries/5720005.sql b/example_queries/5720005.sql new file mode 100644 index 0000000..23aa5a4 --- /dev/null +++ b/example_queries/5720005.sql @@ -0,0 +1,518 @@ +-- First find the trove managers for each trove id at all times + +-- When there is a "troveUpdated" event, it means the trove is no longer managed by a batch manager +with weth_trove_manager_address_updates as ( + SELECT evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + _interestBatchManager as interest_batch_manager + FROM liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt + UNION ALL + SELECT + evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager + FROM liquity_v2_ethereum.trovemanager_weth_evt_troveupdated +) + +-- For each batch updated (bu) event, make sure to apply it to the relevant troves (with latest manager = bu manager) +, weth_bu_with_trove_rows as ( + SELECT + bu.*, + tm.trove_id, + tm.interest_batch_manager, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id + ORDER BY bu.evt_block_number - tm.evt_block_number ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_weth_evt_batchupdated bu + LEFT JOIN weth_trove_manager_address_updates tm + ON ( + tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event + OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) + ) +) + +-- Keep latest value and right managers +, weth_bu_with_trove as + ( + select * + from weth_bu_with_trove_rows + where rn = 1 and _interestBatchManager = interest_batch_manager + ) + +-- Now get the bu events enriched with debt numbers from the latest BatchedTroveUpdated (bt) events +-- We need to do that because we don't have the debt numbers in the bu events +, weth_bu_closest_previous_events AS ( + -- Find the closest previous batchedTroveUpdated for any batchUpdated + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + bu.evt_block_time as block_time, + bu.evt_tx_hash as tx_hash, + bu.evt_index as tx_index, + bu.evt_block_number as block_number, + bu._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bu.trove_id, + bt._batchDebtShares/1e18 as batch_debt_shares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId + ORDER BY bu.evt_block_time - bt.evt_block_time ASC + ) AS rn + FROM weth_bu_with_trove bu + JOIN liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt + ON bu.trove_id = bt._troveId + AND ( + bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event + OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) + ) +) + +, weth_bu_events_enriched as ( + select + 'WETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , batch_debt_shares * debt_to_shares_ratio as debt + , stake + , 'batchUpdated' as event + from weth_bu_closest_previous_events + where rn = 1 +) + +-- Now we have the bu events enriched +-- We need to add 2 event types: batchedTroveUpdated (a bit easier but similar logic) and troveUpdated (super easy) + +-- The complexity in batchedTroveUpdated is to get the latest values of interest rates etc. if they were changed +-- by a batchUpdated event + +, weth_bt_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + + bt.evt_block_time as block_time, + bt.evt_tx_hash as tx_hash, + bt.evt_index as tx_index, + bt.evt_block_number as block_number, + bt._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bt._troveId as trove_id, + bt._batchDebtShares/1e18 as _batchDebtShares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId + ORDER BY bt.evt_block_time - bu.evt_block_time ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt + JOIN liquity_v2_ethereum.trovemanager_weth_evt_batchupdated bu + ON bu._interestBatchManager = bt._interestBatchManager + AND ( + bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event + OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) + ) +) + +-- Below is batchedTroveUpdated events -- full +, weth_bt_events_enriched as ( + select + 'WETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , _batchDebtShares * debt_to_shares_ratio as debt + , stake + , 'batchTroveUpdated' as event + from weth_bt_closest_previous_events + where rn = 1 +) + +-- We do the same for rETH and wstETH +, reth_trove_manager_address_updates as ( + SELECT evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + _interestBatchManager as interest_batch_manager + FROM liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt + UNION ALL + SELECT + evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager + FROM liquity_v2_ethereum.trovemanager_reth_evt_troveupdated +) + +, reth_bu_with_trove_rows as ( + SELECT + bu.*, + tm.trove_id, + tm.interest_batch_manager, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id + ORDER BY bu.evt_block_number - tm.evt_block_number ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_reth_evt_batchupdated bu + LEFT JOIN reth_trove_manager_address_updates tm + ON ( + tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event + OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) + ) +) + +, reth_bu_with_trove as + ( + select * + from reth_bu_with_trove_rows + where rn = 1 and _interestBatchManager = interest_batch_manager + ) + + +, reth_bu_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + bu.evt_block_time as block_time, + bu.evt_tx_hash as tx_hash, + bu.evt_index as tx_index, + bu.evt_block_number as block_number, + bu._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bu.trove_id, + bt._batchDebtShares/1e18 as batch_debt_shares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId + ORDER BY bu.evt_block_time - bt.evt_block_time ASC + ) AS rn + FROM reth_bu_with_trove bu + JOIN liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt + ON bu.trove_id = bt._troveId + AND ( + bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event + OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) + ) +) + +, reth_bu_events_enriched as ( + select + 'rETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , batch_debt_shares * debt_to_shares_ratio as debt + , stake + , 'batchUpdated' as event + from reth_bu_closest_previous_events + where rn = 1 +) + +, reth_bt_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + + bt.evt_block_time as block_time, + bt.evt_tx_hash as tx_hash, + bt.evt_index as tx_index, + bt.evt_block_number as block_number, + bt._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bt._troveId as trove_id, + bt._batchDebtShares/1e18 as _batchDebtShares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId + ORDER BY bt.evt_block_time - bu.evt_block_time ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt + JOIN liquity_v2_ethereum.trovemanager_reth_evt_batchupdated bu + ON bu._interestBatchManager = bt._interestBatchManager + AND ( + bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event + OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) + ) +) + +, reth_bt_events_enriched as ( + select + 'rETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , _batchDebtShares * debt_to_shares_ratio as debt + , stake + , 'batchTroveUpdated' as event + from reth_bt_closest_previous_events + where rn = 1 +) + +, wsteth_trove_manager_address_updates as ( + SELECT evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + _interestBatchManager as interest_batch_manager + FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt + UNION ALL + SELECT + evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager + FROM liquity_v2_ethereum.trovemanager_wsteth_evt_troveupdated +) + +, wsteth_bu_with_trove_rows as ( + SELECT + bu.*, + tm.trove_id, + tm.interest_batch_manager, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id + ORDER BY bu.evt_block_number - tm.evt_block_number ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchupdated bu + LEFT JOIN wsteth_trove_manager_address_updates tm + ON ( + tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event + OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) + ) +) + +, wsteth_bu_with_trove as + ( + select * + from wsteth_bu_with_trove_rows + where rn = 1 and _interestBatchManager = interest_batch_manager + ) + +, wsteth_bu_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + bu.evt_block_time as block_time, + bu.evt_tx_hash as tx_hash, + bu.evt_index as tx_index, + bu.evt_block_number as block_number, + bu._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bu.trove_id, + bt._batchDebtShares/1e18 as batch_debt_shares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId + ORDER BY bu.evt_block_time - bt.evt_block_time ASC + ) AS rn + FROM wsteth_bu_with_trove bu + JOIN liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt + ON bu.trove_id = bt._troveId + AND ( + bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event + OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) + ) +) + +, wsteth_bu_events_enriched as ( + select + 'wstETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , batch_debt_shares * debt_to_shares_ratio as debt + , stake + , 'batchUpdated' as event + from wsteth_bu_closest_previous_events + where rn = 1 +) + +, wsteth_bt_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + + bt.evt_block_time as block_time, + bt.evt_tx_hash as tx_hash, + bt.evt_index as tx_index, + bt.evt_block_number as block_number, + bt._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bt._troveId as trove_id, + bt._batchDebtShares/1e18 as _batchDebtShares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId + ORDER BY bt.evt_block_time - bu.evt_block_time ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt + JOIN liquity_v2_ethereum.trovemanager_wsteth_evt_batchupdated bu + ON bu._interestBatchManager = bt._interestBatchManager + AND ( + bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event + OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) + ) +) + +, wsteth_bt_events_enriched as ( + select + 'wstETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , _batchDebtShares * debt_to_shares_ratio as debt + , stake + , 'batchTroveUpdated' as event + from wsteth_bt_closest_previous_events + where rn = 1 +) + +-- Now we union all results +-- and add troveUpdated events (super easy) +, pre_final_results as ( + select * from weth_bu_events_enriched + union all + select * from weth_bt_events_enriched + union all + select + 'WETH' as collateral_type + , evt_tx_hash as tx_hash + , evt_index as tx_index + , evt_block_time as block_time + , evt_block_number as evt_block_number + , 0x0000000000000000000000000000000000000000 as interest_batch_manager + , _annualInterestRate/1e16 as interest_rates + , _troveId as trove_id + , _coll/1e18 as collateral + , _debt/1e18 as debt + , _stake/1e18 as stake + , 'troveUpdated' as event + from liquity_v2_ethereum.trovemanager_weth_evt_troveupdated + union all + select * from reth_bu_events_enriched + union all + select * from reth_bt_events_enriched + union all + select + 'rETH' as collateral_type + , evt_tx_hash as tx_hash + , evt_index as tx_index + , evt_block_time as block_time + , evt_block_number as evt_block_number + , 0x0000000000000000000000000000000000000000 as interest_batch_manager + , _annualInterestRate/1e16 as interest_rates + , _troveId as trove_id + , _coll/1e18 as collateral + , _debt/1e18 as debt + , _stake/1e18 as stake + , 'troveUpdated' as event + from liquity_v2_ethereum.trovemanager_reth_evt_troveupdated + union all + select * from wsteth_bu_events_enriched + union all + select * from wsteth_bt_events_enriched + union all + select + 'wstETH' as collateral_type + , evt_tx_hash as tx_hash + , evt_index as tx_index + , evt_block_time as block_time + , evt_block_number as evt_block_number + , 0x0000000000000000000000000000000000000000 as interest_batch_manager + , _annualInterestRate/1e16 as interest_rates + , _troveId as trove_id + , _coll/1e18 as collateral + , _debt/1e18 as debt + , _stake/1e18 as stake + , 'troveUpdated' as event + from liquity_v2_ethereum.trovemanager_wsteth_evt_troveupdated +) + +-- Final results with deduplication +-- (there can be duplicates because both batchUpdated & batchTroveUpdated events +-- in this case, keep the batchTroveUpdated event) +select + collateral_type, + tx_hash, + tx_index, + block_time, + block_number, + interest_rates, + interest_batch_manager, + trove_id, + collateral, + debt, + stake, + event +from ( + select + *, + row_number() over ( + partition by collateral_type, tx_hash, block_number, trove_id + order by tx_index desc, (case when event = 'batchTroveUpdated' then 1 else 0 end) desc + ) as rn + from pre_final_results +) ranked +where rn = 1 diff --git a/example_queries/active_pool_updates.sql b/example_queries/active_pool_updates.sql new file mode 100644 index 0000000..bf010fe --- /dev/null +++ b/example_queries/active_pool_updates.sql @@ -0,0 +1,25 @@ +-- Uncap Protocol - Active Pool Collateral Balance Updates +-- Network: Starknet Mainnet +-- Purpose: Track WBTC collateral balance changes in the ActivePool + +with + +cfg as ( + select + 0x0780627de12ac84a7887b7f83496a8ece5ea3c5eb7170f9f00587dabfdbe18d1 as active_pool_address, + 2763027 as deployment_block +) + +select + from_address as contract_address, + 'WBTC' as collateral_type, + transaction_hash as tx_hash, + event_index as tx_index, + block_time, + block_number, + cast(bytearray_to_uint256(data[1]) as double) / 1e18 as collateral_balance +from starknet.events +where from_address = (select active_pool_address from cfg) + and keys[1] = 0x02540b6268ec773045b5da968c6a2bfa1442c16c8c2822b49b7aa5abb78d43b3 + and block_number >= (select deployment_block from cfg) +order by block_number desc, event_index desc diff --git a/example_queries/interest_rates.sql b/example_queries/interest_rates.sql new file mode 100644 index 0000000..4c8ba15 --- /dev/null +++ b/example_queries/interest_rates.sql @@ -0,0 +1,149 @@ +-- Uncap Protocol - Interest Rates Over Time +-- Network: Starknet Mainnet +-- Purpose: Track debt-weighted average interest rates hourly + +with + +-- Get deployment date for time series +deployment_date as ( + select min(date_trunc('hour', time)) as start_hour + from starknet.blocks + where number >= 2759369 +), + +time_seq as ( + select + sequence( + CAST((select start_hour from deployment_date) as timestamp), + date_trunc('hour', cast(now() as timestamp)), + interval '1' hour + ) as time +), + +hours as ( + select + time.time as hour + from time_seq + cross join unnest(time) as time(time) +), + +hourly_rates as ( + select + *, + lead(hour, 1, current_timestamp) over (partition by trove_id, collateral_type order by hour asc) as next_hour + from ( + select + date_trunc('hour', block_time) as hour, + trove_id, + collateral_type, + max_by(interest_rates, (block_number, tx_index)) as interest_rates + from + query_5905300 -- trove_operations + group by 1, 2, 3 + ) +), + +filled_rates as ( + select + h.hour, + c.trove_id, + c.collateral_type, + c.interest_rates + from + hourly_rates c + inner join + hours h + on c.hour <= h.hour + and h.hour < c.next_hour +), + +hourly_debts as ( + select + *, + lead(hour, 1, current_timestamp) over (partition by trove_id, collateral_type order by hour asc) as next_hour + from ( + select + date_trunc('hour', block_time) as hour, + trove_id, + collateral_type, + max_by(debt, (block_number, tx_index)) as debt + from + query_5905310 -- trove_debt_and_interest + group by 1, 2, 3 + ) +), + +filled_debts as ( + select + h.hour, + c.trove_id, + c.collateral_type, + c.debt + from + hourly_debts c + inner join + hours h + on c.hour <= h.hour + and h.hour < c.next_hour +), + +combine_with_debts as ( + select + fr.*, + fd.debt + from + filled_rates fr + inner join + filled_debts fd + on fr.hour = fd.hour + and fr.trove_id = fd.trove_id + and fr.collateral_type = fd.collateral_type +), + +current_rates as ( + select + collateral_type, + sum(interest_rates * debt) / sum(debt) as interest_rates + from + combine_with_debts + where interest_rates > 0 and debt > 0 + and hour >= now() - interval '24' hour + group by 1 +), + +total_rates as ( + select + sum(interest_rates * debt) / sum(debt) as interest_rates + from + combine_with_debts + where interest_rates > 0 and debt > 0 + and hour >= date_trunc('hour', now() - interval '30' day) +), + +hourly_rates_summary as ( + select + date_trunc('hour', hour) as hour, + collateral_type, + (sum(interest_rates * debt) / sum(debt))/100 as interest_rates + from + combine_with_debts + where interest_rates > 0 and debt > 0 + and hour >= date_trunc('hour', now() - interval '30' day) + group by 1, 2 +) + +select + hrs.*, + cr.interest_rates as latest_rates, + tr.interest_rates as total_rates +from +hourly_rates_summary hrs +left join +current_rates cr + on hrs.collateral_type = cr.collateral_type +left join +total_rates tr + on 1 = 1 +where hour >= date_trunc('hour', now() - interval '30' day) +order by hrs.hour desc, lower(hrs.collateral_type) desc + diff --git a/example_queries/protocol_revenue.sql b/example_queries/protocol_revenue.sql new file mode 100644 index 0000000..46ea0c3 --- /dev/null +++ b/example_queries/protocol_revenue.sql @@ -0,0 +1,168 @@ +-- Uncap Protocol - Protocol Revenue +-- Network: Starknet Mainnet +-- Purpose: Track protocol revenue from redemption fees and interest rewards + +with + +cfg as ( + select + 0x06dcfc4ce8ca0c664d5f6c5ee434c9379d15e616246db092cd9e21e5871e4377 as events_emitter, + 0x04695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c as usdu_token, + 0x65ccbeb1516bd1ae841b0729dd0798cb86027fbbbce929f647a2d057655c054 as stability_pool, + 0x307d7378c2543140d607297f4a863fbfe83cb0804297532c52bdd246a7b7d69 as interest_router, + 2759369 as deployment_block +), + +-- Get deployment date for time series +deployment_date as ( + select min(date_trunc('day', time)) as start_date + from starknet.blocks + where number >= (select deployment_block from cfg) +), + +time_seq AS ( + select + sequence( + CAST((select start_date from deployment_date) as timestamp), + date_trunc('day', cast(now() as timestamp)), + interval '1' day + ) as time +), + +days AS ( + select + time.time as day + from time_seq + cross join unnest(time) as time(time) +), + +-- Extract RedemptionFeePaidToTrove events +-- Event structure: trove_id (u256), strk_fee (u256) +wbtc_fee as ( + select + date_trunc('minute', block_time) as minute, + transaction_hash as evt_tx_hash, + cast(bytearray_to_uint256(data[3]) as double) / 1e18 as fee -- strk_fee in STRK + from starknet.events + where from_address = (select events_emitter from cfg) + and keys[1] = 0x03b605674b6c4bd98149476fb8ed7db31a620da8d46f77070759e7b152252ef6 + and block_number >= (select deployment_block from cfg) +), + +-- Get WBTC prices for fee valuation +wbtc_prices as ( + select + date_trunc('minute', timestamp) as minute, + price + from dune.openblocklabs.result_starknet_prices_daily + where contract_address = 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac -- WBTC +), + +add_price as ( + select + wf.minute, + evt_tx_hash, + fee, + coalesce(wp.price, 100000) as price, + fee * coalesce(wp.price, 100000) as fee_usd + from + wbtc_fee wf + left join + wbtc_prices wp + on wf.minute = wp.minute +), + +-- Extract USDU mints (Transfer events from 0x0) +-- Transfer event: from (keys[2]), to (keys[3]), amount (data[1]) +interest_rewards as ( + select + case + when keys[3] = (select stability_pool from cfg) then 'SP' + when keys[3] = (select interest_router from cfg) then 'Interest Router' + end as recipient_type, + date_trunc('day', block_time) as day, + sum(cast(bytearray_to_uint256(data[1]) as double) / 1e18) as usdu_amount + from starknet.events + where from_address = (select usdu_token from cfg) + and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 -- Transfer + and keys[2] = 0x0000000000000000000000000000000000000000 -- from = 0x0 (mint) + and keys[3] in ( + (select stability_pool from cfg), + (select interest_router from cfg) + ) + and block_number >= (select deployment_block from cfg) + group by 1, 2 +), + +all_interest as ( + select + day, + case + when recipient_type = 'SP' then 'SP Yield' + else 'Interest Router Yield' + end as fee_type, + sum(usdu_amount) as fee + from + interest_rewards + where recipient_type in ('SP', 'Interest Router') + group by 1, 2 + + union all + + select + date_trunc('day', minute) as day, + 'Redemption Fees' as fee_type, + sum(fee_usd) as fee + from + add_price + group by 1, 2 +), + +get_next_day as ( + select + *, + sum(fee) over (partition by fee_type order by day asc) as fee_total, + lead(day, 1, current_timestamp) over (partition by fee_type order by day asc) as next_day + from + all_interest +) + +select + day, + fee_type, + fee, + fee_total, + sum(fee_total/1e3) over (partition by day) as fee_total_all, + sum(case + when day > date_trunc('day', now()) - interval '1' day then fee/1e3 + else 0 + end) over (order by day) as fee_param +from ( +select + b.*, + coalesce(c.fee, 0) as fee +from ( +select + d.day, + c.fee_type, + c.fee_total +from +get_next_day c +inner join +days d + on c.day <= d.day + and d.day < c.next_day +) b +left join +get_next_day c + on b.day = c.day + and b.fee_type = c.fee_type +) +where day >= (case + when '{{time_period}}' = 'all time' then (select start_date from deployment_date) + when '{{time_period}}' = '1 year' then date_trunc('day', now() - interval '1' year) + when '{{time_period}}' = '6 months' then date_trunc('day', now() - interval '6' month) + when '{{time_period}}' = '3 months' then date_trunc('day', now() - interval '3' month) + when '{{time_period}}' = '1 month' then date_trunc('day', now() - interval '1' month) + end) +order by day desc diff --git a/example_queries/token_holders.sql b/example_queries/token_holders.sql new file mode 100644 index 0000000..7340d88 --- /dev/null +++ b/example_queries/token_holders.sql @@ -0,0 +1,110 @@ +-- Uncap Protocol - USDU Token Holders Query +-- Network: Starknet Mainnet +-- Purpose: Track daily USDU token holder balances over time + +with + +-- Configuration +cfg as ( + select + 2759369 as deployment_block, + 0x04695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c as usdu_token_address +), + +-- Extract Transfer events from Starknet +-- Transfer event structure on Starknet: +-- keys[1] = event selector (0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9) +-- keys[2] = from_address +-- keys[3] = to_address +-- data[1], data[2] = amount (u256 as low, high) +erc20_aggregated as ( + select + day, + address, + token_address, + token_symbol, + sum(amount) as amount + from ( + -- Incoming transfers (to address) + select + date_trunc('day', block_time) as day, + keys[3] as address, + from_address as token_address, + 'USDU' as token_symbol, + sum(cast(bytearray_to_uint256(data[1]) as double) / 1e18) as amount + from starknet.events + where from_address = (select usdu_token_address from cfg) + and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 + and block_number >= (select deployment_block from cfg) + group by 1, 2, 3, 4 + + union all + + -- Outgoing transfers (from address) + select + date_trunc('day', block_time) as day, + keys[2] as address, + from_address as token_address, + 'USDU' as token_symbol, + -sum(cast(bytearray_to_uint256(data[1]) as double) / 1e18) as amount + from starknet.events + where from_address = (select usdu_token_address from cfg) + and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 + and block_number >= (select deployment_block from cfg) + group by 1, 2, 3, 4 + ) x + group by 1, 2, 3, 4 +), + +cum_aggregated as ( + select + day, + address, + token_address, + token_symbol, + sum(amount) over (partition by address, token_address, token_symbol order by day asc) as token_balance, + lead(day, 1, current_timestamp) over (partition by address, token_address, token_symbol order by day asc) as next_day + from + erc20_aggregated +), + +-- Get deployment date from first block +deployment_date as ( + select min(date_trunc('day', time)) as start_date + from starknet.blocks + where number >= (select deployment_block from cfg) +), + +time_seq AS ( + select + sequence( + CAST((select start_date from deployment_date) as timestamp), + date_trunc('day', cast(now() as timestamp)), + interval '1' day + ) as time +), + +days AS ( + select + time.time as day + from time_seq + cross join unnest(time) as time(time) +) + +select + * +from ( +select + d.day, + c.address, + c.token_address, + c.token_symbol, + c.token_balance +from +cum_aggregated c +inner join +days d + on c.day <= d.day + and d.day < c.next_day +) +where token_balance > 0.001 -- Exclude users who hold dust diff --git a/example_queries/total_usdu_supply.sql b/example_queries/total_usdu_supply.sql new file mode 100644 index 0000000..4bdc9f9 --- /dev/null +++ b/example_queries/total_usdu_supply.sql @@ -0,0 +1,15 @@ +-- Uncap Protocol - Total USDU Supply +-- Network: Starknet Mainnet +-- Purpose: Aggregate daily holder count and total supply from token_holders query + +select + day, + token_address, + token_symbol, + count(distinct(address)) as num_holders, + sum(token_balance) as token_balance, + sum(token_balance) as total_supply_usd -- USDU is a stablecoin, 1 USDU ≈ $1 +from +query_5904342 +group by 1, 2, 3 +order by 1 desc diff --git a/example_queries/trove_debt_and_interest.sql b/example_queries/trove_debt_and_interest.sql new file mode 100644 index 0000000..92d4d76 --- /dev/null +++ b/example_queries/trove_debt_and_interest.sql @@ -0,0 +1,39 @@ +-- Uncap Protocol - Trove Debt and Interest Rates +-- Network: Starknet Mainnet +-- Purpose: Track debt and interest rates for all troves over time +-- Simplified version (no batch management) equivalent to query_5720005 for Liquity v2 + +with + +cfg as ( + select + 0x06dcfc4ce8ca0c664d5f6c5ee434c9379d15e616246db092cd9e21e5871e4377 as events_emitter, + 2759369 as deployment_block +) + +select + 'WBTC' as collateral_type, + transaction_hash as tx_hash, + event_index as tx_index, + block_time, + block_number, + -- Extract trove_id (u256 = data[1] low, data[2] high) + lower(concat('0x', + "right"(substring(to_hex(data[2]), 3), 32), + "right"(substring(to_hex(data[1]), 3), 32) + )) as trove_id, + -- Extract debt (u256 = data[3] low, data[4] high) + cast(bytearray_to_uint256(data[3]) as double) / 1e18 as debt, + -- Extract collateral (u256 = data[5] low, data[6] high) + cast(bytearray_to_uint256(data[5]) as double) / 1e18 as collateral, + -- Extract stake (u256 = data[7] low, data[8] high) + cast(bytearray_to_uint256(data[7]) as double) / 1e18 as stake, + -- Extract annual interest rate (u256 = data[9] low, data[10] high) + cast(bytearray_to_uint256(data[9]) as double) / 1e16 as interest_rates, + 0x0000000000000000000000000000000000000000 as interest_batch_manager, -- No batch manager + 'troveUpdated' as event +from starknet.events +where from_address = (select events_emitter from cfg) + and keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 + and block_number >= (select deployment_block from cfg) +order by block_number desc, event_index desc diff --git a/example_queries/trove_operations.sql b/example_queries/trove_operations.sql new file mode 100644 index 0000000..9ac6486 --- /dev/null +++ b/example_queries/trove_operations.sql @@ -0,0 +1,40 @@ +-- Uncap Protocol - Trove Operations +-- Network: Starknet Mainnet +-- Purpose: Track all trove operations (open, close, adjust, etc.) +-- Equivalent to query_5155365 for Liquity v2 + +with + +cfg as ( + select + 0x06dcfc4ce8ca0c664d5f6c5ee434c9379d15e616246db092cd9e21e5871e4377 as events_emitter, + 2759369 as deployment_block +) + +select + from_address as contract_address, + 'WBTC' as collateral_type, + transaction_hash as tx_hash, + event_index as tx_index, + block_time, + block_number, + -- Extract trove_id (u256 = data[1] low, data[2] high) + lower(concat('0x', + "right"(substring(to_hex(data[2]), 3), 32), + "right"(substring(to_hex(data[1]), 3), 32) + )) as trove_id, + -- Extract operation type (u8 stored as felt = data[3]) + cast(bytearray_to_uint256(data[3]) as bigint) as operation, + -- Extract debt change (u256 = data[4] low, data[5] high) + cast(bytearray_to_uint256(data[4]) as double) / 1e18 as debt_change, + -- Extract upfront fee (u256 = data[6] low, data[7] high) + cast(bytearray_to_uint256(data[6]) as double) / 1e18 as upfront_fees, + -- Extract collateral change (u256 = data[8] low, data[9] high) + cast(bytearray_to_uint256(data[8]) as double) / 1e18 as collateral_change, + -- Extract annual interest rate (u256 = data[10] low, data[11] high) + cast(bytearray_to_uint256(data[10]) as double) / 1e16 as interest_rates +from starknet.events +where from_address = (select events_emitter from cfg) + and keys[1] = 0x02c0b995ca82e6ae9ef5d19fd0659d7748540971842bd71843cfc904d240c4bf + and block_number >= (select deployment_block from cfg) +order by block_number desc, event_index desc diff --git a/example_queries/tvl.sql b/example_queries/tvl.sql new file mode 100644 index 0000000..21b803d --- /dev/null +++ b/example_queries/tvl.sql @@ -0,0 +1,132 @@ +-- Uncap Protocol - Total Value Locked (TVL) +-- Network: Starknet Mainnet +-- Purpose: Track total value locked in WBTC collateral and USDU in StabilityPool + +with + +-- Get deployment date for time series +deployment_date as ( + select min(date_trunc('day', time)) as start_date + from starknet.blocks + where number >= 2759369 +), + +troves as ( + select + date_trunc('day', block_time) as day, + collateral_type, + max_by(collateral_balance, (block_number, tx_index)) as balance + + from + query_5904577 -- active_pool_updates query + group by 1, 2 +), + +missing_days as ( + select + *, + lead(day, 1, current_timestamp) over (partition by collateral_type order by day asc) as next_day + from + troves +), + +time_seq AS ( + select + sequence( + CAST((select start_date from deployment_date) as timestamp), + date_trunc('day', cast(now() as timestamp)), + interval '1' day + ) as time +), + +days AS ( + select + time.time as day + from time_seq + cross join unnest(time) as time(time) +), + +active_pools as ( + select + day, + collateral_type, + balance + from ( + select + d.day, + c.collateral_type, + c.balance + from + missing_days c + inner join + days d + on c.day <= d.day + and d.day < c.next_day + ) + where balance > 0 +), + +-- Get WBTC prices +wbtc_prices as ( + select + DATE(timestamp AT TIME ZONE 'UTC') as day, + price as wbtc_price + from dune.openblocklabs.result_starknet_prices_daily + where contract_address = 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac -- WBTC on Starknet +), + +-- Get latest WBTC price for fallback +latest_wbtc_price as ( + select max(wbtc_price) as latest_price + from wbtc_prices +), + +-- Calculate collateral TVL in USD and WWBTC +enrich_tvl as ( + select + day, + sum(balance_usd) as tvl_usd, + sum(balance_btc) as tvl_btc + from ( + select + ap.*, + ap.balance * coalesce(wp.wbtc_price, lwp.latest_price) as balance_usd, + ap.balance as balance_btc -- Balance already normalized from active_pool_updates + from + active_pools ap + left join + wbtc_prices wp + on ap.day = wp.day + cross join + latest_wbtc_price lwp + ) + group by 1 +), + +-- USDU in StabilityPool +usdu_tvl as ( + select + day, + sum(token_balance) as usdu_tvl + from + query_5904342 -- token_holders query + where address = 0x001ba4a9e2e86a41c6ed15016eda0404d12bf7b01052cccff1ace84d818335c7 -- WWBTC StabilityPool + group by 1 +) + +select + et.day, + et.tvl_usd as collateral_tvl_usd, + et.tvl_btc as collateral_tvl_btc, + coalesce(ut.usdu_tvl, 0) as usdu_in_sp, + et.tvl_usd + coalesce(ut.usdu_tvl, 0) as total_tvl_usd, + et.tvl_btc as total_tvl_btc +from +enrich_tvl et +left join +usdu_tvl ut + on et.day = ut.day +order by et.day desc + + + diff --git a/example_queries/usdu_holders_no_dust.sql b/example_queries/usdu_holders_no_dust.sql new file mode 100644 index 0000000..0150e43 --- /dev/null +++ b/example_queries/usdu_holders_no_dust.sql @@ -0,0 +1,110 @@ +-- Uncap Protocol - USDU Token Holders Query +-- Network: Starknet Mainnet +-- Purpose: Track daily USDU token holder balances over time + +with + +-- Configuration +cfg as ( + select + 2759669 as deployment_block, + 0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 as usdu_token_address +), + +-- Extract Transfer events from Starknet +-- Transfer event structure on Starknet: +-- keys[1] = event selector (0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9) +-- keys[2] = from_address +-- keys[3] = to_address +-- data[1], data[2] = amount (u256 as low, high) +erc20_aggregated as ( + select + day, + address, + token_address, + token_symbol, + sum(amount) as amount + from ( + -- Incoming transfers (to address) + select + date_trunc('day', block_time) as day, + keys[3] as address, + from_address as token_address, + 'USDU' as token_symbol, + sum(cast(bytearray_to_uint256(data[1]) as double) / 1e18) as amount + from starknet.events + where from_address = (select usdu_token_address from cfg) + and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 + and block_number >= (select deployment_block from cfg) + group by 1, 2, 3, 4 + + union all + + -- Outgoing transfers (from address) + select + date_trunc('day', block_time) as day, + keys[2] as address, + from_address as token_address, + 'USDU' as token_symbol, + -sum(cast(bytearray_to_uint256(data[1]) as double) / 1e18) as amount + from starknet.events + where from_address = (select usdu_token_address from cfg) + and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 + and block_number >= (select deployment_block from cfg) + group by 1, 2, 3, 4 + ) x + group by 1, 2, 3, 4 +), + +cum_aggregated as ( + select + day, + address, + token_address, + token_symbol, + sum(amount) over (partition by address, token_address, token_symbol order by day asc) as token_balance, + lead(day, 1, current_timestamp) over (partition by address, token_address, token_symbol order by day asc) as next_day + from + erc20_aggregated +), + +-- Get deployment date from first block +deployment_date as ( + select min(date_trunc('day', time)) as start_date + from starknet.blocks + where number >= (select deployment_block from cfg) +), + +time_seq AS ( + select + sequence( + CAST((select start_date from deployment_date) as timestamp), + date_trunc('day', cast(now() as timestamp)), + interval '1' day + ) as time +), + +days AS ( + select + time.time as day + from time_seq + cross join unnest(time) as time(time) +) + +select + * +from ( +select + d.day, + c.address, + c.token_address, + c.token_symbol, + c.token_balance +from +cum_aggregated c +inner join +days d + on c.day <= d.day + and d.day < c.next_day +) +where token_balance > 0.001 -- Exclude users who hold dust diff --git a/scripts/utils.ts b/scripts/utils.ts index 3ce0934..fedc492 100644 --- a/scripts/utils.ts +++ b/scripts/utils.ts @@ -37,32 +37,6 @@ export function serializeArray( return result; } -export async function invokeContract( - context: DeploymentContext, - contractAddress: string, - selector: string, - calldata: string[] = [] -): Promise { - const { account } = context; - - log(`Invoking ${selector} on ${contractAddress}`); - log(`Calldata:`, calldata); - - try { - const result = await account.execute({ - contractAddress, - entrypoint: selector, - calldata, - }); - - log(`Invoke result:`, result); - return result; - } catch (error) { - log(`Error invoking ${selector}:`, error); - throw error; - } -} - export function saveDeploymentAddresses( addresses: ContractAddresses, filename: string = 'deployment_addresses.json' From 74c105fa9138d79122c8c0777c11caac0dea830a Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Mon, 20 Oct 2025 09:19:31 +0200 Subject: [PATCH 03/14] hourly snapshots --- example_queries/trove_hourly_snapshots.sql | 423 +++++++++++++++++++++ 1 file changed, 423 insertions(+) create mode 100644 example_queries/trove_hourly_snapshots.sql diff --git a/example_queries/trove_hourly_snapshots.sql b/example_queries/trove_hourly_snapshots.sql new file mode 100644 index 0000000..e2c97f1 --- /dev/null +++ b/example_queries/trove_hourly_snapshots.sql @@ -0,0 +1,423 @@ +-- Uncap Protocol - 6-Hourly Trove Snapshots +-- Network: Starknet Mainnet +-- Purpose: Track debt, collateral, and interest rate for each trove every 6 hours + +with + +cfg as ( + select + 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 as events_emitter, + 0x04a1e17f1e4d5fdd839fa6a4c52a742b9d10871028b6ffcfb2f1de8dd5dc2cd4 as trove_nft, + 0x001ba4a9e2e86a41c6ed15016eda0404d12bf7b01052cccff1ace84d818335c7 as stability_pool, + 0x00000005dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b as ekubo_core, + 0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 as usdu_token, + 0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8 as usdc_token, + 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac as wbtc_price_addr, + 2759369 as deployment_block +), + +-- Get deployment date for time series (rounded to nearest 6-hour boundary) +deployment_date as ( + select date_trunc('hour', min(time)) - + (cast(extract(hour from min(time)) as integer) % 6) * interval '1' hour as start_hour + from starknet.blocks + where number >= (select deployment_block from cfg) +), + +-- Get all trove update events with their state +trove_events as ( + select + date_trunc('hour', block_time) as hour, + block_time, + block_number, + event_index, + -- Extract trove_id (u256 = data[1] low, data[2] high) + lower(concat('0x', + "right"(substring(to_hex(data[2]), 3), 32), + "right"(substring(to_hex(data[1]), 3), 32) + )) as trove_id, + -- Extract debt (u256 = data[3] low, data[4] high) + cast(bytearray_to_uint256(data[3]) as double) / 1e18 as debt, + -- Extract collateral (u256 = data[5] low, data[6] high) + cast(bytearray_to_uint256(data[5]) as double) / 1e18 as collateral, + -- Extract stake (u256 = data[7] low, data[8] high) + cast(bytearray_to_uint256(data[7]) as double) / 1e18 as stake, + -- Extract annual interest rate (u256 = data[9] low, data[10] high) + cast(bytearray_to_uint256(data[9]) as double) / 1e16 as interest_rate + from starknet.events + where from_address = (select events_emitter from cfg) + and keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 + and block_number >= (select deployment_block from cfg) +), + +-- Get NFT transfer events to track ownership +nft_transfers as ( + select + block_time, + block_number, + event_index, + -- Extract to address (new owner) from keys[3] + keys[3] as owner, + -- Extract token_id (u256 = keys[4] low, keys[5] high) + -- Token ID is indexed in the Transfer event, so it's in keys not data + lower(concat('0x', + "right"(substring(to_hex(keys[5]), 3), 32), + "right"(substring(to_hex(keys[4]), 3), 32) + )) as trove_id + from starknet.events + where from_address = (select trove_nft from cfg) + and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 + and block_number >= (select deployment_block from cfg) +), + +-- Get StabilityPool deposit events +-- Event: DepositUpdated(depositor, new_deposit, stashed_coll, snapshot_p, snapshot_s, snapshot_b, snapshot_scale) +-- This event fires whenever a user's deposit changes and contains their current balance +sp_deposit_events as ( + select + block_time, + block_number, + event_index, + -- Depositor address (ContractAddress = felt252 in data[1]) + data[1] as user_address, + -- New deposit amount (u256 = data[2] low, data[3] high) + -- This is the user's current deposit balance after the operation + cast(bytearray_to_uint256(data[2]) as double) / 1e18 as deposit_amount + from starknet.events + where from_address = (select stability_pool from cfg) + -- DepositUpdated event selector + and keys[1] = 0x0056985e064b256ecdb949713158ae0953473139aaf1fd57197a852fe36c4f00 + and block_number >= (select deployment_block from cfg) +), + +-- Get Ekubo LP position events +-- Event: PositionUpdated(locker, pool_key, params, delta) +ekubo_position_events as ( + select + e.block_time, + e.block_number, + e.event_index, + e.transaction_hash, + -- Extract delta amounts (sign determines if adding or removing liquidity) + -- delta.amount0 (USDU) + cast(bytearray_to_uint256(e.data[14]) as double) as amount0_mag, + cast(bytearray_to_uint256(e.data[15]) as bigint) as amount0_sign, + -- delta.amount1 (USDC) + cast(bytearray_to_uint256(e.data[16]) as double) as amount1_mag, + cast(bytearray_to_uint256(e.data[17]) as bigint) as amount1_sign + from starknet.events e + cross join cfg + where e.from_address = cfg.ekubo_core + -- PositionUpdated event selector + and e.keys[1] = 0x03a7adca3546c213ce791fabf3b04090c163e419c808c9830fb343a4a395946e + and e.block_number >= cfg.deployment_block + -- Filter by pool_key criteria + and e.data[2] = cfg.usdu_token -- pool_key.token0 (USDU) + and e.data[3] = cfg.usdc_token -- pool_key.token1 (USDC) + and cast(bytearray_to_uint256(e.data[5]) as bigint) = 100 -- tick_spacing = 0x64 = 100 + -- Filter by params.bounds criteria + and cast(bytearray_to_uint256(e.data[9]) as bigint) = 1 -- lower.sign = 0x1 + and cast(bytearray_to_uint256(e.data[8]) as bigint) < 27682400 -- lower.mag < 27682400 + and cast(bytearray_to_uint256(e.data[11]) as bigint) = 1 -- upper.sign = 0x1 + and cast(bytearray_to_uint256(e.data[10]) as bigint) > 27581700 -- upper.mag > 27581700 +), + +-- Get transaction senders for LP events +ekubo_lp_deltas as ( + select + epe.block_time, + epe.block_number, + epe.event_index, + t.sender_address as user_address, + -- Calculate LP value change in USD + -- amount0 is USDU (18 decimals), amount1 is USDC (6 decimals) + -- Apply sign: 0 = positive (add), 1 = negative (remove) + (case when epe.amount0_sign = 0 then epe.amount0_mag / 1e18 else -epe.amount0_mag / 1e18 end + + case when epe.amount1_sign = 0 then epe.amount1_mag / 1e6 else -epe.amount1_mag / 1e6 end) as lp_delta_usd + from ekubo_position_events epe + inner join starknet.transactions t + on epe.transaction_hash = t.hash + and epe.block_number = t.block_number +), + +-- Create 6-hourly time series +time_seq as ( + select + sequence( + cast((select start_hour from deployment_date) as timestamp), + date_trunc('hour', cast(now() as timestamp)), + interval '6' hour + ) as time +), + +six_hour_slots as ( + select + time.time as slot_start + from time_seq + cross join unnest(time) as time(time) +), + +-- Get all unique troves +troves as ( + select distinct trove_id + from trove_events +), + +-- Get all unique users (from troves, SP, and Ekubo LP) +all_users as ( + select distinct owner as user_address + from nft_transfers + where owner != 0x0000000000000000000000000000000000000000 + union + select distinct user_address + from sp_deposit_events + union + select distinct user_address + from ekubo_lp_deltas +), + +-- Cross join 6-hour slots and troves to get all combinations +slot_trove_combinations as ( + select + s.slot_start, + t.trove_id + from six_hour_slots s + cross join troves t +), + +-- Cross join 6-hour slots and users for SP tracking +slot_user_combinations as ( + select + s.slot_start, + u.user_address + from six_hour_slots s + cross join all_users u +), + +-- For each 6-hour slot and trove, get the most recent trove state before the end of that slot +latest_states as ( + select + stc.slot_start, + stc.trove_id, + te.debt, + te.collateral, + te.stake, + te.interest_rate, + row_number() over ( + partition by stc.slot_start, stc.trove_id + order by te.block_time desc, te.event_index desc + ) as rn + from slot_trove_combinations stc + left join trove_events te + on stc.trove_id = te.trove_id + and te.block_time <= stc.slot_start + interval '6' hour -- Include events in this 6-hour slot + where te.trove_id is not null -- Only include if we found an event +), + +-- For each 6-hour slot and trove, get the most recent owner before the end of that slot +latest_owners as ( + select + stc.slot_start, + stc.trove_id, + nt.owner, + row_number() over ( + partition by stc.slot_start, stc.trove_id + order by nt.block_time desc, nt.event_index desc + ) as rn + from slot_trove_combinations stc + left join nft_transfers nt + on stc.trove_id = nt.trove_id + and nt.block_time <= stc.slot_start + interval '6' hour -- Include events in this 6-hour slot + where nt.trove_id is not null -- Only include if we found a transfer +), + +-- For each 6-hour slot and user, get the most recent SP deposit before the end of that slot +latest_sp_deposits as ( + select + suc.slot_start, + suc.user_address, + sp.deposit_amount, + row_number() over ( + partition by suc.slot_start, suc.user_address + order by sp.block_time desc, sp.event_index desc + ) as rn + from slot_user_combinations suc + left join sp_deposit_events sp + on suc.user_address = sp.user_address + and sp.block_time <= suc.slot_start + interval '6' hour + where sp.user_address is not null -- Only include if we found a deposit event +), + +-- Calculate cumulative LP positions per user over time +lp_cumulative as ( + select + block_time, + user_address, + sum(lp_delta_usd) over ( + partition by user_address + order by block_time, event_index + rows between unbounded preceding and current row + ) as cumulative_lp_usd + from ekubo_lp_deltas +), + +-- For each 6-hour slot and user, get the most recent LP position +latest_lp_positions as ( + select + suc.slot_start, + suc.user_address, + lp.cumulative_lp_usd, + row_number() over ( + partition by suc.slot_start, suc.user_address + order by lp.block_time desc + ) as rn + from slot_user_combinations suc + left join lp_cumulative lp + on suc.user_address = lp.user_address + and lp.block_time <= suc.slot_start + interval '6' hour + where lp.user_address is not null -- Only include if we found LP activity +), + +-- Combine trove state and ownership +trove_snapshots as ( + select + ls.slot_start, + ls.trove_id, + lo.owner as user_address, + ls.debt, + ls.collateral, + ls.stake, + ls.interest_rate + from latest_states ls + left join latest_owners lo + on ls.slot_start = lo.slot_start + and ls.trove_id = lo.trove_id + and lo.rn = 1 + where ls.rn = 1 +), + +-- Get users with positions but no troves (SP and/or LP only) +non_trove_users as ( + select distinct + suc.slot_start, + suc.user_address, + coalesce(lsd.deposit_amount, 0) as sp_deposit, + coalesce(llp.cumulative_lp_usd, 0) as lp_value + from slot_user_combinations suc + left join latest_sp_deposits lsd + on suc.user_address = lsd.user_address + and suc.slot_start = lsd.slot_start + and lsd.rn = 1 + left join latest_lp_positions llp + on suc.user_address = llp.user_address + and suc.slot_start = llp.slot_start + and llp.rn = 1 + where not exists ( + select 1 + from trove_snapshots ts + where ts.user_address = suc.user_address + and ts.slot_start = suc.slot_start + ) + and (coalesce(lsd.deposit_amount, 0) > 0 or coalesce(llp.cumulative_lp_usd, 0) > 0) +), + +-- Combine trove snapshots with SP and LP deposits +final_snapshots as ( + -- Trove owners with their SP and LP positions + select + ts.slot_start, + ts.trove_id, + ts.user_address, + ts.debt, + ts.collateral, + ts.interest_rate, + coalesce(lsd.deposit_amount, 0) as sp_deposit, + coalesce(llp.cumulative_lp_usd, 0) as lp_value + from trove_snapshots ts + left join latest_sp_deposits lsd + on ts.user_address = lsd.user_address + and ts.slot_start = lsd.slot_start + and lsd.rn = 1 + left join latest_lp_positions llp + on ts.user_address = llp.user_address + and ts.slot_start = llp.slot_start + and llp.rn = 1 + + union all + + -- Users with only SP/LP positions (no troves) + select + slot_start, + null as trove_id, + user_address, + 0 as debt, + 0 as collateral, + 0 as interest_rate, + sp_deposit, + lp_value + from non_trove_users +), + +-- Get WBTC prices (daily) +daily_prices as ( + select + date(timestamp at time zone 'UTC') as day, + price as wbtc_price + from dune.openblocklabs.result_starknet_prices_daily + cross join cfg + where contract_address = cfg.wbtc_price_addr +), + +-- Get latest WBTC price for fallback +latest_price as ( + select price as wbtc_price + from ( + select price, + row_number() over (order by timestamp desc) as rn + from dune.openblocklabs.result_starknet_prices_daily + cross join cfg + where contract_address = cfg.wbtc_price_addr + ) t + where rn = 1 +), + +-- Combine snapshots with prices +snapshots_with_prices as ( + select + fs.slot_start, + fs.trove_id, + fs.user_address, + fs.collateral as collateral_btc, + coalesce(dp.wbtc_price, lp.wbtc_price) as wbtc_price, + fs.collateral * coalesce(dp.wbtc_price, lp.wbtc_price) as collateral_usd, + fs.debt, + fs.interest_rate, + fs.sp_deposit, + fs.lp_value + from final_snapshots fs + left join daily_prices dp + on date(fs.slot_start) = dp.day + cross join latest_price lp +) + +select + slot_start as snapshot_time, + trove_id, + user_address as owner, + collateral_btc, + collateral_usd, + debt, + -- Collateral ratio: debt / collateral in USD + case + when collateral_usd > 0 then debt / collateral_usd + else null + end as collateral_ratio, + interest_rate, + sp_deposit as in_stability_pool, + lp_value as lp_value_usd, + wbtc_price +from snapshots_with_prices +where (debt > 0 or sp_deposit > 0 or lp_value > 0) -- Show active troves or users with SP/LP deposits + -- OPTIONAL: Uncomment to filter to last N days for weekly reports + -- and slot_start >= current_timestamp - interval '7' day +order by snapshot_time desc, owner asc, trove_id asc From be30e05c2e7d173f803cc645b99c0ba9f4853f8b Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:28:58 +0100 Subject: [PATCH 04/14] trove hourly snapshots query --- example_queries/trove_hourly_snapshots.sql | 278 +++++++++++++++------ 1 file changed, 196 insertions(+), 82 deletions(-) diff --git a/example_queries/trove_hourly_snapshots.sql b/example_queries/trove_hourly_snapshots.sql index e2c97f1..f302fbf 100644 --- a/example_queries/trove_hourly_snapshots.sql +++ b/example_queries/trove_hourly_snapshots.sql @@ -10,6 +10,7 @@ cfg as ( 0x04a1e17f1e4d5fdd839fa6a4c52a742b9d10871028b6ffcfb2f1de8dd5dc2cd4 as trove_nft, 0x001ba4a9e2e86a41c6ed15016eda0404d12bf7b01052cccff1ace84d818335c7 as stability_pool, 0x00000005dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b as ekubo_core, + 0x07b696af58c967c1b14c9dde0ace001720635a660a8e90c565ea459345318b30 as ekubo_positions_nft, 0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 as usdu_token, 0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8 as usdc_token, 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac as wbtc_price_addr, @@ -90,54 +91,124 @@ sp_deposit_events as ( and block_number >= (select deployment_block from cfg) ), --- Get Ekubo LP position events --- Event: PositionUpdated(locker, pool_key, params, delta) -ekubo_position_events as ( +-- Track Ekubo NFT position transfers to know ownership +-- Transfer event: data = [from, to, token_id_low, token_id_high] +ekubo_position_transfers as ( select - e.block_time, - e.block_number, - e.event_index, - e.transaction_hash, - -- Extract delta amounts (sign determines if adding or removing liquidity) - -- delta.amount0 (USDU) - cast(bytearray_to_uint256(e.data[14]) as double) as amount0_mag, - cast(bytearray_to_uint256(e.data[15]) as bigint) as amount0_sign, - -- delta.amount1 (USDC) - cast(bytearray_to_uint256(e.data[16]) as double) as amount1_mag, - cast(bytearray_to_uint256(e.data[17]) as bigint) as amount1_sign - from starknet.events e + block_time, + block_number, + event_index, + transaction_hash, + data[1] as from_address, + data[2] as to_address, + lower(to_hex(data[3])) as position_id + from starknet.events + cross join cfg + where from_address = cfg.ekubo_positions_nft + and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 + and block_number >= cfg.deployment_block +), + +-- Track Ekubo position liquidity updates +-- PositionUpdated event: data[7] = salt (position_id), data[14-17] = delta amounts +ekubo_position_updates as ( + select + block_time, + block_number, + event_index, + transaction_hash, + lower(to_hex(data[7])) as position_id, + data[1] as locker_address, + cast(bytearray_to_uint256(data[14]) as double) as amount0_mag, + cast(bytearray_to_uint256(data[15]) as bigint) as amount0_sign, + cast(bytearray_to_uint256(data[16]) as double) as amount1_mag, + cast(bytearray_to_uint256(data[17]) as bigint) as amount1_sign, + data[2] as token0, + data[3] as token1, + cast(bytearray_to_uint256(data[5]) as bigint) as tick_spacing, + cast(bytearray_to_uint256(data[8]) as bigint) as lower_mag, + cast(bytearray_to_uint256(data[9]) as bigint) as lower_sign, + cast(bytearray_to_uint256(data[10]) as bigint) as upper_mag, + cast(bytearray_to_uint256(data[11]) as bigint) as upper_sign + from starknet.events cross join cfg - where e.from_address = cfg.ekubo_core - -- PositionUpdated event selector - and e.keys[1] = 0x03a7adca3546c213ce791fabf3b04090c163e419c808c9830fb343a4a395946e - and e.block_number >= cfg.deployment_block - -- Filter by pool_key criteria - and e.data[2] = cfg.usdu_token -- pool_key.token0 (USDU) - and e.data[3] = cfg.usdc_token -- pool_key.token1 (USDC) - and cast(bytearray_to_uint256(e.data[5]) as bigint) = 100 -- tick_spacing = 0x64 = 100 - -- Filter by params.bounds criteria - and cast(bytearray_to_uint256(e.data[9]) as bigint) = 1 -- lower.sign = 0x1 - and cast(bytearray_to_uint256(e.data[8]) as bigint) < 27682400 -- lower.mag < 27682400 - and cast(bytearray_to_uint256(e.data[11]) as bigint) = 1 -- upper.sign = 0x1 - and cast(bytearray_to_uint256(e.data[10]) as bigint) > 27581700 -- upper.mag > 27581700 + where from_address = cfg.ekubo_core + and keys[1] = 0x03a7adca3546c213ce791fabf3b04090c163e419c808c9830fb343a4a395946e + and block_number >= cfg.deployment_block + and data[2] = cfg.usdu_token -- USDU + and data[3] = cfg.usdc_token -- USDC + and cast(bytearray_to_uint256(data[5]) as bigint) = 100 -- tick_spacing = 100 +), + +-- Filter positions to relevant price range +ekubo_filtered_positions as ( + select + block_time, + block_number, + event_index, + transaction_hash, + position_id, + locker_address, + amount0_mag, + amount0_sign, + amount1_mag, + amount1_sign + from ekubo_position_updates + where lower_sign = 1 + and lower_mag < 27682400 + and upper_sign = 1 + and upper_mag > 27581700 ), --- Get transaction senders for LP events -ekubo_lp_deltas as ( +-- Calculate liquidity deltas per position (sign: 0 = add, 1 = remove) +ekubo_position_liquidity_changes as ( select - epe.block_time, - epe.block_number, - epe.event_index, - t.sender_address as user_address, - -- Calculate LP value change in USD - -- amount0 is USDU (18 decimals), amount1 is USDC (6 decimals) - -- Apply sign: 0 = positive (add), 1 = negative (remove) - (case when epe.amount0_sign = 0 then epe.amount0_mag / 1e18 else -epe.amount0_mag / 1e18 end + - case when epe.amount1_sign = 0 then epe.amount1_mag / 1e6 else -epe.amount1_mag / 1e6 end) as lp_delta_usd - from ekubo_position_events epe - inner join starknet.transactions t - on epe.transaction_hash = t.hash - and epe.block_number = t.block_number + block_time, + block_number, + event_index, + transaction_hash, + position_id, + locker_address, + (case when amount0_sign = 0 then amount0_mag / 1e18 else -amount0_mag / 1e18 end) as usdu_delta, + (case when amount1_sign = 0 then amount1_mag / 1e6 else -amount1_mag / 1e6 end) as usdc_delta, + (case when amount0_sign = 0 then amount0_mag / 1e18 else -amount0_mag / 1e18 end + + case when amount1_sign = 0 then amount1_mag / 1e6 else -amount1_mag / 1e6 end) as total_usd_delta + from ekubo_filtered_positions +), + +-- Calculate cumulative liquidity per position over time +ekubo_position_cumulative as ( + select + block_time, + block_number, + event_index, + position_id, + locker_address, + usdu_delta, + usdc_delta, + total_usd_delta, + sum(usdu_delta) over ( + partition by position_id + order by block_time, event_index + rows between unbounded preceding and current row + ) as cumulative_usdu, + sum(usdc_delta) over ( + partition by position_id + order by block_time, event_index + rows between unbounded preceding and current row + ) as cumulative_usdc, + sum(total_usd_delta) over ( + partition by position_id + order by block_time, event_index + rows between unbounded preceding and current row + ) as cumulative_usd_value + from ekubo_position_liquidity_changes +), + +-- Get all unique Ekubo positions +ekubo_positions as ( + select distinct position_id + from ekubo_position_cumulative ), -- Create 6-hourly time series @@ -172,8 +243,9 @@ all_users as ( select distinct user_address from sp_deposit_events union - select distinct user_address - from ekubo_lp_deltas + select distinct to_address as user_address + from ekubo_position_transfers + where to_address != 0x0000000000000000000000000000000000000000 ), -- Cross join 6-hour slots and troves to get all combinations @@ -248,34 +320,68 @@ latest_sp_deposits as ( where sp.user_address is not null -- Only include if we found a deposit event ), --- Calculate cumulative LP positions per user over time -lp_cumulative as ( +-- Cross join 6-hour slots and Ekubo positions for time-series tracking +slot_position_combinations as ( select - block_time, - user_address, - sum(lp_delta_usd) over ( - partition by user_address - order by block_time, event_index - rows between unbounded preceding and current row - ) as cumulative_lp_usd - from ekubo_lp_deltas + s.slot_start, + p.position_id + from six_hour_slots s + cross join ekubo_positions p ), --- For each 6-hour slot and user, get the most recent LP position -latest_lp_positions as ( +-- For each time slot and position, get the most recent liquidity state +latest_position_liquidity as ( select - suc.slot_start, - suc.user_address, - lp.cumulative_lp_usd, + spc.slot_start, + spc.position_id, + epc.cumulative_usdu, + epc.cumulative_usdc, + epc.cumulative_usd_value, row_number() over ( - partition by suc.slot_start, suc.user_address - order by lp.block_time desc + partition by spc.slot_start, spc.position_id + order by epc.block_time desc, epc.event_index desc ) as rn - from slot_user_combinations suc - left join lp_cumulative lp - on suc.user_address = lp.user_address - and lp.block_time <= suc.slot_start + interval '6' hour - where lp.user_address is not null -- Only include if we found LP activity + from slot_position_combinations spc + left join ekubo_position_cumulative epc + on spc.position_id = epc.position_id + and epc.block_time <= spc.slot_start + interval '6' hour + where epc.position_id is not null +), + +-- For each time slot and position, get the most recent owner +latest_position_owners as ( + select + spc.slot_start, + spc.position_id, + ept.to_address as owner, + row_number() over ( + partition by spc.slot_start, spc.position_id + order by ept.block_time desc, ept.event_index desc + ) as rn + from slot_position_combinations spc + left join ekubo_position_transfers ept + on spc.position_id = ept.position_id + and ept.block_time <= spc.slot_start + interval '6' hour + and ept.to_address != 0x0000000000000000000000000000000000000000 + where ept.position_id is not null +), + +-- Combine position liquidity and ownership, then aggregate by user +user_lp_positions as ( + select + lpl.slot_start, + lpo.owner as user_address, + sum(lpl.cumulative_usdu) as total_usdu_in_lp, + sum(lpl.cumulative_usdc) as total_usdc_in_lp, + sum(lpl.cumulative_usd_value) as total_lp_value_usd + from latest_position_liquidity lpl + inner join latest_position_owners lpo + on lpl.slot_start = lpo.slot_start + and lpl.position_id = lpo.position_id + and lpo.rn = 1 + where lpl.rn = 1 + and lpl.cumulative_usd_value > 0 + group by lpl.slot_start, lpo.owner ), -- Combine trove state and ownership @@ -302,23 +408,24 @@ non_trove_users as ( suc.slot_start, suc.user_address, coalesce(lsd.deposit_amount, 0) as sp_deposit, - coalesce(llp.cumulative_lp_usd, 0) as lp_value + coalesce(ulp.total_usdu_in_lp, 0) as usdu_in_lp, + coalesce(ulp.total_usdc_in_lp, 0) as usdc_in_lp, + coalesce(ulp.total_lp_value_usd, 0) as lp_value_usd from slot_user_combinations suc left join latest_sp_deposits lsd on suc.user_address = lsd.user_address and suc.slot_start = lsd.slot_start and lsd.rn = 1 - left join latest_lp_positions llp - on suc.user_address = llp.user_address - and suc.slot_start = llp.slot_start - and llp.rn = 1 + left join user_lp_positions ulp + on suc.user_address = ulp.user_address + and suc.slot_start = ulp.slot_start where not exists ( select 1 from trove_snapshots ts where ts.user_address = suc.user_address and ts.slot_start = suc.slot_start ) - and (coalesce(lsd.deposit_amount, 0) > 0 or coalesce(llp.cumulative_lp_usd, 0) > 0) + and (coalesce(lsd.deposit_amount, 0) > 0 or coalesce(ulp.total_lp_value_usd, 0) > 0) ), -- Combine trove snapshots with SP and LP deposits @@ -332,16 +439,17 @@ final_snapshots as ( ts.collateral, ts.interest_rate, coalesce(lsd.deposit_amount, 0) as sp_deposit, - coalesce(llp.cumulative_lp_usd, 0) as lp_value + coalesce(ulp.total_usdu_in_lp, 0) as usdu_in_lp, + coalesce(ulp.total_usdc_in_lp, 0) as usdc_in_lp, + coalesce(ulp.total_lp_value_usd, 0) as lp_value_usd from trove_snapshots ts left join latest_sp_deposits lsd on ts.user_address = lsd.user_address and ts.slot_start = lsd.slot_start and lsd.rn = 1 - left join latest_lp_positions llp - on ts.user_address = llp.user_address - and ts.slot_start = llp.slot_start - and llp.rn = 1 + left join user_lp_positions ulp + on ts.user_address = ulp.user_address + and ts.slot_start = ulp.slot_start union all @@ -354,7 +462,9 @@ final_snapshots as ( 0 as collateral, 0 as interest_rate, sp_deposit, - lp_value + usdu_in_lp, + usdc_in_lp, + lp_value_usd from non_trove_users ), @@ -393,7 +503,9 @@ snapshots_with_prices as ( fs.debt, fs.interest_rate, fs.sp_deposit, - fs.lp_value + fs.usdu_in_lp, + fs.usdc_in_lp, + fs.lp_value_usd from final_snapshots fs left join daily_prices dp on date(fs.slot_start) = dp.day @@ -414,10 +526,12 @@ select end as collateral_ratio, interest_rate, sp_deposit as in_stability_pool, - lp_value as lp_value_usd, + usdu_in_lp, + usdc_in_lp, + lp_value_usd, wbtc_price from snapshots_with_prices -where (debt > 0 or sp_deposit > 0 or lp_value > 0) -- Show active troves or users with SP/LP deposits +where (debt > 0 or sp_deposit > 0 or lp_value_usd > 0) -- Show active troves or users with SP/LP deposits -- OPTIONAL: Uncomment to filter to last N days for weekly reports -- and slot_start >= current_timestamp - interval '7' day order by snapshot_time desc, owner asc, trove_id asc From 15603286519657841e95380cc6c981bd87af0837 Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Thu, 30 Oct 2025 12:39:27 +0100 Subject: [PATCH 05/14] rename folder --- dune_dashboard/liq/protocol_revenue.sql | 170 ++++++ .../liq}/total_usdu_supply.sql | 0 .../liq}/trove_debt_and_interest.sql | 0 .../liq}/trove_operations.sql | 0 .../obl}/trove_hourly_snapshots.sql | 0 .../uncap}/active_pool_updates.sql | 0 .../uncap}/interest_rates.sql | 0 .../uncap}/token_holders.sql | 0 .../uncap}/tvl.sql | 0 .../uncap}/usdu_holders_no_dust.sql | 0 example_queries/5155365.sql | 48 -- example_queries/5720005.sql | 518 ------------------ example_queries/protocol_revenue.sql | 168 ------ 13 files changed, 170 insertions(+), 734 deletions(-) create mode 100644 dune_dashboard/liq/protocol_revenue.sql rename {example_queries => dune_dashboard/liq}/total_usdu_supply.sql (100%) rename {example_queries => dune_dashboard/liq}/trove_debt_and_interest.sql (100%) rename {example_queries => dune_dashboard/liq}/trove_operations.sql (100%) rename {example_queries => dune_dashboard/obl}/trove_hourly_snapshots.sql (100%) rename {example_queries => dune_dashboard/uncap}/active_pool_updates.sql (100%) rename {example_queries => dune_dashboard/uncap}/interest_rates.sql (100%) rename {example_queries => dune_dashboard/uncap}/token_holders.sql (100%) rename {example_queries => dune_dashboard/uncap}/tvl.sql (100%) rename {example_queries => dune_dashboard/uncap}/usdu_holders_no_dust.sql (100%) delete mode 100644 example_queries/5155365.sql delete mode 100644 example_queries/5720005.sql delete mode 100644 example_queries/protocol_revenue.sql diff --git a/dune_dashboard/liq/protocol_revenue.sql b/dune_dashboard/liq/protocol_revenue.sql new file mode 100644 index 0000000..9db8903 --- /dev/null +++ b/dune_dashboard/liq/protocol_revenue.sql @@ -0,0 +1,170 @@ +with + +time_seq AS ( + select + sequence( + CAST('2025-01-01' as timestamp), + date_trunc('day', cast(now() as timestamp)), + interval '1' day + ) as time +), + +days AS ( + select + time.time as day + from time_seq + cross join unnest(time) as time(time) +), + +eth_fee as ( + select + date_trunc('minute', evt_block_time) as minute, + evt_tx_hash, + _ETHFee/1e18 as fee + from liquity_v2_ethereum.troveManager_rETH_evt_RedemptionFeePaidToTrove + + union all + + select + date_trunc('minute', evt_block_time) as minute, + evt_tx_hash, + _ETHFee/1e18 as fee + from liquity_v2_ethereum.troveManager_wstETH_evt_RedemptionFeePaidToTrove + + union all + + select + date_trunc('minute', evt_block_time) as minute, + evt_tx_hash, + _ETHFee/1e18 as fee + from liquity_v2_ethereum.troveManager_wETH_evt_RedemptionFeePaidToTrove +), + +add_price as ( + select + ef.minute, + evt_tx_hash, + fee, + p.price, + fee * p.price as fee_usd + from + eth_fee ef + left join + prices.usd p + on ef.minute = p.minute + and p.symbol = 'WETH' + and p.blockchain = 'ethereum' +), + +interest_rewards as ( + select + case + when to = 0x9502b7c397e9aa22fe9db7ef7daf21cd2aebe56b then 'wstETH' + when to = 0xd442e41019b7f5c4dd78f50dc03726c446148695 then 'rETH' + when to = 0x5721cbbd64fc7ae3ef44a0a3f9a790a9264cf9bf then 'WETH' + when to = 0x807def5e7d057df05c796f4bc75c3fe82bd6eee1 then 'PIL' + end as collateral_type, + date_trunc('day', evt_block_time) as day, + sum(value/1e18) as bold_amount + from liquity_v2_ethereum.boldToken_evt_Transfer + where to in (0x9502b7c397e9aa22fe9db7ef7daf21cd2aebe56b, 0xd442e41019b7f5c4dd78f50dc03726c446148695, 0x5721cbbd64fc7ae3ef44a0a3f9a790a9264cf9bf, 0x807def5e7d057df05c796f4bc75c3fe82bd6eee1) + and "from" = 0x0000000000000000000000000000000000000000 + group by 1, 2 +), + +liquidation_rewards as ( + select + collateral_type, + date_trunc('day', block_time) as day, + sum(collateral_sent_sp * _price/1e18 - debt_offset_sp) as liquidation_rewards + from + query_4412204 + group by 1, 2 +), + +all_interest as ( + select + day, + case + when collateral_type in ('wstETH', 'rETH', 'WETH') then 'SP Yield' + else 'PIL Yield' + end as fee_type, + sum(bold_amount) as fee + from + interest_rewards + where collateral_type in ('wstETH', 'rETH', 'WETH', 'PIL') + group by 1, 2 + + union all + + select + date_trunc('day', minute) as day, + 'Redemption Fees' as fee_type, + sum(fee_usd) as fee + from + add_price + group by 1, 2 + + union all + + select + day, + 'Liquidation Rewards' as fee_type, + sum(liquidation_rewards) as fee + from + liquidation_rewards + group by 1, 2 +), + +get_next_day as ( + select + *, + sum(fee) over (partition by fee_type order by day asc) as fee_total, + lead(day, 1, current_timestamp) over (partition by fee_type order by day asc) as next_day + from + all_interest +) + +select + day, + fee_type, + fee, + fee_total, + sum(fee_total/1e3) over (partition by day) as fee_total_all, + sum(fee_total/1e6) over (partition by day) as fee_total_all_million, + sum(case + when day > date_trunc('day', now()) - interval '1' day then fee/1e3 + else 0 + end) over (order by day) as fee_param +from ( +select + b.*, + coalesce(c.fee, 0) as fee +from ( +select + d.day, + c.fee_type, + c.fee_total +from +get_next_day c +inner join +days d + on c.day <= d.day + and d.day < c.next_day +) b +left join +get_next_day c + on b.day = c.day + and b.fee_type = c.fee_type +) +where day >= (case + when '{{time_period}}' = 'all time' then cast('2025-05-19' as date) + when '{{time_period}}' = '1 year' then date_trunc('day', now() - interval '1' year) + when '{{time_period}}' = '6 months' then date_trunc('day', now() - interval '6' month) + when '{{time_period}}' = '3 months' then date_trunc('day', now() - interval '3' month) + when '{{time_period}}' = '1 month' then date_trunc('day', now() - interval '1' month) + end) +order by day desc + +-- where token_balance > 0 +-- select sum(fee)/1e9 as fee from all_interest diff --git a/example_queries/total_usdu_supply.sql b/dune_dashboard/liq/total_usdu_supply.sql similarity index 100% rename from example_queries/total_usdu_supply.sql rename to dune_dashboard/liq/total_usdu_supply.sql diff --git a/example_queries/trove_debt_and_interest.sql b/dune_dashboard/liq/trove_debt_and_interest.sql similarity index 100% rename from example_queries/trove_debt_and_interest.sql rename to dune_dashboard/liq/trove_debt_and_interest.sql diff --git a/example_queries/trove_operations.sql b/dune_dashboard/liq/trove_operations.sql similarity index 100% rename from example_queries/trove_operations.sql rename to dune_dashboard/liq/trove_operations.sql diff --git a/example_queries/trove_hourly_snapshots.sql b/dune_dashboard/obl/trove_hourly_snapshots.sql similarity index 100% rename from example_queries/trove_hourly_snapshots.sql rename to dune_dashboard/obl/trove_hourly_snapshots.sql diff --git a/example_queries/active_pool_updates.sql b/dune_dashboard/uncap/active_pool_updates.sql similarity index 100% rename from example_queries/active_pool_updates.sql rename to dune_dashboard/uncap/active_pool_updates.sql diff --git a/example_queries/interest_rates.sql b/dune_dashboard/uncap/interest_rates.sql similarity index 100% rename from example_queries/interest_rates.sql rename to dune_dashboard/uncap/interest_rates.sql diff --git a/example_queries/token_holders.sql b/dune_dashboard/uncap/token_holders.sql similarity index 100% rename from example_queries/token_holders.sql rename to dune_dashboard/uncap/token_holders.sql diff --git a/example_queries/tvl.sql b/dune_dashboard/uncap/tvl.sql similarity index 100% rename from example_queries/tvl.sql rename to dune_dashboard/uncap/tvl.sql diff --git a/example_queries/usdu_holders_no_dust.sql b/dune_dashboard/uncap/usdu_holders_no_dust.sql similarity index 100% rename from example_queries/usdu_holders_no_dust.sql rename to dune_dashboard/uncap/usdu_holders_no_dust.sql diff --git a/example_queries/5155365.sql b/example_queries/5155365.sql deleted file mode 100644 index f4cd570..0000000 --- a/example_queries/5155365.sql +++ /dev/null @@ -1,48 +0,0 @@ -select - contract_address, - 'WETH' as collateral_type, - evt_tx_hash as tx_hash, - evt_index as tx_index, - evt_block_time as block_time, - evt_block_number as block_number, - _troveId as trove_id, - _operation as operation, - _debtChangeFromOperation/1e18 as debt_change, - _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, - _collChangeFromOperation/1e18 as collateral_change, - _annualInterestRate/1e16 as interest_rates -from liquity_v2_ethereum.troveManager_wETH_evt_TroveOperation - -union all - -select - contract_address, - 'rETH' as collateral_type, - evt_tx_hash as tx_hash, - evt_index as tx_index, - evt_block_time as block_time, - evt_block_number as block_number, - _troveId as trove_id, - _operation as operation, - _debtChangeFromOperation/1e18 as debt_change, - _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, - _collChangeFromOperation/1e18 as collateral_change, - _annualInterestRate/1e16 as interest_rates -from liquity_v2_ethereum.troveManager_rETH_evt_TroveOperation - -union all - -select - contract_address, - 'wstETH' as collateral_type, - evt_tx_hash as tx_hash, - evt_index as tx_index, - evt_block_time as block_time, - evt_block_number as block_number, - _troveId as trove_id, - _operation as operation, - _debtChangeFromOperation/1e18 as debt_change, - _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, - _collChangeFromOperation/1e18 as collateral_change, - _annualInterestRate/1e16 as interest_rates -from liquity_v2_ethereum.troveManager_wstETH_evt_TroveOperation diff --git a/example_queries/5720005.sql b/example_queries/5720005.sql deleted file mode 100644 index 23aa5a4..0000000 --- a/example_queries/5720005.sql +++ /dev/null @@ -1,518 +0,0 @@ --- First find the trove managers for each trove id at all times - --- When there is a "troveUpdated" event, it means the trove is no longer managed by a batch manager -with weth_trove_manager_address_updates as ( - SELECT evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - _interestBatchManager as interest_batch_manager - FROM liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt - UNION ALL - SELECT - evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager - FROM liquity_v2_ethereum.trovemanager_weth_evt_troveupdated -) - --- For each batch updated (bu) event, make sure to apply it to the relevant troves (with latest manager = bu manager) -, weth_bu_with_trove_rows as ( - SELECT - bu.*, - tm.trove_id, - tm.interest_batch_manager, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id - ORDER BY bu.evt_block_number - tm.evt_block_number ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_weth_evt_batchupdated bu - LEFT JOIN weth_trove_manager_address_updates tm - ON ( - tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event - OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) - ) -) - --- Keep latest value and right managers -, weth_bu_with_trove as - ( - select * - from weth_bu_with_trove_rows - where rn = 1 and _interestBatchManager = interest_batch_manager - ) - --- Now get the bu events enriched with debt numbers from the latest BatchedTroveUpdated (bt) events --- We need to do that because we don't have the debt numbers in the bu events -, weth_bu_closest_previous_events AS ( - -- Find the closest previous batchedTroveUpdated for any batchUpdated - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - bu.evt_block_time as block_time, - bu.evt_tx_hash as tx_hash, - bu.evt_index as tx_index, - bu.evt_block_number as block_number, - bu._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bu.trove_id, - bt._batchDebtShares/1e18 as batch_debt_shares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId - ORDER BY bu.evt_block_time - bt.evt_block_time ASC - ) AS rn - FROM weth_bu_with_trove bu - JOIN liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt - ON bu.trove_id = bt._troveId - AND ( - bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event - OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) - ) -) - -, weth_bu_events_enriched as ( - select - 'WETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , batch_debt_shares * debt_to_shares_ratio as debt - , stake - , 'batchUpdated' as event - from weth_bu_closest_previous_events - where rn = 1 -) - --- Now we have the bu events enriched --- We need to add 2 event types: batchedTroveUpdated (a bit easier but similar logic) and troveUpdated (super easy) - --- The complexity in batchedTroveUpdated is to get the latest values of interest rates etc. if they were changed --- by a batchUpdated event - -, weth_bt_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - - bt.evt_block_time as block_time, - bt.evt_tx_hash as tx_hash, - bt.evt_index as tx_index, - bt.evt_block_number as block_number, - bt._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bt._troveId as trove_id, - bt._batchDebtShares/1e18 as _batchDebtShares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId - ORDER BY bt.evt_block_time - bu.evt_block_time ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt - JOIN liquity_v2_ethereum.trovemanager_weth_evt_batchupdated bu - ON bu._interestBatchManager = bt._interestBatchManager - AND ( - bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event - OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) - ) -) - --- Below is batchedTroveUpdated events -- full -, weth_bt_events_enriched as ( - select - 'WETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , _batchDebtShares * debt_to_shares_ratio as debt - , stake - , 'batchTroveUpdated' as event - from weth_bt_closest_previous_events - where rn = 1 -) - --- We do the same for rETH and wstETH -, reth_trove_manager_address_updates as ( - SELECT evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - _interestBatchManager as interest_batch_manager - FROM liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt - UNION ALL - SELECT - evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager - FROM liquity_v2_ethereum.trovemanager_reth_evt_troveupdated -) - -, reth_bu_with_trove_rows as ( - SELECT - bu.*, - tm.trove_id, - tm.interest_batch_manager, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id - ORDER BY bu.evt_block_number - tm.evt_block_number ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_reth_evt_batchupdated bu - LEFT JOIN reth_trove_manager_address_updates tm - ON ( - tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event - OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) - ) -) - -, reth_bu_with_trove as - ( - select * - from reth_bu_with_trove_rows - where rn = 1 and _interestBatchManager = interest_batch_manager - ) - - -, reth_bu_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - bu.evt_block_time as block_time, - bu.evt_tx_hash as tx_hash, - bu.evt_index as tx_index, - bu.evt_block_number as block_number, - bu._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bu.trove_id, - bt._batchDebtShares/1e18 as batch_debt_shares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId - ORDER BY bu.evt_block_time - bt.evt_block_time ASC - ) AS rn - FROM reth_bu_with_trove bu - JOIN liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt - ON bu.trove_id = bt._troveId - AND ( - bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event - OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) - ) -) - -, reth_bu_events_enriched as ( - select - 'rETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , batch_debt_shares * debt_to_shares_ratio as debt - , stake - , 'batchUpdated' as event - from reth_bu_closest_previous_events - where rn = 1 -) - -, reth_bt_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - - bt.evt_block_time as block_time, - bt.evt_tx_hash as tx_hash, - bt.evt_index as tx_index, - bt.evt_block_number as block_number, - bt._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bt._troveId as trove_id, - bt._batchDebtShares/1e18 as _batchDebtShares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId - ORDER BY bt.evt_block_time - bu.evt_block_time ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt - JOIN liquity_v2_ethereum.trovemanager_reth_evt_batchupdated bu - ON bu._interestBatchManager = bt._interestBatchManager - AND ( - bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event - OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) - ) -) - -, reth_bt_events_enriched as ( - select - 'rETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , _batchDebtShares * debt_to_shares_ratio as debt - , stake - , 'batchTroveUpdated' as event - from reth_bt_closest_previous_events - where rn = 1 -) - -, wsteth_trove_manager_address_updates as ( - SELECT evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - _interestBatchManager as interest_batch_manager - FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt - UNION ALL - SELECT - evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager - FROM liquity_v2_ethereum.trovemanager_wsteth_evt_troveupdated -) - -, wsteth_bu_with_trove_rows as ( - SELECT - bu.*, - tm.trove_id, - tm.interest_batch_manager, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id - ORDER BY bu.evt_block_number - tm.evt_block_number ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchupdated bu - LEFT JOIN wsteth_trove_manager_address_updates tm - ON ( - tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event - OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) - ) -) - -, wsteth_bu_with_trove as - ( - select * - from wsteth_bu_with_trove_rows - where rn = 1 and _interestBatchManager = interest_batch_manager - ) - -, wsteth_bu_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - bu.evt_block_time as block_time, - bu.evt_tx_hash as tx_hash, - bu.evt_index as tx_index, - bu.evt_block_number as block_number, - bu._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bu.trove_id, - bt._batchDebtShares/1e18 as batch_debt_shares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId - ORDER BY bu.evt_block_time - bt.evt_block_time ASC - ) AS rn - FROM wsteth_bu_with_trove bu - JOIN liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt - ON bu.trove_id = bt._troveId - AND ( - bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event - OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) - ) -) - -, wsteth_bu_events_enriched as ( - select - 'wstETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , batch_debt_shares * debt_to_shares_ratio as debt - , stake - , 'batchUpdated' as event - from wsteth_bu_closest_previous_events - where rn = 1 -) - -, wsteth_bt_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - - bt.evt_block_time as block_time, - bt.evt_tx_hash as tx_hash, - bt.evt_index as tx_index, - bt.evt_block_number as block_number, - bt._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bt._troveId as trove_id, - bt._batchDebtShares/1e18 as _batchDebtShares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId - ORDER BY bt.evt_block_time - bu.evt_block_time ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt - JOIN liquity_v2_ethereum.trovemanager_wsteth_evt_batchupdated bu - ON bu._interestBatchManager = bt._interestBatchManager - AND ( - bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event - OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) - ) -) - -, wsteth_bt_events_enriched as ( - select - 'wstETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , _batchDebtShares * debt_to_shares_ratio as debt - , stake - , 'batchTroveUpdated' as event - from wsteth_bt_closest_previous_events - where rn = 1 -) - --- Now we union all results --- and add troveUpdated events (super easy) -, pre_final_results as ( - select * from weth_bu_events_enriched - union all - select * from weth_bt_events_enriched - union all - select - 'WETH' as collateral_type - , evt_tx_hash as tx_hash - , evt_index as tx_index - , evt_block_time as block_time - , evt_block_number as evt_block_number - , 0x0000000000000000000000000000000000000000 as interest_batch_manager - , _annualInterestRate/1e16 as interest_rates - , _troveId as trove_id - , _coll/1e18 as collateral - , _debt/1e18 as debt - , _stake/1e18 as stake - , 'troveUpdated' as event - from liquity_v2_ethereum.trovemanager_weth_evt_troveupdated - union all - select * from reth_bu_events_enriched - union all - select * from reth_bt_events_enriched - union all - select - 'rETH' as collateral_type - , evt_tx_hash as tx_hash - , evt_index as tx_index - , evt_block_time as block_time - , evt_block_number as evt_block_number - , 0x0000000000000000000000000000000000000000 as interest_batch_manager - , _annualInterestRate/1e16 as interest_rates - , _troveId as trove_id - , _coll/1e18 as collateral - , _debt/1e18 as debt - , _stake/1e18 as stake - , 'troveUpdated' as event - from liquity_v2_ethereum.trovemanager_reth_evt_troveupdated - union all - select * from wsteth_bu_events_enriched - union all - select * from wsteth_bt_events_enriched - union all - select - 'wstETH' as collateral_type - , evt_tx_hash as tx_hash - , evt_index as tx_index - , evt_block_time as block_time - , evt_block_number as evt_block_number - , 0x0000000000000000000000000000000000000000 as interest_batch_manager - , _annualInterestRate/1e16 as interest_rates - , _troveId as trove_id - , _coll/1e18 as collateral - , _debt/1e18 as debt - , _stake/1e18 as stake - , 'troveUpdated' as event - from liquity_v2_ethereum.trovemanager_wsteth_evt_troveupdated -) - --- Final results with deduplication --- (there can be duplicates because both batchUpdated & batchTroveUpdated events --- in this case, keep the batchTroveUpdated event) -select - collateral_type, - tx_hash, - tx_index, - block_time, - block_number, - interest_rates, - interest_batch_manager, - trove_id, - collateral, - debt, - stake, - event -from ( - select - *, - row_number() over ( - partition by collateral_type, tx_hash, block_number, trove_id - order by tx_index desc, (case when event = 'batchTroveUpdated' then 1 else 0 end) desc - ) as rn - from pre_final_results -) ranked -where rn = 1 diff --git a/example_queries/protocol_revenue.sql b/example_queries/protocol_revenue.sql deleted file mode 100644 index 46ea0c3..0000000 --- a/example_queries/protocol_revenue.sql +++ /dev/null @@ -1,168 +0,0 @@ --- Uncap Protocol - Protocol Revenue --- Network: Starknet Mainnet --- Purpose: Track protocol revenue from redemption fees and interest rewards - -with - -cfg as ( - select - 0x06dcfc4ce8ca0c664d5f6c5ee434c9379d15e616246db092cd9e21e5871e4377 as events_emitter, - 0x04695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c as usdu_token, - 0x65ccbeb1516bd1ae841b0729dd0798cb86027fbbbce929f647a2d057655c054 as stability_pool, - 0x307d7378c2543140d607297f4a863fbfe83cb0804297532c52bdd246a7b7d69 as interest_router, - 2759369 as deployment_block -), - --- Get deployment date for time series -deployment_date as ( - select min(date_trunc('day', time)) as start_date - from starknet.blocks - where number >= (select deployment_block from cfg) -), - -time_seq AS ( - select - sequence( - CAST((select start_date from deployment_date) as timestamp), - date_trunc('day', cast(now() as timestamp)), - interval '1' day - ) as time -), - -days AS ( - select - time.time as day - from time_seq - cross join unnest(time) as time(time) -), - --- Extract RedemptionFeePaidToTrove events --- Event structure: trove_id (u256), strk_fee (u256) -wbtc_fee as ( - select - date_trunc('minute', block_time) as minute, - transaction_hash as evt_tx_hash, - cast(bytearray_to_uint256(data[3]) as double) / 1e18 as fee -- strk_fee in STRK - from starknet.events - where from_address = (select events_emitter from cfg) - and keys[1] = 0x03b605674b6c4bd98149476fb8ed7db31a620da8d46f77070759e7b152252ef6 - and block_number >= (select deployment_block from cfg) -), - --- Get WBTC prices for fee valuation -wbtc_prices as ( - select - date_trunc('minute', timestamp) as minute, - price - from dune.openblocklabs.result_starknet_prices_daily - where contract_address = 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac -- WBTC -), - -add_price as ( - select - wf.minute, - evt_tx_hash, - fee, - coalesce(wp.price, 100000) as price, - fee * coalesce(wp.price, 100000) as fee_usd - from - wbtc_fee wf - left join - wbtc_prices wp - on wf.minute = wp.minute -), - --- Extract USDU mints (Transfer events from 0x0) --- Transfer event: from (keys[2]), to (keys[3]), amount (data[1]) -interest_rewards as ( - select - case - when keys[3] = (select stability_pool from cfg) then 'SP' - when keys[3] = (select interest_router from cfg) then 'Interest Router' - end as recipient_type, - date_trunc('day', block_time) as day, - sum(cast(bytearray_to_uint256(data[1]) as double) / 1e18) as usdu_amount - from starknet.events - where from_address = (select usdu_token from cfg) - and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 -- Transfer - and keys[2] = 0x0000000000000000000000000000000000000000 -- from = 0x0 (mint) - and keys[3] in ( - (select stability_pool from cfg), - (select interest_router from cfg) - ) - and block_number >= (select deployment_block from cfg) - group by 1, 2 -), - -all_interest as ( - select - day, - case - when recipient_type = 'SP' then 'SP Yield' - else 'Interest Router Yield' - end as fee_type, - sum(usdu_amount) as fee - from - interest_rewards - where recipient_type in ('SP', 'Interest Router') - group by 1, 2 - - union all - - select - date_trunc('day', minute) as day, - 'Redemption Fees' as fee_type, - sum(fee_usd) as fee - from - add_price - group by 1, 2 -), - -get_next_day as ( - select - *, - sum(fee) over (partition by fee_type order by day asc) as fee_total, - lead(day, 1, current_timestamp) over (partition by fee_type order by day asc) as next_day - from - all_interest -) - -select - day, - fee_type, - fee, - fee_total, - sum(fee_total/1e3) over (partition by day) as fee_total_all, - sum(case - when day > date_trunc('day', now()) - interval '1' day then fee/1e3 - else 0 - end) over (order by day) as fee_param -from ( -select - b.*, - coalesce(c.fee, 0) as fee -from ( -select - d.day, - c.fee_type, - c.fee_total -from -get_next_day c -inner join -days d - on c.day <= d.day - and d.day < c.next_day -) b -left join -get_next_day c - on b.day = c.day - and b.fee_type = c.fee_type -) -where day >= (case - when '{{time_period}}' = 'all time' then (select start_date from deployment_date) - when '{{time_period}}' = '1 year' then date_trunc('day', now() - interval '1' year) - when '{{time_period}}' = '6 months' then date_trunc('day', now() - interval '6' month) - when '{{time_period}}' = '3 months' then date_trunc('day', now() - interval '3' month) - when '{{time_period}}' = '1 month' then date_trunc('day', now() - interval '1' month) - end) -order by day desc From cabebdc2c40118a5d1e12e693eee47dd721ba8ea Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Fri, 7 Nov 2025 14:40:55 +0100 Subject: [PATCH 06/14] uncap dune dashboard --- dune_dashboard/liq/interest_rates | 141 +++++ dune_dashboard/liq/liquidations.sql | 61 +++ dune_dashboard/liq/total_bold_holders/all.sql | 38 ++ .../total_bold_holders/bold_token_holders.sql | 82 +++ .../liq/total_bold_holders/sp_depositors.sql | 74 +++ .../total_bold_holders/total_bold_holders.sql | 14 + dune_dashboard/liq/trove_operations.sql | 83 +-- dune_dashboard/liq/trove_updates.sql | 518 ++++++++++++++++++ dune_dashboard/uncap/interest_rates.sql | 59 +- .../uncap/interest_rates/trove_operations.sql | 44 ++ .../interest_rates/trove_updates.sql} | 8 +- dune_dashboard/uncap/protocol_revenue.sql | 176 ++++++ .../uncap/protocol_revenue/liquidations.sql | 44 ++ .../uncap/{ => tvl}/active_pool_updates.sql | 0 .../uncap/{ => tvl}/token_holders.sql | 0 dune_dashboard/uncap/{ => tvl}/tvl.sql | 0 dune_dashboard/uncap/usdu_holders_no_dust.sql | 110 ---- 17 files changed, 1263 insertions(+), 189 deletions(-) create mode 100644 dune_dashboard/liq/interest_rates create mode 100644 dune_dashboard/liq/liquidations.sql create mode 100644 dune_dashboard/liq/total_bold_holders/all.sql create mode 100644 dune_dashboard/liq/total_bold_holders/bold_token_holders.sql create mode 100644 dune_dashboard/liq/total_bold_holders/sp_depositors.sql create mode 100644 dune_dashboard/liq/total_bold_holders/total_bold_holders.sql create mode 100644 dune_dashboard/liq/trove_updates.sql create mode 100644 dune_dashboard/uncap/interest_rates/trove_operations.sql rename dune_dashboard/{liq/trove_debt_and_interest.sql => uncap/interest_rates/trove_updates.sql} (85%) create mode 100644 dune_dashboard/uncap/protocol_revenue.sql create mode 100644 dune_dashboard/uncap/protocol_revenue/liquidations.sql rename dune_dashboard/uncap/{ => tvl}/active_pool_updates.sql (100%) rename dune_dashboard/uncap/{ => tvl}/token_holders.sql (100%) rename dune_dashboard/uncap/{ => tvl}/tvl.sql (100%) delete mode 100644 dune_dashboard/uncap/usdu_holders_no_dust.sql diff --git a/dune_dashboard/liq/interest_rates b/dune_dashboard/liq/interest_rates new file mode 100644 index 0000000..3d32279 --- /dev/null +++ b/dune_dashboard/liq/interest_rates @@ -0,0 +1,141 @@ +with + +time_seq as ( + select + sequence( + CAST('2024-11-01' as timestamp), + date_trunc('hour', cast(now() as timestamp)), + interval '1' hour + ) as time +), + +hours as ( + select + time.time as hour + from time_seq + cross join unnest(time) as time(time) +), + +hourly_rates as ( + select + *, + lead(hour, 1, current_timestamp) over (partition by trove_id, collateral_type order by hour asc) as next_hour + from ( + select + date_trunc('hour', block_time) as hour, + trove_id, + collateral_type, + max_by(interest_rates, (block_number, tx_index)) as interest_rates + from + query_5155365 + group by 1, 2, 3 + ) +), + +filled_rates as ( + select + h.hour, + c.trove_id, + c.collateral_type, + c.interest_rates + from + hourly_rates c + inner join + hours h + on c.hour <= h.hour + and h.hour < c.next_hour +), + + +hourly_debts as ( + select + *, + lead(hour, 1, current_timestamp) over (partition by trove_id, collateral_type order by hour asc) as next_hour + from ( + select + date_trunc('hour', block_time) as hour, + trove_id, + collateral_type, + max_by(debt, (block_number, tx_index)) as debt + from + query_5720005 + group by 1, 2, 3 + ) +), + +filled_debts as ( + select + h.hour, + c.trove_id, + c.collateral_type, + c.debt + from + hourly_debts c + inner join + hours h + on c.hour <= h.hour + and h.hour < c.next_hour +), + +combine_with_debts as ( + select + fr.*, + fd.debt + from + filled_rates fr + inner join + filled_debts fd + on fr.hour = fd.hour + and fr.trove_id = fd.trove_id + and fr.collateral_type = fd.collateral_type +), + +current_rates as ( + select + collateral_type, + sum(interest_rates * debt) / sum(debt) as interest_rates + -- avg(interest_rates) as interest_rates + from + combine_with_debts + where interest_rates > 0 and debt > 0 + and hour >= now() - interval '24' hour + group by 1 +), + +total_rates as ( + select + sum(interest_rates * debt) / sum(debt) as interest_rates + -- avg(interest_rates) as interest_rates + from + combine_with_debts + where interest_rates > 0 and debt > 0 + and hour >= date_trunc('hour', now() - interval '30' day) +), + +hourly_rates_summary as ( + select + date_trunc('hour', hour) as hour, + collateral_type, + (sum(interest_rates * debt) / sum(debt))/100 as interest_rates + -- avg(interest_rates)/100 as interest_rates + from + combine_with_debts + where interest_rates > 0 and debt > 0 + and hour >= date_trunc('hour', now() - interval '30' day) + group by 1, 2 +) + +select + hrs.*, + cr.interest_rates as latest_rates, + tr.interest_rates as total_rates +from +hourly_rates_summary hrs +left join +current_rates cr + on hrs.collateral_type = cr.collateral_type +left join +total_rates tr + on 1 = 1 +where hour >= date_trunc('hour', now() - interval '30' day) +order by hrs.hour desc, lower(hrs.collateral_type) desc; diff --git a/dune_dashboard/liq/liquidations.sql b/dune_dashboard/liq/liquidations.sql new file mode 100644 index 0000000..65674d4 --- /dev/null +++ b/dune_dashboard/liq/liquidations.sql @@ -0,0 +1,61 @@ +select + contract_address, + 'rETH' as collateral_type, + evt_tx_hash as tx_hash, + evt_index as tx_index, + evt_block_time as block_time, + evt_block_number as block_number, + _debtOffsetBySP/1e18 as debt_offset_sp, + _debtRedistributed/1e18 as debt_redistributed, + _boldGasCompensation/1e18 as bold_gas_compensation, + _collGasCompensation/1e18 as collateral_gas_compensation, + _collSentToSP/1e18 as collateral_sent_sp, + _collRedistributed/1e18 as collateral_distributed, + _collSurplus/1e18 as collateral_surplus, + _L_ETH, + _L_boldDebt, + _price +from liquity_v2_ethereum.troveManager_rETH_evt_Liquidation + +union all + +select + contract_address, + 'WETH' as collateral_type, + evt_tx_hash as tx_hash, + evt_index as tx_index, + evt_block_time as block_time, + evt_block_number as block_number, + _debtOffsetBySP/1e18 as debt_offset_sp, + _debtRedistributed/1e18 as debt_redistributed, + _boldGasCompensation/1e18 as bold_gas_compensation, + _collGasCompensation/1e18 as collateral_gas_compensation, + _collSentToSP/1e18 as collateral_sent_sp, + _collRedistributed/1e18 as collateral_distributed, + _collSurplus/1e18 as collateral_surplus, + _L_ETH, + _L_boldDebt, + _price +from liquity_v2_ethereum.troveManager_wETH_evt_Liquidation + +union all + +select + contract_address, + 'wstETH' as collateral_type, + evt_tx_hash as tx_hash, + evt_index as tx_index, + evt_block_time as block_time, + evt_block_number as block_number, + _debtOffsetBySP/1e18 as debt_offset_sp, + _debtRedistributed/1e18 as debt_redistributed, + _boldGasCompensation/1e18 as bold_gas_compensation, + _collGasCompensation/1e18 as collateral_gas_compensation, + _collSentToSP/1e18 as collateral_sent_sp, + _collRedistributed/1e18 as collateral_distributed, + _collSurplus/1e18 as collateral_surplus, + _L_ETH, + _L_boldDebt, + _price +from liquity_v2_ethereum.troveManager_wstETH_evt_Liquidation + diff --git a/dune_dashboard/liq/total_bold_holders/all.sql b/dune_dashboard/liq/total_bold_holders/all.sql new file mode 100644 index 0000000..2d64829 --- /dev/null +++ b/dune_dashboard/liq/total_bold_holders/all.sql @@ -0,0 +1,38 @@ +with +-- Generate a series of dates to join with +date_series as ( + select timestamp + interval '1' day as day + from utils.days +), + +-- Join events with dates to get daily balances +daily_balances as ( + select + ds.day, + e.user, + e.contract_address, + e.balance_at_event + from date_series ds + join query_5354879 e + on ds.day >= date_trunc('day', e.start_time) + and ds.day < date_trunc('day', e.end_time) +), + +latest_daily_balances as ( + select + day, + user, + contract_address, + max(balance_at_event) as daily_balance + from daily_balances + group by day, user, contract_address +) + +-- Final output +select + day + interval '1' day as day, + user as address, + contract_address, + daily_balance as token_balance +from latest_daily_balances +order by day, user, contract_address diff --git a/dune_dashboard/liq/total_bold_holders/bold_token_holders.sql b/dune_dashboard/liq/total_bold_holders/bold_token_holders.sql new file mode 100644 index 0000000..5c17cd7 --- /dev/null +++ b/dune_dashboard/liq/total_bold_holders/bold_token_holders.sql @@ -0,0 +1,82 @@ +with + +erc20_aggregated as ( + select + day, + address, + token_address, + token_symbol, + sum(amount) as amount + from ( + select + date_trunc('day', evt_block_time) as day, + to as address, + contract_address as token_address, + 'BOLD' as token_symbol, + sum(value/1e18) as amount + from liquity_v2_ethereum.boldToken_evt_Transfer + + group by 1, 2, 3, 4 + + union all + + select + date_trunc('day', evt_block_time) as day, + "from" as address, + contract_address as token_address, + 'BOLD' as token_symbol, + -sum(value/1e18) as amount + from liquity_v2_ethereum.boldToken_evt_Transfer + group by 1, 2, 3, 4 + ) x + group by 1, 2, 3, 4 +), + +cum_aggregated as ( + select + day, + address, + token_address, + token_symbol, + sum(amount) over (partition by address, token_address, token_symbol order by day asc) as token_balance, + lead(day, 1, current_timestamp) over (partition by address, token_address, token_symbol order by day asc) as next_day + from + erc20_aggregated +), + +time_seq AS ( + select + sequence( + CAST('2024-11-01' as timestamp), + date_trunc('day', cast(now() as timestamp)), + interval '1' day + ) as time +), + +days AS ( + select + time.time as day + from time_seq + cross join unnest(time) as time(time) +) + +select + * +from ( +select + d.day, + c.address, + c.token_address, + c.token_symbol, + c.token_balance +from +cum_aggregated c +inner join +days d + on c.day <= d.day + and d.day < c.next_day +) +where token_balance > 0.001 -- Exclude users who hold dust + + + diff --git a/dune_dashboard/liq/total_bold_holders/sp_depositors.sql b/dune_dashboard/liq/total_bold_holders/sp_depositors.sql new file mode 100644 index 0000000..ba7034e --- /dev/null +++ b/dune_dashboard/liq/total_bold_holders/sp_depositors.sql @@ -0,0 +1,74 @@ +WITH + +raw_deposits AS ( + SELECT * FROM ( + SELECT 'ETH' AS base_collateral, * + FROM liquity_v2_ethereum.stabilitypool_weth_evt_depositoperation + UNION ALL + SELECT 'rETH', * + FROM liquity_v2_ethereum.stabilitypool_reth_evt_depositoperation + UNION ALL + SELECT 'wstETH', * + FROM liquity_v2_ethereum.stabilitypool_wsteth_evt_depositoperation + ) +) + +, daily_data AS ( + SELECT + date(evt_block_time) as date, + _depositor as user, + SUM((_topUpOrWithdrawal - _depositLossSinceLastOperation - _yieldGainClaimed + _yieldGainSinceLastOperation) / 1e18) AS amount + FROM raw_deposits + WHERE _depositor NOT IN ( + 0x50bd66d59911f5e086ec87ae43c811e0d059dd11, -- sBOLD + 0x2048a730f564246411415f719198d6f7c10a7961 -- yBOLD + ) + GROUP BY date(evt_block_time), _depositor +) + +, intermediary_results AS ( + SELECT date + , user + , SUM(amount) OVER (PARTITION BY user ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_amount + FROM daily_data +) + +, date_series AS ( + WITH dates AS ( + SELECT sequence( + cast('2025-01-01' as date), + cast(date_trunc('day', current_timestamp) AS date), + interval '1' day + ) as d + ) + SELECT date_value as date + FROM dates + CROSS JOIN UNNEST(d) AS t(date_value) +) + +, users AS ( + SELECT DISTINCT user FROM intermediary_results +) + +, date_user_series AS ( + SELECT ds.date + , p.user + FROM date_series ds + CROSS JOIN users p +) + +, filled_data AS ( + SELECT dps.date + , dps.user + , yt.cumulative_amount + , LAST_VALUE(yt.cumulative_amount) IGNORE NULLS OVER (PARTITION BY dps.user ORDER BY dps.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS filled_cumulative_amount + , LAST_VALUE(yt.user) IGNORE NULLS OVER (PARTITION BY dps.user ORDER BY dps.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS filled_user + FROM date_user_series dps + LEFT JOIN intermediary_results yt ON dps.date = yt.date AND dps.user = yt.user +) + +SELECT date as day + , filled_user AS address + , filled_cumulative_amount AS token_balance +FROM filled_data +WHERE filled_user IS NOT NULL diff --git a/dune_dashboard/liq/total_bold_holders/total_bold_holders.sql b/dune_dashboard/liq/total_bold_holders/total_bold_holders.sql new file mode 100644 index 0000000..e37f865 --- /dev/null +++ b/dune_dashboard/liq/total_bold_holders/total_bold_holders.sql @@ -0,0 +1,14 @@ +with users_data as ( + select day, address from query_5155107 -- BOLD Holders + where token_balance > 0.001 + union + select day, address from query_5301941 -- SP Depositors + where token_balance > 0.001 + union + select day, address from query_5354895 -- All + where token_balance > 0.001 +) +select day, count(distinct address) as "Holders" +from users_data +group by 1 +order by 1 diff --git a/dune_dashboard/liq/trove_operations.sql b/dune_dashboard/liq/trove_operations.sql index 9ac6486..4ec1e1b 100644 --- a/dune_dashboard/liq/trove_operations.sql +++ b/dune_dashboard/liq/trove_operations.sql @@ -1,40 +1,49 @@ --- Uncap Protocol - Trove Operations --- Network: Starknet Mainnet --- Purpose: Track all trove operations (open, close, adjust, etc.) --- Equivalent to query_5155365 for Liquity v2 +select + contract_address, + 'WETH' as collateral_type, + evt_tx_hash as tx_hash, + evt_index as tx_index, + evt_block_time as block_time, + evt_block_number as block_number, + _troveId as trove_id, + _operation as operation, + _debtChangeFromOperation/1e18 as debt_change, + _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, + _collChangeFromOperation/1e18 as collateral_change, + _annualInterestRate/1e16 as interest_rates +from liquity_v2_ethereum.troveManager_wETH_evt_TroveOperation -with +union all -cfg as ( - select - 0x06dcfc4ce8ca0c664d5f6c5ee434c9379d15e616246db092cd9e21e5871e4377 as events_emitter, - 2759369 as deployment_block -) +select + contract_address, + 'rETH' as collateral_type, + evt_tx_hash as tx_hash, + evt_index as tx_index, + evt_block_time as block_time, + evt_block_number as block_number, + _troveId as trove_id, + _operation as operation, + _debtChangeFromOperation/1e18 as debt_change, + _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, + _collChangeFromOperation/1e18 as collateral_change, + _annualInterestRate/1e16 as interest_rates +from liquity_v2_ethereum.troveManager_rETH_evt_TroveOperation + +union all + +select + contract_address, + 'wstETH' as collateral_type, + evt_tx_hash as tx_hash, + evt_index as tx_index, + evt_block_time as block_time, + evt_block_number as block_number, + _troveId as trove_id, + _operation as operation, + _debtChangeFromOperation/1e18 as debt_change, + _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, + _collChangeFromOperation/1e18 as collateral_change, + _annualInterestRate/1e16 as interest_rates +from liquity_v2_ethereum.troveManager_wstETH_evt_TroveOperation -select - from_address as contract_address, - 'WBTC' as collateral_type, - transaction_hash as tx_hash, - event_index as tx_index, - block_time, - block_number, - -- Extract trove_id (u256 = data[1] low, data[2] high) - lower(concat('0x', - "right"(substring(to_hex(data[2]), 3), 32), - "right"(substring(to_hex(data[1]), 3), 32) - )) as trove_id, - -- Extract operation type (u8 stored as felt = data[3]) - cast(bytearray_to_uint256(data[3]) as bigint) as operation, - -- Extract debt change (u256 = data[4] low, data[5] high) - cast(bytearray_to_uint256(data[4]) as double) / 1e18 as debt_change, - -- Extract upfront fee (u256 = data[6] low, data[7] high) - cast(bytearray_to_uint256(data[6]) as double) / 1e18 as upfront_fees, - -- Extract collateral change (u256 = data[8] low, data[9] high) - cast(bytearray_to_uint256(data[8]) as double) / 1e18 as collateral_change, - -- Extract annual interest rate (u256 = data[10] low, data[11] high) - cast(bytearray_to_uint256(data[10]) as double) / 1e16 as interest_rates -from starknet.events -where from_address = (select events_emitter from cfg) - and keys[1] = 0x02c0b995ca82e6ae9ef5d19fd0659d7748540971842bd71843cfc904d240c4bf - and block_number >= (select deployment_block from cfg) -order by block_number desc, event_index desc diff --git a/dune_dashboard/liq/trove_updates.sql b/dune_dashboard/liq/trove_updates.sql new file mode 100644 index 0000000..23aa5a4 --- /dev/null +++ b/dune_dashboard/liq/trove_updates.sql @@ -0,0 +1,518 @@ +-- First find the trove managers for each trove id at all times + +-- When there is a "troveUpdated" event, it means the trove is no longer managed by a batch manager +with weth_trove_manager_address_updates as ( + SELECT evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + _interestBatchManager as interest_batch_manager + FROM liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt + UNION ALL + SELECT + evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager + FROM liquity_v2_ethereum.trovemanager_weth_evt_troveupdated +) + +-- For each batch updated (bu) event, make sure to apply it to the relevant troves (with latest manager = bu manager) +, weth_bu_with_trove_rows as ( + SELECT + bu.*, + tm.trove_id, + tm.interest_batch_manager, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id + ORDER BY bu.evt_block_number - tm.evt_block_number ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_weth_evt_batchupdated bu + LEFT JOIN weth_trove_manager_address_updates tm + ON ( + tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event + OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) + ) +) + +-- Keep latest value and right managers +, weth_bu_with_trove as + ( + select * + from weth_bu_with_trove_rows + where rn = 1 and _interestBatchManager = interest_batch_manager + ) + +-- Now get the bu events enriched with debt numbers from the latest BatchedTroveUpdated (bt) events +-- We need to do that because we don't have the debt numbers in the bu events +, weth_bu_closest_previous_events AS ( + -- Find the closest previous batchedTroveUpdated for any batchUpdated + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + bu.evt_block_time as block_time, + bu.evt_tx_hash as tx_hash, + bu.evt_index as tx_index, + bu.evt_block_number as block_number, + bu._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bu.trove_id, + bt._batchDebtShares/1e18 as batch_debt_shares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId + ORDER BY bu.evt_block_time - bt.evt_block_time ASC + ) AS rn + FROM weth_bu_with_trove bu + JOIN liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt + ON bu.trove_id = bt._troveId + AND ( + bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event + OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) + ) +) + +, weth_bu_events_enriched as ( + select + 'WETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , batch_debt_shares * debt_to_shares_ratio as debt + , stake + , 'batchUpdated' as event + from weth_bu_closest_previous_events + where rn = 1 +) + +-- Now we have the bu events enriched +-- We need to add 2 event types: batchedTroveUpdated (a bit easier but similar logic) and troveUpdated (super easy) + +-- The complexity in batchedTroveUpdated is to get the latest values of interest rates etc. if they were changed +-- by a batchUpdated event + +, weth_bt_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + + bt.evt_block_time as block_time, + bt.evt_tx_hash as tx_hash, + bt.evt_index as tx_index, + bt.evt_block_number as block_number, + bt._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bt._troveId as trove_id, + bt._batchDebtShares/1e18 as _batchDebtShares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId + ORDER BY bt.evt_block_time - bu.evt_block_time ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt + JOIN liquity_v2_ethereum.trovemanager_weth_evt_batchupdated bu + ON bu._interestBatchManager = bt._interestBatchManager + AND ( + bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event + OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) + ) +) + +-- Below is batchedTroveUpdated events -- full +, weth_bt_events_enriched as ( + select + 'WETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , _batchDebtShares * debt_to_shares_ratio as debt + , stake + , 'batchTroveUpdated' as event + from weth_bt_closest_previous_events + where rn = 1 +) + +-- We do the same for rETH and wstETH +, reth_trove_manager_address_updates as ( + SELECT evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + _interestBatchManager as interest_batch_manager + FROM liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt + UNION ALL + SELECT + evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager + FROM liquity_v2_ethereum.trovemanager_reth_evt_troveupdated +) + +, reth_bu_with_trove_rows as ( + SELECT + bu.*, + tm.trove_id, + tm.interest_batch_manager, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id + ORDER BY bu.evt_block_number - tm.evt_block_number ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_reth_evt_batchupdated bu + LEFT JOIN reth_trove_manager_address_updates tm + ON ( + tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event + OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) + ) +) + +, reth_bu_with_trove as + ( + select * + from reth_bu_with_trove_rows + where rn = 1 and _interestBatchManager = interest_batch_manager + ) + + +, reth_bu_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + bu.evt_block_time as block_time, + bu.evt_tx_hash as tx_hash, + bu.evt_index as tx_index, + bu.evt_block_number as block_number, + bu._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bu.trove_id, + bt._batchDebtShares/1e18 as batch_debt_shares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId + ORDER BY bu.evt_block_time - bt.evt_block_time ASC + ) AS rn + FROM reth_bu_with_trove bu + JOIN liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt + ON bu.trove_id = bt._troveId + AND ( + bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event + OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) + ) +) + +, reth_bu_events_enriched as ( + select + 'rETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , batch_debt_shares * debt_to_shares_ratio as debt + , stake + , 'batchUpdated' as event + from reth_bu_closest_previous_events + where rn = 1 +) + +, reth_bt_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + + bt.evt_block_time as block_time, + bt.evt_tx_hash as tx_hash, + bt.evt_index as tx_index, + bt.evt_block_number as block_number, + bt._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bt._troveId as trove_id, + bt._batchDebtShares/1e18 as _batchDebtShares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId + ORDER BY bt.evt_block_time - bu.evt_block_time ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt + JOIN liquity_v2_ethereum.trovemanager_reth_evt_batchupdated bu + ON bu._interestBatchManager = bt._interestBatchManager + AND ( + bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event + OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) + ) +) + +, reth_bt_events_enriched as ( + select + 'rETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , _batchDebtShares * debt_to_shares_ratio as debt + , stake + , 'batchTroveUpdated' as event + from reth_bt_closest_previous_events + where rn = 1 +) + +, wsteth_trove_manager_address_updates as ( + SELECT evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + _interestBatchManager as interest_batch_manager + FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt + UNION ALL + SELECT + evt_block_number, + evt_block_time, + evt_index, + _troveId as trove_id, + 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager + FROM liquity_v2_ethereum.trovemanager_wsteth_evt_troveupdated +) + +, wsteth_bu_with_trove_rows as ( + SELECT + bu.*, + tm.trove_id, + tm.interest_batch_manager, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id + ORDER BY bu.evt_block_number - tm.evt_block_number ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchupdated bu + LEFT JOIN wsteth_trove_manager_address_updates tm + ON ( + tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event + OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) + ) +) + +, wsteth_bu_with_trove as + ( + select * + from wsteth_bu_with_trove_rows + where rn = 1 and _interestBatchManager = interest_batch_manager + ) + +, wsteth_bu_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + bu.evt_block_time as block_time, + bu.evt_tx_hash as tx_hash, + bu.evt_index as tx_index, + bu.evt_block_number as block_number, + bu._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bu.trove_id, + bt._batchDebtShares/1e18 as batch_debt_shares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId + ORDER BY bu.evt_block_time - bt.evt_block_time ASC + ) AS rn + FROM wsteth_bu_with_trove bu + JOIN liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt + ON bu.trove_id = bt._troveId + AND ( + bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event + OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) + ) +) + +, wsteth_bu_events_enriched as ( + select + 'wstETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , batch_debt_shares * debt_to_shares_ratio as debt + , stake + , 'batchUpdated' as event + from wsteth_bu_closest_previous_events + where rn = 1 +) + +, wsteth_bt_closest_previous_events AS ( + SELECT + cast(bu._debt/1e18 as double) + / + cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) + as debt_to_shares_ratio, + + bt.evt_block_time as block_time, + bt.evt_tx_hash as tx_hash, + bt.evt_index as tx_index, + bt.evt_block_number as block_number, + bt._interestBatchManager as interest_batch_manager, + bu._annualInterestRate/1e16 as interest_rates, + bt._troveId as trove_id, + bt._batchDebtShares/1e18 as _batchDebtShares, + bt._coll/1e18 as collateral, + bt._stake/1e18 as stake, + ROW_NUMBER() OVER ( + PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId + ORDER BY bt.evt_block_time - bu.evt_block_time ASC + ) AS rn + FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt + JOIN liquity_v2_ethereum.trovemanager_wsteth_evt_batchupdated bu + ON bu._interestBatchManager = bt._interestBatchManager + AND ( + bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event + OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) + ) +) + +, wsteth_bt_events_enriched as ( + select + 'wstETH' as collateral_type + , tx_hash + , tx_index + , block_time + , block_number + , interest_batch_manager + , interest_rates + , trove_id + --, _batchDebtShares + , collateral + , _batchDebtShares * debt_to_shares_ratio as debt + , stake + , 'batchTroveUpdated' as event + from wsteth_bt_closest_previous_events + where rn = 1 +) + +-- Now we union all results +-- and add troveUpdated events (super easy) +, pre_final_results as ( + select * from weth_bu_events_enriched + union all + select * from weth_bt_events_enriched + union all + select + 'WETH' as collateral_type + , evt_tx_hash as tx_hash + , evt_index as tx_index + , evt_block_time as block_time + , evt_block_number as evt_block_number + , 0x0000000000000000000000000000000000000000 as interest_batch_manager + , _annualInterestRate/1e16 as interest_rates + , _troveId as trove_id + , _coll/1e18 as collateral + , _debt/1e18 as debt + , _stake/1e18 as stake + , 'troveUpdated' as event + from liquity_v2_ethereum.trovemanager_weth_evt_troveupdated + union all + select * from reth_bu_events_enriched + union all + select * from reth_bt_events_enriched + union all + select + 'rETH' as collateral_type + , evt_tx_hash as tx_hash + , evt_index as tx_index + , evt_block_time as block_time + , evt_block_number as evt_block_number + , 0x0000000000000000000000000000000000000000 as interest_batch_manager + , _annualInterestRate/1e16 as interest_rates + , _troveId as trove_id + , _coll/1e18 as collateral + , _debt/1e18 as debt + , _stake/1e18 as stake + , 'troveUpdated' as event + from liquity_v2_ethereum.trovemanager_reth_evt_troveupdated + union all + select * from wsteth_bu_events_enriched + union all + select * from wsteth_bt_events_enriched + union all + select + 'wstETH' as collateral_type + , evt_tx_hash as tx_hash + , evt_index as tx_index + , evt_block_time as block_time + , evt_block_number as evt_block_number + , 0x0000000000000000000000000000000000000000 as interest_batch_manager + , _annualInterestRate/1e16 as interest_rates + , _troveId as trove_id + , _coll/1e18 as collateral + , _debt/1e18 as debt + , _stake/1e18 as stake + , 'troveUpdated' as event + from liquity_v2_ethereum.trovemanager_wsteth_evt_troveupdated +) + +-- Final results with deduplication +-- (there can be duplicates because both batchUpdated & batchTroveUpdated events +-- in this case, keep the batchTroveUpdated event) +select + collateral_type, + tx_hash, + tx_index, + block_time, + block_number, + interest_rates, + interest_batch_manager, + trove_id, + collateral, + debt, + stake, + event +from ( + select + *, + row_number() over ( + partition by collateral_type, tx_hash, block_number, trove_id + order by tx_index desc, (case when event = 'batchTroveUpdated' then 1 else 0 end) desc + ) as rn + from pre_final_results +) ranked +where rn = 1 diff --git a/dune_dashboard/uncap/interest_rates.sql b/dune_dashboard/uncap/interest_rates.sql index 4c8ba15..54df114 100644 --- a/dune_dashboard/uncap/interest_rates.sql +++ b/dune_dashboard/uncap/interest_rates.sql @@ -1,6 +1,7 @@ -- Uncap Protocol - Interest Rates Over Time -- Network: Starknet Mainnet -- Purpose: Track debt-weighted average interest rates hourly +-- Simplified version (no batch management) for USDU with @@ -30,7 +31,7 @@ hours as ( hourly_rates as ( select *, - lead(hour, 1, current_timestamp) over (partition by trove_id, collateral_type order by hour asc) as next_hour + lead(hour, 1, date_trunc('hour', cast(now() as timestamp)) + interval '1' hour) over (partition by trove_id, collateral_type order by hour asc) as next_hour from ( select date_trunc('hour', block_time) as hour, @@ -38,7 +39,7 @@ hourly_rates as ( collateral_type, max_by(interest_rates, (block_number, tx_index)) as interest_rates from - query_5905300 -- trove_operations + query_6091847 -- trove_operations group by 1, 2, 3 ) ), @@ -57,10 +58,12 @@ filled_rates as ( and h.hour < c.next_hour ), +-- Get hourly debts from trove_debt_and_interest query +-- TODO: Replace query_YYYYYY with actual query ID for trove_debt_and_interest.sql hourly_debts as ( select *, - lead(hour, 1, current_timestamp) over (partition by trove_id, collateral_type order by hour asc) as next_hour + lead(hour, 1, date_trunc('hour', cast(now() as timestamp)) + interval '1' hour) over (partition by trove_id, collateral_type order by hour asc) as next_hour from ( select date_trunc('hour', block_time) as hour, @@ -68,7 +71,7 @@ hourly_debts as ( collateral_type, max_by(debt, (block_number, tx_index)) as debt from - query_5905310 -- trove_debt_and_interest + query_6091903 -- trove_debt_and_interest group by 1, 2, 3 ) ), @@ -100,31 +103,17 @@ combine_with_debts as ( and fr.collateral_type = fd.collateral_type ), -current_rates as ( - select - collateral_type, - sum(interest_rates * debt) / sum(debt) as interest_rates - from - combine_with_debts - where interest_rates > 0 and debt > 0 - and hour >= now() - interval '24' hour - group by 1 -), - -total_rates as ( - select - sum(interest_rates * debt) / sum(debt) as interest_rates - from - combine_with_debts - where interest_rates > 0 and debt > 0 - and hour >= date_trunc('hour', now() - interval '30' day) -), - hourly_rates_summary as ( select date_trunc('hour', hour) as hour, collateral_type, - (sum(interest_rates * debt) / sum(debt))/100 as interest_rates + (sum(interest_rates * debt) / sum(debt))/100 as hourly_rates, + -- Daily rates: moving 24h window from this hour backwards + (sum(sum(interest_rates * debt)) over (partition by collateral_type order by date_trunc('hour', hour) rows between 24 preceding and current row) / + sum(sum(debt)) over (partition by collateral_type order by date_trunc('hour', hour) rows between 24 preceding and current row))/100 as daily_rates, + -- Monthly rates: moving 30-day window from this hour backwards + (sum(sum(interest_rates * debt)) over (partition by collateral_type order by date_trunc('hour', hour) rows between 720 preceding and current row) / + sum(sum(debt)) over (partition by collateral_type order by date_trunc('hour', hour) rows between 720 preceding and current row))/100 as monthly_rates from combine_with_debts where interest_rates > 0 and debt > 0 @@ -133,17 +122,11 @@ hourly_rates_summary as ( ) select - hrs.*, - cr.interest_rates as latest_rates, - tr.interest_rates as total_rates -from -hourly_rates_summary hrs -left join -current_rates cr - on hrs.collateral_type = cr.collateral_type -left join -total_rates tr - on 1 = 1 + hour, + collateral_type, + hourly_rates, + daily_rates, + monthly_rates +from hourly_rates_summary where hour >= date_trunc('hour', now() - interval '30' day) -order by hrs.hour desc, lower(hrs.collateral_type) desc - +order by hour desc, lower(collateral_type) desc diff --git a/dune_dashboard/uncap/interest_rates/trove_operations.sql b/dune_dashboard/uncap/interest_rates/trove_operations.sql new file mode 100644 index 0000000..1247086 --- /dev/null +++ b/dune_dashboard/uncap/interest_rates/trove_operations.sql @@ -0,0 +1,44 @@ +-- Uncap Protocol - Trove Operations +-- Network: Starknet Mainnet +-- Purpose: Track all trove operations (open, close, adjust, etc.) +-- Equivalent to query_5155365 for Liquity v2 + +with + +cfg as ( + select + 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 as events_emitter, + 2759369 as deployment_block +) + +select + from_address as contract_address, + 'WBTC' as collateral_type, + transaction_hash as tx_hash, + event_index as tx_index, + block_time, + block_number, + -- Extract trove_id (u256 = data[1] low, data[2] high) + lower(concat('0x', + "right"(substring(to_hex(data[2]), 3), 32), + "right"(substring(to_hex(data[1]), 3), 32) + )) as trove_id, + -- Extract operation type (u8 stored as felt = data[3]) + cast(bytearray_to_uint256(data[3]) as bigint) as operation, + -- Extract annual interest rate (u256 = data[4] low, data[5] high) + cast(bytearray_to_uint256(data[4]) as double) / 1e16 as interest_rates, + -- Extract debt_increase_from_redist (u256 = data[6] low, data[7] high) + cast(bytearray_to_uint256(data[6]) as double) / 1e18 as debt_increase_from_redist, + -- Extract debt_increase_from_upfront_fee (u256 = data[8] low, data[9] high) + cast(bytearray_to_uint256(data[8]) as double) / 1e18 as upfront_fees, + -- Extract debt_change_from_operation (i257 = data[10] low, data[11] high, data[12] sign) + cast(bytearray_to_uint256(data[10]) as double) / 1e18 as debt_change, + -- Extract coll_increase_from_redist (u256 = data[13] low, data[14] high) + cast(bytearray_to_uint256(data[13]) as double) / 1e18 as coll_increase_from_redist, + -- Extract coll_change_from_operation (i257 = data[15] low, data[16] high, data[17] sign) + cast(bytearray_to_uint256(data[15]) as double) / 1e18 as collateral_change +from starknet.events +where from_address = (select events_emitter from cfg) + and keys[1] = 0x02c0b995ca82e6ae9ef5d19fd0659d7748540971842bd71843cfc904d240c4bf -- TroveOperation event + and block_number >= (select deployment_block from cfg) +order by block_number desc, event_index desc diff --git a/dune_dashboard/liq/trove_debt_and_interest.sql b/dune_dashboard/uncap/interest_rates/trove_updates.sql similarity index 85% rename from dune_dashboard/liq/trove_debt_and_interest.sql rename to dune_dashboard/uncap/interest_rates/trove_updates.sql index 92d4d76..95b0b68 100644 --- a/dune_dashboard/liq/trove_debt_and_interest.sql +++ b/dune_dashboard/uncap/interest_rates/trove_updates.sql @@ -1,4 +1,4 @@ --- Uncap Protocol - Trove Debt and Interest Rates +-- Uncap Protocol - Trove Updates (Debt and Interest Rates) -- Network: Starknet Mainnet -- Purpose: Track debt and interest rates for all troves over time -- Simplified version (no batch management) equivalent to query_5720005 for Liquity v2 @@ -7,7 +7,7 @@ with cfg as ( select - 0x06dcfc4ce8ca0c664d5f6c5ee434c9379d15e616246db092cd9e21e5871e4377 as events_emitter, + 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 as events_emitter, 2759369 as deployment_block ) @@ -30,10 +30,10 @@ select cast(bytearray_to_uint256(data[7]) as double) / 1e18 as stake, -- Extract annual interest rate (u256 = data[9] low, data[10] high) cast(bytearray_to_uint256(data[9]) as double) / 1e16 as interest_rates, - 0x0000000000000000000000000000000000000000 as interest_batch_manager, -- No batch manager + 0x0000000000000000000000000000000000000000 as interest_batch_manager, -- No batch manager in simplified version 'troveUpdated' as event from starknet.events where from_address = (select events_emitter from cfg) - and keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 + and keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 -- TroveUpdated event and block_number >= (select deployment_block from cfg) order by block_number desc, event_index desc diff --git a/dune_dashboard/uncap/protocol_revenue.sql b/dune_dashboard/uncap/protocol_revenue.sql new file mode 100644 index 0000000..330fefb --- /dev/null +++ b/dune_dashboard/uncap/protocol_revenue.sql @@ -0,0 +1,176 @@ +-- Uncap Protocol - Protocol Revenue +-- Network: Starknet Mainnet +-- Purpose: Track protocol revenue from redemption fees, interest, and liquidations +-- Start date: October 6, 2025 + +with + +cfg as ( + select + 0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 as usdu_token, + 0x001ba4a9e2e86a41c6ed15016eda0404d12bf7b01052cccff1ace84d818335c7 as stability_pool, + 0x0477a98816f0298D678a8C74Bd06E898Ee4E3bB62FdB9bb05c397f3135aE8398 as pil_address, + 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 as events_emitter, + 2759369 as deployment_block +), + +time_seq AS ( + select + sequence( + CAST('2025-10-06' as timestamp), + date_trunc('day', cast(now() as timestamp)), + interval '1' day + ) as time +), + +days AS ( + select + time.time as day + from time_seq + cross join unnest(time) as time(time) +), + +-- Redemption fees paid to troves (in WWBTC) +-- Event: RedemptionFeePaidToTrove +-- keys[1] = event selector +-- data[1], data[2] = trove_id (u256) +-- data[3], data[4] = coll_fee (u256) +wbtc_fee as ( + select + date_trunc('minute', block_time) as minute, + transaction_hash as evt_tx_hash, + cast(bytearray_to_uint256(data[3]) as double) / 1e18 as fee -- WWBTC has 18 decimals + from starknet.events + where from_address = (select events_emitter from cfg) + and keys[1] = 0x03b605674b6c4bd98149476fb8ed7db31a620da8d46f77070759e7b152252ef6 -- RedemptionFeePaidToTrove event selector + and block_number >= (select deployment_block from cfg) +), + +-- Add WBTC price to redemption fees +add_price as ( + select + wf.minute, + wf.evt_tx_hash, + wf.fee, + wp.wbtc_price as price, + wf.fee * wp.wbtc_price as fee_usd + from + wbtc_fee wf + left join ( + select + DATE(timestamp AT TIME ZONE 'UTC') as day, + price as wbtc_price + from dune.openblocklabs.result_starknet_prices_daily + where contract_address = 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac + ) wp + on date_trunc('day', wf.minute) = wp.day +), + +-- Interest rewards minted to StabilityPool (75% of interest) +-- Using Transfer events where from = 0x0 (mint) and to = StabilityPool +-- Event: Transfer +-- keys[1] = event selector (0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9) +-- keys[2] = from_address +-- keys[3] = to_address +-- data[1], data[2] = amount (u256) +interest_rewards as ( + select + case + when keys[3] = (select stability_pool from cfg) then 'WBTC' + when keys[3] = (select pil_address from cfg) then 'PIL' + end as collateral_type, + date_trunc('day', block_time) as day, + sum(cast(bytearray_to_uint256(data[1]) as double) / 1e18) as usdu_amount + from starknet.events + where from_address = (select usdu_token from cfg) + and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 -- Transfer event + and keys[2] = 0x0000000000000000000000000000000000000000000000000000000000000000 -- from = 0x0 (mint) + and (keys[3] = (select stability_pool from cfg) or keys[3] = (select pil_address from cfg)) -- to = StabilityPool or PIL + and block_number >= (select deployment_block from cfg) + group by 1, 2 +), + +-- Liquidation rewards (revenue from liquidations) +-- Revenue = collateral_sent_sp * price - debt_offset_sp +liquidation_rewards as ( + select + collateral_type, + date_trunc('day', block_time) as day, + sum(collateral_sent_sp * _price - debt_offset_sp) as liquidation_rewards + from + query_6089704 + group by 1, 2 +), + +all_interest as ( + select + day, + case + when collateral_type = 'WBTC' then 'SP Yield' + else 'PIL Yield' + end as fee_type, + sum(usdu_amount) as fee + from + interest_rewards + where collateral_type in ('WBTC', 'PIL') + group by 1, 2 + + union all + + select + date_trunc('day', minute) as day, + 'Redemption Fees' as fee_type, + sum(fee_usd) as fee + from + add_price + group by 1, 2 + + union all + + select + day, + 'Liquidation Rewards' as fee_type, + sum(liquidation_rewards) as fee + from + liquidation_rewards + where collateral_type = 'WBTC' + group by 1, 2 +), + +get_next_day as ( + select + *, + sum(fee) over (partition by fee_type order by day asc) as fee_total, + lead(day, 1, date_trunc('day', current_timestamp) + interval '1' day) over (partition by fee_type order by day asc) as next_day + from + all_interest +), + +-- Fill in missing days with forward-filled values +filled_days as ( + select + d.day, + c.fee_type, + c.fee_total, + case when c.day = d.day then c.fee else 0 end as fee + from + get_next_day c + inner join + days d + on c.day <= d.day + and d.day < c.next_day +) + +select + day, + fee_type, + fee, + fee_total, + sum(fee_total/1e3) over (partition by day) as fee_total_all_k, + sum(case + when day > date_trunc('day', now()) - interval '1' day then fee/1e3 + else 0 + end) over (order by day) as fee_param +from filled_days +where day >= cast('2025-10-06' as date) +order by day desc diff --git a/dune_dashboard/uncap/protocol_revenue/liquidations.sql b/dune_dashboard/uncap/protocol_revenue/liquidations.sql new file mode 100644 index 0000000..b28ae25 --- /dev/null +++ b/dune_dashboard/uncap/protocol_revenue/liquidations.sql @@ -0,0 +1,44 @@ +-- Uncap Protocol - Liquidations +-- Network: Starknet Mainnet +-- Purpose: Track all liquidation events with collateral and debt details + +with + +cfg as ( + select + 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 as events_emitter, + 2759369 as deployment_block +) + +select + from_address as contract_address, + 'WBTC' as collateral_type, + transaction_hash as tx_hash, + event_index as tx_index, + block_time, + block_number, + -- Extract debt_offset_by_SP (u256 = data[1] low, data[2] high) + cast(bytearray_to_uint256(data[1]) as double) / 1e18 as debt_offset_sp, + -- Extract debt_redistributed (u256 = data[3] low, data[4] high) + cast(bytearray_to_uint256(data[3]) as double) / 1e18 as debt_redistributed, + -- Extract strk_gas_compensation (u256 = data[5] low, data[6] high) + cast(bytearray_to_uint256(data[5]) as double) / 1e18 as strk_gas_compensation, + -- Extract coll_gas_compensation (u256 = data[7] low, data[8] high) + cast(bytearray_to_uint256(data[7]) as double) / 1e18 as coll_gas_compensation, + -- Extract coll_sent_to_SP (u256 = data[9] low, data[10] high) + cast(bytearray_to_uint256(data[9]) as double) / 1e18 as collateral_sent_sp, + -- Extract coll_redistributed (u256 = data[11] low, data[12] high) + cast(bytearray_to_uint256(data[11]) as double) / 1e18 as collateral_redistributed, + -- Extract coll_surplus (u256 = data[13] low, data[14] high) + cast(bytearray_to_uint256(data[13]) as double) / 1e18 as collateral_surplus, + -- Extract l_coll (u256 = data[15] low, data[16] high) + cast(bytearray_to_uint256(data[15]) as double) / 1e18 as l_coll, + -- Extract l_usdu_debt (u256 = data[17] low, data[18] high) + cast(bytearray_to_uint256(data[17]) as double) / 1e18 as l_usdu_debt, + -- Extract _price (u256 = data[19] low, data[20] high) + cast(bytearray_to_uint256(data[19]) as double) / 1e18 as _price +from starknet.events +where from_address = (select events_emitter from cfg) + and keys[1] = 0x0238a25785a13ab3138feb8f8f517e5a21a377cc1ad47809e9fd5e76daf01df7 + and block_number >= (select deployment_block from cfg) +order by block_number desc, event_index desc diff --git a/dune_dashboard/uncap/active_pool_updates.sql b/dune_dashboard/uncap/tvl/active_pool_updates.sql similarity index 100% rename from dune_dashboard/uncap/active_pool_updates.sql rename to dune_dashboard/uncap/tvl/active_pool_updates.sql diff --git a/dune_dashboard/uncap/token_holders.sql b/dune_dashboard/uncap/tvl/token_holders.sql similarity index 100% rename from dune_dashboard/uncap/token_holders.sql rename to dune_dashboard/uncap/tvl/token_holders.sql diff --git a/dune_dashboard/uncap/tvl.sql b/dune_dashboard/uncap/tvl/tvl.sql similarity index 100% rename from dune_dashboard/uncap/tvl.sql rename to dune_dashboard/uncap/tvl/tvl.sql diff --git a/dune_dashboard/uncap/usdu_holders_no_dust.sql b/dune_dashboard/uncap/usdu_holders_no_dust.sql deleted file mode 100644 index 0150e43..0000000 --- a/dune_dashboard/uncap/usdu_holders_no_dust.sql +++ /dev/null @@ -1,110 +0,0 @@ --- Uncap Protocol - USDU Token Holders Query --- Network: Starknet Mainnet --- Purpose: Track daily USDU token holder balances over time - -with - --- Configuration -cfg as ( - select - 2759669 as deployment_block, - 0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 as usdu_token_address -), - --- Extract Transfer events from Starknet --- Transfer event structure on Starknet: --- keys[1] = event selector (0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9) --- keys[2] = from_address --- keys[3] = to_address --- data[1], data[2] = amount (u256 as low, high) -erc20_aggregated as ( - select - day, - address, - token_address, - token_symbol, - sum(amount) as amount - from ( - -- Incoming transfers (to address) - select - date_trunc('day', block_time) as day, - keys[3] as address, - from_address as token_address, - 'USDU' as token_symbol, - sum(cast(bytearray_to_uint256(data[1]) as double) / 1e18) as amount - from starknet.events - where from_address = (select usdu_token_address from cfg) - and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 - and block_number >= (select deployment_block from cfg) - group by 1, 2, 3, 4 - - union all - - -- Outgoing transfers (from address) - select - date_trunc('day', block_time) as day, - keys[2] as address, - from_address as token_address, - 'USDU' as token_symbol, - -sum(cast(bytearray_to_uint256(data[1]) as double) / 1e18) as amount - from starknet.events - where from_address = (select usdu_token_address from cfg) - and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 - and block_number >= (select deployment_block from cfg) - group by 1, 2, 3, 4 - ) x - group by 1, 2, 3, 4 -), - -cum_aggregated as ( - select - day, - address, - token_address, - token_symbol, - sum(amount) over (partition by address, token_address, token_symbol order by day asc) as token_balance, - lead(day, 1, current_timestamp) over (partition by address, token_address, token_symbol order by day asc) as next_day - from - erc20_aggregated -), - --- Get deployment date from first block -deployment_date as ( - select min(date_trunc('day', time)) as start_date - from starknet.blocks - where number >= (select deployment_block from cfg) -), - -time_seq AS ( - select - sequence( - CAST((select start_date from deployment_date) as timestamp), - date_trunc('day', cast(now() as timestamp)), - interval '1' day - ) as time -), - -days AS ( - select - time.time as day - from time_seq - cross join unnest(time) as time(time) -) - -select - * -from ( -select - d.day, - c.address, - c.token_address, - c.token_symbol, - c.token_balance -from -cum_aggregated c -inner join -days d - on c.day <= d.day - and d.day < c.next_day -) -where token_balance > 0.001 -- Exclude users who hold dust From 38821d5de30e041e33e93123765cb9d2c4642ca9 Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:14:03 +0100 Subject: [PATCH 07/14] updated dune query --- dune_query_with_nft.sql | 633 +++++++++++++++------------------------- 1 file changed, 230 insertions(+), 403 deletions(-) diff --git a/dune_query_with_nft.sql b/dune_query_with_nft.sql index dca83ac..21c903d 100644 --- a/dune_query_with_nft.sql +++ b/dune_query_with_nft.sql @@ -6,439 +6,324 @@ WITH -- ============================================================================ -- CONFIGURATION CONSTANTS --- Define all contract addresses and static values used throughout the query -- ============================================================================ cfg AS ( SELECT - -- Deployment block number - -- This significantly improves query performance by avoiding unnecessary historical scans 2759669 AS deployment_block, - - -- Market address (WWBTC AddressesRegistry) - raw hex for calculations 0x042a37aa9263b01191286f0f800cc85c676441fb9d27d74bbf3ebcbf4e373d81 AS market_address_raw, - -- Same address cast to VARBINARY for output formatting CAST(0x042a37aa9263b01191286f0f800cc85c676441fb9d27d74bbf3ebcbf4e373d81 AS VARBINARY) AS market_address, - -- Human-readable protocol name 'Uncap Protocol' AS market_name, - -- Protocol owner/operator 'Uncap' AS market_owner, - -- WWBTC token contract address - raw hex 0x075d9e518f46a9ca0404fb0a7d386ce056dadf57fd9a0e8659772cb517be4a18 AS wwbtc_addr_raw, - -- Same WWBTC address cast to VARBINARY for output CAST(0x075d9e518f46a9ca0404fb0a7d386ce056dadf57fd9a0e8659772cb517be4a18 AS VARBINARY) AS wwbtc_addr, - -- USDU stablecoin contract address - raw hex 0x04695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c AS usdu_addr_raw, - -- Same USDU address cast to VARBINARY for output CAST(0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 AS VARBINARY) AS usdu_addr, - -- WBTC token contract address for price fetching 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac AS wbtc_price_addr, - -- Debt price (USDU is pegged to $1) 1.0 AS debt_price, - -- Protocol scaling factor (1e18 for 18 decimals) 1e18 AS protocol_scale, - -- TroveManagerEventsEmitter contract that emits all the events we need 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 AS events_emitter, - -- TroveNFT contract that tracks ownership via ERC721 Transfer events 0x04a1e17f1e4d5fdd839fa6a4c52a742b9d10871028b6ffcfb2f1de8dd5dc2cd4 AS trove_nft ), -- ============================================================================ --- EXTRACT TROVEUPDATED EVENTS --- These events fire whenever a trove's state changes (debt, collateral, etc) +-- 1. EXTRACT RAW EVENTS -- ============================================================================ + +-- Regular Trove Updates (Standard TroveUpdated events) +-- Captures updates for troves NOT in a batch (individual troves) trove_updated_events AS ( SELECT - -- Convert block timestamp to date (strips time, keeps only YYYY-MM-DD) DATE(block_time AT TIME ZONE 'UTC') AS date, - -- Keep full timestamp for ordering events within same day block_time AS evt_block_time, - -- Transaction hash for debugging/tracing transaction_hash AS evt_tx_hash, - -- Extract data from TroveUpdated event structure: - -- All fields are non-indexed, u256 values take 2 felts (low, high) - -- data[1], data[2] = trove_id (low, high) - -- data[3], data[4] = debt (low, high) - -- data[5], data[6] = coll (low, high) - -- data[7], data[8] = stake (low, high) - -- data[9], data[10] = annual_interest_rate (low, high) - -- Combine u256 parts as hex string - -- Each part is exactly 32 hex chars (128 bits) + 'REGULAR' AS update_type, LOWER(CONCAT('0x', - "right"(SUBSTRING(TO_HEX(data[2]), 3), 32), -- high part, exactly 32 chars - "right"(SUBSTRING(TO_HEX(data[1]), 3), 32) -- low part, exactly 32 chars - )) AS trove_id, -- Full u256 as hex string (64 chars total) - -- Total debt including principal + interest + redistributions, converted from wei + "right"(SUBSTRING(TO_HEX(data[2]), 3), 32), + "right"(SUBSTRING(TO_HEX(data[1]), 3), 32) + )) AS trove_id, CAST(bytearray_to_uint256(data[3]) AS DOUBLE) / 1e18 AS debt, - -- Total collateral including redistributions, converted from wei CAST(bytearray_to_uint256(data[5]) AS DOUBLE) / 1e18 AS coll, - -- Annual interest rate as decimal (e.g., 0.05 = 5%) - CAST(bytearray_to_uint256(data[9]) AS DOUBLE) / 1e18 AS annual_interest_rate + CAST(bytearray_to_uint256(data[9]) AS DOUBLE) / 1e18 AS annual_interest_rate, + NULL AS shares, + NULL AS interest_batch_manager FROM starknet.events WHERE from_address = (SELECT events_emitter FROM cfg) - -- Filter to TroveUpdated events by selector AND keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 - -- Performance optimization: Only scan events after deployment AND block_number >= (SELECT deployment_block FROM cfg) ), --- ============================================================================ --- EXTRACT ERC721 TRANSFER EVENTS FROM TROVENFT --- Track NFT transfers to know current owner of each trove --- Transfer event structure: from_address, to_address, token_id --- Using Starknet's generic events table structure --- ============================================================================ +-- Batched Trove Updates (BatchedTroveUpdated events) +-- Captures updates for troves IN a batch. +-- Note: These events do NOT contain direct 'debt' values, but 'shares' which must be resolved against the batch state. +batched_trove_updated_events AS ( + SELECT + DATE(block_time AT TIME ZONE 'UTC') AS date, + block_time AS evt_block_time, + transaction_hash AS evt_tx_hash, + 'BATCHED' AS update_type, + LOWER(CONCAT('0x', + "right"(SUBSTRING(TO_HEX(data[2]), 3), 32), + "right"(SUBSTRING(TO_HEX(data[1]), 3), 32) + )) AS trove_id, + NULL AS debt, -- Debt is calculated dynamically via shares + CAST(bytearray_to_uint256(data[6]) AS DOUBLE) / 1e18 AS coll, + NULL AS annual_interest_rate, -- Rate is determined by the batch + CAST(bytearray_to_uint256(data[4]) AS DOUBLE) / 1e18 AS shares, + data[3] AS interest_batch_manager + FROM starknet.events + WHERE from_address = (SELECT events_emitter FROM cfg) + AND keys[1] = 0x03c054baf812ebdae34d56f1322bd89e0b0c4aed0829fe0e7658fce049d25f71 + AND block_number >= (SELECT deployment_block FROM cfg) +), + +-- Batch Updates (BatchUpdated events) +-- Tracks global state of interest batches (total debt, total shares) required to resolve individual trove debt. +batch_updated_events AS ( + SELECT + DATE(block_time AT TIME ZONE 'UTC') AS date, + block_time AS evt_block_time, + transaction_hash AS evt_tx_hash, + data[1] AS interest_batch_manager, + CAST(bytearray_to_uint256(data[3]) AS DOUBLE) / 1e18 AS batch_debt, + CAST(bytearray_to_uint256(data[7]) AS DOUBLE) / 1e18 AS batch_annual_interest_rate, + CAST(bytearray_to_uint256(data[11]) AS DOUBLE) / 1e18 AS batch_total_shares + FROM starknet.events + WHERE from_address = (SELECT events_emitter FROM cfg) + AND keys[1] = 0x039a6ae9867ab3ddba84021a987ac7776389a6cc5bad711eda5e4a44d1a2fe80 + AND block_number >= (SELECT deployment_block FROM cfg) +), + +-- NFT Transfers +-- Tracks ownership of troves (Troves are ERC721 tokens) nft_transfer_events AS ( SELECT - -- Date of the transfer DATE(block_time AT TIME ZONE 'UTC') AS date, - -- Full timestamp for ordering transfers block_time AS evt_block_time, - -- Transaction hash transaction_hash AS evt_tx_hash, - -- Extract data from Transfer event - -- ERC721 Transfer in this implementation: - -- keys[1] = event selector - -- keys[2] = from_address (indexed) - -- keys[3] = to_address (indexed) - -- keys[4], keys[5] = token_id (u256 as two felts, indexed) - -- Token ID is indexed in keys array (u256 = 2 felts at keys[4], keys[5]) LOWER(CONCAT('0x', - "right"(SUBSTRING(TO_HEX(keys[5]), 3), 32), -- high part, exactly 32 chars - "right"(SUBSTRING(TO_HEX(keys[4]), 3), 32) -- low part, exactly 32 chars - )) AS trove_id, -- Full u256 as hex string (64 chars total) - -- From address (0x0 for minting) + "right"(SUBSTRING(TO_HEX(keys[5]), 3), 32), + "right"(SUBSTRING(TO_HEX(keys[4]), 3), 32) + )) AS trove_id, keys[2] AS from_addr, - -- To address (new owner, 0x0 for burning) keys[3] AS to_addr FROM starknet.events WHERE from_address = (SELECT trove_nft FROM cfg) - -- Filter to Transfer events by selector AND keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 - -- Performance optimization: Only scan events after deployment AND block_number >= (SELECT deployment_block FROM cfg) ), -- ============================================================================ --- CALCULATE CURRENT NFT OWNER FOR EACH TROVE ON EACH DAY --- Track ownership changes over time, accounting for transfers and burns +-- 2. UNIFY AND AGGREGATE DAILY STATES (SPARSE) -- ============================================================================ -trove_ownership_history AS ( - SELECT - date, - evt_block_time, - trove_id, - to_addr AS owner, -- The recipient becomes the new owner (0x0 for burns) - -- Create a sequence number to track the order of transfers - ROW_NUMBER() OVER (PARTITION BY trove_id ORDER BY evt_block_time) AS transfer_seq - FROM nft_transfer_events - -- Include all transfers, including burns (to_addr = 0x0) + +-- Combine regular and batched updates into a single stream +all_trove_updates AS ( + SELECT * FROM trove_updated_events + UNION ALL + SELECT * FROM batched_trove_updated_events ), --- Get the current owner of each trove (carry forward from last transfer) --- This handles cases where troves are modified without ownership changes --- Burns (to_addr = 0x0) will set ownership_end_time for the previous owner -trove_current_owners AS ( - SELECT - trove_id, - owner, - evt_block_time AS ownership_start_time, - -- Get the next transfer time (or NULL if this is the current owner) - LEAD(evt_block_time) OVER (PARTITION BY trove_id ORDER BY evt_block_time) AS ownership_end_time - FROM trove_ownership_history - WHERE owner != 0x0000000000000000000000000000000000000000 -- Only track actual owners, not burns +-- Get the last known state for each trove on any day it had an event +daily_trove_states_sparse AS ( + SELECT * FROM ( + SELECT + date, + trove_id, + debt, + coll, + annual_interest_rate, + shares, + interest_batch_manager, + update_type, + ROW_NUMBER() OVER (PARTITION BY trove_id, date ORDER BY evt_block_time DESC) AS rn + FROM all_trove_updates + ) WHERE rn = 1 ), --- ============================================================================ --- GET LATEST TROVE STATE FOR EACH TROVE --- This will be used when we have Transfer events without TroveUpdated --- ============================================================================ -latest_trove_states AS ( - SELECT - trove_id, - debt, - coll, - annual_interest_rate, - ROW_NUMBER() OVER (PARTITION BY trove_id ORDER BY evt_block_time DESC) AS rn - FROM trove_updated_events +-- Get the last known state for each batch on any day it had an event +daily_batch_states_sparse AS ( + SELECT * FROM ( + SELECT + date, + interest_batch_manager, + batch_debt, + batch_total_shares, + batch_annual_interest_rate, + ROW_NUMBER() OVER (PARTITION BY interest_batch_manager, date ORDER BY evt_block_time DESC) AS rn + FROM batch_updated_events + ) WHERE rn = 1 ), -current_trove_states AS ( +-- Define ownership periods (start_time, end_time) for each trove owner +trove_ownership_history AS ( SELECT trove_id, - debt, - coll, - annual_interest_rate - FROM latest_trove_states - WHERE rn = 1 + to_addr AS owner, + evt_block_time AS start_time, + LEAD(evt_block_time) OVER (PARTITION BY trove_id ORDER BY evt_block_time) AS end_time + FROM nft_transfer_events + WHERE to_addr != 0x0000000000000000000000000000000000000000 -- Exclude burns ), -- ============================================================================ --- COMBINE TROVE DATA WITH CURRENT OWNERS --- Handle both TroveUpdated events AND Transfer-only events +-- 3. CREATE SPINES AND FILL FORWARD -- ============================================================================ --- First, get positions from TroveUpdated events -trove_positions_from_updates AS ( - SELECT - tu.date, - tu.evt_block_time, - tu.trove_id, - COALESCE( - LOWER(CONCAT('0x', TO_HEX(tco.owner))), - CONCAT('UNKNOWN_OWNER_', tu.trove_id) - ) AS user, - tu.debt AS entire_debt, - tu.coll AS entire_coll, - tu.annual_interest_rate, - CASE - WHEN tco.owner IS NULL THEN TRUE - ELSE FALSE - END AS is_orphan_trove - FROM trove_updated_events tu - LEFT JOIN trove_current_owners tco - ON tu.trove_id = tco.trove_id - AND tu.evt_block_time >= tco.ownership_start_time - AND (tu.evt_block_time < tco.ownership_end_time OR tco.ownership_end_time IS NULL) -), --- Then, get positions from Transfer events (carry forward last known state) -trove_positions_from_transfers AS ( - SELECT - toh.date, - toh.evt_block_time, - toh.trove_id, - LOWER(CONCAT('0x', TO_HEX(toh.owner))) AS user, - cts.debt AS entire_debt, - cts.coll AS entire_coll, - cts.annual_interest_rate, - FALSE AS is_orphan_trove - FROM trove_ownership_history toh - INNER JOIN current_trove_states cts - ON toh.trove_id = cts.trove_id - -- Only include actual ownership transfers, not burns - WHERE toh.owner != 0x0000000000000000000000000000000000000000 - -- Only include transfers that don't have a corresponding TroveUpdated on the same block - AND NOT EXISTS ( - SELECT 1 - FROM trove_updated_events tue - WHERE tue.trove_id = toh.trove_id - AND tue.evt_block_time = toh.evt_block_time - ) +-- Create a continuous sequence of dates to ensure we have a row for every day, even if no events occurred. +date_spine AS ( + SELECT x AS date + FROM UNNEST(SEQUENCE( + (SELECT MIN(date) FROM all_trove_updates), + DATE(CURRENT_TIMESTAMP AT TIME ZONE 'UTC') + )) t(x) ), --- Combine both sources -trove_positions AS ( - SELECT * FROM trove_positions_from_updates - UNION ALL - SELECT * FROM trove_positions_from_transfers +-- Trove Spine: Every date for every trove since its first interaction +trove_spine AS ( + SELECT t.trove_id, d.date + FROM (SELECT trove_id, MIN(date) as start_date FROM all_trove_updates GROUP BY 1) t + CROSS JOIN date_spine d + WHERE d.date >= t.start_date ), --- ============================================================================ --- GET DAILY END-OF-DAY POSITIONS --- For each day with activity, get the LAST state of each trove --- ============================================================================ -daily_trove_states_raw AS ( - SELECT - date, - trove_id, - user, - entire_debt, - entire_coll, - annual_interest_rate, - is_orphan_trove, - evt_block_time, - -- Rank events to get the last one per trove per day - ROW_NUMBER() OVER ( - PARTITION BY trove_id, date - ORDER BY evt_block_time DESC - ) AS rn - FROM trove_positions +-- Batch Spine: Every date for every batch since its inception +batch_spine AS ( + SELECT b.interest_batch_manager, d.date + FROM (SELECT interest_batch_manager, MIN(date) as start_date FROM batch_updated_events GROUP BY 1) b + CROSS JOIN date_spine d + WHERE d.date >= b.start_date ), -daily_trove_states AS ( +-- Filled Trove States: Carry forward the last known values to subsequent days using IGNORE NULLS +trove_states_filled AS ( SELECT - date, - trove_id, - user, - entire_debt, - entire_coll, - annual_interest_rate, - is_orphan_trove - FROM daily_trove_states_raw - WHERE rn = 1 -- Only keep the last event of each day for each trove -), - --- ============================================================================ --- HANDLE OWNERSHIP CONTINUITY --- Get daily ownership for each trove (for continuous position tracking) --- ============================================================================ -daily_trove_ownership AS ( - SELECT DISTINCT - date, - trove_id, - -- Find the owner on this date based on ownership periods - FIRST_VALUE(owner) OVER ( - PARTITION BY trove_id, date - ORDER BY ownership_start_time DESC - ) AS owner - FROM ( - SELECT DISTINCT date FROM trove_positions - ) d - CROSS JOIN trove_current_owners tco - WHERE d.date >= DATE(tco.ownership_start_time) - AND (d.date < DATE(tco.ownership_end_time) OR tco.ownership_end_time IS NULL) -), - --- ============================================================================ --- RECOMBINE POSITIONS WITH CONTINUOUS OWNERSHIP --- Update trove positions with carried-forward ownership --- ============================================================================ -positions_with_ownership AS ( - SELECT - dts.date, - dts.trove_id, - -- Use ownership from continuous tracking, keeping UNKNOWN_OWNER flags - COALESCE( - CASE - WHEN dto.owner IS NOT NULL THEN LOWER(CONCAT('0x', TO_HEX(dto.owner))) - ELSE NULL - END, - dts.user - ) AS user, - dts.entire_debt, - dts.entire_coll, - dts.annual_interest_rate, - dts.is_orphan_trove - FROM daily_trove_states dts - LEFT JOIN daily_trove_ownership dto - ON dts.trove_id = dto.trove_id AND dts.date = dto.date -), - --- Extend positions to include today even if no events -latest_positions AS ( - SELECT - trove_id, - user, - entire_debt, - entire_coll, - annual_interest_rate, - is_orphan_trove, - date AS last_event_date - FROM positions_with_ownership - WHERE date = (SELECT MAX(date) FROM positions_with_ownership) + ts.date, + ts.trove_id, + LAST_VALUE(dts.debt) IGNORE NULLS OVER ( + PARTITION BY ts.trove_id ORDER BY ts.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS debt, + LAST_VALUE(dts.coll) IGNORE NULLS OVER ( + PARTITION BY ts.trove_id ORDER BY ts.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS coll, + LAST_VALUE(dts.annual_interest_rate) IGNORE NULLS OVER ( + PARTITION BY ts.trove_id ORDER BY ts.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS annual_interest_rate, + LAST_VALUE(dts.shares) IGNORE NULLS OVER ( + PARTITION BY ts.trove_id ORDER BY ts.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS shares, + LAST_VALUE(dts.interest_batch_manager) IGNORE NULLS OVER ( + PARTITION BY ts.trove_id ORDER BY ts.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS interest_batch_manager, + LAST_VALUE(dts.update_type) IGNORE NULLS OVER ( + PARTITION BY ts.trove_id ORDER BY ts.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS update_type + FROM trove_spine ts + LEFT JOIN daily_trove_states_sparse dts + ON ts.trove_id = dts.trove_id AND ts.date = dts.date ), -positions_extended AS ( - SELECT * FROM positions_with_ownership - UNION ALL - -- Add today's date with last known positions if no events today +-- Filled Batch States: Carry forward batch parameters (global debt, total shares) +batch_states_filled AS ( SELECT - DATE(CURRENT_TIMESTAMP AT TIME ZONE 'UTC') AS date, - trove_id, - user, - entire_debt, - entire_coll, - annual_interest_rate, - is_orphan_trove - FROM latest_positions - WHERE last_event_date < DATE(CURRENT_TIMESTAMP AT TIME ZONE 'UTC') + bs.date, + bs.interest_batch_manager, + LAST_VALUE(dbs.batch_debt) IGNORE NULLS OVER ( + PARTITION BY bs.interest_batch_manager ORDER BY bs.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS batch_debt, + LAST_VALUE(dbs.batch_total_shares) IGNORE NULLS OVER ( + PARTITION BY bs.interest_batch_manager ORDER BY bs.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS batch_total_shares, + LAST_VALUE(dbs.batch_annual_interest_rate) IGNORE NULLS OVER ( + PARTITION BY bs.interest_batch_manager ORDER BY bs.date + ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW + ) AS batch_annual_interest_rate + FROM batch_spine bs + LEFT JOIN daily_batch_states_sparse dbs + ON bs.interest_batch_manager = dbs.interest_batch_manager AND bs.date = dbs.date ), -- ============================================================================ --- DETERMINE DATE RANGE FOR EACH USER --- Note: Users might appear/disappear as NFTs are transferred --- Include UNKNOWN_OWNER entries for debugging +-- 4. RESOLVE OWNERSHIP AND CALCULATE DEBT -- ============================================================================ -user_range AS ( + +-- Determine the owner for each trove on each specific date +trove_ownership_resolved AS ( SELECT - user, - -- First day this user owned any trove - MIN(date) AS d0, - -- Today's date (we need data up to today for active positions) - DATE(CURRENT_TIMESTAMP AT TIME ZONE 'UTC') AS d1 - FROM positions_extended - WHERE user IS NOT NULL - GROUP BY user + ts.date, + ts.trove_id, + LOWER(CONCAT('0x', TO_HEX(toh.owner))) AS user + FROM trove_spine ts + JOIN trove_ownership_history toh + ON ts.trove_id = toh.trove_id + AND ts.date >= DATE(toh.start_time) + AND (toh.end_time IS NULL OR ts.date < DATE(toh.end_time)) ), --- ============================================================================ --- CREATE CONTINUOUS DATE SERIES --- Generate one row for every day from first activity to today --- ============================================================================ -user_spine AS ( +-- Calculate effective debt: +-- For Regular troves: use the 'debt' field directly. +-- For Batched troves: calculate debt as (trove_shares * batch_debt) / batch_total_shares. +positions_resolved AS ( SELECT - r.user, - x AS date -- Each date in the sequence becomes a row - FROM user_range r - -- SEQUENCE generates array of dates from d0 to d1 - -- UNNEST converts array into rows - CROSS JOIN UNNEST(SEQUENCE(r.d0, r.d1)) AS t(x) + t.date, + COALESCE(o.user, CONCAT('UNKNOWN_', t.trove_id)) AS user, + t.trove_id, + t.coll AS entire_coll, + CASE + WHEN t.update_type = 'BATCHED' THEN + CASE + WHEN b.batch_total_shares > 0 THEN + (t.shares * b.batch_debt) / b.batch_total_shares + ELSE 0 + END + ELSE t.debt + END AS entire_debt, + CASE + WHEN t.update_type = 'BATCHED' THEN b.batch_annual_interest_rate + ELSE t.annual_interest_rate + END AS annual_interest_rate, + FALSE AS has_orphan_troves + FROM trove_states_filled t + LEFT JOIN trove_ownership_resolved o + ON t.trove_id = o.trove_id AND t.date = o.date + LEFT JOIN batch_states_filled b + ON t.interest_batch_manager = b.interest_batch_manager AND t.date = b.date ), -- ============================================================================ --- FILL FORWARD POSITIONS FOR CONTINUITY --- Carry forward last known values for days without events +-- 5. AGGREGATE BY USER AND CALCULATE INTEREST -- ============================================================================ --- First, compute the carried-forward values -positions_filled AS ( - SELECT - s.date, - s.user, - pwo.trove_id, - -- Carry forward last known values for each trove - LAST_VALUE(pwo.entire_debt) OVER ( - PARTITION BY s.user, pwo.trove_id - ORDER BY s.date - ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - ) AS entire_debt, - LAST_VALUE(pwo.entire_coll) OVER ( - PARTITION BY s.user, pwo.trove_id - ORDER BY s.date - ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - ) AS entire_coll, - LAST_VALUE(pwo.annual_interest_rate) OVER ( - PARTITION BY s.user, pwo.trove_id - ORDER BY s.date - ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - ) AS annual_interest_rate, - LAST_VALUE(CASE WHEN pwo.is_orphan_trove THEN 1 ELSE 0 END) OVER ( - PARTITION BY s.user, pwo.trove_id - ORDER BY s.date - ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW - ) AS is_orphan_flag - FROM user_spine s - LEFT JOIN positions_extended pwo - ON pwo.user = s.user AND pwo.date = s.date -), --- Then aggregate across all troves for each user +-- Aggregate positions by user per day continuous_positions_raw AS ( SELECT date, user, - -- Sum across all user's troves SUM(entire_debt) AS entire_debt, SUM(entire_coll) AS entire_coll, - -- Debt-weighted average interest rate + -- Compute debt-weighted average interest rate for the user CASE WHEN SUM(entire_debt) > 0 THEN SUM(entire_debt * annual_interest_rate) / SUM(entire_debt) ELSE 0 - END AS annual_interest_rate, - -- Track if this user has any orphan troves - MAX(is_orphan_flag) AS has_orphan_troves - FROM positions_filled - WHERE entire_debt IS NOT NULL OR entire_coll IS NOT NULL + END AS annual_interest_rate + FROM positions_resolved + WHERE (entire_debt > 0 OR entire_coll > 0) GROUP BY date, user ), --- Apply daily interest accrual using simple interest --- Note: USDU uses simple interest, not compound interest +-- Apply daily interest accrual (Simple Interest) continuous_positions AS ( SELECT date, user, - -- For each day, calculate the debt with accrued interest - -- If it's been N days since last update, add N days of interest entire_debt * (1 + annual_interest_rate * DATE_DIFF('day', LAG(date, 1, date) OVER (PARTITION BY user ORDER BY date), @@ -446,15 +331,11 @@ continuous_positions AS ( ) / 365 ) AS entire_debt, entire_coll, - annual_interest_rate, - has_orphan_troves + annual_interest_rate FROM continuous_positions_raw ), --- ============================================================================ --- CALCULATE DAILY CHANGES --- Track day-over-day changes in positions for interest calculation --- ============================================================================ +-- Calculate daily changes (deltas) for reporting daily_changes AS ( SELECT date, @@ -462,29 +343,19 @@ daily_changes AS ( entire_debt, entire_coll, annual_interest_rate, - has_orphan_troves, - -- Get yesterday's debt for comparison LAG(entire_debt, 1) OVER (PARTITION BY user ORDER BY date) AS prev_debt, - -- Get yesterday's collateral LAG(entire_coll, 1) OVER (PARTITION BY user ORDER BY date) AS prev_coll, - -- Calculate debt change from yesterday to today - COALESCE(entire_debt, 0) - COALESCE( - LAG(entire_debt, 1) OVER (PARTITION BY user ORDER BY date), 0 - ) AS debt_change, - -- Calculate collateral change from yesterday to today - COALESCE(entire_coll, 0) - COALESCE( - LAG(entire_coll, 1) OVER (PARTITION BY user ORDER BY date), 0 - ) AS coll_change + COALESCE(entire_debt, 0) - COALESCE(LAG(entire_debt, 1) OVER (PARTITION BY user ORDER BY date), 0) AS debt_change, + COALESCE(entire_coll, 0) - COALESCE(LAG(entire_coll, 1) OVER (PARTITION BY user ORDER BY date), 0) AS coll_change FROM continuous_positions - -- Only include days where user has active positions WHERE entire_debt > 0 OR entire_coll > 0 ), -- ============================================================================ --- PRICE DATA --- Using OpenBlockLabs price aggregator +-- 6. PRICES AND FINAL OUTPUT -- ============================================================================ --- Daily prices + +-- Daily Price Data from OpenBlockLabs daily_prices AS ( SELECT DATE(timestamp AT TIME ZONE 'UTC') AS date, @@ -494,20 +365,18 @@ daily_prices AS ( WHERE contract_address = cfg.wbtc_price_addr ), --- Latest price as fallback for current day +-- Latest Price Fallback latest_price AS ( SELECT price AS wbtc_price FROM ( - SELECT price, - ROW_NUMBER() OVER (ORDER BY timestamp DESC) AS rn + SELECT price, ROW_NUMBER() OVER (ORDER BY timestamp DESC) AS rn FROM dune.openblocklabs.result_starknet_prices_daily CROSS JOIN cfg WHERE contract_address = cfg.wbtc_price_addr - ) t - WHERE rn = 1 + ) t WHERE rn = 1 ), --- Combine daily and latest prices +-- Merged Price Data price_data AS ( SELECT d.date, @@ -517,88 +386,47 @@ price_data AS ( CROSS JOIN latest_price lp ), --- ============================================================================ --- FORMAT FINAL OUTPUT --- Combine all data and format according to required schema --- ============================================================================ +-- Final Formatting final_output AS ( SELECT - -- Date for this row of data dc.date, - -- User address (current NFT owner, may include UNKNOWN_OWNER flags) dc.user, - - -- Market identification (from config) cfg.market_address, cfg.market_name, cfg.market_owner, - - -- Collateral asset information cfg.wwbtc_addr AS asset, 'WWBTC' AS asset_symbol, - 18 AS asset_decimals, -- Values are already scaled to 18 decimals in events - - -- Debt asset information + 18 AS asset_decimals, cfg.usdu_addr AS debt_asset, 'USDU' AS debt_asset_symbol, 18 AS debt_decimals, - - -- Accumulator = 1.0 (we use entire_debt which already includes all interest) 1.0 AS latest_accumulator, cfg.protocol_scale, - - -- Prices pd.wbtc_price AS asset_price, cfg.debt_price AS debt_price, - - -- Raw balance data dc.entire_coll AS total_supplied_raw, dc.entire_debt AS total_borrowed_raw, - - -- Daily collateral changes (positive = deposits, negative = withdrawals) dc.coll_change AS raw_amount_deposit, dc.coll_change AS adjusted_amount_deposit, - - -- Daily debt changes (positive = new borrowing, negative = repayments) dc.debt_change AS raw_amount_borrowed, dc.debt_change AS adjusted_amount_borrowed, - - -- Adjusted balances (raw * accumulator, but accumulator = 1.0) dc.entire_coll AS total_supplied_adjusted, dc.entire_debt AS total_borrowed_adjusted, - - -- Token amounts (same as adjusted) dc.entire_coll AS total_supplied_tokens, dc.entire_debt AS total_borrowed_tokens, - - -- USD values dc.entire_coll * pd.wbtc_price AS total_supplied_usd, dc.entire_debt * cfg.debt_price AS total_borrowed_usd, (dc.entire_coll * pd.wbtc_price) - (dc.entire_debt * cfg.debt_price) AS net_usd, - - -- Interest rate data dc.annual_interest_rate AS effective_apr_daily, - - -- Daily interest calculation: debt * (annual_rate / 365) - -- This represents the interest accrued for holding this debt position for one day dc.entire_debt * (dc.annual_interest_rate / 365) AS interest_tokens_daily, - - -- Interest in USD dc.entire_debt * (dc.annual_interest_rate / 365) * cfg.debt_price AS interest_usd_daily, - - -- Debug flag: indicates if this user has orphan troves - dc.has_orphan_troves - + FALSE AS has_orphan_troves FROM daily_changes dc CROSS JOIN cfg - LEFT JOIN price_data pd - ON pd.date = dc.date + LEFT JOIN price_data pd ON pd.date = dc.date ) --- ============================================================================ --- FINAL SELECT --- Output only rows with actual positions, ordered by date and user --- ============================================================================ +-- Final Selection and Ordering SELECT date, user, @@ -619,7 +447,6 @@ SELECT interest_usd_daily FROM final_output WHERE - -- Only include rows where user has some position (total_supplied_adjusted IS NOT NULL AND total_supplied_adjusted <> 0) OR (total_borrowed_adjusted IS NOT NULL AND total_borrowed_adjusted <> 0) From c7e6ea16a51555fbfd9d11b8c27eedbc1f0edefb Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:16:37 +0100 Subject: [PATCH 08/14] rename points and obl folders --- dune_query_with_nft.sql => dune_dashboard/obl/obl_query.sql | 0 .../{obl/trove_hourly_snapshots.sql => points/points_query.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename dune_query_with_nft.sql => dune_dashboard/obl/obl_query.sql (100%) rename dune_dashboard/{obl/trove_hourly_snapshots.sql => points/points_query.sql} (100%) diff --git a/dune_query_with_nft.sql b/dune_dashboard/obl/obl_query.sql similarity index 100% rename from dune_query_with_nft.sql rename to dune_dashboard/obl/obl_query.sql diff --git a/dune_dashboard/obl/trove_hourly_snapshots.sql b/dune_dashboard/points/points_query.sql similarity index 100% rename from dune_dashboard/obl/trove_hourly_snapshots.sql rename to dune_dashboard/points/points_query.sql From 8232627ff2eba1a4961c510410edfcca1aa92d73 Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:17:18 +0100 Subject: [PATCH 09/14] rename dune_dashboard to dune_queries --- dune_dashboard/liq/interest_rates | 141 ----- dune_dashboard/liq/liquidations.sql | 61 --- dune_dashboard/liq/protocol_revenue.sql | 170 ------ dune_dashboard/liq/total_bold_holders/all.sql | 38 -- .../total_bold_holders/bold_token_holders.sql | 82 --- .../liq/total_bold_holders/sp_depositors.sql | 74 --- .../total_bold_holders/total_bold_holders.sql | 14 - dune_dashboard/liq/total_usdu_supply.sql | 15 - dune_dashboard/liq/trove_operations.sql | 49 -- dune_dashboard/liq/trove_updates.sql | 518 ------------------ .../obl/obl_query.sql | 0 .../points/points_query.sql | 0 .../uncap/interest_rates.sql | 0 .../uncap/interest_rates/trove_operations.sql | 0 .../uncap/interest_rates/trove_updates.sql | 0 .../uncap/protocol_revenue.sql | 0 .../uncap/protocol_revenue/liquidations.sql | 0 .../uncap/tvl/active_pool_updates.sql | 0 .../uncap/tvl/token_holders.sql | 0 .../uncap/tvl/tvl.sql | 0 20 files changed, 1162 deletions(-) delete mode 100644 dune_dashboard/liq/interest_rates delete mode 100644 dune_dashboard/liq/liquidations.sql delete mode 100644 dune_dashboard/liq/protocol_revenue.sql delete mode 100644 dune_dashboard/liq/total_bold_holders/all.sql delete mode 100644 dune_dashboard/liq/total_bold_holders/bold_token_holders.sql delete mode 100644 dune_dashboard/liq/total_bold_holders/sp_depositors.sql delete mode 100644 dune_dashboard/liq/total_bold_holders/total_bold_holders.sql delete mode 100644 dune_dashboard/liq/total_usdu_supply.sql delete mode 100644 dune_dashboard/liq/trove_operations.sql delete mode 100644 dune_dashboard/liq/trove_updates.sql rename {dune_dashboard => dune_queries}/obl/obl_query.sql (100%) rename {dune_dashboard => dune_queries}/points/points_query.sql (100%) rename {dune_dashboard => dune_queries}/uncap/interest_rates.sql (100%) rename {dune_dashboard => dune_queries}/uncap/interest_rates/trove_operations.sql (100%) rename {dune_dashboard => dune_queries}/uncap/interest_rates/trove_updates.sql (100%) rename {dune_dashboard => dune_queries}/uncap/protocol_revenue.sql (100%) rename {dune_dashboard => dune_queries}/uncap/protocol_revenue/liquidations.sql (100%) rename {dune_dashboard => dune_queries}/uncap/tvl/active_pool_updates.sql (100%) rename {dune_dashboard => dune_queries}/uncap/tvl/token_holders.sql (100%) rename {dune_dashboard => dune_queries}/uncap/tvl/tvl.sql (100%) diff --git a/dune_dashboard/liq/interest_rates b/dune_dashboard/liq/interest_rates deleted file mode 100644 index 3d32279..0000000 --- a/dune_dashboard/liq/interest_rates +++ /dev/null @@ -1,141 +0,0 @@ -with - -time_seq as ( - select - sequence( - CAST('2024-11-01' as timestamp), - date_trunc('hour', cast(now() as timestamp)), - interval '1' hour - ) as time -), - -hours as ( - select - time.time as hour - from time_seq - cross join unnest(time) as time(time) -), - -hourly_rates as ( - select - *, - lead(hour, 1, current_timestamp) over (partition by trove_id, collateral_type order by hour asc) as next_hour - from ( - select - date_trunc('hour', block_time) as hour, - trove_id, - collateral_type, - max_by(interest_rates, (block_number, tx_index)) as interest_rates - from - query_5155365 - group by 1, 2, 3 - ) -), - -filled_rates as ( - select - h.hour, - c.trove_id, - c.collateral_type, - c.interest_rates - from - hourly_rates c - inner join - hours h - on c.hour <= h.hour - and h.hour < c.next_hour -), - - -hourly_debts as ( - select - *, - lead(hour, 1, current_timestamp) over (partition by trove_id, collateral_type order by hour asc) as next_hour - from ( - select - date_trunc('hour', block_time) as hour, - trove_id, - collateral_type, - max_by(debt, (block_number, tx_index)) as debt - from - query_5720005 - group by 1, 2, 3 - ) -), - -filled_debts as ( - select - h.hour, - c.trove_id, - c.collateral_type, - c.debt - from - hourly_debts c - inner join - hours h - on c.hour <= h.hour - and h.hour < c.next_hour -), - -combine_with_debts as ( - select - fr.*, - fd.debt - from - filled_rates fr - inner join - filled_debts fd - on fr.hour = fd.hour - and fr.trove_id = fd.trove_id - and fr.collateral_type = fd.collateral_type -), - -current_rates as ( - select - collateral_type, - sum(interest_rates * debt) / sum(debt) as interest_rates - -- avg(interest_rates) as interest_rates - from - combine_with_debts - where interest_rates > 0 and debt > 0 - and hour >= now() - interval '24' hour - group by 1 -), - -total_rates as ( - select - sum(interest_rates * debt) / sum(debt) as interest_rates - -- avg(interest_rates) as interest_rates - from - combine_with_debts - where interest_rates > 0 and debt > 0 - and hour >= date_trunc('hour', now() - interval '30' day) -), - -hourly_rates_summary as ( - select - date_trunc('hour', hour) as hour, - collateral_type, - (sum(interest_rates * debt) / sum(debt))/100 as interest_rates - -- avg(interest_rates)/100 as interest_rates - from - combine_with_debts - where interest_rates > 0 and debt > 0 - and hour >= date_trunc('hour', now() - interval '30' day) - group by 1, 2 -) - -select - hrs.*, - cr.interest_rates as latest_rates, - tr.interest_rates as total_rates -from -hourly_rates_summary hrs -left join -current_rates cr - on hrs.collateral_type = cr.collateral_type -left join -total_rates tr - on 1 = 1 -where hour >= date_trunc('hour', now() - interval '30' day) -order by hrs.hour desc, lower(hrs.collateral_type) desc; diff --git a/dune_dashboard/liq/liquidations.sql b/dune_dashboard/liq/liquidations.sql deleted file mode 100644 index 65674d4..0000000 --- a/dune_dashboard/liq/liquidations.sql +++ /dev/null @@ -1,61 +0,0 @@ -select - contract_address, - 'rETH' as collateral_type, - evt_tx_hash as tx_hash, - evt_index as tx_index, - evt_block_time as block_time, - evt_block_number as block_number, - _debtOffsetBySP/1e18 as debt_offset_sp, - _debtRedistributed/1e18 as debt_redistributed, - _boldGasCompensation/1e18 as bold_gas_compensation, - _collGasCompensation/1e18 as collateral_gas_compensation, - _collSentToSP/1e18 as collateral_sent_sp, - _collRedistributed/1e18 as collateral_distributed, - _collSurplus/1e18 as collateral_surplus, - _L_ETH, - _L_boldDebt, - _price -from liquity_v2_ethereum.troveManager_rETH_evt_Liquidation - -union all - -select - contract_address, - 'WETH' as collateral_type, - evt_tx_hash as tx_hash, - evt_index as tx_index, - evt_block_time as block_time, - evt_block_number as block_number, - _debtOffsetBySP/1e18 as debt_offset_sp, - _debtRedistributed/1e18 as debt_redistributed, - _boldGasCompensation/1e18 as bold_gas_compensation, - _collGasCompensation/1e18 as collateral_gas_compensation, - _collSentToSP/1e18 as collateral_sent_sp, - _collRedistributed/1e18 as collateral_distributed, - _collSurplus/1e18 as collateral_surplus, - _L_ETH, - _L_boldDebt, - _price -from liquity_v2_ethereum.troveManager_wETH_evt_Liquidation - -union all - -select - contract_address, - 'wstETH' as collateral_type, - evt_tx_hash as tx_hash, - evt_index as tx_index, - evt_block_time as block_time, - evt_block_number as block_number, - _debtOffsetBySP/1e18 as debt_offset_sp, - _debtRedistributed/1e18 as debt_redistributed, - _boldGasCompensation/1e18 as bold_gas_compensation, - _collGasCompensation/1e18 as collateral_gas_compensation, - _collSentToSP/1e18 as collateral_sent_sp, - _collRedistributed/1e18 as collateral_distributed, - _collSurplus/1e18 as collateral_surplus, - _L_ETH, - _L_boldDebt, - _price -from liquity_v2_ethereum.troveManager_wstETH_evt_Liquidation - diff --git a/dune_dashboard/liq/protocol_revenue.sql b/dune_dashboard/liq/protocol_revenue.sql deleted file mode 100644 index 9db8903..0000000 --- a/dune_dashboard/liq/protocol_revenue.sql +++ /dev/null @@ -1,170 +0,0 @@ -with - -time_seq AS ( - select - sequence( - CAST('2025-01-01' as timestamp), - date_trunc('day', cast(now() as timestamp)), - interval '1' day - ) as time -), - -days AS ( - select - time.time as day - from time_seq - cross join unnest(time) as time(time) -), - -eth_fee as ( - select - date_trunc('minute', evt_block_time) as minute, - evt_tx_hash, - _ETHFee/1e18 as fee - from liquity_v2_ethereum.troveManager_rETH_evt_RedemptionFeePaidToTrove - - union all - - select - date_trunc('minute', evt_block_time) as minute, - evt_tx_hash, - _ETHFee/1e18 as fee - from liquity_v2_ethereum.troveManager_wstETH_evt_RedemptionFeePaidToTrove - - union all - - select - date_trunc('minute', evt_block_time) as minute, - evt_tx_hash, - _ETHFee/1e18 as fee - from liquity_v2_ethereum.troveManager_wETH_evt_RedemptionFeePaidToTrove -), - -add_price as ( - select - ef.minute, - evt_tx_hash, - fee, - p.price, - fee * p.price as fee_usd - from - eth_fee ef - left join - prices.usd p - on ef.minute = p.minute - and p.symbol = 'WETH' - and p.blockchain = 'ethereum' -), - -interest_rewards as ( - select - case - when to = 0x9502b7c397e9aa22fe9db7ef7daf21cd2aebe56b then 'wstETH' - when to = 0xd442e41019b7f5c4dd78f50dc03726c446148695 then 'rETH' - when to = 0x5721cbbd64fc7ae3ef44a0a3f9a790a9264cf9bf then 'WETH' - when to = 0x807def5e7d057df05c796f4bc75c3fe82bd6eee1 then 'PIL' - end as collateral_type, - date_trunc('day', evt_block_time) as day, - sum(value/1e18) as bold_amount - from liquity_v2_ethereum.boldToken_evt_Transfer - where to in (0x9502b7c397e9aa22fe9db7ef7daf21cd2aebe56b, 0xd442e41019b7f5c4dd78f50dc03726c446148695, 0x5721cbbd64fc7ae3ef44a0a3f9a790a9264cf9bf, 0x807def5e7d057df05c796f4bc75c3fe82bd6eee1) - and "from" = 0x0000000000000000000000000000000000000000 - group by 1, 2 -), - -liquidation_rewards as ( - select - collateral_type, - date_trunc('day', block_time) as day, - sum(collateral_sent_sp * _price/1e18 - debt_offset_sp) as liquidation_rewards - from - query_4412204 - group by 1, 2 -), - -all_interest as ( - select - day, - case - when collateral_type in ('wstETH', 'rETH', 'WETH') then 'SP Yield' - else 'PIL Yield' - end as fee_type, - sum(bold_amount) as fee - from - interest_rewards - where collateral_type in ('wstETH', 'rETH', 'WETH', 'PIL') - group by 1, 2 - - union all - - select - date_trunc('day', minute) as day, - 'Redemption Fees' as fee_type, - sum(fee_usd) as fee - from - add_price - group by 1, 2 - - union all - - select - day, - 'Liquidation Rewards' as fee_type, - sum(liquidation_rewards) as fee - from - liquidation_rewards - group by 1, 2 -), - -get_next_day as ( - select - *, - sum(fee) over (partition by fee_type order by day asc) as fee_total, - lead(day, 1, current_timestamp) over (partition by fee_type order by day asc) as next_day - from - all_interest -) - -select - day, - fee_type, - fee, - fee_total, - sum(fee_total/1e3) over (partition by day) as fee_total_all, - sum(fee_total/1e6) over (partition by day) as fee_total_all_million, - sum(case - when day > date_trunc('day', now()) - interval '1' day then fee/1e3 - else 0 - end) over (order by day) as fee_param -from ( -select - b.*, - coalesce(c.fee, 0) as fee -from ( -select - d.day, - c.fee_type, - c.fee_total -from -get_next_day c -inner join -days d - on c.day <= d.day - and d.day < c.next_day -) b -left join -get_next_day c - on b.day = c.day - and b.fee_type = c.fee_type -) -where day >= (case - when '{{time_period}}' = 'all time' then cast('2025-05-19' as date) - when '{{time_period}}' = '1 year' then date_trunc('day', now() - interval '1' year) - when '{{time_period}}' = '6 months' then date_trunc('day', now() - interval '6' month) - when '{{time_period}}' = '3 months' then date_trunc('day', now() - interval '3' month) - when '{{time_period}}' = '1 month' then date_trunc('day', now() - interval '1' month) - end) -order by day desc - --- where token_balance > 0 --- select sum(fee)/1e9 as fee from all_interest diff --git a/dune_dashboard/liq/total_bold_holders/all.sql b/dune_dashboard/liq/total_bold_holders/all.sql deleted file mode 100644 index 2d64829..0000000 --- a/dune_dashboard/liq/total_bold_holders/all.sql +++ /dev/null @@ -1,38 +0,0 @@ -with --- Generate a series of dates to join with -date_series as ( - select timestamp + interval '1' day as day - from utils.days -), - --- Join events with dates to get daily balances -daily_balances as ( - select - ds.day, - e.user, - e.contract_address, - e.balance_at_event - from date_series ds - join query_5354879 e - on ds.day >= date_trunc('day', e.start_time) - and ds.day < date_trunc('day', e.end_time) -), - -latest_daily_balances as ( - select - day, - user, - contract_address, - max(balance_at_event) as daily_balance - from daily_balances - group by day, user, contract_address -) - --- Final output -select - day + interval '1' day as day, - user as address, - contract_address, - daily_balance as token_balance -from latest_daily_balances -order by day, user, contract_address diff --git a/dune_dashboard/liq/total_bold_holders/bold_token_holders.sql b/dune_dashboard/liq/total_bold_holders/bold_token_holders.sql deleted file mode 100644 index 5c17cd7..0000000 --- a/dune_dashboard/liq/total_bold_holders/bold_token_holders.sql +++ /dev/null @@ -1,82 +0,0 @@ -with - -erc20_aggregated as ( - select - day, - address, - token_address, - token_symbol, - sum(amount) as amount - from ( - select - date_trunc('day', evt_block_time) as day, - to as address, - contract_address as token_address, - 'BOLD' as token_symbol, - sum(value/1e18) as amount - from liquity_v2_ethereum.boldToken_evt_Transfer - - group by 1, 2, 3, 4 - - union all - - select - date_trunc('day', evt_block_time) as day, - "from" as address, - contract_address as token_address, - 'BOLD' as token_symbol, - -sum(value/1e18) as amount - from liquity_v2_ethereum.boldToken_evt_Transfer - group by 1, 2, 3, 4 - ) x - group by 1, 2, 3, 4 -), - -cum_aggregated as ( - select - day, - address, - token_address, - token_symbol, - sum(amount) over (partition by address, token_address, token_symbol order by day asc) as token_balance, - lead(day, 1, current_timestamp) over (partition by address, token_address, token_symbol order by day asc) as next_day - from - erc20_aggregated -), - -time_seq AS ( - select - sequence( - CAST('2024-11-01' as timestamp), - date_trunc('day', cast(now() as timestamp)), - interval '1' day - ) as time -), - -days AS ( - select - time.time as day - from time_seq - cross join unnest(time) as time(time) -) - -select - * -from ( -select - d.day, - c.address, - c.token_address, - c.token_symbol, - c.token_balance -from -cum_aggregated c -inner join -days d - on c.day <= d.day - and d.day < c.next_day -) -where token_balance > 0.001 -- Exclude users who hold dust - - - diff --git a/dune_dashboard/liq/total_bold_holders/sp_depositors.sql b/dune_dashboard/liq/total_bold_holders/sp_depositors.sql deleted file mode 100644 index ba7034e..0000000 --- a/dune_dashboard/liq/total_bold_holders/sp_depositors.sql +++ /dev/null @@ -1,74 +0,0 @@ -WITH - -raw_deposits AS ( - SELECT * FROM ( - SELECT 'ETH' AS base_collateral, * - FROM liquity_v2_ethereum.stabilitypool_weth_evt_depositoperation - UNION ALL - SELECT 'rETH', * - FROM liquity_v2_ethereum.stabilitypool_reth_evt_depositoperation - UNION ALL - SELECT 'wstETH', * - FROM liquity_v2_ethereum.stabilitypool_wsteth_evt_depositoperation - ) -) - -, daily_data AS ( - SELECT - date(evt_block_time) as date, - _depositor as user, - SUM((_topUpOrWithdrawal - _depositLossSinceLastOperation - _yieldGainClaimed + _yieldGainSinceLastOperation) / 1e18) AS amount - FROM raw_deposits - WHERE _depositor NOT IN ( - 0x50bd66d59911f5e086ec87ae43c811e0d059dd11, -- sBOLD - 0x2048a730f564246411415f719198d6f7c10a7961 -- yBOLD - ) - GROUP BY date(evt_block_time), _depositor -) - -, intermediary_results AS ( - SELECT date - , user - , SUM(amount) OVER (PARTITION BY user ORDER BY date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_amount - FROM daily_data -) - -, date_series AS ( - WITH dates AS ( - SELECT sequence( - cast('2025-01-01' as date), - cast(date_trunc('day', current_timestamp) AS date), - interval '1' day - ) as d - ) - SELECT date_value as date - FROM dates - CROSS JOIN UNNEST(d) AS t(date_value) -) - -, users AS ( - SELECT DISTINCT user FROM intermediary_results -) - -, date_user_series AS ( - SELECT ds.date - , p.user - FROM date_series ds - CROSS JOIN users p -) - -, filled_data AS ( - SELECT dps.date - , dps.user - , yt.cumulative_amount - , LAST_VALUE(yt.cumulative_amount) IGNORE NULLS OVER (PARTITION BY dps.user ORDER BY dps.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS filled_cumulative_amount - , LAST_VALUE(yt.user) IGNORE NULLS OVER (PARTITION BY dps.user ORDER BY dps.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS filled_user - FROM date_user_series dps - LEFT JOIN intermediary_results yt ON dps.date = yt.date AND dps.user = yt.user -) - -SELECT date as day - , filled_user AS address - , filled_cumulative_amount AS token_balance -FROM filled_data -WHERE filled_user IS NOT NULL diff --git a/dune_dashboard/liq/total_bold_holders/total_bold_holders.sql b/dune_dashboard/liq/total_bold_holders/total_bold_holders.sql deleted file mode 100644 index e37f865..0000000 --- a/dune_dashboard/liq/total_bold_holders/total_bold_holders.sql +++ /dev/null @@ -1,14 +0,0 @@ -with users_data as ( - select day, address from query_5155107 -- BOLD Holders - where token_balance > 0.001 - union - select day, address from query_5301941 -- SP Depositors - where token_balance > 0.001 - union - select day, address from query_5354895 -- All - where token_balance > 0.001 -) -select day, count(distinct address) as "Holders" -from users_data -group by 1 -order by 1 diff --git a/dune_dashboard/liq/total_usdu_supply.sql b/dune_dashboard/liq/total_usdu_supply.sql deleted file mode 100644 index 4bdc9f9..0000000 --- a/dune_dashboard/liq/total_usdu_supply.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Uncap Protocol - Total USDU Supply --- Network: Starknet Mainnet --- Purpose: Aggregate daily holder count and total supply from token_holders query - -select - day, - token_address, - token_symbol, - count(distinct(address)) as num_holders, - sum(token_balance) as token_balance, - sum(token_balance) as total_supply_usd -- USDU is a stablecoin, 1 USDU ≈ $1 -from -query_5904342 -group by 1, 2, 3 -order by 1 desc diff --git a/dune_dashboard/liq/trove_operations.sql b/dune_dashboard/liq/trove_operations.sql deleted file mode 100644 index 4ec1e1b..0000000 --- a/dune_dashboard/liq/trove_operations.sql +++ /dev/null @@ -1,49 +0,0 @@ -select - contract_address, - 'WETH' as collateral_type, - evt_tx_hash as tx_hash, - evt_index as tx_index, - evt_block_time as block_time, - evt_block_number as block_number, - _troveId as trove_id, - _operation as operation, - _debtChangeFromOperation/1e18 as debt_change, - _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, - _collChangeFromOperation/1e18 as collateral_change, - _annualInterestRate/1e16 as interest_rates -from liquity_v2_ethereum.troveManager_wETH_evt_TroveOperation - -union all - -select - contract_address, - 'rETH' as collateral_type, - evt_tx_hash as tx_hash, - evt_index as tx_index, - evt_block_time as block_time, - evt_block_number as block_number, - _troveId as trove_id, - _operation as operation, - _debtChangeFromOperation/1e18 as debt_change, - _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, - _collChangeFromOperation/1e18 as collateral_change, - _annualInterestRate/1e16 as interest_rates -from liquity_v2_ethereum.troveManager_rETH_evt_TroveOperation - -union all - -select - contract_address, - 'wstETH' as collateral_type, - evt_tx_hash as tx_hash, - evt_index as tx_index, - evt_block_time as block_time, - evt_block_number as block_number, - _troveId as trove_id, - _operation as operation, - _debtChangeFromOperation/1e18 as debt_change, - _debtIncreaseFromUpfrontFee/1e18 as upfront_fees, - _collChangeFromOperation/1e18 as collateral_change, - _annualInterestRate/1e16 as interest_rates -from liquity_v2_ethereum.troveManager_wstETH_evt_TroveOperation - diff --git a/dune_dashboard/liq/trove_updates.sql b/dune_dashboard/liq/trove_updates.sql deleted file mode 100644 index 23aa5a4..0000000 --- a/dune_dashboard/liq/trove_updates.sql +++ /dev/null @@ -1,518 +0,0 @@ --- First find the trove managers for each trove id at all times - --- When there is a "troveUpdated" event, it means the trove is no longer managed by a batch manager -with weth_trove_manager_address_updates as ( - SELECT evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - _interestBatchManager as interest_batch_manager - FROM liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt - UNION ALL - SELECT - evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager - FROM liquity_v2_ethereum.trovemanager_weth_evt_troveupdated -) - --- For each batch updated (bu) event, make sure to apply it to the relevant troves (with latest manager = bu manager) -, weth_bu_with_trove_rows as ( - SELECT - bu.*, - tm.trove_id, - tm.interest_batch_manager, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id - ORDER BY bu.evt_block_number - tm.evt_block_number ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_weth_evt_batchupdated bu - LEFT JOIN weth_trove_manager_address_updates tm - ON ( - tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event - OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) - ) -) - --- Keep latest value and right managers -, weth_bu_with_trove as - ( - select * - from weth_bu_with_trove_rows - where rn = 1 and _interestBatchManager = interest_batch_manager - ) - --- Now get the bu events enriched with debt numbers from the latest BatchedTroveUpdated (bt) events --- We need to do that because we don't have the debt numbers in the bu events -, weth_bu_closest_previous_events AS ( - -- Find the closest previous batchedTroveUpdated for any batchUpdated - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - bu.evt_block_time as block_time, - bu.evt_tx_hash as tx_hash, - bu.evt_index as tx_index, - bu.evt_block_number as block_number, - bu._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bu.trove_id, - bt._batchDebtShares/1e18 as batch_debt_shares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId - ORDER BY bu.evt_block_time - bt.evt_block_time ASC - ) AS rn - FROM weth_bu_with_trove bu - JOIN liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt - ON bu.trove_id = bt._troveId - AND ( - bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event - OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) - ) -) - -, weth_bu_events_enriched as ( - select - 'WETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , batch_debt_shares * debt_to_shares_ratio as debt - , stake - , 'batchUpdated' as event - from weth_bu_closest_previous_events - where rn = 1 -) - --- Now we have the bu events enriched --- We need to add 2 event types: batchedTroveUpdated (a bit easier but similar logic) and troveUpdated (super easy) - --- The complexity in batchedTroveUpdated is to get the latest values of interest rates etc. if they were changed --- by a batchUpdated event - -, weth_bt_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - - bt.evt_block_time as block_time, - bt.evt_tx_hash as tx_hash, - bt.evt_index as tx_index, - bt.evt_block_number as block_number, - bt._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bt._troveId as trove_id, - bt._batchDebtShares/1e18 as _batchDebtShares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId - ORDER BY bt.evt_block_time - bu.evt_block_time ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_weth_evt_batchedtroveupdated bt - JOIN liquity_v2_ethereum.trovemanager_weth_evt_batchupdated bu - ON bu._interestBatchManager = bt._interestBatchManager - AND ( - bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event - OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) - ) -) - --- Below is batchedTroveUpdated events -- full -, weth_bt_events_enriched as ( - select - 'WETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , _batchDebtShares * debt_to_shares_ratio as debt - , stake - , 'batchTroveUpdated' as event - from weth_bt_closest_previous_events - where rn = 1 -) - --- We do the same for rETH and wstETH -, reth_trove_manager_address_updates as ( - SELECT evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - _interestBatchManager as interest_batch_manager - FROM liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt - UNION ALL - SELECT - evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager - FROM liquity_v2_ethereum.trovemanager_reth_evt_troveupdated -) - -, reth_bu_with_trove_rows as ( - SELECT - bu.*, - tm.trove_id, - tm.interest_batch_manager, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id - ORDER BY bu.evt_block_number - tm.evt_block_number ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_reth_evt_batchupdated bu - LEFT JOIN reth_trove_manager_address_updates tm - ON ( - tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event - OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) - ) -) - -, reth_bu_with_trove as - ( - select * - from reth_bu_with_trove_rows - where rn = 1 and _interestBatchManager = interest_batch_manager - ) - - -, reth_bu_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - bu.evt_block_time as block_time, - bu.evt_tx_hash as tx_hash, - bu.evt_index as tx_index, - bu.evt_block_number as block_number, - bu._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bu.trove_id, - bt._batchDebtShares/1e18 as batch_debt_shares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId - ORDER BY bu.evt_block_time - bt.evt_block_time ASC - ) AS rn - FROM reth_bu_with_trove bu - JOIN liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt - ON bu.trove_id = bt._troveId - AND ( - bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event - OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) - ) -) - -, reth_bu_events_enriched as ( - select - 'rETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , batch_debt_shares * debt_to_shares_ratio as debt - , stake - , 'batchUpdated' as event - from reth_bu_closest_previous_events - where rn = 1 -) - -, reth_bt_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - - bt.evt_block_time as block_time, - bt.evt_tx_hash as tx_hash, - bt.evt_index as tx_index, - bt.evt_block_number as block_number, - bt._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bt._troveId as trove_id, - bt._batchDebtShares/1e18 as _batchDebtShares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId - ORDER BY bt.evt_block_time - bu.evt_block_time ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_reth_evt_batchedtroveupdated bt - JOIN liquity_v2_ethereum.trovemanager_reth_evt_batchupdated bu - ON bu._interestBatchManager = bt._interestBatchManager - AND ( - bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event - OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) - ) -) - -, reth_bt_events_enriched as ( - select - 'rETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , _batchDebtShares * debt_to_shares_ratio as debt - , stake - , 'batchTroveUpdated' as event - from reth_bt_closest_previous_events - where rn = 1 -) - -, wsteth_trove_manager_address_updates as ( - SELECT evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - _interestBatchManager as interest_batch_manager - FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt - UNION ALL - SELECT - evt_block_number, - evt_block_time, - evt_index, - _troveId as trove_id, - 0x0000000000000000000000000000000000000000 as _interestBatchManager -- i.e. no batch manager - FROM liquity_v2_ethereum.trovemanager_wsteth_evt_troveupdated -) - -, wsteth_bu_with_trove_rows as ( - SELECT - bu.*, - tm.trove_id, - tm.interest_batch_manager, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, tm.trove_id - ORDER BY bu.evt_block_number - tm.evt_block_number ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchupdated bu - LEFT JOIN wsteth_trove_manager_address_updates tm - ON ( - tm.evt_block_number < bu.evt_block_number -- Ensure tm event happened before bu event - OR (tm.evt_block_number = bu.evt_block_number AND tm.evt_index <= bu.evt_index) - ) -) - -, wsteth_bu_with_trove as - ( - select * - from wsteth_bu_with_trove_rows - where rn = 1 and _interestBatchManager = interest_batch_manager - ) - -, wsteth_bu_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - bu.evt_block_time as block_time, - bu.evt_tx_hash as tx_hash, - bu.evt_index as tx_index, - bu.evt_block_number as block_number, - bu._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bu.trove_id, - bt._batchDebtShares/1e18 as batch_debt_shares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bu.evt_tx_hash, bu.evt_block_number, bu.evt_index, bu._interestBatchManager, bt._troveId - ORDER BY bu.evt_block_time - bt.evt_block_time ASC - ) AS rn - FROM wsteth_bu_with_trove bu - JOIN liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt - ON bu.trove_id = bt._troveId - AND ( - bt.evt_block_number < bu.evt_block_number -- Ensure bt event happened before bu event - OR (bt.evt_block_number = bu.evt_block_number AND bt.evt_index <= bu.evt_index) - ) -) - -, wsteth_bu_events_enriched as ( - select - 'wstETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , batch_debt_shares * debt_to_shares_ratio as debt - , stake - , 'batchUpdated' as event - from wsteth_bu_closest_previous_events - where rn = 1 -) - -, wsteth_bt_closest_previous_events AS ( - SELECT - cast(bu._debt/1e18 as double) - / - cast((case when bu._totalDebtShares = 0 then 1 else bu._totalDebtShares end)/1e18 as double) - as debt_to_shares_ratio, - - bt.evt_block_time as block_time, - bt.evt_tx_hash as tx_hash, - bt.evt_index as tx_index, - bt.evt_block_number as block_number, - bt._interestBatchManager as interest_batch_manager, - bu._annualInterestRate/1e16 as interest_rates, - bt._troveId as trove_id, - bt._batchDebtShares/1e18 as _batchDebtShares, - bt._coll/1e18 as collateral, - bt._stake/1e18 as stake, - ROW_NUMBER() OVER ( - PARTITION BY bt.evt_tx_hash, bt.evt_block_number, bt.evt_index, bt._interestBatchManager, bt._troveId - ORDER BY bt.evt_block_time - bu.evt_block_time ASC - ) AS rn - FROM liquity_v2_ethereum.trovemanager_wsteth_evt_batchedtroveupdated bt - JOIN liquity_v2_ethereum.trovemanager_wsteth_evt_batchupdated bu - ON bu._interestBatchManager = bt._interestBatchManager - AND ( - bu.evt_block_number < bt.evt_block_number -- Ensure tm event happened before bu event - OR (bu.evt_block_number = bt.evt_block_number AND bu.evt_index <= bt.evt_index) - ) -) - -, wsteth_bt_events_enriched as ( - select - 'wstETH' as collateral_type - , tx_hash - , tx_index - , block_time - , block_number - , interest_batch_manager - , interest_rates - , trove_id - --, _batchDebtShares - , collateral - , _batchDebtShares * debt_to_shares_ratio as debt - , stake - , 'batchTroveUpdated' as event - from wsteth_bt_closest_previous_events - where rn = 1 -) - --- Now we union all results --- and add troveUpdated events (super easy) -, pre_final_results as ( - select * from weth_bu_events_enriched - union all - select * from weth_bt_events_enriched - union all - select - 'WETH' as collateral_type - , evt_tx_hash as tx_hash - , evt_index as tx_index - , evt_block_time as block_time - , evt_block_number as evt_block_number - , 0x0000000000000000000000000000000000000000 as interest_batch_manager - , _annualInterestRate/1e16 as interest_rates - , _troveId as trove_id - , _coll/1e18 as collateral - , _debt/1e18 as debt - , _stake/1e18 as stake - , 'troveUpdated' as event - from liquity_v2_ethereum.trovemanager_weth_evt_troveupdated - union all - select * from reth_bu_events_enriched - union all - select * from reth_bt_events_enriched - union all - select - 'rETH' as collateral_type - , evt_tx_hash as tx_hash - , evt_index as tx_index - , evt_block_time as block_time - , evt_block_number as evt_block_number - , 0x0000000000000000000000000000000000000000 as interest_batch_manager - , _annualInterestRate/1e16 as interest_rates - , _troveId as trove_id - , _coll/1e18 as collateral - , _debt/1e18 as debt - , _stake/1e18 as stake - , 'troveUpdated' as event - from liquity_v2_ethereum.trovemanager_reth_evt_troveupdated - union all - select * from wsteth_bu_events_enriched - union all - select * from wsteth_bt_events_enriched - union all - select - 'wstETH' as collateral_type - , evt_tx_hash as tx_hash - , evt_index as tx_index - , evt_block_time as block_time - , evt_block_number as evt_block_number - , 0x0000000000000000000000000000000000000000 as interest_batch_manager - , _annualInterestRate/1e16 as interest_rates - , _troveId as trove_id - , _coll/1e18 as collateral - , _debt/1e18 as debt - , _stake/1e18 as stake - , 'troveUpdated' as event - from liquity_v2_ethereum.trovemanager_wsteth_evt_troveupdated -) - --- Final results with deduplication --- (there can be duplicates because both batchUpdated & batchTroveUpdated events --- in this case, keep the batchTroveUpdated event) -select - collateral_type, - tx_hash, - tx_index, - block_time, - block_number, - interest_rates, - interest_batch_manager, - trove_id, - collateral, - debt, - stake, - event -from ( - select - *, - row_number() over ( - partition by collateral_type, tx_hash, block_number, trove_id - order by tx_index desc, (case when event = 'batchTroveUpdated' then 1 else 0 end) desc - ) as rn - from pre_final_results -) ranked -where rn = 1 diff --git a/dune_dashboard/obl/obl_query.sql b/dune_queries/obl/obl_query.sql similarity index 100% rename from dune_dashboard/obl/obl_query.sql rename to dune_queries/obl/obl_query.sql diff --git a/dune_dashboard/points/points_query.sql b/dune_queries/points/points_query.sql similarity index 100% rename from dune_dashboard/points/points_query.sql rename to dune_queries/points/points_query.sql diff --git a/dune_dashboard/uncap/interest_rates.sql b/dune_queries/uncap/interest_rates.sql similarity index 100% rename from dune_dashboard/uncap/interest_rates.sql rename to dune_queries/uncap/interest_rates.sql diff --git a/dune_dashboard/uncap/interest_rates/trove_operations.sql b/dune_queries/uncap/interest_rates/trove_operations.sql similarity index 100% rename from dune_dashboard/uncap/interest_rates/trove_operations.sql rename to dune_queries/uncap/interest_rates/trove_operations.sql diff --git a/dune_dashboard/uncap/interest_rates/trove_updates.sql b/dune_queries/uncap/interest_rates/trove_updates.sql similarity index 100% rename from dune_dashboard/uncap/interest_rates/trove_updates.sql rename to dune_queries/uncap/interest_rates/trove_updates.sql diff --git a/dune_dashboard/uncap/protocol_revenue.sql b/dune_queries/uncap/protocol_revenue.sql similarity index 100% rename from dune_dashboard/uncap/protocol_revenue.sql rename to dune_queries/uncap/protocol_revenue.sql diff --git a/dune_dashboard/uncap/protocol_revenue/liquidations.sql b/dune_queries/uncap/protocol_revenue/liquidations.sql similarity index 100% rename from dune_dashboard/uncap/protocol_revenue/liquidations.sql rename to dune_queries/uncap/protocol_revenue/liquidations.sql diff --git a/dune_dashboard/uncap/tvl/active_pool_updates.sql b/dune_queries/uncap/tvl/active_pool_updates.sql similarity index 100% rename from dune_dashboard/uncap/tvl/active_pool_updates.sql rename to dune_queries/uncap/tvl/active_pool_updates.sql diff --git a/dune_dashboard/uncap/tvl/token_holders.sql b/dune_queries/uncap/tvl/token_holders.sql similarity index 100% rename from dune_dashboard/uncap/tvl/token_holders.sql rename to dune_queries/uncap/tvl/token_holders.sql diff --git a/dune_dashboard/uncap/tvl/tvl.sql b/dune_queries/uncap/tvl/tvl.sql similarity index 100% rename from dune_dashboard/uncap/tvl/tvl.sql rename to dune_queries/uncap/tvl/tvl.sql From 17b036069f2c1b94d58a9ed8bad02cead8e3d10f Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Tue, 18 Nov 2025 18:56:20 +0100 Subject: [PATCH 10/14] obl query fixed --- dune_queries/obl/obl_query.sql | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/dune_queries/obl/obl_query.sql b/dune_queries/obl/obl_query.sql index 21c903d..a04f355 100644 --- a/dune_queries/obl/obl_query.sql +++ b/dune_queries/obl/obl_query.sql @@ -358,11 +358,18 @@ daily_changes AS ( -- Daily Price Data from OpenBlockLabs daily_prices AS ( SELECT - DATE(timestamp AT TIME ZONE 'UTC') AS date, + date, price AS wbtc_price - FROM dune.openblocklabs.result_starknet_prices_daily - CROSS JOIN cfg - WHERE contract_address = cfg.wbtc_price_addr + FROM ( + SELECT + DATE(timestamp AT TIME ZONE 'UTC') AS date, + price, + ROW_NUMBER() OVER (PARTITION BY DATE(timestamp AT TIME ZONE 'UTC') ORDER BY timestamp DESC) as rn + FROM dune.openblocklabs.result_starknet_prices_daily + CROSS JOIN cfg + WHERE contract_address = cfg.wbtc_price_addr + ) + WHERE rn = 1 ), -- Latest Price Fallback From 2929315cbccca0ed487dea94c2c57eacc8b79d4c Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Wed, 19 Nov 2025 14:34:32 +0100 Subject: [PATCH 11/14] update points query --- dune_queries/points/points_query.sql | 250 ++++++++++++++++++++------- 1 file changed, 184 insertions(+), 66 deletions(-) diff --git a/dune_queries/points/points_query.sql b/dune_queries/points/points_query.sql index f302fbf..a52abbb 100644 --- a/dune_queries/points/points_query.sql +++ b/dune_queries/points/points_query.sql @@ -25,32 +25,88 @@ deployment_date as ( where number >= (select deployment_block from cfg) ), --- Get all trove update events with their state -trove_events as ( +-- ============================================================================ +-- 1. EXTRACT RAW EVENTS +-- ============================================================================ + +-- Regular Trove Updates (Standard TroveUpdated events) +trove_updated_events as ( select - date_trunc('hour', block_time) as hour, block_time, block_number, event_index, - -- Extract trove_id (u256 = data[1] low, data[2] high) + 'REGULAR' as update_type, lower(concat('0x', "right"(substring(to_hex(data[2]), 3), 32), "right"(substring(to_hex(data[1]), 3), 32) )) as trove_id, - -- Extract debt (u256 = data[3] low, data[4] high) cast(bytearray_to_uint256(data[3]) as double) / 1e18 as debt, - -- Extract collateral (u256 = data[5] low, data[6] high) cast(bytearray_to_uint256(data[5]) as double) / 1e18 as collateral, - -- Extract stake (u256 = data[7] low, data[8] high) + cast(bytearray_to_uint256(data[9]) as double) / 1e16 as interest_rate, cast(bytearray_to_uint256(data[7]) as double) / 1e18 as stake, - -- Extract annual interest rate (u256 = data[9] low, data[10] high) - cast(bytearray_to_uint256(data[9]) as double) / 1e16 as interest_rate + NULL as shares, + NULL as interest_batch_manager from starknet.events where from_address = (select events_emitter from cfg) and keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 and block_number >= (select deployment_block from cfg) ), +-- Batched Trove Updates (BatchedTroveUpdated events) +batched_trove_updated_events as ( + select + block_time, + block_number, + event_index, + 'BATCHED' as update_type, + lower(concat('0x', + "right"(substring(to_hex(data[2]), 3), 32), + "right"(substring(to_hex(data[1]), 3), 32) + )) as trove_id, + NULL as debt, + cast(bytearray_to_uint256(data[6]) as double) / 1e18 as collateral, + NULL as interest_rate, + cast(bytearray_to_uint256(data[8]) as double) / 1e18 as stake, -- data[7] is low, data[8] is high? No, need to check ordering. + -- Standard ordering: + -- trove_id: data[1], data[2] + -- interest_batch_manager: data[3] + -- batch_debt_shares: data[4], data[5] + -- coll: data[6], data[7] + -- stake: data[8], data[9] + cast(bytearray_to_uint256(data[4]) as double) / 1e18 as shares, + data[3] as interest_batch_manager + from starknet.events + where from_address = (select events_emitter from cfg) + and keys[1] = 0x03c054baf812ebdae34d56f1322bd89e0b0c4aed0829fe0e7658fce049d25f71 + and block_number >= (select deployment_block from cfg) +), + +-- Batch Updates (BatchUpdated events) +batch_updated_events as ( + select + block_time, + block_number, + event_index, + data[1] as interest_batch_manager, + cast(bytearray_to_uint256(data[3]) as double) / 1e18 as batch_debt, + -- annual_interest_rate is data[7], data[8] + cast(bytearray_to_uint256(data[7]) as double) / 1e16 as batch_interest_rate, -- Using 1e16 to match points query convention? OBL used 1e18. + -- Points query uses 1e16 for rate? Let's check line 47: cast(...)/1e16. Yes. + -- total_debt_shares is data[11], data[12] + cast(bytearray_to_uint256(data[11]) as double) / 1e18 as batch_total_shares + from starknet.events + where from_address = (select events_emitter from cfg) + and keys[1] = 0x039a6ae9867ab3ddba84021a987ac7776389a6cc5bad711eda5e4a44d1a2fe80 + and block_number >= (select deployment_block from cfg) +), + +-- Combine regular and batched updates +all_trove_updates as ( + select * from trove_updated_events + union all + select * from batched_trove_updated_events +), + -- Get NFT transfer events to track ownership nft_transfers as ( select @@ -60,7 +116,6 @@ nft_transfers as ( -- Extract to address (new owner) from keys[3] keys[3] as owner, -- Extract token_id (u256 = keys[4] low, keys[5] high) - -- Token ID is indexed in the Transfer event, so it's in keys not data lower(concat('0x', "right"(substring(to_hex(keys[5]), 3), 32), "right"(substring(to_hex(keys[4]), 3), 32) @@ -72,27 +127,20 @@ nft_transfers as ( ), -- Get StabilityPool deposit events --- Event: DepositUpdated(depositor, new_deposit, stashed_coll, snapshot_p, snapshot_s, snapshot_b, snapshot_scale) --- This event fires whenever a user's deposit changes and contains their current balance sp_deposit_events as ( select block_time, block_number, event_index, - -- Depositor address (ContractAddress = felt252 in data[1]) data[1] as user_address, - -- New deposit amount (u256 = data[2] low, data[3] high) - -- This is the user's current deposit balance after the operation cast(bytearray_to_uint256(data[2]) as double) / 1e18 as deposit_amount from starknet.events where from_address = (select stability_pool from cfg) - -- DepositUpdated event selector and keys[1] = 0x0056985e064b256ecdb949713158ae0953473139aaf1fd57197a852fe36c4f00 and block_number >= (select deployment_block from cfg) ), --- Track Ekubo NFT position transfers to know ownership --- Transfer event: data = [from, to, token_id_low, token_id_high] +-- Track Ekubo NFT position transfers ekubo_position_transfers as ( select block_time, @@ -110,7 +158,6 @@ ekubo_position_transfers as ( ), -- Track Ekubo position liquidity updates --- PositionUpdated event: data[7] = salt (position_id), data[14-17] = delta amounts ekubo_position_updates as ( select block_time, @@ -135,9 +182,9 @@ ekubo_position_updates as ( where from_address = cfg.ekubo_core and keys[1] = 0x03a7adca3546c213ce791fabf3b04090c163e419c808c9830fb343a4a395946e and block_number >= cfg.deployment_block - and data[2] = cfg.usdu_token -- USDU - and data[3] = cfg.usdc_token -- USDC - and cast(bytearray_to_uint256(data[5]) as bigint) = 100 -- tick_spacing = 100 + and data[2] = cfg.usdu_token + and data[3] = cfg.usdc_token + and cast(bytearray_to_uint256(data[5]) as bigint) = 100 ), -- Filter positions to relevant price range @@ -160,7 +207,7 @@ ekubo_filtered_positions as ( and upper_mag > 27581700 ), --- Calculate liquidity deltas per position (sign: 0 = add, 1 = remove) +-- Calculate liquidity deltas per position ekubo_position_liquidity_changes as ( select block_time, @@ -187,24 +234,30 @@ ekubo_position_cumulative as ( usdu_delta, usdc_delta, total_usd_delta, - sum(usdu_delta) over ( - partition by position_id - order by block_time, event_index - rows between unbounded preceding and current row - ) as cumulative_usdu, - sum(usdc_delta) over ( + -- Since we cannot accurately track the split between USDU and USDC without tracking all swaps, + -- we stop tracking individual token balances to avoid negative values from IL. + -- We rely solely on cumulative_usd_value. + 0 as cumulative_usdu, + 0 as cumulative_usdc, + -- Clamp the cumulative USD value to 0. If it drops below 0 (due to fee accrual or tiny impermanent loss on withdrawal), + -- we assume the position is closed/empty. We use 1.0 USD as a dust threshold. + case when sum(total_usd_delta) over ( partition by position_id order by block_time, event_index rows between unbounded preceding and current row - ) as cumulative_usdc, - sum(total_usd_delta) over ( + ) < 1.0 then 0 + else sum(total_usd_delta) over ( partition by position_id order by block_time, event_index rows between unbounded preceding and current row - ) as cumulative_usd_value + ) end as cumulative_usd_value from ekubo_position_liquidity_changes ), +-- ============================================================================ +-- 2. CREATE TIME SPINES AND AGGREGATE +-- ============================================================================ + -- Get all unique Ekubo positions ekubo_positions as ( select distinct position_id @@ -231,10 +284,16 @@ six_hour_slots as ( -- Get all unique troves troves as ( select distinct trove_id - from trove_events + from all_trove_updates +), + +-- Get all unique batches +batches as ( + select distinct interest_batch_manager + from batch_updated_events ), --- Get all unique users (from troves, SP, and Ekubo LP) +-- Get all unique users all_users as ( select distinct owner as user_address from nft_transfers @@ -248,7 +307,7 @@ all_users as ( where to_address != 0x0000000000000000000000000000000000000000 ), --- Cross join 6-hour slots and troves to get all combinations +-- Cross join slots and troves slot_trove_combinations as ( select s.slot_start, @@ -257,7 +316,16 @@ slot_trove_combinations as ( cross join troves t ), --- Cross join 6-hour slots and users for SP tracking +-- Cross join slots and batches +slot_batch_combinations as ( + select + s.slot_start, + b.interest_batch_manager + from six_hour_slots s + cross join batches b +), + +-- Cross join slots and users slot_user_combinations as ( select s.slot_start, @@ -266,8 +334,17 @@ slot_user_combinations as ( cross join all_users u ), --- For each 6-hour slot and trove, get the most recent trove state before the end of that slot -latest_states as ( +-- Cross join slots and Ekubo positions +slot_position_combinations as ( + select + s.slot_start, + p.position_id + from six_hour_slots s + cross join ekubo_positions p +), + +-- For each 6-hour slot and trove, get the most recent state +latest_trove_states as ( select stc.slot_start, stc.trove_id, @@ -275,18 +352,40 @@ latest_states as ( te.collateral, te.stake, te.interest_rate, + te.shares, + te.interest_batch_manager, + te.update_type, row_number() over ( partition by stc.slot_start, stc.trove_id order by te.block_time desc, te.event_index desc ) as rn from slot_trove_combinations stc - left join trove_events te + left join all_trove_updates te on stc.trove_id = te.trove_id - and te.block_time <= stc.slot_start + interval '6' hour -- Include events in this 6-hour slot - where te.trove_id is not null -- Only include if we found an event + and te.block_time <= stc.slot_start + interval '6' hour + where te.trove_id is not null +), + +-- For each 6-hour slot and batch, get the most recent state +latest_batch_states as ( + select + sbc.slot_start, + sbc.interest_batch_manager, + be.batch_debt, + be.batch_total_shares, + be.batch_interest_rate, + row_number() over ( + partition by sbc.slot_start, sbc.interest_batch_manager + order by be.block_time desc, be.event_index desc + ) as rn + from slot_batch_combinations sbc + left join batch_updated_events be + on sbc.interest_batch_manager = be.interest_batch_manager + and be.block_time <= sbc.slot_start + interval '6' hour + where be.interest_batch_manager is not null ), --- For each 6-hour slot and trove, get the most recent owner before the end of that slot +-- For each 6-hour slot and trove, get the most recent owner latest_owners as ( select stc.slot_start, @@ -299,11 +398,11 @@ latest_owners as ( from slot_trove_combinations stc left join nft_transfers nt on stc.trove_id = nt.trove_id - and nt.block_time <= stc.slot_start + interval '6' hour -- Include events in this 6-hour slot - where nt.trove_id is not null -- Only include if we found a transfer + and nt.block_time <= stc.slot_start + interval '6' hour + where nt.trove_id is not null ), --- For each 6-hour slot and user, get the most recent SP deposit before the end of that slot +-- For each 6-hour slot and user, get the most recent SP deposit latest_sp_deposits as ( select suc.slot_start, @@ -317,16 +416,7 @@ latest_sp_deposits as ( left join sp_deposit_events sp on suc.user_address = sp.user_address and sp.block_time <= suc.slot_start + interval '6' hour - where sp.user_address is not null -- Only include if we found a deposit event -), - --- Cross join 6-hour slots and Ekubo positions for time-series tracking -slot_position_combinations as ( - select - s.slot_start, - p.position_id - from six_hour_slots s - cross join ekubo_positions p + where sp.user_address is not null ), -- For each time slot and position, get the most recent liquidity state @@ -384,21 +474,42 @@ user_lp_positions as ( group by lpl.slot_start, lpo.owner ), --- Combine trove state and ownership +-- ============================================================================ +-- 3. RESOLVE TROVE STATE AND COMBINE +-- ============================================================================ + +-- Combine trove state, ownership, and batch data to resolve final values trove_snapshots as ( select ls.slot_start, ls.trove_id, lo.owner as user_address, - ls.debt, + -- Resolved Debt Calculation + case + when ls.update_type = 'BATCHED' then + case + when lbs.batch_total_shares > 0 then + (ls.shares * lbs.batch_debt) / lbs.batch_total_shares + else 0 + end + else ls.debt + end as debt, ls.collateral, ls.stake, - ls.interest_rate - from latest_states ls + -- Resolved Interest Rate + case + when ls.update_type = 'BATCHED' then lbs.batch_interest_rate + else ls.interest_rate + end as interest_rate + from latest_trove_states ls left join latest_owners lo on ls.slot_start = lo.slot_start and ls.trove_id = lo.trove_id and lo.rn = 1 + left join latest_batch_states lbs + on ls.slot_start = lbs.slot_start + and ls.interest_batch_manager = lbs.interest_batch_manager + and lbs.rn = 1 where ls.rn = 1 ), @@ -468,14 +579,21 @@ final_snapshots as ( from non_trove_users ), --- Get WBTC prices (daily) +-- Get WBTC prices (deduplicated daily) daily_prices as ( select - date(timestamp at time zone 'UTC') as day, + date, price as wbtc_price - from dune.openblocklabs.result_starknet_prices_daily - cross join cfg - where contract_address = cfg.wbtc_price_addr + from ( + select + date(timestamp at time zone 'UTC') as date, + price, + row_number() over (partition by date(timestamp at time zone 'UTC') order by timestamp desc) as rn + from dune.openblocklabs.result_starknet_prices_daily + cross join cfg + where contract_address = cfg.wbtc_price_addr + ) + where rn = 1 ), -- Get latest WBTC price for fallback @@ -508,7 +626,7 @@ snapshots_with_prices as ( fs.lp_value_usd from final_snapshots fs left join daily_prices dp - on date(fs.slot_start) = dp.day + on date(fs.slot_start) = dp.date cross join latest_price lp ) @@ -531,7 +649,7 @@ select lp_value_usd, wbtc_price from snapshots_with_prices -where (debt > 0 or sp_deposit > 0 or lp_value_usd > 0) -- Show active troves or users with SP/LP deposits +where (debt > 0.000001 or sp_deposit > 0 or lp_value_usd > 0) -- Show active troves or users with SP/LP deposits -- OPTIONAL: Uncomment to filter to last N days for weekly reports -- and slot_start >= current_timestamp - interval '7' day order by snapshot_time desc, owner asc, trove_id asc From 56a1fb398937fe9ab9a56608a09a2739f5660da3 Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:27:56 +0100 Subject: [PATCH 12/14] add solvbtc and tbtc to obl --- dune_queries/obl/obl_query.sql | 277 ++++++++++++++++++++------------- 1 file changed, 166 insertions(+), 111 deletions(-) diff --git a/dune_queries/obl/obl_query.sql b/dune_queries/obl/obl_query.sql index a04f355..ad85fd4 100644 --- a/dune_queries/obl/obl_query.sql +++ b/dune_queries/obl/obl_query.sql @@ -7,22 +7,59 @@ WITH -- ============================================================================ -- CONFIGURATION CONSTANTS -- ============================================================================ +-- Shared constants across all branches +shared_cfg AS ( + SELECT + CAST(0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 AS VARBINARY) AS usdu_addr, + 'USDU' AS debt_asset_symbol, + 18 AS debt_decimals, + 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac AS btc_price_addr, + 1.0 AS debt_price, + 1e18 AS protocol_scale, + 'Uncap Protocol' AS market_name, + 'Uncap' AS market_owner +), + +-- Per-branch configuration cfg AS ( + -- WBTC Branch SELECT + 'WBTC' AS branch_id, + 'WWBTC' AS asset_symbol, + 18 AS asset_decimals, 2759669 AS deployment_block, 0x042a37aa9263b01191286f0f800cc85c676441fb9d27d74bbf3ebcbf4e373d81 AS market_address_raw, CAST(0x042a37aa9263b01191286f0f800cc85c676441fb9d27d74bbf3ebcbf4e373d81 AS VARBINARY) AS market_address, - 'Uncap Protocol' AS market_name, - 'Uncap' AS market_owner, - 0x075d9e518f46a9ca0404fb0a7d386ce056dadf57fd9a0e8659772cb517be4a18 AS wwbtc_addr_raw, - CAST(0x075d9e518f46a9ca0404fb0a7d386ce056dadf57fd9a0e8659772cb517be4a18 AS VARBINARY) AS wwbtc_addr, - 0x04695252ccdd73f1d8ce7d7c78b1d3f55a127161ddbba5fb1174d10a6825397c AS usdu_addr_raw, - CAST(0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 AS VARBINARY) AS usdu_addr, - 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac AS wbtc_price_addr, - 1.0 AS debt_price, - 1e18 AS protocol_scale, + 0x075d9e518f46a9ca0404fb0a7d386ce056dadf57fd9a0e8659772cb517be4a18 AS asset_addr_raw, + CAST(0x075d9e518f46a9ca0404fb0a7d386ce056dadf57fd9a0e8659772cb517be4a18 AS VARBINARY) AS asset_addr, 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 AS events_emitter, 0x04a1e17f1e4d5fdd839fa6a4c52a742b9d10871028b6ffcfb2f1de8dd5dc2cd4 AS trove_nft + UNION ALL + -- solvBTC Branch + SELECT + 'solvBTC' AS branch_id, + 'wsolvBTC' AS asset_symbol, + 18 AS asset_decimals, + 0 AS deployment_block, -- TODO: Update with actual deployment block for solvBTC + 0x0 AS market_address_raw, -- TODO: Update with actual market address for solvBTC + CAST(0x0 AS VARBINARY) AS market_address, -- TODO: Update with actual market address for solvBTC + 0x0 AS asset_addr_raw, -- TODO: Update with actual wsolvBTC address + CAST(0x0 AS VARBINARY) AS asset_addr, -- TODO: Update with actual wsolvBTC address + 0x0 AS events_emitter, -- TODO: Update with actual events_emitter for solvBTC + 0x0 AS trove_nft -- TODO: Update with actual trove_nft for solvBTC + UNION ALL + -- tBTC Branch + SELECT + 'tBTC' AS branch_id, + 'wtBTC' AS asset_symbol, + 18 AS asset_decimals, + 0 AS deployment_block, -- TODO: Update with actual deployment block for tBTC + 0x0 AS market_address_raw, -- TODO: Update with actual market address for tBTC + CAST(0x0 AS VARBINARY) AS market_address, -- TODO: Update with actual market address for tBTC + 0x0 AS asset_addr_raw, -- TODO: Update with actual wtBTC address + CAST(0x0 AS VARBINARY) AS asset_addr, -- TODO: Update with actual wtBTC address + 0x0 AS events_emitter, -- TODO: Update with actual events_emitter for tBTC + 0x0 AS trove_nft -- TODO: Update with actual trove_nft for tBTC ), -- ============================================================================ @@ -33,23 +70,24 @@ cfg AS ( -- Captures updates for troves NOT in a batch (individual troves) trove_updated_events AS ( SELECT - DATE(block_time AT TIME ZONE 'UTC') AS date, - block_time AS evt_block_time, - transaction_hash AS evt_tx_hash, + cfg.branch_id, + DATE(e.block_time AT TIME ZONE 'UTC') AS date, + e.block_time AS evt_block_time, + e.transaction_hash AS evt_tx_hash, 'REGULAR' AS update_type, LOWER(CONCAT('0x', - "right"(SUBSTRING(TO_HEX(data[2]), 3), 32), - "right"(SUBSTRING(TO_HEX(data[1]), 3), 32) + "right"(SUBSTRING(TO_HEX(e.data[2]), 3), 32), + "right"(SUBSTRING(TO_HEX(e.data[1]), 3), 32) )) AS trove_id, - CAST(bytearray_to_uint256(data[3]) AS DOUBLE) / 1e18 AS debt, - CAST(bytearray_to_uint256(data[5]) AS DOUBLE) / 1e18 AS coll, - CAST(bytearray_to_uint256(data[9]) AS DOUBLE) / 1e18 AS annual_interest_rate, + CAST(bytearray_to_uint256(e.data[3]) AS DOUBLE) / 1e18 AS debt, + CAST(bytearray_to_uint256(e.data[5]) AS DOUBLE) / 1e18 AS coll, + CAST(bytearray_to_uint256(e.data[9]) AS DOUBLE) / 1e18 AS annual_interest_rate, NULL AS shares, NULL AS interest_batch_manager - FROM starknet.events - WHERE from_address = (SELECT events_emitter FROM cfg) - AND keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 - AND block_number >= (SELECT deployment_block FROM cfg) + FROM starknet.events e + INNER JOIN cfg ON e.from_address = cfg.events_emitter + WHERE e.keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 + AND e.block_number >= cfg.deployment_block ), -- Batched Trove Updates (BatchedTroveUpdated events) @@ -57,59 +95,62 @@ trove_updated_events AS ( -- Note: These events do NOT contain direct 'debt' values, but 'shares' which must be resolved against the batch state. batched_trove_updated_events AS ( SELECT - DATE(block_time AT TIME ZONE 'UTC') AS date, - block_time AS evt_block_time, - transaction_hash AS evt_tx_hash, + cfg.branch_id, + DATE(e.block_time AT TIME ZONE 'UTC') AS date, + e.block_time AS evt_block_time, + e.transaction_hash AS evt_tx_hash, 'BATCHED' AS update_type, LOWER(CONCAT('0x', - "right"(SUBSTRING(TO_HEX(data[2]), 3), 32), - "right"(SUBSTRING(TO_HEX(data[1]), 3), 32) + "right"(SUBSTRING(TO_HEX(e.data[2]), 3), 32), + "right"(SUBSTRING(TO_HEX(e.data[1]), 3), 32) )) AS trove_id, NULL AS debt, -- Debt is calculated dynamically via shares - CAST(bytearray_to_uint256(data[6]) AS DOUBLE) / 1e18 AS coll, + CAST(bytearray_to_uint256(e.data[6]) AS DOUBLE) / 1e18 AS coll, NULL AS annual_interest_rate, -- Rate is determined by the batch - CAST(bytearray_to_uint256(data[4]) AS DOUBLE) / 1e18 AS shares, - data[3] AS interest_batch_manager - FROM starknet.events - WHERE from_address = (SELECT events_emitter FROM cfg) - AND keys[1] = 0x03c054baf812ebdae34d56f1322bd89e0b0c4aed0829fe0e7658fce049d25f71 - AND block_number >= (SELECT deployment_block FROM cfg) + CAST(bytearray_to_uint256(e.data[4]) AS DOUBLE) / 1e18 AS shares, + e.data[3] AS interest_batch_manager + FROM starknet.events e + INNER JOIN cfg ON e.from_address = cfg.events_emitter + WHERE e.keys[1] = 0x03c054baf812ebdae34d56f1322bd89e0b0c4aed0829fe0e7658fce049d25f71 + AND e.block_number >= cfg.deployment_block ), -- Batch Updates (BatchUpdated events) -- Tracks global state of interest batches (total debt, total shares) required to resolve individual trove debt. batch_updated_events AS ( SELECT - DATE(block_time AT TIME ZONE 'UTC') AS date, - block_time AS evt_block_time, - transaction_hash AS evt_tx_hash, - data[1] AS interest_batch_manager, - CAST(bytearray_to_uint256(data[3]) AS DOUBLE) / 1e18 AS batch_debt, - CAST(bytearray_to_uint256(data[7]) AS DOUBLE) / 1e18 AS batch_annual_interest_rate, - CAST(bytearray_to_uint256(data[11]) AS DOUBLE) / 1e18 AS batch_total_shares - FROM starknet.events - WHERE from_address = (SELECT events_emitter FROM cfg) - AND keys[1] = 0x039a6ae9867ab3ddba84021a987ac7776389a6cc5bad711eda5e4a44d1a2fe80 - AND block_number >= (SELECT deployment_block FROM cfg) + cfg.branch_id, + DATE(e.block_time AT TIME ZONE 'UTC') AS date, + e.block_time AS evt_block_time, + e.transaction_hash AS evt_tx_hash, + e.data[1] AS interest_batch_manager, + CAST(bytearray_to_uint256(e.data[3]) AS DOUBLE) / 1e18 AS batch_debt, + CAST(bytearray_to_uint256(e.data[7]) AS DOUBLE) / 1e18 AS batch_annual_interest_rate, + CAST(bytearray_to_uint256(e.data[11]) AS DOUBLE) / 1e18 AS batch_total_shares + FROM starknet.events e + INNER JOIN cfg ON e.from_address = cfg.events_emitter + WHERE e.keys[1] = 0x039a6ae9867ab3ddba84021a987ac7776389a6cc5bad711eda5e4a44d1a2fe80 + AND e.block_number >= cfg.deployment_block ), -- NFT Transfers -- Tracks ownership of troves (Troves are ERC721 tokens) nft_transfer_events AS ( SELECT - DATE(block_time AT TIME ZONE 'UTC') AS date, - block_time AS evt_block_time, - transaction_hash AS evt_tx_hash, + cfg.branch_id, + DATE(e.block_time AT TIME ZONE 'UTC') AS date, + e.block_time AS evt_block_time, + e.transaction_hash AS evt_tx_hash, LOWER(CONCAT('0x', - "right"(SUBSTRING(TO_HEX(keys[5]), 3), 32), - "right"(SUBSTRING(TO_HEX(keys[4]), 3), 32) + "right"(SUBSTRING(TO_HEX(e.keys[5]), 3), 32), + "right"(SUBSTRING(TO_HEX(e.keys[4]), 3), 32) )) AS trove_id, - keys[2] AS from_addr, - keys[3] AS to_addr - FROM starknet.events - WHERE from_address = (SELECT trove_nft FROM cfg) - AND keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 - AND block_number >= (SELECT deployment_block FROM cfg) + e.keys[2] AS from_addr, + e.keys[3] AS to_addr + FROM starknet.events e + INNER JOIN cfg ON e.from_address = cfg.trove_nft + WHERE e.keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 + AND e.block_number >= cfg.deployment_block ), -- ============================================================================ @@ -127,6 +168,7 @@ all_trove_updates AS ( daily_trove_states_sparse AS ( SELECT * FROM ( SELECT + branch_id, date, trove_id, debt, @@ -135,7 +177,7 @@ daily_trove_states_sparse AS ( shares, interest_batch_manager, update_type, - ROW_NUMBER() OVER (PARTITION BY trove_id, date ORDER BY evt_block_time DESC) AS rn + ROW_NUMBER() OVER (PARTITION BY branch_id, trove_id, date ORDER BY evt_block_time DESC) AS rn FROM all_trove_updates ) WHERE rn = 1 ), @@ -144,12 +186,13 @@ daily_trove_states_sparse AS ( daily_batch_states_sparse AS ( SELECT * FROM ( SELECT + branch_id, date, interest_batch_manager, batch_debt, batch_total_shares, batch_annual_interest_rate, - ROW_NUMBER() OVER (PARTITION BY interest_batch_manager, date ORDER BY evt_block_time DESC) AS rn + ROW_NUMBER() OVER (PARTITION BY branch_id, interest_batch_manager, date ORDER BY evt_block_time DESC) AS rn FROM batch_updated_events ) WHERE rn = 1 ), @@ -157,10 +200,11 @@ daily_batch_states_sparse AS ( -- Define ownership periods (start_time, end_time) for each trove owner trove_ownership_history AS ( SELECT + branch_id, trove_id, to_addr AS owner, evt_block_time AS start_time, - LEAD(evt_block_time) OVER (PARTITION BY trove_id ORDER BY evt_block_time) AS end_time + LEAD(evt_block_time) OVER (PARTITION BY branch_id, trove_id ORDER BY evt_block_time) AS end_time FROM nft_transfer_events WHERE to_addr != 0x0000000000000000000000000000000000000000 -- Exclude burns ), @@ -178,18 +222,18 @@ date_spine AS ( )) t(x) ), --- Trove Spine: Every date for every trove since its first interaction +-- Trove Spine: Every date for every trove since its first interaction (per branch) trove_spine AS ( - SELECT t.trove_id, d.date - FROM (SELECT trove_id, MIN(date) as start_date FROM all_trove_updates GROUP BY 1) t + SELECT t.branch_id, t.trove_id, d.date + FROM (SELECT branch_id, trove_id, MIN(date) as start_date FROM all_trove_updates GROUP BY 1, 2) t CROSS JOIN date_spine d WHERE d.date >= t.start_date ), --- Batch Spine: Every date for every batch since its inception +-- Batch Spine: Every date for every batch since its inception (per branch) batch_spine AS ( - SELECT b.interest_batch_manager, d.date - FROM (SELECT interest_batch_manager, MIN(date) as start_date FROM batch_updated_events GROUP BY 1) b + SELECT b.branch_id, b.interest_batch_manager, d.date + FROM (SELECT branch_id, interest_batch_manager, MIN(date) as start_date FROM batch_updated_events GROUP BY 1, 2) b CROSS JOIN date_spine d WHERE d.date >= b.start_date ), @@ -197,72 +241,76 @@ batch_spine AS ( -- Filled Trove States: Carry forward the last known values to subsequent days using IGNORE NULLS trove_states_filled AS ( SELECT + ts.branch_id, ts.date, ts.trove_id, LAST_VALUE(dts.debt) IGNORE NULLS OVER ( - PARTITION BY ts.trove_id ORDER BY ts.date + PARTITION BY ts.branch_id, ts.trove_id ORDER BY ts.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS debt, LAST_VALUE(dts.coll) IGNORE NULLS OVER ( - PARTITION BY ts.trove_id ORDER BY ts.date + PARTITION BY ts.branch_id, ts.trove_id ORDER BY ts.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS coll, LAST_VALUE(dts.annual_interest_rate) IGNORE NULLS OVER ( - PARTITION BY ts.trove_id ORDER BY ts.date + PARTITION BY ts.branch_id, ts.trove_id ORDER BY ts.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS annual_interest_rate, LAST_VALUE(dts.shares) IGNORE NULLS OVER ( - PARTITION BY ts.trove_id ORDER BY ts.date + PARTITION BY ts.branch_id, ts.trove_id ORDER BY ts.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS shares, LAST_VALUE(dts.interest_batch_manager) IGNORE NULLS OVER ( - PARTITION BY ts.trove_id ORDER BY ts.date + PARTITION BY ts.branch_id, ts.trove_id ORDER BY ts.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS interest_batch_manager, LAST_VALUE(dts.update_type) IGNORE NULLS OVER ( - PARTITION BY ts.trove_id ORDER BY ts.date + PARTITION BY ts.branch_id, ts.trove_id ORDER BY ts.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS update_type FROM trove_spine ts LEFT JOIN daily_trove_states_sparse dts - ON ts.trove_id = dts.trove_id AND ts.date = dts.date + ON ts.branch_id = dts.branch_id AND ts.trove_id = dts.trove_id AND ts.date = dts.date ), -- Filled Batch States: Carry forward batch parameters (global debt, total shares) batch_states_filled AS ( SELECT + bs.branch_id, bs.date, bs.interest_batch_manager, LAST_VALUE(dbs.batch_debt) IGNORE NULLS OVER ( - PARTITION BY bs.interest_batch_manager ORDER BY bs.date + PARTITION BY bs.branch_id, bs.interest_batch_manager ORDER BY bs.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS batch_debt, LAST_VALUE(dbs.batch_total_shares) IGNORE NULLS OVER ( - PARTITION BY bs.interest_batch_manager ORDER BY bs.date + PARTITION BY bs.branch_id, bs.interest_batch_manager ORDER BY bs.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS batch_total_shares, LAST_VALUE(dbs.batch_annual_interest_rate) IGNORE NULLS OVER ( - PARTITION BY bs.interest_batch_manager ORDER BY bs.date + PARTITION BY bs.branch_id, bs.interest_batch_manager ORDER BY bs.date ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS batch_annual_interest_rate FROM batch_spine bs LEFT JOIN daily_batch_states_sparse dbs - ON bs.interest_batch_manager = dbs.interest_batch_manager AND bs.date = dbs.date + ON bs.branch_id = dbs.branch_id AND bs.interest_batch_manager = dbs.interest_batch_manager AND bs.date = dbs.date ), -- ============================================================================ -- 4. RESOLVE OWNERSHIP AND CALCULATE DEBT -- ============================================================================ --- Determine the owner for each trove on each specific date +-- Determine the owner for each trove on each specific date (per branch) trove_ownership_resolved AS ( SELECT + ts.branch_id, ts.date, ts.trove_id, LOWER(CONCAT('0x', TO_HEX(toh.owner))) AS user FROM trove_spine ts JOIN trove_ownership_history toh - ON ts.trove_id = toh.trove_id + ON ts.branch_id = toh.branch_id + AND ts.trove_id = toh.trove_id AND ts.date >= DATE(toh.start_time) AND (toh.end_time IS NULL OR ts.date < DATE(toh.end_time)) ), @@ -272,6 +320,7 @@ trove_ownership_resolved AS ( -- For Batched troves: calculate debt as (trove_shares * batch_debt) / batch_total_shares. positions_resolved AS ( SELECT + t.branch_id, t.date, COALESCE(o.user, CONCAT('UNKNOWN_', t.trove_id)) AS user, t.trove_id, @@ -292,18 +341,19 @@ positions_resolved AS ( FALSE AS has_orphan_troves FROM trove_states_filled t LEFT JOIN trove_ownership_resolved o - ON t.trove_id = o.trove_id AND t.date = o.date + ON t.branch_id = o.branch_id AND t.trove_id = o.trove_id AND t.date = o.date LEFT JOIN batch_states_filled b - ON t.interest_batch_manager = b.interest_batch_manager AND t.date = b.date + ON t.branch_id = b.branch_id AND t.interest_batch_manager = b.interest_batch_manager AND t.date = b.date ), -- ============================================================================ -- 5. AGGREGATE BY USER AND CALCULATE INTEREST -- ============================================================================ --- Aggregate positions by user per day +-- Aggregate positions by user per day per branch continuous_positions_raw AS ( SELECT + branch_id, date, user, SUM(entire_debt) AS entire_debt, @@ -316,17 +366,18 @@ continuous_positions_raw AS ( END AS annual_interest_rate FROM positions_resolved WHERE (entire_debt > 0 OR entire_coll > 0) - GROUP BY date, user + GROUP BY branch_id, date, user ), -- Apply daily interest accrual (Simple Interest) continuous_positions AS ( SELECT + branch_id, date, user, entire_debt * (1 + annual_interest_rate * DATE_DIFF('day', - LAG(date, 1, date) OVER (PARTITION BY user ORDER BY date), + LAG(date, 1, date) OVER (PARTITION BY branch_id, user ORDER BY date), date ) / 365 ) AS entire_debt, @@ -338,15 +389,16 @@ continuous_positions AS ( -- Calculate daily changes (deltas) for reporting daily_changes AS ( SELECT + branch_id, date, user, entire_debt, entire_coll, annual_interest_rate, - LAG(entire_debt, 1) OVER (PARTITION BY user ORDER BY date) AS prev_debt, - LAG(entire_coll, 1) OVER (PARTITION BY user ORDER BY date) AS prev_coll, - COALESCE(entire_debt, 0) - COALESCE(LAG(entire_debt, 1) OVER (PARTITION BY user ORDER BY date), 0) AS debt_change, - COALESCE(entire_coll, 0) - COALESCE(LAG(entire_coll, 1) OVER (PARTITION BY user ORDER BY date), 0) AS coll_change + LAG(entire_debt, 1) OVER (PARTITION BY branch_id, user ORDER BY date) AS prev_debt, + LAG(entire_coll, 1) OVER (PARTITION BY branch_id, user ORDER BY date) AS prev_coll, + COALESCE(entire_debt, 0) - COALESCE(LAG(entire_debt, 1) OVER (PARTITION BY branch_id, user ORDER BY date), 0) AS debt_change, + COALESCE(entire_coll, 0) - COALESCE(LAG(entire_coll, 1) OVER (PARTITION BY branch_id, user ORDER BY date), 0) AS coll_change FROM continuous_positions WHERE entire_debt > 0 OR entire_coll > 0 ), @@ -355,31 +407,31 @@ daily_changes AS ( -- 6. PRICES AND FINAL OUTPUT -- ============================================================================ --- Daily Price Data from OpenBlockLabs +-- Daily Price Data from OpenBlockLabs (using shared BTC price for all branches) daily_prices AS ( SELECT date, - price AS wbtc_price + price AS btc_price FROM ( SELECT DATE(timestamp AT TIME ZONE 'UTC') AS date, price, ROW_NUMBER() OVER (PARTITION BY DATE(timestamp AT TIME ZONE 'UTC') ORDER BY timestamp DESC) as rn FROM dune.openblocklabs.result_starknet_prices_daily - CROSS JOIN cfg - WHERE contract_address = cfg.wbtc_price_addr + CROSS JOIN shared_cfg + WHERE contract_address = shared_cfg.btc_price_addr ) WHERE rn = 1 ), -- Latest Price Fallback latest_price AS ( - SELECT price AS wbtc_price + SELECT price AS btc_price FROM ( SELECT price, ROW_NUMBER() OVER (ORDER BY timestamp DESC) AS rn FROM dune.openblocklabs.result_starknet_prices_daily - CROSS JOIN cfg - WHERE contract_address = cfg.wbtc_price_addr + CROSS JOIN shared_cfg + WHERE contract_address = shared_cfg.btc_price_addr ) t WHERE rn = 1 ), @@ -387,7 +439,7 @@ latest_price AS ( price_data AS ( SELECT d.date, - COALESCE(dp.wbtc_price, lp.wbtc_price) AS wbtc_price + COALESCE(dp.btc_price, lp.btc_price) AS btc_price FROM (SELECT DISTINCT date FROM daily_changes) d LEFT JOIN daily_prices dp ON dp.date = d.date CROSS JOIN latest_price lp @@ -396,21 +448,22 @@ price_data AS ( -- Final Formatting final_output AS ( SELECT + dc.branch_id, dc.date, dc.user, cfg.market_address, - cfg.market_name, - cfg.market_owner, - cfg.wwbtc_addr AS asset, - 'WWBTC' AS asset_symbol, - 18 AS asset_decimals, - cfg.usdu_addr AS debt_asset, - 'USDU' AS debt_asset_symbol, - 18 AS debt_decimals, + scfg.market_name, + scfg.market_owner, + cfg.asset_addr AS asset, + cfg.asset_symbol, + cfg.asset_decimals, + scfg.usdu_addr AS debt_asset, + scfg.debt_asset_symbol, + scfg.debt_decimals, 1.0 AS latest_accumulator, - cfg.protocol_scale, - pd.wbtc_price AS asset_price, - cfg.debt_price AS debt_price, + scfg.protocol_scale, + pd.btc_price AS asset_price, + scfg.debt_price AS debt_price, dc.entire_coll AS total_supplied_raw, dc.entire_debt AS total_borrowed_raw, dc.coll_change AS raw_amount_deposit, @@ -421,20 +474,22 @@ final_output AS ( dc.entire_debt AS total_borrowed_adjusted, dc.entire_coll AS total_supplied_tokens, dc.entire_debt AS total_borrowed_tokens, - dc.entire_coll * pd.wbtc_price AS total_supplied_usd, - dc.entire_debt * cfg.debt_price AS total_borrowed_usd, - (dc.entire_coll * pd.wbtc_price) - (dc.entire_debt * cfg.debt_price) AS net_usd, + dc.entire_coll * pd.btc_price AS total_supplied_usd, + dc.entire_debt * scfg.debt_price AS total_borrowed_usd, + (dc.entire_coll * pd.btc_price) - (dc.entire_debt * scfg.debt_price) AS net_usd, dc.annual_interest_rate AS effective_apr_daily, dc.entire_debt * (dc.annual_interest_rate / 365) AS interest_tokens_daily, - dc.entire_debt * (dc.annual_interest_rate / 365) * cfg.debt_price AS interest_usd_daily, + dc.entire_debt * (dc.annual_interest_rate / 365) * scfg.debt_price AS interest_usd_daily, FALSE AS has_orphan_troves FROM daily_changes dc - CROSS JOIN cfg + INNER JOIN cfg ON dc.branch_id = cfg.branch_id + CROSS JOIN shared_cfg scfg LEFT JOIN price_data pd ON pd.date = dc.date ) -- Final Selection and Ordering SELECT + branch_id, date, user, market_address, @@ -457,4 +512,4 @@ WHERE (total_supplied_adjusted IS NOT NULL AND total_supplied_adjusted <> 0) OR (total_borrowed_adjusted IS NOT NULL AND total_borrowed_adjusted <> 0) -ORDER BY date DESC, user +ORDER BY date DESC, branch_id, user From 75b9642ce834c9ea1a4b5977fa4096ddd6472f7f Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Mon, 12 Jan 2026 18:23:57 +0100 Subject: [PATCH 13/14] points query --- dune_queries/points/points_query.sql | 347 ++++++++++++++++----------- 1 file changed, 210 insertions(+), 137 deletions(-) diff --git a/dune_queries/points/points_query.sql b/dune_queries/points/points_query.sql index a52abbb..52c0d44 100644 --- a/dune_queries/points/points_query.sql +++ b/dune_queries/points/points_query.sql @@ -4,25 +4,50 @@ with -cfg as ( +-- Shared constants (Ekubo, prices, USDU/USDC tokens) +shared_cfg as ( select - 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 as events_emitter, - 0x04a1e17f1e4d5fdd839fa6a4c52a742b9d10871028b6ffcfb2f1de8dd5dc2cd4 as trove_nft, - 0x001ba4a9e2e86a41c6ed15016eda0404d12bf7b01052cccff1ace84d818335c7 as stability_pool, 0x00000005dd3d2f4429af886cd1a3b08289dbcea99a294197e9eb43b0e0325b4b as ekubo_core, 0x07b696af58c967c1b14c9dde0ace001720635a660a8e90c565ea459345318b30 as ekubo_positions_nft, 0x02f94539f80158f9a48a7acf3747718dfbec9b6f639e2742c1fb44ae7ab5aa04 as usdu_token, 0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8 as usdc_token, - 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac as wbtc_price_addr, + 0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac as wbtc_price_addr +), + +-- Per-branch configuration +cfg as ( + -- WBTC Branch + select + 'WBTC' as branch_id, + 0x038a9949900e7905f648cf0b50335efdac16a8acb8dfc870835da21c3f68e934 as events_emitter, + 0x04a1e17f1e4d5fdd839fa6a4c52a742b9d10871028b6ffcfb2f1de8dd5dc2cd4 as trove_nft, + 0x001ba4a9e2e86a41c6ed15016eda0404d12bf7b01052cccff1ace84d818335c7 as stability_pool, 2759369 as deployment_block + union all + -- solvBTC Branch + select + 'solvBTC' as branch_id, + 0x0 as events_emitter, -- TODO: Update with actual events_emitter for solvBTC + 0x0 as trove_nft, -- TODO: Update with actual trove_nft for solvBTC + 0x0 as stability_pool, -- TODO: Update with actual stability_pool for solvBTC + 0 as deployment_block -- TODO: Update with actual deployment_block for solvBTC + union all + -- tBTC Branch + select + 'tBTC' as branch_id, + 0x0 as events_emitter, -- TODO: Update with actual events_emitter for tBTC + 0x0 as trove_nft, -- TODO: Update with actual trove_nft for tBTC + 0x0 as stability_pool, -- TODO: Update with actual stability_pool for tBTC + 0 as deployment_block -- TODO: Update with actual deployment_block for tBTC ), -- Get deployment date for time series (rounded to nearest 6-hour boundary) +-- Use MIN deployment_block across all branches deployment_date as ( select date_trunc('hour', min(time)) - (cast(extract(hour from min(time)) as integer) % 6) * interval '1' hour as start_hour from starknet.blocks - where number >= (select deployment_block from cfg) + where number >= (select min(deployment_block) from cfg where deployment_block > 0) ), -- ============================================================================ @@ -32,72 +57,74 @@ deployment_date as ( -- Regular Trove Updates (Standard TroveUpdated events) trove_updated_events as ( select - block_time, - block_number, - event_index, + cfg.branch_id, + e.block_time, + e.block_number, + e.event_index, 'REGULAR' as update_type, lower(concat('0x', - "right"(substring(to_hex(data[2]), 3), 32), - "right"(substring(to_hex(data[1]), 3), 32) + "right"(substring(to_hex(e.data[2]), 3), 32), + "right"(substring(to_hex(e.data[1]), 3), 32) )) as trove_id, - cast(bytearray_to_uint256(data[3]) as double) / 1e18 as debt, - cast(bytearray_to_uint256(data[5]) as double) / 1e18 as collateral, - cast(bytearray_to_uint256(data[9]) as double) / 1e16 as interest_rate, - cast(bytearray_to_uint256(data[7]) as double) / 1e18 as stake, + cast(bytearray_to_uint256(e.data[3]) as double) / 1e18 as debt, + cast(bytearray_to_uint256(e.data[5]) as double) / 1e18 as collateral, + cast(bytearray_to_uint256(e.data[9]) as double) / 1e16 as interest_rate, + cast(bytearray_to_uint256(e.data[7]) as double) / 1e18 as stake, NULL as shares, NULL as interest_batch_manager - from starknet.events - where from_address = (select events_emitter from cfg) - and keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 - and block_number >= (select deployment_block from cfg) + from starknet.events e + inner join cfg on e.from_address = cfg.events_emitter + where e.keys[1] = 0x01babc9e592593f609d7e88cca6a04e21db92f5faf85fb83153cc9b369b2b3e6 + and e.block_number >= cfg.deployment_block ), -- Batched Trove Updates (BatchedTroveUpdated events) batched_trove_updated_events as ( select - block_time, - block_number, - event_index, + cfg.branch_id, + e.block_time, + e.block_number, + e.event_index, 'BATCHED' as update_type, lower(concat('0x', - "right"(substring(to_hex(data[2]), 3), 32), - "right"(substring(to_hex(data[1]), 3), 32) + "right"(substring(to_hex(e.data[2]), 3), 32), + "right"(substring(to_hex(e.data[1]), 3), 32) )) as trove_id, NULL as debt, - cast(bytearray_to_uint256(data[6]) as double) / 1e18 as collateral, + cast(bytearray_to_uint256(e.data[6]) as double) / 1e18 as collateral, NULL as interest_rate, - cast(bytearray_to_uint256(data[8]) as double) / 1e18 as stake, -- data[7] is low, data[8] is high? No, need to check ordering. + cast(bytearray_to_uint256(e.data[8]) as double) / 1e18 as stake, -- Standard ordering: -- trove_id: data[1], data[2] -- interest_batch_manager: data[3] -- batch_debt_shares: data[4], data[5] -- coll: data[6], data[7] -- stake: data[8], data[9] - cast(bytearray_to_uint256(data[4]) as double) / 1e18 as shares, - data[3] as interest_batch_manager - from starknet.events - where from_address = (select events_emitter from cfg) - and keys[1] = 0x03c054baf812ebdae34d56f1322bd89e0b0c4aed0829fe0e7658fce049d25f71 - and block_number >= (select deployment_block from cfg) + cast(bytearray_to_uint256(e.data[4]) as double) / 1e18 as shares, + e.data[3] as interest_batch_manager + from starknet.events e + inner join cfg on e.from_address = cfg.events_emitter + where e.keys[1] = 0x03c054baf812ebdae34d56f1322bd89e0b0c4aed0829fe0e7658fce049d25f71 + and e.block_number >= cfg.deployment_block ), -- Batch Updates (BatchUpdated events) batch_updated_events as ( select - block_time, - block_number, - event_index, - data[1] as interest_batch_manager, - cast(bytearray_to_uint256(data[3]) as double) / 1e18 as batch_debt, + cfg.branch_id, + e.block_time, + e.block_number, + e.event_index, + e.data[1] as interest_batch_manager, + cast(bytearray_to_uint256(e.data[3]) as double) / 1e18 as batch_debt, -- annual_interest_rate is data[7], data[8] - cast(bytearray_to_uint256(data[7]) as double) / 1e16 as batch_interest_rate, -- Using 1e16 to match points query convention? OBL used 1e18. - -- Points query uses 1e16 for rate? Let's check line 47: cast(...)/1e16. Yes. + cast(bytearray_to_uint256(e.data[7]) as double) / 1e16 as batch_interest_rate, -- total_debt_shares is data[11], data[12] - cast(bytearray_to_uint256(data[11]) as double) / 1e18 as batch_total_shares - from starknet.events - where from_address = (select events_emitter from cfg) - and keys[1] = 0x039a6ae9867ab3ddba84021a987ac7776389a6cc5bad711eda5e4a44d1a2fe80 - and block_number >= (select deployment_block from cfg) + cast(bytearray_to_uint256(e.data[11]) as double) / 1e18 as batch_total_shares + from starknet.events e + inner join cfg on e.from_address = cfg.events_emitter + where e.keys[1] = 0x039a6ae9867ab3ddba84021a987ac7776389a6cc5bad711eda5e4a44d1a2fe80 + and e.block_number >= cfg.deployment_block ), -- Combine regular and batched updates @@ -110,37 +137,39 @@ all_trove_updates as ( -- Get NFT transfer events to track ownership nft_transfers as ( select - block_time, - block_number, - event_index, + cfg.branch_id, + e.block_time, + e.block_number, + e.event_index, -- Extract to address (new owner) from keys[3] - keys[3] as owner, + e.keys[3] as owner, -- Extract token_id (u256 = keys[4] low, keys[5] high) lower(concat('0x', - "right"(substring(to_hex(keys[5]), 3), 32), - "right"(substring(to_hex(keys[4]), 3), 32) + "right"(substring(to_hex(e.keys[5]), 3), 32), + "right"(substring(to_hex(e.keys[4]), 3), 32) )) as trove_id - from starknet.events - where from_address = (select trove_nft from cfg) - and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 - and block_number >= (select deployment_block from cfg) + from starknet.events e + inner join cfg on e.from_address = cfg.trove_nft + where e.keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 + and e.block_number >= cfg.deployment_block ), --- Get StabilityPool deposit events +-- Get StabilityPool deposit events (per-branch) sp_deposit_events as ( select - block_time, - block_number, - event_index, - data[1] as user_address, - cast(bytearray_to_uint256(data[2]) as double) / 1e18 as deposit_amount - from starknet.events - where from_address = (select stability_pool from cfg) - and keys[1] = 0x0056985e064b256ecdb949713158ae0953473139aaf1fd57197a852fe36c4f00 - and block_number >= (select deployment_block from cfg) -), - --- Track Ekubo NFT position transfers + cfg.branch_id, + e.block_time, + e.block_number, + e.event_index, + e.data[1] as user_address, + cast(bytearray_to_uint256(e.data[2]) as double) / 1e18 as deposit_amount + from starknet.events e + inner join cfg on e.from_address = cfg.stability_pool + where e.keys[1] = 0x0056985e064b256ecdb949713158ae0953473139aaf1fd57197a852fe36c4f00 + and e.block_number >= cfg.deployment_block +), + +-- Track Ekubo NFT position transfers (shared across all branches) ekubo_position_transfers as ( select block_time, @@ -151,13 +180,13 @@ ekubo_position_transfers as ( data[2] as to_address, lower(to_hex(data[3])) as position_id from starknet.events - cross join cfg - where from_address = cfg.ekubo_positions_nft + cross join shared_cfg + where from_address = shared_cfg.ekubo_positions_nft and keys[1] = 0x0099cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 - and block_number >= cfg.deployment_block + and block_number >= (select min(deployment_block) from cfg where deployment_block > 0) ), --- Track Ekubo position liquidity updates +-- Track Ekubo position liquidity updates (shared across all branches) ekubo_position_updates as ( select block_time, @@ -178,12 +207,12 @@ ekubo_position_updates as ( cast(bytearray_to_uint256(data[10]) as bigint) as upper_mag, cast(bytearray_to_uint256(data[11]) as bigint) as upper_sign from starknet.events - cross join cfg - where from_address = cfg.ekubo_core + cross join shared_cfg + where from_address = shared_cfg.ekubo_core and keys[1] = 0x03a7adca3546c213ce791fabf3b04090c163e419c808c9830fb343a4a395946e - and block_number >= cfg.deployment_block - and data[2] = cfg.usdu_token - and data[3] = cfg.usdc_token + and block_number >= (select min(deployment_block) from cfg where deployment_block > 0) + and data[2] = shared_cfg.usdu_token + and data[3] = shared_cfg.usdc_token and cast(bytearray_to_uint256(data[5]) as bigint) = 100 ), @@ -281,57 +310,73 @@ six_hour_slots as ( cross join unnest(time) as time(time) ), --- Get all unique troves +-- Get all unique troves (per branch) troves as ( - select distinct trove_id + select distinct branch_id, trove_id from all_trove_updates ), --- Get all unique batches +-- Get all unique batches (per branch) batches as ( - select distinct interest_batch_manager + select distinct branch_id, interest_batch_manager from batch_updated_events ), --- Get all unique users -all_users as ( - select distinct owner as user_address +-- Get all unique users with their branch associations +-- Note: A user can have positions in multiple branches +all_branch_users as ( + select distinct branch_id, owner as user_address from nft_transfers where owner != 0x0000000000000000000000000000000000000000 union - select distinct user_address + select distinct branch_id, user_address from sp_deposit_events - union +), + +-- Get all unique LP users (no branch association) +all_lp_users as ( select distinct to_address as user_address from ekubo_position_transfers where to_address != 0x0000000000000000000000000000000000000000 ), --- Cross join slots and troves +-- Cross join slots and troves (per branch) slot_trove_combinations as ( select s.slot_start, + t.branch_id, t.trove_id from six_hour_slots s cross join troves t ), --- Cross join slots and batches +-- Cross join slots and batches (per branch) slot_batch_combinations as ( select s.slot_start, + b.branch_id, b.interest_batch_manager from six_hour_slots s cross join batches b ), --- Cross join slots and users -slot_user_combinations as ( +-- Cross join slots and branch users (for SP deposits per branch) +slot_branch_user_combinations as ( select s.slot_start, + u.branch_id, u.user_address from six_hour_slots s - cross join all_users u + cross join all_branch_users u +), + +-- Cross join slots and LP users (no branch) +slot_lp_user_combinations as ( + select + s.slot_start, + u.user_address + from six_hour_slots s + cross join all_lp_users u ), -- Cross join slots and Ekubo positions @@ -343,10 +388,11 @@ slot_position_combinations as ( cross join ekubo_positions p ), --- For each 6-hour slot and trove, get the most recent state +-- For each 6-hour slot, branch, and trove, get the most recent state latest_trove_states as ( select stc.slot_start, + stc.branch_id, stc.trove_id, te.debt, te.collateral, @@ -356,65 +402,72 @@ latest_trove_states as ( te.interest_batch_manager, te.update_type, row_number() over ( - partition by stc.slot_start, stc.trove_id + partition by stc.slot_start, stc.branch_id, stc.trove_id order by te.block_time desc, te.event_index desc ) as rn from slot_trove_combinations stc left join all_trove_updates te - on stc.trove_id = te.trove_id + on stc.branch_id = te.branch_id + and stc.trove_id = te.trove_id and te.block_time <= stc.slot_start + interval '6' hour where te.trove_id is not null ), --- For each 6-hour slot and batch, get the most recent state +-- For each 6-hour slot, branch, and batch, get the most recent state latest_batch_states as ( select sbc.slot_start, + sbc.branch_id, sbc.interest_batch_manager, be.batch_debt, be.batch_total_shares, be.batch_interest_rate, row_number() over ( - partition by sbc.slot_start, sbc.interest_batch_manager + partition by sbc.slot_start, sbc.branch_id, sbc.interest_batch_manager order by be.block_time desc, be.event_index desc ) as rn from slot_batch_combinations sbc left join batch_updated_events be - on sbc.interest_batch_manager = be.interest_batch_manager + on sbc.branch_id = be.branch_id + and sbc.interest_batch_manager = be.interest_batch_manager and be.block_time <= sbc.slot_start + interval '6' hour where be.interest_batch_manager is not null ), --- For each 6-hour slot and trove, get the most recent owner +-- For each 6-hour slot, branch, and trove, get the most recent owner latest_owners as ( select stc.slot_start, + stc.branch_id, stc.trove_id, nt.owner, row_number() over ( - partition by stc.slot_start, stc.trove_id + partition by stc.slot_start, stc.branch_id, stc.trove_id order by nt.block_time desc, nt.event_index desc ) as rn from slot_trove_combinations stc left join nft_transfers nt - on stc.trove_id = nt.trove_id + on stc.branch_id = nt.branch_id + and stc.trove_id = nt.trove_id and nt.block_time <= stc.slot_start + interval '6' hour where nt.trove_id is not null ), --- For each 6-hour slot and user, get the most recent SP deposit +-- For each 6-hour slot, branch, and user, get the most recent SP deposit latest_sp_deposits as ( select suc.slot_start, + suc.branch_id, suc.user_address, sp.deposit_amount, row_number() over ( - partition by suc.slot_start, suc.user_address + partition by suc.slot_start, suc.branch_id, suc.user_address order by sp.block_time desc, sp.event_index desc ) as rn - from slot_user_combinations suc + from slot_branch_user_combinations suc left join sp_deposit_events sp - on suc.user_address = sp.user_address + on suc.branch_id = sp.branch_id + and suc.user_address = sp.user_address and sp.block_time <= suc.slot_start + interval '6' hour where sp.user_address is not null ), @@ -478,9 +531,10 @@ user_lp_positions as ( -- 3. RESOLVE TROVE STATE AND COMBINE -- ============================================================================ --- Combine trove state, ownership, and batch data to resolve final values +-- Combine trove state, ownership, and batch data to resolve final values (per branch) trove_snapshots as ( select + ls.branch_id, ls.slot_start, ls.trove_id, lo.owner as user_address, @@ -504,45 +558,46 @@ trove_snapshots as ( from latest_trove_states ls left join latest_owners lo on ls.slot_start = lo.slot_start + and ls.branch_id = lo.branch_id and ls.trove_id = lo.trove_id and lo.rn = 1 left join latest_batch_states lbs on ls.slot_start = lbs.slot_start + and ls.branch_id = lbs.branch_id and ls.interest_batch_manager = lbs.interest_batch_manager and lbs.rn = 1 where ls.rn = 1 ), --- Get users with positions but no troves (SP and/or LP only) -non_trove_users as ( +-- Get users with SP deposits but no troves in that branch +sp_only_users as ( select distinct + suc.branch_id, suc.slot_start, suc.user_address, - coalesce(lsd.deposit_amount, 0) as sp_deposit, - coalesce(ulp.total_usdu_in_lp, 0) as usdu_in_lp, - coalesce(ulp.total_usdc_in_lp, 0) as usdc_in_lp, - coalesce(ulp.total_lp_value_usd, 0) as lp_value_usd - from slot_user_combinations suc + coalesce(lsd.deposit_amount, 0) as sp_deposit + from slot_branch_user_combinations suc left join latest_sp_deposits lsd - on suc.user_address = lsd.user_address + on suc.branch_id = lsd.branch_id + and suc.user_address = lsd.user_address and suc.slot_start = lsd.slot_start and lsd.rn = 1 - left join user_lp_positions ulp - on suc.user_address = ulp.user_address - and suc.slot_start = ulp.slot_start where not exists ( select 1 from trove_snapshots ts - where ts.user_address = suc.user_address + where ts.branch_id = suc.branch_id + and ts.user_address = suc.user_address and ts.slot_start = suc.slot_start ) - and (coalesce(lsd.deposit_amount, 0) > 0 or coalesce(ulp.total_lp_value_usd, 0) > 0) + and coalesce(lsd.deposit_amount, 0) > 0 ), --- Combine trove snapshots with SP and LP deposits +-- Combine trove snapshots with branch-specific SP deposits +-- LP is handled separately to avoid duplication final_snapshots as ( - -- Trove owners with their SP and LP positions + -- Trove owners with their branch-specific SP deposits (no LP here) select + ts.branch_id, ts.slot_start, ts.trove_id, ts.user_address, @@ -550,22 +605,21 @@ final_snapshots as ( ts.collateral, ts.interest_rate, coalesce(lsd.deposit_amount, 0) as sp_deposit, - coalesce(ulp.total_usdu_in_lp, 0) as usdu_in_lp, - coalesce(ulp.total_usdc_in_lp, 0) as usdc_in_lp, - coalesce(ulp.total_lp_value_usd, 0) as lp_value_usd + 0 as usdu_in_lp, + 0 as usdc_in_lp, + 0 as lp_value_usd from trove_snapshots ts left join latest_sp_deposits lsd - on ts.user_address = lsd.user_address + on ts.branch_id = lsd.branch_id + and ts.user_address = lsd.user_address and ts.slot_start = lsd.slot_start and lsd.rn = 1 - left join user_lp_positions ulp - on ts.user_address = ulp.user_address - and ts.slot_start = ulp.slot_start union all - -- Users with only SP/LP positions (no troves) + -- Users with only SP deposits in a branch (no troves in that branch) select + branch_id, slot_start, null as trove_id, user_address, @@ -573,13 +627,30 @@ final_snapshots as ( 0 as collateral, 0 as interest_rate, sp_deposit, - usdu_in_lp, - usdc_in_lp, - lp_value_usd - from non_trove_users + 0 as usdu_in_lp, + 0 as usdc_in_lp, + 0 as lp_value_usd + from sp_only_users + + union all + + -- LP-only rows (separate, not duplicated across branches) + select + 'LP' as branch_id, + slot_start, + null as trove_id, + user_address, + 0 as debt, + 0 as collateral, + 0 as interest_rate, + 0 as sp_deposit, + total_usdu_in_lp as usdu_in_lp, + total_usdc_in_lp as usdc_in_lp, + total_lp_value_usd as lp_value_usd + from user_lp_positions ), --- Get WBTC prices (deduplicated daily) +-- Get WBTC prices (deduplicated daily) - using shared_cfg daily_prices as ( select date, @@ -590,8 +661,8 @@ daily_prices as ( price, row_number() over (partition by date(timestamp at time zone 'UTC') order by timestamp desc) as rn from dune.openblocklabs.result_starknet_prices_daily - cross join cfg - where contract_address = cfg.wbtc_price_addr + cross join shared_cfg + where contract_address = shared_cfg.wbtc_price_addr ) where rn = 1 ), @@ -603,8 +674,8 @@ latest_price as ( select price, row_number() over (order by timestamp desc) as rn from dune.openblocklabs.result_starknet_prices_daily - cross join cfg - where contract_address = cfg.wbtc_price_addr + cross join shared_cfg + where contract_address = shared_cfg.wbtc_price_addr ) t where rn = 1 ), @@ -612,6 +683,7 @@ latest_price as ( -- Combine snapshots with prices snapshots_with_prices as ( select + fs.branch_id, fs.slot_start, fs.trove_id, fs.user_address, @@ -631,6 +703,7 @@ snapshots_with_prices as ( ) select + branch_id, slot_start as snapshot_time, trove_id, user_address as owner, @@ -652,4 +725,4 @@ from snapshots_with_prices where (debt > 0.000001 or sp_deposit > 0 or lp_value_usd > 0) -- Show active troves or users with SP/LP deposits -- OPTIONAL: Uncomment to filter to last N days for weekly reports -- and slot_start >= current_timestamp - interval '7' day -order by snapshot_time desc, owner asc, trove_id asc +order by snapshot_time desc, branch_id, owner asc, trove_id asc From 82d9c0c167cb8e8deb35cbdf3db52c84a78faf50 Mon Sep 17 00:00:00 2001 From: Scott Piriou <30843220+pscott@users.noreply.github.com> Date: Mon, 19 Jan 2026 09:52:33 +0100 Subject: [PATCH 14/14] latest updates --- dune_queries/obl/obl_query.sql | 32 ++++++++++++++-------------- dune_queries/points/points_query.sql | 28 +++++++----------------- 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/dune_queries/obl/obl_query.sql b/dune_queries/obl/obl_query.sql index ad85fd4..13f0307 100644 --- a/dune_queries/obl/obl_query.sql +++ b/dune_queries/obl/obl_query.sql @@ -38,28 +38,28 @@ cfg AS ( -- solvBTC Branch SELECT 'solvBTC' AS branch_id, - 'wsolvBTC' AS asset_symbol, + 'solvBTC' AS asset_symbol, 18 AS asset_decimals, - 0 AS deployment_block, -- TODO: Update with actual deployment block for solvBTC - 0x0 AS market_address_raw, -- TODO: Update with actual market address for solvBTC - CAST(0x0 AS VARBINARY) AS market_address, -- TODO: Update with actual market address for solvBTC - 0x0 AS asset_addr_raw, -- TODO: Update with actual wsolvBTC address - CAST(0x0 AS VARBINARY) AS asset_addr, -- TODO: Update with actual wsolvBTC address - 0x0 AS events_emitter, -- TODO: Update with actual events_emitter for solvBTC - 0x0 AS trove_nft -- TODO: Update with actual trove_nft for solvBTC + 5572500 AS deployment_block, + 0x074ffc9708c6d3b23ff6eb89a5b780ce1e23de71735b7e3353cde9172d5321af AS market_address_raw, + CAST(0x074ffc9708c6d3b23ff6eb89a5b780ce1e23de71735b7e3353cde9172d5321af AS VARBINARY) AS market_address, + 0x0593e034DdA23eea82d2bA9a30960ED42CF4A01502Cc2351Dc9B9881F9931a68 AS asset_addr_raw, + CAST(0x0593e034DdA23eea82d2bA9a30960ED42CF4A01502Cc2351Dc9B9881F9931a68 AS VARBINARY) AS asset_addr, + 0x053fc2fb416e7d5e8e44594c989b638ffabf8a8f804a2041e564db7febccc2a9 AS events_emitter, + 0x034db33f4917ad9f8ca423e7295c303bd21cdf1724749576e35b022d9d70d006 AS trove_nft UNION ALL -- tBTC Branch SELECT 'tBTC' AS branch_id, - 'wtBTC' AS asset_symbol, + 'tBTC' AS asset_symbol, 18 AS asset_decimals, - 0 AS deployment_block, -- TODO: Update with actual deployment block for tBTC - 0x0 AS market_address_raw, -- TODO: Update with actual market address for tBTC - CAST(0x0 AS VARBINARY) AS market_address, -- TODO: Update with actual market address for tBTC - 0x0 AS asset_addr_raw, -- TODO: Update with actual wtBTC address - CAST(0x0 AS VARBINARY) AS asset_addr, -- TODO: Update with actual wtBTC address - 0x0 AS events_emitter, -- TODO: Update with actual events_emitter for tBTC - 0x0 AS trove_nft -- TODO: Update with actual trove_nft for tBTC + 5572000 AS deployment_block, + 0x07f3317c9c26f3956759eb8de992e474dd8feaaccdd3f53d7c8e808503258d77 AS market_address_raw, + CAST(0x07f3317c9c26f3956759eb8de992e474dd8feaaccdd3f53d7c8e808503258d77 AS VARBINARY) AS market_address, + 0x04daa17763b286d1e59b97c283C0b8C949994C361e426A28F743c67bDfE9a32f AS asset_addr_raw, + CAST(0x04daa17763b286d1e59b97c283C0b8C949994C361e426A28F743c67bDfE9a32f AS VARBINARY) AS asset_addr, + 0x02456ca1b9fcf7851577d92c3a4bc26b59c4f44ee222d67ae7032350ee195ac1 AS events_emitter, + 0x063ed1e2a28424366a10123713d601ce0158fc0a7026ace0cf98bd1872d93f1e AS trove_nft ), -- ============================================================================ diff --git a/dune_queries/points/points_query.sql b/dune_queries/points/points_query.sql index 52c0d44..ceb8ca7 100644 --- a/dune_queries/points/points_query.sql +++ b/dune_queries/points/points_query.sql @@ -27,18 +27,18 @@ cfg as ( -- solvBTC Branch select 'solvBTC' as branch_id, - 0x0 as events_emitter, -- TODO: Update with actual events_emitter for solvBTC - 0x0 as trove_nft, -- TODO: Update with actual trove_nft for solvBTC - 0x0 as stability_pool, -- TODO: Update with actual stability_pool for solvBTC - 0 as deployment_block -- TODO: Update with actual deployment_block for solvBTC + 0x053fc2fb416e7d5e8e44594c989b638ffabf8a8f804a2041e564db7febccc2a9 as events_emitter, + 0x034db33f4917ad9f8ca423e7295c303bd21cdf1724749576e35b022d9d70d006 as trove_nft, + 0x0154d14c879ce7dfe559628ed1abff2df38974efd27abf10d9236d05c6aa4741 as stability_pool, + 5572500 as deployment_block union all -- tBTC Branch select 'tBTC' as branch_id, - 0x0 as events_emitter, -- TODO: Update with actual events_emitter for tBTC - 0x0 as trove_nft, -- TODO: Update with actual trove_nft for tBTC - 0x0 as stability_pool, -- TODO: Update with actual stability_pool for tBTC - 0 as deployment_block -- TODO: Update with actual deployment_block for tBTC + 0x02456ca1b9fcf7851577d92c3a4bc26b59c4f44ee222d67ae7032350ee195ac1 as events_emitter, + 0x063ed1e2a28424366a10123713d601ce0158fc0a7026ace0cf98bd1872d93f1e as trove_nft, + 0x000a36230f3d17cba0acb9635810209fe430c26ae585cbfd61e39cac0a9af6fc as stability_pool, + 5572000 as deployment_block ), -- Get deployment date for time series (rounded to nearest 6-hour boundary) @@ -514,8 +514,6 @@ user_lp_positions as ( select lpl.slot_start, lpo.owner as user_address, - sum(lpl.cumulative_usdu) as total_usdu_in_lp, - sum(lpl.cumulative_usdc) as total_usdc_in_lp, sum(lpl.cumulative_usd_value) as total_lp_value_usd from latest_position_liquidity lpl inner join latest_position_owners lpo @@ -605,8 +603,6 @@ final_snapshots as ( ts.collateral, ts.interest_rate, coalesce(lsd.deposit_amount, 0) as sp_deposit, - 0 as usdu_in_lp, - 0 as usdc_in_lp, 0 as lp_value_usd from trove_snapshots ts left join latest_sp_deposits lsd @@ -627,8 +623,6 @@ final_snapshots as ( 0 as collateral, 0 as interest_rate, sp_deposit, - 0 as usdu_in_lp, - 0 as usdc_in_lp, 0 as lp_value_usd from sp_only_users @@ -644,8 +638,6 @@ final_snapshots as ( 0 as collateral, 0 as interest_rate, 0 as sp_deposit, - total_usdu_in_lp as usdu_in_lp, - total_usdc_in_lp as usdc_in_lp, total_lp_value_usd as lp_value_usd from user_lp_positions ), @@ -693,8 +685,6 @@ snapshots_with_prices as ( fs.debt, fs.interest_rate, fs.sp_deposit, - fs.usdu_in_lp, - fs.usdc_in_lp, fs.lp_value_usd from final_snapshots fs left join daily_prices dp @@ -717,8 +707,6 @@ select end as collateral_ratio, interest_rate, sp_deposit as in_stability_pool, - usdu_in_lp, - usdc_in_lp, lp_value_usd, wbtc_price from snapshots_with_prices