Skip to content

feat(earnings): count earnings from join date (verified_at floor) (#978)#984

Merged
biwasxyz merged 1 commit into
mainfrom
feat/earnings-join-date-gate
Jun 8, 2026
Merged

feat(earnings): count earnings from join date (verified_at floor) (#978)#984
biwasxyz merged 1 commit into
mainfrom
feat/earnings-join-date-gate

Conversation

@biwasxyz

@biwasxyz biwasxyz commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Scopes earnings to money earned as an agent, not lifetime personal on-chain history. Only inflows with block_time >= agents.verified_at (registration) count.

Why

Surfaced reviewing the metric semantics: of the three classifiers, inbox_message and bounty are already post-join (they match platform DB records that only exist after registration) — but agent_peer had no time bound. A personal transfer between two addresses years before either registered would today be classified agent_peer and counted as "earnings" — wrong, and trivially gameable (register two old addresses that historically moved money between them).

What

  • fetchAgentPage now returns verified_at parsed to unix seconds (verifiedAtSec); 0 = no floor (all-time fallback when verified_at is missing/unparseable).
  • indexAgent applies the floor in collect() (skips block_time < verifiedAtSec) and terminates backfill on the first pre-join page — so backfill stops at registration instead of walking to genesis. More correct and cheaper Hiro usage.

Reversibility

Flippable to all-time later via a re-backfill (reset earnings_index_state.backfill_complete/backfill_offset; the sweep re-walks history). And free to change right now — the indexer is dormant and no data has accumulated, so this is a no-cost decision until enabled.

Notes

Part of #978.

Only inflows with block_time >= the recipient agent's registration
(agents.verified_at) count as earnings. "Earnings" = earned as an agent, not
lifetime personal on-chain history.

- Closes the agent_peer scoping gap: a pre-platform transfer between two
  addresses that only LATER registered would otherwise be counted as earnings
  (and is trivially gameable). inbox/bounty were already post-join by
  construction (they match platform DB records).
- Bounds backfill: the sweep stops once it pages past registration instead of
  walking to genesis — cheaper Hiro usage.
- fetchAgentPage now returns verified_at parsed to unix seconds (0 = no floor /
  all-time fallback when missing or unparseable).
- indexAgent applies the floor in collect() and terminates backfill on the
  first pre-join page.

Flippable to all-time later via a re-backfill (reset earnings_index_state);
free to change now while the indexer is dormant.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
landing-page d848b05 Commit Preview URL

Branch Preview URL
Jun 08 2026, 08:53 AM

@arc0btc arc0btc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Scopes earnings to post-registration inflows — closes a real semantic gap where pre-platform agent_peer transfers between later-registered addresses would have inflated earnings. Clean implementation.

What works well:

  • The verifiedAtSec: 0 fallback correctly degrades to all-time for agents without verified_at — no floor applied, no data loss.
  • reachedPreJoin terminates backfill early rather than walking to genesis — correct and cheaper Hiro usage.
  • Field alignment verified: reachedPreJoin reads r.tx?.burn_block_time and extractInboundTransfers maps the same tx.burn_block_time to t.blockTime — the two checks are consistent.
  • Tests cover ISO parse, null, and unparseable cases. Good edge-case discipline.
  • Clean separation from #983 (Phase 3 API PR) — different files, no entanglement.

[nit] transfersFound semantic shift (lib/earnings/indexer.ts)
The original code did transfersFound += transfers.length (all extracted transfers). The new code only increments for accepted transfers, so transfersFound now means "earnings-eligible transfers counted" rather than "transfers found on-chain". If this counter feeds logs or metrics, the label might become misleading. Harmless for correctness — worth a rename (earningsRows or transfersCounted) if it surfaces in dashboards.

[nit] Epoch-0 corner case (lib/earnings/d1.ts)
Math.floor(Date.parse(r.verified_at) / 1000) || 0 — if verified_at is exactly "1970-01-01T00:00:00Z", Date.parse returns 0, and 0 || 0 = 0, treating a valid epoch-0 registration as "no floor". Irrelevant in practice, but clarifying with a comment or an explicit Number.isFinite(s) && s > 0 guard would make the intent unambiguous. Current behavior is fine.

Code quality notes:

  • Multi-line parameter comment on verifiedAtSec in indexAgent is verbose but the 0-means-all-time invariant is non-obvious enough to justify it.
  • reachedPreJoin as a named inner closure reads well — cleaner than inlining.

Operational note: We track Hiro API row-read costs and have seen unbounded backfill walks add up quickly (CF DO free tier is 5M rows/day). The early-termination via reachedPreJoin will meaningfully reduce costs once the indexer is enabled — good call.

@biwasxyz biwasxyz merged commit 211ba18 into main Jun 8, 2026
8 checks passed
@biwasxyz biwasxyz deleted the feat/earnings-join-date-gate branch June 8, 2026 09:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants