Skip to content

Feature/escrow event listener#264

Open
Danitello123 wants to merge 2 commits into
DelegoLabs:mainfrom
Danitello123:feature/escrow-event-listener
Open

Feature/escrow event listener#264
Danitello123 wants to merge 2 commits into
DelegoLabs:mainfrom
Danitello123:feature/escrow-event-listener

Conversation

@Danitello123

@Danitello123 Danitello123 commented Jun 28, 2026

Copy link
Copy Markdown

closes #56

Summary

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation

Test plan

  • pnpm typecheck
  • pnpm test
  • Manual testing (describe):

Checklist

  • Follows project code conventions
  • TODOs reference issues where applicable
  • No secrets or credentials committed

Summary by CodeRabbit

  • New Features

    • Added escrow event notifications, including updates for created, released, refunded, and disputed escrow activity.
    • Added database-backed tracking for transaction status in payments, improving reliability of transaction processing.
    • Enabled automatic startup of escrow event listening when the required configuration is present.
  • Bug Fixes

    • Improved duplicate notification handling to reduce repeated email and push alerts.
  • Documentation

    • Expanded setup docs with required environment variables for notifications and payments services.
  • Tests

    • Added coverage for escrow event handling and transaction ledger status updates.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Soroban escrow contract event polling listener to the notifications service that decodes on-chain events and dispatches idempotent email and push notifications. Simultaneously adds a PostgreSQL-backed transaction ledger to the payments service, including a Sequelize model, SQL migration, automatic migration runner, and ledger write functions.

Changes

Notifications: Escrow Event Listener

Layer / File(s) Summary
EscrowContractEvent model and polling loop
apps/backend/notifications/src/escrowListener.ts
Defines EscrowContractEvent interface, module lifecycle state, and startEscrowEventListener/stopEscrowEventListener which poll Soroban RPC every 5 seconds, parse and reclassify raw events, track last processed ledger in Redis, and deduplicate by txHash.
Idempotent email and push dispatch
apps/backend/notifications/src/escrowListener.ts
dispatchEscrowNotification resolves wallet address to userId, selects template/content per event type, and sends email via sendEmail and push to all Redis-stored subscriptions via sendPushNotification, each guarded by checkAndMarkDispatched.
Service wiring, tests, and docs
apps/backend/notifications/src/index.ts, apps/backend/notifications/src/escrowListener.test.ts, apps/backend/notifications/README.md
Entry point conditionally starts the listener from env vars; Vitest tests mock Redis/RPC/email/push and assert event decoding triggers idempotency and correct email template; README lists required env vars.

Payments: Soroban Transaction Ledger

Layer / File(s) Summary
DB connection, model, and migration
apps/backend/payments/src/db.ts, apps/backend/payments/src/models/SorobanTransactionLedger.ts, database/migrations/004_soroban_transaction_ledger.sql, scripts/setup/migrate.js, apps/backend/payments/package.json
Sequelize instance with pool config, SorobanTransactionLedger model with PENDING|CONFIRMED|FAILED status constraint, SQL migration creating the table and indexes, and an automatic migration runner for versioned SQL files.
logSubmission and updateLedgerStatus
apps/backend/payments/events/index.ts
logSubmission uses findOrCreate to write a PENDING row keyed by hash; updateLedgerStatus enforces valid status transitions and sets confirmedAt/errorDetails.
Unit tests and docs
tests/unit/src/payments-ledger.test.js, apps/backend/payments/README.md
Stubs findOrCreate/findByPk with an in-memory map to verify PENDING creation and CONFIRMED/FAILED transitions; README documents schema and env vars.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • #56 ([Notifications] Build Event Listener for On-Chain Escrow Events): This PR directly implements the EscrowContractEvent interface, startEscrowEventListener, deduplication by tx hash, idempotent dispatch, and the required tests described in that issue.
  • #52: The payments transaction ledger (logSubmission, updateLedgerStatus, SorobanTransactionLedger model, and tests) directly addresses the implementation scope described in this issue.

Possibly related PRs

  • DelegoLabs/Delego#144: The escrow listener's event types (escrow_created, escrow_released, escrow_refunded, escrow_disputed) and release_to_seller reclassification logic directly depend on the on-chain escrow contract event fields introduced in that PR.
  • DelegoLabs/Delego#229: The new dispatchEscrowNotification calls checkAndMarkDispatched with {userId, eventType} to enforce idempotent delivery, building directly on the Redis-backed idempotency helper introduced in that PR.

