Indexer: treat TimeCurve buyFor as Buy; enrich with ETH/USDM pay metadata #67

Closed
opened 2026-04-26 07:51:39 +00:00 by PlasticDigits · 15 comments
PlasticDigits commented 2026-04-26 07:51:39 +00:00 (Migrated from gitlab.com)

Context

Issue #65 added TimeCurve.buyFor (companion TimeCurveBuyRouter) so CL8Y can be paid from the router while the buyer receives the same Buy event and CHARM/WarBow accounting as a normal buy. Optional TimeCurveBuyRouter.BuyViaKumbaya logs pay kind and amounts for observability.

Goals

  1. Parity with normal buys — Index buyFor-driven purchases so they appear in the same pipelines as today’s Buy events (same TimeCurve Buy log shape: buyer = participant, charmWad, amount, fees, timer fields, etc.). Do not drop or mis-attribute buys because msg.sender on the sale contract was the router.

  2. Enrichment for multi-asset entry — Persist additional fields (or a sidecar row / JSON column) when the tx is attributable to TimeCurveBuyRouter.buyViaKumbaya (decode BuyViaKumbaya + same-tx Buy correlation by tx hash / log index):

    • Pay asset class: ETH vs USDM (stable) vs CL8Y-only (unchanged).
    • Where useful and decodable: gross CL8Y (already on Buy as amount), optional implied input from BuyViaKumbaya / swap logs if stable for analytics later.
  3. Schema / API — Extend internal models and any public API that exposes buys so downstream surfaces can optionally show “paid via router (ETH/USDM)” without breaking existing CL8Y-only consumers (defaults / nulls for legacy rows).

Acceptance criteria

  • buyFor txs produce the same canonical buy records as buy (correct buyer, amount, CHARM, indexer invariants).
  • When BuyViaKumbaya is present in the same tx as Buy, store ETH vs USDM (and any agreed numeric fields) for later UX/analytics.
  • Tests: decoder round-trip and/or integration test with fixture logs (docs/testing/invariants-and-business-logic.md test map).
  • Document new fields in indexer docs / changelog as appropriate.

References

  • contracts/src/TimeCurve.sol — buyFor, Buy event
  • contracts/src/TimeCurveBuyRouter.sol — BuyViaKumbaya
  • indexer/ decoders and persist layer for TimeCurve
  • Related frontend issue: #66 — single-tx UX
## Context [Issue #65](https://gitlab.com/PlasticDigits/yieldomega/-/issues/65) added `TimeCurve.buyFor` (companion `TimeCurveBuyRouter`) so CL8Y can be paid from the router while the **buyer** receives the same `Buy` event and CHARM/WarBow accounting as a normal `buy`. Optional `TimeCurveBuyRouter.BuyViaKumbaya` logs pay kind and amounts for observability. ## Goals 1. **Parity with normal buys** — Index `buyFor`-driven purchases so they appear in the same pipelines as today’s `Buy` events (same `TimeCurve` `Buy` log shape: `buyer` = participant, `charmWad`, `amount`, fees, timer fields, etc.). Do not drop or mis-attribute buys because `msg.sender` on the sale contract was the router. 2. **Enrichment for multi-asset entry** — Persist **additional** fields (or a sidecar row / JSON column) when the tx is attributable to `TimeCurveBuyRouter.buyViaKumbaya` (decode `BuyViaKumbaya` + same-tx `Buy` correlation by tx hash / log index): - Pay asset class: **ETH** vs **USDM** (stable) vs **CL8Y-only** (unchanged). - Where useful and decodable: **gross CL8Y** (already on `Buy` as `amount`), optional **implied input** from `BuyViaKumbaya` / swap logs if stable for analytics later. 3. **Schema / API** — Extend internal models and any public API that exposes buys so **downstream surfaces can optionally show** “paid via router (ETH/USDM)” without breaking existing CL8Y-only consumers (defaults / nulls for legacy rows). ## Acceptance criteria - [ ] `buyFor` txs produce the same **canonical buy** records as `buy` (correct buyer, `amount`, CHARM, indexer invariants). - [ ] When `BuyViaKumbaya` is present in the same tx as `Buy`, store **ETH vs USDM** (and any agreed numeric fields) for later UX/analytics. - [ ] Tests: decoder round-trip and/or integration test with fixture logs ([`docs/testing/invariants-and-business-logic.md`](docs/testing/invariants-and-business-logic.md) test map). - [ ] Document new fields in indexer docs / changelog as appropriate. ## References - `contracts/src/TimeCurve.sol` — `buyFor`, `Buy` event - `contracts/src/TimeCurveBuyRouter.sol` — `BuyViaKumbaya` - `indexer/` decoders and persist layer for TimeCurve - Related frontend issue: [#66](https://gitlab.com/PlasticDigits/yieldomega/-/issues/66) — single-tx UX
PlasticDigits commented 2026-04-26 07:51:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #66

