Skip to content

feat: introduce Bill of Exchange (eBOE) status lifecycle#151

Open
manishdex25 wants to merge 10 commits into
mainfrom
feature/eboe-bill-of-exchange
Open

feat: introduce Bill of Exchange (eBOE) status lifecycle#151
manishdex25 wants to merge 10 commits into
mainfrom
feature/eboe-bill-of-exchange

Conversation

@manishdex25

@manishdex25 manishdex25 commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Summary

What is the background of this pull request?

Changes

  • What are the changes made in this pull request?
  • Change this and that, etc...

Issues

What are the related issues or stories?

Summary by CodeRabbit

  • New Features

    • Added beta Bill of Exchange status support for Token Registry V5.
    • Added lifecycle actions: accept, reject, and discharge, with role and transition validation.
    • Added status lookup and public status types for Issued, Accepted, Rejected, and Discharged.
    • Added status events to endorsement-chain results, including decoded remarks.
    • Added documentation covering lifecycle flows, compatibility, and limitations.
  • Bug Fixes

    • Improved compatibility when identifying legacy V5 title escrows.

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@manishdex25, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d08e365e-fd8b-4f2b-99e1-54a5fac4c097

📥 Commits

Reviewing files that changed from the base of the PR and between 0f5df69 and c4ba77d.

📒 Files selected for processing (2)
  • src/__tests__/core/endorsement-chain-version-detection.test.ts
  • src/core/endorsement-chain/useEndorsementChain.ts
📝 Walkthrough

Walkthrough

Added Token Registry V5 Bill of Exchange status support with getStatus, accept, reject, and discharge, lifecycle event mapping, legacy V5 escrow detection, public exports, documentation, and Ethers v5/v6 test coverage.

Changes

Status lifecycle

