Skip to content

Make on-chain reconciliation correct and complete (#211) - #212

Merged
Soumen1080 merged 1 commit into
mainfrom
souri
Aug 31, 2026
Merged

Make on-chain reconciliation correct and complete (#211)#212
Soumen1080 merged 1 commit into
mainfrom
souri

Conversation

@Soumen1080

@Soumen1080 Soumen1080 commented Aug 31, 2026

Copy link
Copy Markdown
Owner

User description

close #211

Events are a lossy notification channel, not durable history. This makes the durable contract-state path a first-class source and teaches the code to tell "no payments" apart from "cannot know".

lib/stellar/events.ts

  • Exhaustive pagination: the cursor, not a short page, terminates the walk. Previously a page with fewer than limit records ended the loop even with a cursor outstanding, silently dropping records on an active trip.
  • Hitting MAX_PAGES now reports truncated instead of exiting silently.
  • Retention expiry is detected (isRetentionWindowError) and surfaced as retentionExpired rather than being swallowed into an empty result that looks authoritative.

lib/settlement/reconcile.ts

  • Documented state-vs-event authority rule: durable contract state wins on disagreement; an event-only payment is still accepted (simulation reads an older snapshot, and settlement is monotonic + idempotent).
  • On key collision the state record is kept as canonical.
  • Reports stateArchived, eventsRetentionExpired, and degraded so the UI can render "unknown" rather than "unpaid" when neither source can speak.

hooks/useContractEvents.ts

  • Contract state is re-read on first load, whenever events are pruned or truncated, and on a slow timer, so a long-lived session stays correct as it ages past the RPC retention window.
  • Exposes degraded / stateArchived.
  • Merge is explicitly idempotent and order-independent.

hooks/usePollBudget.ts (new)

  • Process-wide concurrency cap and minimum inter-poll gap, so polling cost does not grow with the number of open trips and the visibility-change refresh no longer produces a burst.

Tests cover retention expiry, pagination overflow and cursor edge cases, and event/state disagreement. Note: they were not executed here - this checkout has no installed node_modules and no dependencies were added.

@


CodeAnt-AI Description

Make on-chain payment reconciliation complete and distinguish unknown status

What Changed

  • Follows event pagination cursors until the stream ends, preventing payments from being missed on active trips.
  • Detects pruned event history and archived contract storage instead of treating unavailable data as “no payments.”
  • Combines contract state and event data, using durable contract state when both sources report the same payment while still accepting newly landed event payments.
  • Reports when settlement status is uncertain so the UI can show “unknown” rather than “unpaid.”
  • Refreshes durable contract state during long-lived sessions and limits polling bursts across open trips.
  • Adds coverage for retention expiry, pagination, source conflicts, archived state, and incomplete event results.

Impact

✅ Fewer missed on-chain payments
✅ Clearer unknown settlement status
✅ Lower polling load with multiple open trips

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Make on-chain reconciliation correct and complete (#211)

Events are a lossy notification channel, not durable history. This makes the
durable contract-state path a first-class source and teaches the code to tell
"no payments" apart from "cannot know".

lib/stellar/events.ts
- Exhaustive pagination: the cursor, not a short page, terminates the walk.
  Previously a page with fewer than `limit` records ended the loop even with a
  cursor outstanding, silently dropping records on an active trip.
- Hitting MAX_PAGES now reports `truncated` instead of exiting silently.
- Retention expiry is detected (`isRetentionWindowError`) and surfaced as
  `retentionExpired` rather than being swallowed into an empty result that
  looks authoritative.

lib/settlement/reconcile.ts
- Documented state-vs-event authority rule: durable contract state wins on
  disagreement; an event-only payment is still accepted (simulation reads an
  older snapshot, and settlement is monotonic + idempotent).
- On key collision the state record is kept as canonical.
- Reports `stateArchived`, `eventsRetentionExpired`, and `degraded` so the UI
  can render "unknown" rather than "unpaid" when neither source can speak.

hooks/useContractEvents.ts
- Contract state is re-read on first load, whenever events are pruned or
  truncated, and on a slow timer, so a long-lived session stays correct as it
  ages past the RPC retention window.
- Exposes `degraded` / `stateArchived`.
- Merge is explicitly idempotent and order-independent.

hooks/usePollBudget.ts (new)
- Process-wide concurrency cap and minimum inter-poll gap, so polling cost does
  not grow with the number of open trips and the visibility-change refresh no
  longer produces a burst.

Tests cover retention expiry, pagination overflow and cursor edge cases, and
event/state disagreement. Note: they were not executed here - this checkout has
no installed node_modules and no dependencies were added.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@
@codeant-ai

codeant-ai Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 CodeAnt AI — Review Status

Status Commit Started (UTC) Finished (UTC)
✅ Reviewed your PR 0aceaad Aug 31, 2026 · 11:09 11:18

@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stellar-star Error Error Aug 31, 2026 11:09am

@codeant-ai

codeant-ai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@Soumen1080
Soumen1080 merged commit 6e29354 into main Aug 31, 2026
2 of 4 checks passed
@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label Aug 31, 2026
// 2. Read live RPC event stream (real-time notifications)
// 2. Read the live RPC event stream — fresher, but lossy.
try {
const eventsResult = await fetchContractEvents(0, tripId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Passing 0 limits events to the latest 600 ledgers, so older trips can return an empty valid page and incorrectly avoid degraded status. [logic error]

Assessment: 🟠 Major · 🔁 Occurrence: Sometimes

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** lib/settlement/reconcile.ts
**Line:** 373:373
**Comment:**
	*Logic Error: Passing `0` limits events to the latest 600 ledgers, so older trips can return an empty valid page and incorrectly avoid `degraded` status.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

if (needsState) {
try {
const stateRes = await getContractPayments(tripId);
lastStateReadRef.current = Date.now();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Updating lastStateReadRef for unsuccessful reads makes later polls treat missing durable state as authoritative and clear degraded incorrectly. [incorrect condition logic]

Assessment: 🟠 Major · 🔁 Occurrence: Sometimes

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** hooks/useContractEvents.ts
**Line:** 115:115
**Comment:**
	*Incorrect Condition Logic: Updating `lastStateReadRef` for unsuccessful reads makes later polls treat missing durable state as authoritative and clear `degraded` incorrectly.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines 139 to 149
setEvents((prev) => {
const known = new Set(prev.map(buildPaymentEventKey));
const toAdd = allNewEvents.filter((e) => !known.has(buildPaymentEventKey(e)));
const seen = new Set<string>();
const toAdd: ContractPaymentEvent[] = [];
for (const e of allNewEvents) {
const key = buildPaymentEventKey(e);
if (known.has(key) || seen.has(key)) continue;
seen.add(key);
toAdd.push(e);
}
return toAdd.length > 0 ? [...prev, ...toAdd] : prev;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: State records are appended after events, but this merge skips existing keys, so an event record remains instead of being replaced by the canonical state record. [api mismatch]

Assessment: 🟠 Major · 🔁 Occurrence: Sometimes

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** hooks/useContractEvents.ts
**Line:** 139:149
**Comment:**
	*Api Mismatch: State records are appended after events, but this merge skips existing keys, so an event record remains instead of being replaced by the canonical state record.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

}, [tripId, fetch_]);

return { events, latestLedger, isLoading, error, refresh };
return { events, latestLedger, isLoading, error, degraded, stateArchived, refresh };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The new degraded and stateArchived values are returned but not passed to the page or settlement components, so unknown settlement status still appears as unpaid. [incomplete implementation]

Assessment: 🟠 Major · 🔁 Occurrence: Sometimes

Use CodeAnt Skill Fix in Cursor Fix in VSCode Claude

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** hooks/useContractEvents.ts
**Line:** 227:227
**Comment:**
	*Incomplete Implementation: The new `degraded` and `stateArchived` values are returned but not passed to the page or settlement components, so unknown settlement status still appears as unpaid.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai

codeant-ai Bot commented Aug 31, 2026

Copy link
Copy Markdown

CodeAnt Nitpicks

1 code suggestion

1. The retention matcher classifies broad errors such as “not found”, “outside”, and any -32600 response as retention expiry, hiding unrelated RPC failures.

Logic error · lib/stellar/events.ts:24-25

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make on-chain reconciliation correct and complete

1 participant