Skip to content

Protocol

Overview

The Protocol is part of an opinionated architecture for an intent based solving protocol which facilitates single and multichain solving of intents. Intents can be solved on a single chain without provisioning up front capital as we arbiters can confirm mandates have been met by solvers at execution time, thus solvers may use the swappers locked funds for execution. It does this by introducing a SolverPayload which can be executed by the Arbiter to ensure the EIP-712 signed mandate is met.

Key Goals for this design include

  • Intent Based Architecture to improve execution
  • Ability for Solvers to execute fills without needing to provide upfront capital

The protocol is inspired by or leverages the following key components

  • Tycho Execution: Is leveraged by Arbiters and solvers for executing most efficient routes.
  • Uniswap the-compact: The foundation of our resource locking mechanism
  • Uniswap Tribunal: Mandates and EIP-712 signing are heavily utilized throughout the protocol
  • Uniswap v4: We leverage V4 hooks for IntentSwap Execution on Uniswap V4.

For a technical overview of this repository automatically generated by DeepWiki please Ask DeepWiki

Mandate Functionality

:information_source: _The following section was inspired by the :unicorn: Tribunal and updated to support monochain swaps which allow for solvers to execute intents with the swappers funds.

To settle a swap, the filler submits a "fill" request to the Arbiter contract. This consists of four core components:

  1. Claim: Contains the chain ID of a Compact, its parameters, and its signatures.
  2. Mandate: Specifies settlement conditions and amount derivation parameters specified by the sponsor.
  3. SolverPayload: Specifies the transactions to execute to solve the intent
  4. Claimant: Specifies the account that will receive the claimed tokens.

Note for cross-chain message protocols integrating with Tribunal: inherit the Arbiter contract and override the _processDirective and _quoteDirective functions to implement the relevant directive processing logic for passing a message to the arbiter on the claim chain (or ensure that the necessary state is updated to allow for the arbiter to "pull" the message themselves). An ERC7683-compatible implementation is provided in ERC7683Arbiter.sol. ⚠️ Note: for cross-chain intents SolverPayloads can be executed on the destination chain, but the solver must provide their own funds and provisioning of the swapper tokens to the solver will be handled by the Settlement Service.

Core Components

Claim Structure

struct Claim {
    uint256 chainId;          // Claim processing chain ID
    Compact compact;          // The compact parameters
    bytes sponsorSignature;   // Authorization from the sponsor
    bytes allocatorSignature; // Authorization from the allocator
}

Compact Structure

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
    uint256 id;               // The token ID of the ERC6909 token to allocate
    uint256 amount;           // The amount of ERC6909 tokens to allocate
}

Solver Payload Structure

/**
 * @notice Defines a single contract call to be executed
 * @param to The target contract address
 * @param data The encoded function call data
 * @param value Amount of ETH to send
 */
struct Call {
    address to; // The target contract address
    bytes data; // The encoded function call data
    uint256 value; //Amount of ETH to send
}
 
struct SolverPayload {
    Call[] calls; //Array of contract calls to execute in sequence
}

Mandate Structure

struct Mandate {
    address recipient;           // Recipient of filled tokens
    uint256 expires;             // Mandate expiration timestamp
    address token;               // Fill token (address(0) for native)
    uint256 minimumAmount;       // Minimum fill amount
    uint256 baselinePriorityFee; // Base fee threshold where scaling kicks in
    uint256 scalingFactor;       // Fee scaling multiplier (1e18 baseline)
    bytes32 salt;                // Preimage resistance parameter
}

Process Flow

  1. Fillers initiate by calling fill(Claim calldata claim, Mandate calldata mandate, SolverPayload calldata solverPayload address claimant) and providing any msg.value required for the settlement to pay to process the solution.
  2. Arbiter verifies that the mandate has not expired by checking the mandate's expires timestamp
  3. Computation phase:
    • Derives mandateHash using an EIP712 typehash for the mandate, destination chainId, tribunal address, and mandate data
    • Derives claimHash using an EIP712 typehash for the compact with the mandate as a witness and the compact data including the mandateHash
    • Ensures that the claimHash has not already been used and marks it as filled
    • Calculates fillAmount and claimAmount based on:
      • Compact amount
      • Mandate parameters (minimumAmount, baselinePriorityFee, scalingFactor)
      • tx.gasprice and block.basefee
      • NOTE: scalingFactor will result in an increased fillAmount if > 1e18 or a decreased claimAmount if < 1e18
      • NOTE: scalingFactor is combined with tx.gasprice - (block.basefee + baselinePriorityFee) (or 0 if it would otherwise be negative) before being applied to the amount
  4. Execution phase:
    • Executes: The Solver Payload using the funds locked in the-compact and ensures that this results in output funds (tokens or ETH) >= that specified in the mandate. IF NOT REVERT
    • Transfers fillAmount of token to mandate recipient
    • Transfers Compact amount of token to the filler.
    • Processes directive via _processDirective(chainId, compact, sponsorSignature, allocatorSignature, mandateHash, claimant, claimAmount)

There are also a few view functions:

  • quote(Claim calldata claim, Mandate calldata mandate, address claimant) will suggest a dispensation amount (function of gas on claim chain + any additional "protocol overhead" if using push-based cross-chain messaging)
  • filled(bytes32 claimHash) will check if a given claim hash has already been filled (used)
  • getCompactWitnessDetails() will return the Mandate witness typestring and that correlates token + amount arguments (so frontends can show context about the token and use decimal inputs)
  • deriveMandateHash(Mandate calldata mandate) will return the EIP712 typehash for the mandate
  • deriveClaimHash(Compact calldata compact, bytes32 mandateHash) will return the unique claim hash for a compact and mandate combination
  • deriveAmounts(uint256 maximumAmount, uint256 minimumAmount, uint256 baselinePriorityFee, uint256 scalingFactor) will return the fill and claim amounts based on the parameters; the base fee and priority fee will be applied to the amount and so should be tuned in the call appropriately

Mandate EIP-712 Typehash

This is what swappers will see as their witness data when signing a Compact:

struct Mandate {
    uint256 chainId;
    address tribunal;
    address recipient;
    uint256 expires;
    address token;
    uint256 minimumAmount;
    uint256 baselinePriorityFee;
    uint256 scalingFactor;
    bytes32 salt;
}

ERC7683 Integration

The ERC7683Arbiter contract implements the IDestinationSettler interface from ERC7683, allowing for standardized cross-chain settlement:

interface IDestinationSettler {
    function fill(bytes32 orderId, bytes calldata originData, bytes calldata fillerData) external;
}

This implementation allows the Tribunal to be used with any ERC7683-compatible cross-chain messaging system.