Layer / File(s) Summary
Public status contract and exports
src/status/types.ts, src/status/index.ts, src/index.ts
Defines lifecycle statuses, shared options, status labels, and package-level exports.
Status reads and transitions
src/status/*.ts
Implements V5-gated status reads and shared transition execution with remark encryption, Ethers v5/v6 static pre-checks, and transaction submission.
Status event discovery and mapping
src/core/endorsement-chain/*, src/__tests__/core/endorsement-chain-status-events.test.ts, src/__tests__/fixtures/endorsement-chain.ts
Adds accepted, rejected, and discharged events to V5 transfer discovery, mapping, classification, and tests.
Legacy V5 escrow resolution
src/core/endorsement-chain/useEndorsementChain.ts, src/core/documentBuilder.ts, src/__tests__/core/endorsement-chain-legacy-v5.test.ts
Adds prevBeneficiary() fallback detection when V5 interface checks fail and updates the related contract connection typing.
Cross-version unit test harness
src/__tests__/status/fixtures.ts, src/__tests__/status/status.test.ts, src/__tests__/token-registry-functions/fixtures.ts
Adds shared provider, wallet, escrow-state, contract, and transition mocks with status helper tests across Ethers v5 and v6.
Lifecycle scenarios and documentation
README.md, src/__tests__/fixtures/boe-raw-file.json, src/__tests__/status/statusE2eDocument.test.ts
Documents the beta eBOE lifecycle and validates signing, acceptance, rejection, discharge, financing, reissue, and transfer compatibility scenarios.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Document
  participant StatusHelper
  participant Provider
  participant TitleEscrow
  Document->>StatusHelper: call accept, reject, discharge, or getStatus
  StatusHelper->>Provider: resolve TitleEscrow and detect V5
  StatusHelper->>TitleEscrow: read status or perform static pre-check
  StatusHelper->>TitleEscrow: submit lifecycle transaction
  TitleEscrow-->>Document: return status or transaction result
Loading

Suggested labels: released

Suggested reviewers: rongquan1, nghaninn

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is only the template placeholders and lacks the required background, changes, and issues details. Fill in the Summary, Changes, and Issues sections with concrete details about the PR background, implementation, and related tickets.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding the eBOE status lifecycle.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/eboe-bill-of-exchange

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.

@manishdex25 manishdex25 changed the base branch from beta-boe to main July 12, 2026 18:20
@manishdex25 manishdex25 changed the base branch from main to beta-boe July 12, 2026 18:20
@manishdex25 manishdex25 changed the base branch from beta-boe to beta July 12, 2026 18:26
@manishdex25 manishdex25 changed the base branch from beta to main July 12, 2026 18:26
@manishdex25 manishdex25 changed the title Feature/eboe bill of exchange feat: introduce Bill of Exchange (eBOE) status lifecycle Jul 12, 2026
@manishdex25 manishdex25 changed the title feat: introduce Bill of Exchange (eBOE) status lifecycle feat: introduce Bill of Exchange (eBOE) status lifecycle. Jul 12, 2026
@manishdex25 manishdex25 changed the title feat: introduce Bill of Exchange (eBOE) status lifecycle. feat: introduce Bill of Exchange (eBOE) status lifecycle Jul 12, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 11

🧹 Nitpick comments (5)
src/status/types.ts (1)

1-3: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Simplify to a direct re-export.

SonarCloud flags this: the import-then-re-export can be collapsed into a single export type ... from statement.

🔧 Proposed fix
-import { ContractOptions, TransactionOptions } from '../token-registry-functions/types';
-
-export type { ContractOptions, TransactionOptions };
+export type { ContractOptions, TransactionOptions } from '../token-registry-functions/types';
🤖 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 `@src/status/types.ts` around lines 1 - 3, Replace the import-then-re-export in
the status types module with a single direct type re-export from the
token-registry-functions types module, preserving the exported ContractOptions
and TransactionOptions symbols.

Source: Linters/SAST tools

src/core/documentBuilder.ts (1)

344-353: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the any cast hereTradeTrustToken__factory.connect is already used with a plain provider elsewhere, so this looks like an unnecessary type suppression. If the v4/v5 factory union is the blocker, narrow that type instead of silencing the checker.

🤖 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 `@src/core/documentBuilder.ts` around lines 344 - 353, Remove the explicit any
cast and its eslint suppression from supportsInterface, passing the provider
directly to contractFactory.connect. If the v4/v5 factory union prevents
type-checking, narrow the factory type or adjust the method signature so both
generated factories accept the plain provider without suppressing the checker.
src/__tests__/status/status.test.ts (2)

113-187: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reject/discharge blocks lack parity with accept's parameter-validation tests.

accept (Lines 76-96) tests missing-tokenRegistryAddress, missing-provider, and not-V5 paths, but reject and discharge only cover happy-path/empty-remarks/callStatic-fail. Per the upstream snippets, reject.ts/discharge.ts duplicate the exact same preflight logic as accept.ts, so a future divergence in any one of them wouldn't be caught here.

🤖 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 `@src/__tests__/status/status.test.ts` around lines 113 - 187, Add
parameter-validation parity tests to the reject and discharge describe blocks,
matching the existing accept tests for missing tokenRegistryAddress, missing
provider, and non-V5 contract paths. Use the existing reject and discharge
helpers and assertions, while preserving their current happy-path,
empty-remarks, and callStatic failure coverage.

98-110: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fragile mock cleanup for the callStatic-fail tests.

mockRejectedValue sets a persistent implementation; vi.clearAllMocks() in the next beforeEach only clears call history, not implementations, so the manual mockV5TitleEscrowContract.callStatic.accept = vi.fn(); / .staticCall = vi.fn(); reassignment is what's actually preventing this rejection from leaking into later tests. Same pattern repeats at Lines 134-146 (reject) and 174-186 (discharge).

Using mockRejectedValueOnce instead removes the need for this manual bookkeeping and is more resilient to test reordering/parallelization. statusE2eDocument.test.ts already re-establishes these mocks proactively in beforeEach, which is a more robust template to follow here too.

♻️ Suggested fix (apply the same pattern at the reject/discharge occurrences)
-        mockV5TitleEscrowContract.callStatic.accept.mockRejectedValue(
-          new Error('Simulated failure'),
-        );
-        mockV5TitleEscrowContract.accept.staticCall.mockRejectedValue(
-          new Error('Simulated failure'),
-        );
+        mockV5TitleEscrowContract.callStatic.accept.mockRejectedValueOnce(
+          new Error('Simulated failure'),
+        );
+        mockV5TitleEscrowContract.accept.staticCall.mockRejectedValueOnce(
+          new Error('Simulated failure'),
+        );
         await expect(
           accept(contractOptions, wallet, { remarks: MOCK_REMARKS }, options),
         ).rejects.toThrow('Pre-check (callStatic) for accept failed');
-        mockV5TitleEscrowContract.callStatic.accept = vi.fn();
-        mockV5TitleEscrowContract.accept.staticCall = vi.fn();

Regarding the clearAllMocks semantics: "function clearAllMocks(): Vitest · Calls .mockClear() on all spies. This will clear mock history without affecting mock implementations."

🤖 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 `@src/__tests__/status/status.test.ts` around lines 98 - 110, Update the
failure setup in the accept, reject, and discharge tests to use one-shot
rejected implementations via mockRejectedValueOnce for both callStatic and
staticCall mocks. Remove the manual
mockV5TitleEscrowContract.callStatic.accept/staticCall and corresponding
reject/discharge reassignments, while preserving the existing rejection
assertions.
src/__tests__/status/statusE2eDocument.test.ts (1)

388-406: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Describe title doesn't match what's tested.

"invalid lifecycle transitions" only asserts that plain ETR functions don't read/mutate status; it never exercises an actual invalid transition (e.g. discharge from Rejected, or a second accept on an already-Accepted escrow) to verify the terminal-state guarantees the README describes. Consider renaming this block (e.g. "ETR functions are status-unaware") and/or adding cases that configure the mock to reject for an invalid precondition and assert the corresponding error.

🤖 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 `@src/__tests__/status/statusE2eDocument.test.ts` around lines 388 - 406,
Rename the describe block to reflect that it verifies ETR functions are
status-unaware, or expand it with genuine invalid-transition cases. If
expanding, configure the mock for an invalid precondition such as discharge from
Rejected or accepting an already Accepted escrow, then assert the expected error
and terminal-state behavior.
🤖 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 `@README.md`:
- Around line 1265-1291: The README documentation for accept, reject, and
discharge promises client-side role, owner, and status pre-flight checks that
the SDK does not implement. Update the “accept / reject / discharge” section to
describe the actual callStatic/on-chain precondition flow, including the real
failure behavior, unless the implementation is explicitly being changed to add
those checks.

In `@src/__tests__/fixtures/endorsement-chain.ts`:
- Around line 3364-3399: The getLogs fixture entries for StatusAccepted,
StatusRejected, and StatusDischarged use incorrect topic0 hashes. Replace the
three corresponding values in the endorsement-chain fixture with the provided
canonical hashes, preserving their addresses, block ranges, and empty results.

In `@src/status/accept.ts`:
- Around line 29-90: Extract the duplicated address resolution and V5 validation
from accept, reject, and discharge into a shared resolveV5TitleEscrow helper.
Have it validate signer.provider before resolving addresses, preserve the
existing token registry/token ID and version checks, and return the resolved
titleEscrowAddress. Update each action to use the helper, while keeping contract
construction and remark encryption in the respective action flows.
- Around line 39-51: Move the signer.provider validation in the accept flow
before the getTitleEscrowAddress call, while retaining the existing title escrow
and token validation behavior. Update the surrounding logic in the accept
function so getTitleEscrowAddress is only invoked after the provider-required
guard passes.
- Around line 52-61: Guard the remarks encryption in the accept flow before
calling encrypt, ensuring options.id is present whenever remarks are supplied.
Reuse the existing explicit validation style used for titleEscrowAddress and
signer.provider, and throw a clear error for missing options.id; preserve the 0x
fallback when no remarks are provided.
- Line 9: Update the return types of the wrapper and runStatusAction to support
both ethers v5 ContractTransaction and the v6 ContractTransactionResponse
returned by the v6 branch. Use a shared compatibility type or explicit union,
and ensure all related declarations consistently expose that broader Promise
return type.

In `@src/status/discharge.ts`:
- Around line 53-62: Validate that options.id is present before calling encrypt
in the discharge flow, instead of relying on the non-null assertion in the
encryptedRemarks assignment. Reuse the existing validation and error-handling
approach from accept.ts/reject.ts, while preserving the current '0x' behavior
when remarks are absent.
- Around line 40-52: Move the signer.provider validation in the discharge flow
before the getTitleEscrowAddress call, ensuring the provider is checked before
being passed to it. Keep the existing title escrow and token validation behavior
unchanged, and remove the later redundant provider guard.

In `@src/status/reject.ts`:
- Around line 53-62: Validate that options.id is present before calling encrypt
in the reject flow, and use the validated identifier as the encryption key
instead of the non-null assertion in the encryptedRemarks calculation. Preserve
the existing '0x' behavior when remarks are absent and follow the validation
approach used by accept.ts.
- Around line 40-52: Move the signer.provider validation before the
getTitleEscrowAddress call in the reject flow, ensuring the provider guard
executes before signer.provider is passed to getTitleEscrowAddress. Keep the
existing title escrow and token validation behavior unchanged.

In `@src/status/status.ts`:
- Around line 29-41: Move the `signer.provider` validation before the
`getTitleEscrowAddress` call in the status flow, ensuring the provider is
checked before it is passed as an argument. Preserve the existing title escrow,
token registry, and token ID validations, and apply the same ordering in the
corresponding accept, reject, and discharge flows if they duplicate this
pattern.

---

Nitpick comments:
In `@src/__tests__/status/status.test.ts`:
- Around line 113-187: Add parameter-validation parity tests to the reject and
discharge describe blocks, matching the existing accept tests for missing
tokenRegistryAddress, missing provider, and non-V5 contract paths. Use the
existing reject and discharge helpers and assertions, while preserving their
current happy-path, empty-remarks, and callStatic failure coverage.
- Around line 98-110: Update the failure setup in the accept, reject, and
discharge tests to use one-shot rejected implementations via
mockRejectedValueOnce for both callStatic and staticCall mocks. Remove the
manual mockV5TitleEscrowContract.callStatic.accept/staticCall and corresponding
reject/discharge reassignments, while preserving the existing rejection
assertions.

In `@src/__tests__/status/statusE2eDocument.test.ts`:
- Around line 388-406: Rename the describe block to reflect that it verifies ETR
functions are status-unaware, or expand it with genuine invalid-transition
cases. If expanding, configure the mock for an invalid precondition such as
discharge from Rejected or accepting an already Accepted escrow, then assert the
expected error and terminal-state behavior.

In `@src/core/documentBuilder.ts`:
- Around line 344-353: Remove the explicit any cast and its eslint suppression
from supportsInterface, passing the provider directly to
contractFactory.connect. If the v4/v5 factory union prevents type-checking,
narrow the factory type or adjust the method signature so both generated
factories accept the plain provider without suppressing the checker.

In `@src/status/types.ts`:
- Around line 1-3: Replace the import-then-re-export in the status types module
with a single direct type re-export from the token-registry-functions types
module, preserving the exported ContractOptions and TransactionOptions symbols.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 56eca595-5d11-4ae3-9daf-117e13628000

📥 Commits

Reviewing files that changed from the base of the PR and between 7598cd1 and 6a11889.

📒 Files selected for processing (19)
  • README.md
  • src/__tests__/core/endorsement-chain-status-events.test.ts
  • src/__tests__/fixtures/boe-raw-file.json
  • src/__tests__/fixtures/endorsement-chain.ts
  • src/__tests__/status/fixtures.ts
  • src/__tests__/status/status.test.ts
  • src/__tests__/status/statusE2eDocument.test.ts
  • src/__tests__/token-registry-functions/fixtures.ts
  • src/core/documentBuilder.ts
  • src/core/endorsement-chain/fetchEscrowTransfer.ts
  • src/core/endorsement-chain/helpers.ts
  • src/core/endorsement-chain/types.ts
  • src/index.ts
  • src/status/accept.ts
  • src/status/discharge.ts
  • src/status/index.ts
  • src/status/reject.ts
  • src/status/status.ts
  • src/status/types.ts

Comment thread README.md Outdated
Comment thread src/__tests__/fixtures/endorsement-chain.ts
Comment thread src/status/accept.ts Outdated
Comment thread src/status/accept.ts Outdated
Comment thread src/status/accept.ts Outdated
Comment thread src/status/discharge.ts Outdated
Comment thread src/status/discharge.ts Outdated
Comment thread src/status/reject.ts Outdated
Comment thread src/status/reject.ts Outdated
Comment thread src/status/status.ts Outdated
manishdex25 and others added 3 commits July 13, 2026 00:21
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
- Updated README to clarify on-chain preconditions for accept, reject, and discharge functions.
- Deleted the legacy V5 TitleEscrow test file.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/status/runStatusAction.ts (1)

79-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Preserve original error details and remove console.error in library code.

The catch block swallows the underlying error and throws a generic message, making debugging difficult. Additionally, console.error in library code can leak internals and is not controllable by consumers. Include the original error message in the thrown error and drop the console log.

♻️ Proposed refactor
   try {
     if (isV6EthersProvider(signer.provider)) {
       await (titleEscrowContract as ContractV6)[action].staticCall(encryptedRemarks);
     } else {
       await (titleEscrowContract as ContractV5).callStatic[action](encryptedRemarks);
     }
   } catch (e) {
-    console.error('callStatic failed:', e);
-    throw new Error(`Pre-check (callStatic) for ${action} failed`);
+    throw new Error(
+      `Pre-check (callStatic) for ${action} failed: ${e instanceof Error ? e.message : String(e)}`,
+    );
   }
🤖 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 `@src/status/runStatusAction.ts` around lines 79 - 88, Update the catch block
around the static call in runStatusAction to remove console.error and rethrow an
error that preserves the original failure details alongside the existing action
context. Keep the pre-check failure wording while including the caught error
message.
🤖 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 `@src/__tests__/fixtures/endorsement-chain.ts`:
- Line 3369: The endorsement-chain fixture contains incorrect topic0 hashes for
three status events. Replace the three status-event topic0 values with the real
V5 TitleEscrow ABI hashes, using only the supported events listed in the comment
and preserving the fixture’s existing event structure.

In `@src/status/runStatusAction.ts`:
- Line 64: Update the encryptedRemarks construction in runStatusAction to only
call encrypt when both remarks and options.id are defined; otherwise use the
existing empty “0x” value. Remove the non-null assertion and preserve the
current encryption behavior when a valid ID is available.
- Around line 42-54: Move the signer.provider validation before the
getTitleEscrowAddress call in the centralized status action flow. Ensure the
existing “Provider is required” error is raised before invoking
getTitleEscrowAddress when titleEscrowAddress is absent, while preserving the
token registry, token ID, and title escrow validations.

---

Nitpick comments:
In `@src/status/runStatusAction.ts`:
- Around line 79-88: Update the catch block around the static call in
runStatusAction to remove console.error and rethrow an error that preserves the
original failure details alongside the existing action context. Keep the
pre-check failure wording while including the caught error message.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a09cc9d0-6e06-4651-af68-727f9554e43f

📥 Commits

Reviewing files that changed from the base of the PR and between 6a11889 and 0f5df69.

📒 Files selected for processing (10)
  • README.md
  • src/__tests__/core/endorsement-chain-legacy-v5.test.ts
  • src/__tests__/fixtures/endorsement-chain.ts
  • src/core/endorsement-chain/useEndorsementChain.ts
  • src/status/accept.ts
  • src/status/discharge.ts
  • src/status/reject.ts
  • src/status/runStatusAction.ts
  • src/status/status.ts
  • src/status/types.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/status/types.ts
  • src/status/status.ts

params: [
{
address: '0x24c9C688cf919D133abB512A41163972dA150f1b',
topics: ['0x29ab698c427c31fa4320edd82b1a5ed0be38d752324ceb1a7a8658f71ff380ac'],

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search for V5 TitleEscrow event definitions =="
rg -n 'event Status(Accepted|Rejected|Discharged)' src/ -A2 \
  --glob '!**/*.map' --glob '!node_modules' || echo "No Solidity event defs found"

