Post a Smart Offer
- The tutorial assumes knowledge of solidity development.
- Follow preparation to create a new
- Open your favorite solidity editor inside that folder.
- It is assumed that the
ADMIN_ADDRESShas 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.
Start by creating 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.
Next, add the contract and the code for the constructor.
Note: we also implement the
ILiquidityProviderinterface which makes the contract compatible with what the SDK expects.
Add offer management functions
The abstract contract
Direct has internal functions that allows one to manage offers:
_newOffer for posting offers,
_updateOffer for updating existing offers and
_retractOffer for unpublishing offers from Mangrove. We need to expose these functions in a restricted manner, so that only the administrator of the contract can manage offers. We expose them through functions matching the
See OfferArgs for an explanation of the parameters for posting an offer.
Add the below code to your contract.
Emit in Posthook
When using our new contract, we can inspect traces and addresses but illustrative purposes, let's insert the following to emit an event in the posthook when the offer is successfully taken.
The contract is now complete - you can see the full example by following the links to github.
But before deploying it on-chain, we should test it!
First, compile the contract:
Start local node
Before proceeding, import the environment variables made as part of the preparation
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
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).
You can also add it to your
export MANGROVE=<contract address> # 0xabcd....
forge create "OfferMakerTutorial" --private-key "$PRIVATE_KEY" --rpc-url $LOCAL_URL --constructor-args "$MANGROVE" "$ADMIN_ADDRESS"
Note: you might need to add the
The output should look like:
No files changed, compilation skipped
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 WBTC/USDT market.
Make sure to set variables with the tokens address into your
Note: the example token addresses are for the Polygon Mumbai testnet.
We have to let Mangrove pull the outbound token from our new contract - we can use the
activate function for that.
cast send --rpc-url $LOCAL_URL "$OFFER_MAKER" "activate(address)" "[$WBTC]" --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
In our example, we are offering 1 WBTC (outbound) in exchange for 26,000 USDT (inbound).
cast send --rpc-url $LOCAL_URL "$OFFER_MAKER" "newOffer(address, address, uint, uint, uint, uint)(uint)" "$WBTC" "$USDT" 100000000 26000000000 0 100000 --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
cast run <transactionHash>
Which would output the following tail:
└─ ← 0x0000000000000000000000000000000000000000000000000000000000000235
Transaction successfully executed.
Gas used: 168639
0x0000000000000000000000000000000000000000000000000000000000000235 is the offer id.
If the offer was now taken, it will fail to deliver the promised liquidity. It promises up to 1 WBTC, but the contract has no WBTC to deliver. We can fix this by sending some WBTC to the contract:
cast send --rpc-url $LOCAL_URL "$WBTC" "transfer(address,uint)" "$OFFER_MAKER" 100000000 --private-key "$PRIVATE_KEY"
If you do not have the liquidity, then see mint below.
One of the big benefits of Mangrove is that liquidity does not have to be locked in - we will have a look at that in the Unlocking Liquidity guide.
If the admin (acting as a maker) does not have required WBTC tokens then the smart offer will fail when taken.
Note: this true in this particular case where we need to lock liquidity in our contract - that's how we designed it. Using a router, you can unlock your funds, and your offer could still be posted - your smart offer can source liquidity elsewhere on-chain.
If you don't have any WBTC you can use this to mint some tokens:
cast send --rpc-url $LOCAL_URL "$WBTC" "mint(uint)" 500000000 --private-key "$PRIVATE_KEY"
If the admin acts as taker and takes the offer (see snipe guide), you will also need USDT.
cast send --rpc-url $LOCAL_URL "$USDT" "mint(uint)" 100000000000 --private-key "$PRIVATE_KEY"
Update an offer
In a similar fashion, we can make use of the
updateOffer function inside our contract. We will need the offer ID from earlier - let's add it in a variable:
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
Now, we can update our offer, for example by changing the amount of USDT we want to 25,000:
cast send --rpc-url $LOCAL_URL "$OFFER_MAKER" "updateOffer(address, address, uint, uint, uint, uint, uint)(uint)" "$WBTC" "$USDT" 100000000 25000000000 0 "$OFFER_ID" 100000 --private-key "$PRIVATE_KEY" --value 0.01ether
Retract an offer
We can also remove our offer from the book, using
retractOffer. Note that we don't need to provide a provision in this case, since we are pulling the offer off the market. We will actually get back our provision with that configuration.
cast send --rpc-url $LOCAL_URL "$OFFER_MAKER" "retractOffer(address, address, uint, bool)(uint)" "$WBTC" "$USDT" "$OFFER_ID" 1 --private-key "$PRIVATE_KEY"
You could publish the contract on mainnet by stopping Anvil and replacing the
--rpc-url $LOCAL_URLin the above
--rpc-url $RPC_URL- and finally, the
newOfferwith sensible prices.
You could try taking your own offer.
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.