CenturionDEX
Launch App

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 val

Do 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:

  1. Caller is sponsor — if msg.sender == sponsor, authorization is granted immediately.
  2. ECDSA signature — attempt standard ECDSA verification.
  3. EIP-1271 isValidSignature — if ECDSA fails, call isValidSignature on the sponsor's address.
  4. Emissary verifyClaim — if EIP-1271 fails, invoke the emissary's verifyClaim function.