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 .
Follow Keeper Bots to set required environment variables and initialize a Drift user account.
Start the Matching Bot:
yarn run dev:fillerTechnical Explanation
The matching bot runs a continuous loop: fetch fillable orders from the DLOB, filter out non-actionable ones, and submit fill transactions to earn keeper rewards.
Get nodes from the DLOB that are ready to be filled
Market orders first go through JIT Auctions. After the auction period ends, they become available for matching bots to fill. The DLOB exposes a findNodesToFill method that returns these eligible orders, using the current virtual bid/ask and oracle price to determine which orders can be matched.
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
);Filter for fillable nodes
Not every node returned is profitable to attempt. Filter out limit orders that are too small for the AMM to execute — submitting a fill for these would waste transaction fees and fail on-chain.
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;
}
}Call fill_order on DriftClient
Submit the fill transaction. On success, the keeper earns a small reward. Expect occasional failures from competing bots filling the same order — handle errors gracefully and continue to the next node.
const user = this.userMap.get(nodeToFill.node.userAccount.toString());
const txSig = await this.driftClient.fillOrder(
nodeToFill.node.userAccount,
user.getUserAccount(),
nodeToFill.node.order,
undefined
);