Limit orders works by creating offers on mangrove via a shared maker contract: MangroveOrder. In addition, each user will have an associated router (called UserRouter here). This router will hold our approvals and will be called by MangroveOrder to fetch and send back the tokens when the offer are matched.
On mangrove order there are 4 types of orders:
GTC (Good Til Cancelled): Will do a market order up to the given price and post the residual volume as an order mangrove.
IOC (Immediate or cancel): Equivalent to a markt order only
FOK (Fill or kill): Only does a market order, if the market is not filled, then we revert
PO (Post only): Does not try a market order and immediately posts order to mangrove.
An order posted on Mangrove has an optional expiry date. Because an offer can fail, the creator of the offer has to deposit a provision. On failure (missing approvals for example), the provision will be used as a bounty sent to the person that tried to execute the offer. This provision can be withdrawn anytime once the offer is not live anymore, and if it has not been used.
The offer is consumed as follows:
The user router is a deterministic address linked to each user. It will be deployed in initial take call if not already. So the first thing to do is to give approval to this router (even though it has no code yet). For infinite approval implementation, see Market orders.
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,
});
}
Creating a limit order
So let's implement a post only limit buy order for 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";
const markets = await getOpenMarkets();
const market = markets[0];
if (!market) {
throw new Error("No market found");
}
const router = await getUserRouter();
// buying the token
// give allowance for the quote asset
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);
}
}
const book = await getBook(market);
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,
});
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 need to have the offer id, as well as the market side, and then just pass it as params:
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 these params.