Markets & Units
Markets in Vif are the fundamental trading pairs, uniquely identified by their configuration parameters. Understanding how markets work and how to configure token units is essential for optimal protocol usage.
Market Identification
Each market is uniquely identified by a keccak256 hash of five parameters:
struct MarketParams {
address outboundToken; // Token makers are selling
uint64 outboundUnits; // Unit size for outbound amounts
address inboundToken; // Token makers are buying
uint64 inboundUnits; // Unit size for inbound amounts
uint16 tickSpacing; // Minimum tick distance
}
bytes32 marketId = keccak256(abi.encodePacked(
outboundToken,
outboundUnits,
inboundToken,
inboundUnits,
tickSpacing
));Market Directionality
Markets are directional - a WETH/USDC market is different from a USDC/WETH market:
// WETH/USDC Market (asks)
// Makers sell WETH, buy USDC
marketId1 = keccak256(WETH, units1, USDC, units2, spacing);
// USDC/WETH Market (bids)
// Makers sell USDC, buy WETH
marketId2 = keccak256(USDC, units2, WETH, units1, spacing);
// These are DIFFERENT markets
marketId1 != marketId2- Asks market: Makers sell base asset (WETH), buy quote asset (USDC)
- Bids market: Makers sell quote asset (USDC), buy base asset (WETH)
Creating Markets
Only the protocol owner can create new markets:
function openMarket(
address outboundToken,
address inboundToken,
uint64 outboundUnits,
uint64 inboundUnits,
uint16 tickSpacing,
uint24 fee,
uint24 minOutboundUnits
) external onlyOwner returns (bytes32 marketId);outboundToken/inboundToken: Token addressesoutboundUnits/inboundUnits: Unit sizes (must be powers of 10)tickSpacing: Minimum tick distance between offersfee: Initial fee in basis pointsminOutboundUnits: Minimum offer size in units
Example: WETH/USDC Pair
// Create asks market (sell WETH for USDC)
bytes32 asksMarket = vif.openMarket(
WETH, // outboundToken
USDC, // inboundToken
1e13, // outboundUnits (0.00001 WETH)
1e4, // inboundUnits (0.0001 USDC)
1, // tickSpacing
0, // fee (0 bps)
1000 // minOutboundUnits (0.01 WETH minimum)
);
// Create bids market (sell USDC for WETH)
bytes32 bidsMarket = vif.openMarket(
USDC, // outboundToken
WETH, // inboundToken
1e4, // outboundUnits
1e13, // inboundUnits
1, // tickSpacing
0, // fee
1000 // minOutboundUnits (0.1 USDC minimum)
);Token Units
Units are the fundamental precision level for token amounts in Vif. They allow packing amounts into 48 bits for storage efficiency.
Why Units?
Without units, storing a full uint256 amount requires:
- 32 bytes per amount in storage
- Extremely expensive for high-volume order books
With units, amounts fit in 6 bytes (48 bits):
uint48can represent up to 281,474,976,710,655 units- Storage cost reduced by ~81%
How Units Work
All amounts are floored to the nearest unit:
actualAmount = floor(requestedAmount / units) * units;Example with USDC (units = 1e4, decimals = 6):
units = 10_000; // 0.0001 USDC precision
// Maker wants to offer 1.000011 USDC
requestedAmount = 1_000_011; // 1.000011 USDC in 6 decimals
// Actual amount stored
actualAmount = (1_000_011 / 10_000) * 10_000
= 100_001 * 10_000
= 1_000_010; // 1.00001 USDC
// Lost precision: 1 wei (0.000001 USDC)Choosing Units
Units should be powers of 10 for readability. Choose based on:
- Token decimals
- Expected price ranges
- Minimum meaningful amounts
units = 10^(decimals - precisionDigits)Where precisionDigits is how many significant digits you want to preserve.
Examples by Token Type
Stablecoins (USDC, USDT, DAI)
// USDC (6 decimals)
units = 1e4; // 10,000 wei = 0.0001 USDC
// Max offer: 281T units * 1e4 / 1e6 = 2.8 billion USDC
// Min tick move: ~0.0001 USDC on $1000 offer
// DAI (18 decimals)
units = 1e16; // 0.01 DAI
// Max offer: 281T units * 1e16 / 1e18 = 2.8 billion DAIMajor Assets (WETH, WBTC)
// WETH (18 decimals)
units = 1e13; // 0.00001 WETH
// Max offer: 281T units * 1e13 / 1e18 = 2.8 million WETH
// At $3000/WETH = $8.4 billion max
// Min tick move: ~$0.03 on 1 WETH offer
// WBTC (8 decimals)
units = 1e3; // 0.00001 WBTC
// Max offer: 281T units * 1e3 / 1e8 = 2.8 million WBTCLow-Value Tokens (PEPE, SHIB)
// PEPE (18 decimals, price ~$0.000001)
units = 1e18; // 1 whole PEPE
// Max offer: 281 trillion PEPE
// At $0.000001 = $281 million maxHigh-Value Tokens (Rare NFT fractions)
// Fractionalized NFT (18 decimals, price ~$10,000/unit)
units = 1e10; // 0.00000001 units
// Max offer: 281T units * 1e10 / 1e18 = 2,814 units
// At $10,000 = $28 million maxUnits and Tick Precision
The combination of units and tick spacing determines effective price precision:
// WETH/USDC market
outboundUnits = 1e13; // 0.00001 WETH
inboundUnits = 1e4; // 0.0001 USDC
tickSpacing = 1;
// Price changes by 0.001% per tick
// On a 1 WETH offer:
// 1 tick = 1 WETH * 0.00001 = 0.00001 WETH = $0.03 (at $3000 WETH)
// Effective price grid:
// ..., 2999.97, 3000.00, 3000.03, 3000.06, ...Minimum Offer Sizes
Markets enforce a minimum outbound amount to prevent dust:
minOutboundUnits = 1000; // Set during market creation
outboundUnits = 1e13; // WETH units
// Minimum WETH offer:
minOffer = minOutboundUnits * outboundUnits
= 1000 * 1e13
= 1e16 wei
= 0.01 WETHMarket Metadata
Each market stores additional metadata:
struct Market {
bool isActive; // Can be toggled by owner
uint24 fee; // Protocol fee in basis points
uint24 minOutboundUnits; // Minimum offer size
uint256 totalFees; // Accumulated protocol fees
// ... other fields
}isActive- Pause/unpause tradingfee- Adjust protocol feesminOutboundUnits- Change minimum order size
- Token addresses
- Units
- Tick spacing
Multi-Market Strategies
Since markets are directional, you often need both directions:
Order Book Display
// Get asks (sell WETH for USDC)
Market asks = {
outboundToken: WETH,
inboundToken: USDC,
outboundUnits: 1e13,
inboundUnits: 1e4,
tickSpacing: 1
};
// Get bids (sell USDC for WETH)
Market bids = {
outboundToken: USDC,
inboundToken: WETH,
outboundUnits: 1e4,
inboundUnits: 1e13,
tickSpacing: 1
};
// Display spread:
// Best ask: lowest tick in asks market
// Best bid: lowest tick in bids market (inverted for display)Market Making
// Place offers on both sides
function provideLiquidity() external {
bytes memory data = abi.encode(...);
vif.lock(data);
}
function lockCallback(bytes calldata) external returns (bytes memory) {
// Sell WETH at higher price (ask)
vif.make(maker, asksMarket, 0, 1 ether, askTick, 0, 0);
// Sell USDC at lower price (bid)
vif.make(maker, bidsMarket, 0, 3000e6, bidTick, 0, 0);
// Settle both
_settle(WETH, 1 ether);
_settle(USDC, 3000e6);
return "";
}
// Now earning spread on both sides ✓Gas Considerations
Market parameters affect gas costs:
| Parameter | Impact on Gas |
|---|---|
tickSpacing = 1 | More price levels, higher matching cost |
tickSpacing = 10 | Fewer price levels, lower matching cost |
outboundUnits small | More precision, but same gas |
outboundUnits large | Less precision, same gas |
Recommendation: Use larger tick spacing (10-100) for volatile or low-volume pairs to reduce gas costs.
Related Concepts
- Tick Tree Structure - Price level organization
- Units - Detailed unit explanation
- Creating Swaps - Using markets for trading