From 70da7c05bb1614868cf72a4127c03bbd6321e77a Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Wed, 4 Mar 2026 22:53:04 +0700 Subject: [PATCH 1/2] chore: distribute rewards to data provider --- .../migrations/033-order-book-settlement.sql | 78 ++++++++++++++----- internal/migrations/036-order-book-audit.sql | 18 +++-- internal/migrations/042-lp-rewards-config.sql | 6 +- 3 files changed, 75 insertions(+), 27 deletions(-) diff --git a/internal/migrations/033-order-book-settlement.sql b/internal/migrations/033-order-book-settlement.sql index 2518ec20..4abe1364 100644 --- a/internal/migrations/033-order-book-settlement.sql +++ b/internal/migrations/033-order-book-settlement.sql @@ -121,24 +121,63 @@ CREATE OR REPLACE ACTION distribute_fees( $query_id INT, $total_fees NUMERIC(78, 0) ) PRIVATE { - -- Get market's bridge for unlock operations + -- Get market details for fee split and unlock $bridge TEXT; - for $row in SELECT bridge FROM ob_queries WHERE id = $query_id { + $query_components BYTEA; + for $row in SELECT bridge, query_components FROM ob_queries WHERE id = $query_id { $bridge := $row.bridge; + $query_components := $row.query_components; } if $bridge IS NULL { ERROR('Market not found for query_id: ' || $query_id::TEXT); } - -- Step 1: Count distinct blocks sampled for this market + -- Step 0: Calculate Shares (75/12.5/12.5 split) + -- Target: 1.5% (LPs), 0.25% (DP), 0.25% (Validator) out of 2.0% total fees + -- Split of the 2.0% pool: 75% LPs, 12.5% DP, 12.5% Validator + $lp_share NUMERIC(78, 0) := ($total_fees * 75::NUMERIC(78, 0)) / 100::NUMERIC(78, 0); + $infra_share NUMERIC(78, 0) := ($total_fees * 125::NUMERIC(78, 0)) / 1000::NUMERIC(78, 0); + + -- Ensure 100% distribution: add any rounding dust to LP pool + $lp_share := $lp_share + ($total_fees - $lp_share - (2::NUMERIC(78, 0) * $infra_share)); + + -- Step 1: Payout Data Provider (0.25%) + $dp_addr BYTEA; + for $row in tn_utils.unpack_query_components($query_components) { + $dp_addr := $row.data_provider; + } + + if $dp_addr IS NOT NULL AND $infra_share > '0'::NUMERIC(78, 0) { + $dp_wallet TEXT := '0x' || encode($dp_addr, 'hex'); + if $bridge = 'hoodi_tt2' { + hoodi_tt2.unlock($dp_wallet, $infra_share); + } else if $bridge = 'sepolia_bridge' { + sepolia_bridge.unlock($dp_wallet, $infra_share); + } else if $bridge = 'ethereum_bridge' { + ethereum_bridge.unlock($dp_wallet, $infra_share); + } + } + + -- Step 2: Payout Validator (Leader) (0.25%) + -- Use @leader_sender to incentivize active block production + if @leader_sender IS NOT NULL AND $infra_share > '0'::NUMERIC(78, 0) { + $validator_wallet TEXT := '0x' || encode(@leader_sender, 'hex'); + if $bridge = 'hoodi_tt2' { + hoodi_tt2.unlock($validator_wallet, $infra_share); + } else if $bridge = 'sepolia_bridge' { + sepolia_bridge.unlock($validator_wallet, $infra_share); + } else if $bridge = 'ethereum_bridge' { + ethereum_bridge.unlock($validator_wallet, $infra_share); + } + } + + -- Step 3: Count distinct blocks sampled for this market $block_count INT := 0; for $row in SELECT COUNT(DISTINCT block) as cnt FROM ob_rewards WHERE query_id = $query_id { $block_count := $row.cnt; } - -- Step 2: Generate distribution ID and create summary record ALWAYS - -- This provides visibility into settled markets even if no rewards were distributed - -- or if zero fees were collected. + -- Step 4: Generate distribution ID and create summary record ALWAYS $distribution_id INT; for $row in SELECT COALESCE(MAX(id), 0) + 1 as next_id FROM ob_fee_distributions { $distribution_id := $row.next_id; @@ -152,11 +191,8 @@ CREATE OR REPLACE ACTION distribute_fees( $wallet_addresses TEXT[]; $amounts NUMERIC(78, 0)[]; - if $block_count > 0 AND $total_fees > '0'::NUMERIC(78, 0) { - -- Step 3: Calculate rewards with zero-loss distribution - -- We calculate all distribution arrays in a SINGLE query using simplified logic - -- to avoid performance bottlenecks in the Kwil engine. - + if $block_count > 0 AND $lp_share > '0'::NUMERIC(78, 0) { + -- Step 5: Calculate rewards with zero-loss distribution -- Get the first participant ID to handle the remainder (dust) $min_participant_id INT; for $row in SELECT MIN(participant_id) as mid FROM ob_rewards WHERE query_id = $query_id { @@ -166,7 +202,7 @@ CREATE OR REPLACE ACTION distribute_fees( -- Calculate total distributed to find the remainder $total_distributed_base NUMERIC(78, 0) := '0'::NUMERIC(78, 0); for $row in - SELECT SUM((($total_fees::NUMERIC(78, 20) * total_percent_numeric) / (100::NUMERIC(78, 20) * $block_count::NUMERIC(78, 20)))::NUMERIC(78, 0))::NUMERIC(78, 0) as total + SELECT SUM((($lp_share::NUMERIC(78, 20) * total_percent_numeric) / (100::NUMERIC(78, 20) * $block_count::NUMERIC(78, 20)))::NUMERIC(78, 0))::NUMERIC(78, 0) as total FROM ( SELECT SUM(reward_percent)::NUMERIC(78, 20) as total_percent_numeric FROM ob_rewards @@ -177,7 +213,7 @@ CREATE OR REPLACE ACTION distribute_fees( $total_distributed_base := $row.total; } - $remainder NUMERIC(78, 0) := $total_fees - $total_distributed_base; + $remainder NUMERIC(78, 0) := $lp_share - $total_distributed_base; -- Aggregate into arrays for batch processing for $result in @@ -195,7 +231,7 @@ CREATE OR REPLACE ACTION distribute_fees( SELECT participant_id, wallet_address, - (($total_fees::NUMERIC(78, 20) * total_percent_numeric) / (100::NUMERIC(78, 20) * $block_count::NUMERIC(78, 20)))::NUMERIC(78, 0) + + (($lp_share::NUMERIC(78, 20) * total_percent_numeric) / (100::NUMERIC(78, 20) * $block_count::NUMERIC(78, 20)))::NUMERIC(78, 0) + (CASE WHEN participant_id = $min_participant_id THEN $remainder ELSE '0'::NUMERIC(78, 0) END) as final_reward FROM participant_totals ), @@ -214,18 +250,20 @@ CREATE OR REPLACE ACTION distribute_fees( if $wallet_addresses IS NOT NULL AND COALESCE(array_length($wallet_addresses), 0) > 0 { $lp_count := array_length($wallet_addresses); - $actual_fees_distributed := $total_fees; + $actual_fees_distributed := $lp_share; - -- Step 4: Batch unlock to all qualifying LPs + -- Step 6: Batch unlock to all qualifying LPs ob_batch_unlock_collateral($bridge, $wallet_addresses, $amounts); } } - -- Step 5: Insert distribution summary + -- Step 7: Insert distribution summary INSERT INTO ob_fee_distributions ( id, query_id, total_fees_distributed, + total_dp_fees, + total_validator_fees, total_lp_count, block_count, distributed_at @@ -233,12 +271,14 @@ CREATE OR REPLACE ACTION distribute_fees( $distribution_id, $query_id, $actual_fees_distributed, + $infra_share, + $infra_share, $lp_count, $block_count, @block_timestamp ); - -- Step 6: Insert per-LP details (only if LPs exist) + -- Step 8: Insert per-LP details (only if LPs exist) if $lp_count > 0 { for $payout in SELECT wallet, amount FROM UNNEST($wallet_addresses, $amounts) AS p(wallet, amount) { $wallet_hex TEXT := $payout.wallet; @@ -276,7 +316,7 @@ CREATE OR REPLACE ACTION distribute_fees( } } - -- Step 7: Cleanup + -- Step 9: Cleanup if $lp_count > 0 { DELETE FROM ob_rewards WHERE query_id = $query_id; } diff --git a/internal/migrations/036-order-book-audit.sql b/internal/migrations/036-order-book-audit.sql index 61dbd9c0..7a1fb209 100644 --- a/internal/migrations/036-order-book-audit.sql +++ b/internal/migrations/036-order-book-audit.sql @@ -49,7 +49,9 @@ CREATE TABLE IF NOT EXISTS ob_fee_distributions ( id INT PRIMARY KEY, query_id INT NOT NULL, - total_fees_distributed NUMERIC(78, 0) NOT NULL, + total_fees_distributed NUMERIC(78, 0) NOT NULL, -- Total LP fees + total_dp_fees NUMERIC(78, 0) NOT NULL DEFAULT 0, + total_validator_fees NUMERIC(78, 0) NOT NULL DEFAULT 0, total_lp_count INT NOT NULL, block_count INT NOT NULL, distributed_at INT8 NOT NULL, @@ -151,7 +153,9 @@ CREATE OR REPLACE ACTION get_distribution_summary( $query_id INT ) PUBLIC VIEW RETURNS TABLE( distribution_id INT, - total_fees_distributed NUMERIC(78, 0), + total_lp_fees_distributed NUMERIC(78, 0), + total_dp_fees NUMERIC(78, 0), + total_validator_fees NUMERIC(78, 0), total_lp_count INT, block_count INT, distributed_at INT8 @@ -159,7 +163,9 @@ CREATE OR REPLACE ACTION get_distribution_summary( for $row in SELECT id as distribution_id, - total_fees_distributed, + total_fees_distributed as total_lp_fees_distributed, + total_dp_fees, + total_validator_fees, total_lp_count, block_count, distributed_at @@ -167,7 +173,7 @@ CREATE OR REPLACE ACTION get_distribution_summary( WHERE query_id = $query_id ORDER BY distributed_at DESC { - RETURN NEXT $row.distribution_id, $row.total_fees_distributed, $row.total_lp_count, $row.block_count, $row.distributed_at; + RETURN NEXT $row.distribution_id, $row.total_lp_fees_distributed, $row.total_dp_fees, $row.total_validator_fees, $row.total_lp_count, $row.block_count, $row.distributed_at; } }; @@ -191,12 +197,14 @@ CREATE OR REPLACE ACTION get_distribution_summary( CREATE OR REPLACE ACTION get_distribution_details( $distribution_id INT ) PUBLIC VIEW RETURNS TABLE( + participant_id INT, wallet_address TEXT, reward_amount NUMERIC(78, 0), total_reward_percent NUMERIC(10, 2) ) { for $row in SELECT + participant_id, '0x' || encode(wallet_address, 'hex') as wallet_hex, reward_amount, total_reward_percent @@ -204,7 +212,7 @@ CREATE OR REPLACE ACTION get_distribution_details( WHERE distribution_id = $distribution_id ORDER BY reward_amount DESC { - RETURN NEXT $row.wallet_hex, $row.reward_amount, $row.total_reward_percent; + RETURN NEXT $row.participant_id, $row.wallet_hex, $row.reward_amount, $row.total_reward_percent; } }; diff --git a/internal/migrations/042-lp-rewards-config.sql b/internal/migrations/042-lp-rewards-config.sql index 9344c13e..1b9a4f51 100644 --- a/internal/migrations/042-lp-rewards-config.sql +++ b/internal/migrations/042-lp-rewards-config.sql @@ -32,7 +32,7 @@ CREATE TABLE IF NOT EXISTS lp_rewards_config ( id INT PRIMARY KEY DEFAULT 1, enabled BOOL NOT NULL DEFAULT TRUE, - sampling_interval_blocks INT NOT NULL DEFAULT 10, + sampling_interval_blocks INT NOT NULL DEFAULT 50, max_markets_per_run INT NOT NULL DEFAULT 1000, CHECK (id = 1), CHECK (sampling_interval_blocks >= 1), @@ -41,7 +41,7 @@ CREATE TABLE IF NOT EXISTS lp_rewards_config ( -- Insert default configuration INSERT INTO lp_rewards_config (id, enabled, sampling_interval_blocks, max_markets_per_run) -VALUES (1, TRUE, 10, 1000) +VALUES (1, TRUE, 50, 1000) ON CONFLICT (id) DO NOTHING; -- ============================================================================ @@ -66,7 +66,7 @@ PUBLIC VIEW RETURNS ( RETURN $row.enabled, $row.sampling_interval_blocks, $row.max_markets_per_run; } -- Default if no row exists - RETURN TRUE, 10, 1000; + RETURN TRUE, 50, 1000; }; /** From 0585ffdc22c7b889ca37af22431577cdbffb1235 Mon Sep 17 00:00:00 2001 From: Michael Buntarman Date: Wed, 4 Mar 2026 23:34:32 +0700 Subject: [PATCH 2/2] chore: apply suggestion --- internal/migrations/033-order-book-settlement.sql | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/internal/migrations/033-order-book-settlement.sql b/internal/migrations/033-order-book-settlement.sql index 4abe1364..b0fb43ce 100644 --- a/internal/migrations/033-order-book-settlement.sql +++ b/internal/migrations/033-order-book-settlement.sql @@ -147,27 +147,35 @@ CREATE OR REPLACE ACTION distribute_fees( $dp_addr := $row.data_provider; } + $actual_dp_fees NUMERIC(78, 0) := '0'::NUMERIC(78, 0); if $dp_addr IS NOT NULL AND $infra_share > '0'::NUMERIC(78, 0) { $dp_wallet TEXT := '0x' || encode($dp_addr, 'hex'); if $bridge = 'hoodi_tt2' { hoodi_tt2.unlock($dp_wallet, $infra_share); + $actual_dp_fees := $infra_share; } else if $bridge = 'sepolia_bridge' { sepolia_bridge.unlock($dp_wallet, $infra_share); + $actual_dp_fees := $infra_share; } else if $bridge = 'ethereum_bridge' { ethereum_bridge.unlock($dp_wallet, $infra_share); + $actual_dp_fees := $infra_share; } } -- Step 2: Payout Validator (Leader) (0.25%) -- Use @leader_sender to incentivize active block production + $actual_validator_fees NUMERIC(78, 0) := '0'::NUMERIC(78, 0); if @leader_sender IS NOT NULL AND $infra_share > '0'::NUMERIC(78, 0) { $validator_wallet TEXT := '0x' || encode(@leader_sender, 'hex'); if $bridge = 'hoodi_tt2' { hoodi_tt2.unlock($validator_wallet, $infra_share); + $actual_validator_fees := $infra_share; } else if $bridge = 'sepolia_bridge' { sepolia_bridge.unlock($validator_wallet, $infra_share); + $actual_validator_fees := $infra_share; } else if $bridge = 'ethereum_bridge' { ethereum_bridge.unlock($validator_wallet, $infra_share); + $actual_validator_fees := $infra_share; } } @@ -271,8 +279,8 @@ CREATE OR REPLACE ACTION distribute_fees( $distribution_id, $query_id, $actual_fees_distributed, - $infra_share, - $infra_share, + $actual_dp_fees, + $actual_validator_fees, $lp_count, $block_count, @block_timestamp