Smart Contract API Reference
Overview
Matching Market:
Matching Market
MatchingMarket extends ExpiringMarket which, in turn, extends SimpleMarket. The base contract, SimpleMarket, provides simple storage for orders and basic order execution and settlement logic. MatchingMarket implements a sorted order book by providing additional sorting structure (double-linked list). For a given market (e.g. WETH/DAI) two separate linked-lists will be used by the matching logic, one for the "buy side" and another one for the "sell side". When a new order is created, MatchingMarket will check the opposing side of the order book and if the order can be matched, it will trigger the trade. Otherwise the order will be put in the appropriate place on the order book.
Orderbook Navigation
Each order on the OasisDEX indicates the Market Maker’s intent to sell a certain amount of tokens (pay_gem
) for a given amount of other tokens (buy_gem
). For a given market (e.g. WETH/DAI, where WETH is a base token and DAI is a quote token), a "sell-side" order book will contain orders to sell WETH with DAI as payment, whereas "buy-side" order book will contain orders to sell DAI with WETH as payment. For convenience, if we assume that WETH is a base token, sell orders are called "asks", while WETH buy orders (i.e. DAI sell orders) are called "bids".
getBestOffer(ERC20, ERC20)
This method is used to get the best sell order for a given token pair.
Given the WETH/DAI market, if WETH's address is passed as a pay_gem then the id of the best ask will be returned. If DAI's address is passed as a pay_gem then the id of the best bid will be returned.
This method is used in combination with getWorseOffer / getBetterOffer.
function getBestOffer(ERC20 pay_gem, ERC20 buy_gem) public view returns(uint)
- pay_gem: An address of an ERC20 token contract
- buy_gem: An address of an ERC20 token contract
- RETURN: Id(number) of the best offer for pay_gem:buy_gem pair.
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4export DAI=0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aaexport WETH=0xd0a1e359811322d97991e03f863a0c30c2cf029c$ seth --to-dec $(seth call $OTC_MARKET 'getBestOffer(address,address)' $WETH \$DAI)118172
getWorseOffer(uint)
This method is used to get the next offer in the sorted linked list. If the offer passed as the argument to the call is an ask, then the offer returned will have higher price.
If the current offer is a bid
, then the offer returned will have lower price.
function getWorseOffer(uint id) public view returns(uint)
- id: Id of an offer compareted to which a worse offer will be returned
- RETURN: Id(number) of a worse offer
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4export DAI=0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aaexport WETH=0xd0a1e359811322d97991e03f863a0c30c2cf029c$ BEST_OFFER_ID=$(seth --to-dec $(seth call $OTC_MARKET 'getBestOffer(address,address)' $WETH $DAI))$ seth --to-dec $(seth call $OTC_MARKET 'getWorseOffer(uint)' $BEST_OFFER_ID)13
getBetterOffer(uint)
This method is used to get the next offer in the sorted linked list. If the offer passed as the argument to the call is an ask, then the offer returned will have lower price.
If the current offer is a bid
, then the offer returned will have a higher price.
function getBetterOffer(uint id) public view returns(uint)
- id: Id of an offer compareted to which a worse offer will be returned
- RETURN: Id(number) of a better offer
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4export DAI=0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aaexport WETH=0xd0a1e359811322d97991e03f863a0c30c2cf029c$ seth --to-dec $(seth call $OTC_MARKET 'getBetterOffer(uint)' 13)118172
getOfferCount(ERC20, ERC20)
This method is used to get the amount of offers for a given side of the order book. Given the WETH/DAI market, if WETH's address is passed as a pay_gem then the number of the asks is returned. If DAI's address is passed as a pay_gem then the number of the bids is returned.
function getOfferCount(ERC20 pay_gem, ERC20 buy_gem) public view returns(uint)
- pay_gem: An address of an ERC20 token contract
- buy_gem: An address of an ERC20 token contract
- RETURN: Size(number) either of all
asks
or allbids
.
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4export DAI=0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aaexport WETH=0xd0a1e359811322d97991e03f863a0c30c2cf029c// Size of asks$ seth --to-dec $(seth call $OTC_MARKET 'getOfferCount(address,address)' $WETH $DAI)14// size of bids$ seth --to-dec $(seth call $OTC_MARKET 'getOfferCount(address,address)' $DAI $WETH)1
getOffer(uint)
This method is used to retrieve offer details for a specific offer id. Price of the order is determined by specified pay_amt and buy_amt. If the offer is an ask then price = buy_amt/pay_amt, while if the offer is a bid then price = pay_amt/buy_amt.
function getOffer(uint id) public view returns (uint, ERC20, uint, ERC20)
id: An id of the offer
RETURN: Tuple of four values (pay_amt pay_gem buy_amt buy_gem).
pay_amt - amount to sell
pay_gem - token that is sold
buy_amt - amount to pay
buy_gem - token that is used as payment
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4export DAI=0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aaexport WETH=0xd0a1e359811322d97991e03f863a0c30c2cf029c$ seth call $OTC_MARKET 'getOffer(uint)(uint256,address,uint256,address)' 11817216345785d8a0000d0a1e359811322d97991e03f863a0c30c2cf029c // 0xd0a1e359811322d97991e03f863a0c30c2cf029c - WETH addressebec21ee1da400004f96fe3b7a6cf9725f59d353f723c1bdb64ca6aa // 0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aa - DAI address$ seth --from-wei $(seth --to-dec 16345785d8a0000)0.100000000000000000$ seth --from-wei $(seth --to-dec ebec21ee1da40000)17.000000000000000000
Placing orders (Market Makers)
Note: Before making any offers, make sure to approve the contract to make transfers on your behalf.
The main method for both makers and takers is the offer
method having 3 different signatures (overloads).
offer(uint, ERC20, uint, ERC20, uint)
Offer method with five arguments is the recommended way of placing new orders.
Calling this method might have three outcomes:
- Offer is not matched and is put into the order book - caller becomes Maker
- Offer is fully matched - caller becomes Taker
- Offer is partially matched - caller is both Maker and Taker. The remainder of the unfilled offer will be placed as a new order.
When creating a new offer, funds are transferred to the contract's escrow ensuring that settlement is always possible.
function offer(uint pay_amt, ERC20 pay_gem, uint buy_amt, ERC20 buy_gem, uint pos) public can_offer returns (uint)
- pay_amt: Amount that will be sold. Number in wei units.
- pay_gem: Address of an ERC20 token contract. Token that maker sells
- buy_amt: Amount to sell. Number in wei units.
- buy_gem: Address of an ERC20 token contract. Token that maker buys
- pos: Position where to insert the new offer. 0 should be used if unknown.
- RETURN: Id of the newly created offer.
pos
parameter
pos
is the OFFER ID of the first offer that has a higher (or lower depending on whether it is bid
or ask
) price than the new offer that the caller is making.
Offers are stored in a sorted linked list and pos
is a hint to the matching engine, where should it start searching for a place to insert the new offer.
If you pass pos=0
this means that the contract will have to search the order list from the very beginning. In the worst case, this can end up using a lot of gas or even causing the transaction to revert.
Providing a pos
that is correct is challenging due to the various factors (ie. mempool frontrunning), but it's sufficient to provide pos
close to the expected value. Matching engine will iterate from that place in a sorted list to find the correct location. The closer you are from the real position, the lower the amount of gas the contract will spend.
Rounding behaviour
The default rounding behaviour will match offers that differ in the price indicated by taker by 1 WEI. If this is not desirable, then you can use the offer
function with additional parameter rounding
set to false
should be used instead.
const OTC_ABI = //otc contract abi definedconst OTC_MARKET='0xe325acb9765b02b8b418199bf9650972299235f4'const DAI='0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aa'const WETH='0xd0a1e359811322d97991e03f863a0c30c2cf029c'const provider = new ethers.providers.Web3Provider(window.ethereum)const signer = provider.getSigner()const otcContract = new ethers.Contract(OTC_MARKET, OTC_ABI, signer);const pay_amt = ethers.utils.parseUnits("2.4", 18)const pay_gem = DAIconst buy_amt = ethers.utils.parseUnits("0.2", 18)const buy_gem = WETHconst price = pay_amt/buy_amtconsole.log(price.toString()) // 12// Orderbook:// 118281 - 20// 118283 - 18// 118282 - 15// 118257 - 10// Using 118282 because it's the first offer that has higher price than the new oneconst tx = await otcContract.functions['offer(uint256,address,uint256,address,uint256)'](pay_amt, pay_gem, buy_amt, buy_gem, 118282)
offer(uint, ERC20, uint, ERC20, uint, bool)
offer
method with 6 arguments allows for explicit setting of the rounding behaviour. The default value is true
. Setting rounding
to false will not round the price of the orders.
function offer(uint pay_amt, ERC20 pay_gem, uint buy_amt, ERC20 buy_gem, uint pos, bool rounding) public can_offer returns (uint)
- pay_amt: Amount that will be sold. Number in wei units.
- pay_gem: Address of an ERC20 token contract. Token that maker sells
- buy_amt: Amount to sell. Number in wei units.
- buy_gem: Address of an ERC20 token contract. Token that maker buys
- pos: Position where to insert the new offer. 0 should be used if unknown
- rounding: Boolean value indicating whether "close enough" orders should be matched
- RETURN: Id of the newly created offer.
offer(uint, ERC20, uint, ERC20)
This method IS NOT recommended and shouldn't be used. Such an offer would not end up in the sorted list but would rather need to be inserted by a keeper at a later date. There is no guarantee that this will ever happen.
insert(uint, unint)
Inserts an unsorted offer to the sorted list. This method should only be used if an offer was created by an offer(uint, ERC20, uint, ERC20)
method that does not insert the offer into a sorted list automatically.
function insert(uint offerId, uint pos) public returns (bool)
- offerId: Id of an offer
- pos: Position where to insert the new offer. 0 should be used if unknown
- RETURN: Boolean result whether the operation was successful
Taking/Filling orders (Market Takers)
Offer method described in the Placing orders section above could be used to take/fill orders.
By placing a new order which fills completely or partially an existing one, the caller is partially or completely a market taker. The offer
method takes advantage of the automatic matching engine but it does not guarantee the caller of the method will always be a taker and their offer may end up resting on the orderbook, if it cannot be matched.
Another method that the Taker can use is the buy
method which fills a specific order (not necessarily the best one). This method can be used if the taker wants to skip the matching (which will save gas) however, there is no best price guarantee, hence it should be used with caution.
Finally, there are two additional methods (sellAllAmount
and buyAllAmount
) that provide fill or kill order semantics ie. the takers offer will be fully matched or it will revert.
buy(uint, uint)
The buy function is used to fill a specific order (“cherrypicks”). By calling this function, it will execute and settle a trade all within one atomic transaction. It will transfer funds from caller to offer maker, and from maker (escrow) to caller.
function buy(uint id, uint amount) public can_buy(id) synchronized returns (bool)
- id: An id of the offer that the send is willing to fill
- amount: Amount that the user is willing to fill. Could be partial or full amount.
- RETURN: Boolean result whether the transaction was successful
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4# Orderbook (Bids)# 118282 0.2 (WETH) 3 DAI$ seth send $OTC_MARKET 'buy(uint,uint)' 118282 $(seth --to-uint256 $(seth --to-wei 3 ether))
sellAllAmount(ERC20, uint, ERC20, uint)
This method attempts to exchange all of the pay_gem
tokens for at least the specified amount of buy_gem
tokens.
It is possible that more tokens will be bought (depending on the current state of the orderbook).
Transaction will fail if the method call determines that the caller will receive less amount than the amount specified as min_fill_amount.
function sellAllAmount(ERC20 pay_gem, uint pay_amt, ERC20 buy_gem, uint min_fill_amount) public returns (uint fill_amt)
- pay_gem: An address of an ERC20 token contract that will be sold
- pay_amt: An amount that will be sold
- buy_gem: An address of an ERC20 token contract that will be bought
- min_fill_amount - The least amount that the caller is willing to receive. If slippage happens and price declines the user might end up with less of the buy_gem. In order to avoid big losses the caller should provide this threshold.
- RETURN: - An amount that has been received in exchange for pay_amt
Example: Let's assume the bid side of the WETH/DAI orderbook:
- buy_amt: 0.2 WETH, pay_amt: 4 DAI (price: 20 DAI)
- buy_amt: 0.2 WETH, pay_amt: 3.6 DAI (price: 18 DAI)
- buy_amt: 0.6 WETH, pay_amt: 7.6 DAI (price: 16 DAI)
- buy_amt: 1.0 WETH, pay_amt: 10.0 DAI (price: 10 DAI)
If the user wanted to sell 1 WETH they would receive 15.2 DAI. To guarantee this amount, the user could specify min_fill_amount
value. As the orderbook might change before this order is mined (and executed), the additional slippage can be taken into account and min_fill_amount
value can be set to 14.44 DAI (5% slippage). This will improve the chance of the order being executed, hovever it will increase the likelihood of being front run by market making bots.
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4export DAI=0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aaexport WETH=0xd0a1e359811322d97991e03f863a0c30c2cf029c$ seth send $OTC_MARKET 'sellAllAmount(address,uint,address,uint)' $WETH $(seth --to-uint256 $(seth --to-wei 1 ether)) $DAI $(seth --to-uint256 $(seth --to-wei 17.2 ether))
buyAllAmount(ERC20, uint, ERC20, uint)
This method attempts to exchange at most specified amount of pay_gem
tokens for a specified amount of buy_gem
tokens.
It is possible that less tokens will be spent (depending on the current state of the orderbook).
Transaction will fail if the method call determines that the caller will pay more than the amount specified as max_fill_amount
.
function buyAllAmount(ERC20 buy_gem, uint buy_amt, ERC20 pay_gem, uint max_fill_amount) public returns (uint fill_amt)
- buy_gem: An address of an ERC20 token contract that will be bought
- buy_amt: An amount that will be bought
- pay_gem: An address of an ERC20 token contract that will be sold
- max_fill_amount - The most amount that the caller is willing to pay. If slippage happens and price increases the user might end up with paying more of the pay_gem. In order to avoid big losses the caller should provide this threshold.
- RETURN: - An amount that has been paid for buy_amt
Example: Let's assume this ask side of the WETH/DAI orderbook:
- pay_amt: 1.0 WETH, buy_amt: 30 DAI (price: 30 DAI)
- pay_amt: 0.6 WETH, buy_amt: 15.6 DAI (price: 26 DAI)
- pay_amt: 0.2 WETH, buy_amt: 4.8 DAI (price: 24 DAI)
- pay_amt: 0.2 WETH, buy_amt: 4.4 DAI (price: 22 DAI)
If the user wanted to buy exactly 1 WETH they would have to pay 24.8 DAI. To guarantee this amount, the user could specify max_fill_amount
value. As the orderbook might change before this order is mined (and executed), the additional slippage can be taken into account and max_fill_amount
value can be set to 26.04 DAI (5% slippage). This will improve the chance of the order being executed, hovever it will increase the likelihood of being front run by market making bots.
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4export DAI=0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aaexport WETH=0xd0a1e359811322d97991e03f863a0c30c2cf029c$ seth send $OTC_MARKET 'buyAllAmount(address,uint,address,uint)' $WETH $(seth --to-uint256 $(seth --to-wei 1 ether)) $DAI $(seth --to-uint256 $(seth --to-wei 25.83 ether))
getPayAmount(ERC20, uint, ERC20, uint)
This function returns the amount of pay_gem
tokens that need to be spent to buy a specified amount of buy_gem
. It can be used to check the current state of the orderbook.
function getPayAmount(ERC20 pay_gem, ERC20 buy_gem, uint buy_amt) public view returns (uint fill_amt)
- pay_gem: An address of ERC20 token contract that will be sold
- buy_gem: An address of ERC20 token contract that will be bought
- buy_amt: Amount that the caller wants to receive
- RETURN: Amount of apy_gem that the caller is going to pay for receiving buy_amt of buy_gem. If there are not enough orders to fill the amount it will fail
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4export DAI=0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aaexport WETH=0xd0a1e359811322d97991e03f863a0c30c2cf029c$ seth --from-wei $(seth --to-dec $(seth call $OTC_MARKET 'getPayAmount(address,address,uint)' $DAI $WETH $(seth --to-uint256 $(seth --to-wei 1 ether))))24.800000000000000000
getBuyAmount(ERC20, uint, ERC20, uint)
This function returns the amount of buy_gem
tokens if pay_gem
tokens are spent. It can be used to check the current state of the orderbook.
function getBuyAmount(ERC20 buy_gem, ERC20 pay_gem, uint pay_amt) public view returns (uint fill_amt)
- buy_gem: An address of ERC20 token contract that will be bought
- pay_gem: An address of ERC20 token contract that will be sold
- pay_amt: Amount that the caller is willing to pay
- RETURN: Amount of buy_gem that the caller is going to receive for paying pay_amt of pay_gem. If there are not enough orders to fill the amount it will fail
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4export DAI=0x4f96fe3b7a6cf9725f59d353f723c1bdb64ca6aaexport WETH=0xd0a1e359811322d97991e03f863a0c30c2cf029c$ seth --from-wei $(seth --to-dec $(seth call $OTC_MARKET 'getBuyAmount(address,address,uint)' $DAI $WETH $(seth --to-uint256 $(seth --to-wei 1 ether))))17.200000000000000000
Offer management
Methods that can be called by the owner of the offer.
cancel(uint)
This method is used to cancel an offer created in the past by the caller. It will transfer funds from the escrow back to the caller.
function cancel(uint id) public can_cancel(id) synchronized returns (bool success)
- id: An id of the offer that needs to be cancelled
- RETURN: Boolean result whether the transaction was successful
export OTC_MARKET=0xe325acb9765b02b8b418199bf9650972299235f4$ seth send $OTC_MARKET 'cancel(uint)' 118283