Skip to content

feat: record order events for wallet history#1343

Merged
MicBun merged 3 commits into
mainfrom
walletHistorical
Mar 25, 2026
Merged

feat: record order events for wallet history#1343
MicBun merged 3 commits into
mainfrom
walletHistorical

Conversation

@MicBun
Copy link
Copy Markdown
Contributor

@MicBun MicBun commented Mar 25, 2026

resolves: https://github.com/truflation/website/issues/3547

Summary by CodeRabbit

Release Notes

  • New Features

    • Order events are now systematically recorded throughout the complete order lifecycle, capturing placement, cancellation, modification, direct matching, minting/burning operations, and final settlement.
    • Order event cleanup functionality enables managing and trimming historical records.
  • Tests

    • Comprehensive test coverage added for order event recording and verification of database persistence across all order operations.

@MicBun MicBun requested a review from pr-time-tracker March 25, 2026 11:42
@MicBun MicBun self-assigned this Mar 25, 2026
@MicBun MicBun added the type: feat New feature or request label Mar 25, 2026
@holdex
Copy link
Copy Markdown

holdex Bot commented Mar 25, 2026

Time Submission Status

Member Status Time Action Last Update
MicBun ✅ Submitted 4h 30min Update time Mar 25, 2026, 1:10 PM

You can submit time with the command. Example:

@holdex pr submit-time 15m

See available commands to help comply with our Guidelines.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

Warning

Rate limit exceeded

@MicBun has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 14 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1e99596f-8a0f-4d51-a667-20b6a52a7029

📥 Commits

Reviewing files that changed from the base of the PR and between 1f64117 and 10957b0.

📒 Files selected for processing (3)
  • internal/migrations/033-order-book-settlement.sql
  • internal/migrations/044-order-book-events.sql
  • tests/streams/order_book/order_events_test.go
📝 Walkthrough

Walkthrough

This PR adds comprehensive order event recording to the order-book system. It creates a new ob_order_events table to persist event records and instruments all order lifecycle actions (placement, matching, modification, cancellation, settlement) to emit standardized events with metadata including transaction hash, block information, and event details.

Changes

Cohort / File(s) Summary
Order Action Event Recording
internal/migrations/032-order-book-actions.sql, internal/migrations/033-order-book-settlement.sql
Adds ob_record_order_event() calls throughout order lifecycle: direct matching emits direct_buy_fill/direct_sell_fill; mint/burn matching emits mint_fill/burn_fill; order placement records buy_placed/sell_placed/split_placed; cancellation records cancelled events; bid/ask modifications record bid_changed/ask_changed events. Settlement now records settled events for all positions with computed settlement prices.
Order Events Infrastructure
internal/migrations/044-order-book-events.sql
Creates new ob_order_events table with referential integrity to ob_queries and ob_participants; implements private ob_record_order_event() action for inserting events with auto-incremented IDs; adds public trim_order_events() action for retention management with configurable block preservation and deletion caps.
Order Events Testing
tests/streams/order_book/order_events_test.go
Comprehensive test suite validating event persistence across all order operations: covers placements, cancellations, direct/mint/burn fills with counterparty cross-references, modifications, and settlement events with price/outcome assertions.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • pr-time-tracker

Poem

🐰 Hops of joy through the event logs so bright,
Recording each order from morning to night,
Fills, placements, and cancels so neat,
With settlement tables, the feature's complete!
Order-book history, preserved in flight! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding order event recording functionality to support wallet history tracking across the order lifecycle.
Docstring Coverage ✅ Passed Docstring coverage is 93.75% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch walletHistorical

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
tests/streams/order_book/order_events_test.go (1)

37-43: Please add coverage for the remaining new event emitters.

