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 Market Orders

Learn how to execute market orders (swaps) on the Vif order book using vifdk.

What is a Market Order?

A market order is a "take" operation that matches against existing limit orders on the book. When you create a market order, you:

  • Specify the maximum amount you want to give (spend)
  • Receive tokens by taking from existing offers at the best available prices
  • Execute immediately as a taker (no waiting for fills)

Basic Market Order

Step 1: Setup

First, ensure you have:

  • Tokens defined
  • Market created
  • VifRouter initialized
  • User authorized the router
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);
 
const client = createWalletClient({
  chain: mainnet,
  transport: http(),
  account: privateKeyToAccount(privateKey),
}).extend(publicActions);

Step 2: Authorize Router

const authData = router.authorizationData(
  client.account.address,
  0n,
  new Date(Date.now() + 1000 * 60 * 60 * 24 * 30)
);
const typedData = router.singatureDataForAuthorization(authData);
const signature = await client.signTypedData(typedData);

Step 3: Approve Tokens

await client.writeContractSync({
  address: USDC_ADDRESS,
  abi: erc20Abi,
  functionName: "approve",
  args: [VIF_CORE_ADDRESS, USDC.amount("3500").amount],
});

Step 4: Create Market Order

// Buy WETH with 3500 USDC
const actions = router
  .createTypedActions()
  .authorize({ authorization: authData, signature })
  .orderSingle({
    market: market.bids,
    fillVolume: USDC.amount("3500"),
    maxTick: market.bids.price(4000),
  })
  .build({
    addRecommendedActions: true,
    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),
});
 
const [, { data: simulationResult }] = actions.parseSimulationResult(result);
 
console.log("Will receive:", simulationResult.got.amountString, "WETH");
console.log("Will spend:", simulationResult.gave.amountString, "USDC");
console.log("Fees:", simulationResult.fee.amountString, "USDC");
 
// Execute
const receipt = await client.writeContractSync(request);
 
const [, { data: orderResult }] = actions.parseLogs(receipt.logs);
console.log("Received:", orderResult?.got.amountString, "WETH");
console.log("Spent:", orderResult?.gave.amountString, "USDC");
console.log("Fees:", orderResult?.fee.amountString, "USDC");

Sell Order (Ask Side)

Selling base token (WETH) for quote token (USDC):

// Sell base token (WETH) for quote token (USDC)
const sellActions = router
  .createTypedActions()
  .orderSingle({
    market: market.asks, // Take from asks to sell WETH
    fillVolume: WETH.amount("1"), // Sell 1 WETH
    maxTick: market.asks.price(3400), // At least 3400 USDC/WETH price
  })
  .build({
    addRecommendedActions: true, // Automatically adds settleAll(WETH) and takeAll(USDC)
    receiver: client.account.address,
  });

Using Native Token (ETH)

To swap ETH directly without wrapping first:

// Swap ETH directly without wrapping first
const wrapActions = router
  .createTypedActions()
  .wrapNative(Token.NATIVE_TOKEN.amount("1")) // Wrap ETH to WETH
  .orderSingle({
    market: market.asks,
    fillVolume: WETH.amount("1"),
    maxTick: market.asks.price(3400),
  })
  .build({
    addRecommendedActions: true, // Automatically adds settleAll(NATIVE_TOKEN/WETH) and takeAll(USDC)
    receiver: client.account.address,
  });
 
const value = wrapActions.expectedValue({
  globalProvision: Token.PROVISION_TOKEN.amount("0"),
});
 
// Send ETH with transaction
const { commands: wrapCommands, args: wrapArgs } = wrapActions.txData();
const receiptWrap = await client.writeContractSync({
  address: VIF_ROUTER_ADDRESS,
  ...execute(wrapCommands, wrapArgs),
  value: value.amount,
});

Receiving Native Token (ETH)

To receive ETH instead of WETH:

// Receive ETH instead of WETH
const unwrapActions = router
  .createTypedActions()
  .orderSingle({
    market: market.asks,
    fillVolume: USDC.amount("3500"),
    maxTick: market.asks.price(4000),
  })
  .unwrapNative(WETH.amount("1")) // Unwrap WETH to ETH
  .build({
    addRecommendedActions: true, // Automatically adds settleAll(USDC) and takeAll(NATIVE_TOKEN)
    receiver: client.account.address,
  });

Slippage Protection

Control the trade outcome with fillVolume and maxTick:

Exact Input - Spend exact amount:

// Exact Input - Spend exact amount with slippage protection
router.createTypedActions().orderSingle({
  market: market.bids,
  fillVolume: USDC.amount("3500"), // Spend exactly 3500 USDC
  maxTick: market.bids.price(3850), // Max 3850 USDC per WETH (10% slippage)
});

Exact Output - Receive exact amount:

// Exact Output - Receive exact amount with slippage protection
router.createTypedActions().orderSingle({
  market: market.asks,
  fillVolume: WETH.amount("1"), // Buy exactly 1 WETH
  maxTick: market.asks.price(3850), // Pay up to 3850 USDC per WETH
});

Error Handling

Common errors to handle:

  • Insufficient Liquidity - Not enough offers in the order book
  • Slippage Exceeded - Received less than minReceives
  • Insufficient Balance - Not enough input tokens

Next Steps