Skip to content

fix(safe): return per-op SafeOp hash for length=1 multi-chain helper#158

Merged
Sednaoui merged 2 commits into
devfrom
fix/multichain-length1-hash
May 2, 2026
Merged

fix(safe): return per-op SafeOp hash for length=1 multi-chain helper#158
Sednaoui merged 2 commits into
devfrom
fix/multichain-length1-hash

Conversation

@Sednaoui
Copy link
Copy Markdown
Member

@Sednaoui Sednaoui commented May 1, 2026

Summary by CodeRabbit

  • Improvements

    • Single-operation bundles now produce the direct per-operation signature digest for faster handling.
    • Multi-operation bundles continue to use the Merkle-style wrapper hash.
    • Added optional entrypoint override for hashing to ensure correct verifying contract is used.
  • Bug Fixes / Validation

    • Strengthened input validation: empty inputs are rejected; helpers enforce single vs. multi-op requirements with clear errors.
  • Tests

    • Added tests covering single-op, multi-op, and invalid-input behaviors.

getMultiChainSingleSignatureUserOperationsEip712Hash now returns the
per-UserOperation SafeOp digest when called with a single op, matching
the on-chain Safe4337MultiChainSignatureModule's merkleTreeDepth == 0
branch (which verifies against keccak256(SafeOp), not the Merkle wrapper).
The companion typed-data helper now throws for length<2.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

📝 Walkthrough

Walkthrough

getMultiChainSingleSignatureUserOperationsEip712Hash now accepts an optional entrypointAddress, throws on empty input, returns a per-UserOperation SafeOp EIP‑712 hash when given exactly one op, and preserves the Merkle-wrapper typed-data path for two-or-more ops. getMultiChainSingleSignatureUserOperationsEip712Data now rejects single-op input.

Changes

Cohort / File(s) Summary
Multi-chain EIP-712 hashing logic
src/account/Safe/SafeMultiChainSigAccount.ts
Added entrypointAddress?: string to overrides, enforce userOperationsToSign.length >= 1 (throw on 0), return per-op EIP‑712 hash via SafeAccount.getUserOperationEip712Hash_V9 when length === 1, and require >= 2 in getMultiChainSingleSignatureUserOperationsEip712Data (throw with guidance to use single-op helpers and pass overrides.safe4337ModuleAddress).
Multi-chain single-signature helper tests
test/signer/signer.test.js
Added tests: empty input throws, single-op returns per-op SafeOp EIP‑712 digest, two-op returns Merkle-wrapper EIP‑712 hash (different from per-op), and getMultiChainSingleSignatureUserOperationsEip712Data throws for single-op input.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • sherifahmed990

Poem

🐰 A twitch, a hop, a clever tweak—
One op now hashes on its peak.
Two or more, the Merkle sings,
Tests confirm the new path springs.
🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: returning the per-op SafeOp hash for single-operation input (length=1) in the multi-chain helper method.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

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


Review rate limit: 3/5 reviews remaining, refill in 16 minutes and 40 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/account/Safe/SafeMultiChainSigAccount.ts (1)

771-795: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Point single-op callers to SafeMultiChainSigAccountV1 helpers instead.

The new guidance currently recommends SafeAccountV0_3_0.getUserOperationEip712Data / getUserOperationEip712Hash_V9, but those helpers do not default to SafeMultiChainSigAccountV1.DEFAULT_SAFE_4337_MODULE_ADDRESS. If callers follow this message literally without adding an override, they'll hash the wrong verifying contract and produce invalid signatures for this account class.

Suggested wording update
- * SafeOp digest, not a Merkle wrapper, so the wrapper typed data would be
- * misleading. Use {`@link` SafeAccountV0_3_0.getUserOperationEip712Data}
- * (or {`@link` getUserOperationEip712Hash_V9}) for single-op signing.
+ * SafeOp digest, not a Merkle wrapper, so the wrapper typed data would be
+ * misleading. Use {`@link` SafeMultiChainSigAccountV1.getUserOperationEip712Data}
+ * or {`@link` SafeMultiChainSigAccountV1.getUserOperationEip712Hash} for
+ * single-op signing.
...
-				"getMultiChainSingleSignatureUserOperationsEip712Data requires >= 2 userOperations. " +
-					"For a single UserOperation, use SafeAccount.getUserOperationEip712Data_V9 / getUserOperationEip712Hash_V9: " +
+				"getMultiChainSingleSignatureUserOperationsEip712Data requires >= 2 userOperations. " +
+					"For a single UserOperation, use SafeMultiChainSigAccountV1.getUserOperationEip712Data / " +
+					"SafeMultiChainSigAccountV1.getUserOperationEip712Hash: " +
 					"the on-chain depth=0 path verifies against the per-op SafeOp digest, not a Merkle wrapper.",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/account/Safe/SafeMultiChainSigAccount.ts` around lines 771 - 795, The
error message thrown in
SafeMultiChainSigAccount.getMultiChainSingleSignatureUserOperationsEip712Data
incorrectly directs single-op users to SafeAccountV0_3_0 helpers which do not
default to SafeMultiChainSigAccountV1.DEFAULT_SAFE_4337_MODULE_ADDRESS; update
the RangeError text thrown in
getMultiChainSingleSignatureUserOperationsEip712Data to point callers to the
SafeMultiChainSigAccountV1 helper methods (e.g.,
SafeMultiChainSigAccountV1.getUserOperationEip712Data and
SafeMultiChainSigAccountV1.getUserOperationEip712Hash_V9) and mention that those
helpers use DEFAULT_SAFE_4337_MODULE_ADDRESS (or that an
overrides.safe4337ModuleAddress must be supplied) so signatures hash the correct
verifying contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/account/Safe/SafeMultiChainSigAccount.ts`:
- Around line 771-795: The error message thrown in
SafeMultiChainSigAccount.getMultiChainSingleSignatureUserOperationsEip712Data
incorrectly directs single-op users to SafeAccountV0_3_0 helpers which do not
default to SafeMultiChainSigAccountV1.DEFAULT_SAFE_4337_MODULE_ADDRESS; update
the RangeError text thrown in
getMultiChainSingleSignatureUserOperationsEip712Data to point callers to the
SafeMultiChainSigAccountV1 helper methods (e.g.,
SafeMultiChainSigAccountV1.getUserOperationEip712Data and
SafeMultiChainSigAccountV1.getUserOperationEip712Hash_V9) and mention that those
helpers use DEFAULT_SAFE_4337_MODULE_ADDRESS (or that an
overrides.safe4337ModuleAddress must be supplied) so signatures hash the correct
verifying contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 85697116-4169-4ee8-9b03-871824bab0ab