This suite never exercises settle_market()/process_settlement(), match_mint(), match_burn(), change_bid(), or change_ask(). Those are all new emitters in this PR, and the settlement path in particular is easy to regress without a dedicated test.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/streams/order_book/order_events_test.go` around lines 37 - 43, The test
suite is missing coverage for the new event emitters; add tests that exercise
settle_market()/process_settlement(), match_mint(), match_burn(), change_bid(),
and change_ask() by creating new TestFunc entries (e.g.
testOrderEventSettleMarket(t), testOrderEventMatchMint(t),
testOrderEventMatchBurn(t), testOrderEventChangeBid(t),
testOrderEventChangeAsk(t)) following the existing test patterns used for
buy/sell/split/cancel/direct-fill: invoke the API/engine actions that trigger
each emitter, wait for the emitted event, and assert the expected event payloads
so the settlement and match paths are covered. Ensure the new test functions are
appended to the FunctionTests slice alongside
testOrderEventBuyPlaced/testOrderEventSellPlaced etc., and reuse existing
helpers for setup/teardown and event assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/migrations/033-order-book-settlement.sql`:
- Around line 294-301: The settled-event logic redeems positions for price >= 0
using a hardcoded 98¢ multiplier but still persists abs(raw_price) into
ob_order_events.price, causing winning holdings to be recorded as 0 or stale
limit prices; update the SELECT that builds settled events so that for the price
>= 0 branch you compute and persist the actual settlement/redemption price (the
post-fee per-share redemption used to compute pay) into the price column (and
use its absolute value where code expects abs(raw_price)), and ensure the
out/fee/pay expressions reference the same computed settlement_price
variable/expression (rather than raw_price) so ob_order_events.price reflects
the true settlement price for winning outcomes.
- Around line 290-301: The settlement-event SELECT currently only emits
winners/open buys; include losing/resting positions too by removing any
winner-only filter and ensuring the CASE expressions set pay, fee, and out
correctly for losers: keep the existing CASEs on price (use price<0 path to
produce pay as 0 or appropriate losing payout calculation), set fee to 0 for
losing positions, and set out to outcome (not $winning_outcome) for losers;
update the SELECT that produces pay, out, fee (fields named pay, out, fee) while
still returning participant_id, wallet_address, raw_amount and raw_price so
wallets for the losing side receive terminal settlement events. Ensure
references to price, amount, $winning_outcome, participant_id and wallet_address
are preserved when expanding the row set.

In `@internal/migrations/044-order-book-events.sql`:
- Around line 15-30: The ob_order_events table's foreign keys currently use ON
DELETE CASCADE which causes events to be removed when ob_queries or
ob_participants rows are deleted; change the FK constraints on query_id and
participant_id in ob_order_events to remove ON DELETE CASCADE (leave plain
FOREIGN KEY (...) REFERENCES ... or use ON DELETE NO ACTION/RESTRICT) so parent
cleanup cannot cascade-delete unsynced history (keep the ob_order_events table,
its columns, and the FK names but drop the cascading behavior relied upon by
trim_order_events()).

---

Nitpick comments:
In `@tests/streams/order_book/order_events_test.go`:
- Around line 37-43: The test suite is missing coverage for the new event
emitters; add tests that exercise settle_market()/process_settlement(),
match_mint(), match_burn(), change_bid(), and change_ask() by creating new
TestFunc entries (e.g. testOrderEventSettleMarket(t),
testOrderEventMatchMint(t), testOrderEventMatchBurn(t),
testOrderEventChangeBid(t), testOrderEventChangeAsk(t)) following the existing
test patterns used for buy/sell/split/cancel/direct-fill: invoke the API/engine
actions that trigger each emitter, wait for the emitted event, and assert the
expected event payloads so the settlement and match paths are covered. Ensure
the new test functions are appended to the FunctionTests slice alongside
testOrderEventBuyPlaced/testOrderEventSellPlaced etc., and reuse existing
helpers for setup/teardown and event assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56eda294-8c9e-4ebb-8a32-ab61504715ad

📥 Commits

Reviewing files that changed from the base of the PR and between 8a7235b and e666c2a.

📒 Files selected for processing (4)
  • internal/migrations/032-order-book-actions.sql
  • internal/migrations/033-order-book-settlement.sql
  • internal/migrations/044-order-book-events.sql
  • tests/streams/order_book/order_events_test.go

