Skip to content

feat: humanize Solidity contract errors from auto-generated selector map#1922

Open
Tranquil-Flow wants to merge 11 commits into
devfrom
feat/contract-error-humanization-v2
Open

feat: humanize Solidity contract errors from auto-generated selector map#1922
Tranquil-Flow wants to merge 11 commits into
devfrom
feat/contract-error-humanization-v2

Conversation

@Tranquil-Flow

@Tranquil-Flow Tranquil-Flow commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Closes SELF-1369

Summary

  • Enhances contracts/scripts/findErrorSelectors.ts to emit a deduplicated selector → name map to new-common/src/data/error-selector-map.json alongside the existing detailed output
  • Adds humanizeContractError(raw) to new-common/src/blockchain/ — decodes Error(string), Panic(uint256), and custom contract errors into readable strings
  • Wires humanizeContractError into both error display paths: provingUtils.ts in webview-app and ProofRequestStatusScreen.tsx in the Self Wallet app
  • Gitignores contracts/error-selectors.json (transient script output)
  • Adds @selfxyz/new-common as a dependency to app/ and packages/webview-app/

How it works

The selector map stays in sync with contracts automatically — run yarn find:error in contracts/ after adding new custom errors. humanizeContractError is exported from @selfxyz/new-common/src/blockchain and applied at both failure reason display sites so users see readable messages instead of raw hex selectors.

Integration

Both consuming packages are updated in this PR:

  • webview-appprovingUtils.ts: getFailureState and normalizeError now pass raw error strings through humanizeContractError before surfacing them to the UI
  • Self Wallet appProofRequestStatusScreen.tsx: the failure reason rendered to the user is now decoded via humanizeContractError

Test plan

  • cd new-common && yarn test src/blockchain/contractErrors.test.ts — 12 tests pass
  • cd new-common && yarn types — no errors
  • cd packages/webview-app && yarn types — no errors
  • yarn workspace @selfxyz/mobile-app types — no errors
  • cd contracts && yarn find:error — regenerates error-selector-map.json correctly

Supersedes

Closes #1911

Summary by CodeRabbit

  • New Features

    • Contract error messages are now humanized and displayed in a user-friendly format across verification and tunnel screens instead of raw technical codes.
  • Tests

    • Added test coverage for error message humanization and normalization logic.

@vercel

vercel Bot commented Apr 4, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
self-webview-app Ignored Ignored Preview Apr 20, 2026 0:21am

Request Review

@coderabbitai

coderabbitai Bot commented Apr 4, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request adds decoding of custom Solidity error selectors into human-readable messages. A new utility function humanizeContractError is introduced in the new-common package, exported and integrated across the webview app and mobile app error handling paths. Static error mapping is deleted, and build configurations are updated to compile and distribute the new blockchain module.

Changes

Cohort / File(s) Summary
Core error handling infrastructure
new-common/src/blockchain/contractErrors.test.ts, new-common/src/blockchain/index.ts
New test suite for humanizeContractError function with custom error selector test case; re-export of function from blockchain module index.
Package configuration
new-common/package.json, new-common/tsup.config.ts
Added ./src/blockchain subpath export mapping with ESM, CJS, and TypeScript declaration outputs; added tsup entry point for blockchain contractErrors module.
Workspace dependencies
app/package.json, packages/webview-app/package.json
Added @selfxyz/new-common as workspace-linked dependency to both app packages.
Error handling integration
packages/webview-app/src/utils/provingUtils.ts, app/src/screens/verification/ProofRequestStatusScreen.tsx
Updated getFailureState and normalizeError to apply humanizeContractError to error messages; updated failure UI to display humanized error reason.
Tunnel screen updates
packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx
Refactored to normalize errors via normalizeError utility and use normalized message in failure analytics, result reporting, and UI display.
Test coverage
packages/webview-app/src/utils/provingUtils.test.ts, packages/webview-app/tests/screens/tunnel/tunnelFlowScreens.test.tsx
Extended getFailureState and normalizeError tests with selector humanization cases; updated tunnel flow tests to verify humanized error display and adjusted failure screen lifecycle assertions.
Build and deployment
packages/webview-app/vercel.json, contracts/.gitignore
Updated Vercel build command to include @selfxyz/new-common build step; added error-selectors.json to gitignore.
Removed artifact
contracts/error-selectors.json
Deleted static error selector metadata file (513 lines).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.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
Title check ✅ Passed The title accurately summarizes the main change: adding error humanization functionality for Solidity contract errors via an auto-generated selector map.
Description check ✅ Passed The description provides a clear summary of changes, explains how the feature works, describes integration points, and includes a comprehensive test plan checklist.
Linked Issues check ✅ Passed The PR implements the core requirements from #1911: static error selector map, humanizeContractError function, integration into error display paths (provingUtils and ProofRequestStatusScreen), and unit tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing error humanization: new contractErrors module, build configuration updates, dependency additions, and integration into the two specified error display locations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/contract-error-humanization-v2

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 and usage tips.

