Makers

Market Orders

A market order is the simplest way for a taker to buy or sell assets on Mangrove. Each order is executed against a specific offer list, which defines the outbound and inbound tokens of the trade.

The takes specifies:

  • how much the want to trade (either in terms of the token they want to receive or the one they are willing to spend), and

  • the maximum price (or tick) they ware willing to accept.

This ensures that trades are execute only within a controller price range.

Market Orders in Mangrove vs. TradFi

In traditional finance, a market order executes immediately at the best available price. However, on-chain trading is exposed to MEV (front-running and sandwich attacks), making such unconstrained market orders unsafe.

For this reason, Mangrove's "market order" corresponds to a TradFi limit order:

It executes immediately, but only at prices equal to or better than the user's specified limit.

This "safe market order" model ensures that users never pay beyond their defined limit price.

Execution Flow

When a market order is submitted, Mangrove's matching engine processes offers in sequence:

  1. It scans offers from the best tick upward (asks) or lowest tick downward (bids).

  2. Offers are consumed one by one, in FIFO order for the same tick.

  3. Matching continues until:

    1. all offers within the taker's maxTick are processed,

    2. the offer list ends, or

    3. the taker's desired volume is fully filled.

For each matched offer:

  1. Mangrove computes the trade amounts:

inboundAmount=outboundAmount×1,0001tickinboundAmount = outboundAmount \times 1,0001^{tick}
  1. The inbound token is sent to the offer maker.

  2. The offer logic (makerExecute) is called.

  3. If successful, the outbound tokens are sent to the taker.

  4. If the call fails, the trade is reverted and the taker receives a bounty for the gas spent.

Function Overview

Mangrove provides two entry points for market orders:

function marketOrderByTick(
  OLKey memory olKey,
  Tick maxTick,
  uint fillVolume,
  bool fillWants,
) external returns (uint takerGot, uint takerGave, uint bounty, uint feePaid);

function marketOrderByVolume(
    OLKey memory olKey,
    uint takerWants,
    uint takerGives,
    bool fillWants
) external returns (uint takerGot, uint takerGave, uint bounty, uint feePaid);

Both return:

  • takerGot, amount of outbound_tkn the taker received. Includes any partial fills and reflects fees already applied (if the offer list charges one).

  • takerGave, amount of inbound_tkn the taker spent to execute the order.

  • bounty, amount of native tokens (in wei) the taker earned as compensation for executing failing offers. Bounties are deducted from the failing maker’s provision.

  • feePaid, amount of outbound_tkn transferred to Mangrove’s vault as payment for any configured offer list fee.

The *ByVolume variant is a wrapper around *ByTick, converting the volume ratio to the corresponding tick, rounding down to the nearest tick allowed on the offer list, such that the taker does not pay more than specified.

Inputs

marketOrderByTick(olKey, maxTick, fillVolume, fillWants)

olKey Identifies the offer list:

  • outbound_tkn — token to buy (received by taker)

  • inbound_tkn — token to pay (spent by taker)

  • tickSpacing — allowed tick spacing

maxTick Maximum tick that can be matched with this order.

fillVolume & fillWants Specify how much the taker wants to trade and when the order stops:

  • If fillWants = truefillVolume is in outbound_tkn (the taker specifies how much they want to receive).

  • If fillWants = falsefillVolume is in inbound_tkn (the taker specifies how much they want to spend). The order ends when the specified volume is reached or no more eligible offers remain.


marketOrderByVolume(olKey, takerWants, takerGives, fillWants)

olKey Identifies the offer list (same structure as above).

takerWants & takerGives Define the desired trade amounts:

  • takerWants — amount of outbound_tkn the taker wants to receive.

  • takerGives — amount of inbound_tkn the taker is willing to spend.

max ratio=takerGivestakerWants\text{max ratio} = \frac{\text{takerGives}}{\text{takerWants}}

This ratio is converted into a corresponding maxTick (rounded down).

fillWants Controls when the order stops:

  • If true, the order ends when the taker has received takerWants.

  • If false, the order ends when the taker has spent takerGives.