Poem

🐇 Hoppity hop through the blockchain stream,
Escrow events arrive — what a dream!
I stamp each ledger with PENDING delight,
Then CONFIRMED or FAILED by the end of the night.
No duplicate emails shall haunt this warren,
My Redis keys keep the dispatches foreign! ✉️

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The escrow listener is implemented, but the required txHash+eventIndex deduplication is not evident in the changes. Add deduplication keyed by transaction hash plus event index and ensure the listener/tests cover that path.
Out of Scope Changes check ⚠️ Warning Large payments database and ledger changes were added even though the linked issue scopes work to the notifications worker. Remove the payments ledger/migration changes from this PR or split them into a separate issue and PR.
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main change: adding an escrow event listener for notifications.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feature/escrow-event-listener

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/backend/notifications/src/escrowListener.ts`:
- Around line 193-224: The idempotency key is being marked too early in
escrowListener’s send flow, so a failed email or push delivery still gets
recorded as dispatched. Update the escrowListener logic around
checkAndMarkDispatched, sendEmail, and the push send path so the key is only
marked after a successful delivery, or is cleared/released inside the failure
catch when delivery fails. Apply the same fix consistently for both the email
and push branches, including the later push block referenced in the diff.
- Around line 47-67: The cursor logic in escrowListener is too coarse:
startLedger/lastProcessedKey only tracks the last ledger, so getEvents
pagination and per-event failures can skip same-ledger overflow permanently.
Update EscrowListener to use an event-granular checkpoint (such as the RPC
paging token or a ledger-plus-event-index cursor) instead of only
latestLedger/last_ledger, and advance that checkpoint only after each event is
successfully processed. Make the replay path resume from the last successfully
handled event in the processing loop, not from the next ledger.
- Around line 47-54: The cold-start ledger calculation in escrowListener’s
checkpoint logic can produce a non-positive startLedger when Redis has no saved
value and latestLedger is below 100. Update the start ledger initialization in
the escrowListener flow so it is clamped to a minimum valid ledger (for example,
never below 1) before calling getEvents, while preserving the existing Redis
checkpoint behavior for the lastProcessedKey/latestLedger path.

In `@apps/backend/payments/events/index.ts`:
- Around line 252-258: The status update logic in the payment event handler
leaves stale confirmation data behind when a row transitions to FAILED. Update
the status branch in the relevant ledger entry update flow so that the same path
that sets status and errorDetails also clears confirmedAt for any non-CONFIRMED
outcome, and keep the CONFIRMED branch setting confirmedAt and clearing
errorDetails as it does now. Use the existing ledgerEntry status assignment
block in the payments events index handler to make this change.
- Around line 213-219: The retry path in the existing ledger row update leaves
stale terminal data behind, so `ledgerEntry` can be `PENDING` while still
carrying `confirmedAt` or `errorDetails` from a prior `FAILED`/`CONFIRMED`
state. In the `if (!created)` block inside the transaction submission flow,
reset the terminal fields when reusing the row by clearing `confirmedAt` and
`errorDetails` along with setting `status` back to `PENDING`, then continue
updating the existing `ledgerEntry` before saving it.

In `@database/migrations/004_soroban_transaction_ledger.sql`:
- Around line 21-24: The inline rollback block in the migration file is being
executed as part of the normal apply path, so remove the down-migration
statements from this file. Update the 004_soroban_transaction_ledger migration
content so it only contains the forward schema changes for
soroban_transaction_ledger and its indexes, and keep any rollback logic out of
the SQL passed to migrate.js.

In `@tests/unit/src/payments-ledger.test.js`:
- Line 1: The payment ledger tests are order-dependent because the CONFIRMED and
FAILED cases rely on state seeded by the first case via mockLedgerEntries for
the same hash. Update the test setup in the payments-ledger suite so each case
seeds its own ledger row independently, likely in the individual test bodies or
a per-test setup helper used by the relevant describe block. Keep the tests
isolated by ensuring each case creates the needed mockLedgerEntries entry before
asserting on CONFIRMED or FAILED behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5bf133a0-efc2-4670-93a8-7e9383fef14a

📥 Commits

Reviewing files that changed from the base of the PR and between 65de858 and 2c96fd7.

📒 Files selected for processing (12)
  • apps/backend/notifications/README.md
  • apps/backend/notifications/src/escrowListener.test.ts
  • apps/backend/notifications/src/escrowListener.ts
  • apps/backend/notifications/src/index.ts
  • apps/backend/payments/README.md
  • apps/backend/payments/events/index.ts
  • apps/backend/payments/package.json
  • apps/backend/payments/src/db.ts
  • apps/backend/payments/src/models/SorobanTransactionLedger.ts
  • database/migrations/004_soroban_transaction_ledger.sql
  • scripts/setup/migrate.js
  • tests/unit/src/payments-ledger.test.js

Comment on lines +47 to +54
const lastProcessedKey = `escrow_listener:last_ledger:${contractId}`;
const lastProcessedStr = await redis.get(lastProcessedKey);

let startLedger = lastProcessedStr ? parseInt(lastProcessedStr, 10) + 1 : latestLedger - 100;

if (startLedger > latestLedger) {
startLedger = latestLedger;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clamp the cold-start ledger floor.

When Redis has no checkpoint and latestLedger < 100, this computes startLedger <= 0. The first getEvents call then polls an invalid ledger range on fresh/test networks.

Proposed fix
-      let startLedger = lastProcessedStr ? parseInt(lastProcessedStr, 10) + 1 : latestLedger - 100;
+      let startLedger = lastProcessedStr
+        ? parseInt(lastProcessedStr, 10) + 1
+        : Math.max(1, latestLedger - 100);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const lastProcessedKey = `escrow_listener:last_ledger:${contractId}`;
const lastProcessedStr = await redis.get(lastProcessedKey);
let startLedger = lastProcessedStr ? parseInt(lastProcessedStr, 10) + 1 : latestLedger - 100;
if (startLedger > latestLedger) {
startLedger = latestLedger;
}
const lastProcessedKey = `escrow_listener:last_ledger:${contractId}`;
const lastProcessedStr = await redis.get(lastProcessedKey);
let startLedger = lastProcessedStr
? parseInt(lastProcessedStr, 10) + 1
: Math.max(1, latestLedger - 100);
if (startLedger > latestLedger) {
startLedger = latestLedger;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/notifications/src/escrowListener.ts` around lines 47 - 54, The
cold-start ledger calculation in escrowListener’s checkpoint logic can produce a
non-positive startLedger when Redis has no saved value and latestLedger is below
100. Update the start ledger initialization in the escrowListener flow so it is
clamped to a minimum valid ledger (for example, never below 1) before calling
getEvents, while preserving the existing Redis checkpoint behavior for the
lastProcessedKey/latestLedger path.