echo
echo "== Search for status event names in ABIs =="
rg -n '"name":\s*"Status(Accepted|Rejected|Discharged)"' src/ -A5 \
  --glob '!**/*.map' --glob '!node_modules' || echo "No ABI event entries found"

echo
echo "== Try computing topic hashes with ethers =="
node -e "
const sigs = [
  'StatusAccepted(address,address,uint256,bytes)',
  'StatusRejected(address,address,uint256,bytes)',
  'StatusDischarged(address,address,uint256,bytes)'
];
try {
  const { ethers } = require('ethers');
  sigs.forEach(s => console.log(s, '->', ethers.id(s)));
} catch(e) {
  console.log('ethers v5 not available:', e.message);
}
" 2>/dev/null || echo 'node/ethers not available'

echo
echo "== Try with Python keccak =="
python3 -c "
try:
    from Crypto.Hash import keccak
    sigs = [
        'StatusAccepted(address,address,uint256,bytes)',
        'StatusRejected(address,address,uint256,bytes)',
        'StatusDischarged(address,address,uint256,bytes)'
    ]
    for s in sigs:
        k = keccak.new(digest_bits=256)
        k.update(s.encode())
        print(f'{s} -> 0x{k.hexdigest()}')
except ImportError:
    print('No keccak library available in Python')