mentioned in issue #66
PlasticDigits commented 2026-04-26 07:51:48 +00:00 (Migrated from gitlab.com)

Companion: frontend single-tx buyViaKumbaya wiring — https://gitlab.com/PlasticDigits/yieldomega/-/issues/66

Companion: frontend single-tx `buyViaKumbaya` wiring — https://gitlab.com/PlasticDigits/yieldomega/-/issues/66
PlasticDigits commented 2026-04-26 07:51:50 +00:00 (Migrated from gitlab.com)

Cross-link: Replace the placeholder “frontend issue” in the description with issue #66.

**Cross-link:** Replace the placeholder “frontend issue” in the description with issue [#66](https://gitlab.com/PlasticDigits/yieldomega/-/issues/66).
PlasticDigits commented 2026-04-26 08:15:54 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-04-26 08:17:01 +00:00 (Migrated from gitlab.com)

mentioned in commit 5eaf345725

mentioned in commit 5eaf34572578b70d7ab35624f8070ca3eb27a400
PlasticDigits commented 2026-04-26 08:17:01 +00:00 (Migrated from gitlab.com)

mentioned in commit 2a89c4941e

mentioned in commit 2a89c4941eda9f7f5c589e2d8786d0005bbbd050
PlasticDigits commented 2026-04-26 08:17:34 +00:00 (Migrated from gitlab.com)

@brouie Please verify the indexer fix for #67 (merged to main in 5eaf345).

What changed

  • BuyViaKumbaya is decoded and stored in idx_timecurve_buy_router_kumbaya (idempotent on tx_hash, log_index).
  • ADDRESS_REGISTRY JSON may include optional TimeCurveBuyRouter so eth_getLogs ingests router events (empty string = omit from filter).
  • GET /v1/timecurve/buys (x-schema-version 1.8.0) left-joins matching rows and adds optional fields when the same tx has a correlated sidecar: entry_pay_asset (eth | stable) and router_attested_gross_cl8y (from the router event). Legacy / CL8Y-only buys omit these keys.
  • Reorg rollback deletes the new table. Integration + decoder round-trip tests cover the new path.
  • Docs: docs/integrations/kumbaya.md, docs/indexer/design.md, docs/testing/invariants-and-business-logic.md, skills/README.md, .cursor/skills/yieldomega-guardrails/SKILL.md. Issue description updated to link #66 for the frontend companion.

Checklist for you

  • Run migrations on a staging DB and confirm idx_timecurve_buy_router_kumbaya exists.
  • Set TimeCurveBuyRouter in ADDRESS_REGISTRY_PATH to the deployed router; restart ingestion; confirm router logs appear in the table after a buyViaKumbaya tx.
  • Confirm GET /v1/timecurve/buys returns entry_pay_asset / router_attested_gross_cl8y for those txs and omits them for ordinary buy txs.
  • Confirm buyer on buy rows matches the participant (unchanged invariant — sourced from TimeCurve Buy).
  • CI / cd indexer && cargo test green with YIELDOMEGA_PG_TEST_URL where you use Postgres integration.

Leaving the issue open for your sign-off.

@brouie Please verify the indexer fix for **#67** (merged to `main` in 5eaf345). ## What changed - **`BuyViaKumbaya`** is decoded and stored in **`idx_timecurve_buy_router_kumbaya`** (idempotent on `tx_hash, log_index`). - **`ADDRESS_REGISTRY`** JSON may include optional **`TimeCurveBuyRouter`** so **`eth_getLogs`** ingests router events (empty string = omit from filter). - **`GET /v1/timecurve/buys`** (`x-schema-version` **1.8.0**) left-joins matching rows and adds optional fields when the same tx has a correlated sidecar: **`entry_pay_asset`** (`eth` \| `stable`) and **`router_attested_gross_cl8y`** (from the router event). Legacy / CL8Y-only buys omit these keys. - **Reorg rollback** deletes the new table. **Integration + decoder round-trip** tests cover the new path. - Docs: **`docs/integrations/kumbaya.md`**, **`docs/indexer/design.md`**, **`docs/testing/invariants-and-business-logic.md`**, **`skills/README.md`**, **`.cursor/skills/yieldomega-guardrails/SKILL.md`**. Issue description updated to link **#66** for the frontend companion. ## Checklist for you - [ ] Run migrations on a staging DB and confirm **`idx_timecurve_buy_router_kumbaya`** exists. - [ ] Set **`TimeCurveBuyRouter`** in **`ADDRESS_REGISTRY_PATH`** to the deployed router; restart ingestion; confirm router logs appear in the table after a **`buyViaKumbaya`** tx. - [ ] Confirm **`GET /v1/timecurve/buys`** returns **`entry_pay_asset`** / **`router_attested_gross_cl8y`** for those txs and omits them for ordinary **`buy`** txs. - [ ] Confirm **`buyer`** on buy rows matches the participant (unchanged invariant — sourced from **`TimeCurve` `Buy`**). - [ ] CI / `cd indexer && cargo test` green with **`YIELDOMEGA_PG_TEST_URL`** where you use Postgres integration. Leaving the issue **open** for your sign-off.
PlasticDigits commented 2026-04-26 08:23:08 +00:00 (Migrated from gitlab.com)

mentioned in commit 3d45e3b90d

mentioned in commit 3d45e3b90dcf6f9e75b4ec3f17f633c0e2c9b3f0
Brouie commented 2026-04-27 02:46:33 +00:00 (Migrated from gitlab.com)

mentioned in issue #72

mentioned in issue #72
Brouie commented 2026-04-27 02:55:13 +00:00 (Migrated from gitlab.com)

@PlasticDigits source + Postgres-roundtrip verification on 5eaf345.

Source review

Decoder (src/decoder.rs):

  • L141: BuyViaKumbaya event ABI
  • L278: DecodedEvent::TimeCurveBuyRouterBuyViaKumbaya enum variant
  • L705-708: signature_hash dispatch and decode_log
  • L922: roundtrip test (round-trip AC item)

Persist (src/persist.rs L125-133):

  • INSERT INTO idx_timecurve_buy_router_kumbaya
  • ON CONFLICT idempotent on (tx_hash, log_index) PK

Reorg rollback (src/reorg.rs L119):

  • Table listed in delete chain

Config (src/config.rs L23-25):

  • timecurve_buy_router optional, empty string omits from filter

API (src/api.rs):

  • L22 SCHEMA_VERSION = '1.8.0'
  • L50 kumbaya_entry_pay_asset() helper maps pay_kind smallint to 'eth' / 'stable'
  • L111 header set to x-schema-version
  • L207-210 entry_pay_asset + router_attested_gross_cl8y as Option (omitted from JSON when null)
  • L254 SELECT pulls gross_cl8y::text AS router_attested_gross_cl8y from join
  • L314-318 row mapping into response DTO

Migration

Up SQL (migrations/20260426120000_timecurve_buy_router_kumbaya.up.sql):

  • CREATE TABLE IF NOT EXISTS idx_timecurve_buy_router_kumbaya
  • block_number, block_hash, tx_hash, log_index, contract_address, buyer (TEXT), charm_wad NUMERIC(78,0), gross_cl8y NUMERIC(78,0), pay_kind SMALLINT
  • PK (tx_hash, log_index), INDEX (tx_hash, buyer, charm_wad)

Down SQL: clean DROP TABLE IF EXISTS

Applied to real yieldomega_indexer DB on yieldomega-pg, table structure inspected via psql \d:

  • All columns + types match SQL spec
  • idx_timecurve_buy_router_kumbaya_pkey + idx_tcb_kumbaya_lookup both present

Tests

cd indexer && cargo test:

  • Unit: 15/15 passing (lib + binary)
  • Integration: postgres_stage2_persist_all_events_and_rollback_after 1/1 passing against real Postgres at YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer
  • The integration test exercises BuyViaKumbaya insert at tests/integration_stage2.rs L362, count assertion at L630/726/861, correlation walk at L319, and the reorg-rollback delete path

Deferred until #72 lands (not closeable today)

  • Live router log ingestion — TimeCurveBuyRouter not deployable on Anvil because TimeCurve.sol exceeds EIP-170 24KB cap (28,454 bytes), filed as #72 LAUNCH-BLOCKER this morning
  • End-to-end buyViaKumbaya tx producing eth/stable rows + correlated buy
  • API response on real data via GET /v1/timecurve/buys with x-schema-version: 1.8.0 header

Source layer is solid. Will close once #72 unblocks the deploy and I can ingest a real router event end-to-end.

Issue stays open.

@PlasticDigits source + Postgres-roundtrip verification on 5eaf345. ## Source review Decoder (src/decoder.rs): - L141: BuyViaKumbaya event ABI - L278: DecodedEvent::TimeCurveBuyRouterBuyViaKumbaya enum variant - L705-708: signature_hash dispatch and decode_log - L922: roundtrip test (round-trip AC item) Persist (src/persist.rs L125-133): - INSERT INTO idx_timecurve_buy_router_kumbaya - ON CONFLICT idempotent on (tx_hash, log_index) PK Reorg rollback (src/reorg.rs L119): - Table listed in delete chain Config (src/config.rs L23-25): - timecurve_buy_router optional, empty string omits from filter API (src/api.rs): - L22 SCHEMA_VERSION = '1.8.0' - L50 kumbaya_entry_pay_asset() helper maps pay_kind smallint to 'eth' / 'stable' - L111 header set to x-schema-version - L207-210 entry_pay_asset + router_attested_gross_cl8y as Option<String> (omitted from JSON when null) - L254 SELECT pulls gross_cl8y::text AS router_attested_gross_cl8y from join - L314-318 row mapping into response DTO ## Migration Up SQL (migrations/20260426120000_timecurve_buy_router_kumbaya.up.sql): - CREATE TABLE IF NOT EXISTS idx_timecurve_buy_router_kumbaya - block_number, block_hash, tx_hash, log_index, contract_address, buyer (TEXT), charm_wad NUMERIC(78,0), gross_cl8y NUMERIC(78,0), pay_kind SMALLINT - PK (tx_hash, log_index), INDEX (tx_hash, buyer, charm_wad) Down SQL: clean DROP TABLE IF EXISTS Applied to real yieldomega_indexer DB on yieldomega-pg, table structure inspected via psql \d: - All columns + types match SQL spec - idx_timecurve_buy_router_kumbaya_pkey + idx_tcb_kumbaya_lookup both present ## Tests cd indexer && cargo test: - Unit: 15/15 passing (lib + binary) - Integration: postgres_stage2_persist_all_events_and_rollback_after 1/1 passing against real Postgres at YIELDOMEGA_PG_TEST_URL=postgres://yieldomega:password@127.0.0.1:5433/yieldomega_indexer - The integration test exercises BuyViaKumbaya insert at tests/integration_stage2.rs L362, count assertion at L630/726/861, correlation walk at L319, and the reorg-rollback delete path ## Deferred until #72 lands (not closeable today) - Live router log ingestion — TimeCurveBuyRouter not deployable on Anvil because TimeCurve.sol exceeds EIP-170 24KB cap (28,454 bytes), filed as #72 LAUNCH-BLOCKER this morning - End-to-end buyViaKumbaya tx producing eth/stable rows + correlated buy - API response on real data via GET /v1/timecurve/buys with x-schema-version: 1.8.0 header Source layer is solid. Will close once #72 unblocks the deploy and I can ingest a real router event end-to-end. Issue stays open.
Brouie commented 2026-04-27 04:03:41 +00:00 (Migrated from gitlab.com)

mentioned in issue #75

mentioned in issue #75
Brouie commented 2026-04-27 04:04:03 +00:00 (Migrated from gitlab.com)

@PlasticDigits per today's process update (file follow-ups, close originals): live ingestion items moved to #75 tracker. Source + real-Postgres roundtrip verification (see prior note on this issue) stands.

Closing #67. Live walkthrough proceeds via #75 once a buyViaKumbaya tx is captured (likely via #74 browser walkthrough).

@PlasticDigits per today's process update (file follow-ups, close originals): live ingestion items moved to #75 tracker. Source + real-Postgres roundtrip verification (see prior note on this issue) stands. Closing #67. Live walkthrough proceeds via #75 once a buyViaKumbaya tx is captured (likely via #74 browser walkthrough).
Brouie (Migrated from gitlab.com) closed this issue 2026-04-27 04:04:04 +00:00
PlasticDigits commented 2026-04-27 07:24:01 +00:00 (Migrated from gitlab.com)

mentioned in issue #83

mentioned in issue #83
PlasticDigits commented 2026-04-28 03:21:40 +00:00 (Migrated from gitlab.com)

mentioned in issue #84

mentioned in issue #84
PlasticDigits commented 2026-05-05 08:35:15 +00:00 (Migrated from gitlab.com)

mentioned in issue #156

mentioned in issue #156
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#67
No description provided.