Limit Orders

Limit orders on Mangrove work by creating offers via a shared maker contract called MangroveOrder. Each user interacts with the system through their own User Router, a contract that holds token approvals and is called by MangroveOrder to transfer tokens when offers are matched.

In essence:

  • You post an offer to buy or sell tokens at a defined price.

  • Mangrove matches it when a counter party executes a trade that meets your conditions.

  • The router handles token transfers securely on your behalf.

creating an order (via MangroveOrder)

Order Types

MangroveOrder supports four types of orders:

Type
Description

GTC (Good Til Cancelled)

Executes a market order up to a given price, then posts any remaining volume as a limit order.

IOC (Immediate or Cancel)

Executes as a market order; any unfilled portion is discarded.

FOK (Fill or Kill)

Executes only if the full order can be filled; otherwise reverts.

PO (Post Only)

Posts directly to the order book without attempting immediate execution.

Note: Each order can have an optional expiry date. When creating an offer, you must deposit a provision (a small amount of collateral). If the offer later fails (e.g. missing approvals), this provision is used as a bounty for the executor. You can withdraw any unused provision once the offer is no longer active.

The offer is consumed as follows:

consuming an offer (via Mangrove Order)

Creating a User Router

Each user’s router is a deterministic address that is derived from their account. It’s automatically deployed during the first transaction that needs it. Before posting any order, you must grant approval to this router (even before it’s deployed).

src/router.ts
import type { Address } from "viem";
import { client } from "./client";

export async function getUserRouter(): Promise<Address> {
  return client.getUserRouter({
    user: client.account.address,
  });
}

For infinite approval logic, see Market orders.

Creating a Limit Order

So let's take a look at an example where we create a Post Only limit buy order, buying 1 ETH with 2500 USDC:

src/limit-order.ts
import { limitOrderParams } from "@mangrovedao/mgv/builder";
import { giveAllowanceIfNeeded } from "./allowance";
import { getBook } from "./book";
import { mangroveConfig } from "./config";
import { getOpenMarkets } from "./markets";
import { getUserRouter } from "./router";
import { BS, Order } from "@mangrovedao/mgv/lib";
import { parseEther, parseUnits } from "viem";
import { client } from "./client";

// 1. Retrieve markets and select one
const markets = await getOpenMarkets();
const market = markets[0];
if (!market) {
  throw new Error("No market found");
}

// 2. Get the User Router address
const router = await getUserRouter();

// 3. Approve router to spend quote token
const allowance = await giveAllowanceIfNeeded(market.quote.address, router);

if (allowance) {
  if (allowance.status === "success") {
    console.log("Allowance given");
  } else {
    console.error("Allowance failed");
    process.exit(1);
  }
}

// 4. Get the current orderbook for this market
const book = await getBook(market);

// 5. Build the order parameters
const params = limitOrderParams(market, {
  orderType: Order.PO, // post only order
  baseAmount: parseEther("1"), // buy 1 ETH
  quoteAmount: parseUnits("2500", 6), // 2500 USDC
  restingOrderGasreq: 250_000n, // 250k gas (fixed)
  bs: BS.buy,
  book,
  // no expiry or expiry timestamp in seconds
  expiryDate: undefined, 
});

// 6. Simulate and execute the order
const {
  // takerGot, takerGave, bounty and fee should be 0 since PO
  // offerId should be defined (not 0)
  result: { takerGot, takerGave, bounty, fee, offerId, offerWriteData },
  request,
} = await client.simulateContract({
  address: mangroveConfig.mgvOrder,
  ...params,
  account: client.account,
});

const tx = await client.writeContract(request);
const receipt = await client.waitForTransactionReceipt({
  hash: tx,
});

console.log(receipt.status, receipt.transactionHash);

Retracting a Limit Order

To cancel or modify a limit order, you'll need to have its offerId, as well as the market side (BS.buy or BS.sell).

src/cancel-limit.ts
import { removeOrderParams } from "@mangrovedao/mgv/builder";
import { BS } from "@mangrovedao/mgv/lib";
import { getOpenMarkets } from "./markets";
import { mangroveConfig } from "./config";
import { client } from "./client";

const markets = await getOpenMarkets();

const market = markets[0];
if (!market) {
  throw new Error("No market found");
}

// Build the parameters
const params = removeOrderParams(market, {
  offerId: 1n,
  bs: BS.buy,
  deprovision: true, // withdraw the provisions (true by default)
});

// Returns the withdrawn provision
const { result: provision, request } = await client.simulateContract({
  address: mangroveConfig.mgvOrder,
  ...params,
  account: client.account,
});

const tx = await client.writeContract(request);

const receipt = await client.waitForTransactionReceipt({
  hash: tx,
});

console.log(receipt.status, receipt.transactionHash);

Modifying a Limit Order

In order to modify a limit order, any updateOrder***Params function may be used to build the modification, and then it should call MangroveOrder with those parameters.

Last updated