# Tutorial: Order Trigger Bot

> Canonical: https://docs.velocity.exchange/developers/trading-automation/keeper-bots/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](/protocol/how-it-works/orderbook-and-keepers.md) for how the decentralized orderbook (DLOB) is built and how matching incentives work.

Trigger Bots are similar to [Tutorial: Order Matching Bot](/developers/trading-automation/keeper-bots/order-matching-bot.md) 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](/developers/trading-automation.md#running-a-reference-bot). Then start the trigger bot:

```shell copy
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.

```typescript copy
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.

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