…r display

Adds @selfxyz/new-common as a dependency to packages/webview-app and app/. Applies humanizeContractError in provingUtils.ts (getFailureState + normalizeError) and ProofRequestStatusScreen.tsx at the failure reason display site. Adds a named ./src/blockchain export to new-common/package.json so the import path resolves cleanly without an explicit /index suffix. Also adds src/blockchain/contractErrors as an explicit tsup entry.

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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 74c919bc-a41f-4d71-82c8-ce45a2b9dba4

📥 Commits

Reviewing files that changed from the base of the PR and between 9820397 and c582df9.

📒 Files selected for processing (5)
  • contracts/error-selectors.json
  • error-selectors.json
  • packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx
  • packages/webview-app/src/utils/provingUtils.test.ts
  • packages/webview-app/tests/screens/tunnel/tunnelFlowScreens.test.tsx
💤 Files with no reviewable changes (2)
  • error-selectors.json
  • contracts/error-selectors.json

Comment thread packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx Outdated
The webview-app now imports humanizeContractError from @selfxyz/new-common, but the Vercel buildCommand did not include building that package. This caused the deployment to fail with a missing export error.
…ents

normalizeError() creates a new object every render, so using it as an effect dependency caused tunnel_result_failure analytics to fire repeatedly. Use the stable message string instead.
@Tranquil-Flow

Copy link
Copy Markdown
Contributor Author

@greptileai review

@Tranquil-Flow Tranquil-Flow changed the title feat(new-common): humanize Solidity contract errors from auto-generated selector map feat: humanize Solidity contract errors from auto-generated selector map Apr 16, 2026
@greptile-apps

greptile-apps Bot commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds contract error humanization to both the Self Wallet app and the WebView SDK — replacing raw 4-byte hex selectors shown to users with readable strings. It introduces humanizeContractError in @selfxyz/new-common, driven by a committed error-selector-map.json regenerated by an updated findErrorSelectors.ts script.

Key changes:

  • new-common/src/blockchain/contractErrors.ts: new humanizeContractError function decoding Error(string), Panic(uint256), and custom-error selectors via a static JSON map
  • new-common/src/data/error-selector-map.json: committed deduplicated selector → name map (regenerated by running yarn find:error in contracts/)
  • contracts/scripts/findErrorSelectors.ts: now emits a second output file — the deduped selector map at ../new-common/src/data/error-selector-map.json
  • packages/webview-app/src/utils/provingUtils.ts: getFailureState and normalizeError both pass error strings through humanizeContractError
  • app/src/screens/verification/ProofRequestStatusScreen.tsx: failure reason rendered in the Info component is decoded via humanizeContractError
  • packages/webview-app/vercel.json: build command prepends @selfxyz/new-common build to the chain
  • Both app/package.json and packages/webview-app/package.json now list @selfxyz/new-common as a direct dependency

Confidence Score: 5/5

