Skip to main content

Post a Smart Offer

In this tutorial you will learn how to post a smart offer managed by your own maker contract and which simply transfers tokens to and from your reserve.


The tutorial assumes knowledge of solidity development. Follow preparation to create a new tutorial folder.

Open your favorite solidity editor inside that folder.

It is assumed that the ADMIN_ADDRESS has enough native tokens to complete the steps.

Simple maker contract (offer logic)

We want to create a new contract OfferMakerTutorial to implement offer logic and utilize the Direct contract in our strat-library for this purpose. Direct provides a safety harness to make it easier to correctly interact with Mangrove, you can read more about it here.

Create a new OfferMakerTutorial.sol file in the src folder and add the following pieces.


Add the imports we are going to need, along with a standard solidity preamble.



Add the contract and the code for the constructor. We will skip some details here, which you can read more about later; routers, gas requirements, and deployment scripts. Note, we also implement the ILiquidityProvider interface which makes the contract compatible with what the SDK expects.


Add offer posting function

The abstract contract Direct has an internal function _newOffer for posting offers on Mangrove. We need to expose this, so that we can post offers using our contract. We expose it through functions matching the ILiquidityProvider interface.

See OfferArgs for an explanation of the parameters for posting an offer.

Also see provision, gasreq, and pivotId, and offer list.


To complete the ILiquidityProvder interface we also need to provide an updateOffer like so (details are not covered in this tutorial, but see Update Your Offer in the SDK for the convenience it provides):


Emit in Posthook

When using our new contract we can inspect traces and addresses, but for illustrative purposes insert the following to emit an event in the posthook when the offer is successfully taken.


There are more hooks to enable the Mangrovian abilities of last look and more advanced reactive liquidity.

Local test

The contract is now complete - you can see the full example by following the links to github.

Before deploying it on-chain we should test it.


First, compile the contract:

forge build

Start local node

Before proceeding, import the environment variables made as part of the preparation

source .env

Start Foundry's local node anvil to test things locally before broadcasting to the real chain, with $RPC_URL coming from .env and pointing, for instance, to the Polygon Mumbai testnet.

anvil --fork-url $RPC_URL

Create contract on chain

Start another terminal and import environment variables again

source .env

Now, create the OfferMakerTutorial contract on the anvil node with your private key by pointing to its local rpc-url, and supplying the parameters for Mangrove core contract (get it from Addresses for the network you have forked)

export MANGROVE=<contract address> # 0xabcd.... 
forge create "OfferMakerTutorial" --private-key "$PRIVATE_KEY" --rpc-url $LOCAL_URL --constructor-args "$MANGROVE" "$ADMIN_ADDRESS"

The output should look like

[] Compiling...
No files changed, compilation skipped
Deployer: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
Deployed to: 0x5deA11A74e15BBfD6F29fd54F0b6F251F4774556
Transaction hash: 0xcfbe7503081ba9a749ee30fac8c40bfe19e0a5da665578ed00f40ce72694ca06

Take a note of the Deployed to address and save it in a variable:

export OFFER_MAKER=<contract address> # 0xabcd..., the address of the newly deployed contract

Activate the contract

In this tutorial we will use the WETH, DAI market (the example token addresses are for the Polygon Mumbai testnet).

We have to let Mangrove pull the outbound token from our new contract, and we can use the activate function for that.

export WETH=0x63e537a69b3f5b03f4f46c5765c82861bd874b6e
cast send --rpc-url $LOCAL_URL "$OFFER_MAKER" "activate(address[])" "[$WETH]" --private-key "$PRIVATE_KEY"

Post an offer

Now that the contract is ready, we can use it to post an offer - note that we have to provision the offer, and we do that by sending some native tokens to newOffer.

export DAI=0xc87385b5e62099f92d490750fcd6c901a524bbca
cast send --rpc-url $LOCAL_URL "$OFFER_MAKER" "newOffer(address, address, uint, uint, uint)(uint)" "$WETH" "$DAI" 1000000000000000000 1700000000000000000000 0 --private-key "$PRIVATE_KEY" --value 0.01ether