" 2>/dev/null || echo 'python3 not available'

echo
echo "== Current fixture topic hashes =="
rg -n 'topics:' src/__tests__/fixtures/endorsement-chain.ts | tail -10

Repository: TrustVC/trustvc

Length of output: 1057


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Locate TitleEscrow-related files =="
fd -i 'TitleEscrow|titleescrow|endorsement|status' .

echo
echo "== Search for event declarations / ABI names repo-wide =="
rg -n 'event\s+Status(Accepted|Rejected|Discharged)|"name"\s*:\s*"Status(Accepted|Rejected|Discharged)"|TitleEscrow' . \
  --glob '!**/*.map' --glob '!node_modules' -A3 -B3

echo
echo "== Show likely contract/ABI file tree (top levels only) =="
find . -maxdepth 2 -type f \( -name '*.sol' -o -name '*.json' -o -name '*.ts' \) | sed 's#^\./##' | sort | head -300

Repository: TrustVC/trustvc

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Inspect the status-event test =="
sed -n '1,220p' src/__tests__/core/endorsement-chain-status-events.test.ts

echo
echo "== Inspect the fixture around the cited lines =="
sed -n '3348,3405p' src/__tests__/fixtures/endorsement-chain.ts

echo
echo "== Search for the three exact hashes elsewhere =="
rg -n '29ab698c427c31fa4320edd82b1a5ed0be38d752324ceb1a7a8658f71ff380ac|4273e3f9fa628dca95157f3f3e43f55a7b8a8e5a85596670d99f3b2f5d32578b|cde6b978de664fc7c3c45ec359bc419b7e601021747927764218fa9af45238d5' src/ -n

