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:
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:
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
Execution Context
Security Validation
Wrong Usage
// 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!
modifier onlyViaRouter () {
// When called via delegatecall from Router:
// address(this) == _ROUTER ✅
require ( address ( this ) == _ROUTER, OnlyDelegateCall ());
_ ;
}
// ❌ WRONG: Direct call bypasses security
adapter. handleFill (data);
// ✅ CORRECT: Delegatecall via Router
router. routeFill (contexts, calldatas, signature);
Relayer Context
Adapters receive solver-specific context data appended to calldata:
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
}
}
The Router appends relayer context to adapter calldata:
[function_selector][function_params][relayer_context][context_length]
|<---- 4 bytes --->|<-- variable -->|<-- variable -->|<-- 32 bytes -->|
Example Usage:
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:
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
Inherit from AdapterBase
contract MyAdapter is AdapterBase {
constructor ( address router , address arbiter )
AdapterBase (router, arbiter)
{ }
}
Return Function Selector
function myFillFunction () external returns ( bytes4 ) {
// ... implementation ...
return this .myFillFunction.selector;
}
Implement ERC165
function supportsInterface ( bytes4 selector )
public pure override returns ( bool )
{
return selector == this .myFillFunction.selector
|| super . supportsInterface (selector);
}
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)
);
// Apply patch upgrade
routerManager. hotfixFillAdapter (
bytes2 ( 0x0001 ),
bytes4 (MyAdapter.myFill.selector),
address (newAdapter)
);
// Remove adapter
routerManager. retireFillAdapter (
bytes2 ( 0x0001 ),
bytes4 (MyAdapter.myFill.selector)
);
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