Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/rhinestonewtf/warp-router/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Adapters are protocol-specific settlement contracts that handle fill and claim operations through the Router. They use a delegatecall pattern to execute in the Router’s context, enabling modular protocol support without contract upgrades.
Adapters are always executed via delegatecall from the Router, meaning they run in the Router’s storage and balance context.

Adapter Architecture

AdapterBase

All adapters inherit from AdapterBase, which provides foundational functionality:
AdapterBase.sol
abstract contract AdapterBase is IAdapter, SemVer {
    address public immutable _ROUTER;
    address public immutable ARBITER;
    
    constructor(address router, address arbiter) {
        _ROUTER = router;
        ARBITER = (arbiter == address(0)) ? address(this) : arbiter;
    }
    
    modifier onlyViaRouter() {
        require(address(this) == _ROUTER, OnlyDelegateCall());
        _;
    }
}
Key Components:

Router Reference

Immutable reference to the Router contract for security validation

Arbiter Reference

Arbiter contract responsible for settlement validation

onlyViaRouter Modifier

Ensures functions are only called via Router delegatecall

Relayer Context

Access to solver-specific context data

Delegatecall Pattern

Adapters use delegatecall to execute in the Router’s context:
AdapterLib.sol
function callAdapterWithRelayerContext(
    address adapter,
    bytes calldata relayerContext,
    bytes calldata adapterCalldata
) internal returns (bytes4 ret) {
    assembly ("memory-safe") {
        let adapterDataLen := adapterCalldata.length
        let contextLen := relayerContext.length
        let totalSize := add(adapterDataLen, add(contextLen, 0x20))
        
        let freeMemPtr := mload(0x40)
        let dataPtr := freeMemPtr
        
        // Copy adapter calldata to memory
        calldatacopy(dataPtr, adapterCalldata.offset, adapterDataLen)
        
        // Copy relayer context after adapter calldata
        let contextPtr := add(dataPtr, adapterDataLen)
        calldatacopy(contextPtr, relayerContext.offset, contextLen)
        
        // Append context length as uint256
        mstore(add(contextPtr, contextLen), contextLen)
        
        // Perform delegatecall
        let success := delegatecall(
            gas(),
            adapter,
            dataPtr,
            totalSize,
            0x00,
            0x00
        )
        
        if iszero(success) {
            revert(0x00, 0x04)
        }
        
        returndatacopy(0x00, 0x00, 0x20)
        ret := mload(0x00)
    }
}

Delegatecall Security Model

Critical Security Implications:
  • Adapters execute with Router’s storage and balance
  • msg.sender and msg.value are preserved from original call
  • address(this) equals Router address during execution
  • Storage writes affect Router’s state, not adapter’s state
  • Never make direct calls to untrusted contracts from adapters
// During delegatecall:
address(this) == ROUTER_ADDRESS  // ✅ True
msg.sender == ORIGINAL_CALLER    // ✅ Preserved
msg.value == ORIGINAL_VALUE      // ✅ Preserved

// Storage operations:
storage = ROUTER_STORAGE         // ⚠️ Writes to Router!

Relayer Context

Adapters receive solver-specific context data appended to calldata:
AdapterBase.sol
function _loadRelayerContext() internal pure returns (
    uint256 contextLength,
    bytes calldata relayerContext
) {
    assembly ("memory-safe") {
        let totalSize := calldatasize()
        contextLength := calldataload(sub(totalSize, 0x20))
        relayerContext.offset := sub(totalSize, add(contextLength, 0x20))
        relayerContext.length := contextLength
    }
}

Calldata Format

The Router appends relayer context to adapter calldata:
[function_selector][function_params][relayer_context][context_length]
|<---- 4 bytes --->|<-- variable -->|<-- variable -->|<-- 32 bytes -->|
Example Usage:
SameChainAdapter.sol
function _tokenInRecipient() internal pure returns (address tokenInRecipient) {
    (uint256 relayerContextLength, bytes calldata relayerContext) = _loadRelayerContext();
    require(relayerContextLength == 20, InvalidRelayerContext());
    return address(bytes20(relayerContext[:20]));
}

Adapter Types

Fill Adapters

