Creating Limit Orders
Learn how to place limit orders (make offers) on the Vif order book using vifdk.
What is a Limit Order?
A limit order in Vif is called a "make" operation. When you create a limit order, you:
- Provide liquidity to the order book at a specific price
- Lock tokens that will be sold when takers match your order
- Optionally set an expiration time with a provision
Basic Limit Order
Setup
import { Token, Market, VifRouter, execute } from "vifdk";
import { createWalletClient, erc20Abi, http, publicActions } from "viem";
import { mainnet } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
// Define tokens
const WETH = Token.from(WETH_ADDRESS, 18, "WETH", 1n);
const USDC = Token.from(USDC_ADDRESS, 6, "USDC", 1n);
// Create market
const market = Market.create({
base: WETH,
quote: USDC,
tickSpacing: 1n,
});
// Initialize router
const router = new VifRouter(VIF_ROUTER_ADDRESS, VIF_CORE_ADDRESS, chainId);
// Create client
const client = createWalletClient({
chain: mainnet,
transport: http(),
account: privateKeyToAccount(privateKey),
}).extend(publicActions);Approve Tokens
// Approve WETH for the limit order
await client.writeContract({
address: WETH_ADDRESS,
abi: erc20Abi,
functionName: "approve",
args: [VIF_CORE_ADDRESS, WETH.amount("1").amount],
});Create Limit Order
// Build the action
const actions = router
.createTypedActions()
.limitSingle({
market: market.asks, // Sell WETH for USDC
gives: WETH.amount("1"), // Sell 1 WETH
tick: market.asks.price(3500), // At 3500 USDC per WETH
})
.build({
addRecommendedActions: true, // Automatically adds settleAll(WETH)
receiver: client.account.address,
});
// Get transaction data
const { commands, args } = actions.txData();
// Simulate first
const { result, request } = await client.simulateContract({
address: VIF_ROUTER_ADDRESS,
...execute(commands, args),
account: client.account,
});
// Parse simulation result
const [{ data: simResult }] = actions.parseSimulationResult(result);
console.log("Offer will be created with ID:", simResult.offerId);
// Execute
const receipt = await client.writeContractSync(request);
// Parse receipt
const [{ data: orderResult }] = actions.parseLogs(receipt.logs);
console.log("Created offer ID:", orderResult?.offerId);Limit Order with Expiration
Adding an expiration requires a provision in native token:
const expiryDate = new Date(Date.now() + 86400000); // 24 hours
const actionsWithExpiry = router
.createTypedActions()
.limitSingle({
market: market.asks,
gives: WETH.amount("1"),
tick: market.asks.price(3500),
expiry: expiryDate, // Set expiration
provision: Token.PROVISION_TOKEN.amount("0.001"), // 0.001 ETH provision
})
.build({
addRecommendedActions: true, // Automatically adds settleAll(WETH) and settleAll(NATIVE_TOKEN)
receiver: client.account.address,
});
const { commands: expiryCommands, args: expiryArgs } =
actionsWithExpiry.txData();
// Calculate required native token for provision
const value = actionsWithExpiry.expectedValue({
globalProvision: Token.PROVISION_TOKEN.amount("0.001"),
});
// Execute with value
const receiptWithExpiry = await client.writeContractSync({
address: VIF_ROUTER_ADDRESS,
...execute(expiryCommands, expiryArgs),
value: value.amount,
});Using Native Token (ETH)
To create a limit order selling ETH directly:
const actionsWithNative = router
.createTypedActions()
.wrapNative(Token.NATIVE_TOKEN.amount("1")) // Wrap ETH to WETH first
.limitSingle({
market: market.asks,
gives: WETH.amount("1"),
tick: market.asks.price(3500),
})
.build({
addRecommendedActions: true, // Automatically adds settleAll(WETH) and settleAll(NATIVE_TOKEN)
receiver: client.account.address,
});
const nativeValue = actionsWithNative.expectedValue({
globalProvision: Token.PROVISION_TOKEN.amount("0.001"),
});
// Send ETH with transaction
const { commands: nativeCommands, args: nativeArgs } =
actionsWithNative.txData();
const receiptWithNative = await client.writeContractSync({
address: VIF_ROUTER_ADDRESS,
...execute(nativeCommands, nativeArgs),
value: nativeValue.amount,
});Placing Multiple Orders
Create a ladder of limit orders at different price levels:
const actionsMultiple = router
.createTypedActions()
.limitSingle({
market: market.asks,
gives: WETH.amount("1"),
tick: market.asks.price(3400),
})
.limitSingle({
market: market.asks,
gives: WETH.amount("1"),
tick: market.asks.price(3500),
})
.limitSingle({
market: market.asks,
gives: WETH.amount("1"),
tick: market.asks.price(3600),
})
.build({
addRecommendedActions: true, // Automatically adds settleAll(WETH) once for all orders
receiver: client.account.address,
});
// This creates 3 offers with one transaction
const { commands: multipleCommands, args: multipleArgs } =
actionsMultiple.txData();
const receiptMultiple = await client.writeContractSync({
address: VIF_ROUTER_ADDRESS,
...execute(multipleCommands, multipleArgs),
});Common Patterns
// Sell Order (Ask) - Sell base token (WETH) for quote token (USDC)
router.createTypedActions().limitSingle({
market: market.asks,
gives: WETH.amount("1"),
tick: market.asks.price(3500),
});
// Buy Order (Bid) - Buy base token (WETH) with quote token (USDC)
router.createTypedActions().limitSingle({
market: market.bids,
gives: USDC.amount("3500"),
tick: market.bids.price(3500),
});
// Price to tick
const tick = market.asks.price(3500);
console.log("Tick value:", tick.value);Best Practices
- Always simulate first - Catch errors before spending gas
- Check approvals - Use
expectedAllowances()to verify approvals - Verify balance - Ensure you have enough tokens
- Set appropriate provision - For expiring orders, provision should cover gas costs
- Use appropriate tick spacing - Respect market's tick spacing
Next Steps
- Edit Limit Orders - Update existing orders
- Cancel Limit Orders - Remove orders from book
- Claim Limit Orders - Collect filled amounts
- Router Actions - All available actions