Safe to merge — all decode paths are correct, fallbacks are in place, and both consumer integration points have passing test coverage.

Core logic in humanizeContractError is correct for all three error types (Error(string), Panic(uint256), custom selector) with a safe pass-through for unknown input. Tests are comprehensive (12 unit tests + integration tests in provingUtils and tunnel flow). Package dependencies, build chain, and export paths are all properly configured. The two P2 observations (single-word SCREAMING name gap in formatErrorName, CWD assumption in the script) don't affect current correctness and don't block merge.

new-common/src/blockchain/contractErrors.ts — minor formatErrorName edge case; contracts/scripts/findErrorSelectors.ts — CWD path assumption.

Important Files Changed

Filename Overview
new-common/src/blockchain/contractErrors.ts Core humanization logic — handles Error(string), Panic(uint256), and custom selectors correctly; minor edge case in formatErrorName for single-word ALL_CAPS names without underscores
new-common/src/blockchain/contractErrors.test.ts Comprehensive 12-test suite covering all decode paths, edge cases (empty input, unknown selector, mixed case, ABI-encoded params), and SCREAMING_CASE formatting
packages/webview-app/src/utils/provingUtils.ts Cleanly wraps both getFailureState and normalizeError with humanizeContractError; BridgeError message is required string so no null-safety concern
app/src/screens/verification/ProofRequestStatusScreen.tsx humanizeContractError applied once at render time on the raw reason from store; no double-humanization; TypeScript narrowing after && reason check is correct
contracts/scripts/findErrorSelectors.ts Correctly writes deduped selector map to ../new-common/src/data/error-selector-map.json relative to CWD; assumes script is always run from the contracts/ directory — no validation of this assumption
new-common/src/data/error-selector-map.json Committed selector-to-name map; kept in sync manually by running yarn find:error; selectors are lowercase 0x-prefixed as expected by the decoder
packages/webview-app/vercel.json Build chain updated to prepend new-common build — ensures the selector map and contractErrors module are compiled before webview-app
packages/webview-app/src/utils/provingUtils.test.ts normalizeError and getFailureState tests updated with humanization assertions; covers string wrapping, BridgeError spread, and selector decoding

Sequence Diagram

sequenceDiagram
    participant Contract as Solidity Contract
    participant Script as findErrorSelectors.ts
    participant Map as error-selector-map.json
    participant Lib as contractErrors.ts
    participant WebView as provingUtils.ts (webview-app)
    participant App as ProofRequestStatusScreen (mobile-app)
    participant User as User

    Note over Script,Map: Build-time (run yarn find:error)
    Script->>Contract: Scan .sol files for custom error declarations
    Script->>Script: Compute keccak256 selectors
    Script->>Map: Write deduplicated selector→name map

    Note over Lib,User: Runtime (proof failure)
    Contract-->>WebView: Revert data (0x selector + params)
    WebView->>Lib: humanizeContractError(raw)
    alt Error(string) 0x08c379a0
        Lib->>Lib: AbiCoder.decode string data
        Lib-->>WebView: Decoded revert message
    else Panic(uint256) 0x4e487b71
        Lib->>Lib: AbiCoder.decode uint256 code
        Lib->>Lib: PANIC_CODES lookup
        Lib-->>WebView: Panic description
    else Known custom error selector
        Lib->>Map: Lookup selector
        Lib->>Lib: formatErrorName(name)
        Lib-->>WebView: Humanized error name
    else Unknown or non-hex
        Lib-->>WebView: raw unchanged
    end
    WebView-->>User: Readable failure message

    Contract-->>App: reason (raw selector)
    App->>Lib: humanizeContractError(reason)
    Lib-->>App: Humanized string
    App-->>User: Readable failure reason
Loading

