Using VifRouter
The built-in VifRouter is a command-based router that provides a flexible interface for interacting with the Vif protocol. It uses a dispatcher pattern to execute multiple operations in a single transaction.
Overview
VifRouter allows you to:
- Execute market orders (swaps)
- Place and manage limit orders
- Batch multiple operations
- Handle token wrapping/unwrapping
- Manage settlements efficiently
All operations use a command-based approach where you encode commands and their arguments to execute complex workflows.
Basic Usage Pattern
// 1. Encode commands and arguments
bytes memory commands = hex"0005"; // ORDER_SINGLE, SETTLE_ALL
bytes[] memory args = new bytes[](2);
args[0] = abi.encode(...); // Order arguments
args[1] = abi.encode(...); // Settlement arguments
// 2. Execute with optional deadline
vifRouter.execute{value: ethAmount}(commands, args, deadline);Command Types
Order Commands
// From Commands.sol
uint8 constant ORDER_SINGLE = 0x00; // Single market order
uint8 constant ORDER_MULTI = 0x01; // Multi-hop market order
uint8 constant LIMIT_SINGLE = 0x02; // Create limit order
uint8 constant CLAIM = 0x03; // Claim filled amounts
uint8 constant CANCEL = 0x04; // Cancel limit orderSettlement Commands
uint8 constant SETTLE = 0x05; // Settle specific amount
uint8 constant TAKE = 0x06; // Take specific amount
uint8 constant SETTLE_ALL = 0x07; // Settle all debt
uint8 constant TAKE_ALL = 0x08; // Take all creditUtility Commands
uint8 constant SWEEP = 0x09; // Sweep tokens from router
uint8 constant WRAP_NATIVE = 0x0a; // Wrap ETH to WETH
uint8 constant UNWRAP_NATIVE = 0x0b; // Unwrap WETH to ETH
uint8 constant AUTHORIZE = 0x0c; // Authorize operator
uint8 constant CLEAR_ALL = 0x0d; // Clear all dust
uint8 constant CLEAR_UPTO_OR_CLAIM = 0x0e; // Clear dust or claimCommand Encoding
Commands are encoded as single bytes where:
- Bits 0-3: Command type (0x00 - 0x0f)
- Bit 7: Allow failure flag (0x80)
// Normal command
bytes1 cmd = 0x00; // ORDER_SINGLE
// Allow failure (won't revert if fails)
bytes1 cmdCanFail = 0x80; // ORDER_SINGLE with allow failure
// Batch commands
bytes memory commands = hex"00070"; // ORDER_SINGLE, SETTLE_ALL, SETTLEExample: Simple Swap
import {Commands} from "vif-core/libraries/periphery/Commands.sol";
contract MyContract {
IVifRouter public router;
IVif public vif;
struct MarketParams {
bytes32 marketId;
int24 maxTick;
uint256 fillVolume;
bool fillWants;
uint256 maxOffers;
}
function swap(
MarketParams memory params,
address tokenIn,
uint256 deadline
) external returns (uint256 amountOut) {
// Approve router
IERC20(tokenIn).approve(address(router), params.fillVolume);
// Build commands
bytes memory commands = abi.encodePacked(
bytes1(Commands.ORDER_SINGLE),
bytes1(Commands.SETTLE_ALL),
bytes1(Commands.TAKE_ALL)
);
// Build arguments
bytes[] memory args = new bytes[](3);
// ORDER_SINGLE arguments
args[0] = abi.encode(
params.marketId,
params.maxTick,
params.fillVolume,
params.fillWants,
params.maxOffers
);
// SETTLE_ALL arguments
args[1] = abi.encode(
tokenIn,
msg.sender
);
// TAKE_ALL arguments (token will be determined by order)
args[2] = abi.encode(
address(0), // Will use the output token from order
msg.sender
);
// Execute
DispatchResult[] memory results = router.execute(
commands,
args,
deadline
);
// Decode result
(,amountOut,,) = abi.decode(
results[0].data,
(uint256, uint256, uint256, uint256)
);
}
}ORDER_SINGLE Command
Execute a single market order.
Arguments
struct OrderSingleArgs {
bytes32 marketId;
int24 maxTick;
uint256 fillVolume;
bool fillWants;
uint256 maxOffers;
}
// Encode
bytes memory args = abi.encode(
marketId,
maxTick,
fillVolume,
fillWants,
maxOffers
);Returns
struct OrderResult {
uint256 gave; // Amount paid
uint256 got; // Amount received
uint256 fee; // Fee paid
uint256 bounty; // Bounty earned
}
// Decode from DispatchResult
(uint256 gave, uint256 got, uint256 fee, uint256 bounty) =
abi.decode(result.data, (uint256, uint256, uint256, uint256));ORDER_MULTI Command
Execute a multi-hop market order (exact input only).
Arguments
struct OrderMultiArgs {
bytes32[] marketIds;
int24[] maxTicks;
uint256 fillVolume;
uint256[] maxOffers;
}
// Encode
bytes memory args = abi.encode(
marketIds,
maxTicks,
fillVolume,
maxOffers
);bytes32[] memory markets = new bytes32[](2);
markets[0] = wethUsdcMarketId;
markets[1] = usdcDaiMarketId;
int24[] memory maxTicks = new int24[](2);
maxTicks[0] = type(int24).max; // No price limit
maxTicks[1] = type(int24).max;
uint256[] memory maxOffers = new uint256[](2);
maxOffers[0] = 100;
maxOffers[1] = 100;
bytes memory args = abi.encode(
markets,
maxTicks,
1 ether, // Spend 1 WETH
maxOffers
);
bytes memory commands = abi.encodePacked(
bytes1(Commands.ORDER_MULTI),
bytes1(Commands.SETTLE_ALL),
bytes1(Commands.TAKE_ALL)
);LIMIT_SINGLE Command
Create or edit a limit order.
Arguments
struct LimitSingleArgs {
bytes32 marketId;
uint40 initialOfferId; // 0 for new
uint256 gives;
int24 tick;
uint32 expiry;
uint24 provision;
}
// Encode
bytes memory args = abi.encode(
marketId,
initialOfferId,
gives,
tick,
expiry,
provision
);Returns
struct LimitResult {
uint40 offerId;
uint256 claimedReceived;
}
// Decode
(uint40 offerId, uint256 claimed) =
abi.decode(result.data, (uint40, uint256));// Approve tokens
WETH.approve(address(router), 1 ether);
// Build command
bytes memory commands = abi.encodePacked(
bytes1(Commands.LIMIT_SINGLE),
bytes1(Commands.SETTLE_ALL),
bytes1(Commands.TAKE_ALL)
);
bytes[] memory args = new bytes[](3);
// Create offer selling 1 WETH
args[0] = abi.encode(
marketId,
uint40(0), // New offer
1 ether, // Give 1 WETH
-2_054_990, // Price tick
uint32(0), // No expiry
uint24(0) // No provision
);
// Settle WETH debt
args[1] = abi.encode(address(WETH), msg.sender);
// Take any claimed credits (if editing)
args[2] = abi.encode(address(0), msg.sender);
DispatchResult[] memory results = router.execute(commands, args);
(uint40 offerId,) = abi.decode(results[0].data, (uint40, uint256));
console.log("Created offer ID:", offerId);CLAIM and CANCEL Commands
CLAIM Arguments
// Claim filled amounts
bytes memory args = abi.encode(
marketId,
offerId
);CANCEL Arguments
// Cancel and claim all
bytes memory args = abi.encode(
marketId,
offerId
);Returns (both)
struct ClaimResult {
uint256 inbound;
uint256 outbound;
uint256 provision;
}
(uint256 inbound, uint256 outbound, uint256 provision) =
abi.decode(result.data, (uint256, uint256, uint256));bytes memory commands = abi.encodePacked(
bytes1(Commands.CLAIM),
bytes1(Commands.TAKE_ALL)
);
bytes[] memory args = new bytes[](2);
args[0] = abi.encode(marketId, offerId);
args[1] = abi.encode(address(0), msg.sender); // Take all credits
router.execute(commands, args);Settlement Commands
SETTLE_ALL
Settle all debt for a token:
// Arguments
bytes memory args = abi.encode(
token,
from // Address to pull from
);TAKE_ALL
Take all credit for a token:
// Arguments
bytes memory args = abi.encode(
token,
receiver // Address to send to
);SETTLE (specific amount)
bytes memory args = abi.encode(
token,
amount,
from
);TAKE (specific amount)
bytes memory args = abi.encode(
token,
amount,
receiver
);Utility Commands
WRAP_NATIVE
Wrap ETH to WETH:
// Arguments
bytes memory args = abi.encode(
amountOrAll // Amount or type(uint256).max for all
);
// Must send ETH with call
router.execute{value: 1 ether}(commands, args);UNWRAP_NATIVE
Unwrap WETH to ETH:
bytes memory args = abi.encode(
amountOrAll,
receiver
);SWEEP
Send all tokens from router to receiver:
bytes memory args = abi.encode(
token,
receiver
);AUTHORIZE
Authorize an operator on behalf of msg.sender:
bytes memory args = abi.encode(
operator,
authorized // true/false
);Advanced Patterns
Batch Multiple Limit Orders
// Place 3 limit orders in one transaction
bytes memory commands = abi.encodePacked(
bytes1(Commands.LIMIT_SINGLE),
bytes1(Commands.LIMIT_SINGLE),
bytes1(Commands.LIMIT_SINGLE),
bytes1(Commands.SETTLE_ALL),
bytes1(Commands.TAKE_ALL)
);
bytes[] memory args = new bytes[](5);
// Three offers at different prices
args[0] = abi.encode(marketId, 0, 1 ether, tick1, 0, 0);
args[1] = abi.encode(marketId, 0, 1 ether, tick2, 0, 0);
args[2] = abi.encode(marketId, 0, 1 ether, tick3, 0, 0);
// Settle total outbound
args[3] = abi.encode(WETH, msg.sender);
// Take any credits
args[4] = abi.encode(address(0), msg.sender);
router.execute(commands, args);
// Created 3 offers with one transaction! ✓Swap with Slippage Protection
// Calculate minimum output
uint256 expectedOut = quoteSwap(marketId, amountIn);
uint256 minOut = expectedOut * 95 / 100; // 5% slippage
// Execute swap
bytes memory commands = abi.encodePacked(
bytes1(Commands.ORDER_SINGLE),
bytes1(Commands.SETTLE_ALL),
bytes1(Commands.TAKE_ALL)
);
bytes[] memory args = new bytes[](3);
args[0] = abi.encode(marketId, maxTick, amountIn, false, 100);
args[1] = abi.encode(tokenIn, msg.sender);
args[2] = abi.encode(tokenOut, msg.sender);
DispatchResult[] memory results = router.execute(commands, args);
(, uint256 got,,) = abi.decode(
results[0].data,
(uint256, uint256, uint256, uint256)
);
require(got >= minOut, "Slippage too high");Flash Loan Pattern
// Borrow, arbitrage, repay in one transaction
bytes memory commands = abi.encodePacked(
bytes1(Commands.TAKE), // Borrow
bytes1(Commands.ORDER_SINGLE), // Arbitrage swap
bytes1(Commands.SETTLE), // Repay
bytes1(Commands.TAKE_ALL) // Take profit
);
bytes[] memory args = new bytes[](4);
// Borrow 100 WETH
args[0] = abi.encode(WETH, 100 ether, address(this));
// Swap on external DEX (handled separately)
args[1] = abi.encode(...); // Market order args
// Repay 100 WETH
args[2] = abi.encode(WETH, 100 ether, address(this));
// Keep profit
args[3] = abi.encode(address(0), msg.sender);
router.execute(commands, args);Error Handling
Allow Failure Flag
Commands with bit 7 set (0x80) can fail without reverting the entire transaction:
// This command can fail
bytes1 cmdCanFail = bytes1(uint8(Commands.ORDER_SINGLE) | 0x80);
bytes memory commands = abi.encodePacked(
cmdCanFail, // Try order, continue if fails
bytes1(Commands.LIMIT_SINGLE) // Always execute this
);Checking Results
DispatchResult[] memory results = router.execute(commands, args);
for (uint256 i = 0; i < results.length; i++) {
if (!results[i].success) {
// Command i failed
// results[i].data contains error data
}
}Gas Optimization Tips
- Batch operations: Combine multiple commands to save on lock overhead
- Use SETTLE_ALL/TAKE_ALL: Cheaper than specific amounts when clearing all
- Order matters: Put operations that may fail early if using allow failure
- Minimize commands: Each command has dispatch overhead
Common Pitfalls
❌ Wrong: Forgetting to settle
bytes memory commands = abi.encodePacked(
bytes1(Commands.ORDER_SINGLE)
// Missing SETTLE_ALL / TAKE_ALL - will revert!
);✓ Correct: Always settle and take
bytes memory commands = abi.encodePacked(
bytes1(Commands.ORDER_SINGLE),
bytes1(Commands.SETTLE_ALL),
bytes1(Commands.TAKE_ALL)
);❌ Wrong: Wrong argument encoding
// ORDER_SINGLE expects 5 arguments
bytes memory args = abi.encode(marketId, maxTick); // Only 2! ❌✓ Correct: Match command signature
bytes memory args = abi.encode(
marketId,
maxTick,
fillVolume,
fillWants,
maxOffers
); // All 5 arguments ✓Transaction Deadline
Protect against stale transactions:
uint256 deadline = block.timestamp + 5 minutes;
router.execute(commands, args, deadline);
// Reverts if block.timestamp > deadlineRelated Concepts
- Creating Swaps - Market order concepts
- Placing Limit Orders - Limit order concepts
- Building Custom Routers - Roll your own
- Flash Accounting - Understanding deltas