Allocator contracts and libraries that integrate with The Compact v1 and the ERC-7683 cross-chain order standard (via Uniswap Tribunal). These allocators prevent double-spend of ERC6909 resource locks and authorize claims according to compact commitments and allocator policy.
🕵 Although The Compact V1 and these example allocator implementations have undergone several independent security reviews (including from OpenZeppelin and Spearbit Cantina), we strongly recommend auditing any systems integrating with these contracts separately.
- Summary
- How It Fits With The Compact
- Allocator Types
- Contract Overview
- Key Concepts
- ERC-7683 + Tribunal Integration
- Setup
- Gas Snapshots
- Deployment
- Security and Trust Assumptions
- Errors
- Examples
- Contributing
- License
This repository provides various example allocator implementations for use with The Compact, an ERC6909-based protocol for reusable resource locks. Allocators co-sign or authorize claims against sponsors’ locked balances, prevent under-allocation, and in the case of the HybridERC7683 implementation, broadcast cross-chain orders using ERC-7683. Some allocators also rely on Uniswap Tribunal. The provided examples include both fully on-chain and hybrid (on-chain + off-chain) allocators.
Core goals:
- Ensure locked balances cannot be double-spent during the allocation window.
- Authorize arbiters to settle valid claims on The Compact against locked resources.
- Support direct integration with ERC-7683 order flows and Uniswap Tribunal settlement.
The Compact defines resource locks, compacts (credible commitments), arbiters, and claim processing. Allocators are registered on The Compact and must implement IAllocator. The Compact invokes the allocator’s authorizeClaim during claims, and attest to validate direct ERC6909 transfers.
For protocol details, read the the full docs for The Compact.
| Contract | Role | Off-chain Logic | ERC-7683 | Notes |
|---|---|---|---|---|
OnChainAllocator |
Pure on-chain allocator | ❌ | ❌ | Manages per-id allocations entirely on-chain; nonces scoped; exhaustively validated. |
HybridAllocator |
Hybrid allocator | ✅ (signers) | ❌ | Accepts off-chain allocator signatures or on-chain allocations; simple signer management. |
ERC7683Allocator |
On-chain allocator + origin settler | ❌ | ✅ | Implements IOriginSettler and opens/relays ERC-7683 orders tied to Compact claims. |
HybridERC7683 |
Hybrid allocator + origin settler | ✅ (signers) | ✅ | ERC-7683 origin settler requiring deposits (no signature path); combines Hybrid + ERC7683 flows. |
File: src/allocators/OnChainAllocator.sol
- Registers itself on The Compact and derives the domain separator and
allocatorId. - Maintains per-(sponsor,id) allocations with amount and expiry, deleting expired entries proactively.
- Key flows:
allocate(commitments, arbiter, expires, typehash, witness)→ on-chain allocation formsg.sender.allocateFor(sponsor, ..., signature)→ allocation on behalf of a sponsor; requires sponsor signature or a prior Compact registration of the derivedclaimHash.allocateAndRegister(recipient, commitments, ...)→ deposits tokens held by the allocator into The Compact and registers claim(s) in a single flow.prepareAllocation/executeAllocation→ two-phase path that enforces correct deposit+registration via transient storage and balance delta checks.attest→ validates ERC6909 transfer safety by ensuring unallocated balance covers the transfer.authorizeClaim/isClaimAuthorized→ ensures theclaimHashmatches an active allocation and cleans it up on success.
File: src/allocators/HybridAllocator.sol
- Maintains a set of authorized off-chain signers; exposes add/remove/replace signer administration.
- Supports two ways to authorize a claim:
- On-chain allocation (claim pre-allocated in contract storage).
- Off-chain signature over the Compact digest by an authorized signer.
- Provides
allocateAndRegisterfor immediate deposit + register flows; does not support ERC6909attest(revertsUnsupported()).
File: src/allocators/ERC7683Allocator.sol
- Extends
OnChainAllocatorand implementsIOriginSettlerto open and resolve ERC-7683 orders. - Two paths:
open(...)for on-chain orders: requires prior Compact registration; allocates on-chain and emitsOpenwith a resolved order for fillers.openFor(...)for gasless orders: supports either sponsor signature or deposit-based flows; allocates (and optionally deposits+registers) for the user and emitsOpen.
- Integrates with Uniswap Tribunal via
ERC7683AllocatorLibto produce mandate hashes and destination fill instructions.
File: src/allocators/HybridERC7683.sol
- Extends
HybridAllocatorand implementsIOriginSettlerfor ERC-7683. - Requires deposits for
openForandresolveForpaths (no pure signature path), then performsallocateAndRegisterand emitsOpen.
src/allocators/lib/AllocatorLib.sol- Transient-storage based
prepareAllocation/executeAllocationto safely bind deposit+registration to a nonce and inputs. - Utilities to derive
claimHash, split/pack ids, compute durations fromlockTag, and recover ECDSA signers.
- Transient-storage based
src/allocators/lib/ERC7683AllocatorLib.sol- Decodes ERC-7683 order payloads, sanitizes values, constructs mandate and fill hashes, and resolves orders for fillers.
src/allocators/lib/TypeHashes.sol- Typehash constants used by allocators (kept for clarity alongside Tribunal’s canonical set).
src/interfaces/IOnChainAllocator.sol— on-chain allocator API and error set.src/interfaces/IHybridAllocator.sol— hybrid allocator admin and allocation API.src/interfaces/IERC7683Allocator.sol— ERC-7683 origin settler + allocator API (includesgetNonce,createFillerData).src/interfaces/ERC7683/IOriginSettler.sol— ERC-7683 origin settler standard used by allocators.
- Allocations lock part of a sponsor’s ERC6909 id (resource lock) for a specified
expirestimestamp. - The allocator derives a
claimHashfrom the compact parameters:(arbiter, sponsor, nonce, expires, commitmentsHash, witness, typehash). - During claim settlement on The Compact, the allocator’s
authorizeClaimverifies thatclaimHashis active for the(sponsor, ids)and then consumes it.
- Nonces are 256-bit values where the most-significant 160 bits embed a scoping address and the least-significant 96 bits are incrementing counters.
- In
OnChainAllocator:allocateuses scope(address(0), sponsor)so anyone can relay; returnednonceencodes the sponsor.prepareAllocation/executeAllocationscope thenonceto(caller, recipient)to allow relayed flows with distinct counters.
- Allocations must expire before the resource lock’s
resetPeriodelapses. The allocator enforces thatexpires < now + minResetPeriodacross all commitments. - If a forced withdrawal has been scheduled on The Compact for a lock, allocations must expire strictly before the withdrawal becomes available.
- For standard ERC6909 transfers, The Compact calls
IAllocator.attestto ensure that unallocated balances cover the transfer amount.OnChainAllocatorcalculates allocated balance lazily and reclaims expired entries.
prepareAllocationstores a transient snapshot of balances and binds the inputs to a unique identifier andnonce.executeAllocationre-reads balances, requires strictly positive deltas for each id, reconstructs the commitments, recomputes theclaimHash, and requires that the claim has been registered on The Compact.
ERC7683AllocatorandHybridERC7683implementIOriginSettler:open(on-chain) andopenFor(gasless) compute mandate hashes, allocate deposits/locks, and emitOpenwith a resolved order containingmaxSpent,minReceived, andfillInstructionsfor the destination settler (Uniswap Tribunal).resolve/resolveForproduce a deterministicResolvedCrossChainOrderfor simulation and integration.
- Order data types are validated against expected typehashes, and origin settler address must match the allocator.
- Install Foundry: see the official guide.
- Install dependencies:
forge install- Build:
forge build- Test (verbose):
forge test -vIf you intend to develop on this repo, follow CONTRIBUTING.md.
Gas is measured via forge-gas-snapshot with snapLastCall in tests. See the snapshots/ directory for reference outputs and CONTRIBUTING.md.
Use the provided Foundry script and optional verification:
forge script script/Deploy.s.sol --broadcast --rpc-url <rpc_url> --verifyFor deployment practices, resumable verification, and generating deployment logs/markdown, see CONTRIBUTING.md.
OnChainAllocatoris fully on-chain and deterministic; safety depends on The Compact’s guarantees and correct allocator registration.HybridAllocatorandHybridERC7683depend on off-chain signer sets. Only authorized signers can approve claims; signer management is on-chain.- ERC-7683 flows emit orders whose destination settlement is performed by Uniswap Tribunal; fillers should validate orders and signatures.
Always review and audit before production use.
Key error families (non-exhaustive):
- Allocation lifecycle:
InvalidCommitments(),InvalidAmount(amount),InvalidExpiration(expires, expected),ForceWithdrawalAvailable(expires, forcedAt),InsufficientBalance(sponsor, id, available, expected). - Authorization and signatures:
InvalidSignature(signer, expectedSigner)(on-chain allocator),InvalidSignature()(hybrid signer path),InvalidClaim(claimHash). - Two-phase checks:
InvalidPreparation(),InvalidRegistration(recipient, claimHash, typehash),InvalidBalanceChange(newBalance, oldBalance). - ERC-7683:
InvalidOrderDataType(got, expected),InvalidOriginSettler(got, expected),InvalidNonce(got, expected),InvalidOrderData(orderData),InvalidRecipientCallbackLength().
Refer to the interfaces and contract sources for the complete error set.
// 1) Sponsor deposits to The Compact, minting ERC6909 id for (lockTag, token)
// The `lockTag` encodes this allocator’s `allocatorId` and a valid reset period/scope
compact.depositERC20(token, lockTag, amount, sponsor);
// 2) Sponsor allocates on-chain for a future claim
Lock[] memory commitments = new Lock[](1);
commitments[0] = Lock({ lockTag: lockTag, token: token, amount: amount });
(bytes32 claimHash, uint256 nonce) = allocator.allocate(commitments, arbiter, expires, BATCH_COMPACT_TYPEHASH, bytes32(0));
// 3) Arbiter submits a claim on The Compact using the allocated id/amount
// The Compact will call allocator.authorizeClaim(claimHash, ...) which consumes the allocation.
// compact.batchClaim(...) // see The Compact’s ITheCompactClaims// Flow: Sponsor pre-deposits; a relayer allocates on behalf of the sponsor using the sponsor’s signature.
// Build the same commitments and compute the intended claimHash off-chain for user visibility
Lock[] memory commitments = new Lock[](1);
commitments[0] = Lock({ lockTag: lockTag, token: token, amount: amount });
bytes32 commitmentsHash = /* keccak256(abi.encodePacked(LOCK(...))) over commitments */;
bytes32 claimHash = keccak256(abi.encode(BATCH_COMPACT_TYPEHASH, arbiter, sponsor, expectedNonce, expires, commitmentsHash));
bytes32 digest = keccak256(abi.encodePacked(bytes2(0x1901), compact.DOMAIN_SEPARATOR(), claimHash));
bytes memory sponsorSig = /* ECDSA signature over digest by sponsor */;
// Relayer submits the allocation on behalf of the sponsor
allocator.allocateFor(sponsor, commitments, arbiter, uint32(expires), BATCH_COMPACT_TYPEHASH, bytes32(0), sponsorSig);It is also possible to pre-register the claimHash directly via The Compact, then call allocateFor with an empty signature.
// Caller contract or EOA funds tokens and approves Compact.
uint256[2][] memory idsAndAmounts = new uint256[2][](2);
idsAndAmounts[0] = [uint256(AllocatorLib.toId(lockTagA, tokenA)), amountA];
idsAndAmounts[1] = [uint256(AllocatorLib.toId(lockTagB, tokenB)), amountB];
// 1) Prepare: caches current ERC6909 balances in transient storage and returns the scoped nonce
uint256 nonce = allocator.prepareAllocation(recipient, idsAndAmounts, arbiter, expires, BATCH_COMPACT_TYPEHASH, bytes32(0), "");
// 2) Deposit + register exact ids/amounts on The Compact using the returned nonce
compact.batchDepositAndRegisterFor(recipient, idsAndAmounts, arbiter, nonce, expires, BATCH_COMPACT_TYPEHASH, bytes32(0));
// 3) Execute: re-checks positive balance deltas per id, reconstructs commitments, recomputes claimHash
allocator.executeAllocation(recipient, idsAndAmounts, arbiter, expires, BATCH_COMPACT_TYPEHASH, bytes32(0), "");// Deposit path (no prior sponsor signature): allocator sends its own ERC20 to Compact and registers
Lock[] memory commitments = new Lock[](1);
commitments[0] = Lock({ lockTag: lockTag, token: token, amount: 0 }); // 0 means "use allocator’s full token balance"
(bytes32 claimHash,, uint256 nonce) = hybrid.allocateAndRegister(recipient, commitments, arbiter, expires, BATCH_COMPACT_TYPEHASH, bytes32(0));
// Signature path (no on-chain pre-allocation): an authorized signer signs the Compact digest of claimHash
bytes32 digest = /* keccak256(0x1901 || DOMAIN_SEPARATOR || claimHash) */;
bytes memory allocatorData = /* ECDSA signature from an authorized signer */;
// Later, during claim, The Compact passes allocatorData into hybrid.authorizeClaim to validate// a) User-opened order (requires prior Compact registration for the claim)
IOriginSettler.OnchainCrossChainOrder memory order = IOriginSettler.OnchainCrossChainOrder({
fillDeadline: uint32(fillDeadline),
orderDataType: ORDERDATA_ONCHAIN_TYPEHASH,
orderData: abi.encode(IERC7683Allocator.OrderDataOnChain({
order: IERC7683Allocator.Order({ arbiter: arbiter, commitments: commitments, mandate: mandate }),
expires: uint32(claimExpires)
}))
});
erc7683Allocator.open(order); // emits Open with ResolvedCrossChainOrder for fillers
// b) Gasless order opened by filler/relayer (either sponsor-signature or deposit path)
IOriginSettler.GaslessCrossChainOrder memory gasless = IOriginSettler.GaslessCrossChainOrder({
originSettler: address(erc7683Allocator),
user: sponsor,
nonce: expectedNonce,
originChainId: block.chainid,
openDeadline: uint32(openDeadline),
fillDeadline: uint32(fillDeadline),
orderDataType: ORDERDATA_GASLESS_TYPEHASH,
orderData: abi.encode(IERC7683Allocator.OrderDataGasless({
order: IERC7683Allocator.Order({ arbiter: arbiter, commitments: commitments, mandate: mandate }),
deposit: true // set true to use allocator-held funds via allocateAndRegister
}))
});
erc7683Allocator.openFor(gasless, sponsorSignature, "");For complete end-to-end tests (including Tribunal integration and ResolvedCrossChainOrder shape), see test/ERC7683Allocator.t.sol.
Please read CONTRIBUTING.md for installation, style, branching, testing, pre-commit hooks, and deployment guidance.
This codebase is published under the MIT License. See the LICENSE file for full details.