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

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

  1. Always simulate first - Catch errors before spending gas
  2. Check approvals - Use expectedAllowances() to verify approvals
  3. Verify balance - Ensure you have enough tokens
  4. Set appropriate provision - For expiring orders, provision should cover gas costs
  5. Use appropriate tick spacing - Respect market's tick spacing

Next Steps