Repository: TrustVC/trustvc

Length of output: 6421


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== package.json dependencies relevant to ethers/contracts =="
sed -n '1,240p' package.json | rg -n '"(ethers|ethersV6|`@tradetrust-tt/token-registry-v5`|`@tradetrust-tt/token-registry-v4`)"'

echo
echo "== Can Node resolve ethersV6 / token-registry-v5? =="
node - <<'JS'
for (const mod of ['ethers', 'ethersV6', '`@tradetrust-tt/token-registry-v5/contracts`']) {
  try {
    const resolved = require.resolve(mod);
    console.log(mod + ' => ' + resolved);
  } catch (e) {
    console.log(mod + ' => NOT RESOLVABLE (' + e.message + ')');
  }
}
JS

echo
echo "== Search for event signatures in repo lockfiles / metadata =="
rg -n 'StatusAccepted|StatusRejected|StatusDischarged|TitleEscrow__factory|event' package-lock.json pnpm-lock.yaml yarn.lock src/ --glob '!**/*.map' --max-count 40

Repository: TrustVC/trustvc

Length of output: 23244


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const { ethers: ethersV6 } = require('ethersV6');
const { TitleEscrow__factory } = require('`@tradetrust-tt/token-registry-v5/contracts`');

const iface = new ethersV6.Interface(TitleEscrow__factory.abi);
for (const name of ['StatusAccepted', 'StatusRejected', 'StatusDischarged']) {
  const fragment = iface.getEvent(name);
  console.log(name);
  console.log('  signature:', fragment.format());
  console.log('  topicHash :', fragment.topicHash);
}
JS