Comments Outside Diff (1)

  1. new-common/src/blockchain/contractErrors.ts, line 22-28 (link)

    P2 formatErrorName silent no-op for single-word SCREAMING names

    The SCREAMING_CASE branch fires only when name.includes('_'). A single-word all-caps error name like "INVALID" skips that branch and falls through to the camelCase regex, which also leaves it unchanged (no lowercase-to-uppercase transitions to insert spaces before).

    Result: formatErrorName("INVALID") returns "INVALID" instead of "Invalid". The current map contains no single-word SCREAMING names, so this is safe today, but if a new error like error INVALID() is added to a contract, it will surface unchanged to the user.

    Consider adding a fallback for all-caps single-word names:

Reviews (1): Last reviewed commit: "fix: stabilize useEffect dependency to p..." | Re-trigger Greptile

Comment thread contracts/scripts/findErrorSelectors.ts Outdated
Comment on lines +175 to +180
const mapOutputFile = path.resolve(
process.cwd(),
"../new-common/src/data/error-selector-map.json",
);
writeFileSync(mapOutputFile, JSON.stringify(selectorMap, null, 2));
console.log(`💾 Selector map saved to ${mapOutputFile}`);

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.

P2 Hardcoded relative path assumes CWD is always contracts/

process.cwd() is used as the anchor, which works correctly when yarn find:error is run from inside the contracts/ workspace as documented. However, if someone runs the script from the monorepo root (e.g., node contracts/scripts/findErrorSelectors.ts), process.cwd() resolves to the repo root and ../new-common/src/data/error-selector-map.json points outside the repo.

Since this is a developer tool rather than production code the risk is low, but a __dirname-relative path would be more robust:

Suggested change
const mapOutputFile = path.resolve(
process.cwd(),
"../new-common/src/data/error-selector-map.json",
);
writeFileSync(mapOutputFile, JSON.stringify(selectorMap, null, 2));
console.log(`💾 Selector map saved to ${mapOutputFile}`);
const mapOutputFile = path.resolve(
new URL('.', import.meta.url).pathname,
'../../..',
'new-common/src/data/error-selector-map.json',
);

Alternatively, add an explicit guard at the top of the script verifying that the CWD ends with /contracts.

Resolves conflicts from 53 commits of dev divergence. Take dev's findErrorSelectors.ts which adds collision detection, a check mode, and repo-root path resolution. Regenerate error-selectors.json and new-common/src/data/error-selector-map.json from the current contracts so the check-error-selectors workflow passes.

Restore new-common/src/blockchain/contractErrors.ts and keep contractErrors.test.ts. These were removed from dev as a temporary cleanup ahead of this PR landing the humanizer. Remove the stale contracts/error-selectors.json.

Merge TunnelResultScreen with both the normalizeError humanization from this branch and the isDemoMode onContinue branch from dev. Extend the TunnelResultState source union to include kyc. Combine vercel.json so the build chain still includes new-common while preserving dev's ignoreCommand and adding new-common to the ignored paths.

Update stale tunnelFlowScreens close-navigation tests to assert lifecycle.setResult plus dismiss semantics and keep the humanizer assertion. Update the receipt-from-success-context test to assert close-only controls.
webview-app now imports humanizeContractError from @selfxyz/new-common/src/blockchain, so the dependency must be built before typecheck and build. The webview-app-ci workflow was building common, mobile-sdk-alpha, and webview-bridge but not new-common, causing TS2307 Cannot find module errors in build and types jobs.

Add yarn workspace @selfxyz/new-common build as the first dep-build step in the build, lint, and types jobs.
yarn types at repo root runs each workspace's types script in topological dev order, but those scripts only run tsc noEmit. webview-app now consumes @selfxyz/new-common at type-resolution time, so new-common needs a prior build to emit its dist/esm/src/blockchain declarations. Add an explicit yarn workspace @selfxyz/new-common build before yarn types in the type-check job.

Bump the Android and iOS bundle thresholds from 48 to 49 MB. Bundling humanizeContractError and the 140-entry error-selector-map.json into the mobile-app bundle adds roughly 86 KB, which pushed the Android bundle past the previous ceiling.
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