Creating & Updating offers
Posting a new offerβ
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 - as opposed to on-the-fly offers made from EOA's.
Offers on Mangrove can be posted in two ways which differ in how the "price" (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.
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 of the newly posted offer.
- Signature
- Events
- Revert strings
- Solidity
function newOfferByTick(
OLKey memory olKey,
Tick tick,
uint gives,
uint gasreq,
uint gasprice
) external payable returns (uint offerId);
function newOfferByVolume(
OLKey memory olKey,
uint wants,
uint gives,
uint gasreq,
uint gasprice
) external payable returns (uint offerId);
// 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:
olKey
: identifies the offer list and consists of:outbound_tkn
: address of the outbound token (that the offer will send).inbound_tkn
: address of the inbound token (that the offer will receive).tickSpacing
: specifies the space between allowed ticks (see Ticks, ratios, and prices 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. togasreq
and offer list's density parameter.gasreq
: the amount of gas that will be given to the offer's account. Must fit in 24 bits and be lower thangasmax
. Should be sufficient to cover all calls to the maker contract's offer logic (makerExecute
) andmakerPosthook
). Must be compatible with the offered volumegives
and the offer list's density parameter. (See also gasreq.)gasprice
: gas price override in Mwei/gas used to compute the order provision (see also offer bounties). Any value lower than Mangrove's current gasprice will be ignored (thus 0 means "use Mangrove's current gasprice"). Must fit in 26 bits.
newOfferByTick(olKey, tick, gives, gasreq, gasprice)
β
tick
: the desired "price" tick of the offer. The actual tick of the offer will be the smallest tickofferTick > tick
that satisfiesofferTick % tickSpacing == 0
.
newOfferByVolume(olKey, wants, gives, gasreq, gasprice)
β
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.
- The ratio
Outputsβ
offerId
the id of the newly created offer. Note that offer ids are scoped to offer lists, so many offers can share the same id if they belong to different offer lists.
Since offers can fail, Mangrove requires each offer to be provisioned 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.
- 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
andgasreq
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) 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.
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 of the updated offer.
- Signature
- Events
- Revert strings
- Solidity
function updateOfferByTick(
OLKey memory olKey,
Tick tick,
uint gives,
uint gasreq,
uint gasprice,
uint offerId
) external payable;
function updateOfferByVolume(
OLKey memory olKey,
uint wants,
uint gives,
uint gasreq,
uint gasprice,
uint offerId
) external payable;
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 of the offer to be updated.- All other parameters are the same as
newOfferByTick
andnewOfferByVolume
- see above.
Outputsβ
None.
An offer can only be updated if msg.sender
is the account that created the offer.
After being executed or retracted, an offer is moved out of the offer list. 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.
Retracting an offerβ
An offer can be withdrawn from the order book via the retractOffer
function described below.
- Signature
- Events
- Revert strings
- Solidity
function retractOffer(
OLKey memory olKey,
uint offerId,
bool deprovision
) external returns (uint provision);
// 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β
olKey
: identifies the offer list, see details above.offerId
: is the id of the offer to be retracted.deprovision
: iftrue
, will free the offer's provision so that you can withdraw them. Otherwise, will leave the provision in the offer.
Outputsβ
provision
: amount of native token deprovisioned for the offer (in wei).
The withdraw(amount)
function can be used to withdraw the funds after deprovisioning.