New offers will mostly be posted by maker contracts able to source liquidity when asked by Mangrove.
Offers posted via maker contracts are called %%smart offers|smart-offer%% - as opposed to %%on-the-fly offers|on-the-fly-offer%% made from EOA's.
Offers on Mangrove can be posted in two ways which differ in how the "price" (%%ratio|ratio%%) is specified:
newOfferByTick: The "price" is specified as a tick.
newOfferByVolume: The "price" is specified as the ratio between two volumes, wants/gives.
The *ByVolume variant is a convenience wrapper for the *ByTick variant: The provided volumes are converted to a corresponding tick.
The output from the two functions is the same.
:::info
Both functions are payable and can be used to credit the maker contract's balance on Mangrove on the fly. A non-zero msg.value will allow Mangrove to credit the maker's balance prior to locking the %%provision|provision%% of the newly posted offer.
// logging new offer's data
event OfferWrite(
bytes32 indexed olKeyHash,
address indexed maker, // account that created the offer, will be called upon execution
int tick,
uint gives,
uint gasprice, // gasprice that was used to compute the offer bounty
uint gasreq,
uint id // id of the new offer
);
// `maker` balance on Mangrove (who is `msg.sender`) is debited of `amount` WEIs to provision the offer
event DebitWei(address maker, uint amount);
// `maker` balance on Mangrove is credited of `amount` WEIs if `msg.value > 0`.
event CreditWei(address maker, uint amount);
// Gatekeeping
"mgv/dead" // Mangrove contract is terminated
"mgv/inactive" // Trying to post an offer in an inactive market
// Order book has reached its maximal number of orders (2**24)
"mgv/offerIdOverflow" // Unlikely as max offer id is 2**24
// Overflow
"mgv/writeOffer/gasprice/tooBig"
"mgv/writeOffer/gives/tooBig"
// Invalid values
"mgv/writeOffer/gasreq/tooHigh" // gasreq above gasmax
"mgv/writeOffer/gives/tooLow" // gives should be greater than 0
"mgv/writeOffer/density/tooLow" // wants / (gasreq + overhead) below density
"mgv/writeOffer/tick/outOfRange"
// Insufficient provision
"mgv/insufficientProvision" // provision of `msg.sender` should cover offer bounty
import {IMangrove} from "@mgv/src/IMangrove.sol";
import "@mgv/src/core/MgvLib.sol";
// context of the call
// IMangrove mgv = IMangrove(payable(<address of Mangrove>));
// Mangrove contract
IMangrove mgv = IMangrove(payable(mgv));
// OLKey olKey = OLKey(<address of outbound token>, <address of inbound token>, <tick spacing>);
// struct containing outbound_tkn, inbound_tkn and tickSpacing
OLKey memory olKey = OLKey(address(base), address(quote), 1);
// Tick tick = TickLib.tickFromRatio(mantissa,exponent);
// ratios are represented as a (mantissa,exponent) pair which represents the number `mantissa * 2**-exponent`
// calculates the tick from a desired 1.25 ratio (1.25 = 20 * 2^(-4))
Tick tick = TickLib.tickFromRatio(20, 4);
// creates an offer at tick
mgv.newOfferByTick(olKey, tick, 1 ether, 10_000, 0);
// creates an offer at ⌈wants/tick⌉
mgv.newOfferByVolume(olKey, 1 ether, 1 ether, 10_000, 0);
Inputs
The two function only differ in one input. They both functions take these inputs:
outbound_tkn: address of the outbound token (that the offer will send).
inbound_tkn: address of the inbound token (that the offer will receive).
tick: the desired "price" tick of the offer. The actual tick of the offer will be the smallest tick offerTick > tick that satisfies offerTick % tickSpacing == 0.
wants: the amount of inbound tokens requested by the offer. Must fit in 127 bits and be strictly positive.
The ratio wants/gives (rounded down) specifies the desired "price" ratio and thus tick.
Outputs
Provisioning :
Since offers can fail, Mangrove requires each offer to be %%provisioned|provision%% in native token. If an offer fails, part of that provision will be sent to the caller that executed the offer, as compensation.
Make sure that your offer is well-provisioned before calling newOffer*, otherwise the call will fail. The easiest way to go is to send a comfortable amount of native token to Mangrove from your offer-posting contract. Mangrove will remember your balance and use it when necessary.
Offer execution :
If the offer account is a contract, it should implement the IMaker interface. At the very least, it must have a function with signature makerExecute(MgvLib.SingleOrder calldata order) or it will systematically revert when called by Mangrove.
gives and gasreq are subject to density constraints on the amount of outbound token provided per gas spent.
The offer account will need to give Mangrove a high enough allowance in outbound tokens since Mangrove will use the ERC20 standard's transferFrom function to source your tokens.
Updating an existing offer
As for newOffer, offers on Mangrove can be updated in two ways which differ in how the "price" (%%ratio|ratio%%) is specified:
updateOfferByTick: The "price" is specified as a tick.
updateOfferByVolume: The "price" is specified as the ratio between two volumes, wants/gives.
The *ByVolume variant is a convenience wrapper for the *ByTick variant: The provided volumes are converted to a corresponding tick.
The output from the two functions is the same.
Info :
Both functions are payable and can be used to credit the maker contract's balance on Mangrove on the fly. A non-zero msg.value will allow Mangrove to credit the maker's balance prior to locking the %%provision|provision%% of the updated offer.
event OfferWrite(
bytes32 indexed olKeyHash,
address indexed maker, // account that created the offer, will be called upon execution
int tick,
uint gives,
uint gasprice, // gasprice that was used to compute the offer bounty
uint gasreq,
uint id // id of the new offer
);
// if old offer bounty is insufficient to cover the update,
// `maker` is debited of `amount` WEIs to complement the bounty
event DebitWei(address maker, uint amount);
// if old offer bounty is greater than the actual bounty,
// `maker` is credited of the corresponding `amount`.
event CreditWei(address maker, uint amount);
// Gatekeeping
"mgv/dead" // Mangrove contract is terminated
"mgv/inactive" // Trying to update an offer in an inactive market
// Type error in the arguments
"mgv/writeOffer/gasprice/tooBig"
"mgv/writeOffer/gives/tooBig"
// Invalid values
"mgv/writeOffer/gasreq/tooHigh" // gasreq above gasmax
"mgv/writeOffer/gives/tooLow" // gives should be greater than 0
"mgv/writeOffer/density/tooLow" // wants / (gasreq + overhead) below density
"mgv/writeOffer/tick/outOfRange"
// Invalid caller
"mgv/updateOffer/unauthorized" // caller must be the account that created the offer
// Insufficient provision
"mgv/insufficientProvision" // provision of caller no longer covers the offer bounty
import {IMangrove} from "@mgv/src/IMangrove.sol";
import "@mgv/src/core/MgvLib.sol";
// continuing from the previous example for the creation of new offers
// context of the call:
// mgv: address of Mangrove's deployment typed as IMangrove
// olKey struct containing outbound_tkn, inbound_tkn and tickSpacing
// creates an offer at `tick` and store its ID in ofrId_1
uint ofrId_1 = mgv.newOfferByTick(olKey, tick, 1 ether, 10_000, 0);
// getting packed (outTkn, inbTkn, tickSpacing) offer list data
Offer offer_1 = mgv.offers(olKey, ofrId_1);
OfferDetail detail_1 = mgv.offerDetails(olKey, ofrId_1);
// update the offer with the "ByTick" version
mgv.updateOfferByTick(
olKey,
tick,
offer_1.gives() * 9 / 10, // decrease what offer gives by 10%
detail_1.gasreq(), // keep offer's current gasreq
detail_1.gasprice(), // keep offer's current gasprice
ofrId_1 // id of the offer to be updated
);
// retrieves the amount of wants from the tick and gives
uint wants = TickLib.inboundFromOutbound(tick, offer_1.gives());
// update the offer with the "ByVolume" version
mgv.updateOfferByVolume(
olKey,
wants, // keep what the offer wants
offer_1.gives() * 12 / 10, // increase what offer gives by 20%
detail_1.gasreq(), // keep offer's current gasreq
detail_1.gasprice(), // keep offer's current gasprice
ofrId_1 // id of the offer to be updated
);
Inputs
offerId is the %%offer id|offer-id%% of the offer to be updated.
All other parameters are the same as newOfferByTick and newOfferByVolume - see above.
Outputs
None.
Offer updater :
An offer can only be updated if msg.sender is the account that created the offer.
Reusing offers :
Retracting an offer
An offer can be withdrawn from the order book via the retractOffer function described below.
// emitted on all successful retractions
event OfferRetract(
bytes32 indexed olKeyHash,
address indexed maker,
uint id, // the id of the offer that has been removed from the offer list
bool deprovision
);
// emitted if offer is deprovisioned
event Credit(
address maker, // account being credited
uint amount // amount (in wei) being credited to the account
);
"mgv/retractOffer/unauthorized" // only the offer's Maker Contract may call.
import {IMangrove} from "@mgv/src/IMangrove.sol";
// continuing from the previous example for the creation of new offers
// context of the call:
// mgv: address of Mangrove's deployment typed as IMangrove
// olKey struct containing outbound_tkn, inbound_tkn and tickSpacing
// ofrId_1: offer identifier of the offer created in the examples for new offer creation
// retracting offer with ID = ofrId_1
mgv.retractOffer(
olKey,
ofrId_1, // id of the offer to be retracted
false // no deprovision of the offer, saves gas if one wishes to repost the offer later
);
...
Inputs
Outputs
provision: amount of native token deprovisioned for the offer (in wei).
Note :
The withdraw(amount) function can be used to withdraw the funds after deprovisioning.
tickSpacing: specifies the space between allowed ticks (see for details)
gives: the amount of outbound tokens promised by the offer. Must fit in 127 bits and be strictly positive. Must provide enough volume w.r.t. to gasreq and offer list's parameter.
gasreq: the amount of gas that will be given to the offer's account. Must fit in 24 bits and be lower than . Should be sufficient to cover all calls to the maker contract's () and ). Must be compatible with the offered volume gives and the offer list's parameter. (See also .)
gasprice: gas price override in Mwei/gas used to compute the order (see also ). Any value lower than Mangrove's current will be ignored (thus 0 means "use Mangrove's current "). Must fit in 26 bits.
offerId the of the newly created offer. Note that offer ids are scoped to , so many offers can share the same id if they belong to different offer lists.
After being executed or , an offer is moved out of the . It can still be updated and reinserted in the offer list. It is generally recommended to update offers instead of creating new ones, as it costs much less gas.
olKey: identifies the offer list, see details .
offerId: is the of the offer to be retracted.
deprovision: if true, will free the offer's so that you can them. Otherwise, will leave the provision in the offer.