Handle settlement operations that fulfill user orders:
SameChainAdapter.sol
function samechain_compact_handleFill(
    FillDataCompact calldata fillData
) external payable onlyViaRouter returns (bytes4) {
    // 1. Pre-fund recipient with output tokens
    _prefundRecipient({
        from: msg.sender,
        to: fillData.order.recipient,
        tokenOut: fillData.order.tokenOut
    });
    
    // 2. Call arbiter to validate and unlock input tokens
    (address sponsor, uint256 nonce) = SameChainArbiter(ARBITER)
        .handleCompact_NotarizedChain({
            order: fillData.order,
            sigs: fillData.userSigs,
            otherElements: fillData.otherElements,
            allocatorData: fillData.allocatorData,
            relayer: _tokenInRecipient()
        });
    
    emit RouterFilled(sponsor, nonce);
    return this.samechain_compact_handleFill.selector;
}

Claim Adapters

Handle resource unlock operations:
function handleClaim(bytes calldata claimData) 
    external payable onlyViaRouter returns (bytes4) 
{
    // Decode claim parameters
    (address user, address token, uint256 amount) = 
        abi.decode(claimData, (address, address, uint256));
    
    // Unlock resources from protocol
    ARBITER.unlockResources(user, token, amount);
    
    return this.handleClaim.selector;
}

Implementation Requirements

1

Inherit from AdapterBase

contract MyAdapter is AdapterBase {
    constructor(address router, address arbiter) 
        AdapterBase(router, arbiter) 
    { }
}
2

Return Function Selector

function myFillFunction() external returns (bytes4) {
    // ... implementation ...
    return this.myFillFunction.selector;
}
3

Implement ERC165

function supportsInterface(bytes4 selector) 
    public pure override returns (bool) 
{
    return selector == this.myFillFunction.selector
        || super.supportsInterface(selector);
}
4

Use onlyViaRouter

function myFillFunction() 
    external 
    onlyViaRouter 
    returns (bytes4) 
{
    // Safe to execute in Router context
}

Example: IntentExecutorAdapter

A passthrough adapter that forwards calls to an intent executor:
IntentExecutorAdapter.sol
contract IntentExecutorAdapter is AdapterBase {
    address internal immutable EXECUTOR;
    
    constructor(address router, address executor) 
        AdapterBase(router, address(0)) 
    {
        EXECUTOR = executor;
    }
    
    function handleFill_intentExecutor_handleCompactTargetOps(
        bytes calldata executorCalldata
    ) external payable onlyViaRouter returns (bytes4) {
        // Forward call to executor
        EXECUTOR.passthrough(
            COMPACT_INTENT_SELECTOR,
            executorCalldata
        );
        return this.handleFill_intentExecutor_handleCompactTargetOps.selector;
    }
    
    function supportsInterface(bytes4 selector) 
        public pure override returns (bool) 
    {
        return selector == this.handleFill_intentExecutor_handleCompactTargetOps.selector
            || super.supportsInterface(selector);
    }
}

Adapter Registration

Adapters are registered with the RouterManager:
// Install new adapter
routerManager.installFillAdapter(
    bytes2(0x0001),  // Protocol version
    bytes4(MyAdapter.myFill.selector),
    address(myAdapter)
);

Adapter Tags

Adapters can specify metadata tags for routing behavior:
function ADAPTER_TAG() external pure virtual returns (bytes12 adapterTag) {
    // Skip relayer context for this adapter
    return Constants.DEFAULT_ADAPTER_TAG.setSkipRelayerContext();
}
  • SKIP_RELAYER_CONTEXT: Adapter doesn’t consume relayer context
  • Additional flags can be defined in future versions

Security Best Practices

Validate Delegatecall

Always use onlyViaRouter modifier on external functions

Trusted Protocols Only

Only integrate with well-audited, trusted protocols

Return Selectors

All fill/claim functions must return their selector

Implement ERC165

Add all function selectors to supportsInterface

Gas Optimizations

Adapters can optimize gas usage:
// ✅ Use assembly for calldata operations
function _loadRelayerContext() internal pure {
    assembly ("memory-safe") {
        // Direct calldata access
    }
}

// ✅ Cache storage reads
address arbiter = ARBITER;  // SLOAD once

// ✅ Use unchecked for safe arithmetic
unchecked {
    ++i;
}

Arbiters

Learn about arbiter layer and resource unlocking

Router System

Understand how Router coordinates adapters