Tutorial: Order Matching Bot
Introduction
Order Matching Bots (Matching Bots) are responsible for matching two orders that cross or a taker order against the AMM. Specifically, this includes:
-
Market Orders: Market Buy and Market Sell
-
Limit Orders: Limit Buy and Limit Sell
Matching Bots receive a small compensation for each order that they successfully fill.
See Keepers & Decentralised Orderbook for a technical explanation of how the decentralised orderbook (DLOB) and matching incentives work.
Matching Bots are similar to Tutorial: Order Trigger Bot in that they:
-
also maintain a local copy of the Decentralised Limit Orderbook (DLOB);
-
do not require the operator to manage collateral; and
-
receive a small reward for performing their duties.
Getting Started
The reference implementation of the Order Matching Bot is available here (opens in a new tab).
Follow the instructions at Keeper Bots to set the required environment variables and make sure a ClearingHouseUser
is initialized.
Start the Matching Bot:
yarn run dev:filler
Technical Explanation
1. Get nodes from the DLOB that are ready to be filled
Market orders that are sent on the Drift Protocol first go through the Just-In-Time (JIT) Auctions. After the auction period, Matching Bots step in to fill orders for a small reward.
The DLOB implementation includes a method for getting orders ready to be filled:
const market = this.clearingHouse.getMarketAccounts()[0]; // get a MarketAccount
const oraclePriceData = this.driftClient.getOracleDataForMarket(marketIndex);
const oracleIsValid = isOracleValid(
market.amm,
oraclePriceData,
this.driftClient.getStateAccount().oracleGuardRails,
this.slotSubscriber.getSlot()
);
const vAsk = calculateAskPrice(market, oraclePriceData);
const vBid = calculateBidPrice(market, oraclePriceData);
const nodesToFill = this.dlob.findNodesToFill(
marketIndex,
vBid,
vAsk,
this.slotSubscriber.getSlot(),
oracleIsValid ? oraclePriceData : undefined
);
2. Filter for Fillable Nodes
To avoid trying to fill orders that aren't ready to be filled, filter out orders that are too small to fill
if (
!nodeToFill.makerNode &&
(isVariant(nodeToFill.node.order.orderType, "limit") ||
isVariant(nodeToFill.node.order.orderType, "triggerLimit"))
) {
const baseAssetAmountMarketCanExecute =
calculateBaseAssetAmountMarketCanExecute(
market,
nodeToFill.node.order,
oraclePriceData
);
if (
baseAssetAmountMarketCanExecute.lt(market.amm.baseAssetAmountStepSize)
) {
// skip order
continue;
}
}
3. Call fill_order
on DriftClient
const user = this.userMap.get(nodeToFill.node.userAccount.toString());
const txSig = await this.driftClient.fillOrder(
nodeToFill.node.userAccount,
user.getUserAccount(),
nodeToFill.node.order,
undefined
);