Skip to content

Feature/boe c2 fixes - #29

Open
manishdex25 wants to merge 15 commits into
betafrom
feature/boe-c2-fixes
Open

Feature/boe c2 fixes#29
manishdex25 wants to merge 15 commits into
betafrom
feature/boe-c2-fixes

Conversation

@manishdex25

@manishdex25 manishdex25 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added Obligation Registry deployment and minting commands.
    • Added comprehensive Bill of Exchange escrow actions, including acceptance, rejection, discharge, returns, transfers, and status checks.
    • Enhanced verification to display obligation registry status and termination details.
    • Added clearer contract transaction errors and safer dry-run handling.
  • Documentation

    • Expanded the README with deployment, lifecycle, verification, configuration, commands, and user-guide content.
    • Added obligation credential examples.
  • Release

    • Added beta-channel publishing and branch-specific CI/release workflows.

manishdex25 and others added 13 commits July 27, 2026 14:46
- Introduced new commands for managing BoE obligations, including accept, reject, transfer, and return functionalities.
- Added types for obligation registry and escrow commands to enhance type safety and clarity.
- Implemented shared utilities for prompting user inputs across obligation escrow commands.
- Enhanced transaction handling with detailed logging and error management for better user experience.
…dentialSubject fields for improved clarity and accuracy
- Updated .releaserc.json to include a new branch 'beta-boe' for beta prereleases.
- Modified release.yml to trigger workflows on pushes to the 'beta-boe' branch.
- Bumped version to 1.1.0 in package.json and package-lock.json.
- Updated @trustvc/trustvc dependency to version 2.15.0-beta.3.
- Added new dependencies including @account-abstraction/contracts and @digitalbazaar/ecdsa-rdfc-2019-cryptosuite.
- Improved README for clarity on obligation escrow commands.
…andling

- Clarified the process for minting BoE token IDs, emphasizing the need to sign with `w3c-sign` before minting.
- Improved instructions for using the Obligation Registry address in documents.
- Enhanced error messages in CLI to provide clearer guidance on contract call exceptions.
- Updated deployment logs to include instructions for using the Obligation Registry address.
- Renamed the 'beta-boe' branch to 'beta' in .releaserc.json for clarity.
- Updated CI workflow to include checks for pushes and pull requests to 'main' and 'beta' branches, as well as feature branches.
- Enhanced release workflow permissions and added npm registry URL for publishing.
- Adjusted npm publish settings in the release configuration to ensure proper deployment.
- Improved formatting for better readability, including consistent use of markdown syntax.
- Clarified instructions for using the Obligation Registry and related commands.
- Removed unnecessary whitespace to enhance overall document cleanliness.
…scrow commands

- Changed @trustvc/trustvc dependency to a local file reference for development.
- Enhanced error handling in various obligation escrow commands to ensure proper exit codes on transaction failures.
- Refactored command handlers to streamline transaction execution and error logging.
- Updated README to clarify command usage and improve overall documentation quality.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@manishdex25
manishdex25 changed the base branch from main to beta July 31, 2026 19:19
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fd6aa5dd-7bcc-4154-aea2-7babd11dce99

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds Obligation Registry deployment and minting, Obligation Escrow lifecycle and transfer commands, BoE verification status reporting, contract error decoding, tests, documentation, and beta release support.

Changes

Obligation Registry and Escrow CLI