Instead of trying to parse the logs, we can make a note of the transactionHash at the end of the output and use local execution to see the offerId returned by newOffer.

cast run <transactionHash>

Which would output the following tail:

└─ ← 0x0000000000000000000000000000000000000000000000000000000000000235

Transaction successfully executed.
Gas used: 168639

0x0000000000000000000000000000000000000000000000000000000000000235 is the offer id.

Unlocked liquidity (reactive liquidity)

Notice that the offer was posted without transferring tokens from the admin to Mangrove or the OfferMakerTutorial. This way the tokens are pulled just-in-time when the offer is taken and can thus be made available for other purposes.

For this to work, we need to let the OfferMakerTutorial pull funds from the admin's reserve of WETH.

cast send --rpc-url $LOCAL_URL "$WETH" "approve(address, uint)" "$OFFER_MAKER" 1000000000000000000 --private-key "$PRIVATE_KEY"

Alternatively, the admin could transfer tokens to the contract and lock them until the offer is taken or the tokens are withdrawn.

The OfferMakerTutorial uses the approval to transfer funds from the admin, but this could also involve a router and require additional approvals depending on the scenario. See approvals for more details.


A brief side step - if the admin (acting as a maker) does not have required WETH tokens then the smart offer will fail when taken (but note that it could still be posted - a smart offer can source liquidity on-chain).

If you don't have any WETH you can use this to mint some tokens:

cast send --rpc-url $LOCAL_URL "$WETH" "mint(uint)" 1000000000000000000000000000000 --private-key "$PRIVATE_KEY"

Shortly the admin will act as taker and take the offer, and therefore DAI are also needed.

cast send --rpc-url $LOCAL_URL "$DAI" "mint(uint)" 1000000000000000000000000000000 --private-key "$PRIVATE_KEY"

Snipe the offer

Before being able to take an offer we need to approve Mangrove to pull funds from the taker's DAI reserve:

cast send --rpc-url $LOCAL_URL "$DAI" "approve(address, uint)" "$MANGROVE" 1700000000000000000000 --private-key "$PRIVATE_KEY"

Now we ensure that we have set everything up correctly for the offer to be successfully taken. We use Mangrove's snipes functionality to ensure it is exactly our own posted offer that we take, and not some other one in the order book. Takers would normally make market orders instead.

export OFFER_ID_HEX=<0xabcd...> # the hexadecimal offer ID captured when posting the offer
export OFFER_ID=$(($OFFER_ID_HEX)) # the decimal ID of the offer captured above
cast send --rpc-url $LOCAL_URL "$MANGROVE" "snipes(address, address, uint[4][], bool)" "$WETH" "$DAI" "[[$OFFER_ID,1000000000000000000,1700000000000000000000,100000000000000000]]" 1 --private-key "$PRIVATE_KEY"

Now, run the resulting transaction via

cast run <transactionHash>

Towards the end of the trace you can find the function of makerPosthook being called and emits an event with the data 42 as specified in the __posthookSuccess__ of your new OfferMakerTutorial contract.

   ├─ [8280] 0xabc...::makerPosthook(...) 
│ ├─ emit topic 0: 0xefg...
│ │ data: 0x000000000000000000000000000000000000000000000000000000000000002a

Next steps

The next step could be to publish the contract on mainnet by stopping Anvil and replacing the --rpc-url $LOCAL_URL in the above create, activate, and approve commands with --rpc-url $RPC_URL - and finally, the newOffer with sensible prices.

To get a view of the order book, the Mangrove UI can be used, or you can use the SDK.

To get a better understanding of how tokens flow between taker, maker, Mangrove, and maker contracts like OfferMakerTutorial, see Mangrove Offer.


If you get errors when taking the offer then it is probably missing approvals or (native) tokens.

  • mgv/takerTransferFail - missing DAI tokens or approval of transfer of DAI from taker to Mangrove.
  • No __posthookSuccess__ but makerExecute is red in the trace - missing WETH tokens of approval of transfer of WETH from maker to Mangrove.