Repository: TrustVC/trustvc

Length of output: 837


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node - <<'JS'
const { ethers: ethersV6 } = require('ethersV6');
const { TitleEscrow__factory } = require('`@tradetrust-tt/token-registry-v5/contracts`');

const iface = new ethersV6.Interface(TitleEscrow__factory.abi);
const events = iface.fragments.filter(f => f.type === 'event');

console.log('event count:', events.length);
for (const f of events) {
  console.log(f.format(), '=>', f.topicHash);
}

const fixture = new Set([
  '0x29ab698c427c31fa4320edd82b1a5ed0be38d752324ceb1a7a8658f71ff380ac',
  '0x4273e3f9fa628dca95157f3f3e43f55a7b8a8e5a85596670d99f3b2f5d32578b',
  '0xcde6b978de664fc7c3c45ec359bc419b7e601021747927764218fa9af45238d5',
]);

console.log('fixture matches:');
for (const h of fixture) {
  const match = events.find(f => f.topicHash === h);
  console.log(h, '=>', match ? match.format() : 'NO MATCH');
}
JS

Repository: TrustVC/trustvc

Length of output: 1640


Replace these three status-event topic0s with the real V5 ABI hashes. @tradetrust-tt/token-registry-v5 does not expose StatusAccepted, StatusRejected, or StatusDischarged; the V5 TitleEscrow ABI only includes BeneficiaryTransfer, HolderTransfer, Initialized, Nomination, RejectTransferBeneficiary, RejectTransferHolder, RejectTransferOwners, ReturnToIssuer, Shred, and TokenReceived.

