Following up on ERC-7803, I’ve drafted ERC-7964: Universal Cross-Chain Signatures for Account Abstraction - a simple extension that enables cross-chain signatures using chainId: 0.
Concept
One rule: chainId: 0 in signing domains = valid on any chain
This allows users to sign once and authorize operations across multiple networks - perfect for cross-chain intents, multi-chain DAO voting, and unified account management.
Hey @codebyMoh! Thanks for the the thoughtful feedback!
On scope: You’re absolutely right about the complexity with SCAs. This ERC intentionally focuses on being a simple building block rather than solving cross-chain authentication holistically. The chainId: 0 pattern gives us universal signature validity, but the verification semantics are left to the implementation layer.
On your specific concerns:
Code differences: This feels like wallet/dapp responsibility to me. Wallets should warn users when signing for contracts with different bytecode across chains, and dapps should validate contract compatibility before accepting signatures. The standard can’t enforce this on already-deployed implementations, but we should definitely call it out in Security Considerations.
State divergence: Similar reasoning - this is where higher-level coordination mechanisms come into play. Things like keystore rollups or dedicated state sync protocols could address this, but probably warrant their own ERCs.
On “unsafe” signatures: I’d love to hear your definition here. In my view, a signature is “unsafe” when it authorizes unintended actions. But with proper wallet UX (showing exactly what’s being signed on each chain) and dapp validation (checking contract compatibility), the risk becomes manageable.
On your solutions: The bytecode checksum idea is elegant! But I lean toward leaving these patterns to implementations rather than standardizing them here. Different use cases might want different safety mechanisms - some might prefer your checksum approach, others might want state synchronization, etc.
Hope this framing makes sense! Curious to hear your thoughts
@ernestognw Your framing makes a lot of sense — especially treating this as a foundational primitive rather than a full authentication framework. I agree that universal signature validity is a powerful enabler, and implementation-level flexibility is key.
On “unsafe” signatures:
Yes, totally agree with your definition — signatures are “unsafe” when they authorize unintended actions. I think the risk amplifies in scenarios where contracts diverge in state or logic but remain address-aligned across chains. So UX + validation are critical — maybe the standard could recommend (non-mandate) checksum practices or at least link to best practices in Security Considerations?
On state divergence:
Right, it’s out of this ERC’s scope, but might be worth noting that coordination layers (e.g., keystore rollups, state pinning, chain-specific guards) become almost essential in high-stakes use cases like governance or treasury control.
On modular safety:
Totally fair to defer specific safety patterns to use case–specific implementations. Maybe we could treat the chainId: 0 concept as an interface layer and publish optional “safety modules” alongside it — like a checksum utility, signer attestations, or bytecode validation hooks?
All in all, loving the direction — this could become a key building block for cross-chain AA architecture. Happy to brainstorm or co-contribute to supporting standards or tooling around it if useful.
maybe the standard could recommend (non-mandate) checksum practices or at least link to best practices in Security Considerations?
I’m hesitant to recommend unproven patterns, and I’m not sure if I would recommend this approach. I think it’s preferable to see real-world usage to inform safety practices rather than speculating upfront.
but might be worth noting that coordination layers (e.g., keystore rollups, state pinning, chain-specific guards) become almost essential in high-stakes use cases like governance or treasury control.
Absolutely agree these become essential for high-stakes use cases. However, I see this as a natural architectural consequence rather than something this ERC should prescribe. Imo, different use cases will have vastly different coordination requirements.
publish optional “safety modules” alongside it — like a checksum utility, signer attestations, or bytecode validation hooks?
While I appreciate the modular thinking, I’m concerned about scope creep. The strength of chainId: 0 is its simplicity. I’d rather see future ERCs build specialized safety frameworks on this foundation if demand emerges. Also happy to provide feedback if such standards appear
Hey everyone! Thanks for the feedback so far, especially @frangio’s point about omitting chainId instead of using 0. I’ve substantially reworked ERC-7964 based on this and other considerations.
Key Changes
1. Omit chainId instead of using 0
Following @frangio’s suggestion, the ERC now simply omits chainId from the root EIP712Domain since all fields are optional per EIP-712. Much cleaner semantics!
2. Dropped ERC-7803 dependency
The proposal now uses only standard EIP-712 encoding - no draft standards required. This means existing wallets can support it immediately without implementing custom logic.
3. Array-based encoding over Merkle trees
Cross-chain operations are encoded as standard EIP-712 arrays. While proposals like EIL use Merkle trees, I’ve opted for arrays because:
For 2-5 chains (typical case), Merkle trees save only 32-64 bytes
Arrays provide full transparency in standard wallet UIs - users see all operations
Merkle trees require custom wallet logic to verify leaves aren’t malicious
4. Introduced EIP712ChainDomain pattern
Each chain-specific operation includes a nested domain with chainId and optionally verifyingContract. This handles both:
Same address deployments (CREATE2) → root domain has verifyingContract
Different address deployments → nested domains have verifyingContract
Updated Examples
All four reference implementations now demonstrate both deployment patterns and include reference on-chain verification code.
Would love feedback on this simplified approach! Does the array encoding trade-off (transparency vs. minimal extra overhead) make sense for the typical 2-5 chain use case?
@fmc Thanks for the SmartSessions pointer. Good prior art and useful confirmation that the core idea holds up in production: omit chainId from the EIP-712 domain, push it into an array of per-chain entries, and use an index to point each chain at its own slot. MultiChainSession { ChainSession[] sessionsAndChainIds } with chainDigestIndex is essentially the same shape as SetValue { ChainOperation[] operations } with structIndex.
The places where ERC-7964 tries to generalize beyond what SmartSessions encodes:
Generic signature envelope, not session-specific. SmartSessions defines the encoding for the ERC-7579 enable flow. ERC-7964 specifies a standard envelope (bytes32 header, bytes32[] structsArray, bytes crossChainSignature) with a 9-byte magic prefix 0x796479647964796479 so a contract that normally calls isValidSignatureNow can detect a cross-chain signature inside the same code path. The goal is reuse by any application doing EIP-712, not just sessions.
Per-chain verifyingContract in each entry.EIP712ChainDomain { uint256 chainId, address verifyingContract } lives inside each ChainOperation, so applications deployed at different addresses across chains stay covered without needing a deterministic deploy.
ERC-5267 indirection for the domain. The header carries an application address pointing at an ERC-5267 source on the current chain, so the verifier reconstructs the domain separator on-chain without trusting the signature blob to carry it.
So 7964 is less “new mechanism” and more “lift the pattern SmartSessions has been using into a standard signature shape that any app can opt into.” Curious whether anything in the SmartSessions experience would push back on the envelope choice (single 32-byte header + ABI-encoded tail).
Separately, here’s an interactive demo that may help readers see the mechanics in motion.
The demo targets three testnets (Sepolia, Arbitrum Sepolia, Base Sepolia) and a minimal CrossChainAppMock that exposes a single setValue(address account, uint256 value, bytes signature) entry point. You pick a value, sign once, and then submit on each chain.
The interesting bits to look at:
Sign tab, typed-data preview. The wallet receives a domain with only name + version. No chainId. The per-chain chainIds sit inside each ChainOperation instead, so the signer reviews and authorizes the concrete set of target chains in the EIP-712 popup. Any wallet that already renders EIP-712 typed data shows this correctly. No custom wallet support needed.
Submit tab, header byte strip. Each chain card shows the 32-byte header as a colored strip: gray magic (9B) and blue fields (1B) are identical across chains; red structIndex (2B) and purple application (20B) differ per chain. The crossChainSignature and the structsArray next to it are identical on every card. The only thing that changes per submission is the 22 bytes that point this chain at its own slot.
One signature, three submissions. The same 65-byte ECDSA signature satisfies all three contracts, because each one reconstructs the same EIP-712 hash from the shared structsArray and its local domain.
Integration-side it stays small: an application that today does SignatureChecker.isValidSignatureNow(...) can swap to CrossChainSignatureChecker.isValidCrossChainSignatureNowCalldata(...) (per the reference implementation in the ERC) and get cross-chain semantics without any account-specific logic. The wallet UX and the signature primitive are unchanged.
I’m researching EIP-7999’s Universal Overflow design : ethresear.ch/t/gas-overflow-for-multidimensional-fee-markets/24766 and trying to understand its contract compatibility implications.
One thing I’m struggling with is separating:
assumptions developers genuinely rely on today when using gasleft() and gas-limited CALLs, from
assumptions protocol researchers believe developers rely on.
For example, Universal Overflow appears to preserve the classic “retain gas after a subcall” pattern, but it may no longer preserve the interpretation of GAS as a measure of total remaining execution capacity.
My current hypothesis is that most compatibility risk isn’t around retained-gas patterns themselves, but around contracts that use gas observability as a runtime decision signal.
As part of this work, I’m speaking with protocol engineers, smart contract developers, auditors, and tooling teams to collect a broad set of perspectives and real-world examples. I’m trying to understand not just whether the design is theoretically sound, but whether it aligns with the mental models and assumptions developers actually use in production.
Given your experience with production contracts, I’d love to hear whether that intuition sounds right or completely wrong.
Would you be open to a short 15–20 minute chat sometime this week? Even a few quick thoughts over messages would be incredibly helpful