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.
Quick Start Guide
This guide will help you integrate Warp Router into your solver or relayer in minutes. By the end, you’ll understand how to execute fill and claim operations, manage solver contexts, and optimize for gas efficiency.
Prerequisites
Before you begin, ensure you have:
Solidity ^0.8.28 development environment
Basic understanding of delegatecall patterns
Familiarity with either TheCompact or Permit2 protocols
Installation
Install dependencies
Add Warp Router to your project dependencies: forge install rhinestonewtf/warp-router
Import the Router interface
Import the Router interface in your contract: import { IRouter } from "warp-router/src/interfaces/IRouter.sol" ;
Connect to the Router
Initialize the Router in your contract: contract MySolver {
IRouter public immutable router;
constructor ( address _router ) {
router = IRouter (_router);
}
}
Understanding Solver Context
The solver context is a critical component that allows you to pass settlement-specific data to adapters. Each adapter defines its own context format.
Solver Context Format
When the Router calls an adapter, it appends the solver context using:
abi . encodePacked (adapterCalldata, relayerContext, uint256 (relayerContext.length))
The resulting calldata structure looks like this:
Example: SameChainAdapter Context
The SameChainAdapter expects the simplest possible context - just the recipient address for input tokens:
src/arbiters/samechain/SameChainAdapter.sol
function _tokenInRecipient () internal pure returns ( address tokenInRecipient ) {
( uint256 relayerContextLength, bytes calldata relayerContext) = _loadRelayerContext ();
require (relayerContextLength == 20 , InvalidRelayerContext ());
// The first 20 bytes are the tokenIn recipient address
return address ( bytes20 (relayerContext[ : 20 ]));
}
For solvers integrating with SameChainAdapter:
// Prepare solver context - just the recipient address
address myRecipientAddress = 0x123 ...; // Where you want input tokens sent
bytes memory relayerContext = abi . encodePacked (myRecipientAddress);
// Call the router with this context
router. routeFill (relayerContext, adapterCalldata);
Always check the adapter’s documentation to understand its expected context format. Incorrect context encoding will cause transactions to revert.
Executing Fill Operations
Fill operations are the core of settlement execution. Here’s how to execute them:
Standard Fill Route
Prepare adapter calldata
Encode your fill operation with the 4-byte function selector: bytes memory adapterCalldata = abi . encodeWithSelector (
ISameChainAdapter.samechain_compact_handleFill.selector,
fillData // Your fill parameters
);
Prepare solver context
Create your solver context with settlement-specific data: // For SameChainAdapter: just the recipient address
address tokenInRecipient = msg.sender ;
bytes memory relayerContext = abi . encodePacked (tokenInRecipient);
Execute the fill
Call the router to execute the fill: router. routeFill (relayerContext, adapterCalldata);
Complete Fill Example
contract MySolver {
IRouter public immutable router;
function executeFill (
Types . Order calldata order ,
Types . Signatures calldata signatures ,
bytes32 [] calldata otherElements ,
bytes calldata allocatorData
) external {
// 1. Prepare fill data
ISameChainAdapter.FillDataCompact memory fillData =
ISameChainAdapter. FillDataCompact ({
order : order,
userSigs : signatures,
otherElements : otherElements,
allocatorData : allocatorData
});
// 2. Encode adapter calldata
bytes memory adapterCalldata = abi . encodeWithSelector (
ISameChainAdapter.samechain_compact_handleFill.selector,
fillData
);
// 3. Prepare solver context
bytes memory relayerContext = abi . encodePacked ( msg.sender );
// 4. Execute fill
router. routeFill (relayerContext, adapterCalldata);
}
}
Fill operations require atomic signatures from the Router’s designated atomic signer. Ensure you have proper authorization before executing fills.
Executing Batch Fills (Optimized)
For gas-optimized batch operations, use the optimized_routeFill921336808 function:
Prepare multiple adapter calldatas
Create an array of adapter calldatas for your batch: bytes [] memory adapterCalldatas = new bytes []( orderCount );
for ( uint i = 0 ; i < orderCount; i ++ ) {
adapterCalldatas[i] = abi . encodeWithSelector (
ISameChainAdapter.samechain_compact_handleFill.selector,
fillDataArray[i]
);
}
Encode and hash the batch
Encode the array and compute its hash: bytes memory encoded = abi . encode (adapterCalldatas);
bytes32 hash = keccak256 (encoded);
Get atomic signature
Obtain a signature from the atomic signer: // Off-chain: sign the hash with the atomic signer's private key
bytes memory atomicSig = getAtomicSignature (hash);
Prepare solver contexts
Create context for each operation: bytes [] memory relayerContexts = new bytes []( orderCount );
for ( uint i = 0 ; i < orderCount; i ++ ) {
relayerContexts[i] = abi . encodePacked (recipientAddresses[i]);
}
Execute optimized batch
Call the optimized fill function: router. optimized_routeFill921336808 (
relayerContexts,
encoded,
atomicSig
);
The optimized batch function provides significant gas savings through adapter caching and reduced calldata overhead. Use it for all multi-operation batches.
Executing Claim Operations
Claim operations unlock user resources from protocols. Unlike fills, they don’t require atomic signatures:
Single Claim
function executeClaim (
Types . Order calldata order ,
Types . Signatures calldata signatures
) external {
// Prepare claim calldata
bytes memory claimCalldata = abi . encodeWithSelector (
ISameChainAdapter.samechain_compact_handleClaim.selector,
order,
signatures
);
// Prepare context (if needed by adapter)
bytes memory relayerContext = abi . encodePacked ( msg.sender );
// Execute claim
router. routeClaim (relayerContext, claimCalldata);
}
Batch Claims
function executeBatchClaims (
Types . Order [] calldata orders ,
Types . Signatures [] calldata signatures
) external {
uint256 claimCount = orders.length;
bytes [] memory claimCalldatas = new bytes []( claimCount );
bytes [] memory relayerContexts = new bytes []( claimCount );
for ( uint i = 0 ; i < claimCount; i ++ ) {
claimCalldatas[i] = abi . encodeWithSelector (
ISameChainAdapter.samechain_compact_handleClaim.selector,
orders[i],
signatures[i]
);
relayerContexts[i] = abi . encodePacked ( msg.sender );
}
router. routeClaim (relayerContexts, claimCalldatas);
}
Claim operations rely on protocol-level authorization (TheCompact or Permit2 signatures) rather than atomic Router signatures.
Gas Optimization Tips
Batch Similar Operations Group operations using the same adapter to leverage caching. Each cache hit saves approximately 2,100 gas.
Use Optimized Routes The optimized_routeFill921336808 function provides significant gas savings through efficient encoding and caching.
Special Selectors Use built-in selectors like singleCall and multiCall when possible to avoid adapter overhead (saves 2,600+ gas).
Minimize Context Size Keep solver context as small as possible to reduce calldata costs, especially for batch operations.
Working with Same-Chain Settlements
Same-chain settlements follow a specific 3-step pattern:
Pre-funding
Solver pre-funds the recipient with output tokens before claiming input tokens: src/arbiters/samechain/SameChainAdapter.sol
function samechain_compact_handleFill ( FillDataCompact calldata fillData )
external payable onlyViaRouter returns ( bytes4 )
{
// Extract the solver's recipient address for input tokens
address tokenInRecipient = _tokenInRecipient ();
// Pre-fund the user with output tokens
_prefundRecipient ( msg.sender , fillData.order.recipient, fillData.order.tokenOut);
// ...
}
Pre-funding prevents order manipulation attacks where malicious actors could front-run settlements.
Resource unlock
Arbiter validates signatures and unlocks user’s input resources: SameChainArbiter (ARBITER). handleCompact_NotarizedChain ({
order : fillData.order,
sigs : fillData.userSigs,
otherElements : fillData.otherElements,
elementOffset : 0 ,
notarizedChainId : block .chainid,
allocatorData : fillData.allocatorData,
relayer : tokenInRecipient // Solver receives input tokens here
});
Completion
The arbiter transfers input tokens to the solver’s designated recipient, completing the atomic swap.
Security Considerations
Always validate signatures before executing operations. The Router’s atomic signature system is your primary security mechanism.
For Solvers
Fill operations require valid atomic signatures from the Router’s designated atomic signer. Never attempt to bypass signature validation.
Ensure your solver context matches the adapter’s expected format. Incorrect encoding will cause reverts and wasted gas.
All operations in a batch succeed or revert together. Design your batches carefully to avoid partial execution scenarios.
Account for pre-claim operation gas stipends when estimating total gas costs. Pre-claim operations receive dedicated gas allocations.
Common Patterns
Pattern 1: Simple Token Swap
// Execute a simple same-chain token swap
function executeSwap (
Types . Order calldata order ,
Types . Signatures calldata sigs
) external {
bytes memory fillCalldata = abi . encodeWithSelector (
ISameChainAdapter.samechain_compact_handleFill.selector,
ISameChainAdapter. FillDataCompact ({
order : order,
userSigs : sigs,
otherElements : new bytes32 []( 0 ),
allocatorData : ""
})
);
router. routeFill (
abi . encodePacked ( msg.sender ),
fillCalldata
);
}
Pattern 2: Batch Operations with Same Adapter
// Maximize gas efficiency by batching operations using the same adapter
function executeBatchSwaps (
Types . Order [] calldata orders ,
Types . Signatures [] calldata sigs ,
bytes memory atomicSig
) external {
bytes [] memory calldatas = new bytes []( orders . length );
bytes [] memory contexts = new bytes []( orders . length );
for ( uint i = 0 ; i < orders.length; i ++ ) {
calldatas[i] = abi . encodeWithSelector (
ISameChainAdapter.samechain_compact_handleFill.selector,
ISameChainAdapter. FillDataCompact ({
order : orders[i],
userSigs : sigs[i],
otherElements : new bytes32 []( 0 ),
allocatorData : ""
})
);
contexts[i] = abi . encodePacked ( msg.sender );
}
router. optimized_routeFill921336808 (
contexts,
abi . encode (calldatas),
atomicSig
);
}
Next Steps
Architecture Guide Dive deep into the Router, Adapter, and Arbiter layers
API Reference Explore the complete API documentation
Advanced Patterns Learn advanced integration patterns and optimizations
Protocol Adapters Explore available protocol adapters and their capabilities