🤖 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 `@src/__tests__/fixtures/endorsement-chain.ts` at line 3369, The
endorsement-chain fixture contains incorrect topic0 hashes for three status
events. Replace the three status-event topic0 values with the real V5
TitleEscrow ABI hashes, using only the supported events listed in the comment
and preserving the fixture’s existing event structure.

Comment on lines +42 to +54
if (!titleEscrowAddress) {
if (!tokenRegistryAddress) throw new Error('Token registry address is required');
if (!tokenId) throw new Error('Token ID is required');
titleEscrowAddress = await getTitleEscrowAddress(
tokenRegistryAddress,
tokenId as string,
signer.provider,
{},
);
}

if (!titleEscrowAddress) throw new Error('Title escrow address is required');
if (!signer.provider) throw new Error('Provider is required');

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Move signer.provider null check before getTitleEscrowAddress call.

signer.provider is passed to getTitleEscrowAddress at line 48 before the guard at line 54 validates it. When titleEscrowAddress is absent and the provider is null, the user gets an opaque ethers-internal error instead of the clear "Provider is required" message. This same issue was previously identified and fixed in the individual accept/reject/discharge files but re-appeared when the logic was centralized here.

🔧 Proposed fix
   const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options;

+  if (!signer.provider) throw new Error('Provider is required');
+
   if (!titleEscrowAddress) {
     if (!tokenRegistryAddress) throw new Error('Token registry address is required');
     if (!tokenId) throw new Error('Token ID is required');
     titleEscrowAddress = await getTitleEscrowAddress(
       tokenRegistryAddress,
       tokenId as string,
       signer.provider,
       {},
     );
   }

   if (!titleEscrowAddress) throw new Error('Title escrow address is required');