Layer / File(s) Summary
Contracts, types, and document extraction
src/commands/helpers.ts, src/types.ts, src/utils/obligation-document.ts, src/utils/index.ts
Adds contract connection helpers, command argument types, and obligation document metadata extraction.
Registry deployment and minting
src/commands/obligation-registry/*, tests/commands/obligation-registry/*, tests/fixtures/obligation/*
Adds registry command discovery, deployment, minting, dry-run handling, transaction submission, and tests.
Escrow lifecycle and transfers
src/commands/obligation-escrow/*, tests/commands/obligation-escrow/*
Adds shared escrow execution and commands for acceptance, rejection, discharge, status, returns, nominations, endorsements, and transfers.
Verification and error handling
src/commands/verify.ts, src/utils/cli-errors.ts, src/utils/cli-options.ts, src/utils/formatting.ts, tests/utils/contract-errors.test.ts
Adds BoE status extraction, network-backed verification, contract revert decoding, and definitive-revert handling.
Release and package configuration
.github/workflows/*, .releaserc.json, package.json
Adds beta release support, npm authentication, release permissions, and the fixed TrustVC prerelease dependency.
Documentation and samples
README.md, samples/obligation-credential-subject.sample.json
Documents BoE commands, workflows, verification, restrictions, project structure, and sample credential data.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title identifies BoE-related fixes, which matches the pull request's main focus on obligation registry, escrow, verification, and documentation changes.
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 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/boe-c2-fixes

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.

- Removed redundant error checks for obligationRegistry and tokenId.
- Enhanced error message for unsupported chain IDs to provide clearer guidance on using a valid BoE document.

@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: 15

🧹 Nitpick comments (11)
src/utils/cli-errors.ts (2)

17-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

InvalidTokenTransferToZeroAddressOwners has a selector but no user-facing message.

REVERT_SELECTOR_TO_NAME['0xbd805e91'] maps to InvalidTokenTransferToZeroAddressOwners, but KNOWN_REVERT_MESSAGES has no entry for that name, unlike every other selector-mapped error. When this selector is decoded, describeContractError falls back to the generic "Contract reverted with InvalidTokenTransferToZeroAddressOwners" instead of actionable guidance.

Add a matching entry to KNOWN_REVERT_MESSAGES for consistency with the rest of the map.

🤖 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/utils/cli-errors.ts` around lines 17 - 75, Add an
InvalidTokenTransferToZeroAddressOwners entry to KNOWN_REVERT_MESSAGES with
actionable guidance for the zero-address owners transfer error, matching the
existing REVERT_SELECTOR_TO_NAME mapping so describeContractError returns a
user-facing message instead of the generic fallback.

50-75: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Decode custom reverts from the contract ABI instead of hardcoding selectors.

REVERT_SELECTOR_TO_NAME maps bare 4-byte data values to error names at src/utils/cli-errors.ts:151. If a mapped error is deprecated, renamed, or its argument list changes, the CLI can show the wrong message. Use the existing ethers dependency’s ABI Interface.parseError(data) for contract reverts, and keep this table only for selectors whose full ABI signatures are unavailable.

🤖 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/utils/cli-errors.ts` around lines 50 - 75, The CLI currently hardcodes
contract revert selectors in REVERT_SELECTOR_TO_NAME, which can become stale.
Update the revert-decoding logic in cli-errors.ts to use the existing ethers ABI
Interface.parseError(data) for selectors covered by the contract ABI, formatting
the parsed error name and arguments as appropriate; retain
REVERT_SELECTOR_TO_NAME only as a fallback for selectors whose full ABI
signatures are unavailable.
tests/utils/contract-errors.test.ts (1)

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

Add coverage for plain-text, multi-word revert reasons.

None of the current cases pass a multi-word err.reason (e.g., a standard require(condition, "insufficient funds for transfer") revert). This is the scenario that reveals the label-truncation bug flagged in src/utils/cli-errors.ts (normalizeLabel), where only the first word of the reason would be kept.

Add a case asserting that getErrorMessage/describeContractError return the full reason text unchanged when err.reason contains spaces and does not match a known custom-error name.

Do you want me to add this test case once the normalizeLabel fix in src/utils/cli-errors.ts lands?

🤖 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/utils/contract-errors.test.ts` around lines 1 - 67, Add a test in the
contract revert error formatting suite using an error with a plain-text,
multi-word reason such as “insufficient funds for transfer” that is not a known
custom-error label. Assert that both getErrorMessage and describeContractError
preserve the complete reason text unchanged, covering normalizeLabel without
altering the existing custom-error cases.
tests/commands/obligation-escrow/discharge.test.ts (1)

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

Both escrow tests assert remarks: undefined, which passes trivially. expect.objectContaining({ remarks: undefined }) also matches when the key is absent, so neither test proves that the remark flows into sdkParams. Pass a remark in the handler args and assert that value.

  • tests/commands/obligation-escrow/discharge.test.ts#L79-L90: add remark: 'discharged' to the dischargeHandler args and assert remarks: 'discharged'.
  • tests/commands/obligation-escrow/reject.test.ts#L76-L87: add remark: 'rejected' to the rejectHandler args and assert remarks: 'rejected'.
🤖 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/commands/obligation-escrow/discharge.test.ts` around lines 79 - 90,
Update tests/commands/obligation-escrow/discharge.test.ts lines 79-90 by passing
remark: 'discharged' to dischargeHandler and asserting sdkParams.remarks equals
'discharged'. Update tests/commands/obligation-escrow/reject.test.ts lines 76-87
by passing remark: 'rejected' to rejectHandler and asserting sdkParams.remarks
equals 'rejected', ensuring both tests verify remark propagation rather than an
absent property.
src/commands/obligation-escrow/status.ts (2)

40-42: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Run the three reads concurrently.

The three calls are independent read-only queries. Each one performs its own escrow resolution and RPC round trips. Sequential awaits triple the wall-clock latency of the command.

⚡ Proposed refactor
-  const status = await getObligationRegistryStatus(opts, wallet, { tokenId });
-  const registered = await isObligationRegistryRegistered(opts, wallet, { tokenId });
-  const reason = await getObligationEscrowTerminationReason(opts, wallet, { tokenId });
+  const [status, registered, reason] = await Promise.all([
+    getObligationRegistryStatus(opts, wallet, { tokenId }),
+    isObligationRegistryRegistered(opts, wallet, { tokenId }),
+    getObligationEscrowTerminationReason(opts, wallet, { tokenId }),
+  ]);
🤖 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/commands/obligation-escrow/status.ts` around lines 40 - 42, Update the
status command flow around getObligationRegistryStatus,
isObligationRegistryRegistered, and getObligationEscrowTerminationReason to
start all three independent reads concurrently and await their results together,
preserving the existing variables and downstream behavior.

16-27: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use the SDK reverse mappings for status/reason labels.

ObligationDocumentStatus and ObligationEscrowTerminationReason are exported TypeScript numeric enums, so these maps duplicate labels and require manual updates when the SDK adds values. Derive them from the enum objects instead.

🤖 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/commands/obligation-escrow/status.ts` around lines 16 - 27, Replace the
manually maintained STATUS_LABEL and REASON_LABEL mappings with labels derived
from the exported numeric enum reverse mappings, ObligationDocumentStatus and
ObligationEscrowTerminationReason. Update the status/reason lookup usage as
needed while preserving the existing label output for current values and
automatically supporting newly added enum members.
src/commands/helpers.ts (2)

440-459: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the bytecode check into a shared helper.

connectToObligationRegistry and connectToObligationEscrow repeat the same provider check and getCode validation. Extract one helper and reuse it. The message can also state the real cause, which is that no contract exists at the address.

♻️ Proposed refactor
+const assertContractDeployed = async (
+  wallet: Wallet | HDNodeWallet | ConnectedSigner | Signer,
+  address: string,
+  label: string,
+): Promise<void> => {
+  const provider = wallet.provider;
+  if (!provider) {
+    throw new Error(`Wallet provider is required to validate the ${label} contract`);
+  }
+  const code = await provider.getCode(address);
+  if (!code || code === '0x') {
+    throw new Error(`No contract deployed at ${label} address: ${address}`);
+  }
+};
+
 export const connectToObligationRegistry = async ({
   address,
   wallet,
 }: ConnectToObligationRegistryArgs) => {
   try {
     signale.info(`Connecting to obligation registry at: ${address}`);
     const registry = new ethers.Contract(address, TrustVCToken__factory.abi, wallet as any);
-    const provider = wallet.provider;
-    if (!provider) {
-      throw new Error('Wallet provider is required to validate the obligation registry contract');
-    }
-    const code = await provider.getCode(address);
-    if (!code || code === '0x') {
-      throw new Error(`Failed to connect to obligation registry at address: ${address}`);
-    }
+    await assertContractDeployed(wallet, address, 'obligation registry');
     signale.success(`Successfully connected to obligation registry`);
     return registry;
🤖 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/commands/helpers.ts` around lines 440 - 459, Extract the provider
presence and provider.getCode(address) validation from
connectToObligationRegistry into a shared helper, then reuse that helper in both
connectToObligationRegistry and connectToObligationEscrow. Preserve the existing
rejection behavior and update the missing-contract error to state that no
contract exists at the supplied address.

476-487: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Validate the registry bytecode before you call ownerOf.

If address holds no contract, ownerOf rejects with a low-level ethers decode error. The user then sees an opaque message instead of "no contract at address". Reuse connectToObligationRegistry (or the shared assert helper) first, then resolve the escrow.

♻️ Proposed refactor
-    signale.info(`Connecting to obligation registry at: ${address}`);
-    const registry = new ethers.Contract(address, TrustVCToken__factory.abi, wallet as any);
-
+    const registry = await connectToObligationRegistry({ address, wallet });
     signale.info(`Fetching obligation escrow address for tokenId: ${tokenId}`);
🤖 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/commands/helpers.ts` around lines 476 - 487, Update the registry lookup
flow around `registry` and `ownerOf` to validate that `address` contains
deployed contract bytecode before calling `ownerOf`. Reuse
`connectToObligationRegistry` or the existing shared assertion helper, then
resolve and validate the escrow address as before.
tests/commands/obligation-registry/mint.test.ts (1)

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

Add tests for the failure and cancellation paths.

The suite covers only the happy path. Two behaviors in mint.ts remain untested:

  • mintObligationToken when mintObligationRegistry rejects. Assert the exit code.
  • mintToObligationRegistry when performDryRunWithConfirmation resolves false. Assert that no transaction is sent.

Both paths carry the exit-code defects flagged on src/commands/obligation-registry/mint.ts.

🤖 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/commands/obligation-registry/mint.test.ts` around lines 113 - 157,
Extend the obligation-registry mint tests with failure and cancellation cases:
configure mintObligationRegistry to reject and assert mintObligationToken
reports the expected exit code, then configure performDryRunWithConfirmation to
resolve false and assert mintToObligationRegistry does not send a transaction.
Reuse the existing mocks and symbols in mint.test.ts, covering the corresponding
paths in mintObligationToken and mintToObligationRegistry.
tests/commands/obligation-registry/deploy.test.ts (1)

94-125: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a failure-path test for deployObligationRegistryContract.

Add a test where deployObligationRegistry rejects or a dependency throws. Assert on the resulting process.exitCode or on error propagation. This would surface the exit-code gap flagged in src/commands/obligation-registry/deploy.ts (lines 146-148).

🤖 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/commands/obligation-registry/deploy.test.ts` around lines 94 - 125,
Extend the deployObligationRegistryContract test suite with a failure-path case
where deployObligationRegistry rejects or a dependency throws. Assert the
resulting process.exitCode or propagated error, targeting the error handling in
deployObligationRegistryContract and covering the exit-code behavior referenced
in the review.
src/commands/obligation-escrow/shared.ts (1)

23-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate credential-selection fallback logic.

This block repeats the same encryptedWalletPathkeyFilekey priority chain found in src/commands/obligation-registry/deploy.ts (lines 89-98). Extract a shared helper, for example withWalletCredentials(baseResult, { encryptedWalletPath, key, keyFile }), to keep the fallback order consistent as more commands are added.

🤖 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/commands/obligation-escrow/shared.ts` around lines 23 - 41, Extract the
credential-priority selection from the shared command result builder into a
reusable withWalletCredentials helper, preserving the encryptedWalletPath →
keyFile → key order and baseResult fallback. Replace the duplicate chain in the
current flow and reuse the helper in the obligation-registry deploy flow so both
commands share one consistent implementation.
🤖 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 @.github/workflows/release.yml:
- Line 64: Remove the registry-url configuration from the setup-node step in the
semantic-release workflow so it no longer creates an .npmrc; retain a single
authentication mechanism by using NPM_TOKEN or fully adopting npm Trusted
Publishing, without configuring both.

In `@README.md`:
- Around line 306-309: Remove the duplicate [mint](`#mint`) row from the Token
Registry table in README.md, keeping one primary mint entry and the existing
token-registry mint alternative row.
- Line 1422: Update the `documentverify` prompt label in README.md to insert the
missing separator and use either “Path to BoE / obligation document” or “Path to
document to verify.”
- Line 1675: Update the Node.js version guidance in the README, including the
“Node.js 22+” text and the associated nvm install/use examples, to specify
Node.js 22.19.5+ in alignment with the package.json engine requirement.

In `@src/commands/obligation-escrow/accept-return-to-issuer.ts`:
- Around line 17-25: Ensure prompt-phase failures set a nonzero exit code in the
outer handler catches. In
src/commands/obligation-escrow/accept-return-to-issuer.ts lines 17-25,
src/commands/obligation-escrow/endorse-transfer-owner.ts lines 18-26, and
src/commands/obligation-escrow/return-to-issuer.ts lines 17-25, update each
handler catch to set process.exitCode = 1 after logging the error.

In `@src/commands/obligation-escrow/discharge.ts`:
- Around line 17-25: Replace the duplicated try/catch handlers with
runObligationEscrowCommand: update src/commands/obligation-escrow/discharge.ts
lines 17-25 to pass promptForInputs and dischargeHandler, and
src/commands/obligation-escrow/reject.ts lines 17-25 to pass promptForInputs and
rejectHandler. Remove the manual error logging wrapper so the shared command
runner handles failures and exit codes.

In `@src/commands/obligation-escrow/reject-return-to-issuer.ts`:
- Around line 17-25: Replace the manual outer try/catch in handler with the
shared runObligationEscrowCommand helper, adding its import from ./shared and
passing promptForInputs and rejectReturnedHandler in
src/commands/obligation-escrow/reject-return-to-issuer.ts:17-25. Apply the same
change in src/commands/obligation-escrow/transfer-holder.ts:18-26, passing
promptForInputs and changeHolderHandler; remove the duplicated error handling so
prompt failures set the CLI exit code through the shared helper.

In `@src/commands/obligation-escrow/shared.ts`:
- Around line 48-60: The obligation command handlers must consistently return a
nonzero exit code on prompt or command failure. Keep runObligationEscrowCommand
in src/commands/obligation-escrow/shared.ts as the single wrapper; update
handlers in src/commands/obligation-escrow/accept.ts lines 17-25 and
src/commands/obligation-escrow/reject-transfer-owner.ts lines 17-25 to call it
with their prompt and handler functions, removing duplicated try/catch logic. In
src/commands/obligation-registry/deploy.ts lines 146-148, rethrow the deployment
error or explicitly set process.exitCode = 1 after logging.

In `@src/commands/obligation-registry/deploy.ts`:
- Around line 112-124: Update the dry-run flow around
performDryRunWithConfirmation so getTransactionCallback returns the populated
unsigned transaction for deployObligationRegistry from `@trustvc/trustvc`,
including its actual calldata and value, instead of a zero-data self-transfer.
If that deployment request cannot be populated before confirmation, defer fee
reporting to the broadcast receipt rather than estimating the placeholder
transaction.

In `@src/commands/obligation-registry/mint.ts`:
- Around line 94-96: Update the mint failure handling in mintObligationToken and
the handler catch path to set process.exitCode = 1 after logging the error,
matching the escrow command behavior so failed mints exit non-zero while
preserving the existing error message.
- Around line 126-128: Update the `!shouldProceed` branch after
`performDryRunWithConfirmation` so it does not call `process.exit(0)`. Return or
throw a sentinel that distinguishes cancellation from a definitive dry-run
revert, allowing the caller to choose a nonzero exit status for the revert and
preserving pending output flushing.

In `@src/commands/verify.ts`:
- Around line 149-154: Update verifyW3CDocument’s W3C lookup-failure and
OpenAttestation missing-chainId paths to avoid unconditionally calling
promptNetworkSelection. Prefer the explicit --network option when provided, and
otherwise use a non-interactive fallback that verifies without a provider in
non-TTY environments; retain interactive selection only when appropriate.

In `@src/utils/cli-errors.ts`:
- Around line 100-108: Update normalizeLabel so it only returns a label when the
entire cleaned value matches the intended identifier format, rather than
extracting the first word from free-text messages. Preserve the existing cleanup
and unknown-error filtering, allowing multi-word err.reason values handled by
extractContractRevertLabel to fall through and be surfaced intact by
describeContractError.
- Around line 158-224: Remove the unreachable error-instance branches from
extractContractRevertLabel and isContractCallException, since asEthersError
already returns a non-null object for every Error. Preserve the existing
fallback behavior through the later err.message handling and the remaining
pre-check validation without duplicating the plain Error path.

In `@src/utils/obligation-document.ts`:
- Around line 42-58: Remove the duplicated obligationRegistry and tokenId
validation block in the obligation-document validation flow, ensuring the
remaining if (!tokenId) conditional is properly closed before the chainId
checks. Preserve the existing validation order and error messages for
obligationRegistry, tokenId, chainId, and SUPPORTED_CHAINS.

---

Nitpick comments:
In `@src/commands/helpers.ts`:
- Around line 440-459: Extract the provider presence and
provider.getCode(address) validation from connectToObligationRegistry into a
shared helper, then reuse that helper in both connectToObligationRegistry and
connectToObligationEscrow. Preserve the existing rejection behavior and update
the missing-contract error to state that no contract exists at the supplied
address.
- Around line 476-487: Update the registry lookup flow around `registry` and
`ownerOf` to validate that `address` contains deployed contract bytecode before
calling `ownerOf`. Reuse `connectToObligationRegistry` or the existing shared
assertion helper, then resolve and validate the escrow address as before.

In `@src/commands/obligation-escrow/shared.ts`:
- Around line 23-41: Extract the credential-priority selection from the shared
command result builder into a reusable withWalletCredentials helper, preserving
the encryptedWalletPath → keyFile → key order and baseResult fallback. Replace
the duplicate chain in the current flow and reuse the helper in the
obligation-registry deploy flow so both commands share one consistent
implementation.

In `@src/commands/obligation-escrow/status.ts`:
- Around line 40-42: Update the status command flow around
getObligationRegistryStatus, isObligationRegistryRegistered, and
getObligationEscrowTerminationReason to start all three independent reads
concurrently and await their results together, preserving the existing variables
and downstream behavior.
- Around line 16-27: Replace the manually maintained STATUS_LABEL and
REASON_LABEL mappings with labels derived from the exported numeric enum reverse
mappings, ObligationDocumentStatus and ObligationEscrowTerminationReason. Update
the status/reason lookup usage as needed while preserving the existing label
output for current values and automatically supporting newly added enum members.

In `@src/utils/cli-errors.ts`:
- Around line 17-75: Add an InvalidTokenTransferToZeroAddressOwners entry to
KNOWN_REVERT_MESSAGES with actionable guidance for the zero-address owners
transfer error, matching the existing REVERT_SELECTOR_TO_NAME mapping so
describeContractError returns a user-facing message instead of the generic
fallback.
- Around line 50-75: The CLI currently hardcodes contract revert selectors in
REVERT_SELECTOR_TO_NAME, which can become stale. Update the revert-decoding
logic in cli-errors.ts to use the existing ethers ABI Interface.parseError(data)
for selectors covered by the contract ABI, formatting the parsed error name and
arguments as appropriate; retain REVERT_SELECTOR_TO_NAME only as a fallback for
selectors whose full ABI signatures are unavailable.

In `@tests/commands/obligation-escrow/discharge.test.ts`:
- Around line 79-90: Update tests/commands/obligation-escrow/discharge.test.ts
lines 79-90 by passing remark: 'discharged' to dischargeHandler and asserting
sdkParams.remarks equals 'discharged'. Update
tests/commands/obligation-escrow/reject.test.ts lines 76-87 by passing remark:
'rejected' to rejectHandler and asserting sdkParams.remarks equals 'rejected',
ensuring both tests verify remark propagation rather than an absent property.

In `@tests/commands/obligation-registry/deploy.test.ts`:
- Around line 94-125: Extend the deployObligationRegistryContract test suite
with a failure-path case where deployObligationRegistry rejects or a dependency
throws. Assert the resulting process.exitCode or propagated error, targeting the
error handling in deployObligationRegistryContract and covering the exit-code
behavior referenced in the review.

In `@tests/commands/obligation-registry/mint.test.ts`:
- Around line 113-157: Extend the obligation-registry mint tests with failure
and cancellation cases: configure mintObligationRegistry to reject and assert
mintObligationToken reports the expected exit code, then configure
performDryRunWithConfirmation to resolve false and assert
mintToObligationRegistry does not send a transaction. Reuse the existing mocks
and symbols in mint.test.ts, covering the corresponding paths in
mintObligationToken and mintToObligationRegistry.

In `@tests/utils/contract-errors.test.ts`:
- Around line 1-67: Add a test in the contract revert error formatting suite
using an error with a plain-text, multi-word reason such as “insufficient funds
for transfer” that is not a known custom-error label. Assert that both
getErrorMessage and describeContractError preserve the complete reason text
unchanged, covering normalizeLabel without altering the existing custom-error
cases.
🪄 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 Plus

Run ID: 415fbd43-034f-4828-a2c6-278ab78a3d17

📥 Commits

Reviewing files that changed from the base of the PR and between ea07583 and 4c3f853.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (52)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .releaserc.json
  • README.md
  • package.json
  • samples/obligation-credential-subject.sample.json
  • src/commands/helpers.ts
  • src/commands/obligation-escrow/accept-return-to-issuer.ts
  • src/commands/obligation-escrow/accept.ts
  • src/commands/obligation-escrow/discharge.ts
  • src/commands/obligation-escrow/endorse-transfer-owner.ts
  • src/commands/obligation-escrow/index.ts
  • src/commands/obligation-escrow/nominate-transfer-owner.ts
  • src/commands/obligation-escrow/reject-return-to-issuer.ts
  • src/commands/obligation-escrow/reject-transfer-holder.ts
  • src/commands/obligation-escrow/reject-transfer-owner-holder.ts
  • src/commands/obligation-escrow/reject-transfer-owner.ts
  • src/commands/obligation-escrow/reject.ts
  • src/commands/obligation-escrow/return-to-issuer.ts
  • src/commands/obligation-escrow/runTx.ts
  • src/commands/obligation-escrow/shared.ts
  • src/commands/obligation-escrow/status.ts
  • src/commands/obligation-escrow/transfer-holder.ts
  • src/commands/obligation-escrow/transfer-owner-holder.ts
  • src/commands/obligation-registry/deploy.ts
  • src/commands/obligation-registry/index.ts
  • src/commands/obligation-registry/mint.ts
  • src/commands/verify.ts
  • src/types.ts
  • src/utils/cli-errors.ts
  • src/utils/cli-options.ts
  • src/utils/formatting.ts
  • src/utils/index.ts
  • src/utils/obligation-document.ts
  • tests/commands/obligation-escrow/accept-return-to-issuer.test.ts
  • tests/commands/obligation-escrow/accept.test.ts
  • tests/commands/obligation-escrow/discharge.test.ts
  • tests/commands/obligation-escrow/endorse-transfer-owner.test.ts
  • tests/commands/obligation-escrow/nominate-transfer-owner.test.ts
  • tests/commands/obligation-escrow/reject-return-to-issuer.test.ts
  • tests/commands/obligation-escrow/reject-transfer-holder.test.ts
  • tests/commands/obligation-escrow/reject-transfer-owner-holder.test.ts
  • tests/commands/obligation-escrow/reject-transfer-owner.test.ts
  • tests/commands/obligation-escrow/reject.test.ts
  • tests/commands/obligation-escrow/return-to-issuer.test.ts
  • tests/commands/obligation-escrow/status.test.ts
  • tests/commands/obligation-escrow/transfer-holder.test.ts
  • tests/commands/obligation-escrow/transfer-owner-holder.test.ts
  • tests/commands/obligation-registry/deploy.test.ts
  • tests/commands/obligation-registry/mint.test.ts
  • tests/fixtures/obligation/w3c-obligation-record.json
  • tests/utils/contract-errors.test.ts

with:
node-version: 22.x
cache: 'npm'
registry-url: 'https://registry.npmjs.org/'

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

if rg -n 'registry-url|NODE_AUTH_TOKEN' .github/workflows/release.yml; then
  echo "Remove setup-node npm authentication for semantic-release." >&2
  exit 1
fi

Repository: TrustVC/trustvc-cli

Length of output: 328


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## release workflow around setup-node"
sed -n '50,85p' .github/workflows/release.yml || true

echo
echo "## workflow trigger and dependencies"
sed -n '1,45p' .github/workflows/release.yml || true

echo
echo "## package type and scripts"
python3 - <<'PY'
import json
from pathlib import Path
p=Path('package.json')
if p.exists():
    data=json.loads(p.read_text())
    for k in ['name','type','scripts','devDependencies','dependencies']:
        if k in data:
            print(f"{k}:", data[k])
PY

Repository: TrustVC/trustvc-cli

Length of output: 2992


Remove setup-node npm authentication for semantic-release.

setup-node creates an .npmrc when registry-url is set, and semantic-release npm authentication can conflict with that file. Use NPM_TOKEN for token authentication, or switch fully to npm Trusted Publishing, but do not configure both mechanisms.

Proposed fix
       with:
         node-version: 22.x
         cache: 'npm'
-        registry-url: 'https://registry.npmjs.org/'

         env:
           GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
           NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
-          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
🤖 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 @.github/workflows/release.yml at line 64, Remove the registry-url
configuration from the setup-node step in the semantic-release workflow so it no
longer creates an .npmrc; retain a single authentication mechanism by using
NPM_TOKEN or fully adopting npm Trusted Publishing, without configuring both.

Comment thread README.md
Comment on lines +306 to +309
| **Token Registry** | `[mint](#mint)` | Mint tokens to blockchain registries |
| | `[token-registry deploy](#token-registry-deploy)` | Deploy token registry contracts |
| | `[mint](#mint)` | Mint tokens to blockchain registries |
| | `token-registry mint` | Alternative: `mint` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the duplicate mint entry.

The table lists [mint] twice and then repeats token-registry mint as an alternative. Keep one primary mint row and one alternative row.

🤖 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 `@README.md` around lines 306 - 309, Remove the duplicate [mint](`#mint`) row
from the Token Registry table in README.md, keeping one primary mint entry and
the existing token-registry mint alternative row.

Comment thread README.md Outdated

**Interactive Prompts:**

- Path to BoE / obligation documentverify

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the documentverify prompt label.

The prompt concatenates document and verify. Change it to Path to BoE / obligation document or Path to document to verify.

🤖 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 `@README.md` at line 1422, Update the `documentverify` prompt label in
README.md to insert the missing separator and use either “Path to BoE /
obligation document” or “Path to document to verify.”

Comment thread README.md
### Before you start

1. **Install the CLI** — `npm install -g @trustvc/trustvc-cli` or `npx @trustvc/trustvc-cli <command>`
2. **Node.js 22+**

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

🧩 Analysis chain

🏁 Script executed:

jq -r '.engines.node' package.json
rg -n 'Node\.js 22\+|nvm (install|use) 22' README.md

Repository: TrustVC/trustvc-cli

Length of output: 338


Document the declared Node.js engine floor.

package.json requires Node.js >=22.19.5, but the README and the nvm example say Node.js 22+. Update the Node version guidance and nvm install 22 / nvm use 22 to Node.js 22.19.5+.

🤖 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 `@README.md` at line 1675, Update the Node.js version guidance in the README,
including the “Node.js 22+” text and the associated nvm install/use examples, to
specify Node.js 22.19.5+ in alignment with the package.json engine requirement.

Source: MCP tools

Comment on lines +17 to +25
export const handler = async (): Promise<void> => {
try {
const answers = await promptForInputs();
if (!answers) return;
await acceptReturnedHandler(answers);
} catch (err: unknown) {
error(err instanceof Error ? err.message : String(err));
}
};

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 | 🟠 Major | ⚡ Quick win

Outer handler catch never sets process.exitCode in three obligation-escrow commands. In each file, the outer handler try/catch logs the error from promptForInputs() but does not set process.exitCode. Each command's inner handler already sets process.exitCode = 1 on its own failures, so a prompt-phase failure (document verification, wallet selection) silently exits with code 0 while an execution-phase failure exits with code 1. Automation or CI scripts relying on the exit code cannot reliably detect prompt-phase failures.

  • src/commands/obligation-escrow/accept-return-to-issuer.ts#L17-L25: add process.exitCode = 1; inside the outer catch block of handler.
  • src/commands/obligation-escrow/endorse-transfer-owner.ts#L18-L26: add process.exitCode = 1; inside the outer catch block of handler.
  • src/commands/obligation-escrow/return-to-issuer.ts#L17-L25: add process.exitCode = 1; inside the outer catch block of handler.
📍 Affects 3 files
  • src/commands/obligation-escrow/accept-return-to-issuer.ts#L17-L25 (this comment)
  • src/commands/obligation-escrow/endorse-transfer-owner.ts#L18-L26
  • src/commands/obligation-escrow/return-to-issuer.ts#L17-L25
🤖 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/commands/obligation-escrow/accept-return-to-issuer.ts` around lines 17 -
25, Ensure prompt-phase failures set a nonzero exit code in the outer handler
catches. In src/commands/obligation-escrow/accept-return-to-issuer.ts lines
17-25, src/commands/obligation-escrow/endorse-transfer-owner.ts lines 18-26, and
src/commands/obligation-escrow/return-to-issuer.ts lines 17-25, update each
handler catch to set process.exitCode = 1 after logging the error.

Comment on lines +126 to +128
if (!shouldProceed) {
process.exit(0);
}

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 | ⚡ Quick win

Do not exit with code 0 when the dry run reports a revert.

performDryRunWithConfirmation returns false in two cases: the user cancels, and the gas estimation hits a definitive contract revert (see src/utils/cli-options.ts Lines 575-583). This code maps both cases to process.exit(0), so a would-revert mint reports success. process.exit also terminates the process before pending output flushes.

Return a sentinel or throw instead, and let the caller decide the exit code.

🐛 Proposed fix
-  if (!shouldProceed) {
-    process.exit(0);
-  }
+  if (!shouldProceed) {
+    throw new Error('Mint transaction was not submitted.');
+  }
📝 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 (!shouldProceed) {
process.exit(0);
}
if (!shouldProceed) {
throw new Error('Mint transaction was not submitted.');
}
🤖 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/commands/obligation-registry/mint.ts` around lines 126 - 128, Update the
`!shouldProceed` branch after `performDryRunWithConfirmation` so it does not
call `process.exit(0)`. Return or throw a sentinel that distinguishes
cancellation from a definitive dry-run revert, allowing the caller to choose a
nonzero exit status for the revert and preserving pending output flushing.

Comment thread src/commands/verify.ts
Comment on lines +149 to +154
const networkName = await promptNetworkSelection();
const provider = getSupportedNetwork(networkName).provider() as unknown as V5Provider;
if (provider) {
return await CaptureConsoleWarnAsync(() => verifyDocument(signedVC, { provider }));
}

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 | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether the verify command exposes a --network style flag or TTY guard
rg -n --type=ts -C5 "promptNetworkSelection|isTTY|--network|builder" src/commands/verify.ts

Repository: TrustVC/trustvc-cli

Length of output: 1335


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- verify command outline ---\n'
ast-grep outline src/commands/verify.ts --view compact || true

printf '\n--- relevant verify.ts sections ---\n'
sed -n '1,220p' src/commands/verify.ts | cat -n

printf '\n--- promptNetworkSelection definition/usages ---\n'
rg -n --type=ts -C8 "function promptNetworkSelection|const promptNetworkSelection|promptNetworkSelection\\(" src

printf '\n--- CLI option definitions for verify ---\n'
rg -n --type=ts -C6 "\"verify\"|name:\\s*['\"]verify|command.*verify|builder:*|options:*|--network|network" src

Repository: TrustVC/trustvc-cli

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- verify command outline ---'
ast-grep outline src/commands/verify.ts --view compact || true

echo
echo '--- relevant verify.ts sections ---'
sed -n '1,220p' src/commands/verify.ts | cat -n

echo
echo '--- promptNetworkSelection definition/usages ---'
rg -n --type=ts -C8 'function promptNetworkSelection|const promptNetworkSelection|promptNetworkSelection\(' src

echo
echo '--- CLI option definitions for verify ---'
rg -n --type=ts -C6 '["'"'"']verify["'"'"']|name:\s*["'"'"']verify|command.*verify|builder:*|options:*|--network|network' src

Repository: TrustVC/trustvc-cli

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- src/commands/verify.ts lines 1-220 ---'
sed -n '1,220p' src/commands/verify.ts | cat -n

echo
echo '--- all verify command definitions/options in src ---'
rg -n --type=ts -C8 'command\(["'"'"'"]verify|command:"verify"|name:\s*["'"'"'"]verify|builder:|builder\s*[\(:]|--network|network|promptNetworkSelection' src/commands src | head -n 220

echo
echo '--- cli-options promptNetworkSelection definition ---'
file_candidate="$(rg -l 'promptNetworkSelection\s*['"'"']|function\s+promptNetworkSelection|const\s+promptNetworkSelection' src | head -n 1 || true)"
if [ -n "${file_candidate:-}" ]; then
  echo "candidate: $file_candidate"
  sed -n '130,190p' "$file_candidate" | cat -n
fi

Repository: TrustVC/trustvc-cli

Length of output: 27258


Avoid an unconditional interactive network prompt in verify runs.

In verifyW3CDocument, both the W3C automatic chain lookup failure path and the OpenAttestation missing chainId path call promptNetworkSelection() without a --network fallback. promptNetworkSelection() uses an interactive select(), while non-TTY environments need either an explicit network option or a silent fallback to verify without a provider.

🤖 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/commands/verify.ts` around lines 149 - 154, Update verifyW3CDocument’s
W3C lookup-failure and OpenAttestation missing-chainId paths to avoid
unconditionally calling promptNetworkSelection. Prefer the explicit --network
option when provided, and otherwise use a non-interactive fallback that verifies
without a provider in non-TTY environments; retain interactive selection only
when appropriate.

Comment thread src/utils/cli-errors.ts
Comment on lines +100 to +108
const normalizeLabel = (raw: string): string | undefined => {
const cleaned = raw.trim().replace(/\(\)$/, '');
if (!cleaned || UNKNOWN_CUSTOM_ERROR.test(cleaned) || cleaned === '(unknown custom error)') {
return undefined;
}
// Drop leading junk like ": OwnerHolderMustDiffer" or parentheses wrappers
const ident = cleaned.match(/([A-Za-z][A-Za-z0-9_]*)/);
return ident?.[1];
};

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 | 🟠 Major | ⚡ Quick win

Multi-word revert reasons get truncated to their first word.

normalizeLabel extracts an identifier with an unanchored regex (cleaned.match(/([A-Za-z][A-Za-z0-9_]*)/)), returning only the first matched word segment. extractContractRevertLabel calls this directly on err.revert?.name ?? err.errorName (Line 172) and on err.reason (Line 179) and returns as soon as it gets a truthy result, without the isKnownRevertName/SOLIDITY_CUSTOM_ERROR_NAME validation that acceptFailedSuffixLabel applies elsewhere in this file.

For a standard require(condition, "message") revert, ethers' CallExceptionError.reason is the full decoded message and commonly contains spaces (for example "insufficient funds for transfer"). With the current logic:

  • normalizeLabel("insufficient funds for transfer") returns just "insufficient".
  • extractContractRevertLabel returns "insufficient" as the label.
  • describeContractError then emits "Contract reverted with insufficient", discarding the rest of the actual revert message.

This affects any plain-text require revert across every obligation registry/escrow command that routes through getErrorMessage/describeContractError, not just the known custom errors this file targets.

Require the identifier to consume the whole cleaned string before treating it as a label, so free-text reasons fall through to the err.reason fallback in describeContractError (Line 246-248) and surface intact.

🐛 Proposed fix
 const normalizeLabel = (raw: string): string | undefined => {
   const cleaned = raw.trim().replace(/\(\)$/, '');
   if (!cleaned || UNKNOWN_CUSTOM_ERROR.test(cleaned) || cleaned === '(unknown custom error)') {
     return undefined;
   }
-  // Drop leading junk like ": OwnerHolderMustDiffer" or parentheses wrappers
-  const ident = cleaned.match(/([A-Za-z][A-Za-z0-9_]*)/);
-  return ident?.[1];
+  // Drop leading junk like ": OwnerHolderMustDiffer" or parentheses wrappers.
+  // Require the whole remaining string to be a single identifier so plain-text
+  // require() reasons (e.g. "insufficient funds for transfer") are rejected
+  // instead of being truncated to their first word.
+  const ident = cleaned.match(/^:?\s*([A-Za-z][A-Za-z0-9_]*)$/);
+  return ident?.[1];
 };

Also applies to: 172-181

🤖 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/utils/cli-errors.ts` around lines 100 - 108, Update normalizeLabel so it
only returns a label when the entire cleaned value matches the intended
identifier format, rather than extracting the first word from free-text
messages. Preserve the existing cleanup and unknown-error filtering, allowing
multi-word err.reason values handled by extractContractRevertLabel to fall
through and be surfaced intact by describeContractError.

Comment thread src/utils/cli-errors.ts
Comment on lines +158 to +224
export function extractContractRevertLabel(error: unknown): string | undefined {
const err = asEthersError(error);
if (!err) {
if (error instanceof Error) {
const fromExec = error.message.match(/execution reverted:\s*([A-Za-z0-9_]+)/);
if (fromExec?.[1]) {
const label = normalizeLabel(fromExec[1]);
if (label) return label;
}
return labelFromWrappedMessage(error.message);
}
return undefined;
}

const revertName = err.revert?.name ?? err.errorName;
if (typeof revertName === 'string') {
const label = normalizeLabel(revertName);
if (label) return label;
}

if (typeof err.reason === 'string') {
const label = normalizeLabel(err.reason);
if (label) return label;
}

// Prefer selector decoding before shortMessage — gas estimate often only has
// "execution reverted (unknown custom error)" + data: 0x7e288225
const fromData = labelFromRevertData(err);
if (fromData) return fromData;

if (typeof err.shortMessage === 'string' && err.shortMessage.trim()) {
if (!UNKNOWN_CUSTOM_ERROR.test(err.shortMessage)) {
const match = err.shortMessage.match(/execution reverted(?::\s*)?(.+)?/i);
if (match?.[1]) {
const label = normalizeLabel(match[1]);
if (label) return label;
}
}
}

if (typeof err.message === 'string') {
const custom = err.message.match(/execution reverted:\s*([A-Za-z0-9_]+)/);
if (custom?.[1]) {
const label = normalizeLabel(custom[1]);
if (label) return label;
}
const wrapped = labelFromWrappedMessage(err.message);
if (wrapped) return wrapped;
}

return undefined;
}

/** True when the failure is a definitive on-chain revert (not a transient gas/RPC issue). */
export function isContractCallException(error: unknown): boolean {
const err = asEthersError(error);
if (!err) {
if (error instanceof Error && /Pre-check .* failed:/i.test(error.message)) {
return Boolean(extractContractRevertLabel(error));
}
return false;
}
if (err.code === 'CALL_EXCEPTION') return true;
if (err.revert?.name || err.errorName) return true;
if (labelFromRevertData(err)) return true;
return Boolean(extractContractRevertLabel(error));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Dead branch: asEthersError never returns null for Error instances.

asEthersError treats any value with typeof value === 'object' as non-null, and typeof (new Error(...)) === 'object' is always true. As a result, error instanceof Error can only be true in exactly the cases where asEthersError(error) already returned non-null. This makes the if (!err) { if (error instanceof Error) {...} } blocks in extractContractRevertLabel (Lines 161-168) and isContractCallException (Lines 215-217) unreachable for every possible input: any value satisfying instanceof Error also satisfies the "object" type guard, and any value failing instanceof Error also fails that inner check.

Current tests still pass because equivalent logic is duplicated later via the err.message handling (Lines 198-206), but this duplication means the two code paths can silently diverge in the future if one is edited without the other.

Remove the dead branches, or make asEthersError return null for plain Error instances that lack ethers-specific fields so the intended fallback path actually executes.

🤖 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/utils/cli-errors.ts` around lines 158 - 224, Remove the unreachable
error-instance branches from extractContractRevertLabel and
isContractCallException, since asEthersError already returns a non-null object
for every Error. Preserve the existing fallback behavior through the later
err.message handling and the remaining pre-check validation without duplicating
the plain Error path.

Comment thread src/utils/obligation-document.ts Outdated
Comment on lines +42 to +58
if (!obligationRegistry) {
throw new Error('Document does not contain a valid obligationRegistry address');
}
if (!tokenId) {
throw new Error('Document does not contain a valid token ID');
if (!obligationRegistry) {
throw new Error('Document does not contain a valid obligationRegistry address');
}
if (!tokenId) {
throw new Error('Document does not contain a valid token ID');
}
if (!chainId) {
throw new Error('Document does not contain a valid chain ID');
}
if (!SUPPORTED_CHAINS[chainId]) {
throw new Error(`Document references unsupported chain ID: ${chainId}`);
}

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 | 🔴 Critical | ⚡ Quick win

Fix duplicated, unbalanced conditional blocks causing a syntax error.

Lines 45-51 duplicate lines 42-44 and re-open if (!tokenId) without closing the first one. This produces unbalanced braces. The CI pipeline logs confirm an ESLint parsing error "'}' expected" at line 77, which is the direct result of this malformed block.

🐛 Proposed fix to remove the duplicated block
   if (!obligationRegistry) {
     throw new Error('Document does not contain a valid obligationRegistry address');
   }
   if (!tokenId) {
     throw new Error('Document does not contain a valid token ID');
-  if (!obligationRegistry) {
-    throw new Error('Document does not contain a valid obligationRegistry address');
-  }
-  if (!tokenId) {
-    throw new Error('Document does not contain a valid token ID');
   }
   if (!chainId) {
     throw new Error('Document does not contain a valid chain ID');
   }
📝 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 (!obligationRegistry) {
throw new Error('Document does not contain a valid obligationRegistry address');
}
if (!tokenId) {
throw new Error('Document does not contain a valid token ID');
if (!obligationRegistry) {
throw new Error('Document does not contain a valid obligationRegistry address');
}
if (!tokenId) {
throw new Error('Document does not contain a valid token ID');
}
if (!chainId) {
throw new Error('Document does not contain a valid chain ID');
}
if (!SUPPORTED_CHAINS[chainId]) {
throw new Error(`Document references unsupported chain ID: ${chainId}`);
}
if (!obligationRegistry) {
throw new Error('Document does not contain a valid obligationRegistry address');
}
if (!tokenId) {
throw new Error('Document does not contain a valid token ID');
}
if (!chainId) {
throw new Error('Document does not contain a valid chain ID');
}
if (!SUPPORTED_CHAINS[chainId]) {
throw new Error(`Document references unsupported chain ID: ${chainId}`);
}
🤖 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/utils/obligation-document.ts` around lines 42 - 58, Remove the duplicated
obligationRegistry and tokenId validation block in the obligation-document
validation flow, ensuring the remaining if (!tokenId) conditional is properly
closed before the chainId checks. Preserve the existing validation order and
error messages for obligationRegistry, tokenId, chainId, and SUPPORTED_CHAINS.

Source: Pipeline failures

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