Skip to content

[sdk]: emit standard eth_signTypedData_v4 payload for userop signing - #1134

Merged
seunlanlege merged 4 commits into
mainfrom
roy/mpc-userop-typed-data
Aug 14, 2026
Merged

seunlanlege merged 4 commits into
mainfrom
roy/mpc-userop-typed-data

Conversation

@royvardhan

Copy link
Copy Markdown
Collaborator

packedUserOpTypedData omitted EIP712Domain from types and passed chainId as a bigint. Local signers hash this fine, but backends that hash the typed data server-side from JSON do not: viem's serializeTypedData strips the domain to {} when types.EIP712Domain is absent (Turnkey path), and MPC Vault builds an empty domain separator, so the signature recovers to the wrong address and userops fail ERC-4337 validation with AA24 "invalid account signature".

Emit the canonical v4 payload instead: EIP712Domain listed in types, chainId as a number. The digest is unchanged for local signers (hashTypedData parity with computeUserOpHash is covered by the existing tests).

Comment thread sdk/packages/simplex/package.json Outdated
@royvardhan
royvardhan force-pushed the roy/mpc-userop-typed-data branch from abe3884 to 03258d3 Compare August 14, 2026 14:42
@Wizdave97

Copy link
Copy Markdown
Member

Also revert this commit c96c995bf141a548f983b4165f8c8f890f1f7403 when you merge main into your branch

The length-only assertions let MPCVault's typed-data endpoint ship
signatures that verify against nothing (#1132). Every signing test now
recovers the signer address; the typed-data case uses the canonical v4
payload shape that packedUserOpTypedData emits, so it verifies the fix
in this branch directly.
…enerics

viem derives the domain's type from types.EIP712Domain, so the entries
must be string literals (as const) and the numeric chainId needs a cast
to bigint — viem's uint256 mapping wants bigint at the type level while
its runtime accepts the number the canonical v4 JSON requires. Fixes the
indexer and sdk test builds without changing the emitted payload.
@seunlanlege

Copy link
Copy Markdown
Member

Empirically validated against a live MPCVault vault: the canonical payload (EIP712Domain in types) makes TYPE_SIGN_TYPED_DATA sign the correct EIP-712 digest — recovery-asserted via pnpm test:mpcvault on this branch and an isolated A/B probe (results in #1132 (comment) — see issue). The missing types.EIP712Domain alone was the trigger; string-vs-numeric chainId had no effect, though emitting a number is still the more canonical shape.

@seunlanlege

Copy link
Copy Markdown
Member

Tested & working

@seunlanlege
seunlanlege merged commit 0009b75 into main Aug 14, 2026
2 checks passed
@seunlanlege
seunlanlege deleted the roy/mpc-userop-typed-data branch August 14, 2026 16:13
seunlanlege added a commit that referenced this pull request Aug 15, 2026
…ners

Rebase integration. Main's #1136 batches every phantom bid registered in one
Hyperbridge block into a single extrinsic — one bid per chain used to mean one
extrinsic per chain, serialised on the account nonce, so the last chain's bid
trailed the first by many blocks against a bid window measured in tens. This
branch had meanwhile moved phantom consumption into the shared
HyperbridgeScanner. The two now compose:

  - the scanner delivers per-block batches (`onPhantomOrders`), because the
    batch boundary is load-bearing — flattening it to per-order events would
    rebuild the nonce queue main just removed
  - the filler keeps main's preparePhantomBid/handlePhantomOrders pair
    unchanged; quoting stays concurrent per chain, submission is one extrinsic
  - BalanceProvider takes main's shape — it reuses the filler's Hyperbridge
    connection instead of dialling a second socket to the same node — merged
    with this branch's per-instance logger and the no-timer-after-stop guard
  - IntentFiller regains the hyperbridgeConnection getter boot now feeds it

The sdk-side hardening (#1136's HTTP fallback and batch submission, #1134's
typed-data payloads) flows through untouched: the scanner calls the same
pollPhantomOrders and the filler the same submitPhantomBids.
seunlanlege added a commit that referenced this pull request Aug 18, 2026
Points the MPCVault integration test at what actually ships: the three
operations through `mpcVaultSigner`, each checked by recovery, plus the set-code
branch asserting a 0x04 envelope. 10/10 against a live vault on chain 1.

Deletes `createMpcVaultAccount`. With the interface no longer carrying a viem
account and `accountFor` building one from any Signer, nothing called it — the
test was the only caller, exercising a factory the solver no longer runs. Its
7702 branch already moved to `mpcVaultSigner.signTransaction` and its
`signMessage` throw to `accountFor`. An MPC-backed viem account, if anything
wants one, is `accountFor(mpcVaultSigner(config))`.

The first live run failed one case and the cause was the test's own payload: it
omitted `types.EIP712Domain`. viem ignores that entry when hashing locally, but
MPCVault hashes server-side from the JSON and derives the domain type from it,
so the vault signed a digest recovery never checked — the #1134 failure, exactly.
The constraint is now on `TypedDataPayload`'s doc comment, where someone
building a payload will read it, instead of only in the sdk builder that already
knew.
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.

3 participants