Comment on lines +47 to +67
const lastProcessedKey = `escrow_listener:last_ledger:${contractId}`;
const lastProcessedStr = await redis.get(lastProcessedKey);

let startLedger = lastProcessedStr ? parseInt(lastProcessedStr, 10) + 1 : latestLedger - 100;

if (startLedger > latestLedger) {
startLedger = latestLedger;
}

if (startLedger <= latestLedger) {
const eventsResponse = await server.getEvents({
startLedger,
filters: [
{
type: "contract",
contractIds: [contractId],
topics: [["*"]], // Match any topics for this contract
},
],
limit: 1000,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Ledger-only checkpointing can drop events permanently.

getEvents is capped at 1000, but the persisted cursor is just latestLedger. That skips any same-ledger overflow and any event that throws during processing, because the next poll resumes at lastLedger + 1 instead of replaying from the last successfully handled event. This listener needs an event-granular cursor (RPC paging token or ledger + event index) and should only advance it after successful handling.

Also applies to: 69-124

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/notifications/src/escrowListener.ts` around lines 47 - 67, The
cursor logic in escrowListener is too coarse: startLedger/lastProcessedKey only
tracks the last ledger, so getEvents pagination and per-event failures can skip
same-ledger overflow permanently. Update EscrowListener to use an event-granular
checkpoint (such as the RPC paging token or a ledger-plus-event-index cursor)
instead of only latestLedger/last_ledger, and advance that checkpoint only after
each event is successfully processed. Make the replay path resume from the last
successfully handled event in the processing loop, not from the next ledger.

Comment on lines +193 to +224
const shouldSend = await checkAndMarkDispatched(redis, {
userId: target.userId,
channel: "email",
eventType: event.eventType,
eventId,
});

if (shouldSend) {
tasks.push(
sendEmail({
to: target.email,
subject,
templateName,
templateData: {
orderId: event.orderId,
amount: event.amountStroops,
},
}).catch((err) =>
log.error("Failed to send escrow email", { error: err, userId: target.userId })
)
);
}
}

// Push
if (target.pushEnabled) {
const shouldSend = await checkAndMarkDispatched(redis, {
userId: target.userId,
channel: "push",
eventType: event.eventType,
eventId,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Don't consume the idempotency key before a successful send.

Both channels call checkAndMarkDispatched before delivery, then catch and log send failures. A transient SendGrid/web-push failure or a bad stored subscription therefore becomes a permanent drop, because retries are now suppressed by Redis. Mark success after delivery, or clear the key on failure.

Also applies to: 226-247

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/notifications/src/escrowListener.ts` around lines 193 - 224, The
idempotency key is being marked too early in escrowListener’s send flow, so a
failed email or push delivery still gets recorded as dispatched. Update the
escrowListener logic around checkAndMarkDispatched, sendEmail, and the push send
path so the key is only marked after a successful delivery, or is
cleared/released inside the failure catch when delivery fails. Apply the same
fix consistently for both the email and push branches, including the later push
block referenced in the diff.

Comment on lines +213 to +219
if (!created) {
log.warn("Transaction submission ledger entry already exists, updating for retry", { hash });
ledgerEntry.status = "PENDING";
ledgerEntry.method = method;
if (orderId) ledgerEntry.orderId = orderId;
if (contractId) ledgerEntry.contractId = contractId;
await ledgerEntry.save();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reset terminal fields when reusing an existing ledger row.

If this hash was previously FAILED or CONFIRMED, this retry path only flips status back to PENDING. The old confirmedAt and errorDetails remain, so one row can read as pending and already settled at the same time.

🛠️ Proposed fix
     if (!created) {
       log.warn("Transaction submission ledger entry already exists, updating for retry", { hash });
       ledgerEntry.status = "PENDING";
       ledgerEntry.method = method;
-      if (orderId) ledgerEntry.orderId = orderId;
-      if (contractId) ledgerEntry.contractId = contractId;
+      ledgerEntry.confirmedAt = null;
+      ledgerEntry.errorDetails = null;
+      ledgerEntry.submittedAt = new Date();
+      if (orderId !== undefined) ledgerEntry.orderId = orderId;
+      if (contractId !== undefined) ledgerEntry.contractId = contractId;
       await ledgerEntry.save();
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!created) {
log.warn("Transaction submission ledger entry already exists, updating for retry", { hash });
ledgerEntry.status = "PENDING";
ledgerEntry.method = method;
if (orderId) ledgerEntry.orderId = orderId;
if (contractId) ledgerEntry.contractId = contractId;
await ledgerEntry.save();
if (!created) {
log.warn("Transaction submission ledger entry already exists, updating for retry", { hash });
ledgerEntry.status = "PENDING";
ledgerEntry.method = method;
ledgerEntry.confirmedAt = null;
ledgerEntry.errorDetails = null;
ledgerEntry.submittedAt = new Date();
if (orderId !== undefined) ledgerEntry.orderId = orderId;
if (contractId !== undefined) ledgerEntry.contractId = contractId;
await ledgerEntry.save();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/payments/events/index.ts` around lines 213 - 219, The retry path
in the existing ledger row update leaves stale terminal data behind, so
`ledgerEntry` can be `PENDING` while still carrying `confirmedAt` or
`errorDetails` from a prior `FAILED`/`CONFIRMED` state. In the `if (!created)`
block inside the transaction submission flow, reset the terminal fields when
reusing the row by clearing `confirmedAt` and `errorDetails` along with setting
`status` back to `PENDING`, then continue updating the existing `ledgerEntry`
before saving it.

Comment on lines +252 to +258
ledgerEntry.status = status;
if (status === "CONFIRMED") {
ledgerEntry.confirmedAt = new Date();
ledgerEntry.errorDetails = null;
} else {
ledgerEntry.errorDetails = error || "Transaction failed";
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clear confirmedAt when marking a row as failed.

A row that was previously confirmed keeps its old confirmation timestamp in the FAILED branch. That leaves the persisted state contradictory.

🛠️ Proposed fix
     if (status === "CONFIRMED") {
       ledgerEntry.confirmedAt = new Date();
       ledgerEntry.errorDetails = null;
     } else {
+      ledgerEntry.confirmedAt = null;
       ledgerEntry.errorDetails = error || "Transaction failed";
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ledgerEntry.status = status;
if (status === "CONFIRMED") {
ledgerEntry.confirmedAt = new Date();
ledgerEntry.errorDetails = null;
} else {
ledgerEntry.errorDetails = error || "Transaction failed";
}
ledgerEntry.status = status;
if (status === "CONFIRMED") {
ledgerEntry.confirmedAt = new Date();
ledgerEntry.errorDetails = null;
} else {
ledgerEntry.confirmedAt = null;
ledgerEntry.errorDetails = error || "Transaction failed";
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/payments/events/index.ts` around lines 252 - 258, The status
update logic in the payment event handler leaves stale confirmation data behind
when a row transitions to FAILED. Update the status branch in the relevant
ledger entry update flow so that the same path that sets status and errorDetails
also clears confirmedAt for any non-CONFIRMED outcome, and keep the CONFIRMED
branch setting confirmedAt and clearing errorDetails as it does now. Use the
existing ledgerEntry status assignment block in the payments events index
handler to make this change.

Comment on lines +21 to +24
-- Down migration
DROP INDEX IF EXISTS idx_soroban_tx_ledger_order_id;
DROP INDEX IF EXISTS idx_soroban_tx_ledger_status;
DROP TABLE IF EXISTS soroban_transaction_ledger;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Remove the inline rollback block from this migration.

scripts/setup/migrate.js executes the full file with one client.query(migrationSql) call, so these DROP ... statements run during normal migration application. That leaves soroban_transaction_ledger gone immediately after creation.

🛠️ Proposed fix
- -- Down migration
- DROP INDEX IF EXISTS idx_soroban_tx_ledger_order_id;
- DROP INDEX IF EXISTS idx_soroban_tx_ledger_status;
- DROP TABLE IF EXISTS soroban_transaction_ledger;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
-- Down migration
DROP INDEX IF EXISTS idx_soroban_tx_ledger_order_id;
DROP INDEX IF EXISTS idx_soroban_tx_ledger_status;
DROP TABLE IF EXISTS soroban_transaction_ledger;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@database/migrations/004_soroban_transaction_ledger.sql` around lines 21 - 24,
The inline rollback block in the migration file is being executed as part of the
normal apply path, so remove the down-migration statements from this file.
Update the 004_soroban_transaction_ledger migration content so it only contains
the forward schema changes for soroban_transaction_ledger and its indexes, and
keep any rollback logic out of the SQL passed to migrate.js.

@@ -0,0 +1,75 @@
import { describe, it, before, after } from "node:test";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make each test case seed its own ledger row.

The CONFIRMED and FAILED cases depend on the first test populating mockLedgerEntries for the same hash. That makes this suite order-dependent and brittle when cases are run or debugged in isolation.

🛠️ Proposed fix
-import { describe, it, before, after } from "node:test";
+import { describe, it, before, after, beforeEach } from "node:test";
@@
   before(() => {
@@
   });
+
+  beforeEach(() => {
+    mockLedgerEntries = {};
+  });
@@
   it("should update ledger status to CONFIRMED", async () => {
     const hash = "0000000000000000000000000000000000000000000000000000000000000001";
-    
+    await logSubmission(hash, "create_escrow");
     const entry = await updateLedgerStatus(hash, "CONFIRMED");
@@
   it("should update ledger status to FAILED with error details", async () => {
     const hash = "0000000000000000000000000000000000000000000000000000000000000001";
-    
+    await logSubmission(hash, "create_escrow");
     const entry = await updateLedgerStatus(hash, "FAILED", "Simulation failed");

Also applies to: 45-74

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/src/payments-ledger.test.js` at line 1, The payment ledger tests
are order-dependent because the CONFIRMED and FAILED cases rely on state seeded
by the first case via mockLedgerEntries for the same hash. Update the test setup
in the payments-ledger suite so each case seeds its own ledger row
independently, likely in the individual test bodies or a per-test setup helper
used by the relevant describe block. Keep the tests isolated by ensuring each
case creates the needed mockLedgerEntries entry before asserting on CONFIRMED or
FAILED behavior.

@ScriptedBro

Copy link
Copy Markdown
Contributor

Resolve the Conflicts

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.

[Notifications] Build Event Listener for On-Chain Escrow Events

2 participants