Comment thread internal/migrations/033-order-book-settlement.sql Outdated
Comment thread internal/migrations/033-order-book-settlement.sql Outdated
Comment thread internal/migrations/044-order-book-events.sql
@MicBun MicBun force-pushed the walletHistorical branch from 977b8b3 to ccbc5e0 Compare March 25, 2026 12:48
@MicBun MicBun force-pushed the walletHistorical branch from ccbc5e0 to 1f64117 Compare March 25, 2026 12:49
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@extensions/tn_digest/internal/engine_ops.go`:
- Around line 520-543: parseTrimResultFromTxLog currently ignores fmt.Sscanf's
return and silently returns zeroed TrimOrderEventsTxResult on parse failure;
change parseTrimResultFromTxLog to return (*TrimOrderEventsTxResult, error),
validate fmt.Sscanf by checking the scanned count and error (expecting 3
fields), and return a descriptive error if parsing fails (include the offending
line or full log). Update the caller that does result :=
parseTrimResultFromTxLog(txResult.Log) (and the subsequent e.logger.Info/return)
to handle the error path: log the parse failure and return the error instead of
treating a zeroed result as success (e.g., in BroadcastTrimOrderEvents or the
function invoking parseTrimResultFromTxLog).

In `@internal/migrations/033-order-book-settlement.sql`:
- Around line 285-309: The loop over ob_positions that emits settlement events
is non-deterministic because the SELECT used by the for $evt_row has no ORDER
BY; modify the SELECT in the for loop that reads from ob_positions (the query
feeding $evt_row in the settlement block) to include a deterministic ORDER BY
(e.g., ORDER BY participant_id, price, amount, outcome) so
ob_record_order_event($query_id, $evt_pid, 'settled', ...) is called in a
stable, repeatable sequence.

In `@internal/migrations/044-order-book-events.sql`:
- Around line 15-16: The ob_order_events.id must be monotonic across trims:
change the column from INT to BIGINT and back it with a dedicated sequence (use
a BIGSERIAL-like sequence owned by ob_order_events.id) so inserts use
nextval(...) instead of recomputing the next id from table contents; remove or
replace the logic that recomputes the next id from current rows (the code
referenced around the recompute block) and ensure the sequence's last_value is
advanced if you seed events so the sequence never hands out reused small values
after a trim.

In `@tests/streams/order_book/order_events_test.go`:
- Around line 693-706: The test collects settlement events from the slice
settled and currently only asserts foundWinnerAt98; update the assertions to
also assert that foundLoserAt0 is true so losing NO-position settlements are
verified. Specifically, in the test that declares vars foundWinnerAt98 and
foundLoserAt0 and iterates over settled, add a require.True(t, foundLoserAt0,
"should have losing settlement event with price=0") (or similar message)
alongside the existing require for foundWinnerAt98 so both winning (price==98)
and losing (price==0) settlement paths are enforced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d100e10c-c5fd-47e8-9595-bd7990e6deac

📥 Commits

Reviewing files that changed from the base of the PR and between e666c2a and 977b8b3.

📒 Files selected for processing (6)
  • extensions/tn_digest/internal/engine_ops.go
  • extensions/tn_digest/scheduler/constants.go
  • extensions/tn_digest/scheduler/scheduler.go
  • internal/migrations/033-order-book-settlement.sql
  • internal/migrations/044-order-book-events.sql
  • tests/streams/order_book/order_events_test.go
✅ Files skipped from review due to trivial changes (1)
  • extensions/tn_digest/scheduler/constants.go

Comment thread extensions/tn_digest/internal/engine_ops.go Outdated
Comment thread internal/migrations/033-order-book-settlement.sql
Comment thread internal/migrations/044-order-book-events.sql Outdated
Comment thread tests/streams/order_book/order_events_test.go Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (3)
internal/migrations/044-order-book-events.sql (1)

16-16: ⚠️ Potential issue | 🔴 Critical

Make ob_order_events.id monotonic across trims.

trim_order_events() can eventually empty this table, and Lines 50-63 then restart ids from 1 via MAX(id)+1. Any indexer cursor keyed by last-seen id will miss fresh events after the first full trim. INT also leaves limited headroom for a long-lived event stream. Please back id with a monotonic bigint sequence/identity instead of deriving it from current contents.

Also applies to: 50-63

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/migrations/044-order-book-events.sql` at line 16, The
ob_order_events table currently defines id as INT and the code in
trim_order_events() (and the insert path using MAX(id)+1) derives new ids from
table contents; change id to a monotonic BIGINT identity/sequence instead: alter
the table to use BIGINT for id and set a DEFAULT nextval(...) from a dedicated
sequence (or use GENERATED ALWAYS AS IDENTITY), update any INSERTs to rely on
the sequence (remove MAX(id)+1 usage), and ensure trim_order_events() resets or
advances the sequence safely after trimming (set the sequence's nextval to
greater than the current max id if needed) so ids remain monotonic across trims.
tests/streams/order_book/order_events_test.go (1)

693-706: ⚠️ Potential issue | 🟡 Minor

Assert the other settlement branches this fixture already creates.

This setup leaves both a losing position (price = 0) and an unmatched YES bid (price = 30) at settlement, but the test only enforces the winner-at-98 path. A regression in either non-winning branch would still pass.

🛠 Proposed fix
 		// Check that winning holdings have price=98 (redemption price)
 		var foundWinnerAt98 bool
 		var foundLoserAt0 bool
