This ERC extends ERC-7803 to enable readable cross-chain account abstraction by reserving chainId
0 for universal signature validity in EIP-712 domains, following the same pattern established in EIP-7702. It allows accounts to sign messages that authorize operations across multiple chains through a single signature, enabling cross-chain intents, multi-chain DAO voting, and unified account management.
Current account abstraction solutions require separate signatures for each blockchain network. This creates poor user experience for cross-chain operations such as:
By extending ERC-7803's signing domains to support cross-chain scenarios, this ERC enables these use cases while maintaining security through explicit chain specification.
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.
When an EIP-712 domain in ERC-7803 uses chainId: 0
, it MUST be interpreted as "valid on any chain where the account exists" (even counterfactually).
Applications verifying cross-chain signatures MUST recognize chainId: 0
as valid on any chain where the account exists. For on-chain validation, the account SHOULD accept the signature as valid regardless of the chainId
in each signing domain and SHOULD filter out the domains that are not valid on the current chain.
Applications implementing cross-chain signatures SHOULD validate account existence on each target chain. Wallets SHOULD display the signing domains and their associated operations in a clear, chain-specific format that helps users understand exactly what actions will be executed on each network. For example, a wallet might an scrollable list of "Ethereum: Transfer 100 USDC to 0x123...", "Polygon: Approve 50 MATIC to 0x456...", etc.
EIP-712 has become the standard for readable and structured data signing. Assuming ERC-7803 is implemented, it becomes a building block that already streamlines from applications to users. By reusing the same mechanism, signingDomains
and authMethods
become building blocks that fit into the wider ecosystem with relatively low effort.
This document extends ERC-7803 and uses these building blocks to allow for a more consistent and informed user experience while interacting with cross-chain applications.
The account-centric approach represents the most interoperable way for users to express cross-chain intents. By binding signatures to user accounts rather than specific chains, this design enables:
chainId: 0
SemanticsUsing chainId: 0
leverages EIP-712's existing chainId
field to indicate cross-chain validity. Zero is never used by real networks, making it a natural choice for "universal" signatures.
This ERC adds one simple rule to ERC-7803: treat chainId: 0
domains as valid on any chain. No new encoding, no complex message structures, no additional verification rules beyond checking the account exists.
This ERC is fully backward compatible with ERC-7803. Applications that don't support chainId: 0
will reject such signatures safely.
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:
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
],
CrossChainOrder: [
{ name: "originToken", type: "address" },
{ name: "originAmount", type: "uint256" },
{ name: "destinationChainId", type: "uint256" },
{ name: "destinationToken", type: "address" },
{ name: "minDestinationAmount", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
},
primaryType: "CrossChainOrder",
domain: {
name: "CrossChainDEX",
version: "1",
chainId: 0, // Cross-chain validity
verifyingContract: "0xUserAccount..." // User's account
},
message: {
originToken: "0xA0b86a33E6776885F5Db...", // USDC
originAmount: "1000000000", // 1000 USDC
destinationChainId: 42161, // Arbitrum
destinationToken: "0x0000000000000000000000000000000000000000", // ETH
minDestinationAmount: "500000000000000000", // 0.5 ETH
deadline: 1704067200
},
signingDomains: [
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "EthereumSettler",
chainId: 1, // Ethereum
verifyingContract: "0xEthereumSettler..."
}
},
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "ArbitrumSettler",
chainId: 42161, // Arbitrum
verifyingContract: "0xArbitrumSettler..."
}
}
],
authMethods: [
{ "id": "ERC-1271" }
]
}
This signature is valid on both Ethereum and Arbitrum. The Ethereum settler can verify it to custody the user's USDC, while the Arbitrum settler can verify it to release ETH to the user. The chainId: 0
domain enables this cross-chain validity.
A DAO member votes on a proposal affecting all chain deployments:
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
],
Vote: [
{ name: "proposalId", type: "uint256" },
{ name: "support", type: "bool" }
]
},
primaryType: "Vote",
domain: {
name: "MultiChainDAO",
version: "1",
chainId: 0, // Cross-chain validity
verifyingContract: "0xVoterAccount..." // User's account
},
message: {
proposalId: 42,
support: true
},
signingDomains: [
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "DAOGovernor",
chainId: 1, // Ethereum
verifyingContract: "0xEthereumDAO..."
}
},
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "DAOGovernor",
chainId: 137, // Polygon
verifyingContract: "0xPolygonDAO..."
}
}
],
authMethods: [
{ "id": "ERC-1271" }
]
}
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 using an ERC-7579 module:
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
],
AddSigner: [
{ name: "newSigner", type: "bytes" },
{ name: "newThreshold", type: "uint256" },
{ name: "nonce", type: "uint256" }
]
},
primaryType: "AddSigner",
domain: {
name: "MultiChainMultisig",
version: "1",
chainId: 0, // Cross-chain validity
verifyingContract: "0x1234567890123456789012345678901234567890" // Same account address
},
message: {
newSigner: "0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20", // ERC-7913 signer
newThreshold: 3, // Update threshold from 2-of-4 to 3-of-5
nonce: 42
},
signingDomains: [
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "MultiSignerERC7913",
chainId: 1, // Ethereum
verifyingContract: "0x1234567890123456789012345678901234567890"
}
},
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "MultiSignerERC7913",
chainId: 137, // Polygon
verifyingContract: "0x1234567890123456789012345678901234567890"
}
},
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "MultiSignerERC7913",
chainId: 42161, // Arbitrum
verifyingContract: "0x1234567890123456789012345678901234567890"
}
}
],
authMethods: [
{ "id": "ERC-7913" }
]
}
This signature enables the multisig owners to add a new ERC-7913 signer and update the threshold across all chain deployments simultaneously. The same account address (0x1234...
) exists on Ethereum, Polygon, and Arbitrum, and this single signature authorizes the signer addition on all three networks.
A user has lost access to their account and guardians need to initiate recovery across multiple networks:
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "version", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
],
Recover: [
{ name: "account", type: "address" },
{ name: "salt", type: "bytes32" },
{ name: "mode", type: "bytes32" },
{ name: "executionCalldata", type: "bytes" }
]
},
primaryType: "Recover",
domain: {
name: "CrossChainSocialRecovery",
version: "1",
chainId: 0, // Cross-chain validity
verifyingContract: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef" // Lost account address
},
message: {
account: "0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef", // Account being recovered
salt: "0x1111111111111111111111111111111111111111111111111111111111111111",
mode: "0x0100000000000000000000000000000000000000000000000000000000000000", // ERC-7579 batch execution
executionCalldata: "0x608060405234801561001057600080fd5b50" // Encoded calls to replace signer
},
signingDomains: [
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "DelayedSocialRecovery",
chainId: 1, // Ethereum
verifyingContract: "0x1111111111111111111111111111111111111111" // DelayedSocialRecovery module
}
},
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "DelayedSocialRecovery",
chainId: 137, // Polygon
verifyingContract: "0x2222222222222222222222222222222222222222" // DelayedSocialRecovery module
}
},
{
types: {
EIP712Domain: [
{ name: "name", type: "string" },
{ name: "chainId", type: "uint256" },
{ name: "verifyingContract", type: "address" }
]
},
domain: {
name: "DelayedSocialRecovery",
chainId: 42161, // Arbitrum
verifyingContract: "0x3333333333333333333333333333333333333333" // DelayedSocialRecovery module
}
}
],
authMethods: [
{ "id": "ERC-7913" }
]
}
This signature enables guardians (using a ERC7579DelayedSocialRecovery
module combining multisig and delayed execution) to schedule a recovery operation across all chains. The module would require:
abi.encode(bytes[] guardians, bytes[] signatures)
where guardians are ERC-7913 formatted signers and signatures are their individual approvalsThe executionCalldata
contains batched ERC-7579 calls to remove the compromised signer and add the new recovery signer, ensuring atomic recovery across all target networks.
Cross-Chain Replay: This ERC intentionally enables replay across chains. The signing domain binds signatures to specific accounts, preventing unauthorized use by different accounts. Each application that receives the signature should filter the signing domains to only include those relevant to its chain and maintain its own non-replayability scheme.
Account Validation: Applications should verify the signing account exists on each chain where the signature is used. 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.
Code and State Differences: Be aware that contract code and state may differ across chains at the same address. For example, signatures that pass isValidSignature()
on one chain may fail on another due to code differences, state divergence, or chain-specific logic.
Atomicity: Cross-chain signatures do not guarantee atomic execution across all chains. A signature may be partially executed on some chains while becoming unexecutable on others. This can leave users with funds pending to fulfill their intent. Developers should implement mechanisms that ensure recoverability in case of partial execution. Consider using an ERC-7786 gateway for routing messages and managing cross-chain state.
Signature Expiration: Signatures that weren't executed on some chains may remain executable for an indefinite period. Protocols must implement native expiration or invalidation mechanisms to prevent sudden use of dangling signatures. This is especially important for signatures with chainId: 0
domains, as they remain valid across all chains where the account exists.
Copyright and related rights waived via CC0.