Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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 order

Settlement 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 credit

Utility 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 claim

Command 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, SETTLE

Example: 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
);
Example: WETH → USDC → DAI
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));
Example: Place Limit Order
// 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));
Example: Claim and Settle
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

  1. Batch operations: Combine multiple commands to save on lock overhead
  2. Use SETTLE_ALL/TAKE_ALL: Cheaper than specific amounts when clearing all
  3. Order matters: Put operations that may fail early if using allow failure
  4. 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 > deadline

Related Concepts