Maker contract
Overview
A maker contract is a smart contract bound to an offer on Mangrove. When a market order matches that offer, Mangrove calls the maker contract to execute its logic.
Maker contracts define two callbacks:
Trade Execution — via
makerExecuteTrade Posthook — via
makerPosthook
Trade Execution
function makerExecute(MgvLib.SingleOrder calldata order)
internal
returns (uint gasused, bytes32 makerData);The logic associated with an offer must be implemented in the makerExecute callback function. (See data structures for SingleOrder type).
Example
import {IERC20, IMaker, SingleOrder} from "@mgv/src/core/MgvLib.sol";
contract MyOffer is IMaker {
// IMangrove mgv = IMangrove(payable(<address of Mangrove>));
// Mangrove contract
IMangrove mgv = IMangrove(payable(mgv));
address reserve; // token reserve for inbound tokens
// an example of offer execution that simply verifies that `this` contract has enough outbound tokens to satisfy the market order.
function makerExecute(MgvLib.SingleOrder calldata order) external returns (bytes32 makerData) {
// revert below (in case of insufficient funds) to signal mangrove we renege on trade
// reverting as soon as early to minimize bounty
require(
IERC20(order.offer.gives()).balanceOf(address(this)) >= order.offer.wants(),
"MyOffer/NotEnoughFunds");
// do not perform any state changing call if caller is not Mangrove!
require(msg.sender == mgv, "MyOffer/OnlyMangroveCanCallMe");
// `order.gives` has been transfered by Mangrove to `this` balance
// sending incoming tokens to reserve
IERC20(order.offer.wants()).transfer(reserve, order.offer.gives());
// this string will be passed to `makerPosthook`
return "MyOffer/tradeSuccess";
}
}Inputs
order — struct containing order execution data:
olKey — identifies the offer list
outbound_tkn — token the offer sends
inbound_tkn — token the offer receives
tickSpacing — spacing between valid ticks
offerId — Id of the matched offer
offer — information about the matched offer
takerWants — amount of outbound tokens the taker wants to receive
takerGives — amount of inbound tokens the taker gives
offerDetail — optional detail about the offer (only populated when needed)
global — snapshot of Mangrove’s global configuration.
local — snapshot of local (market-level) configuration, cleaned of tick tree info.
The protocol guarantees that
takerGives / takerWantsmatches the offer price within precision limits.
Outputs
gasused— is the gas consumed by the execution.makerData— arbitrary data returned by the maker. Passed directly tomakerPosthook.
Important Notes
Security
Always require
msg.sender == address(Mangrove).The prev/next pointers from an offer are removed before sending it to the maker. This ensures that the maker has no information about the state of the book when it gets called.
Failing Early
To renege on a trade, revert with a string reason (castable to
bytes32).Example:
revert("MyOffer/NotEnoughFunds");Failing later (e.g., after partial execution) increases gas cost and bounty loss.
Gas Efficiency
The taker’s bounty is proportional to gas consumed during failure.
Check conditions early to minimize gas usage on revert.
Reentrancy Protection
Mangrove prevents reentrancy during
makerExecute.
Locked Offer List
During
makerExecute, the offer list is temporarily locked. Offers cannot be added, removed, or updated at this stage. UsemakerPosthookto modify offers after execution.
Trade Posthook
After makerExecute returns, Mangrove calls the maker’s posthook to handle updates or clean-up.
function makerPosthook(
MgvLib.SingleOrder calldata order,
MgvLib.OrderResult calldata result
) external;This function is optional but useful for reposting offers, updating states, or tracking results.
Example
import {IERC20, IMaker, SingleOrder, OrderResult, MgvStructs} from "@mgv/src/core/MgvLib.sol";
abstract contract MakerContract is IMaker {
// context
// IMangrove mgv = IMangrove(payable(<address of Mangrove>));
// Mangrove contract
IMangrove mgv = IMangrove(payable(mgv));
// Example of post-hook
// if market order was a success, try to repost residual offer at the same price
function makerPosthook(MgvLib.SingleOrder calldata order, MgvLib.OrderResult calldata result) external {
require (msg.sender == mgv, "posthook/invalid_caller");
if (result.mgvData == "mgv/tradeSuccess") {
// retrieving offer data
// the following call to updateOfferByTick will revert if:
// * `this` MakerContract doesn't have enough provision on Mangrove for the offer
// * the residual/(GASREQ+offer_gasbase) is below Mangrove's minimal density
// * NB : a reverting posthook does not revert the offer execution
// update the offer with the "ByTick" version
mgv.updateOfferByTick(
order.olkey, // same offer list
order.offer.tick, // same tick
order.offer.gives() - TickLib.inboundFromOutbound(order.offer.ticktick, order.takerWants()), // what the offer was giving, minus what the taker took (wants)
order.offerDetail.gasreq(), // keep offer's current gasreq
order.offerDetail.gasprice(), // keep offer's current gasprice
order.offerId // ID of the offer to be updated
);
}
}
}Inputs
oder(SingleOrder)is the same as in
makerExecute.
result(OrderResult) contains:makerData — message returned or revert reason from
makerExecute.mgvData —Internal Mangrove status code — indicates trade result (e.g. "mgv/tradeSuccess", "mgv/offerFail").
Outputs
None.
Important Notes
Security
Always ensure:
require(msg.sender == address(Mangrove), "posthook/invalid_caller");
Gas Handling
The posthook runs with the remaining gas from the offer’s
gasreq, after subtracting the gas used bymakerExecute.
Reposting Offers
The offer list is unlocked during
makerPosthook. You can:Repost the same offer with a new price or volume.
Add new offers.
Remove or update existing ones.
Reverting
Reverting during
makerPosthookdoes not cancel the trade. Settlement frommakerExecuteremains valid.
Last updated