This ERC defines a standard approach for creating and verifying cross-chain signatures using EIP-712. By omitting the chainId field from the EIP-712 domain (which is optional per the standard), a signature can be valid across multiple chains. Chain-specific operations are encoded as an array of structured messages, where each chain receives the array of message hashes and only the full message data relevant to that chain. This enables efficient cross-chain intents, multi-chain DAO voting, and unified account management using standard EIP-712 encoding without requiring special wallet support.
Current account abstraction solutions require separate signatures for each blockchain network. This creates poor user experience for cross-chain operations such as:
Existing proposals either require complex Merkle tree constructions (which need wallet-specific UI to verify all leaves) or non-standard encoding schemes that lack wallet adoption. This ERC provides a simpler approach using only standard EIP-712 encoding with array types, enabling cross-chain signatures with minimal on-chain overhead while maintaining full transparency in standard wallet signing interfaces.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
A cross-chain EIP-712 signature MUST omit the chainId field from the EIP712Domain type and domain object. Since chainId is optional per EIP-712, omitting it signals that the signature is intended for cross-chain validity. The domain's verifyingContract field MAY be set to the account address that controls the operations across chains if it is deterministically deployed at the same address across all target chains.
Cross-chain operations MUST be encoded as an array of chain-specific message structs. Each struct in the array SHOULD include a nested domain struct identifying both the target chain and verifying contract. This follows EIP-712's recursive encoding pattern and ensures proper binding to chain-specific contracts.
The nested domain struct SHOULD be named to avoid collision with EIP712Domain (e.g., EIP712ChainDomain) and SHOULD include at minimum the chainId field. In case the root EIP712Domain does not include the verifyingContract field, the nested domain struct MUST include it.
Per EIP-712, arrays are encoded as keccak256(encodeData(element[0]) ‖ encodeData(element[1]) ‖ ... ‖ encodeData(element[n])), where each element is a struct that is recursively encoded as hashStruct(element[i]), and nested structs are also recursively hashed.
When verifying a cross-chain signature on a specific chain, contracts MUST:
hashStruct(currentChainMessage) and verify it matches the hash at the expected position in the provided arrayThis approach minimizes on-chain calldata and computation costs since only one full message is passed to each chain, while the other chains' operations are represented as 32-byte hashes.
Standard EIP-712 wallets will automatically display the array of chain-specific messages in a readable format. Wallets MAY enhance the display by grouping operations by chain and showing chain names instead of chain IDs for better user experience.
This ERC uses only standard EIP-712 encoding without any extensions or special constructs. All fields in the EIP712Domain are optional per the standard, so omitting chainId is perfectly valid. This means:
Alternative approaches use Merkle trees to commit to cross-chain operations. While Merkle trees can reduce on-chain overhead for many chains, they have a critical drawback:
Wallet Verification Complexity: Standard EIP-712 wallets cannot display Merkle tree leaves. Users signing a Merkle root have no way to verify all operations in their wallet UI. Wallets would need to implement custom logic to:
This breaks the principle of trustless signing, users must trust the application to correctly provide all leaves, and wallet developers must implement and maintain custom verification logic.
The array-based approach provides full transparency using standard EIP-712. Users see all chain-specific operations in any compliant wallet without custom support. No hidden operations are possible since all array elements are displayed as part of the standard EIP-712 message structure.
On-chain overhead comparison: For reasonable cross-chain operations, the overhead difference is minimal.
With the array approach, each chain receives all N operation hashes (N × 32 bytes). With Merkle trees (assuming a binary tree), each chain receives a Merkle proof of size ceil(log₂(N)) × 32 bytes. Both approaches reconstruct the root/array hash on-chain from the provided data and verify it against the signature, no additional calldata for the root is needed. While Merkle proofs grow logarithmically vs. the array's linear growth, the practical savings are small for reasonable use cases:
| Chains | Array (N × 32) | Merkle (⌈log₂(N)⌉ × 32) | Savings |
|---|---|---|---|
| 2 | 64 bytes (2 × 32) | 32 bytes (1 × 32) | 32 bytes (1 hash) |
| 3 | 96 bytes (3 × 32) | 64 bytes (2 × 32) | 32 bytes (1 hash) |
| 4 | 128 bytes (4 × 32) | 64 bytes (2 × 32) | 64 bytes (2 hashes) |
| 5 | 160 bytes (5 × 32) | 96 bytes (3 × 32) | 64 bytes (2 hashes) |
| 8 | 256 bytes (8 × 32) | 96 bytes (3 × 32) | 160 bytes (5 hashes) |
For 2-5 chains (the reasonable case for cross-chain operations), Merkle trees save only 32-64 bytes (1-2 hashes) per transaction. This minimal savings doesn't justify the complexity and loss of transparency. Merkle trees only become significantly more efficient at 8+ chains, which is an uncommon use case and may indicate the operation should be split into multiple signatures for better user comprehension and failure isolation.
By setting verifyingContract to the user's account address, this design enables:
Omitting chainId entirely is cleaner than using a special value like 0:
0 as a magic valueThis ERC uses standard EIP-712 without modifications. Existing wallets and applications that support EIP-712 can immediately work with cross-chain signatures without any changes, though they may not recognize the cross-chain semantics.
Applications that verify signatures with a domain that includes a specific chainId will reject cross-chain signatures (where chainId is omitted), providing safe failure by default. Applications that wish to support cross-chain signatures must explicitly implement the verification pattern described in this ERC.
A collection of examples of how to use this ERC to fulfill the Motivation use cases.
A user wants to execute a cross-chain trade: sell USDC on Ethereum, receive ETH on Arbitrum:
On-chain verification on Ethereum:
The Arbitrum settler would use the same signature with currentIndex: 1 and the full data for op[1]. Each chain only receives the full calldata for its own operation, while other operations are represented as 32-byte hashes.
A DAO member votes on a proposal affecting all chain deployments:
On-chain verification on each DAO:
This vote signature can be submitted to DAO contracts on both Ethereum and Polygon, enabling coordinated multi-chain governance decisions.
A user wants to add a new signer to their multisig account deployed across multiple chains:
On-chain execution:
This signature enables the multisig owners to add a new signer and update the threshold across all chain deployments simultaneously. The same account address exists on Ethereum, Polygon, and Arbitrum, and this single signature authorizes the updates on all three networks.
A user has lost access to their account and guardians need to initiate recovery across multiple networks:
On-chain execution with guardian multisig:
This signature enables guardians to schedule a recovery operation across all chains. The process includes:
This ERC intentionally enables replay across chains, the same signature is designed to be used on multiple chains. The verifyingContract field (set to the user's account address) binds the signature to a specific account, preventing unauthorized use. However, applications MUST implement their own replay protection mechanisms:
Applications verifying signatures SHOULD check that the signing account exists on the current chain. An account that exists on Ethereum but not Polygon should not have signatures accepted on Polygon. For counterfactual accounts that have not been deployed yet, applications should follow ERC-6492 to validate signatures.
Contract code and state may differ across chains at the same address. Signatures that pass isValidSignature() on one chain may fail on another due to:
Applications MUST handle signature validation failures gracefully and should not assume uniform behavior across chains.
Cross-chain signatures do not guarantee atomic execution. A signature may be:
This can result in:
Applications SHOULD implement:
The message array defines operations for all chains. Applications MUST:
chainId matches the current chainFailing to validate these can allow:
Signatures without explicit expiration remain valid indefinitely. If an operation is:
The signature can be executed later, potentially with unexpected consequences. Applications MUST include deadline or validUntil fields in messages to prevent stale signature execution.
Since this ERC relies on standard EIP-712 wallet display, users depend on their wallet to correctly show all cross-chain operations. Users should:
Wallet developers SHOULD enhance displays to highlight cross-chain nature and show warnings about partial execution risks.
Copyright and related rights waived via CC0.