Velocity ProtocolDevelopers
DevelopersTrading automationKeeper Bots

Tutorial: Order Trigger Bot

Introduction

Order Trigger Bots (Trigger Bots) are responsible for marking orders that satisfy the trigger condition, including:

  • Trigger market orders: stop market and take profit.
  • Trigger limit orders: stop limit and take profit limit.

Trigger Bots receive a small compensation for each successfully marked order.

See Keepers and the decentralized orderbook for how the decentralized orderbook (DLOB) is built and how matching incentives work.

Trigger Bots are similar to Tutorial: Order Matching Bot in that they:

  • also maintain a local copy of the decentralized orderbook (DLOB)
  • do not require the operator to manage collateral
  • receive a small reward for performing their duties.

Getting Started

The reference implementation is src/bots/trigger.ts in apps/keeper-bots-v2, inside the velocity-v1 monorepo, which is not public yet.

Set the environment variables and initialize a Velocity user account as described in Trading Automation. Then start the trigger bot:

bun run dev:trigger

Technical Explanation

The trigger bot polls the DLOB for orders whose trigger conditions are now met (e.g. oracle price crossed a stop-loss level) and submits a transaction to mark them as triggered, earning a small keeper reward.

Get nodes from the DLOB that are ready to be triggered

The DLOB tracks conditional orders (stop market, stop limit, take profit, take profit limit) and their trigger prices. findNodesToTrigger returns orders where the trigger price has crossed the trigger threshold, these are ready to be marked onchain.

The trigger price is not always the raw oracle price. It is gated on the state account's MedianTriggerPrice feature bit. While the bit is clear, the program (and getTriggerPrice) compares against the raw oracle price; while it is set, it compares against a median of the oracle price, the last fill price, and a funding-basis price instead. Read the bit from the state account rather than assuming either mode. A bot that passes the raw oracle price directly can both miss real triggers and submit triggers that revert.

const market = this.velocityClient.getPerpMarketAccount(marketIndex)!;
const oraclePriceData = this.velocityClient.getOracleDataForPerpMarket(marketIndex);

const triggerPrice = getTriggerPrice(
    market,
    oraclePriceData.price,
    new BN(Date.now() / 1000),
    useMedianTriggerPrice(this.velocityClient.getStateAccount())
);

const nodesToTrigger = this.dlob.findNodesToTrigger(
    marketIndex,
    this.slotSubscriber.getSlot(),
    triggerPrice,
    MarketType.PERP,
    this.velocityClient.getStateAccount()
);

Call getTriggerOrderIx on VelocityClient

Submit the trigger transaction for each eligible node. Once triggered onchain, the order becomes a regular order available for matching bots to fill. The transaction can still revert, for example if the market's fill operation is paused. Expect competition from other trigger bots, and handle errors gracefully and continue to the next node.

const user = this.userMap.get(nodeToTrigger.node.userAccount.toString());
const triggerIx = await this.velocityClient.getTriggerOrderIx(
    nodeToTrigger.node.userAccount,
    user.getUserAccount(),
    nodeToTrigger.node.order
);