Compacts & EIP-712
Last modified:
A compact is an agreement created by a sponsor that allows their locked resources to be claimed under specified conditions. The Compact protocol uses EIP-712 typed structured data for signing and verifying these agreements.
Compact types
Single compact
For a single resource lock on one chain:
struct Compact {
address arbiter; // The account tasked with verifying and submitting the claim
address sponsor; // The account to source the tokens from
uint256 nonce; // A parameter to enforce replay protection, scoped to allocator
uint256 expires; // The time at which the claim expires
bytes12 lockTag; // A tag representing the allocator, reset period, and scope
address token; // The locked token, or address(0) for native tokens
uint256 amount; // The amount of ERC6909 tokens to commit from the lock
// (Optional) Witness data may follow:
// Mandate mandate;
}Batch compact
For allocating multiple resource locks on one chain:
struct BatchCompact {
address arbiter; // The account tasked with verifying and submitting the claim
address sponsor; // The account to source the tokens from
uint256 nonce; // A parameter to enforce replay protection, scoped to allocator
uint256 expires; // The time at which the claim expires
Lock[] commitments; // The committed locks with lock tags, tokens, & amounts
// (Optional) Witness data may follow:
// Mandate mandate;
}
struct Lock {
bytes12 lockTag; // A tag representing the allocator, reset period, and scope
address token; // The locked token, or address(0) for native tokens
uint256 amount; // The maximum committed amount of tokens
}Multichain compact
For allocating one or more resource locks across multiple chains:
struct MultichainCompact {
address sponsor; // The account to source the tokens from
uint256 nonce; // A parameter to enforce replay protection, scoped to allocator
uint256 expires; // The time at which the claim expires
Element[] elements; // Arbiter, chainId, commitments, and mandate for each chain
}
struct Element {
address arbiter; // The account tasked with verifying and submitting the claim
uint256 chainId; // The chainId where the tokens are located
Lock[] commitments; // The committed locks with lock tags, tokens, & amounts
// Witness data MUST follow (mandatory for multichain compacts):
Mandate mandate;
}Witness structure
The witness mechanism extends compacts with additional conditions or parameters via a Mandate struct appended to the compact:
Compact(..., Mandate mandate)Mandate(uint256 myArg, bytes32 otherArg)Witness typestring
The witnessTypestring provided during a claim should contain the arguments inside the Mandate struct (e.g., uint256 myArg,bytes32 otherArg), followed by any nested structs.
EIP-712 requires nested structs to appear in alphanumeric order after the top-level struct in the typestring. Prefix nested structs with Mandate (e.g., MandateCondition) to ensure correct ordering:
MandateCondition condition,uint256 arg)MandateCondition(bool flag,uint256 valDo not include the closing parenthesis in your witness typestring. The protocol appends it during dynamic typestring construction.
Permit2 integration
The Compact supports Permit2 for gasless deposits.
CompactDeposit
For basic Permit2 deposits:
CompactDeposit(bytes12 lockTag,address recipient)Activation
Combines a deposit with single-compact registration:
Activation(address activator,uint256 id,Compact compact)Compact(...)Mandate(...)BatchActivation
Combines a deposit with batch-compact registration:
BatchActivation(address activator,uint256[] ids,Compact compact)Compact(...)Mandate(...)CompactCategory enum
Distinguishes compact types when using Permit2:
enum CompactCategory {
Compact, // 0
BatchCompact, // 1
MultichainCompact // 2
}On-chain registration
As an alternative to EIP-712 signatures, compacts can be registered directly on The Compact contract. A registered compact does not require a sponsor signature at claim time.
Register functions
// Register a single compact
function register(bytes32 claimHash, bytes32 typehash) external;
// Register multiple compacts
function registerMultiple(bytes32[] calldata claimHashes, bytes32[] calldata typehashes) external;
// Register on behalf of sponsor
function registerFor(
address sponsor,
bytes32 claimHash,
bytes32 typehash,
bytes memory sponsorSignature
) external;Registration event
event CompactRegistered(
address indexed sponsor,
bytes32 claimHash,
bytes32 typehash
);Check registration status
function isRegistered(
address sponsor,
bytes32 claimHash,
bytes32 typehash
) external view returns (bool);Signature verification
When a claim is submitted for a non-registered compact, The Compact verifies the sponsor's authorization in order:
- Caller is sponsor — if
msg.sender == sponsor, authorization is granted immediately. - ECDSA signature — attempt standard ECDSA verification.
- EIP-1271
isValidSignature— if ECDSA fails, callisValidSignatureon the sponsor's address. - Emissary
verifyClaim— if EIP-1271 fails, invoke the emissary'sverifyClaimfunction.