CenturionDEX
Launch App

Allocators

Last modified:

Allocators are the gatekeepers of resource locks. Every lock is assigned an allocator, and that allocator has four responsibilities:

  1. Prevent double-spending — ensure sponsors do not commit the same tokens to multiple compacts or transfer away committed funds.
  2. Validate transfers — attest to standard ERC6909 transfers of resource-lock tokens.
  3. Authorize claims — approve claims against resource locks.
  4. Manage nonces — ensure nonces are not reused across claims.

Registration

Allocators must be registered with The Compact before they can be assigned to locks. Anyone can register an allocator provided one of three conditions holds:

function __registerAllocator(
    address allocator,
    bytes calldata proof
) external returns (uint96 allocatorId)

Create2 proof format

To pre-register a deterministic address that has not yet been deployed, supply an 85-byte proof:

0xff ++ factory ++ salt ++ initcode hash

IAllocator interface

Every allocator must implement IAllocator.

attest

Called on standard ERC6909 transfers to validate them. Must return IAllocator.attest.selector on success.

function attest(
    address operator,
    address from,
    address to,
    uint256 id,
    uint256 amount
) external returns (bytes4)

authorizeClaim

Called by The Compact during claim processing for on-chain authorization. Must return IAllocator.authorizeClaim.selector on success.

function authorizeClaim(
    bytes32 claimHash,
    address arbiter,
    address sponsor,
    uint256 nonce,
    uint256 expires,
    uint256[2][] calldata idsAndAmounts,
    bytes calldata allocatorData
) external returns (bytes4)
ParameterPurpose
claimHashHash of the claim being processed.
arbiterThe arbiter submitting the claim.
sponsorThe compact's sponsor.
nonceReplay-protection nonce.
expiresExpiration timestamp.
idsAndAmountsArray of [id, amount] pairs.
allocatorDataArbitrary data (e.g., off-chain signatures, Merkle proofs).

isClaimAuthorized

Off-chain view function that mirrors the logic of authorizeClaim without mutating state.

function isClaimAuthorized(
    bytes32 claimHash,
    address arbiter,
    address sponsor,
    uint256 nonce,
    uint256 expires,
    uint256[2][] calldata idsAndAmounts,
    bytes calldata allocatorData
) external view returns (bool)

Allocator data

The allocatorData parameter is an opaque blob whose format is defined entirely by the allocator implementation. Common uses include off-chain signatures, Merkle proofs, and other authorization evidence.

Nonce management

Allocators can directly consume nonces to invalidate compacts:

function consume(uint256 nonce) external

This emits NonceConsumedDirectly and prevents any compact using that nonce from being claimed.

function hasConsumedAllocatorNonce(
    address allocator,
    uint256 nonce
) external view returns (bool)

Implementation patterns

On-chain allocators

Track balances in contract storage, implement authorization logic directly, and optionally consult on-chain oracles.

Hybrid allocators

Combine off-chain tracking and signature generation with on-chain signature verification. Authorization evidence is passed through allocatorData.

Sample implementations

Trust assumptions

Sponsors trust that allocators will not unduly censor valid requests against fully funded locks. As a safety valve, sponsors can initiate forced withdrawals if an allocator becomes unresponsive.

Claimants trust that allocators are sound — they will not allow resource locks to become underfunded and will properly enforce balance constraints.

Forced withdrawal mechanism

The forced withdrawal mechanism lets sponsors bypass an unresponsive or malicious allocator after a waiting period.

Flow

  1. Initiate — call enableForcedWithdrawal(uint256 id) to signal intent.
  2. Wait — the resource lock's resetPeriod must elapse.
  3. Execute — call forcedWithdrawal(uint256 id, address recipient, uint256 amount) to withdraw the underlying tokens.

A sponsor can cancel at any time by calling disableForcedWithdrawal(uint256 id). Once enabled, the withdrawal status persists until explicitly disabled — so a sponsor must disable it before reusing the resource lock normally.

function enableForcedWithdrawal(uint256 id) external
function disableForcedWithdrawal(uint256 id) external
function forcedWithdrawal(
    uint256 id,
    address recipient,
    uint256 amount
) external

Checking status

function getForcedWithdrawalStatus(
    address account,
    uint256 id
) external view returns (ForcedWithdrawalStatus status, uint256 withdrawableAt)
StatusMeaning
DisabledNo forced withdrawal pending.
PendingInitiated but timelock not yet expired.
EnabledTimelock expired — withdrawal can execute.

Security considerations

Events

event AllocatorRegistered(
    uint96 allocatorId,
    address allocator
)
 
event NonceConsumedDirectly(
    address indexed allocator,
    uint256 nonce
)

Errors

ErrorMeaning
InvalidAllocation(address allocator)Invalid allocator used.
InvalidBatchAllocation()Batch allocation is invalid.
InvalidRegistrationProof(address allocator)Registration proof is invalid.
InconsistentAllocators()Allocators are inconsistent across batch operations.
InvalidNonce(address account, uint256 nonce)Nonce is invalid or already consumed.