Skip to main content
Liquidation bots monitor accounts and liquidate undercollateralized positions to earn fees.

Overview

Liquidation bots:
  1. Monitor all user accounts
  2. Identify accounts below maintenance margin
  3. Execute liquidations to earn fees
  4. Help maintain protocol solvency

Basic Liquidation Bot

liquidation-bot.ts
import {
  DriftClient,
  BulkAccountLoader,
  User,
  MarketType,
  SpotBalanceType,
  convertToNumber,
  QUOTE_PRECISION,
  BN,
} from '@drift-labs/sdk';
import { PublicKey } from '@solana/web3.js';

class LiquidationBot {
  private driftClient: DriftClient;
  private userMap: Map<string, User> = new Map();

  constructor(driftClient: DriftClient) {
    this.driftClient = driftClient;
  }

  async run() {
    console.log('Liquidation bot starting...');

    // Load all user accounts
    await this.loadUserAccounts();

    // Check for liquidations every 10 seconds
    setInterval(async () => {
      await this.checkLiquidations();
    }, 10000);
  }

  async loadUserAccounts() {
    console.log('Loading user accounts...');

    // Get all user account public keys
    const userAccounts = await this.driftClient.program.account.user.all();

    console.log(`Found ${userAccounts.length} user accounts`);

    // Create User instances for monitoring
    for (const account of userAccounts.slice(0, 100)) { // Limit for example
      const user = new User({
        driftClient: this.driftClient,
        userAccountPublicKey: account.publicKey,
        accountSubscription: {
          type: 'polling',
          accountLoader: this.driftClient.accountSubscriber.accountLoader,
        },
      });

      await user.subscribe();
      this.userMap.set(account.publicKey.toBase58(), user);
    }

    console.log(`Monitoring ${this.userMap.size} accounts`);
  }

  async checkLiquidations() {
    console.log('\nChecking for liquidations...');

    for (const [pubkey, user] of this.userMap) {
      try {
        await user.fetchAccounts();

        // Check if account can be liquidated
        const canBeLiquidated = user.canBeLiquidated();

        if (canBeLiquidated) {
          console.log(`\n🚨 Found liquidatable account: ${pubkey}`);

          // Get account details
          const totalCollateral = user.getTotalCollateral();
          const marginRequirement = user.getMaintenanceMarginRequirement();
          const health = user.getHealth();

          console.log('  Collateral:', convertToNumber(totalCollateral, QUOTE_PRECISION));
          console.log('  Margin req:', convertToNumber(marginRequirement, QUOTE_PRECISION));
          console.log('  Health:', health.toString());

          // Attempt liquidation
          await this.liquidateUser(user);
        }
      } catch (error) {
        console.error(`Error checking ${pubkey}:`, error.message);
      }
    }
  }

  async liquidateUser(user: User) {
    const userAccount = user.getUserAccount();

    // Liquidate perp positions
    const perpPositions = user.getActivePerpPositions();
    
    for (const position of perpPositions) {
      try {
        console.log(`  Liquidating perp position ${position.marketIndex}...`);

        const txSig = await this.driftClient.liquidatePerp(
          userAccount.authority,
          userAccount,
          position.marketIndex,
          position.baseAssetAmount.abs() // Max amount
        );

        console.log(`  Liquidated perp: ${txSig}`);
      } catch (error) {
        console.error(`  Failed to liquidate perp:`, error.message);
      }
    }

    // Liquidate spot positions
    const spotPositions = user.getActiveSpotPositions();
    
    for (const position of spotPositions) {
      if (position.balanceType === SpotBalanceType.BORROW) {
        try {
          console.log(`  Liquidating spot borrow ${position.marketIndex}...`);

          const txSig = await this.driftClient.liquidateBorrowForPerpPnl(
            userAccount.authority,
            userAccount,
            position.marketIndex,
            0, // USDC market
            user.getTokenAmount(position.marketIndex).abs()
          );

          console.log(`  Liquidated spot: ${txSig}`);
        } catch (error) {
          console.error(`  Failed to liquidate spot:`, error.message);
        }
      }
    }
  }
}

// Run the bot
async function main() {
  // Initialize DriftClient (see guides/initialization for setup)
  const driftClient = /* initialize your DriftClient */ ;
  
  const bot = new LiquidationBot(driftClient);
  await bot.run();
}

main().catch(console.error);

Key Concepts

Accounts are liquidatable when:
  • Total collateral < Maintenance margin requirement
  • Account health < 0
Liquidators earn a percentage of the liquidated position as a fee.
The protocol uses partial liquidations to minimize impact.
When liquidations result in bad debt, the insurance fund covers losses.

Production Considerations

Efficient Account Monitoring

// Use WebSocket or gRPC for real-time updates
const driftClient = new DriftClient({
  // ...
  accountSubscription: {
    type: 'websocket',
  },
});

// Listen for account updates
driftClient.eventEmitter.on('userAccountUpdate', async (account) => {
  // Check if liquidatable
});

Priority Fees

Liquidations are competitive. Use priority fees to win:
import { ComputeBudgetProgram } from '@solana/web3.js';

// Add priority fee
const priorityFee = ComputeBudgetProgram.setComputeUnitPrice({
  microLamports: 100_000,
});

Risk Management

  • Collateral: Maintain sufficient collateral for liquidations
  • Gas costs: Monitor profitability vs gas costs
  • Competition: Be prepared for failed transactions
Running liquidation bots requires capital and technical expertise. Test thoroughly on devnet first.

Next Steps

Keeper Bots Repo

Production keeper implementations

Liquidation Concepts

Understanding liquidations

Build docs developers (and LLMs) love