-  if (!signer.provider) throw new Error('Provider is required');
📝 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 (!titleEscrowAddress) {
if (!tokenRegistryAddress) throw new Error('Token registry address is required');
if (!tokenId) throw new Error('Token ID is required');
titleEscrowAddress = await getTitleEscrowAddress(
tokenRegistryAddress,
tokenId as string,
signer.provider,
{},
);
}
if (!titleEscrowAddress) throw new Error('Title escrow address is required');
if (!signer.provider) throw new Error('Provider is required');
const { chainId, maxFeePerGas, maxPriorityFeePerGas, titleEscrowVersion } = options;
if (!signer.provider) throw new Error('Provider is required');
if (!titleEscrowAddress) {
if (!tokenRegistryAddress) throw new Error('Token registry address is required');
if (!tokenId) throw new Error('Token ID is required');
titleEscrowAddress = await getTitleEscrowAddress(
tokenRegistryAddress,
tokenId as string,
signer.provider,
{},
);
}
if (!titleEscrowAddress) throw new Error('Title escrow address is required');
🤖 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 `@src/status/runStatusAction.ts` around lines 42 - 54, Move the signer.provider
validation before the getTitleEscrowAddress call in the centralized status
action flow. Ensure the existing “Provider is required” error is raised before
invoking getTitleEscrowAddress when titleEscrowAddress is absent, while
preserving the token registry, token ID, and title escrow validations.

// eslint-disable-next-line @typescript-eslint/no-explicit-any
signer as any,
);
const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x';

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard options.id before encrypting remarks.

When remarks is provided but options.id is undefined, the non-null assertion options.id! passes undefined to encrypt, which calls key.length on undefined — a TypeError crash. This was previously flagged and fixed in the individual action files but re-appeared here during centralization.

🛡️ Proposed fix
   const { remarks } = params;
+  if (remarks && !options.id) throw new Error('`id` is required to encrypt remarks');

   const Contract = getEthersContractFromProvider(signer.provider);
📝 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 encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x';
const { remarks } = params;
if (remarks && !options.id) throw new Error('`id` is required to encrypt remarks');
const encryptedRemarks = remarks ? `0x${encrypt(remarks, options.id!)}` : '0x';
🤖 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 `@src/status/runStatusAction.ts` at line 64, Update the encryptedRemarks
construction in runStatusAction to only call encrypt when both remarks and
options.id are defined; otherwise use the existing empty “0x” value. Remove the
non-null assertion and preserve the current encryption behavior when a valid ID
is available.

- updated testcases of the v4 / v5 and new v5 status changes in endorsment chain
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
3.0% Duplication on New Code (required ≤ 3%)
E Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

💡 Need a hand with PR review? Try Gitar by Sonar!

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.

1 participant