📥 Commits

Reviewing files that changed from the base of the PR and between f3d333a and 8318486.

📒 Files selected for processing (2)
  • src/account/Safe/SafeMultiChainSigAccount.ts
  • test/signer/signer.test.js

…ect module default

The previous message directed callers to SafeAccount.getUserOperationEip712Data_V9
and getUserOperationEip712Hash_V9, which default safe4337ModuleAddress to the
parent's module address rather than the multi-chain module. Following the message
verbatim would hash against the wrong verifying contract. Point at
SafeMultiChainSigAccountV1.getUserOperationEip712Data and getUserOperationEip712Hash
instead, and document the override requirement when using parent helpers directly.
@Sednaoui Sednaoui requested a review from sherifahmed990 May 1, 2026 15:13
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/account/Safe/SafeMultiChainSigAccount.ts (1)

749-758: ⚡ Quick win

Reuse the class-level hash helper here.

The length=1 branch already has the inputs that SafeMultiChainSigAccountV1.getUserOperationEip712Hash() normalizes for you. Calling that wrapper instead of SafeAccount.getUserOperationEip712Hash_V9() keeps the Safe 4337 module / entrypoint defaults centralized and avoids this branch drifting if the multi-chain defaults change again.

♻️ Proposed fix
-            return SafeAccount.getUserOperationEip712Hash_V9(u.userOperation, u.chainId, {
-                validAfter: u.validAfter,
-                validUntil: u.validUntil,
-                safe4337ModuleAddress:
-                    overrides.safe4337ModuleAddress ??
-                    SafeMultiChainSigAccountV1.DEFAULT_SAFE_4337_MODULE_ADDRESS,
-                entrypointAddress: overrides.entrypointAddress,
-            });
+            return SafeMultiChainSigAccountV1.getUserOperationEip712Hash(u.userOperation, u.chainId, {
+                validAfter: u.validAfter,
+                validUntil: u.validUntil,
+                safe4337ModuleAddress: overrides.safe4337ModuleAddress,
+                entrypointAddress: overrides.entrypointAddress,
+            });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/account/Safe/SafeMultiChainSigAccount.ts` around lines 749 - 758, The
single-item branch directly calls SafeAccount.getUserOperationEip712Hash_V9;
instead, reuse the class-level helper
SafeMultiChainSigAccountV1.getUserOperationEip712Hash so the module/entrypoint
defaults stay centralized. Replace the call in the length===1 branch (where u =
userOperationsToSignsToSign[0]) with
SafeMultiChainSigAccountV1.getUserOperationEip712Hash(u.userOperation,
u.chainId, { validAfter: u.validAfter, validUntil: u.validUntil,
safe4337ModuleAddress: overrides.safe4337ModuleAddress, entrypointAddress:
overrides.entrypointAddress }) so the same normalization and defaulting logic is
used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/account/Safe/SafeMultiChainSigAccount.ts`:
- Around line 749-758: The single-item branch directly calls
SafeAccount.getUserOperationEip712Hash_V9; instead, reuse the class-level helper
SafeMultiChainSigAccountV1.getUserOperationEip712Hash so the module/entrypoint
defaults stay centralized. Replace the call in the length===1 branch (where u =
userOperationsToSignsToSign[0]) with
SafeMultiChainSigAccountV1.getUserOperationEip712Hash(u.userOperation,
u.chainId, { validAfter: u.validAfter, validUntil: u.validUntil,
safe4337ModuleAddress: overrides.safe4337ModuleAddress, entrypointAddress:
overrides.entrypointAddress }) so the same normalization and defaulting logic is
used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 86163b59-e856-4695-b3d7-66645c3f1fb6

📥 Commits

Reviewing files that changed from the base of the PR and between 8318486 and f6cb798.

📒 Files selected for processing (1)
  • src/account/Safe/SafeMultiChainSigAccount.ts

Copy link
Copy Markdown
Member

@sherifahmed990 sherifahmed990 left a comment

Choose a reason for hiding this comment

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

LGTM

@Sednaoui Sednaoui merged commit 1fff4be into dev May 2, 2026
2 checks passed
@Sednaoui Sednaoui deleted the fix/multichain-length1-hash branch May 2, 2026 12:30
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.

2 participants