Example: DAI/WETH Market

Tick
Ratio (WETH/DAI)
Offer ID
Gives (DAI)

-75103

0.0005476

77

925.26

-75103

0.0005476

177

916.47

-75041

0.0005510

42

871.76

Buying DAI

A taker calls marketOrderByTick on the DAI/WETH offer list with:

  • maxTick = -75000 -> corresponds to a max ratio of 0.0005539

  • fillVolume = 2,500×1018 2,500 \times 10^{18}

  • fillWants = true

Since fillWants = true, we have:

  • fillVolume is in olKey.outbound_tkn and corresponds to 2,500 DAI

  • the market order will be considered filled once 2,500 DAI has been received.

In other words, this is a buy order for 2,500 DAI, where the taker is willing to pay up to 2,500×0.0005539=1.3832 2,500 × 0.0005539 = 1.3832 WETH.

The market order will execute as follows:

  1. Get 925.26 DAI for 925.26 * 0.0005476 = 0.5067 WETH from offer #77

  2. Get 916.47 DAI for 916.47 * 0.0005476 = 0.5019 WETH from offer #177

  3. Get the remaining 2500 - 925.26 - 916.47 = 658.27 DAI for 658.27 * 0.0005510 = 0.3627 WETH from offer #42.

In total, the taker gets 2,500 DAI and sends 0.5067 + 0.5019 + 0.3627 = 1.3713 WETH.

Selling WETH

A taker calls marketOrderByTick on the offer list with:

  • maxTick = -75000 -> corresponds to a max ratio of 0.0005539

  • fillVolume = 1.2×1018 1.2 \times 10^{18}

  • fillWants = false

Because fillWants = false, the taker specifies how much they want to spend:

  • fillVolume refers to 1.2 WETH (the inbound_tkn).

  • The market order will end once 1.2 WETH has been sent.

This corresponds to a sell order of 1.2 WETH. At the maximum allowed ratio, the taker expects to receive at least: 1.2÷0.0005539=2,168.84 1.2 ÷ 0.0005539 = 2,168.84 DAI.

Execution proceeds as follows:

  1. Sell 925.26 * 0.0005476 = 0.5067 WETH for 925.26 DAI to offer #77

  2. Sell 916.47 * 0.0005476 = 0.5019 WETH for 916.47 DAI to offer #177

  3. Sell the remaining 1.2 - 0.5067 - 0.5019 = 0.1914 WETH for 0.1914 / 0.0005510 = 347.37 DAI to offer #42.

In total, the taker gets 925.26 + 916.47 + 347.37 = 2,189.10 DAI and sends 1.2 WETH.

Market Order Behavior

Suppose you want to trade a base token (B) against a quote token (Q), on a market with tick spacing T.

  • Market buy:

    Buy x units of B using Q. → Call marketOrderByTick on (B, Q, T) with: fillWants = true, fillVolume = x, and

    maxTick = log₁.₀₀₀₁(priceLimit).

  • Market sell:

    Sell x units of B for Q. → Call marketOrderByTick on (Q, B, T) with: fillWants = false, fillVolume = x, and maxTick = −log₁.₀₀₀₁(priceLimit).

Order Residuals

Unlike traditional exchanges, any unfilled portion of a market order (the residual) is not posted to the order book. The order simply ends partially filled once the price limit is reached.

If you want GTC-style (“Good Till Cancelled”) behavior, this can be implemented using a maker contract. The MangroveOrder contract in the Strat Library provides GTC and other advanced order types.

Bounties for Failing Offers

If an offer fails to deliver the promised liquidity, the taker receives a bounty in native tokens to compensate for gas costs. This bounty is deducted from the maker’s provision, the deposit they supplied when posting the offer. See Offer provisions for details.

Token Allowances

Mangrove transfers tokens using transferFrom. If the taker’s allowance for Mangrove (on the inbound token) is too low, the transaction will revert.

Active Offer Lists

Each offer list in Mangrove can be active or inactive, and the Mangrove contract itself can be alive or dead. Orders can only be executed when both Mangrove and the selected offer list are active.

Last updated