Core Classes
The vifdk provides several core classes for working with tokens, markets, offers, and order books.
Token
The Token class represents an ERC20 token with Vif-specific unit configuration.
Creating Tokens
import { Token } from "vifdk";
const USDC = Token.from(
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // address
6, // decimals
"USDC", // symbol
1n, // unit (optional, default 1n)
);Units
Units define the precision level for amounts stored in Vif. Amounts are floored to the nearest unit:
// Set custom unit (e.g., 0.0001 USDC precision)
const USDC2 = Token.from(address, 6, "USDC", 10_000n);
// Create new token with different unit
const USDC_HIGHER_PRECISION = USDC.withUnit(100n);Native Token
Token.NATIVE_TOKEN; // ETH with 18 decimals, 1 wei unitOverride if needed:
import { zeroAddress } from "viem";
Token.NATIVE_TOKEN = Token.from(zeroAddress, 18, "ETH", 1n);Provision Token
Token.PROVISION_TOKEN; // Native token with gwei unit (10^9)TokenAmount
Represents an amount of a specific token with automatic unit flooring.
Creating Amounts
// From string (parsed using token decimals)
const amount1 = USDC.amount("1000.5");
// amount1.amount === 1000500000n
// From bigint (raw amount)
const amount2 = USDC.amount(1000500000n);
// Both methods auto-floor to nearest unit
const USDC_UNITS = Token.from(address, 6, "USDC", 10_000n);
const amount3 = USDC_UNITS.amount("1000.50001");
// Floored to 1000.5000 (removed the last digit)Properties and Methods
const amount = USDC.amount("1000.5");
// Get raw amount (bigint)
amount.amount; // 1000500000n
// Set raw amount
amount.amount = 2000000000n;
// Get normalized amount (in units)
amount.normalizedAmount; // amount / token.unit
// Get formatted string
amount.amountString; // '1000.5'
// Set from string
amount.amountString = "2000";
// Access token
amount.token; // Token instance
// Copy amount
const copy = amount.copy();Market
The Market class represents a trading pair with both directions (asks and bids).
Creating Markets
import { Market } from "vifdk";
const WETH = Token.from(WETH_ADDRESS, 18, "WETH", 1n);
const market = Market.create({
base: WETH, // Or TokenAmount for min offer size
quote: USDC, // Or TokenAmount for min offer size
tickSpacing: 1n,
askFees: 3000, // Optional: 0.3% (in parts per million)
bidsFees: 3000, // Optional: 0.3% (in parts per million)
});Market Directions
// Asks: Makers sell WETH for USDC (buyers get WETH)
market.asks;
// Bids: Makers buy WETH with USDC (sellers give WETH)
market.bids;Market Keys
Each direction has a unique identifier:
market.asksKey; // keccak256(base, baseUnit, quote, quoteUnit, tickSpacing)
market.bidsKey; // keccak256(quote, quoteUnit, base, baseUnit, tickSpacing)Price Conversion
Convert between price and tick:
// Price to tick (for asks: USDC per WETH)
const tick = market.asks.price(3500); // 3500 USDC/WETH
// tick.value is the int24 tick value
// Tick to price
const price = Number(tick.price) / 2 ** 128;Fee Calculations
// Calculate fees on an amount
const feeAmount = USDC.amount("1000");
const fees = market.asks.computeFees(feeAmount);
// Returns TokenAmount with fee
// Get amount excluding fees
const amountWithFees = USDC.amount("1000");
const amountExcludingFees = market.asks.excludingFees(amountWithFees);
// Inverse of computeFeesFrom Open Markets
Create markets from VifReader results:
import { openMarkets, Market as MarketClass } from "vifdk";
const openMarketsResult = await client.readContract({
address: VIF_READER_ADDRESS,
...openMarkets(),
});
// Convert to Market instances
const DAI = Token.from(DAI_ADDRESS, 18, "DAI", 1n);
const WETH2 = Token.from(WETH_ADDRESS, 18, "WETH", 1n);
const marketInstances = openMarketsResult
.map((m) => MarketClass.fromOpenMarketResult(m, [WETH2, USDC, DAI]))
.filter((m) => m !== undefined);SemiMarket
Represents one direction of a market (either asks or bids).
const asks = market.asks;
asks.outboundToken; // Token makers are selling (WETH for asks)
asks.inboundToken; // Token makers are buying (USDC for asks)
asks.market.tickSpacing;
asks.market.base;
asks.market.quote;Tick
Represents a price level in the order book.
import { Tick } from "vifdk";
// From tick value
const tick1 = Tick.fromValue(10000n, 1n);
// From price (estimate)
const tick2 = Tick.fromPrice(1.0001, true, 1n);
// Get price from tick
const tickPrice = tick1.price; // Fixed point 128.128 number
// Convert outbound to inbound
const outbound = WETH.amount("1");
const inbound = tick1.inboundFromOutbound(outbound, USDC);Offer
Represents a limit order in the book.
import { Offer, rawOffer, offerOwner } from "vifdk";
const offerId = 42;
// Query the offer
const packedOffer = await client.readContract({
address: "0x0000000000000000000000000000000000000000" as Address,
...rawOffer(market.asks, offerId),
});
// Get the owner
const ownerBigInt = await client.readContract({
address: "0x0000000000000000000000000000000000000000" as Address,
...offerOwner(market.asks, offerId),
});
const owner = `0x${ownerBigInt.toString(16).padStart(40, "0")}` as Address;
// Create Offer instance
const offer = Offer.fromPacked(market.asks, packedOffer, offerId, owner);
// Access offer data
offer.data.gives; // TokenAmount
offer.data.received; // TokenAmount
offer.data.tick; // Tick
offer.owner; // AddressOfferList
Represents a list of offers from the order book.
import { OfferList, packedOfferList } from "vifdk";
const [nextOfferId, offerIds, packedOffers, owners] =
await client.readContract({
address: VIF_READER_ADDRESS,
...packedOfferList(market.asks, 0, 100),
});
const offerList = OfferList.fromPacked(
market.asks,
offerIds,
packedOffers,
owners,
);
// Iterate offers
for (const o of offerList.offers) {
console.log(o.data.gives.amountString);
}
// Simulate order
const result = offerList.simulateOrder({
amount: WETH.amount("1"),
maxTick: market.asks.price(3600),
});
console.log("Got:", result.got.amountString);
console.log("Gave:", result.gave.amountString);Book
Represents aggregated price levels from the order book.
import { Book, packedBook } from "vifdk";
const [, packedElements] = await client.readContract({
address: VIF_READER_ADDRESS,
...packedBook(market.asks, 0, 100),
});
const book = Book.fromPacked(market.asks, packedElements);
// View price levels
for (const element of book.elements) {
const elementPrice = Number(element.tick.price) / 2 ** 128;
console.log("Price:", elementPrice);
console.log("Total:", element.totalGives.amountString);
console.log("Count:", element.offerCount);
}
// Simulate order
const bookResult = book.grossSimulation(WETH.amount("1"));
console.log("Got:", bookResult.got.amountString);Best Practices
- Define tokens once - Create token instances at app startup and reuse them
- Use strings for amounts - Avoid floating point precision issues
- Choose appropriate units - Balance precision with gas costs
- Cache markets - Create Market instances once and share them
Related Concepts
- VifRouter - Building transactions
- Router Actions - Available actions
- Viewing Offers - Querying order books
- Markets & Units - Protocol documentation