+		var foundOpenBuyAt30 bool
 		for _, evt := range settled {
 			if evt.Outcome == true && evt.Price == 98 {
 				foundWinnerAt98 = true
 			}
-			if evt.Price == 0 {
+			if evt.Outcome == false && evt.Price == 0 {
 				foundLoserAt0 = true
 			}
+			if evt.Outcome == true && evt.Price == 30 {
+				foundOpenBuyAt30 = true
+			}
 		}
 		require.True(t, foundWinnerAt98, "should have winning settlement event with price=98")
-		// Losers may or may not exist depending on position state after matching
-		_ = foundLoserAt0
+		require.True(t, foundLoserAt0, "should have losing settlement event with price=0")
+		require.True(t, foundOpenBuyAt30, "should have unmatched bid settlement event with price=30")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/streams/order_book/order_events_test.go` around lines 693 - 706, The
test currently only asserts a winning settlement event at price 98; update it to
also assert the other expected settlement branches by scanning the settled
events for a loser with Price == 0 and for an unmatched YES bid with Price == 30
(add a new boolean like foundUnmatchedYes30), set them true when encountered,
and add require.True checks for both foundLoserAt0 and foundUnmatchedYes30 so
the test fails if either the losing settlement or the unmatched YES bid branch
is missing; use the existing loop over settled and the same pattern used for
foundWinnerAt98 to locate these events.
internal/migrations/033-order-book-settlement.sql (1)

285-289: ⚠️ Potential issue | 🔴 Critical

Add a deterministic ordering to the settlement-event scan.

ob_record_order_event() assigns ids in loop order. Without an ORDER BY here, the same ob_positions set can produce different settled id sequences across executions, which is risky for consensus and for downstream sync that reads ob_order_events sequentially.

🛠 Proposed fix
     for $evt_row in
         SELECT pos.participant_id, pos.price, pos.amount, pos.outcome
         FROM ob_positions pos
         WHERE pos.query_id = $query_id
+        ORDER BY pos.participant_id ASC, pos.outcome ASC, pos.price ASC
     {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/migrations/033-order-book-settlement.sql` around lines 285 - 289,
The SELECT over ob_positions used to drive the settlement loop is unordered,
causing non-deterministic id assignment by ob_record_order_event(); fix by
adding a deterministic ORDER BY to that query (e.g., ORDER BY participant_id,
price, amount, outcome or by a stable primary key if present) so the loop
processes rows in a stable sequence and produced settled ids are reproducible
across runs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@internal/migrations/033-order-book-settlement.sql`:
- Around line 285-289: The SELECT over ob_positions used to drive the settlement
loop is unordered, causing non-deterministic id assignment by
ob_record_order_event(); fix by adding a deterministic ORDER BY to that query
(e.g., ORDER BY participant_id, price, amount, outcome or by a stable primary
key if present) so the loop processes rows in a stable sequence and produced
settled ids are reproducible across runs.

In `@internal/migrations/044-order-book-events.sql`:
- Line 16: The ob_order_events table currently defines id as INT and the code in
trim_order_events() (and the insert path using MAX(id)+1) derives new ids from
table contents; change id to a monotonic BIGINT identity/sequence instead: alter
the table to use BIGINT for id and set a DEFAULT nextval(...) from a dedicated
sequence (or use GENERATED ALWAYS AS IDENTITY), update any INSERTs to rely on
the sequence (remove MAX(id)+1 usage), and ensure trim_order_events() resets or
advances the sequence safely after trimming (set the sequence's nextval to
greater than the current max id if needed) so ids remain monotonic across trims.

In `@tests/streams/order_book/order_events_test.go`:
- Around line 693-706: The test currently only asserts a winning settlement
event at price 98; update it to also assert the other expected settlement
branches by scanning the settled events for a loser with Price == 0 and for an
unmatched YES bid with Price == 30 (add a new boolean like foundUnmatchedYes30),
set them true when encountered, and add require.True checks for both
foundLoserAt0 and foundUnmatchedYes30 so the test fails if either the losing
settlement or the unmatched YES bid branch is missing; use the existing loop
over settled and the same pattern used for foundWinnerAt98 to locate these
events.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d5c496e-348c-44d8-8b4c-66a48367c040

📥 Commits

Reviewing files that changed from the base of the PR and between 977b8b3 and 1f64117.

📒 Files selected for processing (4)
  • internal/migrations/032-order-book-actions.sql
  • internal/migrations/033-order-book-settlement.sql
  • internal/migrations/044-order-book-events.sql
  • tests/streams/order_book/order_events_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/migrations/032-order-book-actions.sql

@MicBun MicBun merged commit c771af1 into main Mar 25, 2026
6 checks passed
@MicBun MicBun deleted the walletHistorical branch March 25, 2026 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feat New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant